mirror of
https://github.com/ruvnet/RuView
synced 2026-07-25 17:51:48 +00:00
42c7f44678bd72a4e751663ae72acd994700040d
13 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cee414f3c0 |
firmware/esp32-csi-node: IDF 6 build, HE CSI config, unicore DSP, provision chip detect (#522)
* firmware/esp32-csi-node: fix IDF 6 build (PSA SHA-256, explicit REQUIRES) - rvf_parser: use psa_hash_* / psa_hash_compute; mbedTLS 4 has no public mbedtls/sha256.h on the IDF include path. - main/CMakeLists: declare REQUIRES for WiFi, netif, HTTP, OTA, drivers, lwip, mbedtls per ESP-IDF v6 component dependency checks; optional wasm3 when CONFIG_WASM_ENABLE. Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com> Co-authored-by: Cursor <cursoragent@cursor.com> * firmware/esp32-csi-node: fix CSI config for Wi-Fi 6 (ESP32-C6) When CONFIG_SOC_WIFI_HE_SUPPORT is set, wifi_csi_config_t is the wifi_csi_acquire_config_t bitfield layout. The legacy bool fields (lltf_en, htltf_en, ...) only apply to ESP32-S3-class targets. Initialize acquire fields for HE targets; add MAC v3-only members when CONFIG_SOC_WIFI_MAC_VERSION_NUM >= 3. Verified: idf.py build for esp32c6 and esp32s3 (ESP-IDF v6.1). Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com> Co-authored-by: Cursor <cursoragent@cursor.com> * firmware/esp32-csi-node: pin edge DSP task for unicore (ESP32-C6) edge_processing_init used xTaskCreatePinnedToCore(..., core 1). ESP32-C6 runs FreeRTOS unicore (portNUM_PROCESSORS == 1), so core 1 trips the xTaskCreatePinnedToCore range assert right after CSI init. Use core 1 only when SMP is available; otherwise pin to core 0. Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com> Co-authored-by: Cursor <cursoragent@cursor.com> * firmware/esp32-csi-node: provision NVS with chip auto-detect provision.py always passed --chip esp32s3 to esptool, so flashing NVS on ESP32-C6 failed. Default --chip to auto (esptool v5) and add an explicit --chip override. Use write-flash instead of deprecated write_flash. Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com> Co-authored-by: Cursor <cursoragent@cursor.com> --------- Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
3f55c95b34 |
fix(esp32): disable WiFi modem sleep so CSI capture isn't starved (#521)
csi_collector_init() never called esp_wifi_set_ps(), leaving the radio on the ESP-IDF STA default WIFI_PS_MIN_MODEM. The modem then sleeps between DTIM beacons; combined with the MGMT-only promiscuous filter (#396) the CSI callback is starved and the per-second yield collapses toward 0 pps, which is what users on a clean multi-node setup were seeing (motion=0.00 presence=0.00 yield=0pps). Force WIFI_PS_NONE before enabling promiscuous mode — the textbook requirement for reliable CSI capture (every ESP-IDF CSI example does it). New boot line: "csi_collector: WiFi modem sleep disabled (WIFI_PS_NONE) for CSI capture". Battery duty-cycling is unaffected: power_mgmt_init() runs after this and re-enables modem sleep when provision.py is given --duty-cycle <100. Builds clean for esp32s3 (idf.py build, 48% flash free). Closes #521 Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
f06d0c6ab5 |
fix(firmware): SPI cache crash fix + node_id/filter_mac defensive copies + esptool v5 (rebased #397)
* fix(firmware): move defensive node_id capture before wifi_init_sta()
The original defensive copy in csi_collector_init() (line 172 of main.c)
runs AFTER wifi_init_sta() (line 147), which on some ESP32-S3 devices
corrupts g_nvs_config.node_id back to the Kconfig default of 1.
Reproduced on device 80:b5:4e:c1:be:b8 (ESP32-S3 QFN56 rev v0.2):
- NVS provisioned with node_id=5
- Release firmware (no fix): seed receives node_id=1 (clobbered)
- This patch: seed receives node_id=5 (correct)
Changes:
- Add csi_collector_set_node_id() called from main.c immediately
after nvs_config_load(), before wifi_init_sta() runs
- csi_collector_init() now detects and logs the clobber if early
capture disagrees with current g_nvs_config value
- Fallback path preserved: if set_node_id() is never called,
init() still captures from g_nvs_config (backwards compatible)
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(firmware): defensive copy of filter_mac to prevent callback crash
The CSI callback reads g_nvs_config.filter_mac_set and filter_mac on
every invocation (100-500 Hz). If wifi_init_sta() corrupts g_nvs_config
(same root cause as the node_id clobber), the callback reads garbage
from the struct, leading to Core 0 LoadProhibited panic after ~2400
callbacks (~70 seconds of operation).
Extends the early-capture pattern from the node_id fix to also copy
filter_mac_set and filter_mac into module-local statics before WiFi
init runs. Adds canary logging to detect filter_mac corruption.
Observed on device 80:b5:4e:c1:be:b8 via serial:
CSI cb #2400 → Guru Meditation Error: Core 0 panic'ed (LoadProhibited)
→ TG0WDT_SYS_RST → reboot → crash again at ~2900 callbacks
Refs #232 #375 #385 #386 #390
Co-Authored-By: Ruflo & AQE
* fix(firmware): MGMT-only promiscuous filter to prevent SPI cache crash
The WiFi driver's wDev_ProcessFiq interrupt handler crashes with
LoadProhibited in cache_ll_l1_resume_icache when promiscuous mode
captures MGMT+DATA frames (100-500 interrupts/sec). The high interrupt
rate races with SPI flash cache operations, corrupting cache state.
Changes:
- Promiscuous filter: MGMT+DATA → MGMT-only (~10 Hz beacons)
- CSI config: disable htltf_en and stbc_htltf2_en (LLTF-only)
LLTF provides 64 subcarriers (HT20) — sufficient for presence,
breathing, and fall detection. The 10 Hz beacon rate eliminates
the SPI flash cache contention that caused the crash.
Verified on device 80:b5:4e:c1:be:b8:
- Before: LoadProhibited crash at ~1600-2400 callbacks (every ~70s)
- After: 2700+ callbacks over 4.7 minutes, zero crashes
Backtrace decode confirmed crash in ESP-IDF closed-source WiFi blob:
_xt_lowint1 → wDev_ProcessFiq → spi_flash_restore_cache
→ cache_ll_l1_resume_icache → EXCVADDR=0x00000004 (NULL deref)
Co-Authored-By: Ruflo & AQE
* fix(provision): write-flash → write_flash for esptool v5 compat
esptool v5+ rejects hyphenated subcommands. The provision script
used 'write-flash' which fails with "invalid choice". Changed to
'write_flash' (underscore) which works with both old and new esptool.
Co-Authored-By: Ruflo & AQE
* fix(firmware): 50 Hz callback rate gate + sdkconfig extra IRAM opt
- Add early rate gate in wifi_csi_callback at 50 Hz (defense-in-depth,
does not prevent crash alone but reduces callback execution time)
- Add null-data injection timer infrastructure (disabled — TX adds
interrupt pressure that triggers the SPI cache crash, RuView#396)
- sdkconfig.defaults: add CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y
- sdkconfig.defaults: document SPIRAM XIP attempt (crashes differently)
Co-Authored-By: Ruflo & AQE
* fix(firmware): address PR #397 review feedback
Applies @ruvnet's five review requests on PR #397 (RuView#397 comment
4289417527):
1. **Inline comment on `provision.py` `write_flash`** — ESP-IDF v5.4
bundles esptool 4.10.0 (underscore-only). #391's hyphen swap broke
the documented venv flow; kept the underscore form and added a
three-line comment warning future maintainers not to "re-fix" it.
2. **Correct `edge_processing.c` sample_rate** (blocking) — changed
hard-coded `20.0f` → `10.0f` at line 718 so
`estimate_bpm_zero_crossing()` matches the MGMT-only CSI rate.
Without this, breathing and heart-rate reports were 2× the true
value. Added a comment tying the constant to the callback rate gate.
3. **Removed disabled probe-injection infrastructure** — dropped the
forward declaration, the `CSI_PROBE_INTERVAL_MS` define, six static
variables (`s_probe_timer`, `s_probe_tx_count`, `s_probe_tx_fail`,
`s_ap_bssid`, `s_ap_bssid_known`), and three functions
(`csi_send_probe_request`, `probe_timer_cb`,
`csi_collector_start_probe_timer`). None were reachable.
`csi_inject_ndp_frame()` reverted to the original ADR-029 stub.
Can be revived from this commit's parent if needed.
4. **Cleaned `sdkconfig.defaults`** — removed the SPIRAM prose and
commented-out `# CONFIG_SPIRAM is not set` line. Kept only the live
`CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y` with a concise rationale.
5. **Bumped firmware version 0.6.1 → 0.6.2** and added four
`[Unreleased]` CHANGELOG entries covering the SPI cache crash fix,
the `filter_mac` / `node_id` clobber defense, the sample-rate
correction, and the `write_flash` command-form revert.
Net: +39 / -128 across six files.
Validation in this devcontainer:
- Static sanity on modified C files: braces balance (csi_collector.c
59/59; edge_processing.c 96/96), zero dangling references to removed
probe-injection symbols.
- Rust workspace tests and Python proof not executed here — cargo not
installed and pip blocked by PEP 668. Deferring hardware build +
flash + miniterm verification to @ruvnet's COM7 per his offer in
the review comment.
Co-Authored-By: claude-flow <ruv@ruv.net>
---------
Co-authored-by: Dragan Spiridonov <spiridonovdragan@gmail.com>
|
||
|
|
5a7f431b0e |
ADR-081: Implement 5-layer adaptive CSI mesh firmware kernel (#404)
* ADR-081: adaptive CSI mesh firmware kernel + scaffolding
Introduces a 5-layer firmware kernel that reframes the existing ESP32
modules as components of a chipset-agnostic architecture and authorizes
adaptive control + a compact feature-state stream as the default upstream.
Layers:
L1 Radio Abstraction Layer — rv_radio_ops_t vtable + ESP32 binding
L2 Adaptive Controller — fast/medium/slow loops (200ms/1s/30s)
L3 Mesh Sensing Plane — anchor/observer/relay/coordinator (spec)
L4 On-device Feature Extr. — rv_feature_state_t (magic 0xC5110006)
L5 Rust handoff — feature_state default; debug raw gated
Files:
docs/adr/ADR-081-adaptive-csi-mesh-firmware-kernel.md (new)
firmware/esp32-csi-node/main/rv_radio_ops.h (new)
firmware/esp32-csi-node/main/rv_radio_ops_esp32.c (new)
firmware/esp32-csi-node/main/rv_feature_state.{h,c} (new)
firmware/esp32-csi-node/main/adaptive_controller.{h,c} (new)
firmware/esp32-csi-node/main/main.c (wire L1+L2)
firmware/esp32-csi-node/main/CMakeLists.txt (add 4 sources)
firmware/esp32-csi-node/main/Kconfig.projbuild (controller knobs)
CHANGELOG.md (Unreleased)
Default policy is conservative: enable_channel_switch and
enable_role_change are off, so behavior matches today's firmware
unless an operator opts in via menuconfig. The pure
adaptive_controller_decide() is exposed for offline unit tests.
Reuses (does not rewrite): csi_collector, edge_processing (ADR-039),
swarm_bridge (ADR-066), secure_tdm (ADR-032), wasm_runtime (ADR-040).
* ADR-081: implement Layers 1/2/4 end-to-end + host tests + QEMU hooks
Turns the ADR-081 scaffolding into a working adaptive CSI mesh kernel:
Layer 1 radio abstraction has an ESP32 binding and a mock binding; Layer 2
adaptive controller runs on FreeRTOS timers; Layer 4 feature-state packet
is emitted at 5 Hz by default, replacing raw ADR-018 CSI as the default
upstream.
New files:
firmware/esp32-csi-node/main/adaptive_controller_decide.c (pure policy)
firmware/esp32-csi-node/main/rv_radio_ops_mock.c (QEMU binding)
firmware/esp32-csi-node/tests/host/Makefile (host tests)
firmware/esp32-csi-node/tests/host/test_adaptive_controller.c
firmware/esp32-csi-node/tests/host/test_rv_feature_state.c
firmware/esp32-csi-node/tests/host/esp_err.h (shim)
firmware/esp32-csi-node/tests/host/.gitignore
Modified:
adaptive_controller.c — includes pure decide.c; emit_feature_state()
wired into fast loop (200 ms = 5 Hz)
rv_radio_ops_esp32.c — get_health() fills pkt_yield + send_fail
csi_collector.{c,h} — pkt_yield/send_fail accessors (ADR-081 L1)
rv_feature_state.h — packed size corrected to 60 bytes
(was incorrectly 80 in initial commit)
main.c — mock binding registered under mock CSI
CMakeLists.txt — rv_radio_ops_mock.c under CSI_MOCK_ENABLED
scripts/validate_qemu_output.py — 3 new ADR-081 checks (17/18/19)
docs/adr/ADR-081-*.md — status → Accepted (partial);
implementation-status matrix; measured
benchmarks (decide 3.2 ns, CRC32 614 ns);
bandwidth 300 B/s @ 5 Hz (99.7% vs raw);
verification section
CHANGELOG.md — artifact-level entries
Tests (host, gcc -O2 -std=c11):
test_adaptive_controller: 18/18 pass, decide() = 3.2 ns/call
test_rv_feature_state: 15/15 pass, CRC32(56 B) = 614 ns/pkt, 87 MB/s
sizeof(rv_feature_state_t) == 60 asserted
IEEE CRC32 known vectors verified
Deferred (tracked in ADR-081 roadmap Phase 3/4):
Layer 3 mesh-plane message types, role-assignment FSM, Rust-side mirror
trait in crates/wifi-densepose-hardware/src/radio_ops.rs.
* ADR-081: Layer 3 mesh plane + Rust mirror trait — all 5 layers landed
Fully implements the remaining deferred pieces of the adaptive CSI mesh
firmware kernel. All 5 layers (Radio Abstraction, Adaptive Controller,
Mesh Sensing Plane, On-device Feature Extraction, Rust handoff) are
now implemented and host-tested end-to-end.
Layer 3 — Mesh Sensing Plane (firmware/esp32-csi-node/main/rv_mesh.{h,c}):
* 4 node roles: Unassigned / Anchor / Observer / FusionRelay / Coordinator
* 7 message types: TIME_SYNC, ROLE_ASSIGN, CHANNEL_PLAN,
CALIBRATION_START, FEATURE_DELTA, HEALTH, ANOMALY_ALERT
* 3 auth classes: None / HMAC-SHA256-session / Ed25519-batch
* Payload types: rv_node_status_t (28 B), rv_anomaly_alert_t (28 B),
rv_time_sync_t (16 B), rv_role_assign_t (16 B),
rv_channel_plan_t (24 B), rv_calibration_start_t (20 B)
* 16-byte envelope + payload + IEEE CRC32 trailer
* Pure rv_mesh_encode()/rv_mesh_decode() plus typed convenience encoders
* rv_mesh_send_health() + rv_mesh_send_anomaly() helpers
Controller wiring (adaptive_controller.c):
* Slow loop (30 s default) now emits HEALTH
* apply_decision() emits ANOMALY_ALERT on transitions to ALERT /
DEGRADED
* Role + mesh epoch tracked in module state; epoch bumps on role
change
Layer 5 — Rust mirror (crates/wifi-densepose-hardware/src/radio_ops.rs):
* RadioOps trait mirrors rv_radio_ops_t vtable
* MockRadio backend for offline tests
* MeshHeader / NodeStatus / AnomalyAlert types mirror rv_mesh.h
* Byte-identical IEEE CRC32 (poly 0xEDB88320) verified against
firmware test vectors (0xCBF43926 for "123456789")
* decode_mesh / decode_node_status / decode_anomaly_alert / encode_health
* 8 unit tests, including mesh_constants_match_firmware which asserts
MESH_MAGIC/VERSION/HEADER_SIZE/MAX_PAYLOAD match rv_mesh.h
byte-for-byte
* Exported from lib.rs
* signal/ruvector/train/mat crates untouched — satisfies ADR-081
portability acceptance test
Tests (all passing):
test_adaptive_controller: 18/18 (C, decide() 3.2 ns/call)
test_rv_feature_state: 15/15 (C, CRC32 87 MB/s)
test_rv_mesh: 27/27 (C, roundtrip 1.0 µs)
radio_ops::tests (Rust): 8/8
--- total: 68/68 assertions green ---
Docs:
* ADR-081 status flipped to Accepted
* Implementation-status matrix updated; L3 + Rust mirror both
marked Implemented
* Benchmarks table extended with rv_mesh encode+decode roundtrip
* Verification section updated with cargo test invocation
* CHANGELOG: two new entries for L3 mesh plane + Rust mirror
Remaining follow-ups (Phase 3.5 polish, not blocking):
* Mesh RX path (UDP listener + dispatch) on the firmware
* Ed25519 signing for CHANNEL_PLAN / CALIBRATION_START
* Hardware validation on COM7
* Add test_rv_mesh to host-test .gitignore
Fixes an untracked-file warning from the repo stop-hook: the compiled
binary was built by make but the .gitignore update was missed in
|
||
|
|
425f0e6aac |
fix(firmware): defensive node_id capture prevents runtime clobber (#390)
Users on multi-node ESP32 deployments have been reporting for months
that their provisioned `node_id` reverts to the Kconfig default of `1`
in UDP frames and the `csi_collector` init log, despite boot showing:
nvs_config: NVS override: node_id=4
main: ESP32-S3 CSI Node (ADR-018) - Node ID: 4
csi_collector: CSI collection initialized (node_id=1, channel=11)
See #232, #375, #385, #386, #390. The root memory-corruption path for
the `g_nvs_config.node_id` byte has not been definitively isolated
(does not reproduce on my attached ESP32-S3 running current source
and the v0.6.0 release binary), but the UDP frame header can be made
tamper-proof regardless:
1. `csi_collector_init()` now captures `g_nvs_config.node_id` into a
module-local `static uint8_t s_node_id` at init time.
2. `csi_serialize_frame()` reads `buf[4]` from `s_node_id`, not from
the global - so any later corruption of `g_nvs_config` cannot
affect outgoing CSI frames.
3. All other consumers (`edge_processing.c` x3, `wasm_runtime.c`,
`display_ui.c`, `main.c swarm_bridge_init`) now go through a new
`csi_collector_get_node_id()` accessor instead of reading the
global directly.
4. A canary at end-of-init logs `WARN` if `g_nvs_config.node_id`
already diverges from the captured value - this will pinpoint
the corruption path if it happens on a user's device.
Hardware validation on attached ESP32-S3 (COM8):
- NVS loads node_id=2
- Boot log: `main: ... Node ID: 2`
- NEW log: `csi_collector: Captured node_id=2 at init (defensive
copy for #232/#375/#385/#390)`
- Init log: `csi_collector: CSI collection initialized (node_id=2)`
- UDP frame byte[4] = 2 (verified via socket sniffer, 15/15 packets)
This is defense in depth - it shields the UDP frame from whatever
upstream bug is clobbering the struct. When a user hits the original
bug, the canary WARN will help isolate the root cause.
Refs #232 #375 #385 #386 #390
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
8a84748a83 |
fix(firmware): use NVS node_id instead of Kconfig constant (#279)
CONFIG_CSI_NODE_ID (compile-time, always 1) was hardcoded in 6 places: CSI frame serialization, compressed frames, vitals packets, WASM output packets, and display UI. NVS provisioning wrote the correct node_id but it was never used at runtime. Fixed all occurrences to use g_nvs_config.node_id: - csi_collector.c: frame header + log message - edge_processing.c: compressed frame + vitals packet - wasm_runtime.c: WASM output packet - display_ui.c: system info display This means --node-id 0/1/2 provisioning now actually works for multi-node mesh deployments. Closes #279 Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
d793c1f49f |
feat(firmware): --channel and --filter-mac provisioning (ADR-060)
- provision.py: add --channel (CSI channel override) and --filter-mac (AA:BB:CC:DD:EE:FF format) arguments with validation - nvs_config: add csi_channel, filter_mac[6], filter_mac_set fields; read from NVS on boot - csi_collector: auto-detect AP channel when no NVS override is set; filter CSI frames by source MAC when filter_mac is configured - ADR-060 documents the design and rationale Fixes #247, fixes #229 |
||
|
|
952f27a1ce |
fix(firmware): enable CSI in sdkconfig and add build guard (ADR-057)
The committed sdkconfig had CONFIG_ESP_WIFI_CSI_ENABLED disabled, causing all builds to crash at runtime with "CSI not enabled in menuconfig". Root cause: sdkconfig.defaults.template existed but ESP-IDF only reads sdkconfig.defaults (no .template suffix). Fixes: - Add sdkconfig.defaults with CONFIG_ESP_WIFI_CSI_ENABLED=y - Add #error compile guard in csi_collector.c to prevent recurrence - Fix NVS encryption default (requires eFuse, breaks clean builds) Verified: Docker build + flash to ESP32-S3 + CSI callbacks confirmed. Closes #241 Relates to #223, #238, #234, #210, #190 Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
ce171696b2 |
fix: rate-limit CSI sends and add ENOMEM backoff to prevent crash (#132)
The CSI callback fires for every WiFi frame in promiscuous mode (100-500+ fps). Each call invoked sendto() synchronously, exhausting lwIP packet buffers (errno 12 = ENOMEM). The rapid-fire failures cascaded into a LoadProhibited guru meditation crash. Two fixes: 1. csi_collector.c: Rate-limit UDP sends to 50 Hz (20ms interval). CSI frames arriving between sends are dropped — the sensing pipeline only needs 20-50 Hz. 2. stream_sender.c: When sendto fails with ENOMEM, suppress further sends for 100ms to let lwIP reclaim buffers. Logs the backoff event and resumes automatically. Closes #127 |
||
|
|
4b1005524e |
feat: complete vendor repos, add edge intelligence and WASM modules
- Add 154 missing vendor files (gitignore was filtering them) - vendor/midstream: 564 files (was 561) - vendor/sublinear-time-solver: 1190 files (was 1039) - Add ESP32 edge processing (ADR-039): presence, vitals, fall detection - Add WASM programmable sensing (ADR-040/041) with wasm3 runtime - Add firmware CI workflow (.github/workflows/firmware-ci.yml) - Add wifi-densepose-wasm-edge crate for edge WASM modules - Update sensing server, provision.py, UI components Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
915943cef4 |
feat: ESP32 CSI MAC address filtering with NVS/Kconfig support (#101)
* feat: add MAC address filter for ESP32 CSI collection In multi-AP environments, CSI frames from different access points get mixed together, corrupting the sensing signal. Add transmitter MAC filtering so only frames from a specified AP are processed. Implementation: - csi_collector: filter in wifi_csi_callback by comparing info->mac against configured MAC; log transmitter MAC in periodic debug output - csi_collector_set_filter_mac(): runtime API to enable/disable filter - Kconfig: CSI_FILTER_MAC option (format "AA:BB:CC:DD:EE:FF") - NVS: "filter_mac" 6-byte blob overrides Kconfig at runtime - nvs_config: parse Kconfig MAC string at boot, load NVS override - main: apply filter from config after csi_collector_init() When no filter is configured (default), behavior is unchanged — all transmitter MACs are accepted for backward compatibility. Fixes #98 Co-Authored-By: claude-flow <ruv@ruv.net> * chore: add CLAUDE.local.md to .gitignore Local machine configuration (ESP-IDF paths, COM port, build instructions) should not be committed to the repository. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
303871275b |
feat: ADR-029/031 TDM sensing protocol, channel hopping, and NVS config
Implement the hardware and firmware portions of RuvSense (ADR-029) and RuView (ADR-031) for multistatic WiFi sensing: Rust (wifi-densepose-hardware): - TdmSchedule: uniform slot assignments with configurable cycle period, guard intervals, and processing window (default 4-node 20 Hz) - TdmCoordinator: manages sensing cycles, tracks per-slot completion, cumulative clock drift compensation (±10 ppm over 50 ms = 0.5 us) - SyncBeacon: 16-byte wire format for cycle synchronization with drift correction offsets - TdmSlotCompleted event for aggregator notification - 18 unit tests + 4 doctests, all passing Firmware (C, ESP32): - Channel-hop table in csi_collector.c (s_hop_channels, configurable via csi_collector_set_hop_table) - Timer-driven channel hopping via esp_timer at dwell intervals - NDP frame injection stub via esp_wifi_80211_tx() - Backward-compatible: hop_count=1 disables hopping entirely - NVS config extension: hop_count, chan_list, dwell_ms, tdm_slot, tdm_node_count with bounds validation and Kconfig fallback defaults Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
92a5182dc3 |
feat(adr-018): ESP32-S3 firmware, Rust aggregator, and live CSI pipeline
Complete end-to-end WiFi CSI capture pipeline verified on real hardware: - ESP32-S3 firmware: WiFi STA + promiscuous mode CSI collection, ADR-018 binary serialization, UDP streaming at ~20 Hz - Rust aggregator CLI binary (clap): receives UDP frames, parses with Esp32CsiParser, prints per-frame summary (node, seq, rssi, amp) - UDP aggregator module with per-node sequence tracking and drop detection - CsiFrame bridge to detection pipeline (amplitude/phase/SNR conversion) - Python ESP32 binary parser with UDP reader - Presence detection confirmed: motion score 10/10 from live CSI variance Hardware verified: ESP32-S3-DevKitC-1 (CP2102, MAC 3C:0F:02:EC:C2:28), Docker ESP-IDF v5.2 build, esptool 5.1.0 flash, 20 Rust + 6 Python tests pass. Co-Authored-By: claude-flow <ruv@ruv.net> |