First firmware release of the ADR-081 Adaptive CSI Mesh Kernel, plus the Timer Service stack-overflow regression fix caught by on-device validation. Ships both 8 MB and 4 MB variants from CI.
Highlights
- 5-layer adaptive CSI kernel (Radio Abstraction → Adaptive Controller → Mesh Plane → Feature State → Rust handoff) — swap radio silicon without touching the Rust signal/ruvector/mat crates
- 60-byte
rv_feature_state_tpacket (magic0xC5110006) replaces raw CSI as the default upstream — ~99.7 % bandwidth reduction (300 B/s at 5 Hz vs ~100 KB/s raw) - Mesh sensing plane (
rv_mesh) with 4 roles, 7 message types, CRC32-integrity envelope — HEALTH every 30 s, ANOMALY_ALERT on state transitions - Host-testable pure decision policy (
adaptive_controller_decide.c) — 33/33 assertions,decide()at 3.2 ns/call - 4 MB variant (ESP32-S3 SuperMini) now built in CI alongside the 8 MB default
Validated on real hardware
ESP32-S3 QFN56 rev v0.2 (MAC 3c:0f:02:e9:b5:f8), 38 s continuous run after flash:
App version: 0.6.2✅- 0 stack-overflow / panic / reboot markers
- 28
adaptive_ctrlmedium ticks, steady state, no drift - 149
rv_feature_state_temissions at ~5 Hz on earlier 30 s validation - Real WiFi CSI: 37 – 72 pps yield, RSSI -43 to -57 dBm
Fixes
| Fix | Detail |
|---|---|
| Timer Svc stack overflow (new) | emit_feature_state() + stream_sender network I/O runs inside FreeRTOS Timer Svc; ESP-IDF default 2 KiB stack overflows ~1 s after boot. Bumped CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=8192 in all three sdkconfig.defaults*. Follow-up: move heavy work out of the timer daemon.
|
adaptive_controller.c implicit decl (#404)
| fast_loop_cb called emit_feature_state() before its static definition, tripping -Werror=implicit-function-declaration. Added forward declaration.
|
provision.py esptool v5 compat (#391)
| write-flash (hyphenated) in dry-run hint for esptool ≥ 5.
|
provision.py silent NVS wipe (#391)
| Now refuses to run without --ssid, --password, --target-ip unless --force-partial is passed; --force-partial prints which keys will be wiped.
|
Defensive node_id capture (#232, #375, #385, #386, #390)
| csi_collector_init() now captures g_nvs_config.node_id once into a module-local; a canary WARN fires on divergence.
|
CI change
firmware-ci.yml is now a matrix job building both variants in parallel and uploading variant-named artifacts — 6-binary releases no longer need local ESP-IDF.
Installing
Six binaries are attached below. The default flash layout for ESP32-S3 8 MB:
esptool.py --chip esp32s3 --port /dev/ttyUSB0 --baud 460800 \
--before default_reset --after hard_reset \
write_flash -z --flash_mode dio --flash_freq 80m --flash_size 8MB \
0x0 bootloader.bin \
0x8000 partition-table.bin \
0xf000 ota_data_initial.bin \
0x20000 esp32-csi-node.binFor ESP32-S3 SuperMini 4 MB, use esp32-csi-node-4mb.bin at 0x20000 and partition-table-4mb.bin at 0x8000 (reuse the same bootloader and OTA data).
After flash, provision WiFi + target (replaces the full csi_cfg NVS namespace — pass all creds at once):
python firmware/esp32-csi-node/provision.py \
--port /dev/ttyUSB0 \
--ssid <SSID> --password <PSK> --target-ip <DEST_IP>Artifacts
| File | Size | Target |
|---|---|---|
esp32-csi-node.bin
| 1.06 MB | 8 MB app (flash @ 0x20000)
|
esp32-csi-node-4mb.bin
| 851 KB | 4 MB app (flash @ 0x20000)
|
bootloader.bin
| 18 KB | Both (flash @ 0x0)
|
partition-table.bin
| 3 KB | 8 MB layout (flash @ 0x8000)
|
partition-table-4mb.bin
| 3 KB | 4 MB layout (flash @ 0x8000)
|
ota_data_initial.bin
| 8 KB | OTA data (flash @ 0xf000)
|
Compatibility
- ESP-IDF: v5.4
- Target: ESP32-S3 (Xtensa dual-core). Not supported: ESP32 (original), ESP32-C3 (single-core, can't run CSI DSP).
- Supersedes: v0.6.1-esp32
- Wire format:
rv_feature_state_tmagic0xC5110006is the new default upstream; raw ADR-018 CSI (magic0xC5110001) remains available as a debug stream gated by the channel plan.
Reference
- ADR-081 — Adaptive CSI Mesh Firmware Kernel (
docs/adr/ADR-081-adaptive-csi-mesh-firmware-kernel.md) - Full changelog entry:
CHANGELOG.md#v062-esp32 - Source tag:
ae40e2b