From f9aad7541312a5b952396db4dcb0b84886e97523 Mon Sep 17 00:00:00 2001 From: ruv Date: Sat, 23 May 2026 11:36:09 -0400 Subject: [PATCH] =?UTF-8?q?witness+opt:=20ADR-110=20=C2=A7A0.6=20=E2=80=94?= =?UTF-8?q?=20IDF=20v5.4=20soft-AP=20HE=20gap,=20swarm=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Iter 1 finding from /loop 5m SOTA sprint: two C6 boards now mesh through the c6_softap_he soft-AP (COM12 hosts ruview-c6-twt, COM9 associates), but COM9 lands at phymode(0x3, 11bgn), he:0 — the soft-AP doesn't advertise HE. Confirmed by full grep of components/esp_wifi/include/esp_wifi*.h: the public API exposes ONLY STA-side iTWT/bTWT. There is no esp_wifi_ap_set_he_config, no wifi_he_ap_config_t, no wifi_config_t.ap.he_* field — soft-AP HE/TWT-Responder advertise is not user-controllable on ESP32-C6 in IDF v5.4. Consequence: B1/B2 cannot be measured via the two-C6 path on this IDF release. The c6_softap_he module ships as the in-place hook for any future IDF release that exposes the API; until then a real 11ax router or phone hotspot remains the path. Sharpens the open question from "do we need an 11ax AP?" to "we need either a future IDF AP-side HE config API, or an external 11ax AP". WITNESS-LOG-110 §A0.6 records the parallel boot logs from both boards plus the IDF surface grep evidence. c6_softap_he.c gains an ESP_LOGW at AP-up time so operators understand exactly why STAs land at 11bgn (avoids confusion with the v0.6.6 §A8 graceful-TWT-NACK story). While here: cleared the three -Wunused-variable warnings in swarm_bridge.c that fired on every build (fw_ver, free_heap, presence in heartbeat block). fw_ver now feeds an ESP_LOGI so the boot log names the build; free_heap + heartbeat-presence were dead anyway. Pure ultra-opt: smaller .o files, zero warning noise. Co-Authored-By: claude-flow --- docs/WITNESS-LOG-110.md | 1 + firmware/esp32-csi-node/main/c6_softap_he.c | 24 +++++++++++++-------- firmware/esp32-csi-node/main/swarm_bridge.c | 11 +++++----- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/docs/WITNESS-LOG-110.md b/docs/WITNESS-LOG-110.md index 742344e2..c79b54f4 100644 --- a/docs/WITNESS-LOG-110.md +++ b/docs/WITNESS-LOG-110.md @@ -25,6 +25,7 @@ This witness separates what was **empirically observed on real silicon today** f | **A0.3** | Soft-AP HE/TWT helper compiles | `c6_softap_he.{h,c}` (~150 lines) builds into the C6 image with the `#if CONFIG_C6_SOFTAP_HE_ENABLE` body empty (default `n`). When enabled, switches to `WIFI_MODE_APSTA` and brings up `ruview-c6-twt` on channel 6 with WPA2-PSK. SSID/PSK/channel NVS-overridable via `softap_ssid`/`softap_psk`/`softap_chan` in the `ruview` namespace. | | **A0.4** | **v0.6.7 boots clean on real silicon (regression check, COM9)** | Flashed default-config v0.6.7 to ESP32-C6 on COM9 (`20:6e:f1:17:05:3c`). Boot log captured in `dist/firmware-v0.6.7/COM9-v0.6.7-regression.log`. Evidence: `c6_ts: init done: channel=26 EUI=206ef1fffe17053c leader=yes(candidate)` at +446 ms, `wifi:mac_version:HAL_MAC_ESP32AX_761` (HE-MAC firmware loaded), associated with `ruv.net` at +5206 ms (DHCP `192.168.1.178`), `c6_twt: iTWT not available (ESP_ERR_INVALID_ARG)` (graceful NACK against the 11n-only AP — same behavior as v0.6.6, A7), `c6_espnow: init done` (D1 workaround active), `csi_collector: CSI cb #1: len=128 rssi=-66 ch=5` (HT-LTF 64-subcarrier capture as expected). Zero regression vs v0.6.6 — new code paths default off, observed behavior is byte-for-byte the v0.6.6 path. | | **A0.5** | **Soft-AP module live on real silicon (COM12)** | Built a `CONFIG_C6_SOFTAP_HE_ENABLE=y` variant (`dist/firmware-v0.6.7/esp32-csi-node-c6-4mb-softap.bin`, 1023 KB / 45% slack), flashed to ESP32-C6 on COM12 (`20:6e:f1:17:00:84`). Boot log: `dist/firmware-v0.6.7/COM12-v0.6.7-softap.log`. **Evidence the new module fires**:

`I (556) c6_softap: soft-AP starting: ssid="ruview-c6-twt" channel=6 auth=wpa2-psk`
`I (556) main: C6 soft-AP HE armed on channel 6 (ADR-110 B1/B2)`
`I (636) wifi:mode : sta (20:6e:f1:17:00:84) + softAP (20:6e:f1:17:00:85)`
`I (666) c6_softap: AP started on channel 6`

The IDF assigns the soft-AP MAC at the STA-MAC+1 offset (`...00:85`), standard behavior. **Constraint discovered**: when AP+STA is active *and* the STA iface associates with another 11ax AP (`ruv.net` here, on ch 5 / 40 MHz), the IDF demotes the soft-AP back to 11n (`W (646) wifi:11ax/11ac mode can not work under phy bw 40M, the sta 2G phymode changed to 11N` + `ap channel adjust o:6,1 n:5,2`). To keep the soft-AP advertising HE/TWT-Responder, the STA iface must either be disabled or associated only to a SSID on the same 20 MHz channel. Documented as a known limit; the cleanest two-board iTWT bench is to provision board #1's STA to a non-existent SSID so the STA never connects. | +| **A0.6** | **Two-C6 iTWT bench attempted live — surfaces an IDF v5.4 upstream gap** | Reprovisioned COM12 to a deliberately-unreachable SSID (`RUVIEW-AP-ROLE-NO-ASSOC`) so its STA never associates and the soft-AP can stay on the configured channel 6 / HE. Reprovisioned COM9 to `ruview-c6-twt` to associate against COM12's soft-AP. Parallel boot logs in `dist/firmware-v0.6.7/iter1-{COM9,COM12}-*-role.log`.

**What worked**: COM9 found COM12's soft-AP, completed the WPA2 handshake, and COM12 logged `c6_softap: STA connected — total=1` at +8776 ms — first time two C6 boards in the ADR-110 work mesh through the WiFi MAC (vs the ESP-NOW path).

**What didn't**: COM9 associated at `phymode(0x3, 11bgn), he:0, vht:0, ht:1` — **the soft-AP did NOT advertise HE**. Source of the gap: a full grep of `components/esp_wifi/include/esp_wifi*.h` in IDF v5.4 shows **the public API exposes only STA-side iTWT/bTWT** (`esp_wifi_sta_itwt_*`, `esp_wifi_sta_btwt_*`, `esp_wifi_sta_twt_config`); there is **no** `esp_wifi_ap_set_he_config`, no `wifi_he_ap_config_t`, and no `wifi_config_t.ap.he_*` field. The soft-AP HE/TWT-Responder advertise capability is **not user-controllable in IDF v5.4** for the ESP32-C6.

Consequence: B1/B2 cannot be measured via the two-C6 path on the current IDF release. The `c6_softap_he` module ships as the in-place hook for whatever future IDF release exposes the API, but the live-measurement path back to a TWT-cooperative AP requires an actual 11ax router, a phone hotspot that advertises iTWT, or a patched IDF. **Sharpens the open question from "do we need an 11ax AP?" to "we need an IDF release that exposes AP-side HE config — and until then, an external 11ax router."** | ## A. Empirically verified (real silicon, today) diff --git a/firmware/esp32-csi-node/main/c6_softap_he.c b/firmware/esp32-csi-node/main/c6_softap_he.c index 6b28348e..7cde16f9 100644 --- a/firmware/esp32-csi-node/main/c6_softap_he.c +++ b/firmware/esp32-csi-node/main/c6_softap_he.c @@ -143,19 +143,25 @@ esp_err_t c6_softap_he_start(uint8_t *out_channel) return err; } - /* On IDF v5.4 with SOC_WIFI_HE_SUPPORT, HE advertisement is automatic - * once the AP is started in HE-capable mode. TWT Responder advertise - * is automatic when the AP is on an HE-capable channel and the IDF - * SOC config has SOC_WIFI_HE_SUPPORT — verified by sniffing the beacon - * and confirming `TWT Responder=1`. If a future IDF exposes - * `esp_wifi_ap_set_he_config()` or similar, hook it here. + /* IDF v5.4 LIMIT (verified empirically 2026-05-23 — WITNESS-LOG-110 §A0.6): + * the public API exposes ONLY STA-side iTWT/bTWT (esp_wifi_sta_itwt_*, + * esp_wifi_sta_btwt_*). There is NO esp_wifi_ap_set_he_config(), NO + * wifi_he_ap_config_t, and NO wifi_config_t.ap.he_* field. A second C6 + * associating against this soft-AP currently lands at phymode 11bgn + * (he:0, vht:0, ht:1) — the AP doesn't advertise HE because there's no + * way to ask it to. A future IDF release that exposes AP-side HE config + * (or a patched WiFi blob) is required to make this AP iTWT-capable. * - * Empirically against IDF v5.4 / C6 silicon: the beacon advertises - * HE capability when the band is 2.4 GHz and the AP is on an - * 11ax-capable channel, and TWT Responder follows. */ + * Until then, this module still gives you a working WPA2 soft-AP on a + * controlled channel for AP+STA bench experiments and ESP-NOW peer + * discovery — just not iTWT validation. The c6_twt module on the STA + * side will return ESP_ERR_INVALID_ARG against this AP (no TWT Responder + * in the beacon), exactly as it does against any other 11n-only AP. */ ESP_LOGI(TAG, "soft-AP starting: ssid=\"%s\" channel=%u auth=%s", ssid, s_channel, ap_cfg.ap.authmode == WIFI_AUTH_OPEN ? "open" : "wpa2-psk"); + ESP_LOGW(TAG, "IDF v5.4 soft-AP does NOT advertise HE — STAs will associate at 11bgn. " + "iTWT validation requires an external 11ax AP. See WITNESS-LOG-110 §A0.6."); /* Don't call esp_wifi_start() here — main.c brings the WiFi up once * for both AP and STA. We just configured the AP iface so it joins diff --git a/firmware/esp32-csi-node/main/swarm_bridge.c b/firmware/esp32-csi-node/main/swarm_bridge.c index b6b485b2..3c5a19d9 100644 --- a/firmware/esp32-csi-node/main/swarm_bridge.c +++ b/firmware/esp32-csi-node/main/swarm_bridge.c @@ -230,9 +230,13 @@ static void swarm_task(void *arg) ESP_LOGI(TAG, "Bearer token configured for Seed auth"); } - /* Get firmware version string. */ + /* Firmware version + IP captured locally so logs name the build; both + * intentionally unused in the JSON payloads — the seed extracts them + * from the register/heartbeat IDs. Keep as side-effect probes. */ const esp_app_desc_t *app = esp_app_get_description(); - const char *fw_ver = app ? app->version : "unknown"; + if (app) { + ESP_LOGI(TAG, "swarm bridge fw=%s", app->version); + } /* Get local IP. */ char ip_str[16]; @@ -278,15 +282,12 @@ static void swarm_task(void *arg) xSemaphoreGive(s_mutex); uint32_t uptime_s = (uint32_t)(esp_timer_get_time() / 1000000ULL); - uint32_t free_heap = esp_get_free_heap_size(); uint32_t ts = (uint32_t)(esp_timer_get_time() / 1000ULL); /* ---- Heartbeat ---- */ if ((now - last_heartbeat) >= pdMS_TO_TICKS(s_cfg.heartbeat_sec * 1000U)) { last_heartbeat = now; - bool presence = vit_valid && (vit.flags & 0x01); - /* Heartbeat ID: node_id * 1000000 + 100000 + ts_sec */ uint32_t hb_id = (uint32_t)s_node_id * 1000000U + 100000U + (uptime_s % 100000U); char json[SWARM_JSON_BUF];