Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

xw

Device-free presence/motion sensing using WiFi signals — no camera. A Rust port of the core idea behind WiFi CSI sensing

How it actually works (read this before wiring anything up)

WiFi radios constantly measure Channel State Information (CSI) — how each OFDM subcarrier of a received packet was distorted by the path it travelled (multipath reflections, in plain terms). A body moving through that path perturbs the reflections in a detectable way. That's the whole trick: no video, no audio, just statistics on radio distortion.

Two things this is not:

  • Not something your existing router + a laptop can do alone. Consumer WiFi chipsets don't expose CSI to normal software. You need at least one extra piece of hardware that can — this project targets ESP32 boards because they're cheap and Espressif ships an official CSI example (esp-csi) to build on. Your router is still useful here — it's the ambient traffic source the ESP32 node listens to — but the sensing itself happens on the ESP32, not the router.
  • Not pose/imaging out of the box. What's here is occupancy + motion detection (empty / occupied / motion, per node) from a fairly simple online anomaly score over subcarrier amplitudes. Turning CSI into an actual body outline is a much bigger project (dense antenna arrays, trained neural models) — worth knowing that "WiFi that sees a person's shape" claims from some open-source projects are contested; treat those demos skeptically and start from what's verifiable.

Two ways to get signal in: buy nothing, or buy an ESP32

Zero-purchase path (crates/rssi-sniffer) — uses your existing wifi card, switched into monitor mode, to track RSSI (plain signal strength) of packets from your router. No new hardware. Much coarser than CSI — you lose per-subcarrier detail entirely, so this is closer to "something changed near the router" than real sensing, and it's more prone to false positives (a passing car, someone's phone, another network on the same channel). It needs a wifi chipset that supports monitor mode (most laptop cards on Linux do; check with iw list) and root to switch modes. It reuses the exact same wire format as the ESP32 path, so it plugs straight into the existing aggregator and radar.html with zero changes there.

ESP32 path (firmware/esp32-csi-node) — true CSI, per-subcarrier detail, much better signal-to-noise, needs a ~$8-10 board. See the section further down.

Running the zero-purchase path

# 1. put your wifi card into monitor mode (needs root)
sudo scripts/enable-monitor-mode.sh wlan0
# lock it to your router's channel so you're not sniffing every network in range
sudo iw dev wlan0mon set channel <N>

# 2. find your router's BSSID (its MAC address) so you can filter to just it
iw dev wlan0mon scan | grep -B5 "<your SSID>" | grep freq

# 3. run the aggregator (same one used for ESP32 nodes)
cargo run -p aggregator

# 4. run the sniffer, pointed at your router's BSSID
sudo cargo run -p rssi-sniffer -- --interface wlan0mon --target-bssid AA:BB:CC:DD:EE:FF

# 5. open radar.html — it doesn't care which path fed the data

Expect this to need real tuning (--occupied-threshold / --motion-threshold on aggregator) against your own space before it's usable — RSSI is noisy, and the default thresholds were picked with CSI's much richer signal in mind. Walk around and watch the raw score numbers in the aggregator's terminal output before trusting any alert off it. The guess (human vs animal vs object) heuristic in csi-core leans much harder on burst duration than CSI does here, since RSSI alone gives it almost nothing else to work with — trust that label even less on this path than on the ESP32 one.

Radar view + "what is it" guessing

Open radar.html in a browser (no build step, it's static) while aggregator is running — it polls http://localhost:8090/api/status every 800ms and draws a sweeping radar with a blip per node, colored by state (green = empty, amber = occupied, red = motion).

Two things worth being upfront about:

  • The angle on the dial is not measured. There's no antenna array here, so there's no real angle-of-arrival or range estimate. radar.html has a NODE_ANGLES map at the top — you tell it where each node_id physically sits in your home, and it draws the blip there. It's a labeled floorplan, not a sensor-derived position.
  • The "guess" column is a heuristic, not a classifier. csi-core now tracks how long a motion burst lasts and how large the disturbance peaks, and makes a rough call: longer bursts / sustained occupied readings lean "likely human", short sharp bursts lean "likely small animal or object". There's no training data behind this and no way for it to reliably tell a person from a large dog, or a pet from a draft — treat the label as a hint to glance at, not a verified detection. If you want something closer to real man/animal/object discrimination, that means collecting labeled CSI recordings in your own space and training a small classifier on top of the amplitude features already computed in csi-core::amplitudes() — a meaningfully bigger project than this starting point.

Architecture

 ESP32 node(s)                          this repo (Rust)
 ┌─────────────────────┐   UDP/JSON    ┌──────────────────────────┐
 │ esp-csi capture      │ ────────────▶│ aggregator (crates/aggregator)
 │ (see firmware/)      │   :5005      │  - parses CsiFrame        │
 │ one board per room/  │               │  - per-node PresenceDetector
 │ hallway you want to  │               │    (crates/csi-core)      │
 │ cover                │               │  - prints live status     │
 └─────────────────────┘               └──────────────────────────┘
  • crates/csi-core — the CsiFrame type and PresenceDetector: a rolling per-subcarrier baseline (mean/variance, EMA-updated) scored against each new frame. High deviation = motion; moderate and sustained = someone present but still; low = empty. Unit-tested.
  • crates/aggregator — a Tokio UDP server (port 5005) plus an HTTP status endpoint (port 8090, CORS enabled) at /api/status. Any number of ESP32 nodes can point the UDP side at it; each node_id gets its own independent detector, so you get per-room state, not just one global reading.
  • radar.html — static page, polls /api/status, renders the radar view described above.
  • crates/rssi-sniffer — the zero-purchase capture path: monitor-mode packet sniffing + RSSI extraction, repackaged as the same CsiFrame wire format so it feeds the aggregator unchanged.
  • scripts/enable-monitor-mode.sh — puts a wifi interface into monitor mode (needs root; not every chipset supports this).
  • firmware/esp32-csi-node — instructions + the glue snippet for the ESP32 side, built on top of Espressif's esp-csi.

Running the Rust side

Building rssi-sniffer needs libpcap's headers installed first: sudo apt install libpcap-dev on Debian/Ubuntu (or your distro's equivalent). csi-core and aggregator need nothing extra.

cd xw
cargo test -p csi-core      # sanity-check the detection logic, no hardware needed
cargo run -p aggregator     # starts listening on 0.0.0.0:5005

Sample output once a node is sending frames:

[21:04:12] node=living-room rssi= -47 ch= 6 score=  0.41 avg=  0.38  -> empty
[21:04:13] node=living-room rssi= -48 ch= 6 score=  0.55 avg=  0.40  -> empty
[21:04:14] node=living-room rssi= -46 ch= 6 score=  3.92 avg=  0.95  -> MOTION!

You can also feed it synthetic frames (no ESP32 yet) to see it work — crates/csi-core's tests do exactly that, or write a tiny script that sendtos the aggregator UDP port with hand-built JSON matching CsiFrame's shape.

About

Rust based WiFi radar

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages