Skip to content

fix: ESP32 CSI 0pps (#521), aggregator sibling magics (#517), version.txt (#505) + fix-marker CI guard - #526

Merged
ruvnet merged 4 commits into
mainfrom
fix/esp32-issues-505-517-521
May 11, 2026
Merged

fix: ESP32 CSI 0pps (#521), aggregator sibling magics (#517), version.txt (#505) + fix-marker CI guard#526
ruvnet merged 4 commits into
mainfrom
fix/esp32-issues-505-517-521

Conversation

@ruvnet

@ruvnet ruvnet commented May 11, 2026

Copy link
Copy Markdown
Owner

Three open-issue fixes plus a new CI regression guard. Split into four reviewable commits.

Fixes

fix(esp32) — disable WiFi modem sleep so CSI capture isn't starved — closes #521

csi_collector_init() never called esp_wifi_set_ps(), so the radio stayed 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 — exactly the motion=0.00 presence=0.00 yield=0pps symptom reported on clean multi-node setups. Now forces WIFI_PS_NONE before enabling promiscuous mode (the textbook requirement for reliable CSI capture). 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 --duty-cycle <100 is used. Builds clean for esp32s3 (idf.py build, 48% flash free). Needs hardware verification on a real ESP32-S3 before the next v*-esp32 tag.

fix(hardware) — aggregator tolerates sibling RuView UDP packet magics — closes #517

The firmware multiplexes 0xC51100020xC5110007 (vitals, feature, fused, compressed, feature-state, temporal) onto the same UDP port as ADR-018 raw CSI frames (0xC5110001). Esp32CsiParser only knew 0xC5110001, so the standalone aggregator binary printed parse error: Invalid magic: expected 0xc5110001, got 0xc5110002 for every vitals packet — noise, not data loss. Adds the sibling-magic constants + ruview_sibling_packet_name(), classifies recognized siblings before the CSI-frame length gate, and returns ParseError::NonCsiPacket { magic, kind } instead of InvalidMagic. The aggregator CLI now skips them quietly (--verbose[skipped ADR-039 edge vitals packet — not a CSI frame]); the library-level CsiAggregator already dropped them silently. New regression tests cover all seven magics. cargo test -p wifi-densepose-hardware --no-default-features → 116 passed.

fix(firmware) — bump version.txt to 0.6.4 + CI guard for tag↔version — closes #505

version.txt on main was still 0.6.2; CMake reads PROJECT_VER from it, so esp_app_get_description()->version (and the boot log) reported 0.6.2 for any source build, and the v0.6.3-esp32 release binary internally identified as 0.6.2. Bumps version.txt0.6.4 and adds a version-guard job to firmware-ci.yml that runs on v*-esp32 tag pushes and fails the run when the tag's X.Y.Zversion.txt.

New: ci — fix-marker regression guard (witness-style)

A fast per-PR gate that asserts previously-shipped fixes are still present in the tree — the CI analogue of the ruflo witness fix-marker system, but self-contained (no plugin dependency, reviewable as plain JSON). Complements the heavier checks (firmware build, deterministic pipeline proof, release witness bundle) by catching the silent-revert class of regression that build+test wouldn't.

$ python scripts/check_fix_markers.py
Fix-marker regression guard - 11 marker(s)
  [PASS] RuView#396 ...  [PASS] RuView#521 ...  ... (all 11) ...
All 11 fix markers present.

If a fix is intentionally removed, you update scripts/fix-markers.json in the same PR with a rationale — the diff becomes the audit trail.

Pre-merge notes

🤖 Generated with claude-flow

ruvnet added 4 commits May 11, 2026 10:47
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>
…517)

The ESP32 firmware multiplexes several wire packet types onto the same
UDP port as ADR-018 raw CSI frames (magic 0xC5110001):

  0xC5110002  ADR-039 edge vitals (32 B)
  0xC5110003  ADR-069 feature vector
  0xC5110004  ADR-063 fused vitals
  0xC5110005  ADR-039 compressed CSI
  0xC5110006  ADR-081 feature state
  0xC5110007  ADR-095/#513 temporal classification

Esp32CsiParser only knew 0xC5110001, so the standalone `aggregator`
binary printed "parse error: Invalid magic: expected 0xc5110001, got
0xc5110002" for every vitals packet. No CSI data was lost — just noise.

Add the sibling-magic constants + ruview_sibling_packet_name(), classify
recognized siblings before the CSI-frame length gate, and return a new
ParseError::NonCsiPacket { magic, kind } instead of InvalidMagic. The
`aggregator` CLI now skips them quietly (logs "[skipped ADR-039 edge
vitals packet — not a CSI frame]" only with --verbose); the library-level
CsiAggregator already dropped them silently. New regression tests cover
all seven magics.

Closes #517

Co-Authored-By: claude-flow <ruv@ruv.net>
…atch (#505)

version.txt on main was still 0.6.2. CMake reads PROJECT_VER from it, so
esp_app_get_description()->version (and the boot log line) reported 0.6.2
for any source build — and v0.6.3-esp32 shipped a release binary that
internally identified as 0.6.2 because the bump never landed on main.

  - version.txt: 0.6.2 -> 0.6.4 (matches the latest release tag)
  - firmware-ci.yml: new `version-guard` job that runs on v*-esp32 tag
    pushes and fails the run if the tag's X.Y.Z != version.txt, so a
    future release can't ship a mislabeled binary.

Closes #505

Co-Authored-By: claude-flow <ruv@ruv.net>
Adds a fast per-PR gate that asserts previously-shipped fixes are still
present in the tree — the CI analogue of the ruflo witness fix-marker
system, but self-contained (no plugin dependency, reviewable as plain
JSON). Complements the heavier checks (firmware build, deterministic
pipeline proof, release witness bundle) by catching the silent-revert
class of regression that build+test wouldn't.

  - scripts/fix-markers.json   manifest: 11 markers (RuView#396, #521,
    #517, #505, #354, #263, #266/#321, #265, #232/#375/#385/#386/#390,
    ADR-028 proof + witness bundle). Each has files / require (literal
    substring or /regex/) / optional forbid / rationale / ref.
  - scripts/check_fix_markers.py  stdlib-only checker. Exit 0 clean /
    1 regression / 2 bad manifest. Modes: --list, --json, --only ID.
  - .github/workflows/fix-regression-guard.yml  runs on PR + push to
    main/master; gates on the checker and writes the result table into
    the run summary + an artifact.

If a fix is intentionally removed, update scripts/fix-markers.json in the
same PR with a rationale — the diff becomes the audit trail.

Co-Authored-By: claude-flow <ruv@ruv.net>
@ruvnet

ruvnet commented May 11, 2026

Copy link
Copy Markdown
Owner Author

Hardware-verified on a real ESP32-S3 (COM8, d0:cf:13:44:01:84)

Flashed this branch's firmware build (idf.py reconfigure && build && flash) and watched the serial log:

I (253) app_init: App version:      0.6.4
I (257) app_init: Compile time:     May 11 2026 11:09:43
I (389) main: ESP32-S3 CSI Node (ADR-018) — v0.6.4 — Node ID: 1
I (629) wifi:connected with ruv.net, ... channel 5, BW20, ...
I (1809) wifi:Set ps type: 0, coexist: 0
I (1809) csi_collector: WiFi modem sleep disabled (WIFI_PS_NONE) for CSI capture
I (1819) csi_collector: Promiscuous mode enabled (MGMT-only, RuView#396)
I (1829) csi_collector: CSI cb #1: len=128 rssi=-63 ch=5
I (1869) csi_collector: CSI cb #2: len=128 rssi=-51 ch=5
...
I (5159) adaptive_ctrl: medium tick: state=8 yield=0pps  motion=0.00 presence=0.00 rssi=-42   ← partial first 1s window
I (6159) adaptive_ctrl: medium tick: state=5 yield=16pps motion=0.00 presence=0.00 rssi=-41
I (7159) adaptive_ctrl: medium tick: state=5 yield=15pps motion=0.00 presence=0.00 rssi=-38
I (10159) adaptive_ctrl: medium tick: state=6 yield=16pps motion=1.00 presence=11.66 rssi=-38
... steady 13–16 pps thereafter, presence detection live ...

Checklist item "Hardware-verify #521" — done.

🤖 Generated with claude-flow

@ruvnet
ruvnet merged commit f2e3a6a into main May 11, 2026
32 of 46 checks passed
@ruvnet
ruvnet deleted the fix/esp32-issues-505-517-521 branch May 11, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant