From aa54abdb50b297afb5d4327dd6916238f7005570 Mon Sep 17 00:00:00 2001 From: ruv Date: Thu, 11 Jun 2026 08:30:43 -0400 Subject: [PATCH] fix(firmware): c6_sync_espnow IDF v5.5 send-callback guard + B1 HE-LTF resolution (#1005) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Espressif backported the esp_now_send_cb_t signature change to v5.5 (esp_now_send_info_t = wifi_tx_info_t there), so the #944 guard must be ESP_IDF_VERSION >= VAL(5,5,0), not MAJOR >= 6. Validated on this repo's hardware toolchain: - WITHOUT fix, IDF v5.5.2 esp32c6 build fails with the reporter's exact incompatible-pointer error at c6_sync_espnow.c:199 (reproduced) - WITH fix, clean build on IDF v5.5.2 (esp32c6) AND IDF v5.4 (regression) Docs: WITNESS-LOG-110 §B1 marked RESOLVED WITH MEASUREMENT (external, @stuinfla, issue #1005): IDF v5.4 driver downconverts HE->HT; v5.5.2 delivers true HE-LTF (532B / 256 bins / 242 tones, PPDU 0x01 HE-SU). ADR-110 capability table updated accordingly. Co-Authored-By: claude-flow --- docs/WITNESS-LOG-110.md | 2 +- docs/adr/ADR-110-esp32-c6-firmware-extension.md | 2 +- firmware/esp32-csi-node/main/c6_sync_espnow.c | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/WITNESS-LOG-110.md b/docs/WITNESS-LOG-110.md index 2b6dbcfb..e85fe53c 100644 --- a/docs/WITNESS-LOG-110.md +++ b/docs/WITNESS-LOG-110.md @@ -57,7 +57,7 @@ This witness separates what was **empirically observed on real silicon today** f | # | Claim | Why it's not verified | |---|---|---| -| **B1** | "Wi-Fi 6 HE-LTF: 242 subcarriers per HE20 frame" | The only AP in range (`ruv.net`) is 11n-only. Every captured frame is 128 bytes = 64 subcarriers (HT-LTF, `ppdu_type=0`). No HE-SU/HE-MU/HE-TB observed. Even if an 11ax AP were available, **whether ESP-IDF v5.4's CSI callback exposes HE-LTF subcarriers via `wifi_csi_info_t.buf` is an open question** — the public API was designed for HT-LTF, and the driver may quietly downconvert. **Validate by capturing CSI against an 11ax AP and comparing `info->len` between HT and HE frames.** | +| **B1** | "Wi-Fi 6 HE-LTF: 242 subcarriers per HE20 frame" | The only AP in range (`ruv.net`) is 11n-only. Every captured frame is 128 bytes = 64 subcarriers (HT-LTF, `ppdu_type=0`). No HE-SU/HE-MU/HE-TB observed. Even if an 11ax AP were available, **whether ESP-IDF v5.4's CSI callback exposes HE-LTF subcarriers via `wifi_csi_info_t.buf` is an open question** — the public API was designed for HT-LTF, and the driver may quietly downconvert. **Validate by capturing CSI against an 11ax AP and comparing `info->len` between HT and HE frames.**

**RESOLVED WITH MEASUREMENT (2026-06-11, external — issue #1005, production deployment by @stuinfla):** the open question is answered in both directions. **IDF v5.4's driver blob downconverts** (148 B / 64-subcarrier HT frames, PPDU byte 0x00, on a confirmed-HE link); **IDF v5.5.2 delivers true HE-LTF** — 532 B frames = 256 bins (242 active HE20 tones), PPDU byte 0x01 (HE-SU), ~90% of frames, same board/AP/link. Setup: XIAO ESP32-C6 → hostapd on Intel AX210, 2.4 GHz ch 6, `ieee80211ax=1`. No firmware change required (`acquire_csi_su=1` was already set); the gate was purely the IDF driver version. Three C6 nodes ran this mode simultaneously with ADR-110 ESP-NOW sync. Requires the issue-#1005 version-guard fix in `c6_sync_espnow.c` to build on v5.5.x. | | **B2** | "TWT-bounded deterministic CSI cadence (10 ms wake)" | No 11ax AP in range. The TWT setup *call* was exercised live and the graceful fallback path is now correct (A9), but the agreement itself was never accepted. **Validate by associating with an 11ax AP that has TWT Responder=1, then capturing the timestamped CSI cadence vs the wall clock.** | | **B3** | "±100 µs cross-node alignment over 802.15.4" | 3 boards initialized their radios with correct EUIs (A4/A5), but **none stepped down from candidate-leader to follower** during repeated 35-second multi-board captures.

**Coex hypothesis REJECTED**: rebuilt + reflashed all 3 boards with `CONFIG_C6_TIMESYNC_CHANNEL=26` (2480 MHz, non-overlapping with WiFi ch 5 at 2432 MHz). Result identical: 3× candidate, 0× "stepping down". So 2.4 GHz radio coex was NOT the cause.

**Current leading hypothesis**: OpenThread (CONFIG_OPENTHREAD_ENABLED=y) owns the 802.15.4 radio when its stack is initialized — our weak-symbol overrides of `esp_ieee802154_receive_done` / `_transmit_done` may never be called because OpenThread registers strong handlers. Validation in progress: rebuilding with `CONFIG_OPENTHREAD_ENABLED=n` (raw 802.15.4 only, our beacon protocol is private — no need for the Thread stack). If leader election fires under raw-15.4-only, hypothesis confirmed.

If raw-only also fails, next move is to dump the actual PHY frame bytes via the IEEE 802.15.4 sniffer mode on a 4th board and diagnose at the frame level. | | **B4** | "~5 µA hibernation for battery seed nodes" | No INA / Joulescope current measurement available on this bench. The shipped code uses `esp_deep_sleep_enable_gpio_wakeup` (ext1 path, ESP-IDF default ~10 µA), not a true LP-core polling program. The 5 µA number is the C6 datasheet figure for ULP-level hibernation, not a measured value. **Validate by hooking an INA219/INA226 between the dev board's 3V3 rail and the regulator output, then averaging current over a 60-second cycle with the LP-core armed.** | diff --git a/docs/adr/ADR-110-esp32-c6-firmware-extension.md b/docs/adr/ADR-110-esp32-c6-firmware-extension.md index 4d325bf2..3905f167 100644 --- a/docs/adr/ADR-110-esp32-c6-firmware-extension.md +++ b/docs/adr/ADR-110-esp32-c6-firmware-extension.md @@ -19,7 +19,7 @@ The production CSI node firmware (`firmware/esp32-csi-node`) was built around th | C6 capability | What it enables for sensing | Why we can't get it on S3 | |---|---|---| -| **802.11ax (Wi-Fi 6) HE-LTF CSI** | 242 subcarriers per HE20 frame (vs 52 for HT-LTF), HE-MU/HE-TB PPDU types, OFDMA-aware channel sounding | S3 radio is HT-only (n) | +| **802.11ax (Wi-Fi 6) HE-LTF CSI** | 242 subcarriers per HE20 frame (vs 52 for HT-LTF), HE-MU/HE-TB PPDU types, OFDMA-aware channel sounding. **Hardware-confirmed 2026-06-11** (issue #1005, external production deployment): requires **ESP-IDF ≥ 5.5** — the v5.4 driver blob silently downconverts to 64-subcarrier HT even on a confirmed-HE link; v5.5.2 delivers 532 B frames = 256 bins (242 active tones), PPDU 0x01 (HE-SU). See WITNESS-LOG-110 §B1 (resolved). | S3 radio is HT-only (n) | | **802.15.4 (Thread / Zigbee)** | Cross-node time-sync over a separate radio — frees Wi-Fi airtime for CSI, ±100 µs alignment possible without coordination traffic on the sensing channel | S3 has no 802.15.4 | | **TWT (Target Wake Time)** | Sensor negotiates a deterministic wake slot with the AP; CSI cadence becomes scheduler-bounded instead of opportunistic | Requires 802.11ax — S3 can't speak it | | **LP-core + hibernation (~5 µA)** | Always-on motion gate runs on a separate RISC-V LP core in deep sleep; HP core stays off until a real event | S3 ULP is FSM-only, ~10 µA floor | diff --git a/firmware/esp32-csi-node/main/c6_sync_espnow.c b/firmware/esp32-csi-node/main/c6_sync_espnow.c index 39c5625f..83581229 100644 --- a/firmware/esp32-csi-node/main/c6_sync_espnow.c +++ b/firmware/esp32-csi-node/main/c6_sync_espnow.c @@ -151,9 +151,13 @@ static void on_recv(const uint8_t *src_mac, const uint8_t *data, int len) * void (*)(const esp_now_send_info_t *tx_info, esp_now_send_status_t status) * Both signatures ignore the address-side argument here — we only inspect * `status` to bump the TX-fail counter — so the body is identical; only the - * function-pointer type differs. ESP_IDF_VERSION_MAJOR is the canonical guard. + * function-pointer type differs. + * + * Issue #1005: Espressif backported the new signature to v5.5 + * (`esp_now_send_info_t` = typedef of `wifi_tx_info_t` there), so the guard + * must be the full version triple, not ESP_IDF_VERSION_MAJOR. */ -#if ESP_IDF_VERSION_MAJOR >= 6 +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) static void on_send(const esp_now_send_info_t *tx_info, esp_now_send_status_t status) { (void)tx_info;