mirror of
https://github.com/ruvnet/RuView
synced 2026-07-23 17:33:20 +00:00
f23e34ee5c
`firmware/esp32-csi-node` now builds for both `esp32s3` (existing
production) and `esp32c6` (new research / battery-seed target) from
the same source tree. ESP-IDF auto-applies `sdkconfig.defaults.esp32c6`
when the target is set to esp32c6; every C6 module is gated on
CONFIG_IDF_TARGET_ESP32C6 (or the SOC_WIFI_HE_SUPPORT capability) so
the S3 build path is byte-identical to today.
New modules (all #ifdef-gated, no-op stubs on S3):
- c6_twt.{h,c} — iTWT wrapper, graceful AP-NACK fallback
- c6_timesync.{h,c} — 802.15.4 beacon-based mesh time-sync, EUI-64
leader election, c6_timesync_get_epoch_us()
- c6_lp_core.{h,c} — wake-on-motion deep-sleep helper (ext1 path
this cut; real LP-core polling deferred)
ADR-018 frame extension:
- byte 18: PPDU type (0=HT/legacy, 1=HE-SU, 2=HE-MU, 3=HE-TB)
- byte 19: bandwidth + STBC + 802.15.4-sync-valid flags
- Magic 0xC5110001 unchanged — backwards compatible
- Dual-branch encoding handles both struct variants of
wifi_pkt_rx_ctrl_t (legacy S3 / HE C6) per CONFIG_SOC_WIFI_HE_SUPPORT
Critical bug fixed during live witness collection (verified across 3
boards on COM6/COM9/COM12):
- c6_timesync.c read MAC into a 6-byte buffer and ran MAC-48->EUI-64
conversion. But esp_read_mac(ESP_MAC_IEEE802154) returns 8 bytes
already in EUI-64 form on C6 — code was double-inserting FFFE.
Boot log was 206ef1fffefffe17, fix yields 206ef1fffe17278c which
matches esptool's eFuse reading exactly.
Tooling:
- CI workflow (firmware-ci.yml) extended with c6-4mb matrix row +
ADR-110 host-unit-test step
- Host unit tests for pure functions (mac48_to_eui64,
eui64_bytes_to_u64, PPDU encoding both branches) — runs on Ubuntu CI
- Multi-board live-capture harness (test/capture-3board-experiment.py)
- Witness bundle script records SHA-256s for s3-adr110, c6-adr110, and
s3-fair-adr110 (apples-to-apples) binary archives
Honest empirical findings (full report in docs/WITNESS-LOG-110.md):
- Verified live on 3 C6 boards: boot, 802.15.4 init w/ correct EUIs,
WiFi STA reaching assoc->run on ruv.net, TWT setup attempted +
gracefully NACKed (AP is 11n-only, TWT Responder:0), HE-MAC firmware
loaded
- NOT verified (need 11ax AP / second-channel exp / INA meter):
HE-LTF subcarrier expansion, TWT cadence determinism, ±100 µs sync
alignment, 5 µA hibernation
- Bug found: leader election doesn't step down under live WiFi load —
likely 2.4 GHz radio coex preemption (WiFi ch 5 vs 15.4 ch 15);
follow-up task #30
- Apples-to-apples size: S3-no-display = 886 KB, C6 = 1003 KB
(C6 is 13% LARGER for equivalent CSI features; the extra is the
802.15.4 + OpenThread stack that S3 lacks)
Tracking: ruvnet/RuView#762
Co-Authored-By: claude-flow <ruv@ruv.net>
156 lines
5.2 KiB
C
156 lines
5.2 KiB
C
/**
|
|
* @file c6_twt.c
|
|
* @brief ESP32-C6 TWT setup implementation — ADR-110 Phase 3.
|
|
*
|
|
* Implementation note: ESP-IDF v5.4's iTWT API on C6 is
|
|
*
|
|
* esp_err_t esp_wifi_sta_itwt_setup(wifi_itwt_setup_config_t *cfg);
|
|
* esp_err_t esp_wifi_sta_itwt_teardown(uint8_t flow_id);
|
|
*
|
|
* The setup is asynchronous — the actual accept/reject arrives later as
|
|
* a WIFI_EVENT_ITWT_SETUP event. The default handler in this module
|
|
* logs the outcome; the helper itself returns as soon as the request
|
|
* is queued.
|
|
*/
|
|
|
|
#include "sdkconfig.h"
|
|
#include "soc/soc_caps.h"
|
|
|
|
#if defined(CONFIG_IDF_TARGET_ESP32C6) && SOC_WIFI_HE_SUPPORT
|
|
|
|
#include "c6_twt.h"
|
|
#include "esp_log.h"
|
|
#include "esp_wifi.h"
|
|
#include "esp_wifi_he.h" /* esp_wifi_sta_itwt_setup / _teardown */
|
|
#include "esp_wifi_he_types.h"
|
|
#include "esp_wifi_types.h"
|
|
#include "esp_event.h"
|
|
#include <string.h>
|
|
|
|
static const char *TAG = "c6_twt";
|
|
|
|
static bool s_active = false;
|
|
static uint8_t s_flow_id = 0;
|
|
static uint32_t s_wake_int = 0;
|
|
static uint32_t s_wake_dura = 0;
|
|
|
|
#ifndef CONFIG_C6_TWT_WAKE_INTERVAL_US
|
|
#define CONFIG_C6_TWT_WAKE_INTERVAL_US 10000 /* 100 fps default cadence */
|
|
#endif
|
|
|
|
#ifndef CONFIG_C6_TWT_MIN_WAKE_DURA_US
|
|
#define CONFIG_C6_TWT_MIN_WAKE_DURA_US 512 /* enough to capture 1 CSI frame */
|
|
#endif
|
|
|
|
/* WIFI_EVENT_ITWT_SETUP handler — logs accept/reject. */
|
|
static void on_itwt_event(void *arg, esp_event_base_t base,
|
|
int32_t event_id, void *event_data)
|
|
{
|
|
(void)arg;
|
|
(void)base;
|
|
(void)event_data;
|
|
switch (event_id) {
|
|
case WIFI_EVENT_ITWT_SETUP:
|
|
ESP_LOGI(TAG, "iTWT setup event received from AP (flow_id captured)");
|
|
s_active = true;
|
|
break;
|
|
case WIFI_EVENT_ITWT_TEARDOWN:
|
|
ESP_LOGI(TAG, "iTWT teardown event received");
|
|
s_active = false;
|
|
break;
|
|
case WIFI_EVENT_ITWT_SUSPEND:
|
|
ESP_LOGI(TAG, "iTWT suspended by AP");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool s_handler_installed = false;
|
|
|
|
static void install_event_handler_once(void)
|
|
{
|
|
if (s_handler_installed) return;
|
|
esp_err_t e = esp_event_handler_instance_register(
|
|
WIFI_EVENT, ESP_EVENT_ANY_ID, on_itwt_event, NULL, NULL);
|
|
if (e == ESP_OK) {
|
|
s_handler_installed = true;
|
|
} else {
|
|
ESP_LOGW(TAG, "Could not install iTWT event handler: %s",
|
|
esp_err_to_name(e));
|
|
}
|
|
}
|
|
|
|
esp_err_t c6_twt_setup(uint32_t wake_interval_us, uint32_t min_wake_dura_us)
|
|
{
|
|
install_event_handler_once();
|
|
|
|
s_wake_int = wake_interval_us;
|
|
s_wake_dura = min_wake_dura_us < 256 ? 256 : min_wake_dura_us;
|
|
|
|
wifi_itwt_setup_config_t cfg = {0};
|
|
cfg.setup_cmd = TWT_REQUEST;
|
|
cfg.flow_id = s_flow_id;
|
|
cfg.twt_id = 0;
|
|
cfg.flow_type = 1; /* unannounced */
|
|
cfg.min_wake_dura = (uint8_t)((s_wake_dura + 255) / 256); /* 256 µs units */
|
|
cfg.wake_duration_unit = 0; /* 0 = 256 µs, 1 = 1024 µs */
|
|
cfg.wake_invl_expn = 10; /* mantissa * 2^10 ≈ 1024 µs base */
|
|
/* mantissa = wake_interval_us / 1024, clamped to uint16 */
|
|
uint32_t mant = wake_interval_us >> 10;
|
|
if (mant == 0) mant = 1;
|
|
if (mant > 0xFFFF) mant = 0xFFFF;
|
|
cfg.wake_invl_mant = (uint16_t)mant;
|
|
cfg.trigger = 0; /* non-triggered: STA wakes on its own */
|
|
|
|
esp_err_t ret = esp_wifi_sta_itwt_setup(&cfg);
|
|
if (ret == ESP_OK) {
|
|
ESP_LOGI(TAG, "iTWT setup queued: wake_interval=%lu µs (mant=%u expn=10), "
|
|
"min_wake_dura=%u (%lu µs)",
|
|
(unsigned long)wake_interval_us, (unsigned)mant,
|
|
cfg.min_wake_dura, (unsigned long)s_wake_dura);
|
|
return ESP_OK;
|
|
}
|
|
/* Treat AP-rejection / not-supported / wrong-AP-mode as graceful — log
|
|
* and continue. ESP_ERR_INVALID_ARG is included here because empirically
|
|
* (live capture on ruv.net 2026-05-22) the ESP-IDF v5.4 driver returns
|
|
* INVALID_ARG when the associated AP advertises TWT Responder=0 — the
|
|
* call validates against the AP's HE capability bitmap, not just the
|
|
* struct fields. */
|
|
if (ret == ESP_ERR_NOT_SUPPORTED || ret == ESP_ERR_WIFI_NOT_CONNECT ||
|
|
ret == ESP_ERR_INVALID_STATE || ret == ESP_ERR_INVALID_ARG) {
|
|
ESP_LOGW(TAG, "iTWT not available (%s) - AP likely not 11ax/iTWT capable,"
|
|
" falling back to opportunistic CSI",
|
|
esp_err_to_name(ret));
|
|
return ESP_OK;
|
|
}
|
|
ESP_LOGE(TAG, "iTWT setup failed: %s", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t c6_twt_setup_default(void)
|
|
{
|
|
return c6_twt_setup(CONFIG_C6_TWT_WAKE_INTERVAL_US,
|
|
CONFIG_C6_TWT_MIN_WAKE_DURA_US);
|
|
}
|
|
|
|
void c6_twt_teardown(void)
|
|
{
|
|
if (!s_active) return;
|
|
/* IDF v5.4 signature: esp_err_t esp_wifi_sta_itwt_teardown(int flow_id) */
|
|
esp_err_t ret = esp_wifi_sta_itwt_teardown((int)s_flow_id);
|
|
if (ret == ESP_OK) {
|
|
ESP_LOGI(TAG, "iTWT teardown sent (flow_id=%u)", s_flow_id);
|
|
} else {
|
|
ESP_LOGW(TAG, "iTWT teardown failed: %s", esp_err_to_name(ret));
|
|
}
|
|
s_active = false;
|
|
}
|
|
|
|
bool c6_twt_is_active(void)
|
|
{
|
|
return s_active;
|
|
}
|
|
|
|
#endif /* CONFIG_IDF_TARGET_ESP32C6 && SOC_WIFI_HE_SUPPORT */
|