OccupancyStarted / EndedsimulatedOccupancy
A space becomes occupied or empty. The exit event carries the dwell time. 98.2–99.5% accurate, depending on the number of links.
evidence
Where WiFi sensing fits beside other sensors, what each detector does, how three implementations are held to one spec, and which phases are finished. Every figure on this page was measured in simulation. There is no hardware yet, so nothing here comes from a real room.
No sensor is best at everything. Here is where WiFi sensing fits, including where it does not.
| saans · wifi | camera | mmWave radar | PIR | wearable | |
|---|---|---|---|---|---|
| Captures no images or audio | ✓strong fit | ✕no | ✓strong fit | ✓strong fit | ✓strong fit |
| Notices a person sitting still | ✓strong fitbreathing band | ✓strong fit | ✓strong fit | ✕noneeds movement | ~with caveatsif worn |
| Works in the dark | ✓strong fit | ~with caveatswith IR | ✓strong fit | ✓strong fit | ✓strong fit |
| Breathing rate without contact | ✓strong fitopt-in per space | ~with caveatsresearch | ✓strong fit | ✕no | ✕nocontact sensor |
| Nothing to wear, carry or charge | ✓strong fit | ✓strong fit | ✓strong fit | ✓strong fit | ✕no |
| Cannot tell who someone is | ✓strong fitby design | ✕no | ✓strong fit | ✓strong fit | ✕no |
| Position within the room | ✕nonot yet | ✓strong fit | ✓strong fit | ✕no | ~with caveatswith UWB anchors |
| Hardware for one room | two ~$6–10 boards | camera + storage | one radar module | one sensor | one per person |
| Measured in real rooms | ✕nonot yet · phase S5 | ✓strong fit | ✓strong fit | ✓strong fit | ✓strong fit |
what saans is not for
Each detector writes an event that records what it saw and how confident it was. Health detectors are advisory and run only in spaces that have opted in.
OccupancyStarted / EndedsimulatedA space becomes occupied or empty. The exit event carries the dwell time. 98.2–99.5% accurate, depending on the number of links.
RestStarted / EndedbuiltA long still period with steady breathing, reported with start, end, restlessness and pauses. It is not sleep staging.
BreathingPausesimulatedFires after confident breathing when the band falls quiet for at least 10 s. 98.7% precision. Reported about 9–10 s late because of filter lag.
RateHigh / RateLowsimulatedRate outside 8–24 BPM at rest for at least 2 min. Raised in 31 of 36 fast-breathing runs, and falsely in 2 of 36 reader runs.
TrendDeviationbuiltResting breathing rate compared with that space's own baseline: median ± 3 MAD over at least three days.
InactivitysimulatedPresent, with no gross motion for N minutes. Escalates if breathing is not detected. It also fires for a 40-minute reader, which is correct by definition and why it is advisory.
PossibleCollapseoff · not usableCaught 100% of simulated falls, and also 100% of sit-downs. It ships off and should stay off.
SpaceBlind / VisiblebuiltNo usable link means the space reports “I can't see”, never “empty”.
LinkDown / Up / DegradedbuiltSilence, falling rate, packet loss and interference, raised for each link so a wiring fault is never mistaken for an empty room.
CalibrationStalebuiltThe age of each link's empty-room calibration, which presence depends on.
RawModeActivebuiltA node streaming raw CSI for debugging sets a flag in every frame, so the debug mode is always visible.
Python is the reference. Rust and C++ must reproduce it on golden vectors. When they disagree, the implementation that disagrees gets fixed. Tolerances are never loosened quietly.
Standard library only. Every stage is plain float arithmetic.
A dependency-free port of the whole reference.
Header-only, no heap after init, single-precision FPU.
Pre-hardware: the golden vectors behind these counts, and the frames behind the bars below, come from the simulator rather than from a radio.
Unit tests passed. The evaluation on scripted timelines still found three bugs. Each fix went into the Python reference first, the golden vectors were regenerated, and those vectors caught the same bug in both the Rust and the C++ ports before either was edited by hand.
Thirty seconds of an 8 s sliding RMS holds only a few independent windows, so the noise estimate came out far too small. The fix was a floor derived from the filter bandwidth.
empty room read occupied 36% → 0.6–1.2%One biquad's skirt passed about 19% of a 0.25 Hz breathing component into the 1–4 Hz motion band. The fix was two cascaded sections.
still breather vs empty 1.33 / 0.88 → 0.79 / 0.75The heartbeat sits in the motion band, so a single sample could restart the stillness timer. The fix was a higher threshold plus three confirming samples.
false stillness resets 60/h → 0.15/hEvery phase has an exit gate that is measured, not declared. Simulation is done. Real rooms need hardware, and that comes next.
Every gate marked done was passed in simulation. No gate on this list has been passed on hardware.
keep reading