10 KiB
Usage Guide
Firmware Variants
This repo contains three firmware approaches for CSI data collection:
| Firmware | Directory | Output | Use Case |
|---|---|---|---|
| csi_recv_router | get-started/csi_recv_router/ |
UDP to Pi | Production - deployed on 3 sensors |
| csi_recv | get-started/csi_recv/ |
Serial console | Development/debugging |
| csi_send | get-started/csi_send/ |
N/A (transmit only) | Paired with csi_recv |
csi_recv_router (Deployed Firmware)
How It Works
- ESP32 connects to WiFi router as a station
- Pings the gateway at 100 Hz (10ms interval)
- Each ping response triggers a CSI callback
- CSI data is formatted as CSV and sent via UDP
Data Flow
Router ←── ping ──→ ESP32 ──── UDP ──→ Pi (192.168.129.11:5500)
(100 Hz) (CSI_DATA)
CSI Data Format (ESP32)
CSI_DATA,<seq>,<mac>,<rssi>,<rate>,<sig_mode>,<mcs>,<cwb>,<smoothing>,
<not_sounding>,<aggregation>,<stbc>,<fec_coding>,<sgi>,<noise_floor>,
<ampdu_cnt>,<channel>,<secondary_channel>,<timestamp>,<ant>,<sig_len>,
<rx_state>,<len>,<first_word_invalid>,"[csi_values]"
| Field | Type | Description |
|---|---|---|
| seq | int | Packet sequence number (increments from 0) |
| mac | MAC | Gateway MAC address |
| rssi | int8 | Received signal strength (dBm) |
| rate | uint8 | PHY rate |
| sig_mode | uint8 | 0=non-HT, 1=HT, 3=VHT |
| mcs | uint8 | Modulation and coding scheme |
| cwb | uint8 | Channel bandwidth (0=20MHz, 1=40MHz) |
| smoothing | uint8 | Channel estimate smoothing |
| not_sounding | uint8 | Not sounding frame |
| aggregation | uint8 | AMPDU aggregation |
| stbc | uint8 | Space-time block coding |
| fec_coding | uint8 | FEC coding (0=BCC, 1=LDPC) |
| sgi | uint8 | Short guard interval |
| noise_floor | int8 | Noise floor (dBm) |
| ampdu_cnt | uint8 | AMPDU sub-frame count |
| channel | uint8 | Primary channel number |
| secondary_channel | uint8 | Secondary channel offset |
| timestamp | uint32 | Local timestamp (microseconds) |
| ant | uint8 | Antenna number |
| sig_len | uint16 | Signal length |
| rx_state | uint32 | RX state (0 = valid) |
| len | int | CSI data length (bytes) |
| first_word_invalid | bool | First 4 bytes invalid flag |
| csi_values | int8[] | Raw CSI I/Q values (interleaved) |
CSI Values Interpretation
The csi_values array contains interleaved I/Q pairs per subcarrier:
[I0, Q0, I1, Q1, I2, Q2, ...]
- Each pair represents one OFDM subcarrier
- Amplitude:
sqrt(I^2 + Q^2) - Phase:
atan2(Q, I) - Typical length: 128 bytes (64 subcarriers x 2 values) for HT20
csi_recv vs csi_recv_router Comparison
| Aspect | csi_recv | csi_recv_router |
|---|---|---|
| WiFi mode | STA, no AP connection | STA, connects to router |
| CSI source | ESP-NOW packets from csi_send | Ping responses from gateway |
| Output | Serial (ets_printf) |
UDP socket |
| Requires | Paired csi_send device | WiFi router only |
| Promiscuous mode | Yes | No |
| MAC filtering | Fixed 1a:00:00:00:00:00 |
Gateway BSSID |
| WiFi credentials | Not needed | Required (menuconfig) |
| CSI config (ESP32) | All LTF types enabled | LLTF only (router compat) |
| Gain compensation | Logged to serial | Applied to UDP data |
| Sequence counter | From ESP-NOW payload (rx_id) |
Local counter (s_count) |
| Network | Standalone (no router needed) | Depends on router |
| Deployment | Lab/testing | Production |
Key Differences in CSI Config (ESP32 target)
| Setting | csi_recv | csi_recv_router |
|---|---|---|
lltf_en |
true | true |
htltf_en |
true | false |
stbc_htltf2_en |
true | false |
manu_scale |
false | true |
shift |
false | true |
The router firmware uses only LLTF for broader router compatibility. The ESP-NOW firmware enables all LTF types since it controls both endpoints.
CSI Configuration Reference
sdkconfig.defaults
WiFi Settings
| Config | Value | Description |
|---|---|---|
CONFIG_ESP32_WIFI_CSI_ENABLED |
y |
Required. Enables CSI extraction from received frames |
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED |
(empty) | Disables TX aggregation. Aggregated frames combine CSI, reducing quality |
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED |
(empty) | Disables RX aggregation (csi_recv_router only) |
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM |
128 |
Number of dynamic RX buffers. Higher = fewer drops at high CSI rates. Default is 32 |
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM |
32 |
Number of dynamic TX buffers |
Performance Settings
| Config | Value | Description |
|---|---|---|
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ |
240 |
Max CPU clock. Ensures CSI callback and UDP send complete within 10ms |
CONFIG_COMPILER_OPTIMIZATION_PERF |
y |
-O2 optimization. Faster CSI processing |
CONFIG_FREERTOS_HZ |
1000 |
1ms tick resolution. Required for 100 Hz ping timer accuracy |
System Settings
| Config | Value | Description |
|---|---|---|
CONFIG_ESP_TASK_WDT_TIMEOUT_S |
30 |
Watchdog timeout. Extended from default 5s to avoid false resets during WiFi reconnect |
CONFIG_ESP_CONSOLE_UART_BAUDRATE |
921600 |
Fast serial for debug output |
CONFIG_ESPTOOLPY_MONITOR_BAUD |
921600 |
Monitor baud rate (must match console) |
Kconfig.projbuild (Custom Settings)
| Config | Default | Range | Description |
|---|---|---|---|
CONFIG_CSI_UDP_TARGET_IP |
192.168.129.11 |
Any IPv4 | Destination IP for UDP CSI packets |
CONFIG_CSI_UDP_TARGET_PORT |
5500 |
1024-65535 | Destination UDP port |
wifi_csi_config_t (Code-Level Settings)
ESP32 (Xtensa)
| Field | csi_recv_router | Description |
|---|---|---|
lltf_en |
true |
Enable Legacy Long Training Field. Most compatible with routers |
htltf_en |
false |
HT-LTF. Only in HT (802.11n) frames. Disabled for router compat |
stbc_htltf2_en |
false |
STBC HT-LTF2. Only with STBC encoding |
ltf_merge_en |
true |
Merge multiple LTF into one CSI report |
channel_filter_en |
true |
Apply channel estimation filter |
manu_scale |
true |
Manual amplitude scaling |
shift |
true |
Bit shift for value range |
Compile-Time Defines (app_main.c)
| Define | Value | Description |
|---|---|---|
CONFIG_SEND_FREQUENCY |
100 |
Ping rate in Hz (10ms interval) |
CONFIG_FORCE_GAIN |
0 |
Force AGC/FFT gain to baseline (disabled) |
CONFIG_GAIN_CONTROL |
1 (auto) |
Enable gain compensation (ESP32-S3, C3, C5, C6, C61) |
Remote Management
Commands (esp-cmd)
Send commands to individual devices over UDP port 5501:
esp-cmd <host> STATUS # Uptime, heap, RSSI, tx_power, rate, version
esp-cmd <host> IDENTIFY # LED solid 5s
esp-cmd <host> RATE <10-100> # Set ping rate (NVS saved)
esp-cmd <host> POWER <2-20> # Set TX power dBm (NVS saved)
esp-cmd <host> OTA <url> # Trigger OTA update (prefer esp-ota)
esp-cmd <host> REBOOT # Restart device
Fleet Management (esp-fleet)
Send commands to all sensors in parallel:
esp-fleet status # Query all devices
esp-fleet identify # Blink all LEDs
esp-fleet rate 50 # Set rate on all
esp-fleet reboot # Reboot all
esp-fleet ota # OTA update all (sequential)
esp-fleet ota /path/to/fw # OTA with custom firmware
OTA Updates
Overview
Firmware updates are delivered over WiFi using ESP-IDF's esp_https_ota with a dual OTA partition layout. The device downloads a new binary from an HTTP server on the Pi, writes it to the inactive OTA slot, and reboots.
Partition Layout
| Partition | Offset | Size | Purpose |
|---|---|---|---|
| nvs | 0x9000 | 16 KB | NVS config storage |
| otadata | 0xd000 | 8 KB | OTA boot selection |
| phy_init | 0xf000 | 4 KB | PHY calibration |
| ota_0 | 0x10000 | 1920 KB | App slot A |
| ota_1 | 0x1F0000 | 1920 KB | App slot B |
Using esp-ota
The esp-ota tool handles the full OTA workflow:
esp-ota amber-maple.local # Default build path
esp-ota amber-maple.local -f custom.bin # Custom firmware
esp-ota amber-maple.local --no-wait # Don't verify reboot
What it does:
- Verifies the device is alive (
STATUS) - Starts a temporary HTTP server on port 8070
- Sends
OTA http://<pi-ip>:8070/<firmware>.binto the device - Waits for the device to download, flash, reboot
- Verifies the device responds with the new version
Rollback
The bootloader supports automatic rollback on failed updates:
- New firmware is written to the inactive OTA slot
- Device reboots into the new firmware in
PENDING_VERIFYstate - If the firmware boots successfully (WiFi connects, mDNS starts, command listener ready), it marks itself as valid
- If the firmware crashes or hangs, the 30s watchdog triggers a reboot and the bootloader rolls back to the previous slot
First-Time Setup
The initial flash must be done via USB because switching from a single-app to a dual-OTA partition table requires erasing the entire flash. After the first USB flash, all subsequent updates can be done via OTA.
idf.py -p /dev/ttyUSB0 flash # First time: USB required
esp-ota amber-maple.local # All subsequent updates: OTA
Receiving CSI Data on the Pi
Listen on UDP port 5500:
# Quick test
nc -lu 5500
# Save to file
nc -lu 5500 > csi_capture.csv
# With socat (more reliable)
socat UDP-RECV:5500 STDOUT
Python Visualization Tools
get-started/tools/
pip install -r get-started/tools/requirements.txt
python get-started/tools/csi_data_read_parse.py
Requires serial connection (not UDP). Use for development with csi_recv firmware.
esp-radar/console_test/tools/
pip install -r esp-radar/console_test/tools/requirements.txt
python esp-radar/console_test/tools/esp_csi_tool.py
python esp-radar/console_test/tools/esp_csi_tool_gui.py
CLI and GUI tools for CSI analysis with presence detection algorithms.