Note · budding
What an ESP32 reports as WiFi CSI
- Stage
- budding
- Links
- 2 out, 1 in
Channel state information is what a WiFi receiver works out about the radio channel while it decodes a packet: one complex number per subcarrier, saying how that frequency was attenuated and delayed on the way. On an ESP32 it comes from the modem after the fact. It is a by-product of receiving 802.11 frames, and the chip never hands over raw I/Q samples.
These are the things I had to pin down before Aquila could use it.
Which chips. Espressif’s CSI interface exists on the ESP32, S2, C3, S3, C5, C6 and C61. The S3 is a WiFi 4 radio on 2.4 GHz only, which gives about 64 subcarriers on a 20 MHz channel and about 128 on 40 MHz, in a buffer of at most 612 bytes. More subcarriers would need the WiFi 6 training fields on a C6.
What a sample is. Each subcarrier is a pair of signed 8-bit integers. The first complex sample in the buffer is unreliable and has to be zeroed and flagged. The order of the pair is a trap of its own: see ESP32 CSI stores the imaginary part first.
There is no sample rate. A node produces a CSI frame when it receives a packet, so the rate is whatever traffic happens to be in the air. The firmware can cap it (Aquila caps at 200 Hz) but cannot guarantee a floor, and two nodes never sample at the same instants. Everything downstream has to resample onto a uniform grid, and refuse to when the gaps are too large instead of inventing samples.
Clocks drift. Each node timestamps frames from its own free-running timer, and those drift apart by tens of parts per million. Aquila corrects this in software with an NTP-style ping and a per-node straight-line fit of device time against arrival time. The budget is 3 ms RMS between nodes. Sub-millisecond alignment is out of reach this way.
Phase does not compare across nodes. Separate ESP32s share no RF reference, so the phase one node measures means nothing next to another’s, and no amount of software recovers it. Within one node the usual cleaning applies: unwrap the phase, then remove the straight-line slope and offset across subcarriers.
What follows from that. Amplitude changes over time carry presence and motion. Breathing sits in a 0.1 to 0.6 Hz band and heartbeat in 0.8 to 2.5 Hz, and both need a clean, well-sampled signal from a subset of subcarriers chosen at calibration. Angle of arrival, Doppler and anything that needs coherent phase between nodes are not available from this hardware.
Projects
- Aquila Presence, occupancy and vital signs for Home Assistant, sensed from WiFi by ESP32-S3 nodes.
Projects
- Aquila Presence, occupancy and vital signs for Home Assistant, sensed from WiFi by ESP32-S3 nodes.