# 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 1. ESP32 connects to WiFi router as a station 2. Pings the gateway at 100 Hz (10ms interval) 3. Each ping response triggers a CSI callback 4. 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,,,,,,,,, ,,,,,, ,,,,,, ,,,"[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: ```bash esp-cmd STATUS # Uptime, heap, RSSI, tx_power, rate, version esp-cmd IDENTIFY # LED solid 5s esp-cmd RATE <10-100> # Set ping rate (NVS saved) esp-cmd POWER <2-20> # Set TX power dBm (NVS saved) esp-cmd OTA # Trigger OTA update (prefer esp-ota) esp-cmd REBOOT # Restart device ``` ### Fleet Management (esp-fleet) Send commands to all sensors in parallel: ```bash 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: ```bash 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:** 1. Verifies the device is alive (`STATUS`) 2. Starts a temporary HTTP server on port 8070 3. Sends `OTA http://:8070/.bin` to the device 4. Waits for the device to download, flash, reboot 5. Verifies the device responds with the new version ### Rollback The bootloader supports automatic rollback on failed updates: 1. New firmware is written to the inactive OTA slot 2. Device reboots into the new firmware in `PENDING_VERIFY` state 3. If the firmware boots successfully (WiFi connects, mDNS starts, command listener ready), it marks itself as valid 4. 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. ```bash 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: ```bash # 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/ ```bash 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/ ```bash 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.