* docs: ADR-063 mmWave sensor fusion with WiFi CSI 60 GHz mmWave radar (Seeed MR60BHA2, HLK-LD2410/LD2450) fusion with WiFi CSI for dual-confirm fall detection, clinical-grade vitals, and self-calibrating CSI pipeline. Covers auto-detection, 6 supported sensors, Kalman fusion, extended 48-byte vitals packet, RuVector/RuvSense integration points, and 6-phase implementation plan. Based on live hardware capture from ESP32-C6 + MR60BHA2 on COM4. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(firmware): ADR-063 mmWave sensor fusion — full implementation Phase 1-2 of ADR-063: mmwave_sensor.c/h: - MR60BHA2 UART parser (60 GHz: HR, BR, presence, distance) - LD2410 UART parser (24 GHz: presence, distance) - Auto-detection: probes UART for known frame headers at boot - Mock generator for QEMU testing (synthetic HR 72±2, BR 16±1) - Capability flag registration per sensor type edge_processing.c/h: - 48-byte fused vitals packet (magic 0xC5110004) - Kalman-style fusion: mmWave 80% + CSI 20% when both available - Automatic fallback to CSI-only 32-byte packet when no mmWave - Dual presence flag (Bit3 = mmwave_present) main.c: - mmwave_sensor_init() called at boot with auto-detect - Status logged in startup banner Fuzz stubs updated for mmwave_sensor API. Build verified: QEMU mock build passes. Co-Authored-By: claude-flow <ruv@ruv.net> * fix(firmware): correct MR60BHA2 + LD2410 UART protocols (ADR-063) MR60BHA2: SOF=0x01 (not 0x5359), XOR+NOT checksums on header and data, frame types 0x0A14 (BR), 0x0A15 (HR), 0x0A16 (distance), 0x0F09 (presence). Based on Seeed Arduino library research. LD2410: 256000 baud (not 115200), 0xAA report head marker, target state byte at offset 2 (after data_type + head_marker). Auto-detect: probes MR60 at 115200 first, then LD2410 at 256000. Sets final baud rate after detection. Co-Authored-By: claude-flow <ruv@ruv.net> * feat: ADR-063 Phase 6 server-side mmWave + CSI fusion bridge Python script reads both serial ports simultaneously: - COM4 (ESP32-C6 + MR60BHA2): parses ESPHome debug output for HR, BR, presence, distance - COM7 (ESP32-S3): reads CSI edge processing frames Kalman-style fusion: mmWave 80% + CSI 20% for vitals, OR gate for presence. Verified on real hardware: mmWave HR=75bpm, BR=25/min at 52cm range, CSI frames flowing concurrently. Both sensors live for 30 seconds. Co-Authored-By: claude-flow <ruv@ruv.net> * docs: ADR-064 multimodal ambient intelligence roadmap 25+ applications across 4 tiers from practical to exotic: - Tier 1 (build now): zero-FP fall detection, sleep monitoring, occupancy HVAC, baby breathing, bathroom safety - Tier 2 (research): gait analysis, stress detection, gesture control, respiratory screening, multi-room activity - Tier 3 (frontier): cardiac arrhythmia, RF tomography, sign language, cognitive load, swarm sensing - Tier 4 (exotic): emotion contagion, lucid dreaming, plant monitoring, pet behavior Priority matrix with effort estimates. All P0-P1 items work with existing hardware (ESP32-S3 + MR60BHA2 + BH1750). Co-Authored-By: claude-flow <ruv@ruv.net> * fix(ci): add ESP_ERR_NOT_FOUND to fuzz stubs mmwave_sensor stub returns ESP_ERR_NOT_FOUND which wasn't defined in the minimal esp_stubs.h for host-based fuzz testing. Co-Authored-By: claude-flow <ruv@ruv.net>
14 KiB
ADR-063: 60 GHz mmWave Sensor Fusion with WiFi CSI
Status: Proposed Date: 2026-03-15 Deciders: @ruvnet Related: ADR-014 (SOTA signal processing), ADR-021 (vital sign extraction), ADR-029 (RuvSense multistatic), ADR-039 (edge intelligence), ADR-042 (CHCI coherent sensing)
Context
RuView currently senses the environment using WiFi CSI — a passive technique that analyzes how WiFi signals are disturbed by human presence and movement. While this works through walls and requires no line of sight, CSI-derived vital signs (breathing rate, heart rate) are inherently noisy because they rely on phase extraction from multipath-rich WiFi channels.
A complementary sensing modality exists: 60 GHz mmWave radar modules (e.g., Seeed MR60BHA2) that use active FMCW radar at 60 GHz to measure breathing and heart rate with clinical-grade accuracy. These modules are inexpensive (~$15), run on ESP32-C6/C3, and output structured vital signs over UART.
Live hardware capture (COM4, 2026-03-15) from a Seeed MR60BHA2 on an ESP32-C6 running ESPHome:
[D][sensor:093]: 'Real-time respiratory rate': Sending state 22.00000
[D][sensor:093]: 'Real-time heart rate': Sending state 92.00000 bpm
[D][sensor:093]: 'Distance to detection object': Sending state 0.00000 cm
[D][sensor:093]: 'Target Number': Sending state 0.00000
[D][binary_sensor:036]: 'Person Information': Sending state OFF
[D][sensor:093]: 'Seeed MR60BHA2 Illuminance': Sending state 0.67913 lx
The Opportunity
Fusing WiFi CSI with mmWave radar creates a sensor system that is greater than the sum of its parts:
| Capability | WiFi CSI Alone | mmWave Alone | Fused |
|---|---|---|---|
| Through-wall sensing | Yes (5m+) | No (LoS only, ~3m) | Yes — CSI for room-scale, mmWave for precision |
| Heart rate accuracy | ±5-10 BPM | ±1-2 BPM | ±1-2 BPM (mmWave primary, CSI cross-validates) |
| Breathing accuracy | ±2-3 BPM | ±0.5 BPM | ±0.5 BPM |
| Presence detection | Good (adaptive threshold) | Excellent (range-gated) | Excellent + through-wall |
| Multi-person | Via subcarrier clustering | Via range-Doppler bins | Combined spatial + RF resolution |
| Fall detection | Phase acceleration | Range/velocity + micro-Doppler | Dual-confirm reduces false positives to near-zero |
| Pose estimation | Via trained model | Not available | CSI provides pose; mmWave provides ground-truth vitals for training |
| Coverage | Whole room (passive) | ~120° cone, 3m range | Full room + precision zone |
| Cost per node | ~$9 (ESP32-S3) | ~$15 (ESP32-C6 + MR60BHA2) | ~$24 combined |
RuVector Integration Points
The RuVector v2.0.4 stack (already integrated per ADR-016) provides the signal processing backbone:
| RuVector Component | Role in mmWave Fusion |
|---|---|
ruvector-attention (bvp.rs) |
Blood Volume Pulse estimation — mmWave heart rate can calibrate the WiFi CSI BVP phase extraction |
ruvector-temporal-tensor (breathing.rs) |
Breathing rate estimation — mmWave provides ground-truth for adaptive filter tuning |
ruvector-solver (triangulation.rs) |
Multilateration — mmWave range-gated distance + CSI amplitude = 3D position |
ruvector-attn-mincut (spectrogram.rs) |
Time-frequency decomposition — mmWave Doppler complements CSI phase spectrogram |
ruvector-mincut (metrics.rs, DynamicPersonMatcher) |
Multi-person association — mmWave target IDs help disambiguate CSI subcarrier clusters |
RuvSense Integration Points
The RuvSense multistatic sensing pipeline (ADR-029) gains new capabilities:
| RuvSense Module | mmWave Integration |
|---|---|
pose_tracker.rs (AETHER re-ID) |
mmWave distance + velocity as additional re-ID features for Kalman tracker |
longitudinal.rs (Welford stats) |
mmWave vitals as reference signal for CSI drift detection |
intention.rs (pre-movement) |
mmWave micro-Doppler detects pre-movement 100-200ms earlier than CSI |
adversarial.rs (consistency check) |
mmWave provides independent signal to detect CSI spoofing/anomalies |
coherence_gate.rs |
mmWave presence as additional gate input — if mmWave says "no person", CSI coherence gate rejects |
Cross-Viewpoint Fusion Integration
The viewpoint fusion pipeline (ruvector/src/viewpoint/) extends naturally:
| Viewpoint Module | mmWave Extension |
|---|---|
attention.rs (CrossViewpointAttention) |
mmWave range becomes a new "viewpoint" in the attention mechanism |
geometry.rs (GeometricDiversityIndex) |
mmWave cone geometry contributes to Fisher Information / Cramer-Rao bounds |
coherence.rs (phase phasor) |
mmWave phase coherence as validation for WiFi phasor coherence |
fusion.rs (MultistaticArray) |
mmWave node becomes a member of the multistatic array with its own domain events |
Decision
Add 60 GHz mmWave radar sensor support to the RuView firmware and sensing pipeline with auto-detection and device-specific capabilities.
Architecture
┌─────────────────────────────────────────────────────────┐
│ Sensing Node │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ ESP32-S3 │ │ ESP32-C6 │ │ Combined │ │
│ │ WiFi CSI │ │ + MR60BHA2 │ │ S3 + UART │ │
│ │ (COM7) │ │ 60GHz mmWave │ │ mmWave │ │
│ │ │ │ (COM4) │ │ │ │
│ │ Passive │ │ Active radar │ │ Both modes │ │
│ │ Through-wall │ │ LoS, precise │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └─────┬──────┘ │
│ │ │ │ │
│ └────────┬───────────┘ │ │
│ ▼ │ │
│ ┌────────────────┐ │ │
│ │ Fusion Engine │◄──────────────────────┘ │
│ │ │ │
│ │ • Kalman fuse │ Vitals packet (extended): │
│ │ • Cross-validate│ magic 0xC5110004 │
│ │ • Ground-truth │ + mmwave_hr, mmwave_br │
│ │ calibration │ + mmwave_distance │
│ │ • Fall confirm │ + mmwave_target_count │
│ └────────────────┘ + confidence scores │
└─────────────────────────────────────────────────────────┘
Three Deployment Modes
Mode 1: Standalone CSI (existing) — ESP32-S3 only, WiFi CSI sensing.
Mode 2: Standalone mmWave — ESP32-C6 + MR60BHA2, precise vitals in a single room.
Mode 3: Fused (recommended) — ESP32-S3 + mmWave module on UART, or two separate nodes with server-side fusion.
Auto-Detection Protocol
The firmware will auto-detect connected mmWave modules at boot:
- UART probe — On configured UART pins, send the MR60BHA2 identification command (
0x01 0x01 0x00 0x01 ...) and check for valid response header - Protocol detection — Identify the sensor family:
- Seeed MR60BHA2 (breathing + heart rate)
- Seeed MR60FDA1 (fall detection)
- Seeed MR24HPC1 (presence + light sleep/deep sleep)
- HLK-LD2410 (presence + distance)
- HLK-LD2450 (multi-target tracking)
- Capability registration — Register detected sensor capabilities in the edge config:
typedef struct {
uint8_t mmwave_detected; /** 1 if mmWave module found on UART */
uint8_t mmwave_type; /** Sensor family (MR60BHA2, MR60FDA1, etc.) */
uint8_t mmwave_has_hr; /** Heart rate capability */
uint8_t mmwave_has_br; /** Breathing rate capability */
uint8_t mmwave_has_fall; /** Fall detection capability */
uint8_t mmwave_has_presence; /** Presence detection capability */
uint8_t mmwave_has_distance; /** Range measurement capability */
uint8_t mmwave_has_tracking; /** Multi-target tracking capability */
float mmwave_hr_bpm; /** Latest heart rate from mmWave */
float mmwave_br_bpm; /** Latest breathing rate from mmWave */
float mmwave_distance_cm; /** Distance to nearest target */
uint8_t mmwave_target_count; /** Number of detected targets */
bool mmwave_person_present;/** mmWave presence state */
} mmwave_state_t;
Supported Sensors
| Sensor | Frequency | Capabilities | UART Protocol | Cost |
|---|---|---|---|---|
| Seeed MR60BHA2 | 60 GHz | HR, BR, presence, illuminance | Seeed proprietary frames | ~$15 |
| Seeed MR60FDA1 | 60 GHz | Fall detection, presence | Seeed proprietary frames | ~$15 |
| Seeed MR24HPC1 | 24 GHz | Presence, sleep stage, distance | Seeed proprietary frames | ~$10 |
| HLK-LD2410 | 24 GHz | Presence, distance (motion + static) | HLK binary protocol | ~$3 |
| HLK-LD2450 | 24 GHz | Multi-target tracking (x,y,speed) | HLK binary protocol | ~$5 |
Fusion Algorithms
1. Vital Sign Fusion (Kalman filter)
mmWave HR (high confidence, 1 Hz) ─┐
├─► Kalman fuse → fused HR ± confidence
CSI-derived HR (lower confidence) ─┘
2. Fall Detection (dual-confirm)
CSI phase accel > thresh ──────┐
├─► AND gate → confirmed fall (near-zero false positives)
mmWave range-velocity pattern ─┘
3. Presence Validation
CSI adaptive threshold ────┐
├─► Weighted vote → robust presence
mmWave target count > 0 ──┘
4. Training Calibration
mmWave ground-truth vitals → train CSI BVP extraction model
mmWave distance → calibrate CSI triangulation
mmWave micro-Doppler → label CSI activity patterns
Vitals Packet Extension
Extend the existing 32-byte vitals packet (magic 0xC5110002) with a new 48-byte fused packet:
typedef struct __attribute__((packed)) {
/* Existing 32-byte vitals fields */
uint32_t magic; /* 0xC5110004 (fused vitals) */
uint8_t node_id;
uint8_t flags; /* Bit0=presence, Bit1=fall, Bit2=motion, Bit3=mmwave_present */
uint16_t breathing_rate; /* Fused BPM * 100 */
uint32_t heartrate; /* Fused BPM * 10000 */
int8_t rssi;
uint8_t n_persons;
uint8_t mmwave_type; /* Sensor type enum */
uint8_t fusion_confidence;/* 0-100 fusion quality score */
float motion_energy;
float presence_score;
uint32_t timestamp_ms;
/* New mmWave fields (16 bytes) */
float mmwave_hr_bpm; /* Raw mmWave heart rate */
float mmwave_br_bpm; /* Raw mmWave breathing rate */
float mmwave_distance; /* Distance to nearest target (cm) */
uint8_t mmwave_targets; /* Target count */
uint8_t mmwave_confidence;/* mmWave signal quality 0-100 */
uint16_t reserved;
} edge_fused_vitals_pkt_t;
_Static_assert(sizeof(edge_fused_vitals_pkt_t) == 48, "fused vitals must be 48 bytes");
NVS Configuration
New provisioning parameters:
python provision.py --port COM7 \
--mmwave-uart-tx 17 --mmwave-uart-rx 18 \ # UART pins for mmWave module
--mmwave-type auto \ # auto-detect, or: mr60bha2, ld2410, etc.
--fusion-mode kalman \ # kalman, vote, mmwave-primary, csi-primary
--fall-dual-confirm true # require both CSI + mmWave for fall alert
Implementation Phases
| Phase | Scope | Effort |
|---|---|---|
| Phase 1 | UART driver + MR60BHA2 parser + auto-detection | 2 weeks |
| Phase 2 | Fused vitals packet + Kalman vital sign fusion | 1 week |
| Phase 3 | Dual-confirm fall detection + presence voting | 1 week |
| Phase 4 | HLK-LD2410/LD2450 support + multi-target fusion | 2 weeks |
| Phase 5 | RuVector calibration pipeline (mmWave as ground truth) | 3 weeks |
| Phase 6 | Server-side fusion for separate CSI + mmWave nodes | 2 weeks |
Consequences
Positive
- Near-zero false positive fall detection (dual-confirm)
- Clinical-grade vital signs when mmWave is present, with CSI as fallback
- Self-calibrating CSI pipeline using mmWave ground truth
- Backward compatible — existing CSI-only nodes work unchanged
- Low incremental cost (~$3-15 per mmWave module)
- Auto-detection means zero configuration for supported sensors
- RuVector attention/solver/temporal-tensor modules gain a high-quality reference signal
Negative
- Added firmware complexity (~2-3 KB RAM for mmWave state + UART buffer)
- mmWave modules require line-of-sight (complementary to CSI, not replacement)
- Multiple UART protocols to maintain (Seeed, HLK families)
- 48-byte fused packet requires server parser update
Neutral
- ESP32-C6 cannot run the full CSI pipeline (single-core RISC-V) but can serve as a dedicated mmWave bridge node
- mmWave modules add ~15 mA power draw per node