fix(c6): TWT INVALID_ARG graceful + ch26 + diagnostic counters (ADR-110 D1)

After 3 systematic hypotheses tested + rejected (radio coex, OpenThread
shadowing, manual RX re-arm), the 802.15.4 leader-election bug is
narrowed to: TX path works perfectly (~10/s clean, 0 fail), but the RX
path stops after exactly 1 frame. Manual esp_ieee802154_receive() from
either callback bootloops the driver (verified across all 3 boards).

The IDF reference example uses the same handle_done-only pattern as
this code, implying the driver should auto-restart RX — but empirically
doesn't here. Either a half-duplex radio state issue or an IDF v5.4
bug. Tracked as known issue D1 in WITNESS-LOG-110.

Changes shipped:
- c6_twt.c: ESP_ERR_INVALID_ARG added to graceful-fallback list
  (empirically: ruv.net AP advertises TWT Responder=0, IDF driver
  validates against AP HE capability and rejects with INVALID_ARG)
- c6_timesync.c: diagnostic counters (s_tx_count, s_tx_fail, s_rx_count,
  s_rx_magic_match) + per-10-beacon log line preserved so future
  investigation has the diagnostic harness ready
- sdkconfig.defaults.esp32c6: 15.4 channel default 15 → 26 (non-overlap
  with WiFi 2.4 GHz channels), OpenThread disabled (we use raw 15.4)
- promiscuous=true on the radio (broadcast frames addressed to 0xFFFF)
- WITNESS-LOG-110 §D1 expanded with the full diagnostic trace +
  3-hypothesis investigation record

Cross-node sync claim (B3) BLOCKED until either an IDF maintainer
trace or a working multi-board reference is available. The other
three SOTA dimensions (HE-LTF, TWT cadence, 5 µA hibernation) are
also still unverified and need different hardware (11ax AP, INA meter)
— honestly recorded in §B.

Tracking: ruvnet/RuView#762, task #30 closed as known-issue.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-05-22 20:39:50 -04:00
parent f23e34ee5c
commit 66523843e6
3 changed files with 56 additions and 15 deletions
+36 -2
View File
@@ -74,6 +74,11 @@ static uint64_t eui64_bytes_to_u64(const uint8_t eui[8])
((uint64_t)eui[6] << 8 ) | (uint64_t)eui[7];
}
static uint32_t s_tx_count = 0;
static uint32_t s_tx_fail = 0;
static uint32_t s_rx_count = 0;
static uint32_t s_rx_magic_match = 0;
static void send_beacon(void)
{
uint8_t frame[32];
@@ -95,11 +100,30 @@ static void send_beacon(void)
uint8_t tx_buf[64];
tx_buf[0] = (uint8_t)(total + 2); /* +2 for FCS appended by HW */
memcpy(&tx_buf[1], frame, total);
esp_ieee802154_transmit(tx_buf, false);
esp_err_t r = esp_ieee802154_transmit(tx_buf, false);
s_tx_count++;
if (r != ESP_OK) s_tx_fail++;
/* Diag log every 10 beacons. */
if ((s_tx_count % 10) == 1) {
ESP_LOGI(TAG, "tx#%lu (fail=%lu) rx#%lu (magic_match=%lu) is_leader=%d",
(unsigned long)s_tx_count, (unsigned long)s_tx_fail,
(unsigned long)s_rx_count, (unsigned long)s_rx_magic_match,
(int)s_is_leader);
}
}
/* KNOWN ISSUE (see WITNESS-LOG-110 §D1 / task #30):
* Empirically observed on 3 C6 boards with channel=26, OpenThread disabled,
* promiscuous=true, and IDF v5.4 reference RX/TX callback pattern: only 1
* RX event ever fires after init, despite ~381 successful TX events from
* the other boards in the same 38-second window. Manual re-arm with
* esp_ieee802154_receive() in either callback context bootloops the
* driver. Hypothesis: half-duplex radio + driver state-machine issue;
* needs an IDF maintainer trace or a working multi-board reference.
* Cross-node sync claim (ADR-110 §B3) is BLOCKED on this. */
void esp_ieee802154_receive_done(uint8_t *frame, esp_ieee802154_frame_info_t *frame_info)
{
s_rx_count++;
/* PHY length is frame[0]; payload starts at frame[1]. */
if (frame == NULL || frame[0] < (9 + sizeof(ts_beacon_t) + 2)) {
if (frame) esp_ieee802154_receive_handle_done(frame);
@@ -110,6 +134,7 @@ void esp_ieee802154_receive_done(uint8_t *frame, esp_ieee802154_frame_info_t *fr
esp_ieee802154_receive_handle_done(frame);
return;
}
s_rx_magic_match++;
uint64_t now = (uint64_t)esp_timer_get_time();
if (b->leader_flag) {
/* Adopt this leader if its EUI is lower than ours (or unknown). */
@@ -124,6 +149,9 @@ void esp_ieee802154_receive_done(uint8_t *frame, esp_ieee802154_frame_info_t *fr
}
}
}
/* handle_done auto-restarts RX in the IDF driver; calling
* esp_ieee802154_receive() here would double-arm and panic
* (verified empirically — 25 reboot loops observed). */
esp_ieee802154_receive_handle_done(frame);
}
@@ -132,6 +160,9 @@ void esp_ieee802154_transmit_done(const uint8_t *frame,
esp_ieee802154_frame_info_t *ack_frame_info)
{
(void)frame; (void)ack; (void)ack_frame_info;
/* Note: do NOT call esp_ieee802154_receive() here — it panics the
* driver (verified empirically, all 3 boards bootloop). The IDF
* driver internally manages RX/TX state transitions. */
}
void esp_ieee802154_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_error_t error)
@@ -184,7 +215,10 @@ esp_err_t c6_timesync_init(uint8_t channel)
ESP_LOGE(TAG, "ieee802154_enable failed: %s", esp_err_to_name(ret));
return ret;
}
esp_ieee802154_set_promiscuous(false);
/* promiscuous=true so we accept broadcast frames addressed to 0xFFFF.
* In non-promiscuous mode the radio filters to frames addressed to
* our short or extended address. Our beacon protocol uses broadcast. */
esp_ieee802154_set_promiscuous(true);
esp_ieee802154_set_panid(0xCAFE);
esp_ieee802154_set_short_address(0x0000);
esp_ieee802154_set_extended_address(mac);
@@ -28,17 +28,22 @@ CONFIG_ESP_WIFI_CSI_ENABLED=y
# on chips that have HE support (C6/C5). WPA3 is opt-in:
CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y
# ── ADR-110 P4: 802.15.4 + OpenThread (MTD) ──
# IEEE 802.15.4 PHY + OpenThread Minimal Thread Device for mesh time-sync.
# MTD is lighter than FTD (no router/leader code) — perfect for sensor nodes.
# ── ADR-110 P4: 802.15.4 (raw, no OpenThread) ──
# IEEE 802.15.4 PHY enabled for our raw beacon protocol in c6_timesync.c.
# OpenThread is DISABLED — empirically (ch15 + ch26 tested with the same
# negative result), enabling OpenThread MTD caused our weak-symbol overrides
# of esp_ieee802154_receive_done/transmit_done to never fire, breaking
# leader election. Raw 802.15.4 mode is what we actually need: a private
# mesh protocol on a private channel, no Thread network attach.
CONFIG_IEEE802154_ENABLED=y
CONFIG_OPENTHREAD_ENABLED=y
CONFIG_OPENTHREAD_MTD=y
CONFIG_OPENTHREAD_FTD=n
CONFIG_OPENTHREAD_RADIO=n
# Disable joiner / commissioner — we use a pre-shared network key in NVS.
CONFIG_OPENTHREAD_JOINER=n
CONFIG_OPENTHREAD_COMMISSIONER=n
CONFIG_OPENTHREAD_ENABLED=n
# ADR-110 P4: 802.15.4 channel override.
# Default Kconfig value is 15 (2425 MHz). On the 2.4 GHz radio that's
# directly under WiFi channel 5 (2432 MHz). Channel 26 = 2480 MHz is on
# the WiFi guard band above channel 14, giving the 15.4 path room to RX
# without competing with WiFi traffic for radio time.
CONFIG_C6_TIMESYNC_CHANNEL=26
# ── ADR-110 P5: LP-core (deep-sleep coprocessor) ──
# Enable the LP RISC-V core so c6_lp_core.c can ship a wake-on-motion stub.