11 KiB
ADR-077: Novel RF Sensing Applications
Status: Accepted
Date: 2026-04-02
Authors: ruv
Depends on: ADR-018 (CSI binary protocol), ADR-073 (multifrequency mesh scan), ADR-075 (MinCut person separation), ADR-076 (CSI spectrogram embeddings)
Context
The existing ESP32 CSI + Cognitum Seed infrastructure collects rich multi-modal data:
- 2 ESP32-S3 nodes streaming CSI at ~22 fps each (64-128 subcarriers, channel hopping ch 1/3/5/6/9/11)
- Vitals extraction: breathing rate, heart rate, motion energy, presence score (1 Hz per node)
- 8-dimensional feature vectors per frame
- Cognitum Seed with BME280 (temp/humidity/pressure), PIR, reed switch, vibration sensor
No new hardware is required. All 6 applications below derive novel insights from data already being collected via the ADR-018 binary protocol over UDP port 5006.
Decision
Implement 6 novel RF sensing applications as standalone Node.js scripts that process live UDP or replayed .csi.jsonl recordings.
Application 1: Sleep Quality Monitoring
Input
Breathing rate (BR) and heart rate (HR) time series from vitals packets (0xC5110002), sampled at ~1 Hz per node over 6-8 hours.
Algorithm
Sliding window analysis (5-minute windows, 1-minute stride) classifying sleep stages:
| Stage | BR (BPM) | BR Variance | HR Pattern | Motion |
|---|---|---|---|---|
| Deep (N3) | 6-12 | Very low (<2.0) | Slow, regular | None |
| Light (N1/N2) | 12-18 | Moderate (2.0-8.0) | Normal | Minimal |
| REM | 15-25 | High (>8.0), irregular | Elevated | Eyes only (low CSI motion) |
| Awake | >18 or <6 | Any | Variable | Moderate-high |
Each 5-minute window is scored by:
- Compute BR mean and variance within the window
- Compute HR mean and coefficient of variation (CV)
- Compute motion energy mean (from vitals
motion_energyfield) - Classify stage using threshold hierarchy: Awake > REM > Light > Deep
Output
- Real-time sleep stage classification
- ASCII hypnogram (time vs. stage)
- Summary: total sleep time, sleep efficiency (TST / time in bed), time per stage
- Optional JSON for health app integration
Validation
Overnight recording (overnight-1775217646.csi.jsonl, 113k frames, ~40 min) should show:
- Transition from active (awake) to resting states
- Decreased motion energy over time
- BR stabilization in sleeping segments
Clinical Relevance
Consumer-grade sleep tracking without wearables. RF-based sensing avoids compliance issues (forgotten wristbands, dead batteries). Not diagnostic; informational only.
Application 2: Breathing Disorder Screening (Apnea Detection)
Input
Breathing rate time series from vitals packets at ~1 Hz.
Algorithm
Detect respiratory events in the BR time series:
| Event | Definition | Duration |
|---|---|---|
| Apnea | BR drops below 3 BPM (effective cessation) | >= 10 seconds |
| Hypopnea | BR drops > 50% from 5-min rolling baseline | >= 10 seconds |
Scoring:
- Maintain 5-minute rolling baseline BR (exponential moving average)
- Flag apnea when BR < 3 BPM for >= 10 consecutive seconds
- Flag hypopnea when BR < 50% of baseline for >= 10 consecutive seconds
- Compute AHI (Apnea-Hypopnea Index) = total events / hours monitored
| AHI | Severity |
|---|---|
| < 5 | Normal |
| 5-15 | Mild |
| 15-30 | Moderate |
| > 30 | Severe |
Output
- Per-event log: type (apnea/hypopnea), start time, duration, BR during event
- Hourly AHI and overall AHI
- Severity classification
- Alert on severe events (consecutive apneas > 30s)
Clinical Relevance
Pre-screening tool for obstructive sleep apnea (OSA). Provides motivation for clinical polysomnography referral. Not a diagnostic device; informational pre-screen only.
Application 3: Emotional State / Stress Detection
Input
Heart rate time series from vitals packets at ~1 Hz.
Algorithm
Heart Rate Variability (HRV) analysis:
-
RMSSD (Root Mean Square of Successive Differences):
- Compute successive HR differences within 5-minute windows
- RMSSD = sqrt(mean(diff^2))
- High RMSSD = high vagal tone = relaxed
- Low RMSSD = sympathetic dominance = stressed
-
LF/HF Ratio (via FFT on 5-minute HR windows):
- LF band: 0.04-0.15 Hz (sympathetic + parasympathetic)
- HF band: 0.15-0.40 Hz (parasympathetic)
- High LF/HF (> 2.0) = stressed
- Low LF/HF (< 1.0) = relaxed
-
Stress Score (0-100):
score = 50 * (1 - RMSSD_norm) + 50 * LF_HF_norm- Where
RMSSD_norm= RMSSD / max_expected_RMSSD (capped at 1.0) - And
LF_HF_norm= min(LF_HF / 4.0, 1.0)
Output
- Real-time stress score (0-100)
- RMSSD and LF/HF ratio per window
- ASCII trend chart over hours
- Activity context correlation (motion level vs. stress)
Validation
- Periods of activity (walking, working) should correlate with higher stress scores
- Quiet rest should show lower scores
- Sleeping should show lowest scores (high HRV, low LF/HF)
Application 4: Gait Analysis / Movement Disorder Detection
Input
- Motion energy time series from vitals packets
- CSI phase variance from raw CSI frames (0xC5110001)
- Cross-node RSSI from vitals packets
Algorithm
-
Cadence Extraction: FFT on motion_energy within 5-second sliding windows
- Walking cadence: dominant frequency 0.8-2.0 Hz (normal: ~1.0 Hz = 120 steps/min)
- Running: > 2.0 Hz
- Stationary: no dominant peak
-
Stride Regularity: Autocorrelation of motion_energy
- Regular walking: strong autocorrelation peak at step period
- Irregularity score = 1 - (peak_height / baseline)
-
Asymmetry Detection: Compare motion energy oscillation between two ESP32 nodes
- Symmetric gait: both nodes see similar oscillation period and amplitude
- Asymmetry index = |period_node1 - period_node2| / mean_period
-
Tremor Detection: High-frequency phase variance analysis
- Compute phase variance per subcarrier in 2-second windows
- Tremor band: 3-8 Hz component in phase variance time series
- Parkinsonian tremor: 4-6 Hz, resting
- Essential tremor: 5-8 Hz, action
Output
- Cadence (steps/min)
- Stride regularity score (0-1)
- Asymmetry index (0 = symmetric, 1 = highly asymmetric)
- Tremor score and dominant frequency
- Walking vs. stationary classification
Validation
Overnight data should show clear stationary periods with no cadence detected. Any walking segments should show cadence in the 0.8-2.0 Hz range.
Application 5: Material/Object Change Detection
Input
Per-subcarrier amplitude from raw CSI frames (0xC5110001).
Algorithm
-
Baseline Establishment (first 10 minutes or configurable):
- Record mean amplitude per subcarrier (Welford online mean)
- Record null pattern: which subcarriers are below null threshold (amplitude < 2.0)
-
Change Detection (sliding 30-second windows):
- Compare current null pattern to baseline
- New nulls appearing = new metal object blocking RF path
- Existing nulls disappearing = metal object removed
- Null position shifted = object moved
- Amplitude change without null change = non-metal material (wood, water, glass)
-
Material Classification heuristic:
- Metal: sharp null (amplitude drops to near 0 on specific subcarriers)
- Water/human: broad amplitude reduction across many subcarriers
- Wood/plastic: minimal amplitude change, mostly phase shift
- Glass: frequency-selective (affects higher subcarriers more)
Output
- Change events with timestamp, type (add/remove/move), affected subcarrier range
- Estimated material category
- Null pattern delta visualization (ASCII)
- Event timeline for monitoring
Validation
Overnight data has 19% null baseline. Changes in null pattern over the recording period indicate environment changes (doors opening/closing, person entering/leaving).
Application 6: Room Environment Fingerprinting
Input
- 8-dimensional feature vectors from feature packets (0xC5110003)
- Motion energy and presence score from vitals packets
Algorithm
-
Online Clustering using running k-means (k=5, updateable centroids):
- Each incoming 8-dim feature vector is assigned to nearest centroid
- Centroid updated via exponential moving average (alpha=0.01)
- New cluster created if distance to all centroids exceeds threshold
-
State Labeling (heuristic from vitals correlation):
- Cluster with lowest motion_energy = "empty/sleeping"
- Cluster with highest motion_energy = "active/walking"
- Intermediate clusters = "resting", "working", "transitional"
-
Transition Tracking:
- Build state transition matrix (from_state -> to_state counts)
- Detect anomalous transitions (rare in historical data)
-
Daily Profile:
- Aggregate state durations per hour
- Compare across days for routine detection
Output
- Current room state and confidence
- State timeline (ASCII)
- Transition matrix
- Daily pattern profile
- Anomaly score (deviation from established daily pattern)
Validation
Overnight recording should show 2-3 stable clusters corresponding to activity periods at different times. Transitions should be infrequent and correspond to real behavioral changes.
Implementation
All scripts share common infrastructure:
- ADR-018 binary packet parsing (same as rf-scan.js, mincut-person-counter.js)
- JSONL replay via readline interface
- Live UDP via dgram
- Pure Node.js, no external dependencies
- CLI:
--replay <file>for offline,--port <N>for live,--jsonfor programmatic output
| Script | Primary Packets | Key Algorithm |
|---|---|---|
sleep-monitor.js |
vitals (0xC5110002) | BR/HR window classification |
apnea-detector.js |
vitals (0xC5110002) | BR pause detection, AHI scoring |
stress-monitor.js |
vitals (0xC5110002) | HRV RMSSD + FFT LF/HF |
gait-analyzer.js |
vitals + raw CSI | FFT cadence + phase tremor |
material-detector.js |
raw CSI (0xC5110001) | Null pattern baseline + delta |
room-fingerprint.js |
feature (0xC5110003) + vitals | Online k-means clustering |
Consequences
Positive
- 6 new sensing applications from existing hardware (zero additional cost)
- All offline-capable via JSONL replay (no live hardware needed for development)
- Pure JS, no native dependencies, runs on any platform with Node.js
- Each script is standalone and composable
Negative
- Vitals accuracy depends on ESP32 CSI quality (RSSI, multipath)
- HRV analysis at 1 Hz HR sampling is coarse compared to ECG
- Material classification is heuristic, not definitive
- Sleep staging without EEG is approximate (consumer-grade accuracy)
Risks
- Users may misinterpret health-related outputs as clinical diagnoses
- Mitigation: all scripts include disclaimers in output headers