178 lines
6.5 KiB
C
178 lines
6.5 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;
|
|
}
|
|
|
|
/* 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.
|
|
*
|
|
* 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
|
|
* 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 */
|