FAQ
Hardware
Which board should I buy?
An ESP32-C6 if you have no other constraint. It is RISC-V (so it builds on stable Rust), it has native USB-Serial-JTAG, and it is verified as an emitter. An ESP32-C5 is the same answer plus dual-band 2.4/5 GHz, which is worth having if you care about wider channels or better delay resolution.
How many boards do I need?
One is enough to sniff ambient traffic and confirm everything works. Two is what you actually want, because the controlled emitter/collector pairing — one board sounding the channel at a known rate, one measuring it — is the basis of almost all sensing work.
Which chips can act as an emitter?
Every supported chip can collect. Emitter support varies by part and by the
esp-radio version underneath, so check the current status in the
esp-csi-rs repository before buying
for that purpose. If raw injection is not an option on the hardware you have,
the associated-link arrangement — a softAP collector paired with a station —
puts energy in the channel without it.
Do I need special antennas or RF equipment?
No. Development board antennas are fine. Node placement affects results far more than antenna quality — see Troubleshooting & Calibration.
Getting Data
I flashed the firmware and see nothing.
In order: is the channel right (defaults across this ecosystem disagree — 1 in
some places, 7 in others)? On a C5, is the channel in the band you meant (the
default 149 is 5 GHz)? Is there any traffic on that channel at all? Is the baud
rate right? Then run show-stats and see whether the radio received anything.
I get far fewer packets than the emitter is sending.
Almost always the console, not the radio. text mode emits twenty-odd lines per
packet; switch to array-list or serialized and measure again. show-stats
distinguishes the two cases: a healthy receive count with few delivered rows
means the delivery path is the limit.
My subcarrier count is stuck at ~53 even though I configured HT40.
The default CSI configuration acquires legacy and ACK frames too, and those
arrive at ambient rates and swamp the HT40 reports. Turn them off:
set-csi --csi-legacy=off --csi-ht20=off --csi-ht40=on --dump-ack=off. Also
check that the 40 MHz block fits in the band — above on channel 7 reaches
channel 11.
Some rows have sequence numbers that make no sense.
Those are other people’s frames. A collector is promiscuous and reports CSI for
every frame it decodes, and the leading field is each frame’s own 802.11
sequence number, which is per-transmitter. Filter with
set-csi-filter --peer-mac=<emitter> or --min-phy=ht.
Naming and Versions
What happened to set-collection-mode?
It became set-csi-output --enabled=<true|false> (and POST /config/csi-output
over HTTP). Once “collector” became the name of the receive role, it could no
longer also name a delivery setting. The behaviour is unchanged: capture keeps
running, delivery stops.
What happened to esp-csi-rs-core?
It existed only between 0.9.0 and 0.10.0, and the split has been undone —
everything is back in esp-csi-rs. The core crate stays published and is
not yanked, because esp-csi-rs 0.9.0 depends on it, but it receives no
further versions. Change esp_csi_rs_core:: to esp_csi_rs::; the paths are
otherwise identical.
Is it csi-webserver or csi-webserver-rs?
The repository is csi-webserver-rs; the published crate is csi-webserver.
Same for the client. The esp- prefix is reserved for crates that run on an
Espressif part, which is why there is no esp-csi-webserver-rs — that name was
never published.
Why isn’t esp-csi-cli-rs on crates.io?
It is firmware, not a library. Get it from the GitHub releases as a prebuilt
per-chip .bin, or build it from source. The same is true of
esp-csi-litetui-rs and esp-csi-litegui-rs.
Did ESP-NOW get removed?
No. Some front-end READMEs say so, and they are out of step: esp-csi-rs 0.10.1
retains the ESP-NOW central and peripheral topologies, and esp-csi-cli-rs
still exposes four ESP-NOW modes. What is true is that the open webserver
baseline names only five Wi-Fi modes and does not route the ESP-NOW ones.
Choices
2.4 GHz or 5 GHz?
Only the C5 gives you the choice. 5 GHz has wider channels (better delay resolution for impulse-response work) and is usually less congested; 2.4 GHz propagates further and through more walls. The two bands see the same room differently, which is itself sometimes the point.
Which log mode should I use?
text when a human is reading. array-list when you want one parseable line
per packet. serialized when a program is consuming it — it is the densest, and
it is what the host tools decode. esp-csi-tool when you have an existing
analysis script written against ESP32-CSI-Tool.
Callback or async delivery?
Callback if the work fits inside the Wi-Fi hot path — no heap, no locks, no I/O,
no .await. Async if it does not; it costs a ~640-byte copy per packet and buys
you a normal task to work in. Exactly one is active at a time, and
set_csi_delivery_mode switches between them at runtime.
defmt or println?
println to start with: any serial monitor reads it. defmt when console
bandwidth is the constraint — it is much denser on the wire, at the cost of
needing espflash --log-format defmt to decode and three extra lines of project
setup. Note that defmt shrinks each line; it does not make the radio capture
more.
Doing Something With It
Why does my phase data look like noise?
Because the receiver’s own clock is in it. The transmitter and receiver oscillators are free-running and not locked to each other, so every packet is measured against a slightly different reference. Three effects follow:
- Phase-locked loop jitter — the receiver’s PLL does not reacquire to the same phase on each packet, so every measurement carries a different constant offset.
- Carrier frequency offset — the oscillators differ in frequency, so the error accumulates as phase between packets.
- Sampling frequency offset and packet detection delay — timing errors that tilt phase across subcarriers, by a different amount in each packet.
None of these are properties of the room, and together they are larger than the signal you are looking for. Raw phase is therefore genuinely not a measurement of the environment until those terms are accounted for — see Signal Processing in Rust for the standard ways of doing that.
My model works in one room and fails in another.
That is the field’s central open problem, not a bug in your pipeline. The channel response encodes the room’s geometry alongside the activity, and the model cannot tell them apart. Partial mitigations — training across domains, choosing position-invariant features such as Doppler, calibrating against an empty-room baseline on deployment — are discussed in Edge AI & Classification.
Can I run inference on the device?
For the easy tasks, yes, and it is often the right call: a node that transmits “motion detected” instead of a CSI stream saves airtime, power, and privacy. Presence detection frequently needs no model at all — a variance threshold against a baseline works. For anything harder, prototype on a host first.
My Parquet file will not open.
It was not stopped cleanly. The footer is written when the session ends;
unplugging a board to end a capture leaves the file without one. Use stop.