WiFi moves in mysterious ways. It is invisible, silent, and odorless. Yet it can be harnessed to detect motion!Β
When someone moves in a room, they "disturb" the WiFi waves traveling between the router and the sensor. It's like when you move your hand in front of a flashlight and see the shadow change.
The ESP32 device "listens" to these changes and understands if there's movement.
This project is an Arduino port of ESPectre on a standalone microcontroller, in this case an Adafruit Feather ESP32-S3 Reverse TFT. The guide will also show how to use Claude Code to develop the port from the original project to the Arduino-on-Feather platform.
The Core Concept: WiFi CSI (Channel State Information)
Every WiFi packet that travels between your ESP32 and a router carries hidden data about the channel β specifically how the signal was distorted by the environment along the way. This distortion is encoded as complex numbers across multiple subcarrier frequencies. This is Channel State Information (CSI for short).
When a person is present and moving, their body absorbs, reflects, and scatters the radio waves, subtly changing those complex values. The ESP32's esp_wifi API exposes a callback (esp_wifi_set_csi_rx_cb) that fires on every received packet, providing the raw CSI data to analyze.
Signal Processing Pipeline
The code extracts amplitude from each subcarrier by computing sqrt(realΒ² + imagΒ²), averaged across subcarriers to get a single scalar per packet, then feeds those values into a circular buffer (a window ofΒ about 15 samples). The key metric is normalized variance β how much the signal is fluctuating relative to its mean. When the room is empty and still, variance is low and stable. When a person moves, it spikes.
The detection logic: compute the rolling variance, compare it to a calibrated baseline established at startup, and trigger if it exceedsΒ baseline + threshold.
Key Practical Details
- The ESP32 needs to be connected to WiFi to receive packets β no traffic, no CSI data
- Optimal distance from the router is 3β8 meters; too close (1m) and the signal is too strong and stable to show useful variation
- A calibration phase at boot (10β30 seconds of stillness) establishes the baseline variance
What It Can and Can't Do
It works through walls and in the dark with no camera or PIR sensor β just the ambient WiFi signal. The tradeoff is that it's noisy, environment-dependent, and requires tuning per-location. It's more of a presence/activity detector than a precise motion tracker.
The code runs in Arduino on the ESP32-S3 rather than CircuitPython, since the esp_wifi CSI API isn't exposed to CircuitPython β it requires direct access to the ESP-IDF layer, which only Arduino/C++ provides.
Page last edited March 25, 2026
Text editor powered by tinymce.