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-thresh and --fall-thresh: Presence and fall detection thresholds used by edge security modules
  • --target-ip and --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:

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.c using esptool.py or the ./install.sh script.
  • Provision each node with scripts/provision.py to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →