|
1 | 1 | # ADR-012: ESP32 CSI Sensor Mesh for Distributed Sensing |
2 | 2 |
|
3 | 3 | ## Status |
4 | | -Proposed |
| 4 | +Accepted — Partially Implemented (firmware + aggregator working, see ADR-018) |
5 | 5 |
|
6 | 6 | ## Date |
7 | 7 | 2026-02-28 |
@@ -112,23 +112,25 @@ We will build an ESP32 CSI Sensor Mesh as the primary hardware integration path, |
112 | 112 | ``` |
113 | 113 | firmware/esp32-csi-node/ |
114 | 114 | ├── CMakeLists.txt |
115 | | -├── sdkconfig.defaults # Menuconfig defaults with CSI enabled |
| 115 | +├── sdkconfig.defaults # Menuconfig defaults with CSI enabled (gitignored) |
116 | 116 | ├── main/ |
117 | 117 | │ ├── CMakeLists.txt |
118 | | -│ ├── main.c # Entry point, WiFi init, CSI callback |
119 | | -│ ├── csi_collector.c # CSI data collection and buffering |
| 118 | +│ ├── main.c # Entry point, NVS config, WiFi init, CSI callback |
| 119 | +│ ├── csi_collector.c # CSI collection, promiscuous mode, ADR-018 serialization |
120 | 120 | │ ├── csi_collector.h |
121 | | -│ ├── feature_extract.c # On-device FFT and feature extraction |
122 | | -│ ├── feature_extract.h |
| 121 | +│ ├── nvs_config.c # Runtime config from NVS (WiFi creds, target IP) |
| 122 | +│ ├── nvs_config.h |
123 | 123 | │ ├── stream_sender.c # UDP stream to aggregator |
124 | 124 | │ ├── stream_sender.h |
125 | | -│ ├── config.h # Node configuration (SSID, aggregator IP) |
126 | 125 | │ └── Kconfig.projbuild # Menuconfig options |
127 | | -├── components/ |
128 | | -│ └── esp_dsp/ # Espressif DSP library for FFT |
129 | | -└── README.md # Flash instructions |
| 126 | +└── README.md # Flash instructions (verified working) |
130 | 127 | ``` |
131 | 128 |
|
| 129 | +> **Implementation note**: On-device feature extraction (`feature_extract.c`) is deferred. |
| 130 | +> The current firmware streams raw I/Q data in ADR-018 binary format; feature extraction |
| 131 | +> happens in the Rust aggregator. This simplifies the firmware and keeps the ESP32 code |
| 132 | +> under 200 lines of C. |
| 133 | +
|
132 | 134 | **On-device processing** (reduces bandwidth, node does pre-processing): |
133 | 135 |
|
134 | 136 | ```c |
@@ -257,34 +259,58 @@ Specifically: |
257 | 259 |
|
258 | 260 | ### Minimal Build Spec (Clone-Flash-Run) |
259 | 261 |
|
| 262 | +**Option A: Use pre-built binaries (no toolchain required)** |
| 263 | + |
| 264 | +```bash |
| 265 | +# Download binaries from GitHub Release v0.1.0-esp32 |
| 266 | +# Flash with esptool (pip install esptool) |
| 267 | +python -m esptool --chip esp32s3 --port COM7 --baud 460800 \ |
| 268 | + write-flash --flash-mode dio --flash-size 4MB \ |
| 269 | + 0x0 bootloader.bin 0x8000 partition-table.bin 0x10000 esp32-csi-node.bin |
| 270 | + |
| 271 | +# Provision WiFi credentials (no recompile needed) |
| 272 | +python scripts/provision.py --port COM7 \ |
| 273 | + --ssid "YourWiFi" --password "secret" --target-ip 192.168.1.20 |
| 274 | + |
| 275 | +# Run aggregator |
| 276 | +cargo run -p wifi-densepose-hardware --bin aggregator -- --bind 0.0.0.0:5005 --verbose |
260 | 277 | ``` |
261 | | -# Step 1: Flash one node (requires ESP-IDF installed) |
262 | | -cd firmware/esp32-csi-node |
263 | | -idf.py set-target esp32s3 |
264 | | -idf.py menuconfig # Set WiFi SSID/password, aggregator IP |
265 | | -idf.py build flash monitor |
266 | 278 |
|
267 | | -# Step 2: Run aggregator (Docker) |
268 | | -docker compose -f docker-compose.esp32.yml up |
| 279 | +**Option B: Build from source with Docker (no ESP-IDF install needed)** |
269 | 280 |
|
270 | | -# Step 3: Verify with proof bundle |
271 | | -# Aggregator captures 10 seconds, produces feature JSON, verifies hash |
272 | | -docker exec aggregator python verify_esp32.py |
| 281 | +```bash |
| 282 | +# Step 1: Edit WiFi credentials |
| 283 | +vim firmware/esp32-csi-node/sdkconfig.defaults |
273 | 284 |
|
274 | | -# Step 4: Open visualization |
275 | | -open http://localhost:3000 # Three.js dashboard |
| 285 | +# Step 2: Build with Docker |
| 286 | +cd firmware/esp32-csi-node |
| 287 | +MSYS_NO_PATHCONV=1 docker run --rm -v "$(pwd):/project" -w /project \ |
| 288 | + espressif/idf:v5.2 bash -c "idf.py set-target esp32s3 && idf.py build" |
| 289 | + |
| 290 | +# Step 3: Flash |
| 291 | +cd build |
| 292 | +python -m esptool --chip esp32s3 --port COM7 --baud 460800 \ |
| 293 | + write-flash --flash-mode dio --flash-size 4MB \ |
| 294 | + 0x0 bootloader/bootloader.bin 0x8000 partition_table/partition-table.bin \ |
| 295 | + 0x10000 esp32-csi-node.bin |
| 296 | + |
| 297 | +# Step 4: Run aggregator |
| 298 | +cargo run -p wifi-densepose-hardware --bin aggregator -- --bind 0.0.0.0:5005 --verbose |
276 | 299 | ``` |
277 | 300 |
|
| 301 | +**Verified**: 20 Hz CSI streaming, 64/128/192 subcarrier frames, RSSI -47 to -88 dBm. |
| 302 | +See tutorial: https://github.com/ruvnet/wifi-densepose/issues/34 |
| 303 | + |
278 | 304 | ### Proof of Reality for ESP32 |
279 | 305 |
|
280 | | -``` |
281 | | -firmware/esp32-csi-node/proof/ |
282 | | -├── captured_csi_10sec.bin # Real 10-second CSI capture from ESP32 |
283 | | -├── captured_csi_meta.json # Board: ESP32-S3-DevKitC, ESP-IDF: 5.2, Router: TP-Link AX1800 |
284 | | -├── expected_features.json # Feature extraction output |
285 | | -├── expected_features.sha256 # Hash verification |
286 | | -└── capture_photo.jpg # Photo of actual hardware setup |
287 | | -``` |
| 306 | +**Live verified** with ESP32-S3-DevKitC-1 (CP2102, MAC 3C:0F:02:EC:C2:28): |
| 307 | +- 693 frames in 18 seconds (~21.6 fps) |
| 308 | +- Sequence numbers contiguous (zero frame loss) |
| 309 | +- Presence detection confirmed: motion score 10/10 with per-second amplitude variance |
| 310 | +- Frame types: 64 sc (148 B), 128 sc (276 B), 192 sc (404 B) |
| 311 | +- 20 Rust tests + 6 Python tests pass |
| 312 | + |
| 313 | +Pre-built binaries: https://github.com/ruvnet/wifi-densepose/releases/tag/v0.1.0-esp32 |
288 | 314 |
|
289 | 315 | ## Consequences |
290 | 316 |
|
@@ -316,3 +342,6 @@ firmware/esp32-csi-node/proof/ |
316 | 342 | - [ESP32 CSI Research Papers](https://ieeexplore.ieee.org/document/9439871) |
317 | 343 | - [Wi-Fi Sensing with ESP32: A Tutorial](https://arxiv.org/abs/2207.07859) |
318 | 344 | - ADR-011: Python Proof-of-Reality and Mock Elimination |
| 345 | +- ADR-018: ESP32 Development Implementation (binary frame format specification) |
| 346 | +- [Pre-built firmware release v0.1.0-esp32](https://github.com/ruvnet/wifi-densepose/releases/tag/v0.1.0-esp32) |
| 347 | +- [Step-by-step tutorial (Issue #34)](https://github.com/ruvnet/wifi-densepose/issues/34) |
0 commit comments