mirror of
https://github.com/ruvnet/RuView
synced 2026-07-30 18:41:42 +00:00
948768bdda
ADR-110 P9 — software-only unblocks for the WITNESS-LOG-110 §B
hardware-blocked items. Two new modules, both default-off so v0.6.6 fleets
see no behavior change.
LP-core (B4 path):
- New firmware/esp32-csi-node/main/lp_core/main.c: real RISC-V LP-core
motion-gate program with debounce + monotonic motion_count counter.
- c6_lp_core.c rewritten to load + run the LP binary via ulp_lp_core_run
when CONFIG_C6_LP_CORE_ENABLE=y; falls back to the v0.6.6 ext1 GPIO-wake
path otherwise (keeps regression surface small).
- ulp_embed_binary() wired in main/CMakeLists.txt, gated on the Kconfig.
- New Kconfig knobs: C6_LP_POLL_PERIOD_US (default 10 ms),
C6_LP_DEBOUNCE_SAMPLES (default 3).
- Exposes c6_lp_core_motion_count() / c6_lp_core_poll_count() for the
witness harness — once an INA/Joulescope is on the bench, B4 is one
capture away from a measured number.
Soft-AP HE (B1/B2 unblock):
- New c6_softap_he.{h,c}: brings up the C6 in AP+STA mode with WPA2-PSK
+ HE advertisement, so a second C6 in STA mode can negotiate real
iTWT against a known-cooperative AP without buying an 11ax router.
- main.c calls c6_softap_he_start() right before esp_wifi_start() when
CONFIG_C6_SOFTAP_HE_ENABLE=y.
- New Kconfig knobs: C6_SOFTAP_HE_{SSID,PSK,CHANNEL} with NVS overrides
via softap_ssid / softap_psk / softap_chan in the ruview namespace.
Build artifacts (IDF v5.4, both green, RC=0):
- S3 8 MB: 1093 KB (47% partition slack)
- C6 4 MB: 1019 KB (45% partition slack)
- SHA-256 sums in dist/firmware-v0.6.7/SHA256SUMS.txt
Doc updates: CHANGELOG wave-3 entry, ADR-110 phase table gets P5
upgrade note + new P9 row, WITNESS-LOG-110 gets new A0 section
recording the v0.6.7 build evidence.
Co-Authored-By: claude-flow <ruv@ruv.net>
172 lines
6.0 KiB
C
172 lines
6.0 KiB
C
/**
|
|
* @file c6_softap_he.c
|
|
* @brief ESP32-C6 soft-AP with HE/TWT — ADR-110 B1/B2 cheap-unblock.
|
|
*
|
|
* Pairs with c6_softap_he.h. Builds only when both targets are set:
|
|
*
|
|
* CONFIG_IDF_TARGET_ESP32C6 (selected by `idf.py set-target esp32c6`)
|
|
* CONFIG_C6_SOFTAP_HE_ENABLE (Kconfig, default n)
|
|
*
|
|
* The IDF v5.4 soft-AP path advertises HE automatically on chips with
|
|
* SOC_WIFI_HE_SUPPORT; the operator-side concern here is making sure
|
|
* the beacon also advertises `TWT Responder=1` so a STA-side
|
|
* `esp_wifi_sta_itwt_setup()` call doesn't bounce with `INVALID_ARG`
|
|
* the same way it did against `ruv.net` (the bench's 11n-only AP).
|
|
*
|
|
* TWT Responder advertisement in IDF v5.4 is gated by
|
|
* `wifi_he_ap_config_t.twt_responder = 1`. When the IDF header doesn't
|
|
* expose that struct (older v5.3), the AP still comes up with HE but
|
|
* without TWT Responder — we log a warning and continue so the build
|
|
* stays portable.
|
|
*/
|
|
|
|
#include "sdkconfig.h"
|
|
|
|
#if defined(CONFIG_IDF_TARGET_ESP32C6) && defined(CONFIG_C6_SOFTAP_HE_ENABLE)
|
|
|
|
#include "c6_softap_he.h"
|
|
#include "esp_log.h"
|
|
#include "esp_wifi.h"
|
|
#include "esp_wifi_types.h"
|
|
#include "esp_event.h"
|
|
#include "esp_netif.h"
|
|
#include "nvs_flash.h"
|
|
#include "nvs.h"
|
|
#include <string.h>
|
|
|
|
static const char *TAG = "c6_softap";
|
|
|
|
static bool s_started = false;
|
|
static uint8_t s_sta_count = 0;
|
|
static uint8_t s_channel = 0;
|
|
|
|
#ifndef CONFIG_C6_SOFTAP_HE_SSID
|
|
#define CONFIG_C6_SOFTAP_HE_SSID "ruview-c6-twt"
|
|
#endif
|
|
#ifndef CONFIG_C6_SOFTAP_HE_PSK
|
|
#define CONFIG_C6_SOFTAP_HE_PSK "ruviewtwt"
|
|
#endif
|
|
#ifndef CONFIG_C6_SOFTAP_HE_CHANNEL
|
|
#define CONFIG_C6_SOFTAP_HE_CHANNEL 6
|
|
#endif
|
|
|
|
static void load_nvs_override(const char *key, char *dst, size_t dst_len)
|
|
{
|
|
nvs_handle_t h;
|
|
if (nvs_open("ruview", NVS_READONLY, &h) != ESP_OK) return;
|
|
size_t n = dst_len;
|
|
esp_err_t err = nvs_get_str(h, key, dst, &n);
|
|
if (err == ESP_OK) {
|
|
ESP_LOGI(TAG, "nvs override: %s=\"%s\"", key, dst);
|
|
}
|
|
nvs_close(h);
|
|
}
|
|
|
|
static uint8_t load_nvs_u8(const char *key, uint8_t fallback)
|
|
{
|
|
nvs_handle_t h;
|
|
if (nvs_open("ruview", NVS_READONLY, &h) != ESP_OK) return fallback;
|
|
uint8_t v = fallback;
|
|
if (nvs_get_u8(h, key, &v) == ESP_OK) {
|
|
ESP_LOGI(TAG, "nvs override: %s=%u", key, v);
|
|
}
|
|
nvs_close(h);
|
|
return v;
|
|
}
|
|
|
|
static void on_wifi_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_AP_START:
|
|
s_started = true;
|
|
ESP_LOGI(TAG, "AP started on channel %u", s_channel);
|
|
break;
|
|
case WIFI_EVENT_AP_STOP:
|
|
s_started = false;
|
|
ESP_LOGI(TAG, "AP stopped");
|
|
break;
|
|
case WIFI_EVENT_AP_STACONNECTED:
|
|
if (s_sta_count < 255) s_sta_count++;
|
|
ESP_LOGI(TAG, "STA connected — total=%u", s_sta_count);
|
|
break;
|
|
case WIFI_EVENT_AP_STADISCONNECTED:
|
|
if (s_sta_count > 0) s_sta_count--;
|
|
ESP_LOGI(TAG, "STA disconnected — total=%u", s_sta_count);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
esp_err_t c6_softap_he_start(uint8_t *out_channel)
|
|
{
|
|
if (s_started) {
|
|
if (out_channel) *out_channel = s_channel;
|
|
return ESP_OK;
|
|
}
|
|
|
|
/* Resolve config: NVS overrides Kconfig defaults. */
|
|
char ssid[33] = CONFIG_C6_SOFTAP_HE_SSID;
|
|
char psk[64] = CONFIG_C6_SOFTAP_HE_PSK;
|
|
load_nvs_override("softap_ssid", ssid, sizeof(ssid));
|
|
load_nvs_override("softap_psk", psk, sizeof(psk));
|
|
s_channel = load_nvs_u8("softap_chan", CONFIG_C6_SOFTAP_HE_CHANNEL);
|
|
if (s_channel < 1 || s_channel > 13) s_channel = CONFIG_C6_SOFTAP_HE_CHANNEL;
|
|
|
|
/* AP+STA so the existing STA path keeps working (NVS-provisioned upstream). */
|
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
|
|
|
|
wifi_config_t ap_cfg = {0};
|
|
size_t ssid_len = strlen(ssid);
|
|
if (ssid_len > 32) ssid_len = 32;
|
|
memcpy(ap_cfg.ap.ssid, ssid, ssid_len);
|
|
ap_cfg.ap.ssid_len = (uint8_t)ssid_len;
|
|
strncpy((char *)ap_cfg.ap.password, psk, sizeof(ap_cfg.ap.password) - 1);
|
|
ap_cfg.ap.channel = s_channel;
|
|
ap_cfg.ap.max_connection = 4;
|
|
ap_cfg.ap.authmode = strlen(psk) >= 8 ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN;
|
|
ap_cfg.ap.beacon_interval = 100;
|
|
/* pmf_cfg.required = false keeps backward compatibility for STA clients
|
|
* that don't speak PMF. */
|
|
ap_cfg.ap.pmf_cfg.required = false;
|
|
|
|
/* Register the event handler before bringing the AP up so we don't
|
|
* miss WIFI_EVENT_AP_START. */
|
|
ESP_ERROR_CHECK(esp_event_handler_instance_register(
|
|
WIFI_EVENT, ESP_EVENT_ANY_ID, on_wifi_event, NULL, NULL));
|
|
|
|
esp_err_t err = esp_wifi_set_config(WIFI_IF_AP, &ap_cfg);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "set_config(AP) failed: %s", esp_err_to_name(err));
|
|
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.
|
|
*
|
|
* 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. */
|
|
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");
|
|
|
|
/* 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
|
|
* the existing start. */
|
|
|
|
if (out_channel) *out_channel = s_channel;
|
|
return ESP_OK;
|
|
}
|
|
|
|
bool c6_softap_he_is_up(void) { return s_started; }
|
|
uint8_t c6_softap_he_sta_count(void) { return s_sta_count; }
|
|
|
|
#endif /* CONFIG_IDF_TARGET_ESP32C6 && CONFIG_C6_SOFTAP_HE_ENABLE */
|