Experience Report: ESP32-S3 N16R8 + RuView Real-World Deployment
Hardware
- 5x AITRIP ESP32-S3 N16R8 (ESP32-S3-WROOM-1 module, 16MB flash, 8MB Octal PSRAM, no display)
- Connected via native USB-JTAG (
/dev/ttyACM0) and FT232 UART (/dev/ttyUSB0)
- WiFi: Standard 2.4 GHz AP (SSID: Blose)
What We Learned
1. Pre-built firmware binaries do NOT work on N16R8 boards
The release binaries at firmware/esp32-csi-node/release_bins/ produce boards that:
- Connect to WiFi successfully
- Boot into the adaptive controller
- Immediately enter state 8 (ADAPT_STATE_DEGRADED) and never leave
- Only send 60-byte feature-state heartbeat packets (type
0x06) — no CSI frames ever
- CSI callback count stays at 0 permanently —
pkt_yield_per_sec is 0, so the degraded gate at adaptive_controller_decide.c:27 (obs->pkt_yield_per_sec < cfg->min_pkt_yield) immediately locks the board in DEGRADED state with no recovery path
2. Building firmware from source fixes it
Building the firmware using the IDF Docker container:
cd firmware/esp32-csi-node
docker run --rm -v "$(pwd):/project" -w /project \
espressif/idf:v5.4 bash -c \
'rm -rf build sdkconfig && idf.py set-target esp32s3 && idf.py build'
Self-built firmware boots to state 6 (ADAPT_STATE_SENSE_ACTIVE) with real CSI frames streaming at 30-40 fps. Boards successfully enter sensing states and respond to motion/presence.
3. Flashing considerations
--flash_mode dio --flash_size 8MB worked fine for 16MB flash boards — the partition table only uses ~7MB so the extra space is unused
- The eFuse on these boards reports
Flash type: quad (4 data lines) but DIO mode still functions
- Must flash:
bootloader.bin, partition-table.bin, ota_data_initial.bin, esp32-csi-node.bin at 0x20000
- Provision with
--edge-tier 2 --force-partial
4. Docker sensing server works well
ruvnet/wifi-densepose:latest receives and processes real ESP32 CSI data
- Use
--disable-host-validation for LAN access
- Multi-node auto-detection works — nodes appear as they come online
- WebSocket at
:3001/ws/sensing and REST API at :3000/api/v1/sensing/latest both reflect real data
5. Model loading
- The
wifi-densepose-v1.rvf in the docker/ directory loads but produces ~20K+ engine errors and gets trust-demoted
- HuggingFace model at
ruvnet/wifi-densepose-pretrained can be converted to RVF via --convert-model (after fixing safetensors header null-padding issue), but still gets trust-demoted with hundreds of engine errors
- Person detection (
n_persons) appears to use a heuristic, not the model — documented as "slot-capacity heuristic, not a learned classifier" in the firmware readme
6. Current observations
- 3 nodes streaming real CSI simultaneously
- Person detection state flickers between present/moving and still even with no one in the room (sleeping family on another floor)
- Adaptive controller shows
motion=1.00 presence=3.79 in state 6 even in empty rooms
- The
yield=0pps problem in DEGRADED state has no recovery mechanism — once a board enters state 8 it stays there permanently
Questions / Suggestions
- Could the pre-built release binaries be rebuilt with the current sdkconfig? The ones in
release_bins/ appear to have an incompatible config for boards with Octal PSRAM
- The DEGRADED state should have a timeout or recovery path — if yield recovers above threshold, the board should leave state 8
- Training documentation would be valuable — what does "empty room" mean for calibration? Do pets trigger false positives? How many minutes of calibration data are needed?
- The trust/engine-error demotion system needs documentation — what causes demotion, how to diagnose, how to recover
Current Setup (working)
- 3/5 boards flashed with self-built firmware, provisioned via
provision.py
- Docker server receiving UDP on port 5005, dashboard at port 3000
- HuggingFace model converted and loaded (but trust-demoted)
- All nodes send to
192.168.1.4:5005 with unique --node-id
Experience Report: ESP32-S3 N16R8 + RuView Real-World Deployment
Hardware
/dev/ttyACM0) and FT232 UART (/dev/ttyUSB0)What We Learned
1. Pre-built firmware binaries do NOT work on N16R8 boards
The release binaries at
firmware/esp32-csi-node/release_bins/produce boards that:0x06) — no CSI frames everpkt_yield_per_secis 0, so the degraded gate atadaptive_controller_decide.c:27(obs->pkt_yield_per_sec < cfg->min_pkt_yield) immediately locks the board in DEGRADED state with no recovery path2. Building firmware from source fixes it
Building the firmware using the IDF Docker container:
Self-built firmware boots to state 6 (ADAPT_STATE_SENSE_ACTIVE) with real CSI frames streaming at 30-40 fps. Boards successfully enter sensing states and respond to motion/presence.
3. Flashing considerations
--flash_mode dio --flash_size 8MBworked fine for 16MB flash boards — the partition table only uses ~7MB so the extra space is unusedFlash type: quad (4 data lines)but DIO mode still functionsbootloader.bin,partition-table.bin,ota_data_initial.bin,esp32-csi-node.binat0x20000--edge-tier 2 --force-partial4. Docker sensing server works well
ruvnet/wifi-densepose:latestreceives and processes real ESP32 CSI data--disable-host-validationfor LAN access:3001/ws/sensingand REST API at:3000/api/v1/sensing/latestboth reflect real data5. Model loading
wifi-densepose-v1.rvfin thedocker/directory loads but produces ~20K+ engine errors and gets trust-demotedruvnet/wifi-densepose-pretrainedcan be converted to RVF via--convert-model(after fixing safetensors header null-padding issue), but still gets trust-demoted with hundreds of engine errorsn_persons) appears to use a heuristic, not the model — documented as "slot-capacity heuristic, not a learned classifier" in the firmware readme6. Current observations
motion=1.00 presence=3.79in state 6 even in empty roomsyield=0ppsproblem in DEGRADED state has no recovery mechanism — once a board enters state 8 it stays there permanentlyQuestions / Suggestions
release_bins/appear to have an incompatible config for boards with Octal PSRAMCurrent Setup (working)
provision.py192.168.1.4:5005with unique--node-id