Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions firmware/esp32-csi-node/main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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(
Expand Down
25 changes: 25 additions & 0 deletions firmware/esp32-csi-node/main/csi_collector.c
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,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,
Expand All @@ -365,6 +389,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));
Expand Down
15 changes: 9 additions & 6 deletions firmware/esp32-csi-node/main/edge_processing.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1050,22 +1051,24 @@ 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",
8192, /* 8 KB stack — sufficient for DSP pipeline. */
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;
}
48 changes: 25 additions & 23 deletions firmware/esp32-csi-node/main/rvf_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

#include <string.h>
#include "esp_log.h"
#include "mbedtls/sha256.h"
#include "psa/crypto.h"

static const char *TAG = "rvf";

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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)
*
Expand All @@ -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;
}

Expand Down
33 changes: 21 additions & 12 deletions firmware/esp32-csi-node/provision.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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")
Expand Down Expand Up @@ -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__":
Expand Down