-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
ESP32-S3 CSI Pipeline: Step-by-Step Tutorial (ADR-018)
Complete end-to-end guide for building, flashing, and running the WiFi CSI human presence detection pipeline using an ESP32-S3 and the Rust aggregator.
Verified hardware: ESP32-S3-DevKitC-1 (CP2102 UART, MAC 3C:0F:02:EC:C2:28)
Verified performance: ~20 Hz CSI streaming, 64/128/192 subcarrier frames, RSSI -47 to -88 dBm
Prerequisites
| Component | Version | Install |
|---|---|---|
| Docker Desktop | 28.x+ | https://www.docker.com/products/docker-desktop |
| esptool | 5.x+ | pip install esptool |
| Rust toolchain | stable | https://rustup.rs |
| CP210x driver | latest | https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers |
| ESP32-S3 board | any | DevKitC-1, WROOM, or similar |
Step 1: Clone the repository
git clone https://github.com/ruvnet/wifi-densepose.git
cd wifi-denseposeStep 2: Build the Rust aggregator
cd rust-port/wifi-densepose-rs
cargo build -p wifi-densepose-hardware --bin aggregator --releaseThe binary will be at target/release/aggregator (or aggregator.exe on Windows).
Step 3: Configure WiFi credentials
Create or edit firmware/esp32-csi-node/sdkconfig.defaults:
CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESP_WIFI_CSI_ENABLED=y
CONFIG_CSI_NODE_ID=1
CONFIG_CSI_WIFI_SSID="YOUR_WIFI_SSID"
CONFIG_CSI_WIFI_PASSWORD="YOUR_WIFI_PASSWORD"
CONFIG_CSI_TARGET_IP="192.168.1.20"
CONFIG_CSI_TARGET_PORT=5005
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=yReplace:
YOUR_WIFI_SSIDwith your WiFi network nameYOUR_WIFI_PASSWORDwith your WiFi password192.168.1.20with the IP of the machine running the aggregator
Security:
sdkconfig.defaultsis gitignored. Never commit real credentials.
Step 4: Build firmware with Docker
cd firmware/esp32-csi-node
# Linux/macOS:
docker run --rm -v "$(pwd):/project" -w /project \
espressif/idf:v5.2 bash -c "idf.py set-target esp32s3 && idf.py build"
# Windows (Git Bash):
MSYS_NO_PATHCONV=1 docker run --rm -v "$(pwd -W)://project" -w //project \
espressif/idf:v5.2 bash -c "idf.py set-target esp32s3 && idf.py build"Build output appears in build/ (~960 compilation steps, takes 3-5 minutes first time).
Step 5: Find your serial port
| OS | Command | Typical Port |
|---|---|---|
| Windows | Device Manager > Ports | COM7 |
| Linux | ls /dev/ttyUSB* |
/dev/ttyUSB0 |
| macOS | ls /dev/cu.usbserial* |
/dev/cu.usbserial-0001 |
Step 6: Flash firmware to ESP32-S3
cd firmware/esp32-csi-node/build
python -m esptool --chip esp32s3 --port COM7 --baud 460800 \
--before default-reset --after hard-reset \
write-flash --flash-mode dio --flash-freq 80m --flash-size 4MB \
0x0 bootloader/bootloader.bin \
0x8000 partition_table/partition-table.bin \
0x10000 esp32-csi-node.binReplace COM7 with your serial port. Expected output:
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
Step 7: Open firewall (Windows only)
Run in elevated PowerShell:
netsh advfirewall firewall add rule name="ESP32 CSI" dir=in action=allow protocol=UDP localport=5005Step 8: Run the aggregator
cargo run -p wifi-densepose-hardware --bin aggregator -- --bind 0.0.0.0:5005 --verboseExpected output when ESP32 connects:
Listening on 0.0.0.0:5005...
[148 bytes from 192.168.1.71:60764]
[node:1 seq:0] sc=64 rssi=-49 amp=9.5
[276 bytes from 192.168.1.71:60764]
[node:1 seq:1] sc=128 rssi=-64 amp=16.0
Step 9: Verify presence detection
Walk near the ESP32 and observe changes in amplitude and RSSI values. Expect:
- ~20 frames/sec streaming rate
- Amplitude variance increases when someone is moving near the antenna
- RSSI fluctuates with proximity changes
- Frame types: 64 sc (148 B), 128 sc (276 B), 192 sc (404 B)
Step 10: Multi-node setup (optional)
For multi-node coverage, repeat Steps 3-6 for additional ESP32-S3 boards with different CONFIG_CSI_NODE_ID values (2, 3, ...). All nodes stream to the same aggregator.
Architecture
ESP32-S3 Node(s) Host Machine
+-------------------+ +-------------------+
| WiFi CSI callback | UDP/5005 | aggregator binary |
| (promiscuous mode)| ----------> | (Rust, clap CLI) |
| ADR-018 serialize | ADR-018 | Esp32CsiParser |
| stream_sender.c | binary | CsiFrame output |
+-------------------+ +-------------------+
ADR-018 Binary Frame Format
Offset Size Field
0 4 Magic: 0xC5110001 (LE)
4 1 Node ID
5 1 Number of antennas
6 2 Number of subcarriers (LE u16)
8 4 Frequency MHz (LE u32)
12 4 Sequence number (LE u32)
16 1 RSSI (i8)
17 1 Noise floor (i8)
18 2 Reserved
20 N*2 I/Q pairs (n_antennas * n_subcarriers * 2 bytes)
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| No serial output | Wrong baud rate | Use 115200 for ESP32 monitor |
| WiFi connection fails | Wrong SSID/password | Check sdkconfig.defaults |
| No UDP frames arrive | Firewall blocking | Add UDP 5005 inbound rule (Step 7) |
| CSI callback not firing | Promiscuous mode issue | Verify esp_wifi_set_promiscuous(true) in csi_collector.c |
| Parse errors in aggregator | Firmware/parser version mismatch | Rebuild both from same commit |
| Docker path errors (Windows) | MSYS path conversion | Use MSYS_NO_PATHCONV=1 prefix |
esptool not found |
Not installed | pip install esptool |
Verified Test Results
- 20 Rust tests pass (
cargo test -p wifi-densepose-hardware) - 6 Python tests pass (
pytest v1/tests/) - 693 frames captured in 18 seconds (~21.6 fps)
- Sequence numbers contiguous (zero frame loss)
- Presence detection confirmed with live motion scoring
Related
- ADR-018: ESP32 Development Implementation
- Commit
92a5182: feat(adr-018) ESP32-S3 firmware, Rust aggregator, and live CSI pipeline firmware/esp32-csi-node/README.md: Firmware-specific documentation