How to Build a Multistatic Mesh with ESP32 for 360-Degree Room Coverage Using RuView
You can build a 360-degree multistatic sensing mesh by deploying 4–6 ESP32-S3 nodes running RuView's CSI firmware, provisioning them with unique node IDs, and running the Rust-based RuView server to fuse multistatic Channel State Information (CSI) into full-room pose estimates.
RuView is an open-source Wi-Fi sensing stack that transforms inexpensive ESP32-S3 hardware into a professional-grade multistatic radar. By following the ADR-029 – RuvSense multistatic sensing mode specification, you can achieve sub-inch pose jitter, vital-sign monitoring, and persistent-field modeling across an entire room using only Wi-Fi signals.
Hardware Layout for 360-Degree Coverage
For complete room coverage, position nodes to maximize TX-RX link diversity. According to ADR-029, the baseline configuration uses 4 nodes placed at wall midpoints approximately 2 meters apart, creating roughly 12 unique links. For production-grade robustness, deploy 6 nodes to generate 30 redundant links (N·(N-1)), improving SNR and enabling multi-person separation.
Each node runs identical firmware from firmware/esp32-csi-node/main/csi_collector.c, which handles UDP CSI serialization, channel hopping, and rate-limited output to prevent lwIP buffer exhaustion.
Flashing the ESP32 CSI Firmware
Build the firmware using the provided install script, or flash the pre-built binary directly.
To build from source:
./install.sh --profile iot --yes
This generates firmware/esp32-csi-node/build/esp32_csi_node.bin.
To flash a node (replace port as needed):
esptool.py --chip esp32s3 --port /dev/ttyUSB0 \
--baud 460800 write_flash 0x1000 \
firmware/esp32-csi-node/build/esp32_csi_node.bin
The firmware implements channel hopping across 2.4 GHz channels 1, 6, and 11 (configurable in csi_collector.c lines 66-73) and a TDM schedule where each node transmits null-data-packets in assigned slots while others listen, as defined in rust-port/wifi-densepose-rs/crates/wifi-densepose-hardware/src/esp32/tdm.rs.
Provisioning Each Node with Wi-Fi Credentials
Use scripts/provision.py to write Wi-Fi credentials, node IDs, and sensing parameters into the ESP32 NVS partition. Each node requires a unique --node-id (0-255) so the server can differentiate streams.
python scripts/provision.py \
--port /dev/ttyUSB0 \
--ssid "MyWiFi" \
--password "SuperSecret" \
--target-ip 192.168.1.42 \
--target-port 5005 \
--node-id 0 \
--edge-tier 2 \
--pres-thresh 0.6 \
--fall-thresh 2.0 \
--vital-window 64 \
--vital-interval 200 \
--subk-count 16
Key parameters include:
--edge-tier 2: Enables full AI processing on the device (tier 0 = raw only)--pres-threshand--fall-thresh: Presence and fall detection thresholds used by edge security modules--target-ipand--target-port: The server address (default UDP port 5005)
The provisioner generates a CSV NVS map and calls nvs_partition_gen.py to create the binary blob, then flashes it to the device (implementation in provision.py lines 33-71 for CSV creation, 74-104 for binary generation, and 118-135 for flashing).
Running the RuView Multistatic Server
The Rust server ingests UDP CSI frames from all nodes, performs multi-band fusion, multistatic viewpoint fusion, coherence gating, and Kalman tracking.
Launch the server:
cd rust-port/wifi-densepose-rs
cargo run -p wifi-densepose-sensing-server --release \
--listen-udp 0.0.0.0:5005 \
--listen-http 0.0.0.0:3000
The server pipeline includes:
- Multi-Band Fusion (
wifi-densepose-signal/src/ruvsense/multiband.rs): Merges per-channel CSI intoMultiBandCsiFrame(lines 39-55) - Phase Alignment (
wifi-densepose-signal/src/ruvsense/phase_align.rs): Removes LO phase offsets using a Neumann solver - Multistatic Fusion (
wifi-densepose-signal/src/ruvsense/multistatic.rs): Cross-node attention withruvector-attn-mincut(ADR-029-p4) - Coherence Gating (
wifi-densepose-signal/src/ruvsense/coherence.rsandcoherence_gate.rs): Computes Z-score coherence and decides Accept/PredictOnly/Reject/Recalibrate (enumGateDecisionlines 58-67) - Pose Tracking (
wifi-densepose-signal/src/ruvsense/pose_tracker.rs): 17-keypoint Kalman filter with Re-ID (cost function lines 94-98)
Verifying and Visualizing Results
Connect to http://localhost:3000 to access the Observatory UI, which displays:
- 3-D skeleton overlay with 17 keypoints
- Real-time vital-sign plots (breathing rate, heart rate)
- Presence heat-maps and persistent field fingerprints
The UI consumes WebSocket data from /ws. For custom integrations, query the REST API at /api/v1/pose, which returns JSON matching the internal Pose struct defined in pose_tracker.rs (lines 77-84).
Summary
- Deploy 4-6 ESP32-S3 nodes around the room perimeter to create a multistatic mesh with 360-degree coverage.
- Flash the firmware from
firmware/esp32-csi-node/main/csi_collector.cusingesptool.pyor the./install.shscript. - Provision each node with
scripts/provision.pyto set unique node IDs, Wi-Fi credentials, and sensing thresholds. - Run the Rust server (
wifi-densepose-sensing-server) to fuse multi-band CSI, perform multistatic fusion, and track 17-keypoint poses. - Visualize results via the Observatory UI at port 3000 or consume the REST/WebSocket API for custom applications.
Frequently Asked Questions
How many ESP32 nodes are required for 360-degree coverage?
You need at least 4 nodes for baseline coverage, placed at wall midpoints approximately 2 meters apart. This configuration generates roughly 12 unique TX-RX links. For production deployments requiring higher SNR and multi-person separation, use 6 nodes to create 30 redundant links (N·(N-1)), as specified in ADR-029.
What is the difference between edge-tier 0 and edge-tier 2?
Edge-tier 0 streams raw CSI frames without on-device processing, requiring the server to handle all computation. Edge-tier 2 enables full AI processing on the ESP32, including presence detection, fall detection, and vital-sign extraction, reducing bandwidth and server load. Configure this via the --edge-tier flag in scripts/provision.py.
How does the server handle synchronization between multiple nodes?
The server implements Time Division Multiplexing (TDM) scheduling defined in rust-port/wifi-densepose-rs/crates/wifi-densepose-hardware/src/esp32/tdm.rs. Each node transmits null-data-packets in assigned slots while others listen, creating deterministic TX/RX pairs. The firmware in csi_collector.c handles the slot timing, while the server performs multistatic fusion using ruvector-attn-mincut attention mechanisms to align and fuse data from all viewpoints.
Can I use 5 GHz Wi-Fi instead of 2.4 GHz?
Yes. While the default configuration uses 2.4 GHz channels 1, 6, and 11 for channel hopping (configured in csi_collector.c lines 66-73), the firmware supports 5 GHz operation. You must modify the channel list in csi_collector.c and ensure your ESP32-S3 variant supports the 5 GHz band (note: standard ESP32-S3 is 2.4 GHz only; you may need ESP32-C6 or ESP32-C5 for 5 GHz). The server handles multi-band fusion automatically via the MultiBandCsiFrame structure in multiband.rs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →