The RPi runs the Rust sensing server that receives and processes CSI data from all ESP32 nodes.
sudo apt update && sudo apt upgrade -y
sudo apt install -y git curl build-essential pkg-config libssl-dev python3 python3-pipcurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source $HOME/.cargo/env
rustc --version # should be 1.70+curl https://get.docker.com | sh
docker pull ruvnet/wifi-densepose:latestThe ESP32 nodes need a predictable IP to send data to.
sudo nmcli con mod "YourWiFiSSID" ipv4.addresses 192.168.1.20/24
sudo nmcli con mod "YourWiFiSSID" ipv4.method manual
sudo nmcli con mod "YourWiFiSSID" ipv4.gateway 192.168.1.1
sudo nmcli con mod "YourWiFiSSID" ipv4.dns "8.8.8.8"
sudo nmcli con up "YourWiFiSSID"Pre-built binaries are available on GitHub Releases — no ESP-IDF toolchain required.
pip3 install esptoolUsing the v0.2.0-esp32 stable release:
mkdir ~/ruview-firmware && cd ~/ruview-firmware
curl -LO https://github.com/ruvnet/RuView/releases/download/v0.2.0-esp32/bootloader.bin
curl -LO https://github.com/ruvnet/RuView/releases/download/v0.2.0-esp32/partition-table.bin
curl -LO https://github.com/ruvnet/RuView/releases/download/v0.2.0-esp32/esp32-csi-node.bin
curl -LO https://github.com/ruvnet/RuView/releases/download/v0.2.0-esp32/provision.pyPlug in one board via USB and find its port:
# Linux / RPi
ls /dev/ttyUSB* /dev/ttyACM*
# macOS
ls /dev/cu.usb*Flash it (repeat for each of the 3 boards, one at a time):
python3 -m esptool --chip esp32s3 --port <PORT> --baud 460800 \
write_flash --flash_mode dio --flash_size 8MB \
0x0 bootloader.bin \
0x8000 partition-table.bin \
0x10000 esp32-csi-node.binEach node needs WiFi credentials, the RPi's IP, and its TDM slot so nodes take turns transmitting without collisions.
mkdir -p ~/esp && cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32
. ./export.shRun the following for --node-id 0, 1, and 2:
python3 provision.py \
--port <PORT> \
--ssid "YourWiFiSSID" \
--password "YourWiFiPassword" \
--target-ip 192.168.1.20 \
--target-port 5005 \
--node-id 0 # change to 1, then 2 for the remaining nodesA 3-node mesh produces 6 TX-RX measurement links (3×2). Place nodes in a triangle around the sensing area:
- Height: ~1–1.5m (table or shelf level)
- Spacing: 2–5m apart
- Antennas unobstructed (not behind metal)
- All within WiFi range of your router
Power each node from a USB adapter or powered hub.
Option A — Docker (quick start):
docker run -p 3000:3000 -p 3001:3001 -p 5005:5005/udp ruvnet/wifi-densepose:latestOption B — Build from source (recommended):
cd ~/RuView/rust-port/wifi-densepose-rs
cargo build --release -p wifi-densepose-sensing-server
cargo run --release -p wifi-densepose-sensing-server -- \
--http-port 3000 \
--source esp32The server listens on:
| Port | Protocol | Purpose |
|---|---|---|
| 5005 | UDP | Receives CSI frames from ESP32 nodes |
| 3000 | HTTP | REST API |
| 3001 | WS | WebSocket real-time stream |
# Health check
curl http://localhost:3000/health
# Latest sensing frame
curl http://localhost:3000/api/v1/sensing/latest
# Vital signs
curl http://localhost:3000/api/v1/vital-signs
# Pose
curl http://localhost:3000/api/v1/pose/currentcd ~/RuView
python3 -m http.server 3000 --directory uiThen open http://192.168.1.20:3000 in any browser on the same network.