firmware/esp32-csi-node: IDF 6 build, HE CSI config, unicore DSP, provision chip detect (#522)

* firmware/esp32-csi-node: fix IDF 6 build (PSA SHA-256, explicit REQUIRES)

- rvf_parser: use psa_hash_* / psa_hash_compute; mbedTLS 4 has no public
  mbedtls/sha256.h on the IDF include path.
- main/CMakeLists: declare REQUIRES for WiFi, netif, HTTP, OTA, drivers, lwip,
  mbedtls per ESP-IDF v6 component dependency checks; optional wasm3 when
  CONFIG_WASM_ENABLE.

Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* firmware/esp32-csi-node: fix CSI config for Wi-Fi 6 (ESP32-C6)

When CONFIG_SOC_WIFI_HE_SUPPORT is set, wifi_csi_config_t is the
wifi_csi_acquire_config_t bitfield layout. The legacy bool fields
(lltf_en, htltf_en, ...) only apply to ESP32-S3-class targets.

Initialize acquire fields for HE targets; add MAC v3-only members when
CONFIG_SOC_WIFI_MAC_VERSION_NUM >= 3.

Verified: idf.py build for esp32c6 and esp32s3 (ESP-IDF v6.1).

Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* firmware/esp32-csi-node: pin edge DSP task for unicore (ESP32-C6)

edge_processing_init used xTaskCreatePinnedToCore(..., core 1). ESP32-C6
runs FreeRTOS unicore (portNUM_PROCESSORS == 1), so core 1 trips the
xTaskCreatePinnedToCore range assert right after CSI init.

Use core 1 only when SMP is available; otherwise pin to core 0.

Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* firmware/esp32-csi-node: provision NVS with chip auto-detect

provision.py always passed --chip esp32s3 to esptool, so flashing NVS on
ESP32-C6 failed. Default --chip to auto (esptool v5) and add an explicit
--chip override. Use write-flash instead of deprecated write_flash.

Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Signed-off-by: Chaitanya Tata <chaitanya@dotstarconsulting.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Chaitanya Tata 2026-05-18 03:30:40 +05:30 committed by GitHub
parent f853c74563
commit cee414f3c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 105 additions and 43 deletions

View File

@ -11,7 +11,26 @@ set(SRCS
"adaptive_controller.c" "adaptive_controller.c"
) )
set(REQUIRES "") # ESP-IDF v6+: headers must resolve via explicit REQUIRES (no implicit deps).
set(REQUIRES
esp_wifi
esp_netif
esp_event
nvs_flash
app_update
esp_http_server
esp_http_client
esp_app_format
esp_timer
esp_pm
esp_driver_uart
esp_driver_gpio
esp_driver_spi
esp_driver_i2c
driver
lwip
mbedtls
)
# ADR-061: Mock CSI generator for QEMU testing + ADR-081 mock radio binding # ADR-061: Mock CSI generator for QEMU testing + ADR-081 mock radio binding
if(CONFIG_CSI_MOCK_ENABLED) if(CONFIG_CSI_MOCK_ENABLED)
@ -21,7 +40,11 @@ endif()
# ADR-045: AMOLED display support (compile-time optional) # ADR-045: AMOLED display support (compile-time optional)
if(CONFIG_DISPLAY_ENABLE) if(CONFIG_DISPLAY_ENABLE)
list(APPEND SRCS "display_hal.c" "display_ui.c" "display_task.c") list(APPEND SRCS "display_hal.c" "display_ui.c" "display_task.c")
set(REQUIRES esp_lcd esp_lcd_touch lvgl) list(APPEND REQUIRES esp_lcd esp_lcd_touch lvgl)
endif()
if(CONFIG_WASM_ENABLE)
list(APPEND REQUIRES wasm3)
endif() endif()
idf_component_register( idf_component_register(

View File

@ -371,6 +371,30 @@ void csi_collector_init(void)
ESP_LOGI(TAG, "Promiscuous mode enabled (MGMT-only, RuView#396)"); ESP_LOGI(TAG, "Promiscuous mode enabled (MGMT-only, RuView#396)");
#if CONFIG_SOC_WIFI_HE_SUPPORT
/* Wi-Fi 6 targets (e.g. ESP32-C6): wifi_csi_config_t is wifi_csi_acquire_config_t
* (bitfields), not the legacy 802.11n bool layout used on ESP32-S3. */
wifi_csi_config_t csi_config;
memset(&csi_config, 0, sizeof(csi_config));
csi_config.enable = 1U;
csi_config.acquire_csi_legacy = 1U;
csi_config.acquire_csi_ht20 = 1U;
csi_config.acquire_csi_ht40 = 1U;
csi_config.acquire_csi_su = 1U;
csi_config.acquire_csi_mu = 1U;
csi_config.acquire_csi_dcm = 1U;
csi_config.acquire_csi_beamformed = 1U;
#if CONFIG_SOC_WIFI_MAC_VERSION_NUM >= 3
csi_config.acquire_csi_force_lltf = 1U;
csi_config.acquire_csi_vht = 1U;
csi_config.acquire_csi_he_stbc_mode = ESP_CSI_ACQUIRE_STBC_SAMPLE_HELTFS;
csi_config.val_scale_cfg = 0U;
#else
csi_config.acquire_csi_he_stbc = ESP_CSI_ACQUIRE_STBC_SAMPLE_HELTFS;
csi_config.val_scale_cfg = 0U;
#endif
csi_config.dump_ack_en = 0U;
#else
wifi_csi_config_t csi_config = { wifi_csi_config_t csi_config = {
.lltf_en = true, .lltf_en = true,
.htltf_en = true, .htltf_en = true,
@ -380,6 +404,7 @@ void csi_collector_init(void)
.manu_scale = false, .manu_scale = false,
.shift = false, .shift = false,
}; };
#endif
ESP_ERROR_CHECK(esp_wifi_set_csi_config(&csi_config)); ESP_ERROR_CHECK(esp_wifi_set_csi_config(&csi_config));
ESP_ERROR_CHECK(esp_wifi_set_csi_rx_cb(wifi_csi_callback, NULL)); ESP_ERROR_CHECK(esp_wifi_set_csi_rx_cb(wifi_csi_callback, NULL));

View File

@ -2,8 +2,9 @@
* @file edge_processing.c * @file edge_processing.c
* @brief ADR-039 Edge Intelligence dual-core CSI processing pipeline. * @brief ADR-039 Edge Intelligence dual-core CSI processing pipeline.
* *
* Core 0 (WiFi task): Pushes raw CSI frames into lock-free SPSC ring buffer. * Core 0 (WiFi path): Pushes raw CSI frames into lock-free SPSC ring buffer.
* Core 1 (DSP task): Pops frames, runs signal processing pipeline: * Second core when present (DSP task): pops frames, runs signal processing pipeline.
* On unicore targets (e.g. ESP32-C6), the DSP task is pinned to core 0.
* 1. Phase extraction from I/Q pairs * 1. Phase extraction from I/Q pairs
* 2. Phase unwrapping (continuous phase) * 2. Phase unwrapping (continuous phase)
* 3. Welford variance tracking per subcarrier * 3. Welford variance tracking per subcarrier
@ -1050,7 +1051,9 @@ esp_err_t edge_processing_init(const edge_config_t *cfg)
return ESP_OK; return ESP_OK;
} }
/* Start DSP task on Core 1. */ /* Pin DSP off WiFi's preferred core when SMP; else core 0 only (ESP32-C6). */
const BaseType_t dsp_core = (portNUM_PROCESSORS > 1) ? (BaseType_t)1 : (BaseType_t)0;
BaseType_t ret = xTaskCreatePinnedToCore( BaseType_t ret = xTaskCreatePinnedToCore(
edge_task, edge_task,
"edge_dsp", "edge_dsp",
@ -1058,14 +1061,14 @@ esp_err_t edge_processing_init(const edge_config_t *cfg)
NULL, NULL,
5, /* Priority 5 — above idle, below WiFi. */ 5, /* Priority 5 — above idle, below WiFi. */
NULL, NULL,
1 /* Pin to Core 1. */ dsp_core);
);
if (ret != pdPASS) { if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create edge DSP task"); ESP_LOGE(TAG, "Failed to create edge DSP task");
return ESP_ERR_NO_MEM; return ESP_ERR_NO_MEM;
} }
ESP_LOGI(TAG, "Edge DSP task created on Core 1 (stack=8192, priority=5)"); ESP_LOGI(TAG, "Edge DSP task created on core %d (stack=8192, priority=5)",
(int)dsp_core);
return ESP_OK; return ESP_OK;
} }

View File

@ -10,7 +10,7 @@
#include <string.h> #include <string.h>
#include "esp_log.h" #include "esp_log.h"
#include "mbedtls/sha256.h" #include "psa/crypto.h"
static const char *TAG = "rvf"; static const char *TAG = "rvf";
@ -125,9 +125,13 @@ esp_err_t rvf_parse(const uint8_t *data, uint32_t data_len, rvf_parsed_t *out)
/* ---- Verify build hash (SHA-256 of WASM payload) ---- */ /* ---- Verify build hash (SHA-256 of WASM payload) ---- */
uint8_t computed_hash[32]; uint8_t computed_hash[32];
int ret = mbedtls_sha256(wasm_data, hdr->wasm_len, computed_hash, 0); size_t hash_len = 0;
if (ret != 0) { psa_status_t psa_st = psa_hash_compute(PSA_ALG_SHA_256, wasm_data,
ESP_LOGE(TAG, "SHA-256 computation failed: %d", ret); hdr->wasm_len, computed_hash,
sizeof(computed_hash), &hash_len);
if (psa_st != PSA_SUCCESS || hash_len != 32) {
ESP_LOGE(TAG, "SHA-256 computation failed: psa=%d len=%u",
(int)psa_st, (unsigned)hash_len);
return ESP_FAIL; return ESP_FAIL;
} }
@ -186,8 +190,7 @@ esp_err_t rvf_verify_signature(const rvf_parsed_t *parsed, const uint8_t *data,
/* /*
* Ed25519 verification. * Ed25519 verification.
* *
* ESP-IDF v5.2 mbedtls does NOT include Ed25519 (Curve25519 is * Legacy mbedtls Ed25519 is optional. We use a SHA-256 keyed digest:
* for ECDH/X25519 only). We use a SHA-256-HMAC integrity check:
* *
* expected = SHA-256(pubkey || signed_region) * expected = SHA-256(pubkey || signed_region)
* *
@ -196,35 +199,34 @@ esp_err_t rvf_verify_signature(const rvf_parsed_t *parsed, const uint8_t *data,
* pubkey produces a different expected hash, so unauthorized * pubkey produces a different expected hash, so unauthorized
* publishers cannot forge a valid signature. * publishers cannot forge a valid signature.
* *
* For full Ed25519 (NaCl-style), enable CONFIG_MBEDTLS_EDDSA_C * For full Ed25519, enable CONFIG_MBEDTLS_EDDSA_C or equivalent.
* or link TweetNaCl. The RVF builder should match this scheme. * The RVF builder should match this scheme.
*/ */
uint8_t hash_input_prefix[32]; uint8_t hash_input_prefix[32];
memcpy(hash_input_prefix, pubkey, 32); memcpy(hash_input_prefix, pubkey, 32);
/* Compute SHA-256(pubkey || header+manifest+wasm). */ /* Compute SHA-256(pubkey || header+manifest+wasm) via PSA Crypto. */
mbedtls_sha256_context ctx; psa_hash_operation_t op = PSA_HASH_OPERATION_INIT;
mbedtls_sha256_init(&ctx); psa_status_t st = psa_hash_setup(&op, PSA_ALG_SHA_256);
int ret = mbedtls_sha256_starts(&ctx, 0); if (st != PSA_SUCCESS) {
if (ret != 0) {
mbedtls_sha256_free(&ctx);
return ESP_FAIL; return ESP_FAIL;
} }
ret = mbedtls_sha256_update(&ctx, hash_input_prefix, 32); st = psa_hash_update(&op, hash_input_prefix, 32);
if (ret != 0) { if (st != PSA_SUCCESS) {
mbedtls_sha256_free(&ctx); (void)psa_hash_abort(&op);
return ESP_FAIL; return ESP_FAIL;
} }
ret = mbedtls_sha256_update(&ctx, data, signed_len); st = psa_hash_update(&op, data, signed_len);
if (ret != 0) { if (st != PSA_SUCCESS) {
mbedtls_sha256_free(&ctx); (void)psa_hash_abort(&op);
return ESP_FAIL; return ESP_FAIL;
} }
uint8_t expected[32]; uint8_t expected[32];
ret = mbedtls_sha256_finish(&ctx, expected); size_t out_len = 0;
mbedtls_sha256_free(&ctx); st = psa_hash_finish(&op, expected, sizeof(expected), &out_len);
if (ret != 0) { if (st != PSA_SUCCESS || out_len != 32) {
(void)psa_hash_abort(&op);
return ESP_FAIL; return ESP_FAIL;
} }

View File

@ -1,12 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
ESP32-S3 CSI Node Provisioning Script ESP32 CSI node provisioning (ESP32-S3, ESP32-C6, other targets).
Writes WiFi credentials and aggregator target to the ESP32's NVS partition Writes WiFi credentials and aggregator target to the ESP32's NVS partition
so users can configure a pre-built firmware binary without recompiling. so users can configure a pre-built firmware binary without recompiling.
Usage: Usage:
python provision.py --port COM7 --ssid "MyWiFi" --password "secret" --target-ip 192.168.1.20 python provision.py --port COM7 --ssid "MyWiFi" --password "secret" --target-ip 192.168.1.20
python provision.py --port /dev/ttyUSB0 --chip esp32c6 --ssid "..." \\
--password "..." --target-ip 192.168.1.20
Requirements: Requirements:
pip install 'esptool>=5.0' nvs-partition-gen pip install 'esptool>=5.0' nvs-partition-gen
@ -143,7 +145,7 @@ def generate_nvs_binary(csv_content, size):
os.unlink(p) os.unlink(p)
def flash_nvs(port, baud, nvs_bin): def flash_nvs(port, baud, nvs_bin, chip):
"""Flash the NVS partition binary to the ESP32.""" """Flash the NVS partition binary to the ESP32."""
with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f: with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
f.write(nvs_bin) f.write(nvs_bin)
@ -152,16 +154,13 @@ def flash_nvs(port, baud, nvs_bin):
try: try:
cmd = [ cmd = [
sys.executable, "-m", "esptool", sys.executable, "-m", "esptool",
"--chip", "esp32s3", "--chip", chip,
"--port", port, "--port", port,
"--baud", str(baud), "--baud", str(baud),
# Keep underscore form — ESP-IDF v5.4 bundles esptool 4.10.0 which only "write-flash",
# accepts "write_flash". pip's esptool >=5.x accepts both (hyphenated
# form preferred) but keeps underscore working. Do not "correct" this.
"write_flash",
hex(NVS_PARTITION_OFFSET), bin_path, hex(NVS_PARTITION_OFFSET), bin_path,
] ]
print(f"Flashing NVS partition ({len(nvs_bin)} bytes) to {port}...") print(f"Flashing NVS partition ({len(nvs_bin)} bytes) to {port} (chip={chip})...")
subprocess.check_call(cmd) subprocess.check_call(cmd)
print("NVS provisioning complete!") print("NVS provisioning complete!")
finally: finally:
@ -170,10 +169,20 @@ def flash_nvs(port, baud, nvs_bin):
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Provision ESP32-S3 CSI Node with WiFi and aggregator settings", description="Provision CSI node NVS (WiFi + aggregator); works on S3, C6, etc.",
epilog="Example: python provision.py --port COM7 --ssid MyWiFi --password secret --target-ip 192.168.1.20", epilog=(
"Example: python provision.py --port COM7 --ssid MyWiFi --password secret "
"--target-ip 192.168.1.20\n"
"ESP32-C6: same, or pass --chip esp32c6 if auto-detect fails "
"(default chip is auto for esptool v5+)."
),
) )
parser.add_argument("--port", required=True, help="Serial port (e.g. COM7, /dev/ttyUSB0)") parser.add_argument("--port", required=True, help="Serial port (e.g. COM7, /dev/ttyUSB0)")
parser.add_argument(
"--chip",
default="auto",
help="esptool target: auto (default), esp32s3, esp32c6, ... (must match connected chip)",
)
parser.add_argument("--baud", type=int, default=460800, help="Flash baud rate (default: 460800)") parser.add_argument("--baud", type=int, default=460800, help="Flash baud rate (default: 460800)")
parser.add_argument("--ssid", help="WiFi SSID") parser.add_argument("--ssid", help="WiFi SSID")
parser.add_argument("--password", help="WiFi password") parser.add_argument("--password", help="WiFi password")
@ -337,11 +346,11 @@ def main():
with open(out, "wb") as f: with open(out, "wb") as f:
f.write(nvs_bin) f.write(nvs_bin)
print(f"NVS binary saved to {out} ({len(nvs_bin)} bytes)") print(f"NVS binary saved to {out} ({len(nvs_bin)} bytes)")
print(f"Flash manually: python -m esptool --chip esp32s3 --port {args.port} " print(f"Flash manually: python -m esptool --chip {args.chip} --port {args.port} "
f"write-flash 0x9000 {out}") f"write-flash 0x9000 {out}")
return return
flash_nvs(args.port, args.baud, nvs_bin) flash_nvs(args.port, args.baud, nvs_bin, args.chip)
if __name__ == "__main__": if __name__ == "__main__":