"The router was never trying to see you. It was only trying to equalize its own channel. That it can count your breaths is an accident of physics the standards committee is still deciding how to feel about."
An Eavesdropping AI Agent
Prerequisites
This section assumes the wave-propagation and measurement-model discipline of Chapter 2: a sensor hands you a physically transformed proxy, and here the proxy is a complex channel gain that no one built for perception. It leans on the sampling, frequency, and phase vocabulary of Chapter 3, since every CSI value is a per-frequency phasor and every artifact we sanitize is a timing or frequency offset in disguise. You should be comfortable with complex exponentials, the discrete Fourier transform of Chapter 7, and the idea of a wavelength (\(\lambda \approx 5.2\) cm at 5.8 GHz Wi-Fi). This chapter advances the measure and infer verbs. The device-free activity models built on top of CSI arrive in Section 47.2; this section builds only the raw measurement and what has to happen to it before any model sees it.
A perception sensor hiding inside every access point
Wi-Fi sensing is the most audacious act of sensor repurposing in this book. No new hardware ships. The same OFDM radios that stream video already estimate, on every received packet, how the wireless channel distorted each frequency component, because they must undo that distortion to decode bits. That estimate is channel state information (CSI): a vector of complex numbers, one per subcarrier per antenna pair, describing amplitude attenuation and phase shift across the band. Because a human body reflects, absorbs, and phase-shifts 2.4 and 5 GHz energy, a person walking through the link perturbs those numbers in structured, learnable ways. From that single by-product, researchers have read gaits, gestures, respiration, and even coarse body pose (Section 47.3). But CSI as it leaves a commodity chip is a hostile signal: corrupted by automatic-gain jumps, sampling-clock offsets, and random phase spins that dwarf the millimeter-scale motion you want. This section builds what CSI physically is, why it is measurable at all, and the sanitization that turns a communications diagnostic into a usable sensor.
What CSI is: the channel as a per-subcarrier phasor
What a Wi-Fi receiver estimates is the frequency response of the propagation channel. Modern Wi-Fi uses orthogonal frequency-division multiplexing (OFDM), which splits the 20, 40, 80, or 160 MHz band into many narrow, independently modulated subcarriers. For each subcarrier \(k\), the received symbol \(Y_k\) relates to the transmitted symbol \(X_k\) through a single complex channel coefficient \(H_k\) plus noise:
$$Y_k = H_k X_k + N_k, \qquad H_k = |H_k|\, e^{j\angle H_k}.$$The receiver already knows \(X_k\) for the known training symbols in every packet preamble, so it solves for \(H_k\) directly. That estimate, stacked across the \(K\) subcarriers, is the CSI. Why it is a rich sensing signal is that \(H_k\) is the coherent sum of every path the signal took, direct, floor-bounced, wall-bounced, body-bounced:
$$H_k = \sum_{p} a_p\, e^{-j 2\pi f_k \tau_p},$$where path \(p\) has complex gain \(a_p\) and delay \(\tau_p\), and \(f_k\) is the subcarrier frequency. A target moving by half a wavelength changes a reflected path's length by \(\lambda\), spinning that path's phasor through a full \(2\pi\). At 5.8 GHz a mere 2.6 cm of motion completes that cycle, which is precisely why CSI can resolve the centimeter-scale chest displacement of breathing. How it differs from the received signal strength indicator (RSSI) that older Wi-Fi localization used is dimensionality: RSSI is one scalar per packet, a lossy sum of squared magnitudes, whereas CSI gives \(K\) complex values, exposing frequency-selective fading that a scalar averages away. On an Intel 5300 card you get 30 subcarriers per antenna stream; on an ESP32 you get up to 64; on an 802.11ac 80 MHz link, 256. More subcarriers means finer delay resolution, the same range-resolution-from-bandwidth argument that governs the FMCW radar of Chapter 44.
CSI is a bistatic, uncalibrated, opportunistic radar
It clarifies everything downstream to name what CSI actually is. Radar (Chapter 44) transmits a known waveform and coherently measures its own echo with a shared clock. CSI does the same physics, path gains and delays, but under three handicaps: the transmitter and receiver are separate devices (bistatic) with independent, drifting oscillators; the "waveform" is whatever data packet happened to be sent, at irregular times; and no one calibrated the chain for sensing. The prize is that the hardware is already deployed in a billion homes. The price is that the clean, absolute phase a radar enjoys is buried under offsets you must estimate away before the body's contribution is visible. Every sanitization step below exists to reclaim, from an opportunistic bistatic link, the coherence a purpose-built radar gets for free.
Why raw CSI is not yet a sensor: the artifact stack
What corrupts commodity CSI is a stack of receiver-side impairments that have nothing to do with the environment. Automatic gain control (AGC) rescales the whole vector packet to packet, so raw amplitude carries a nuisance multiplier. Worse, the phase is wrecked by three offsets. Sampling time offset (STO) arises because the receiver's analog-to-digital clock is not aligned to the symbol boundary; a residual timing error \(\delta\) adds a phase ramp \(-2\pi f_k \delta\) that grows linearly across subcarriers. Carrier frequency offset (CFO) comes from the transmit and receive oscillators sitting at slightly different frequencies, adding a phase term that drifts across packets. Sampling frequency offset (SFO) and packet-detection jitter add further per-packet spins. Why this matters is one of scale: these offsets rotate the phase by many radians, while a breathing chest contributes a fraction of one. Feed raw phase to a model and it learns the oscillator, not the person.
How practitioners recover usable signal follows two classic ideas. For amplitude, normalize each packet (for example divide by its own mean or median across subcarriers) to cancel the AGC multiplier, then filter in slow time. For phase, exploit structure: STO is a linear ramp across subcarriers, so fitting and subtracting a per-packet line removes both the ramp and the constant offset. Even more robust is the CSI ratio: divide the CSI of one antenna by another on the same receiver. Because CFO, STO, and SFO are shared across a receiver's antennas (one clock, one packet-detection instant), they cancel in the ratio, leaving a stable complex quantity whose variation is dominated by genuine path changes. The conjugate-multiplication and ratio tricks are what made robust respiration sensing possible on unmodified hardware.
Eldercare: counting breaths through a $6 chip
A smart-home startup wants nighttime respiration monitoring for an assisted-living apartment without a wearable or a camera. They place a $6 ESP32-S3 as transmitter and a second as receiver on opposite nightstands, streaming CSI at 100 packets per second across 64 subcarriers. Raw amplitude on any single subcarrier is a mess: AGC steps and multipath nulls swamp the roughly 0.5 mm chest wall motion. The engineer computes the CSI ratio between the two receive antennas, which cancels the shared clock offsets, then band-pass filters the ratio's phase between 0.1 and 0.5 Hz (6 to 30 breaths per minute). A clean sinusoid emerges, and a simple peak-count yields respiration rate within one breath per minute of a clinical chest belt. The lesson that governs the whole chapter surfaces here: the raw sensor was unusable, and the entire signal lived in a ratio that removed hardware nuisance before any model was trained. Cross-environment robustness, the reason a model tuned in this apartment may fail in the next, is the subject of Section 47.6.
From complex channel to model-ready tensor
What a learning pipeline consumes is a tensor, not a phasor list. A window of CSI is naturally shaped \([\text{time} \times \text{subcarrier} \times \text{antenna-pair}]\), complex-valued. Why the representation choice matters is that different tasks live in different domains. Amplitude spectrograms over a sliding window expose the Doppler signature of gross motion (walking, falling), the same time-frequency view of Chapter 7. Sanitized phase, or phase differences across antennas, carries fine displacement and angle-of-arrival cues. An inverse Fourier transform across subcarriers yields a coarse channel impulse response, separating paths by delay, which is how through-wall systems isolate a person from static clutter (Section 47.4). How you feed it to a network follows the encoder discipline of Chapter 13: treat subcarrier-antenna as channels of a 1D or 2D convolution over time, or tokenize windows for the transformers of Chapter 15. Whatever the model, the leakage-safe splitting of Chapter 5 is non-negotiable here: CSI within one recording session is so autocorrelated that random-window splits leak nearly perfect neighbors into the test set and report fantasy accuracy.
import numpy as np
def sanitize_csi(csi, ref_ant=0):
"""csi: complex array [packets, subcarriers, antennas] from a commodity NIC.
Returns a clock-offset-free feature: CSI ratio magnitude + unwrapped phase."""
# 1. AGC / scale nuisance: normalize each packet by its own energy.
scale = np.sqrt((np.abs(csi) ** 2).mean(axis=(1, 2), keepdims=True))
csi = csi / (scale + 1e-9)
# 2. CSI ratio vs a reference antenna cancels CFO/STO/SFO (shared clock).
ratio = csi / (csi[:, :, ref_ant:ref_ant + 1] + 1e-9) # [P, K, A]
amp = np.abs(ratio) # clean amplitude
# 3. Residual STO shows as a linear phase ramp across subcarriers: remove it.
phase = np.unwrap(np.angle(ratio), axis=1)
k = np.arange(csi.shape[1])[None, :, None]
slope = (phase[:, -1:, :] - phase[:, :1, :]) / (k[:, -1:, :] - k[:, :1, :] + 1e-9)
phase = phase - slope * k # de-ramped phase
return amp, phase
# Synthetic demo: 2 antennas, shared random clock spin + one moving path.
P, K, A = 300, 30, 2
sc = np.linspace(-15, 15, K)[None, :, None]
clock = np.exp(-1j * 2 * np.pi * (0.07 * sc) * np.random.randn(P, 1, 1)) # STO
breath = np.exp(1j * 0.3 * np.sin(2 * np.pi * 0.25 * np.arange(P))[:, None, None])
csi = (1.0 + 0.2 * breath) * clock + 0.02 * (np.random.randn(P, K, A)
+ 1j * np.random.randn(P, K, A))
amp, phase = sanitize_csi(csi)
print("ratio amplitude std across subcarriers:", amp[:, :, 1].std().round(4))
Right tool: PicoScenes and CSIKit
Parsing a proprietary CSI binary from an Intel 5300, Atheros, or Nexmon-patched Broadcom chip by hand is roughly 300 to 500 lines of byte-unpacking, subcarrier de-interleaving, and endian wrangling per vendor, and every firmware revision breaks it. The CSIKit Python library reads all the common commodity formats and yields a normalized amplitude/phase array in about five lines, and the PicoScenes platform unifies capture and parsing across Intel AX210, QCA, and USRP hardware. Using them collapses hundreds of lines of fragile vendor-specific parsing into a single reader.read() call and hands you exactly the \([\text{packet} \times \text{subcarrier} \times \text{antenna}]\) tensor the sanitizer above expects. Write the physics yourself once to understand it; ship on the library.
What CSI can and cannot see
What sets the ceiling on Wi-Fi sensing is the same resolution physics as any wave sensor. Delay (range) resolution is \(c/(2B)\), so a 20 MHz link resolves paths only to about 7.5 m, far too coarse to image; sensing works by tracking changes in the superimposed channel, not by resolving individual reflectors. Widening to 80 or 160 MHz sharpens this, and combining bandwidth with multiple antennas (MIMO) buys crude angle, which is what the 802.11bf standardization effort of Section 47.5 aims to make first-class. Why the modality still wins is coverage and privacy posture: RF passes through walls and darkness, needs no line of sight, and captures no photograph, which is exactly the property that makes it attractive for elderly-fall detection and simultaneously fraught for consent, the tension Section 47.7 confronts. When to reach for CSI over the alternatives: choose it when the sensing must be unobtrusive and infrastructure-free, when lighting or occlusion defeats cameras, and when coarse motion or vital signs, not identity-grade detail, is the target. When you need precise, calibrated ranging, the FMCW and mmWave radar of Chapter 44 remains the sharper instrument.
Frontier: from handcrafted CSI features to cross-domain foundation encoders
Classic CSI pipelines (Widar3.0's body-coordinate velocity profile, the DFS-based signatures of early gesture work) engineered domain-invariant features by hand so a model would transfer across rooms and orientations. The current frontier replaces that with learned invariance: self-supervised and contrastive pretraining (the recipes of Chapter 17) on large unlabeled CSI corpora, and cross-modal supervision that distills a camera or radar teacher into a CSI student so the student inherits pose or depth structure it could never label alone. RF-Pose and DensePose-from-WiFi (Section 47.3) are the flagship results; the open problem is a single CSI encoder that survives new hardware, new rooms, and new subcarrier layouts without per-site retuning, the robustness gap quantified in Section 47.6.
Exercise: prove the ratio, not the raw phase, is stable
Extend the code above. (1) Plot the unwrapped phase of a single raw subcarrier, np.angle(csi[:,0,1]), across the 300 packets and confirm the injected clock spin dominates the 0.25 Hz breathing. (2) Plot the phase of the ratio for the same subcarrier and confirm the sinusoid now stands out. (3) Estimate the breathing frequency from the ratio phase with an FFT and check it lands near 0.25 Hz. (4) Sweep the clock-spin magnitude from 0.01 to 0.3 and report at what level raw-phase estimation fails while the ratio still succeeds. State, in one sentence, why the ratio is immune.
Self-check
1. Why does CSI carry far more sensing information than RSSI, and what physical quantity does each subcarrier's phasor encode? 2. Name the three phase impairments in commodity CSI and explain why forming a CSI ratio between two antennas on the same receiver cancels them. 3. Given a 20 MHz Wi-Fi link, why can you not resolve two reflectors 2 m apart by their delay, yet still detect a person breathing between them?
What's Next
In Section 47.2, we put this sanitized channel to work: device-free human activity recognition and presence detection, where the same CSI tensor drives models that tell an empty room from an occupied one and a walk from a fall, and where the leakage-safe, cross-environment evaluation discipline first flagged here decides whether the reported accuracy is real or an artifact of testing in the training room.