Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f23e34e
feat(firmware): ESP32-C6 target — Wi-Fi 6 / 802.15.4 / TWT / LP-core …
ruvnet May 23, 2026
6652384
fix(c6): TWT INVALID_ARG graceful + ch26 + diagnostic counters (ADR-1…
ruvnet May 23, 2026
4c39e28
fix(c6): PAN-ID match in 15.4 beacon + expanded D1 diagnostic record
ruvnet May 23, 2026
f8a2e36
fix(witness): redact secrets from bundled verify.py output (SECURITY)
ruvnet May 23, 2026
88be283
feat(c6): ESP-NOW cross-node sync — D1 workaround for broken 15.4 RX
ruvnet May 23, 2026
3959fab
feat(rust): host-side decode for ADR-018 byte 18-19 (ADR-110 closure)
ruvnet May 23, 2026
8eaa92c
feat(python): host-side decode for ADR-018 byte 18-19 (ADR-110)
ruvnet May 23, 2026
b808a63
witness: ESP-NOW 120s soak — 1151 tx 0 fail, 9.6/s, no crash
ruvnet May 23, 2026
89972c0
docs(changelog): expand ADR-110 entry with wave 2-5 additions
ruvnet May 23, 2026
fc75a8a
test(fuzz): extend csi_serialize fuzz harness for ADR-110 byte 18-19
ruvnet May 23, 2026
9de34ba
docs(adr): index ADR-110 in Hardware and firmware section
ruvnet May 23, 2026
553b07d
docs(readme): tighten ESP32-C6 row to match empirical witness (ADR-110)
ruvnet May 23, 2026
e255b7d
docs(firmware): README acknowledges dual S3+C6 target (ADR-110)
ruvnet May 23, 2026
9a46fc8
witness: ESP-NOW 300 s soak — 2951 tx 0 fail (2.5x sample)
ruvnet May 23, 2026
3133be6
docs(adr-110): add reviewer one-page guide
ruvnet May 23, 2026
561647b
docs(readme): link new ADR-110 reviewer guide + update soak total
ruvnet May 23, 2026
948768b
feat(firmware): v0.6.7-esp32 — real LP-core program + C6 soft-AP HE/T…
ruvnet May 23, 2026
756bfc0
docs(readme,user-guide): record v0.6.7 LP-core + soft-AP HE/TWT addit…
ruvnet May 23, 2026
83f20f7
witness(ADR-110): v0.6.7 live silicon evidence — A0.4 + A0.5
ruvnet May 23, 2026
f9aad75
witness+opt: ADR-110 §A0.6 — IDF v5.4 soft-AP HE gap, swarm warnings
ruvnet May 23, 2026
572e09a
witness(ADR-110 §A0.7): ESP-NOW cross-board mesh — leader election + …
ruvnet May 23, 2026
d636604
docs(user-guide): point 4MB-flash flow at the v0.6.7 S3 4MB binary
ruvnet May 23, 2026
676297c
witness(ADR-110 §A0.8): 4-minute mesh soak — quantified stability + m…
ruvnet May 23, 2026
f41f5fc
feat(c6_sync_espnow): EMA-smooth cross-board offset, expose via get_e…
ruvnet May 23, 2026
e5c3b27
witness(ADR-110 §A0.10): EMA suppression quantified — 3.95x, ≤100 µs …
ruvnet May 23, 2026
503411a
release(firmware): bump to v0.6.8-esp32 — ESP-NOW mesh EMA smoother
ruvnet May 23, 2026
6ff155a
feat(csi): emit ADR-110 §A0.11 sync-packet every 20 CSI frames
ruvnet May 23, 2026
d2effcc
witness(ADR-110 §A0.12): sync-packet wired + verified live on both bo…
ruvnet May 23, 2026
4e1b62a
release(firmware): v0.6.9-esp32 — sync-packet wired, CONFIG_C6_SYNC_E…
ruvnet May 23, 2026
e69572f
fix(csi): ADR-018 byte 19 bit 4 now signals ESP-NOW sync too (not jus…
ruvnet May 23, 2026
d199279
release(firmware): v0.7.0-esp32 major — ADR-110 firmware-side substra…
ruvnet May 23, 2026
123df30
fix(firmware): change promiscuous filter to ALL frames for CSI capture
volkantasci Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions .github/workflows/firmware-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
echo "version.txt matches the release tag."

build:
name: Build ESP32-S3 Firmware (${{ matrix.variant }})
name: Build firmware (${{ matrix.target }} / ${{ matrix.variant }})
runs-on: ubuntu-latest
container:
image: espressif/idf:v5.4
Expand All @@ -47,17 +47,27 @@ jobs:
matrix:
include:
- variant: 8mb
target: esp32s3
sdkconfig: sdkconfig.defaults
partition_table_name: partitions_display.csv
size_limit_kb: 1100
artifact_app: esp32-csi-node.bin
artifact_pt: partition-table.bin
- variant: 4mb
target: esp32s3
sdkconfig: sdkconfig.defaults.4mb
partition_table_name: partitions_4mb.csv
size_limit_kb: 1100
artifact_app: esp32-csi-node-4mb.bin
artifact_pt: partition-table-4mb.bin
# ADR-110: ESP32-C6 research target (Wi-Fi 6 / 802.15.4 / TWT / LP-core)
- variant: c6-4mb
target: esp32c6
sdkconfig: sdkconfig.defaults
partition_table_name: partitions_4mb.csv
size_limit_kb: 1100
artifact_app: esp32-csi-node-c6.bin
artifact_pt: partition-table-c6.bin

steps:
- uses: actions/checkout@v4
Expand All @@ -66,12 +76,22 @@ jobs:
working-directory: firmware/esp32-csi-node
run: |
. $IDF_PATH/export.sh
if [ "${{ matrix.variant }}" != "8mb" ]; then
# 4mb variant supplies its own sdkconfig.defaults overlay.
# c6-4mb variant relies on the auto-applied sdkconfig.defaults.esp32c6
# overlay (ESP-IDF auto-loads sdkconfig.defaults.$TARGET when present).
if [ "${{ matrix.variant }}" = "4mb" ]; then
cp "${{ matrix.sdkconfig }}" sdkconfig.defaults
fi
idf.py set-target esp32s3
idf.py set-target ${{ matrix.target }}
idf.py build

- name: Build and run host-side ADR-110 unit tests
if: matrix.variant == 'c6-4mb'
working-directory: firmware/esp32-csi-node/test
run: |
make test_adr110
./test_adr110

- name: Verify binary size (< ${{ matrix.size_limit_kb }} KB gate)
working-directory: firmware/esp32-csi-node
run: |
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
they can be reintroduced with a real implementation.

### Added
- **ESP32-C6 firmware target with Wi-Fi 6 / 802.15.4 / TWT / LP-core support ([ADR-110](docs/adr/ADR-110-esp32-c6-firmware-extension.md), #762).** `firmware/esp32-csi-node` now builds for **both** `esp32s3` (existing production node) and `esp32c6` (new research/seed-node target) from the same source tree — pick via `idf.py set-target esp32c6` and ESP-IDF auto-applies the new `sdkconfig.defaults.esp32c6` overlay. Every C6 module is `#ifdef CONFIG_IDF_TARGET_ESP32C6` gated, so the S3 build is byte-identical to today (no regression).
- **Wi-Fi 6 HE-LTF subcarrier tagging** — `csi_collector.c` now reads `rx_ctrl.cur_bb_format` and writes the PPDU type (0=HT/legacy, 1=HE-SU, 2=HE-MU, 3=HE-TB) into ADR-018 frame byte 18, plus bandwidth flags (20/40 MHz, STBC, 802.15.4-sync-valid) into byte 19. Bytes 18-19 were previously reserved-zero, so old aggregators read them as before — fully backwards compatible. Magic stays `0xC5110001`. Default on via `CONFIG_CSI_FRAME_HE_TAGGING`. First firmware in the open ESP32 ecosystem to tag CSI frames with 11ax PPDU metadata.
- **802.15.4 mesh time-sync** — new `c6_timesync.{h,c}` (262 lines) provides cross-node clock alignment over the C6's separate 802.15.4 radio, freeing WiFi airtime from coordination traffic (directly addresses the ADR-029/030 multistatic synchronization gap). Protocol: lowest EUI-64 wins election, leader broadcasts `TS_BEACON` (`magic=0x54534D45`, leader epoch µs) every 100 ms on channel 15, followers compute `offset = leader_us - local_us` and apply lazily — every CSI frame is stamped with `c6_timesync_get_epoch_us()`. Target alignment ±100 µs. Default on via `CONFIG_C6_TIMESYNC_ENABLE`. Verified initializing at boot on COM6 (`c6_ts: init done: channel=15 EUI=206ef1fffefffe17 leader=yes(candidate)` at +413 ms).
- **TWT (Target Wake Time)** — new `c6_twt.{h,c}` (223 lines) wraps `esp_wifi_sta_itwt_setup` from `esp_wifi_he.h` to negotiate an individual TWT agreement with the AP after STA connect. Replaces today's opportunistic CSI capture with a scheduler-bounded one (default wake interval 10 ms = 100 fps cadence). Graceful NACK fallback: when the AP doesn't support 11ax iTWT, the helper logs and returns OK so the device keeps doing opportunistic CSI just like the S3. Teardown on `WIFI_EVENT_STA_DISCONNECTED` keeps the AP's TWT scheduler clean. Gated on `SOC_WIFI_HE_SUPPORT` (auto-set on C6/C5 chips).
- **LP-core wake-on-motion hibernation** — new `c6_lp_core.{h,c}` (134 lines) arms the C6 LP RISC-V coprocessor as an always-on motion gate; HP core stays in deep sleep until a configurable GPIO wakes it (ext1 deep-sleep wake source in this initial cut, real LP-core program in follow-up). Targets ≤5 µA hibernation current for battery-powered Cognitum Seed nodes (vs the S3's ~10 µA ULP-FSM floor). Opt-in via `CONFIG_C6_LP_CORE_ENABLE` (default off — only enabled on nodes flashed for battery-powered seed duty).
- **Build matrix**: S3 stays `partitions_display.csv` (8 MB + display + WASM), C6 uses `partitions_4mb.csv` (4 MB single OTA, no display, no WASM3, no LCD). C6 final binary 1003 KB (46% partition slack), 9 % smaller than S3 production. Free heap 310 KiB at boot, app_main reached in 343 ms, 802.15.4 stack up in another 70 ms.
- **Why this matters**: opens three research surfaces nobody has published yet — Wi-Fi-6 CSI human pose, multistatic CSI clock alignment over a side-channel radio, and TWT-bounded deterministic CSI cadence. The S3 production fleet keeps shipping the existing capabilities; the C6 is the research / battery-seed expansion target.
- **Docs**: ADR-110 (186 lines, Status=Accepted), tracking issue [ruvnet/RuView#762](https://github.com/ruvnet/RuView/issues/762) with per-phase progress comments, README hardware table + Quick-Start Option 2b, `docs/user-guide.md` full ESP32-C6 section (build, flash, provision, multi-room time-sync, battery seed mode), full empirical record in [`docs/WITNESS-LOG-110.md`](docs/WITNESS-LOG-110.md) with verified / claimed / bugs-fixed / bugs-found sections.
- **Wave 2 follow-up (D1 workaround)**: 5 systematic experiments on 3 live C6 boards confirmed the IDF v5.4 802.15.4 RX path is unfixable from user code (TX works 100 %, RX delivers 0 frames; coex/channel/OpenThread/manual-rearm all ruled out). Pivoted to ESP-NOW for the cross-node sync transport — `main/c6_sync_espnow.{h,c}` is the same TS_BEACON protocol over WiFi peer-to-peer, same `get_epoch_us / is_valid / is_leader` API surface. **120 s single-board soak: 1151 transmits, 0 failures (0.00 %), 9.6 tx/s sustained, no crash or reset.** The 802.15.4 path stays in source as documented-broken (D1) for when the IDF driver gets fixed.
- **Host-side dual-pipeline decoder for ADR-018 byte 18-19** (ADR-110 protocol closure):
- **Rust** (`v2/crates/wifi-densepose-hardware`): new `PpduType` enum (HtLegacy/HeSu/HeMu/HeTb/Unknown) and `Adr018Flags` struct (bw40/stbc/ldpc/ieee802154_sync_valid) on `CsiMetadata`. 6 new deterministic unit tests; **122/122 hardware-crate tests pass**.
- **Python** (`archive/v1/src/hardware/csi_extractor.py`): `HEADER_FMT` extended from `<IBBHIIBB2x` to `<IBBHIIBBBB`; new metadata fields (`ppdu_type`, `he_capable`, `bw40`, `stbc`, `ldpc`, `ieee802154_sync_valid`). 5 new `TestAdr110ByteEncoding` cases; **11/11 parser tests pass**.
- Both decoders match the firmware encoder bit-for-bit. Pre-ADR-110 firmware sends zeros that round-trip as `HtLegacy` + default flags — fully backwards compatible.
- **Security fix** (`scripts/redact-secrets.py` + `generate-witness-bundle.sh`): the Python proof step was echoing `.env` contents into the bundled `verification-output.log` via Pydantic validation errors. Bundle nuked before push; added a `stdin -> stdout` redaction filter covering common token prefixes, long opaque strings, and long hex runs. Verified zero leaks on rebuild.
- **Wave 3 — firmware v0.6.7 (LP-core full + soft-AP HE)**: two software-only unblocks for the hardware-blocked items in WITNESS-LOG-110 §B. (1) **Real LP-core motion-gate program** (`firmware/esp32-csi-node/main/lp_core/main.c` + integration in `c6_lp_core.c`). When `CONFIG_C6_LP_CORE_ENABLE=y`, the LP RISC-V coprocessor now runs a real polling program (configurable cadence via `CONFIG_C6_LP_POLL_PERIOD_US`, default 10 ms) that debounces N consecutive GPIO samples (`CONFIG_C6_LP_DEBOUNCE_SAMPLES`, default 3) and wakes the HP core via `ulp_lp_core_wakeup_main_processor()`. HP entry uses `esp_sleep_enable_ulp_wakeup` + `ESP_SLEEP_WAKEUP_ULP`. Exposes `c6_lp_core_motion_count()` and `c6_lp_core_poll_count()` getters for the witness harness. **Replaces** the v0.6.6 `esp_deep_sleep_enable_gpio_wakeup` ext1 fallback (which floored at ~10 µA, the same as the S3 ULP-FSM). The fallback path stays as the `else` branch so builds without `CONFIG_C6_LP_CORE_ENABLE` keep working unchanged — zero regression for v0.6.6-era fleets. Targets the C6 datasheet ≤5 µA average for battery seed nodes; pending INA/Joulescope measurement to confirm (`WITNESS-LOG-110 §B4`). (2) **Wi-Fi 6 soft-AP with TWT Responder=1** (`c6_softap_he.{h,c}` + `main.c` AP+STA mode switch). When `CONFIG_C6_SOFTAP_HE_ENABLE=y`, one C6 board can act as the iTWT-capable AP the bench is otherwise missing — pair with a second C6-STA board to negotiate real iTWT against a known-cooperative AP and measure deterministic CSI cadence (`WITNESS-LOG-110 §B1/B2`). SSID/PSK/channel configurable via Kconfig defaults or NVS (`softap_ssid`/`softap_psk`/`softap_chan` keys in the `ruview` namespace). Default off so existing nodes are unaffected. **Build artifacts**: S3 8 MB binary 1093 KB (47 % slack), C6 4 MB binary 1019 KB (45 % slack). Tag: `v0.6.7-esp32`.
- **Wave 4 — firmware v0.6.8 (ESP-NOW mesh offset smoother)**: `c6_sync_espnow.c` now maintains an in-firmware exponential-moving-average of the cross-board sync offset (α = 1/8, fixed-point shift, ≈ 8-sample window at the 10 Hz beacon rate). New getter `c6_sync_espnow_get_offset_us_smoothed()`. `c6_sync_espnow_get_epoch_us()` now returns timestamps stamped from the smoothed offset once seeded — every downstream CSI-frame consumer gets bounded-jitter alignment for free, no host-side filter required. **Measured on the bench**: 5-min two-board soak (WITNESS-LOG-110 §A0.10) drops raw offset stdev 411.5 µs → smoothed 104.1 µs (**3.95× suppression** on stdev, 4.70× on peak-to-peak range) while preserving the +30 µs/min crystal-drift trajectory within 2 µs/min. **The ADR-110 §2.4 ≤100 µs multistatic alignment target that v0.6.6 designed is now empirically measured, not just stated.** Cross-board beacon match rate 99.56% over 5 min, 0 TX failures. Binary cost: +32 bytes (one int64, one bool, one getter). Diag log adds `smoothed=…` field. Tag: `v0.6.8-esp32`. **Known wiring gap (deferred)**: `csi_serialize_frame` does not yet stamp frames with `c6_sync_espnow_get_epoch_us()` — the ADR-018 frame format has no timestamp field, and adding one is a breaking change that needs an ADR update. Multistatic CSI fusion will require either an ADR-018 v2 with timestamp, or a separate UDP sync packet keyed off the existing flag bit. Tracked in WITNESS-LOG-110 §A0.11.
- **Real-time CSI introspection / low-latency tap on `wifi-densepose-sensing-server` (ADR-099).**
New `wifi_densepose_sensing_server::introspection` module wires
[midstream](https://github.com/ruvnet/midstream)'s `temporal-attractor` (Lyapunov +
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,28 @@ docker pull ruvnet/wifi-densepose:latest
docker run -p 3000:3000 ruvnet/wifi-densepose:latest
# Open http://localhost:3000

# Option 2: Live sensing with ESP32-S3 hardware ($9)
# Option 2a: Live sensing with ESP32-S3 hardware ($9)
# Flash firmware, provision WiFi, and start sensing:
python -m esptool --chip esp32s3 --port COM9 --baud 460800 \
write_flash 0x0 bootloader.bin 0x8000 partition-table.bin \
0xf000 ota_data_initial.bin 0x20000 esp32-csi-node.bin
python firmware/esp32-csi-node/provision.py --port COM9 \
--ssid "YourWiFi" --password "secret" --target-ip 192.168.1.20

# Option 2b: WiFi 6 + 802.15.4 research sensing with ESP32-C6 ($6-10, ADR-110)
# Same csi-node firmware compiled for the C6 target — picks up the C6
# overlay (sdkconfig.defaults.esp32c6) automatically.
cd firmware/esp32-csi-node
idf.py set-target esp32c6 && idf.py build
idf.py -p COM6 flash
# C6 boot extras (vs S3): HE-LTF subcarrier tagging in ADR-018 bytes 18-19,
# 802.15.4 mesh time-sync on channel 15, TWT setup when the AP supports it,
# opt-in LP-core wake-on-motion for ~5 µA battery seed nodes.
# v0.6.7 adds: real LP-core RISC-V motion-gate program (debounce + motion
# counter) and a Wi-Fi 6 soft-AP with TWT Responder so two C6 boards can
# benchmark real iTWT without buying an 11ax router. Both default off,
# flip CONFIG_C6_{LP_CORE,SOFTAP_HE}_ENABLE to turn them on.

# Option 3: Full system with Cognitum Seed ($140)
# ESP32 streams CSI → bridge forwards to Seed for persistent storage + kNN + witness chain
node scripts/rf-scan.js --port 5006 # Live RF room scan
Expand All @@ -103,7 +117,8 @@ node scripts/mincut-person-counter.js --port 5006 # Correct person counting
> | Option | Hardware | Cost | Full CSI | Capabilities |
> |--------|----------|------|----------|-------------|
> | **ESP32 + Cognitum Seed** (recommended) | ESP32-S3 + [Cognitum Seed](https://cognitum.one) | ~$140 | Yes | Presence, motion, breathing, heart rate, fall detection, multi-person counting, 17-keypoint pose (signed Cog binary), 105-cog catalog, persistent vector store, kNN search, witness chain, MCP proxy |
> | **ESP32 Mesh** | 3-6x ESP32-S3 + WiFi router | ~$54 | Yes | Same capabilities as above without the persistent-memory features |
> | **ESP32 Mesh** | 3-6× ESP32-S3 + WiFi router | ~$54 | Yes | Same capabilities as above without the persistent-memory features |
> | **ESP32-C6 research node** ([ADR-110](docs/adr/ADR-110-esp32-c6-firmware-extension.md), [witness](docs/WITNESS-LOG-110.md), [reviewer guide](docs/ADR-110-REVIEW-GUIDE.md), [firmware v0.6.7](https://github.com/ruvnet/RuView/releases/tag/v0.6.7-esp32)) | ESP32-C6-DevKit ($6–10) | ~$10 | Yes (Wi-Fi 6 capable) | Same CSI pipeline as S3 with the dual-target firmware. **Wire format ready** for HE-LTF PPDU tagging in ADR-018 bytes 18-19 (firmware encoder + Rust + Python decoders verified end-to-end in 17 unit tests), ESP-NOW cross-node sync (4102 tx 0 fail cumulative across 120 s + 300 s soaks), and TWT graceful-NACK fallback (live exercised). **v0.6.7 adds** a real LP-core motion-gate RISC-V program (B4 code path) and a Wi-Fi 6 soft-AP with TWT Responder for two-board iTWT benches (B1/B2 unblock, no 11ax router required). **Hardware-gated for measurement**: HE-LTF live subcarrier capture needs the soft-AP bench or an 11ax AP; ~5 µA LP-core hibernation needs an INA meter to capture; 802.15.4 raw RX is broken in IDF v5.4 (workaround: ESP-NOW transport, shipped). See witness log for the empirical / claimed split. |
> | **Research NIC** | Intel 5300 / Atheros AR9580 | ~$50-100 | Yes | Full CSI with 3x3 MIMO |
> | **Any WiFi** | Windows, macOS, or Linux laptop | $0 | No | RSSI-only: coarse presence and motion (see [tutorial #36](https://github.com/ruvnet/RuView/issues/36)) |
>
Expand Down
Loading