Files
ruvnet--ruview/docs/adr/ADR-039-esp32-edge-intelligence.md
wehub-resource-sync 9740bc64c9
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier1) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (full-adr060) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (tdm-3node) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / Swarm Test (ADR-062) (push) Has been skipped
npm packages / tools/ruview-mcp (node 22) (push) Failing after 1s
nvsim-server → ghcr.io / build-and-publish (push) Failing after 1s
ruview-swarm CI guard / tests (full+train) (push) Failing after 2s
Bench Regression Guard / bench compile-verify (--no-run) (push) Failing after 0s
Bench Regression Guard / bench fast-run (informational, non-gating) (push) Has been skipped
Firmware CI / Verify version.txt matches release tag (push) Has been skipped
Dashboard a11y + cross-browser / a11y (push) Failing after 0s
nvsim Dashboard → GitHub Pages / build-and-deploy (push) Failing after 2s
Firmware CI / Build firmware (esp32s3 / 4mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / Build Espressif QEMU (push) Failing after 1s
Firmware QEMU Tests (ADR-061) / Fuzz Testing (ADR-061 Layer 6) (push) Failing after 1s
Continuous Deployment / Pre-deployment Checks (push) Has been skipped
Firmware CI / Build firmware (esp32c6 / c6-4mb) (push) Failing after 15s
Firmware CI / Build firmware (esp32s3 / 8mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-max) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-min) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (default) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier0) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / NVS Matrix Generation (push) Failing after 1s
Security Scanning / Security Policy Compliance (push) Failing after 0s
Security Scanning / Dependency Vulnerability Scan (push) Failing after 0s
Security Scanning / Static Application Security Testing (push) Failing after 1s
Security Scanning / Infrastructure Security Scan (push) Failing after 1s
Security Scanning / Secret Scanning (push) Failing after 1s
npm packages / harness/ruview (node 22) (push) Failing after 17s
Security Scanning / License Compliance Scan (push) Failing after 1s
Security Scanning / Container Security Scan (push) Failing after 4s
three.js demos → GitHub Pages / build-and-deploy (push) Failing after 1s
Verify Pipeline Determinism / Verify Pipeline Determinism (3.11) (push) Failing after 1s
Fix-Marker Regression Guard / Verify fix markers (push) Failing after 1s
ADR-115 MQTT integration tests / mqtt-integration (push) Failing after 1s
npm packages / harness/ruview (node 20) (push) Failing after 1s
npm packages / tools/ruview-mcp (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 22) (push) Failing after 1s
BFLD MQTT Integration / cargo test --features mqtt (live mosquitto) (push) Failing after 29s
ruview-swarm CI guard / build train_marl bin (push) Failing after 2s
ruview-swarm CI guard / clippy (-D warnings, --no-deps) (push) Failing after 3s
ruview-swarm CI guard / tests (ruflo) (push) Failing after 1s
ruview-swarm CI guard / tests (train) (push) Failing after 2s
ruview-swarm CI guard / tests (default) (push) Failing after 2s
Point Cloud Viewer → GitHub Pages / build-and-deploy (push) Failing after 8s
ruview-swarm CI guard / ITAR / publish guard (push) Failing after 0s
wifi-densepose sensing-server → Docker Hub + ghcr.io / build · push · smoke-test (push) Failing after 1s
Continuous Deployment / Deploy to Production (push) Has been cancelled
Continuous Deployment / Rollback Deployment (push) Has been cancelled
Continuous Deployment / Post-deployment Monitoring (push) Has been cancelled
Continuous Deployment / Notify Deployment Status (push) Has been cancelled
Continuous Deployment / Deploy to Staging (push) Has been cancelled
Security Scanning / Security Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:59:54 +08:00

9.1 KiB
Raw Permalink Blame History

ADR-039: ESP32-S3 Edge Intelligence Pipeline

Status: Accepted (hardware-validated on RuView ESP32-S3) Date: 2026-03-02 Deciders: @ruvnet

Context

WiFi-DensePose captures Channel State Information (CSI) from ESP32-S3 nodes and streams raw I/Q data to a host server for processing. This architecture has limitations:

  1. Bandwidth: Raw CSI at 20 Hz × 128 subcarriers × 2 bytes = ~5 KB/frame = ~100 KB/s per node. Multi-node deployments saturate low-bandwidth links.
  2. Latency: Server-side processing adds network round-trip delay for time-critical signals like fall detection.
  3. Power: Continuous raw streaming prevents duty-cycling for battery-powered deployments.
  4. Scalability: Server CPU scales linearly with node count for basic signal processing that could run on the ESP32-S3's dual cores.

Decision

Implement a tiered edge processing pipeline on the ESP32-S3 that performs signal processing locally and sends compact results:

Tier 0 — Raw Passthrough (default, backward compatible)

No on-device processing. CSI frames streamed as-is (magic 0xC5110001).

Tier 1 — Basic Signal Processing

  • Phase extraction and unwrapping from I/Q pairs
  • Welford running variance per subcarrier
  • Top-K subcarrier selection by variance
  • Delta compression (XOR + RLE) for 30-50% bandwidth reduction (magic 0xC5110005, reassigned from 0xC5110003 by ADR-069)

Tier 2 — Full Edge Intelligence

All of Tier 1, plus:

  • Biquad IIR bandpass filters: breathing (0.1-0.5 Hz), heart rate (0.8-2.0 Hz)
  • Zero-crossing BPM estimation
  • Presence detection with adaptive threshold calibration (1200 frames, 3-sigma)
  • Fall detection (phase acceleration exceeding configurable threshold)
  • Multi-person vitals via subcarrier group clustering (up to 4 persons)
  • 32-byte vitals packet at configurable interval (magic 0xC5110002)

Architecture

Core 0 (WiFi)                    Core 1 (DSP)
┌─────────────────┐              ┌──────────────────────────┐
│ CSI callback     │──SPSC ring──▶│ Phase extract + unwrap   │
│ (wifi_csi_cb)    │   buffer     │ Welford variance         │
│                  │              │ Top-K selection           │
│ UDP raw stream   │              │ Biquad bandpass filters   │
│ (0xC5110001)     │              │ Zero-crossing BPM         │
└─────────────────┘              │ Presence detection        │
                                 │ Fall detection             │
                                 │ Multi-person clustering    │
                                 │ Delta compression          │
                                 │ ──▶ UDP vitals (0xC5110002)│
                                 │ ──▶ UDP compressed (0x05)  │
                                 └──────────────────────────┘

Wire Protocols

Vitals Packet (32 bytes, magic 0xC5110002):

Offset Type Field
0-3 u32 LE Magic 0xC5110002
4 u8 Node ID
5 u8 Flags (bit0=presence, bit1=fall, bit2=motion)
6-7 u16 LE Breathing rate (BPM × 100)
8-11 u32 LE Heart rate (BPM × 10000)
12 i8 RSSI
13 u8 Number of detected persons
14-15 u8[2] Reserved
16-19 f32 LE Motion energy
20-23 f32 LE Presence score
24-27 u32 LE Timestamp (ms since boot)
28-31 u32 LE Reserved

Compressed Frame (magic 0xC5110005, reassigned from 0xC5110003 by ADR-069):

Offset Type Field
0-3 u32 LE Magic 0xC5110005
4 u8 Node ID
5 u8 WiFi channel
6-7 u16 LE Original I/Q length
8-9 u16 LE Compressed length
10+ bytes RLE-encoded XOR delta

Configuration

Six NVS keys in the csi_cfg namespace:

NVS Key Type Default Description
edge_tier u8 2 Processing tier (0/1/2)
pres_thresh u16 0 Presence threshold × 1000 (0 = auto)
fall_thresh u16 2000 Fall threshold × 1000 (rad/s²)
vital_win u16 256 Phase history window
vital_int u16 1000 Vitals interval (ms)
subk_count u8 8 Top-K subcarrier count

All configurable via provision.py --edge-tier 2 --pres-thresh 0.05 ...

Additional Features

  • OTA Updates: HTTP server on port 8032 (POST /ota, GET /ota/status) with rollback support
  • Power Management: WiFi modem sleep + automatic light sleep with configurable duty cycle

Consequences

Positive

  • Fall detection latency reduced from ~500 ms (network RTT) to <50 ms (on-device)
  • Bandwidth reduced 30-50% with delta compression, or 95%+ with vitals-only mode
  • Battery-powered deployments possible with duty-cycled light sleep
  • Server can handle 10x more nodes (only parses 32-byte vitals instead of ~5 KB CSI)

Negative

  • Firmware complexity increases (edge_processing.c is ~750 lines)
  • ESP32-S3 RAM usage increases ~12 KB for ring buffer + filter state
  • Binary size increases from ~550 KB to ~925 KB with full WASM3 Tier 3 (10% free in 1 MB partition — see ADR-040)

Risks

  • BPM accuracy depends on subject distance and movement; needs real-world validation
  • Fall detection heuristic may false-positive on environmental motion (doors, pets)
  • Multi-person separation via subcarrier clustering is approximate without calibration

Implementation

  • firmware/esp32-csi-node/main/edge_processing.c — DSP pipeline (~750 lines)
  • firmware/esp32-csi-node/main/edge_processing.h — Types and API
  • firmware/esp32-csi-node/main/ota_update.c/h — HTTP OTA endpoint
  • firmware/esp32-csi-node/main/power_mgmt.c/h — Power management
  • v2/.../wifi-densepose-sensing-server/src/main.rs — Vitals parser + REST endpoint
  • scripts/provision.py — Edge config CLI arguments
  • .github/workflows/firmware-ci.yml — CI build + size gate (updated to 950 KB for Tier 3)

Tier 3 — WASM Programmable Sensing (ADR-040, ADR-041)

See ADR-040 for hot-loadable WASM modules compiled from Rust, executed via WASM3 interpreter on-device. Core modules: gesture recognition, coherence monitoring, adversarial detection.

ADR-041 defines the curated module collection (37 modules across 6 categories). Phase 1 implemented modules:

  • vital_trend.rs — Clinical vital sign trend analysis (bradypnea, tachypnea, apnea)
  • intrusion.rs — State-machine intrusion detection (calibrate-monitor-arm-alert)
  • occupancy.rs — Spatial occupancy zone detection with per-zone variance analysis

Hardware Benchmark (RuView ESP32-S3)

Measured on ESP32-S3 (QFN56 rev v0.2, 8 MB flash, 160 MHz, ESP-IDF v5.2).

Boot Timing

Milestone Time (ms)
app_main() 412
WiFi STA init 627
WiFi connected + IP 3,732
CSI collection init 3,754
Edge DSP task started 3,773
WASM runtime initialized 3,857
Total boot → ready ~3.9 s

CSI Performance

Metric Value
Frame rate 28.5 Hz (measured, ch 5 BW20)
Frame sizes 128 / 256 bytes
RSSI range -83 to -32 dBm (mean -62 dBm)
Per-frame interval 30.6 ms avg

Memory

Region Size
RAM (main heap) 256 KiB
RAM (secondary) 21 KiB
DRAM 32 KiB
RTC RAM 7 KiB
Total available 316 KiB
PSRAM Not populated on test board
WASM arena fallback Internal heap (160 KB/slot × 4)

Firmware Binary

Metric Value
Binary size 925 KB (0xE7440 bytes)
Partition size 1 MB (factory)
Free space 10% (99 KB)
CI size gate 950 KB (PASS)
WASM3 interpreter Included (full, ~100 KB)
WASM binary (7 modules) 13.8 KB (wasm32-unknown-unknown release)

WASM Runtime

Metric Value
Init time 106 ms
Module slots 4
Arena per slot 160 KB
Frame budget 10,000 µs (10 ms)
Timer interval 1,000 ms (1 Hz)

Findings

  1. Fall detection threshold too low — default fall_thresh=2000 (2.0 rad/s²) triggers 6.7 false positives/s in static indoor environment. Recommend increasing to 5000-8000 for typical deployments.
  2. No PSRAM on test board — WASM arena falls back to internal heap. Boards with PSRAM would support larger modules.
  3. CSI rate exceeds spec — measured 28.5 Hz vs. expected ~20 Hz. Performance headroom is better than estimated.
  4. WiFi-to-Ethernet isolation — some routers block UDP between WiFi and wired clients. Recommend same-subnet verification in deployment guide.
  5. sendto ENOMEM crash (Issue #127) — CSI callbacks in promiscuous mode fire 100-500+ times/sec, exhausting the lwIP pbuf pool and causing a guru meditation crash. Fixed with a dual approach: 50 Hz rate limiter in csi_collector.c (20 ms minimum send interval) and a 100 ms ENOMEM backoff in stream_sender.c. Binary size with fix: 947 KB. Hardware-verified stable for 200+ CSI callbacks with zero ENOMEM errors.