mirror of
https://github.com/ruvnet/RuView.git
synced 2026-06-02 00:58:56 +02:00
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:
@@ -11,7 +11,26 @@ set(SRCS
|
||||
"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
|
||||
if(CONFIG_CSI_MOCK_ENABLED)
|
||||
@@ -21,7 +40,11 @@ endif()
|
||||
# ADR-045: AMOLED display support (compile-time optional)
|
||||
if(CONFIG_DISPLAY_ENABLE)
|
||||
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()
|
||||
|
||||
idf_component_register(
|
||||
|
||||
@@ -371,6 +371,30 @@ void csi_collector_init(void)
|
||||
|
||||
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 = {
|
||||
.lltf_en = true,
|
||||
.htltf_en = true,
|
||||
@@ -380,6 +404,7 @@ void csi_collector_init(void)
|
||||
.manu_scale = false,
|
||||
.shift = false,
|
||||
};
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_csi_config(&csi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_csi_rx_cb(wifi_csi_callback, NULL));
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* @file edge_processing.c
|
||||
* @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 1 (DSP task): Pops frames, runs signal processing pipeline:
|
||||
* Core 0 (WiFi path): Pushes raw CSI frames into lock-free SPSC ring buffer.
|
||||
* 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
|
||||
* 2. Phase unwrapping (continuous phase)
|
||||
* 3. Welford variance tracking per subcarrier
|
||||
@@ -1050,7 +1051,9 @@ esp_err_t edge_processing_init(const edge_config_t *cfg)
|
||||
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(
|
||||
edge_task,
|
||||
"edge_dsp",
|
||||
@@ -1058,14 +1061,14 @@ esp_err_t edge_processing_init(const edge_config_t *cfg)
|
||||
NULL,
|
||||
5, /* Priority 5 — above idle, below WiFi. */
|
||||
NULL,
|
||||
1 /* Pin to Core 1. */
|
||||
);
|
||||
dsp_core);
|
||||
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create edge DSP task");
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "psa/crypto.h"
|
||||
|
||||
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) ---- */
|
||||
uint8_t computed_hash[32];
|
||||
int ret = mbedtls_sha256(wasm_data, hdr->wasm_len, computed_hash, 0);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "SHA-256 computation failed: %d", ret);
|
||||
size_t hash_len = 0;
|
||||
psa_status_t psa_st = psa_hash_compute(PSA_ALG_SHA_256, wasm_data,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -186,8 +190,7 @@ esp_err_t rvf_verify_signature(const rvf_parsed_t *parsed, const uint8_t *data,
|
||||
/*
|
||||
* Ed25519 verification.
|
||||
*
|
||||
* ESP-IDF v5.2 mbedtls does NOT include Ed25519 (Curve25519 is
|
||||
* for ECDH/X25519 only). We use a SHA-256-HMAC integrity check:
|
||||
* Legacy mbedtls Ed25519 is optional. We use a SHA-256 keyed digest:
|
||||
*
|
||||
* 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
|
||||
* publishers cannot forge a valid signature.
|
||||
*
|
||||
* For full Ed25519 (NaCl-style), enable CONFIG_MBEDTLS_EDDSA_C
|
||||
* or link TweetNaCl. The RVF builder should match this scheme.
|
||||
* For full Ed25519, enable CONFIG_MBEDTLS_EDDSA_C or equivalent.
|
||||
* The RVF builder should match this scheme.
|
||||
*/
|
||||
uint8_t hash_input_prefix[32];
|
||||
memcpy(hash_input_prefix, pubkey, 32);
|
||||
|
||||
/* Compute SHA-256(pubkey || header+manifest+wasm). */
|
||||
mbedtls_sha256_context ctx;
|
||||
mbedtls_sha256_init(&ctx);
|
||||
int ret = mbedtls_sha256_starts(&ctx, 0);
|
||||
if (ret != 0) {
|
||||
mbedtls_sha256_free(&ctx);
|
||||
/* Compute SHA-256(pubkey || header+manifest+wasm) via PSA Crypto. */
|
||||
psa_hash_operation_t op = PSA_HASH_OPERATION_INIT;
|
||||
psa_status_t st = psa_hash_setup(&op, PSA_ALG_SHA_256);
|
||||
if (st != PSA_SUCCESS) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ret = mbedtls_sha256_update(&ctx, hash_input_prefix, 32);
|
||||
if (ret != 0) {
|
||||
mbedtls_sha256_free(&ctx);
|
||||
st = psa_hash_update(&op, hash_input_prefix, 32);
|
||||
if (st != PSA_SUCCESS) {
|
||||
(void)psa_hash_abort(&op);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ret = mbedtls_sha256_update(&ctx, data, signed_len);
|
||||
if (ret != 0) {
|
||||
mbedtls_sha256_free(&ctx);
|
||||
st = psa_hash_update(&op, data, signed_len);
|
||||
if (st != PSA_SUCCESS) {
|
||||
(void)psa_hash_abort(&op);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
uint8_t expected[32];
|
||||
ret = mbedtls_sha256_finish(&ctx, expected);
|
||||
mbedtls_sha256_free(&ctx);
|
||||
if (ret != 0) {
|
||||
size_t out_len = 0;
|
||||
st = psa_hash_finish(&op, expected, sizeof(expected), &out_len);
|
||||
if (st != PSA_SUCCESS || out_len != 32) {
|
||||
(void)psa_hash_abort(&op);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#!/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
|
||||
so users can configure a pre-built firmware binary without recompiling.
|
||||
|
||||
Usage:
|
||||
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:
|
||||
pip install 'esptool>=5.0' nvs-partition-gen
|
||||
@@ -143,7 +145,7 @@ def generate_nvs_binary(csv_content, size):
|
||||
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."""
|
||||
with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
|
||||
f.write(nvs_bin)
|
||||
@@ -152,16 +154,13 @@ def flash_nvs(port, baud, nvs_bin):
|
||||
try:
|
||||
cmd = [
|
||||
sys.executable, "-m", "esptool",
|
||||
"--chip", "esp32s3",
|
||||
"--chip", chip,
|
||||
"--port", port,
|
||||
"--baud", str(baud),
|
||||
# Keep underscore form — ESP-IDF v5.4 bundles esptool 4.10.0 which only
|
||||
# accepts "write_flash". pip's esptool >=5.x accepts both (hyphenated
|
||||
# form preferred) but keeps underscore working. Do not "correct" this.
|
||||
"write_flash",
|
||||
"write-flash",
|
||||
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)
|
||||
print("NVS provisioning complete!")
|
||||
finally:
|
||||
@@ -170,10 +169,20 @@ def flash_nvs(port, baud, nvs_bin):
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Provision ESP32-S3 CSI Node with WiFi and aggregator settings",
|
||||
epilog="Example: python provision.py --port COM7 --ssid MyWiFi --password secret --target-ip 192.168.1.20",
|
||||
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\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(
|
||||
"--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("--ssid", help="WiFi SSID")
|
||||
parser.add_argument("--password", help="WiFi password")
|
||||
@@ -337,11 +346,11 @@ def main():
|
||||
with open(out, "wb") as f:
|
||||
f.write(nvs_bin)
|
||||
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}")
|
||||
return
|
||||
|
||||
flash_nvs(args.port, args.baud, nvs_bin)
|
||||
flash_nvs(args.port, args.baud, nvs_bin, args.chip)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user