Releases: ruvnet/RuView
v0.6.2-esp32 — ADR-081 Adaptive CSI Mesh Kernel + Timer Svc stack fix
First firmware release of the ADR-081 Adaptive CSI Mesh Kernel, plus the Timer Service stack-overflow regression fix caught by on-device validation. Ships both 8 MB and 4 MB variants from CI.
Highlights
- 5-layer adaptive CSI kernel (Radio Abstraction → Adaptive Controller → Mesh Plane → Feature State → Rust handoff) — swap radio silicon without touching the Rust signal/ruvector/mat crates
- 60-byte
rv_feature_state_tpacket (magic0xC5110006) replaces raw CSI as the default upstream — ~99.7 % bandwidth reduction (300 B/s at 5 Hz vs ~100 KB/s raw) - Mesh sensing plane (
rv_mesh) with 4 roles, 7 message types, CRC32-integrity envelope — HEALTH every 30 s, ANOMALY_ALERT on state transitions - Host-testable pure decision policy (
adaptive_controller_decide.c) — 33/33 assertions,decide()at 3.2 ns/call - 4 MB variant (ESP32-S3 SuperMini) now built in CI alongside the 8 MB default
Validated on real hardware
ESP32-S3 QFN56 rev v0.2 (MAC 3c:0f:02:e9:b5:f8), 38 s continuous run after flash:
App version: 0.6.2✅- 0 stack-overflow / panic / reboot markers
- 28
adaptive_ctrlmedium ticks, steady state, no drift - 149
rv_feature_state_temissions at ~5 Hz on earlier 30 s validation - Real WiFi CSI: 37 – 72 pps yield, RSSI -43 to -57 dBm
Fixes
| Fix | Detail |
|---|---|
| Timer Svc stack overflow (new) | emit_feature_state() + stream_sender network I/O runs inside FreeRTOS Timer Svc; ESP-IDF default 2 KiB stack overflows ~1 s after boot. Bumped CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=8192 in all three sdkconfig.defaults*. Follow-up: move heavy work out of the timer daemon. |
adaptive_controller.c implicit decl (#404) |
fast_loop_cb called emit_feature_state() before its static definition, tripping -Werror=implicit-function-declaration. Added forward declaration. |
provision.py esptool v5 compat (#391) |
write-flash (hyphenated) in dry-run hint for esptool ≥ 5. |
provision.py silent NVS wipe (#391) |
Now refuses to run without --ssid, --password, --target-ip unless --force-partial is passed; --force-partial prints which keys will be wiped. |
Defensive node_id capture (#232, #375, #385, #386, #390) |
csi_collector_init() now captures g_nvs_config.node_id once into a module-local; a canary WARN fires on divergence. |
CI change
firmware-ci.yml is now a matrix job building both variants in parallel and uploading variant-named artifacts — 6-binary releases no longer need local ESP-IDF.
Installing
Six binaries are attached below. The default flash layout for ESP32-S3 8 MB:
esptool.py --chip esp32s3 --port /dev/ttyUSB0 --baud 460800 \
--before default_reset --after hard_reset \
write_flash -z --flash_mode dio --flash_freq 80m --flash_size 8MB \
0x0 bootloader.bin \
0x8000 partition-table.bin \
0xf000 ota_data_initial.bin \
0x20000 esp32-csi-node.binFor ESP32-S3 SuperMini 4 MB, use esp32-csi-node-4mb.bin at 0x20000 and partition-table-4mb.bin at 0x8000 (reuse the same bootloader and OTA data).
After flash, provision WiFi + target (replaces the full csi_cfg NVS namespace — pass all creds at once):
python firmware/esp32-csi-node/provision.py \
--port /dev/ttyUSB0 \
--ssid <SSID> --password <PSK> --target-ip <DEST_IP>Artifacts
| File | Size | Target |
|---|---|---|
esp32-csi-node.bin |
1.06 MB | 8 MB app (flash @ 0x20000) |
esp32-csi-node-4mb.bin |
851 KB | 4 MB app (flash @ 0x20000) |
bootloader.bin |
18 KB | Both (flash @ 0x0) |
partition-table.bin |
3 KB | 8 MB layout (flash @ 0x8000) |
partition-table-4mb.bin |
3 KB | 4 MB layout (flash @ 0x8000) |
ota_data_initial.bin |
8 KB | OTA data (flash @ 0xf000) |
Compatibility
- ESP-IDF: v5.4
- Target: ESP32-S3 (Xtensa dual-core). Not supported: ESP32 (original), ESP32-C3 (single-core, can't run CSI DSP).
- Supersedes: v0.6.1-esp32
- Wire format:
rv_feature_state_tmagic0xC5110006is the new default upstream; raw ADR-018 CSI (magic0xC5110001) remains available as a debug stream gated by the channel plan.
Reference
- ADR-081 — Adaptive CSI Mesh Firmware Kernel (
docs/adr/ADR-081-adaptive-csi-mesh-firmware-kernel.md) - Full changelog entry:
CHANGELOG.md#v062-esp32 - Source tag:
ae40e2b
ESP32-S3 CSI Firmware v0.6.1 — Defensive node_id Fix
ESP32-S3 CSI Firmware v0.6.1
Critical fix: node_id clobber on multi-node deployments
Closes #232, #375, #385, #386, #390.
Users on multi-node deployments reported that every ESP32 node transmitted node_id=1 in UDP frames, despite NVS provisioning showing the correct value. This made multi-node setups indistinguishable downstream.
What changed
Defense-in-depth fix — the firmware now captures node_id into a module-local static at init time, isolating the UDP frame header from any memory corruption of g_nvs_config:
csi_collector_init()capturesg_nvs_config.node_idintostatic uint8_t s_node_idonce at initcsi_serialize_frame()writesbuf[4] = s_node_id— bypasses the global entirely- All other consumers (
edge_processing.c,wasm_runtime.c,display_ui.c,swarm_bridge_init) use the newcsi_collector_get_node_id()accessor - Clobber canary logs
WARNifg_nvs_config.node_iddiverges from the captured value at init — helps isolate the upstream corruption path
Hardware validation (ESP32-S3 on COM8)
| Check | Result |
|---|---|
NVS node_id |
2 (provisioned) |
Boot log main: |
Node ID: 2 |
| Defensive capture log | Captured node_id=2 at init |
csi_collector init log |
node_id=2, channel=5 |
| UDP packets (15/15) | byte[4] = 2 |
| Clobber canary | Quiet (no WARN) |
| App version | 0.6.1 |
| Binary size | 865 KB (54% flash free) |
Binaries
| File | Flash size | Description |
|---|---|---|
esp32-csi-node.bin |
8MB | Main firmware |
bootloader.bin |
8MB | Bootloader |
partition-table.bin |
8MB | Partition table (dual OTA) |
ota_data_initial.bin |
8MB | OTA data |
esp32-csi-node-4mb.bin |
4MB | Main firmware (SuperMini) |
partition-table-4mb.bin |
4MB | Partition table (SuperMini) |
Flash command
python -m esptool --chip esp32s3 -p COM8 -b 460800 \
--before default_reset --after hard_reset \
write_flash --flash_mode dio --flash_size 8MB --flash_freq 80m \
0x0 bootloader.bin \
0x8000 partition-table.bin \
0xf000 ota_data_initial.bin \
0x20000 esp32-csi-node.binFor 4MB SuperMini boards, use esp32-csi-node-4mb.bin and partition-table-4mb.bin with --flash_size 4MB.
SHA-256 hashes
7f943b26...88197f7 bootloader.bin
9e650bf1...54bd43 esp32-csi-node.bin
15c263f6...02b32 esp32-csi-node-4mb.bin
7d2c7ac4...82c62f ota_data_initial.bin
4c2cc4ff...d81f0 partition-table.bin
4c2cc4ff...d81f0 partition-table-4mb.bin
Who should upgrade
All users running multi-node ESP32-S3 deployments. Single-node users are unaffected but the upgrade is safe.
v0.7.0 — WiFlow Camera-Supervised Pose Model (92.9% PCK@20)
WiFlow v1 — Camera-Supervised WiFi Pose Estimation
First WiFlow model trained on real ESP32 CSI + real camera ground truth.
Headline Result
| Metric | Value |
|---|---|
| PCK@20 | 92.9% |
| Eval loss | 0.082 |
| Bone constraint | 0.008 |
| Parameters | 186,946 |
| Model size | 974 KB |
| Training time | 19 minutes |
What's New
Camera Ground-Truth Pipeline (ADR-079)
collect-ground-truth.py— MediaPipe webcam capture synced with CSIalign-ground-truth.js— Nanosecond time alignmenttrain-wiflow-supervised.js— 3-phase curriculum trainingeval-wiflow.js— PCK/MPJPE evaluationrecord-csi-udp.py— Lightweight ESP32 CSI recorder
ruvector Optimizations
- O6: Subcarrier selection (70→35, 50% reduction)
- O7: Attention-weighted subcarriers
- O8: Stoer-Wagner min-cut person separation
- O9: Multi-SPSA gradient estimation
- O10: Scalable model (lite/small/medium/full)
Data Collection
- ESP32-S3 CSI: 7,000 frames at 23fps (5 min)
- Mac Mini M4 Pro camera: 6,470 frames via MediaPipe (5 min)
- 345 time-aligned paired samples
Model Archive Contents
wiflow-v1.json— Trained model weights (974 KB)training-log.json— Loss curves per phasebaseline-report.json— Pre-training baseline metricsMODEL_CARD.md— Model documentation
How to Use
# Collect your own ground truth
python scripts/collect-ground-truth.py --duration 300 --preview
python scripts/record-csi-udp.py --duration 300
# Train
node scripts/train-wiflow-supervised.js --data data/paired/your-data.jsonl --scale lite
# Evaluate
node scripts/eval-wiflow.js --model models/wiflow-real/wiflow-v1.json --data data/paired/your-data.jsonlRelated
- PR #363: Camera ground-truth training pipeline
- ADR-079: Camera Ground-Truth Training Pipeline
- ADR-072: WiFlow Architecture
v0.6.0 — Pre-Trained Models on HuggingFace + 17 Sensing Apps
Pre-trained weights: https://huggingface.co/ruv/ruview | mirror
0.008ms inference | 164K emb/s | 100% presence | 51.6% contrastive improvement | 8 KB model
Trained on 60,630 overnight samples. 17 sensing applications. 10 ADRs (069-078).
New: sleep monitor, apnea detector, stress monitor, gait analyzer, RF tomography, passive radar, material classifier, through-wall detector, device fingerprint.
PR #341 (Kalman tracker by @taylorjdawson) + PR #310 (security fix) merged.
ESP32-S3 CSI Firmware v0.5.5 — Advanced Sensing + WiFlow + Camera-Free Training
v0.5.5 — Advanced Sensing, WiFlow Architecture, Camera-Free Pose Training
TL;DR
Your $27 sensor kit now has spiking neural networks that learn your room in 30 seconds, min-cut person counting that actually works (fixes #348), CNN spectrogram embeddings for environment fingerprinting, multi-frequency mesh scanning across 6 WiFi channels, and the WiFlow SOTA architecture (1.8M params) for 17-keypoint pose estimation — all trainable without a camera.
What Changed (v0.5.4 → v0.5.5)
| Feature | v0.5.4 | v0.5.5 |
|---|---|---|
| Person counting | Broken (always 4) | MinCut: correct count (fixes #348) |
| WiFi channels | 1 (ch 5 only) | 6 channels (hopping 1/3/5/6/9/11) |
| Pose model | Simple 8→64→128 encoder | WiFlow: TCN + axial attention (1.8M params) |
| Pose keypoints | 5 proxy → 17 interpolated | 17 COCO keypoints (WiFlow decoder) |
| Online learning | None | SNN with STDP (adapts in <30s) |
| Environment fingerprint | 8-dim features | 128-dim CNN spectrogram |
| Multi-node fusion | Average | Graph transformer (GATv2 attention) |
| RF scanning | None | Live spectrum visualization |
| Neighbor WiFi | Ignored | Used as passive radar illuminators |
| Training pipeline | ruvllm basic | + camera-free + WiFlow + GCloud + Mac Mini |
| ADRs | 069-071 | 069-076 (8 total) |
New Capabilities
ADR-073: Multi-Frequency Mesh Scanning
ESP32 nodes hop across channels 1/3/5/6/9/11 at 200ms dwell. Neighbor WiFi networks (your printer, router, neighbors) become free RF illuminators. Null subcarriers dropped from 19% to 16%.
# Provision channel hopping
python firmware/esp32-csi-node/provision.py --port COM9 \
--hop-channels "1,6,11" --hop-dwell 200
# Live RF scan
node scripts/rf-scan.js --port 5006 --duration 30ADR-074: Spiking Neural Network
128→64→8 SNN with STDP unsupervised learning. Adapts to room in <30s without labels. 16-160x less compute than the FC encoder (event-driven, only processes changes).
node scripts/snn-csi-processor.js --port 5006ADR-075: MinCut Person Separation (fixes #348)
Stoer-Wagner min-cut on subcarrier correlation graph. Correctly counts 1 person on all 24 test windows where old firmware showed 4. <5ms per window.
node scripts/mincut-person-counter.js --port 5006
# Or replay: node scripts/mincut-person-counter.js --replay data/recordings/*.csi.jsonlADR-076: CNN Spectrogram Embeddings + Graph Transformer
CSI 64×20 matrix → 224×224 grayscale → CNN → 128-dim embedding. Same-node similarity 0.95+. GATv2 multi-head attention fuses multi-node features.
node scripts/csi-spectrogram.js --replay data/recordings/*.csi.jsonl
node scripts/mesh-graph-transformer.js --port 5006ADR-072: WiFlow SOTA Architecture
Full reimplementation of WiFlow (arXiv:2602.08661) in pure JS:
- TCN temporal encoder (dilated causal conv, k=7)
- Asymmetric spatial encoder (1×3 residual blocks)
- Axial self-attention (8 heads, width + height)
- Pose decoder → 17 COCO keypoints
- SmoothL1 + bone constraint loss (14 skeleton connections)
- 1.8M parameters (881 KB at 4-bit quantization)
Camera-Free Training (ADR-071 extended)
10 sensor signals replace cameras: PIR, BME280 temp/humidity, RSSI triangulation, subcarrier asymmetry, vibration, reed switch, kNN clusters, boundary fragility.
5-phase pipeline: multi-modal collection → weak labels → 5-keypoint proxy → 17-keypoint interpolation → self-refinement.
Validated Benchmarks
| Metric | Value |
|---|---|
| Rust tests | 1,463 passed |
| Presence accuracy | 100% |
| MinCut person count | 24/24 correct (was 0/24) |
| Inference latency | 0.012 ms (M4 Pro) |
| Throughput | 171,472 emb/s |
| WiFlow PCK@20 | 2.5% (camera-free baseline) |
| WiFlow parameters | 1,804,962 |
| WiFlow model (4-bit) | 881 KB |
| CNN spectrogram similarity | 0.95+ (same-node) |
| SNN adaptation time | <30s (STDP online) |
| Channel hopping | 6 channels, 200ms dwell |
| Null reduction | 19% → 16% |
Flash Instructions
Same firmware binary as v0.5.4 (channel hopping is NVS-configured, no reflash needed for existing v0.5.4 users):
python -m esptool --chip esp32s3 --port COM9 --baud 460800 \
write_flash --flash-mode dio --flash-size 8MB --flash-freq 80m \
0x0 bootloader.bin 0x8000 partition-table.bin \
0xf000 ota_data_initial.bin 0x20000 esp32-csi-node.bin
# Enable channel hopping (new in v0.5.5)
python firmware/esp32-csi-node/provision.py --port COM9 \
--hop-channels "1,6,11" --hop-dwell 200New Scripts (15+)
| Script | Purpose |
|---|---|
rf-scan.js |
Live RF spectrum scanner |
rf-scan-multifreq.js |
Multi-channel wideband view |
snn-csi-processor.js |
Spiking neural network processor |
mincut-person-counter.js |
Correct person counting |
csi-graph-visualizer.js |
Correlation graph visualization |
csi-spectrogram.js |
CNN spectrogram embeddings |
mesh-graph-transformer.js |
GATv2 multi-node fusion |
train-wiflow.js |
WiFlow SOTA training |
train-camera-free.js |
Camera-free 17-keypoint training |
train-ruvllm.js |
ruvllm contrastive + LoRA + TurboQuant |
benchmark-ruvllm.js |
Model benchmarking |
benchmark-wiflow.js |
WiFlow benchmarking |
benchmark-rf-scan.js |
RF scan benchmarking |
wiflow-model.js |
WiFlow architecture (pure JS) |
seed_csi_bridge.py |
ESP32 → Cognitum Seed ingest |
Learn more: Cognitum.one · Tutorial · User Guide
Full Changelog: v0.5.4-esp32...v0.5.5-esp32
Session Statistics
| Metric | Value |
|---|---|
| Commits | 24 |
| Files changed | 74 |
| Lines added | 22,231 |
| ADRs | 8 (069-076) |
| Research docs | 26 |
| New scripts | 20+ |
| PRs merged | 2 (#350, #352) |
| Issues addressed | 4 (#348, #188, #190, #268) |
| Rust tests | 1,463 passed |
| WiFlow PCK@20 | 2.5% (camera-free, improving with more data) |
| Overnight data | 65,938+ frames collecting |
| Session cost | ~$0.50 (Mac Mini electricity) |
ESP32-S3 CSI Firmware v0.5.4 — Cognitum Seed Integration
ESP32-S3 CSI Firmware v0.5.4 — Cognitum Seed Integration
TL;DR
Your ESP32 can now remember what it senses. By connecting to a Cognitum Seed every presence detection, breathing measurement, and motion event gets stored as a searchable vector with cryptographic proof it happened.
What Changed (v0.5.3 → v0.5.4)
| Feature | v0.5.3 | v0.5.4 |
|---|---|---|
| CSI sensing | ✅ Real-time only | ✅ Real-time + persistent storage |
| Data retention | ❌ Lost on reboot | ✅ RVF vector store on Cognitum Seed |
| Similarity search | ❌ None | ✅ kNN queries ("find states like this one") |
| Data integrity | ❌ None | ✅ SHA-256 witness chain + Ed25519 signing |
| AI integration | ❌ None | ✅ 114-tool MCP proxy (Claude, GPT can query sensors) |
| Environmental sensors | ❌ CSI only | ✅ + BME280, PIR, vibration, ADC (via Seed) |
| Feature vectors | ❌ Raw only | ✅ 8-dim normalized @ 1 Hz (new packet 0xC5110003) |
| Security | Basic | ✅ Bearer tokens, TLS, source filtering, NaN rejection |
New: 8-Dimension Feature Vector
Every second, the ESP32 sends a compact 48-byte packet summarizing what it sees:
| Dim | What it measures | Example |
|---|---|---|
| 0 | Is someone there? (presence) | 0.45 = maybe, 0.95 = definitely |
| 1 | Are they moving? (motion) | 0.0 = still, 0.87 = active |
| 2 | Breathing rate | 0.69 = 20.8 BPM (normal) |
| 3 | Heart rate | 0.75 = 90 BPM (normal) |
| 4 | Signal quality (phase variance) | Higher = more activity |
| 5 | How many people? | 0.25 = 1 person, 0.50 = 2 |
| 6 | Fall detected? | 0.0 = no, 1.0 = yes |
| 7 | Signal strength (RSSI) | 0.54 = -46 dBm (good) |
These vectors are stored on the Cognitum Seed and can be searched: "Find the 10 most similar states to right now" — useful for anomaly detection, pattern recognition, and environment fingerprinting.
Architecture
ESP32-S3 ($9) ──UDP──> Your laptop ──HTTPS──> Cognitum Seed ($15)
WiFi CSI capture Bridge script Stores vectors forever
28 Hz raw frames Batches & validates kNN similarity search
1 Hz feature vectors NaN rejection Witness chain (proof)
1 Hz vital signs Source filtering 114-tool AI proxy
How to Use
# 1. Flash firmware to ESP32-S3
python -m esptool --chip esp32s3 --port COM9 --baud 460800 \
write_flash --flash-mode dio --flash-size 8MB --flash-freq 80m \
0x0 bootloader.bin 0x8000 partition-table.bin \
0xf000 ota_data_initial.bin 0x20000 esp32-csi-node.bin
# 2. Set WiFi + target (your laptop IP)
python firmware/esp32-csi-node/provision.py --port COM9 \
--ssid "YourWiFi" --password "secret" \
--target-ip 192.168.1.20 --target-port 5006
# 3. Pair with Cognitum Seed (plug in via USB first)
curl -sk -X POST https://169.254.42.1:8443/api/v1/pair/window
curl -sk -X POST https://169.254.42.1:8443/api/v1/pair \
-H 'Content-Type: application/json' -d '{"client_name":"my-laptop"}'
# 4. Run the bridge
export SEED_TOKEN="<token-from-step-3>"
python scripts/seed_csi_bridge.py \
--seed-url https://169.254.42.1:8443 --token "$SEED_TOKEN" --validate
# 5. Check what's stored
python scripts/seed_csi_bridge.py --token "$SEED_TOKEN" --statsNo Cognitum Seed? The ESP32 firmware works exactly as before — all existing features (CSI streaming, vitals, presence, fall detection, mesh TDM) are unchanged. The Seed integration is additive.
Learn more: Cognitum.one · ADR-069 · User Guide
Validation Results
| Test | Result |
|---|---|
| Rust workspace | 1,463 passed, 0 failed |
| Python proof | VERDICT: PASS |
| Firmware size | 844 KB (55% flash free) |
| Feature vectors | 1 Hz, 8-dim, all in [0.0, 1.0] |
| Vitals ↔ Features | HR delta=0.0, BR delta=0.0 |
| Seed ingest | 941 vectors, 100% kNN match |
| Witness chain | SHA-256 verified (1,325 entries) |
| Regression | 6/6 PASSED — NO REGRESSIONS |
Binary Hashes (SHA-256)
cf05de2b esp32-csi-node.bin (844 KB)
cbb8e3ba bootloader.bin (19 KB)
4c2cc4ff partition-table.bin (3 KB)
7d2c7ac4 ota_data_initial.bin (8 KB)
Full Changelog: v0.5.3-esp32...v0.5.4-esp32
ESP32-S3 CSI Firmware v0.5.3 — Cross-Node Fusion + DynamicMinCut
What's Changed
Cross-Node Mesh Fusion
- RSSI-weighted feature fusion across multiple ESP32 nodes — closer node gets higher weight
- DynamicMinCut person separation via
ruvector_mincuton subcarrier temporal correlation graph - RSSI-based skeleton position tracking — walk between nodes and the skeleton follows
- Motion-responsive skeleton — arm swing (0-80px), leg kick (0-50px), walking bob, head nod all driven by real CSI data
Per-Node State (ADR-068)
- Each ESP32 gets independent classification, vitals, person count
- Stale node eviction after 60s inactivity
- Multi-node aggregation: max(per_node_counts)
RuVector Signal Processing (Phases 1-3)
- Subcarrier importance weighting via mincut partition
- Temporal EMA keypoint smoothing with coherence gating
- Skeleton kinematic constraints (Jakobsen relaxation, 12-bone COCO-17 tree)
- Compressed pose history for trajectory matching
Smoothing & Quality
- Client-side lerp interpolation (alpha=0.15) for fluid skeleton
- Server EMA (alpha=0.15) for temporal blending
- Dampened procedural animation, recalibrated for real ESP32 data
- Vital sign filtering: larger median window (31), faster EMA, looser jump filter
Hardware Benchmarks (2 ESP32 nodes, 30s)
| Metric | v0.5.2 (baseline) | v0.5.3 (fusion) | Improvement |
|---|---|---|---|
| Keypoint jitter | std=4.5px | std=1.3px | -72% |
| Feature noise | 109.4 | 77.6 | -29% |
| Feature stability | std=154.1 | std=105.4 | -32% |
| Confidence | 0.643 | 0.686 | +7% |
| Presence accuracy | 93.4% | 94.6% | +1.3pp |
Bug Fixes
- RSSI byte offset (#332), stack overflow, stale node leak, unsafe pointer removed
- Person count: sum→max aggregation, recalibrated thresholds
- Firmware CI: IDF v5.4, xxd→od
Binaries
| File | Flash Size | Description |
|---|---|---|
esp32-csi-node.bin |
8MB | Main firmware |
esp32-csi-node-4mb.bin |
4MB | For 4MB flash boards |
bootloader.bin |
— | Bootloader |
partition-table.bin |
8MB | Partition table |
partition-table-4mb.bin |
4MB | Partition table (4MB) |
ota_data_initial.bin |
— | OTA data |
Full Changelog: v0.5.2-esp32...v0.5.3-esp32
ESP32-S3 CSI Firmware v0.5.2 — RSSI Fix + Per-Node State
What's Changed
Bug Fixes
RSSI byte offset mismatch (#332)
- Server was reading RSSI from byte 14 (sequence counter high byte) instead of byte 16 (actual RSSI)
- Root cause: firmware packs
n_subcarriersas u16 andfreq_mhzas u32, but server assumed u8 and u16 - Result: RSSI now shows correct negative dBm values (e.g., -46) instead of bogus positive values (+9)
Per-node state pipeline (#249, ADR-068)
- Each ESP32 node now gets independent classification, person count, and vital sign tracking
- Fixes the #1 user-reported issue: detection showing same output regardless of number of nodes/people
- Scales to 256 nodes with <13 MB memory overhead
Firmware CI fixed (#327)
- Upgraded from ESP-IDF v5.2 → v5.4, replaced
xxdwithod - Pre-compiled binaries now built and uploaded automatically
Sensing Server
Rebuild from source to get all fixes:
git pull origin main
cd rust-port/wifi-densepose-rs
cargo build --release -p wifi-densepose-sensing-serverFirmware
No firmware binary changes from v0.5.1 — the RSSI fix and per-node state are server-side only. If you already flashed v0.5.1, no reflash needed.
Full Changelog: v0.5.1-esp32...v0.5.2-esp32
ESP32-S3 CSI Firmware v0.5.1 — Watchdog Fix + Edge Detection
What's Changed
Bug Fixes
Firmware: Watchdog crash on busy LANs (#321)
edge_dsptask now batch-limits to 4 frames before a 20ms yield- Fixed idle-path busy-spin where
pdMS_TO_TICKS(5)==0at 100Hz tick rate - Added ring buffer drop counter (
drops=Nin serial log) for diagnostics - QEMU validated: All 11 CI jobs pass (7 configs + fuzz + swarm + NVS + build)
Sensing Server: No detection from edge vitals (#323)
- ESP32 nodes running Tier 2+ edge DSP now trigger full
sensing_updatein the UI - Previously, vitals packets (magic
0xC5110002) were broadcast but never generated person detection - Breathing animation now works from edge vitals (non-zero
breathing_band_power)
Python Package: ModuleNotFoundError (#314)
- Added
wifi_densepose/package withWiFiDensePosefacade class from wifi_densepose import WiFiDensePosenow works as documented in README
CI Validation
| Job | Status |
|---|---|
| Build Espressif QEMU | ✅ |
| NVS Matrix Generation | ✅ |
| Fuzz Testing (ADR-061 Layer 6) | ✅ |
| Swarm Test (ADR-062) | ✅ |
| QEMU Test — default | ✅ |
| QEMU Test — edge-tier0 | ✅ |
| QEMU Test — edge-tier1 | ✅ |
| QEMU Test — tdm-3node | ✅ |
| QEMU Test — boundary-min | ✅ |
| QEMU Test — boundary-max | ✅ |
| QEMU Test — full-adr060 | ✅ |
Upgrade Notes
- Firmware: Reflash required for the watchdog fix. Build from source using ESP-IDF v5.4.
- Sensing server: Rebuild from
main—cargo build --release -p wifi-densepose-sensing-server - Python:
pip install -e .from repo root, orfrom wifi_densepose import WiFiDensePose
Full Changelog: v0.5.0-esp32...v0.5.1-esp32
ESP32-S3 CSI Firmware v0.5.0 — mmWave Sensor Fusion
ESP32-S3 CSI Firmware v0.5.0 — mmWave Sensor Fusion (ADR-063/064)
What's New
60 GHz mmWave Radar Fusion — The firmware now auto-detects mmWave radar modules connected via UART and fuses their data with WiFi CSI for dramatically improved sensing.
Supported mmWave sensors:
| Sensor | Frequency | Capabilities | Cost |
|---|---|---|---|
| Seeed MR60BHA2 | 60 GHz | Heart rate, breathing, presence, distance | ~$15 |
| HLK-LD2410 | 24 GHz | Presence, distance (motion + static) | ~$3 |
Auto-detection: The firmware probes UART1 (GPIO17/18) at boot — first at 115200 baud (MR60BHA2), then 256000 baud (LD2410). If a sensor is found, it registers capabilities and starts a background parsing task. No configuration needed.
48-byte fused vitals packet (magic 0xC5110004): When mmWave is active, vitals are fused using weighted Kalman averaging (mmWave 80% + CSI 20%). Falls back to the standard 32-byte CSI-only packet when no mmWave is detected — fully backward compatible.
Server-side fusion bridge (scripts/mmwave_fusion_bridge.py): For setups where the mmWave runs on a separate ESP32 (e.g., ESP32-C6 with ESPHome), this script reads both serial ports and fuses data in real-time.
Hardware Verified
Dual-sensor live capture (30 seconds, 2026-03-15):
- COM7 (ESP32-S3): WiFi CSI on channel 5, RSSI -41 dBm
- COM4 (ESP32-C6 + MR60BHA2): HR 75 bpm, BR 25/min, person at 52 cm
- Both sensors feeding data concurrently
Also Includes (from v0.4.3.1)
- Fall detection fix: threshold 15.0 rad/s², 3-frame debounce, 5s cooldown (#263)
- Task watchdog fix: vTaskDelay after every frame (#266)
- 4MB flash support (#265)
- All 11 QEMU CI jobs green
Download Guide
| File | Flash | Size |
|---|---|---|
esp32-csi-node.bin |
8MB | 990 KB |
bootloader.bin |
Both | 18 KB |
partition-table.bin |
8MB | 3 KB |
ota_data_initial.bin |
Both | 8 KB |
esp32-csi-node-4mb.bin |
4MB | 773 KB |
partition-table-4mb.bin |
4MB | 3 KB |
Flash — 8MB:
python -m esptool --chip esp32s3 --port COM7 --baud 460800 \
write_flash --flash_mode dio --flash_size 8MB --flash_freq 80m \
0x0 bootloader.bin 0x8000 partition-table.bin \
0xf000 ota_data_initial.bin 0x20000 esp32-csi-node.binFlash — 4MB:
python -m esptool --chip esp32s3 --port COM7 --baud 460800 \
write_flash --flash_mode dio --flash_size 4MB --flash_freq 80m \
0x0 bootloader.bin 0x8000 partition-table-4mb.bin \
0xF000 ota_data_initial.bin 0x20000 esp32-csi-node-4mb.binProvision:
python firmware/esp32-csi-node/provision.py --port COM7 \
--ssid "YourWiFi" --password "YourPassword" --target-ip 192.168.1.20Full Changelog
See CHANGELOG.md
Closes #269