Multi-board Wi-Fi / BLE radar: three ESP32-C3 nodes scan passively, a Mac-hosted Python bridge merges their contacts, and a web dashboard shows a sweeping radar plus a floor-plan map with RSSI-based distance.
This is not the standalone ideaspark ESP32 + ST7789 TFT project. That
firmware lives in the sibling checkout
../esp32-radar (oisinryan/esp32-radar).
xraywifi reuses its scan and contact-table logic on headless C3 boards and
adds Wi-Fi backhaul, fusion, and a browser UI.
ESP32-C3 A/B/C --USB serial JSON--> host/bridge.py
--TCP :8082 JSON--> |
<--UDP :49421 beacon-- |
|-- HTTP :8080 dashboard/
|-- WebSocket :8081
|-- GET /api/state
- Three ESP32-C3 boards (USB-C CDC serial). GPIO 9 (BOOT) cycles scan mode:
BOTH → WI-FI → BLE. - Place them in a 3–6 m triangle, roughly 1–1.5 m high, near corners or walls.
- After Wi-Fi provisioning they can run on wall power only. The Mac must
keep
bridge.pyrunning; boards do not serve the UI themselves.
This is a stylized RF scanner, not direction-finding or through-wall radar:
- Bearing is a hash of BSSID / BLE address (stable on screen, not a compass).
- Distance is a path-loss estimate from RSSI after calibration, not a measured range.
- Wi-Fi scans see access points, not associated client stations.
- BLE privacy addresses rotate, so one device can appear as several contacts.
| Role | Port |
|---|---|
| Dashboard HTTP | 8080 |
| Dashboard WebSocket | 8081 |
| Board TCP ingest | 8082 |
| UDP discovery beacon | 49421 |
Allow incoming TCP/UDP for Python if macOS Firewall asks.
Requires the esp32 Arduino core and NimBLE-Arduino.
arduino-cli compile --fqbn "esp32:esp32:esp32c3:PartitionScheme=min_spiffs,CPUFreq=160,FlashFreq=80,UploadSpeed=115200,CDCOnBoot=cdc" firmware/xraywifi
arduino-cli upload -p /dev/cu.usbmodemXXXX --fqbn "esp32:esp32:esp32c3:PartitionScheme=min_spiffs,CPUFreq=160,FlashFreq=80,UploadSpeed=115200,CDCOnBoot=cdc" firmware/xraywifiSerial is 115200. If no SSID is stored, the board starts AP xraywifi-XXXX
(password xraywifi) with a captive portal at 192.168.4.1.
| Command | Action |
|---|---|
ssid NAME |
Store home Wi-Fi SSID |
pass SECRET |
Store password |
host auto / host IP |
UDP discovery, or pin the Mac LAN IP |
join |
Save prefs and associate |
erase |
Clear prefs and reboot |
d |
Dump contact register |
c |
Toggle human dump after each sweep |
j |
Toggle JSON sweep lines |
m |
Cycle scan mode |
h |
Help |
Boards stream newline JSON (hello, status, blip, sweep) over USB and,
once joined, over TCP 8082. UDP beacons look like XRAYWIFI ip=… tcp=8082.
cd host
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python bridge.pyOpen http://127.0.0.1:8080/. The process auto-detects ESP32 USB ports, listens for Wi-Fi boards, broadcasts the UDP beacon, and merges contacts by MAC/BSSID (nodes labelled A/B/C). Contacts older than 30 s are dropped; nodes quiet for 15 s are marked offline.
First-time Wi-Fi setup over USB (boards must be plugged in):
python provision.py --ssid 'YourNetwork' --password 'secret'
# or: XRAY_SSID / XRAY_PASS / XRAY_HOSTThen unplug and power the boards from chargers. Leave bridge.py running.
- Radar — sweeping scope, metre or dBm ring labels, Dist column (nearest first).
- Map — optional floor-plan image, draggable A/B/C markers, path-loss model
distance = d0 × 10^((P0 − rssi) / (10 × n)), optional trilateration. - Calibrate — six-step wizard (room size, node layout, pick a Wi-Fi AP,
1 m reference, 3 m walk). Speech synthesis plus Web Speech input in Chrome;
Safari is TTS-only, use the buttons. Settings persist in
localStoragekeyxraywifi.v1.
Use a stable Wi-Fi BSSID for calibration, not BLE.
- No true bearing or centimetre-accurate ranging.
- No firmware-side calibration commands; the model is client-side only.
- The Mac must stay on the LAN with the bridge running.
- Active Wi-Fi / BLE scans may transmit probe / scan requests even though collected contacts are not forwarded off-LAN except to this host.
This tree previously lived as a combined extension of esp32-radar. The open
PR oisinryan/esp32-radar#1
is that earlier combined branch and is left unchanged. Local remotes
legacy-upstream and legacy-fork point at Oisin’s repository and Terry’s
fork for reference only; this project has no origin of its own yet.
MIT — see LICENSE and THIRD_PARTY.md.