the pipeline

how it works

A dedicated transmitter gives the receiver a channel to measure. The receiver turns 20 channel snapshots a second into one 68-byte summary, twice a second. The gateway fuses those links into a space and decides what to report, and when to stay silent.

skip to the results
tx nodeesp-now · 50 Hz beacon, 12 bytes
rx nodec++ · float32 · no heap capture CSI fill the gaps suppress the room fuse subcarriers presence breathing feature frame + consent gate → 68 B · 2 Hz · UDP
gatewayrust · f64 fuse links → detectors → events

The same stages exist three times, in Python, Rust and C++, and all three must agree on the same golden vectors.

  1. 01CaptureA TX board broadcasts an ESP-NOW frame 50 times a second. The RX board reads the channel state on every subcarrier, at a rate Saans sets rather than one that depends on other network traffic.
  2. 02Fill the gapsLost packets are interpolated by sequence number, up to one second. Without this, in simulation, 15% loss made a 15 BPM breather read 17.8 BPM at confidence 0.98.
  3. 03Suppress the roomWalls and furniture dominate the signal. A slowly varying baseline is subtracted from each subcarrier, and each one is weighted by how much it actually moves.
  4. 04FuseA weighted collapse to one signal. Optional BNR selection with sign-flip fusion keeps only the subcarriers that carry breathing.
  5. 05PresenceEnergy in the 0.1–2 Hz band, compared with a threshold learned from 30 s of empty room, with hysteresis so the state doesn't flicker.
  6. 06BreathingBandpass 0.1–0.5 Hz, decimate, then a Goertzel scan of only the breathing band. The peak comes with a confidence calibrated against noise. Below 0.4, the result means “I don't know”.
  7. 07Feature frame68 bytes, twice a second, with a CRC-32. The consent gate on the node writes every vital-sign field as zero unless the space opted in.
  8. 08Space and detectorsThe gateway fuses links (k-of-n presence, best-confidence breathing), runs health and system-health detectors, and logs events that record what was seen and how sure the gateway was.

what it measures

Four signals from one radio link. Each comes with a confidence, and each may abstain.

Every number on this page comes from the simulator. None of it has been measured in a real room.

98.2%occupancy accuracy, one link

Presence, including still people

Motion energy measured against an empty-room calibration. A still person read as empty 2.1% of the time on one link and 0.4% on two.

93.9%within 1 BPM on one link · 100% on three

Breathing rate

A Goertzel scan of the breathing band, 8–24 BPM at rest, with a confidence that abstains instead of guessing. Only in spaces that opted in.

98.7%precision · 81.5–89.9% of pauses caught

Breathing pauses

Presence held, no gross motion, and breathing-band energy below its own baseline for at least 10 s. None were reported in 70.6 h of timelines that had none.

2 Hzhealth report from every link

The system's own health

Rate, loss, calibration age and noise floor. A space with no usable link reports “blind”, never “empty”.

off by default

Heart rate is the weak output. CSI amplitude is nonlinear in chest movement, so breathing harmonics and intermodulation can outshine the cardiac peak. Gated on confidence it is right 97% of the time, but it abstains on 39% of scenarios. It stays off until it has been measured against a reference.

Held-out seeds 2–7: 252 runs, 187 simulated hours, thresholds tuned on other seeds. SYNTHETIC. The simulator's body never sways or blocks line of sight, and real ESP32 noise is unmeasured.

keep reading