Learn/rusty_esp_sensewi-fi sensingcsipresence detectionesp32rust

rusty_esp_sense: Wi-Fi Presence Sensing Without a Camera

rusty_esp_sense reads Wi-Fi channel state from a Janus ESP32 and tells your home computer whether a room is occupied, with a model calibrated for that room.

Signed by M·
Brick-built room where a quiet sensor brick in the corner notices a small figure through soft glowing orbs, with no camera in sight
What is rusty_esp_sense?

rusty_esp_sense is a Rust program for the home computer that reads raw Wi-Fi channel state streamed by a Janus ESP32 and says what a room is doing: empty or occupied, with experimental fall and night readings. It uses a small model calibrated for that room. It is benchmarked on a public ESP32-C6 dataset and has not yet run against a live device.

rusty_esp_sense is the home computer's reading of Wi-Fi channel state for the Janus ESP32 family. A Janus device streams the raw channel state of every Wi-Fi frame it hears, and rusty_esp_sense turns that stream into a plain answer: this room is empty, or someone is in it. It needs no camera, no microphone and no cloud, and the model it uses is fitted in your room, not borrowed from somebody else's. It is benchmarked on public data and has not run against a live device yet, and this field note says so wherever it matters.

If you are new to the family, the Learn index and the Sense vision page give the wider picture. The source lives at github.com/Remade-With-Rust/rusty_esp_sense.

What rusty_esp_sense does

Channel state, not pixels

Every Wi-Fi frame carries a preamble the receiver uses to measure the channel: how each subcarrier arrived, in amplitude and phase. Espressif chips expose this as channel state information, or CSI. An ESP32 gives one antenna and 52 to 56 subcarriers at around 50 frames a second. When a person walks through a room, their body changes how the signal bounces, and the channel moves. That movement is the signal rusty_esp_sense reads.

This kind of Wi-Fi sensing is attractive in a home because it sees nothing. There is no picture of your living room, no audio of your kitchen, just numbers about a radio channel. The rusty_esp_signal package already runs a small fixed-point presence detector on the chip itself. rusty_esp_sense is the second opinion, running where there is more memory and time to think.

One design choice sits under all of it: the device never has to be reflashed to improve the reading. The chip streams raw I/Q samples, roughly 7 KB per second at 50 Hz, and every model decision happens downstream.

Where it runs in your home

rusty_esp_sense runs on the home computer or any box on your LAN, never on the ESP32. It reads a recording from a CSV file, or it subscribes live to a device over the iroh mesh described in the rusty_esp_iroh field note. A bare-metal sensor that cannot run iroh reaches it through a bridge instead.

The command line keeps the whole loop small:

rusty_esp_sense fit --out room.safetensors empty=quiet.csv occupied=walk1.csv,walk2.csv
rusty_esp_sense run --model room.safetensors recording.csv
rusty_esp_sense watch --model room.safetensors <janus1 ticket>

run prints one JSON line per one-second window. watch is the live path, built with the live feature. It compiles against the mesh branches but has not run against a device, and the README says exactly that. Until those branches merge, the live build only works inside the Janus umbrella checkout. The default build stands alone and is not yet on crates.io.

How the room gets calibrated

A 17 KB model fitted where the device is

A calibration in rusty_esp_sense is a file of about 17 KB. It holds a fixed random encoder, stored only as its seed, and a ridge readout fitted on your recordings. Nothing about the room is guessed from another building.

That approach comes from evidence rather than taste. The project looked at RuView's released model first and could not use it: it expects three antennas, 114 subcarriers and 100 Hz from a different radio, it is licensed CC BY-NC 4.0, and it ships as a pickled file. Their own study also found roughly 10 % accuracy when moved to a new environment, and a random frozen encoder within a few points of a trained one. Channel state is locked to its room. The useful part is a readout fitted where the device lives, and that is what this Wi-Fi sensing tool builds.

The benchmark uses the Universidad de Cuenca dataset (doi.org/10.5281/zenodo.21148028, CC BY 4.0): 100 one-minute captures from two ESP32-C6 boards covering an empty room, a person walking, an empty room flooded with UDP traffic, and walking with traffic. With every capture held out of its own calibration, the default model scores 99.4 % balanced. The on-chip detector, with no training at all, scores 80.0 %. The whole gain is in catching the walker: 99.8 % of walking windows against 59.3 %.

The trap it refuses to publish

The most interesting row in the rusty_esp_sense ledger is the one that looks best. Feed the model the raw, uncentred window and it scores 99.8 % held out. It also calls every empty room with network traffic occupied. It had learned each recording session's static channel shape, not the person. A benchmark that only reported the headline would have shipped that.

So a confound test is part of the benchmark: fit only on the quiet empty room and the walker, then score traffic and a day the model never saw. Under that test the default variant false-alarms on 9.8 % of traffic windows and catches 98.4 % of walkers under traffic. A 2 KB linear variant false-alarms on 0.1 % and catches 74.4 %. You choose the trade.

The practical lesson for a home: calibrate with your normal network traffic running. Calibrated on four quiet minutes, a 10 Mbps capture read 13 of 60 windows occupied.

Falls, nights, and honest status

Falls and nights, carefully

Two experimental readings sit beside presence. A fall is treated as a shape: moving, a burst above anything walking produces, then lasting stillness. The detector itself lives in rusty_esp_signal-core and runs on the chip; rusty_esp_sense holds its evidence. With thresholds set on half the Cuenca captures, the other half, 0.84 hours of walking, traffic and empty room, raised zero false falls. On synthetic splices of real segments it raised 10 of 10 falls and 0 of 10 "walked out of the room". No real fall has been recorded, so no detection rate is claimed.

A night is read as 30-second epochs: empty, awake or asleep. Still epochs count as asleep only when a breath was accepted recently; an empty bed and a still sleeper look the same in amplitude alone. Across the dataset, 0 of 122 empty-room epochs were scored asleep and every walking epoch was awake. The estimator accepted no breath anywhere in that data, so the asleep path has not met real data and there are no sleep stages. Treat both readings as research, not as a safety device.

What has and has not run

Here is the state of rusty_esp_sense on 2026-09-30, in plain words:

  • Measured: the presence model, fall evidence and night reader on a public ESP32-C6 dataset, 27 unit tests, clippy clean. The full 100-capture benchmark runs in about 0.27 s on a 24-thread desktop, byte-identical to the single-threaded result.
  • Written but not run on hardware: watch, the live subscriber over the mesh.
  • Not claimed: anything on Janus hardware, whose subcarrier layout differs from the dataset's; per-frame accuracy, since the labels are per capture; pose; and "no C", because the tensor library pulls a C regex engine in at build time, off the model's path.

Wi-Fi sensing pairs naturally with device ownership. A presence reading is only as trustworthy as the device that sent it, which is why the stream arrives from a device with its own identity, covered in the rusty_esp_mid field note. For the bigger reasons to keep this data at home, read smart home without the cloud, and when you are ready to pick hardware, choosing an ESP32 board walks through the options. The project sits inside Remade With Rust, backed by Mata Network.

FAQ

Quick answers for builders evaluating this technology.

Does rusty_esp_sense need a camera or microphone?

No. It reads Wi-Fi channel state information (CSI): how a radio signal changes as it crosses the room. A person moving changes the channel. No image or sound is captured.

Does rusty_esp_sense run on the ESP32 itself?

No. The ESP32 keeps its own fixed-point presence detector from rusty_esp_signal and streams raw channel state (about 7 KB/s at 50 Hz). rusty_esp_sense runs downstream on the home computer or any LAN box, so a model can be recalibrated without reflashing.

How accurate is rusty_esp_sense?

On 100 captures from two ESP32-C6 boards in a public dataset, with every capture held out of its own calibration, it scored 99.4 % balanced against the on-chip detector's 80.0 %. Those numbers come from someone else's bench, not from a Janus device in your home.

Can rusty_esp_sense detect falls or track sleep?

There is a fall detector and a night-epoch reader, but no real fall has been recorded and no breath was accepted in the test data. So no fall detection rate or sleep result is claimed. Both are early and should not be relied on for safety.

How do I calibrate rusty_esp_sense for my room?

Record a few one-minute captures of the empty room and of someone walking, then run the fit command to produce a model file of about 17 KB. Calibrate with your normal network traffic running, because traffic the model never saw can cause false alarms.