Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Driving the CLI

Getting Started flashed esp-csi-cli-rs and ran a capture on the defaults. This section uses the rest of it: the real configuration surface, the two-board pairings, and how to tell on hardware whether a pairing is actually working.

Everything here happens over the serial console. The command reference lives in the esp-csi-cli-rs section; this is the operating guide.

The One Rule

Most commands stage configuration; they do not apply it. They write into an in-memory UserConfig, and the next start builds a node from it. Only set-log-mode, restart, and set-csi-delivery take effect immediately.

This explains most confusion at the console. If a change appears to do nothing, it is because you have not restarted the session. show-config prints the staged state; reset-config restores defaults.

A Single-Board Capture, Configured

Ambient sniffing on a channel your own network uses, in a format worth parsing:

reset-config
set-wifi --mode=sniffer --set-channel=6
set-protocol --protocol=n
set-traffic --frequency-hz=0
set-log-mode --mode=array-list
show-config
start

Two choices there are deliberate. --frequency-hz=0 disables the traffic generator, because a sniffer has nothing to generate traffic for — it is measuring what already exists. And array-list is one line per packet instead of text’s twenty-odd, which matters as soon as the rate rises.

Emitter and Collector

This is the arrangement most sensing work is built on, and the reason to own two boards. One sounds the channel at a known rate and bandwidth; the other measures it. Nothing associates, so there is no handshake to fail.

Collector board — lock the emitter’s channel and measure everything overheard:

set-wifi --mode=sniffer --set-channel=6
set-traffic --frequency-hz=0
set-log-mode --mode=array-list
start

Emitter board — 20 MHz injection every 20 ms, so roughly 50 frames a second:

set-wifi --mode=ht20-emitter --set-channel=6 --inject-period-ms=20
start

Use --mode=ht40-emitter for 40 MHz.

A single emitter sounds every collector in range at once, and adding collectors costs the emitter nothing. Going the other way, several emitters can share one collector: each frame carries its transmitter’s MAC, so the collector attributes measurements by source. Unicasting to one collector with --peer-mac tends to raise that collector’s CSI rate noticeably compared to broadcasting.

Cleaning Up the Capture

Out of the box, that collector reports CSI for everything it decodes — your AP’s beacons and ACKs, the association exchange, and any third-party device on the channel — mixed in with your emitter’s frames. Those rows are valid CSI, but they look wrong next to the ones you want: the leading field is the frame’s own 802.11 sequence number, which is per-transmitter and so neither starts at zero nor shares a counter with your traffic, and a legacy-rate frame carries the shorter L-LTF-only payload.

Two commands fix this, and both are worth applying by default:

set-csi-filter --peer-mac=aa:bb:cc:dd:ee:ff
set-csi --csi-legacy=off --csi-ht20=off --csi-ht40=on --dump-ack=off

The first delivers CSI only from your emitter’s MAC — set-csi-filter --min-phy=ht is the looser version, keeping 802.11n and better while dropping legacy-rate management and control frames. The second restricts what the radio acquires at all. On classic parts (ESP32, C3, S3) set-csi takes --lltf, --htltf, --stbc-htltf, and --ltf-merge instead.

Filtering on the device rather than on the host returns console bandwidth to the traffic you asked for: a rejected frame is dropped in the Wi-Fi callback before the packet copy and before any formatting. Rejected frames are still counted in show-stats as RX drops, so the gap between captured and delivered stays visible rather than unexplained.

Confirming HT40 Actually Engaged

If you configured ht40-emitter and the collector’s rows still show about 53 subcarriers, HT40 did not engage. Check the collector’s csi_data_len: 100 or more (commonly ~117) confirms 40 MHz; ~53 or ~56 means it fell back to legacy or HT20.

The usual causes, in order of likelihood:

  1. Legacy and ACK acquisition still on. Those reports arrive at ambient rates and swamp the HT40 ones. Apply the set-csi line above.
  2. No room in the band. --ht40=above on channel 7 occupies up to channel 11 and --ht40=below occupies down to channel 3. A primary too close to the band edge silently falls back.
  3. Channel mismatch. The two boards are not on the same primary channel.

When you want CSI from an ordinary Wi-Fi link rather than from blind sounding, pair a softAP collector with a station.

Board A, the AP collector:

reset-config
set-wifi --mode=wifi-ap --set-channel=6 --ap-ssid=esp-csi-ap
set-protocol --protocol=n
set-traffic --frequency-hz=4000
set-log-mode --mode=array-list
start

Board B, the station:

reset-config
set-wifi --mode=station --sta-ssid=esp-csi-ap --set-channel=6
set-protocol --protocol=n
set-traffic --frequency-hz=4000
set-log-mode --mode=array-list
start

CSI appears primarily on board A, from the station’s uplink replies.

The AP’s DHCP pool holds four leases by default, and with more than one lease the traffic round-robins across all associated stations — so the pair scales to several station boards with no extra configuration. --ap-leases=<1-8> sizes the pool (1 is a single-target flood). The offered rate is shared: with N stations, each sees roughly frequency-hz / N packets per second.

For time-aligned multi-receiver measurements, --ap-burst=on sends one frame back-to-back to every station each tick instead of round-robining. Total airtime then becomes frequency-hz × leases, so size it accordingly.

On a C5, the channel number selects the band. The default is 149, which is 5 GHz. Associating to a 2.4 GHz AP from that default reports only “no access point found” — pass an explicit 2.4 GHz channel.

ESP-NOW Pairs

Connectionless: no AP, no DHCP, no association, and both sides capture.

# Central board
set-wifi --mode=esp-now-central --set-channel=6
set-rate --rate=mcs0-lgi
set-log-mode --mode=array-list
start

# Peripheral board
set-wifi --mode=esp-now-peripheral --set-channel=6
set-rate --rate=mcs0-lgi
set-log-mode --mode=array-list
start

With more than two boards on one channel, set --peer-mac on both ends to pin the pair explicitly rather than relying on magic-prefix discovery.

The asymmetric variant reaches the highest CSI rate of any pairing, because the collector stops transmitting once it hears a source and all airtime then belongs to one transmitter. Start the collector first:

# Collector board
set-wifi --mode=esp-now-fast-collector --set-channel=6
set-log-mode --mode=serialized
start

# Source board
set-wifi --mode=esp-now-fast-source --set-channel=6
start

set-rate does not apply to this pair — the fast profile fixes its own PHY.

Reading the Counters

show-stats is the first thing to reach for whenever a capture looks wrong. It reports packets transmitted and received, rates in Hz, and drop counts.

Interpreting it is mostly one distinction. If the received count tracks your emitter’s rate but the rows arriving on your terminal are fewer, the bottleneck is the console or the async logging queue, not the radio — switch to a denser format (array-list, or serialized) or raise the emitter’s period. If the received count itself is low, the radio genuinely is not hearing the emitter, and the problem is channel, band, distance, or acquisition configuration.

Drop counts also include frames rejected by set-csi-filter, which is intentional: it keeps the difference between captured and delivered visible rather than making filtered frames vanish silently.

Where to Go Next

Driving two boards from two serial terminals stops scaling quickly. The next section puts a server in front of them, so configuration, control, and recording happen in one place: Streaming to a Host.