Inspiration

Wi-Fi signals bounce off people. Research labs (MIT's Wi-Vi and RF-Pose; Gu et al.'s Wi-Fi breathing monitor) have shown those reflections reveal movement and even breathing, but with special radios and lab Wi-Fi cards. We wanted to know how much of that four $5 ESP32 boards can do, and to turn it into something people play. Then a mentor asked: if Wi-Fi can catch you freezing behind a curtain, what else can it find? Smoke blinds cameras, but not Wi-Fi.

What it does

  • Red Light, Green Light, judged by Wi-Fi. A real person hides behind a curtain. No camera points at them: three ESP32 receivers listen to a fourth board's Wi-Fi packets, and the way the signal changes tells whether the hider moved. Their avatar runs in a Roblox game; move on red and the Wi-Fi catches you.
  • A live swamp in 16-bit pixel art. The judges' screen shows what the Wi-Fi feels: each receiver is a fishing bobber that dips with movement, the transmitter is a lantern, and a gator watches the water on red light.
  • Breathing check. When someone is in the room and still, GatorWave shows BREATHING: YES / NO live, from the rhythm of their chest moving (a few millimetres), plus a breathing rate.
  • Sensing Lab: one sensing, four uses. The same live reading, reframed:
    • Fire rescue: is someone in the smoke-filled room? Moving? Breathing? "Still, no breathing rhythm: GO NOW."
    • Smart lights: motion sensors switch off when you sit still; we keep the lights on while someone is still there and switch off 20 s after nobody is sensed.
    • Hot car: a sleeping baby or pet doesn't move, but it breathes. Parked car + someone inside = alert; no breathing rhythm = "call 911 and open the car".
    • Home care: "last moved 42 min ago", and an alert when someone has been still for a minute with no breathing rhythm. No cameras in the home.
  • An AI host. After each round, a gator (DeepSeek on Vultr, using tool calls to fetch the round's facts) says one line about what happened, checked so it can't invent numbers, and speaks it with Vultr text-to-speech.

How we built it

  • Hardware: 4 ESP32 boards (1 transmitter on a battery, 3 receivers on a laptop) sending ESP-NOW packets 100 times a second on a fixed channel. Our firmware prints each packet's Channel State Information (CSI): how 50+ Wi-Fi frequencies were changed on their way through the room.
  • Motion (Python): features every 0.1 s per board (motion size, smoothness, presence, signal strength), all relative to a 10-second scan of the empty room. A small neural network trained on our own recordings decides moving / still / empty; old hand-tuned rules are kept only as a baseline.
  • Breathing, in three steps:
    1. Is a still person there? A small neural network: still person vs empty room (15 s windows).
    2. How fast? A frequency analysis (FFT) in the 12-36 breaths/min band, checked against a phone on the belly.
    3. Breathing or holding their breath? Inside each packet we divide every frequency by the strongest one, which cancels the ESP32's own phase noise (the SA-WiSense idea). Then, over 8 s windows, we score how strong a breathing-speed rhythm is and whether the most rhythmic signals agree on one rate. This only runs when someone is present and still.
  • Labels without a camera: a camera can't see behind a curtain, so our recorders speak a script ("freeze", "move", "hold your breath", "breathe") and label the Wi-Fi with what they asked for.
  • Phone-checked labels: people don't do exactly what they're told. A phone (the phyphox accelerometer app) in the pocket flags moments where a "freeze" had a fidget, and on the belly it gives the true breathing rate. We train only where the phone agrees.
  • Quantization: the motion network is 451 parameters. int8 quantization keeps its accuracy (80.3% → 80.2%) at 451 bytes, small enough to run on an ESP32 itself.
  • Roblox (Luau): the game runs the booth step by step (step out, room scan, hide, 3-2-1, lights, results) and drives the hider's avatar from the Wi-Fi.
  • Vultr: a cloud server is the public address for Roblox and the judges' pages. The laptop connects out to it over a WebSocket, so no tunnel is needed, and the red-light decision stays on the laptop where the boards are. Vultr Serverless Inference runs the gator's lines and voice.
  • Tiger Data (TimescaleDB): every reading, recording, labelled training window and test result is stored, so every number here can be traced back to the data.

Challenges we ran into

  • Freezing isn't silent. With the transmitter 50 cm behind the hider, even standing still (sway, breathing) read as movement. With it in front of the curtain, moves were missed (2% caught). Placement matters as much as the model: the hider must be between the transmitter and receivers, about 1 m from the transmitter.
  • Spoken labels lie. Our first booth model called only 47% of freezes "still", because the person sometimes moved during a "freeze". The phone fixed the labels and the model.
  • Slow body sway hid breathing. The breathing rate read ~9/min for everyone until we limited it to 12-36 breaths per minute.
  • Our first breathing detector was fooled. It found a still person 100% of the time, but in breath-hold tests it still said "breathing" while the person held their breath. It was detecting a still body, not breathing. Signal strength alone could not tell them apart.
  • One good result isn't proof. The subcarrier-ratio rhythm looked good on one session, so we wrote down a pass/fail check before the next recording, and it failed (AUC 0.35). On the session after that, every breath-hold scored lower than the breathing next to it. We report both.
  • An empty room has "rhythm" too. Noise can look rhythmic, so the breathing check only counts when the motion model says someone is there and still.
  • The best receiver changes. One board tracked breathing within ~2/min while the others didn't; Gu et al. report the same.
  • Doing it solo in one weekend: hardware, firmware, ML, Roblox, cloud and pixel art.

Accomplishments that we're proud of

What Result Conditions
Booth model, phone-checked labels 87.5% accuracy; freezes called still 80% (was 47%); moves caught 90%; empty room 94.5% 3 booth sessions, each tested on a model that never saw it
Moving vs still vs empty, open room 96.6% accuracy, 0 false eliminations 2 sessions, each held out
Same, old hand-tuned rules 48% same data
Pretrained Wi-Fi model (CSI-BERT2) + our head 54.9% same test
Still person vs empty room 100% found, 0% empty-room false alarms 2 sessions behind the curtain, each held out
Same, old frequency rule 19-20% found same data
Breathing rate vs phone on the belly 2.3 breaths/min median error (was 12.6) 3 min natural breathing
Breathing vs breath-hold (rhythm) 12 of 12 holds scored lower than the breathing next to them (4 holds × 3 boards); live: holds called "breathing" 6%, real breathing 88% 1 session, standing still, 25 s holds; threshold set on this session
int8 vs float32 model 80.2% vs 80.3%, 4× smaller same booth sessions

And it works live: a real person behind a real curtain, judged in a Roblox game by Wi-Fi alone.

What we learned

  • Cheap ESP32s can feel a person through fabric, but where the boards sit matters as much as the model.
  • Ground truth is the hard part. A phone accelerometer made a better label checker than any camera, because it works behind the curtain.
  • Test the claim you actually make. "Detects breathing" and "detects a still person" look identical until someone holds their breath. Breath-hold tests caught our first detector.
  • Always compare against a baseline, test on sessions the model never saw, and decide the pass/fail check before looking at new data.
  • Keep the safety-critical decision local. The cloud is for reach, not for the red light.

What's next for GatorWave

  • Repeat the breath-hold test on new sessions, people and poses (lying down, sitting), with the phone on the belly, to confirm the rhythm result holds.
  • Run the int8 model on the ESP32 itself, with no laptop needed.
  • Fall detection for home care: a sudden big movement, then stillness, then breathing.
  • Two hiders judged separately using the three receivers as a spatial array (the zone recorder is built).
  • Use a building's existing Wi-Fi router instead of our transmitter, and test in a real car and home.

Honest limits: one person and a handful of sessions so far. ESP32s are not lab radios. The breathing-vs-holding result is from one session, with the threshold set on that same session, so it is promising, not proven. The Sensing Lab uses are prototypes shown at our booth, not tested in a real car, home or fire. A quiet signal never proves a room is empty.

Built with: ESP32, ESP-NOW, Python, NumPy/SciPy, FastAPI, Roblox Studio, Luau, Vultr, Vultr Serverless Inference, Tiger Data, TimescaleDB, phyphox, HTML Canvas

Research: MIT Wi-Vi (2013), RF-Pose (2018); Gu, Zhang, Liu, Ren (2019), Wi-Fi breathing and heart rate during sleep; SA-WiSense (2025); CSI-BERT2 (2025); Espressif ESP-CSI.

Built With

  • esp-now
  • esp32
  • fastapi
  • html
  • luau
  • numpy/scipy
  • phyphox
  • python
  • roblox-studio
  • tiger-data
  • timescaledb
  • vultr
  • vultr-serverless-inference
Share this project:

Updates

Submission history