← index #6989PR #18460
Likely Duplicate · high · value 3.853
QUERY · ISSUE

ESP32: add support for channel state information (CSI)

openby roger-opened 2021-03-04updated 2025-11-23
enhancementport-esp32

Channel state information basically gives the complex frequency response of the current channel (at the OFDM subcarriers) and was added to a recent ESP-IDF. It's very useful to researchers for doing things like localization, detecting motion, human activities, etc.

It's fairly easy to use in ESP-IDF: set a callback, configure and then enable. After every WiFi packet, the callback is passed some metadata (MAC address, RSSI, etc.) along with 64 complex numbers.

I imagine an API something like this:

WLAN.csi(callback=None, *, lltf_en=True, htltf_en=True, stbc_htltf2_en=True, ltf_merge_en=True, channel_filter_en=True, manu_scale=False, shift=0)

which would configure and enable CSI, and call callback(metadata, csi_list) where metadata = namedtuple('CSI data', 'mac_addr, rssi, rate, mcs, timestamp, ...') and csi_list is a list of complex numbers (or real, imag pairs).

The case of callback=None could disable CSI (or there could be explicit start() / stop() methods).

CANDIDATE · PULL REQUEST

esp32: Add Wi-Fi CSI (Channel State Information) module

openby francescopaceopened 2025-11-23updated 2026-03-08
port-esp32

esp32: Add Wi-Fi CSI (Channel State Information) module

Summary

This PR adds Wi-Fi CSI (Channel State Information) support to the ESP32 port, providing low-level access to Channel State Information data. This enables applications like motion detection, indoor localization, and gesture recognition.

CSI provides detailed information about the Wi-Fi channel state by analyzing physical layer signals. This is exposed as methods on the network.WLAN object: csi_enable(), csi_disable(), csi_read(), csi_available(), and csi_dropped().

The implementation includes:

  • Lock-free circular buffer for efficient frame management in ISR context
  • Support for all ESP32 variants (ESP32, S2, S3, C3, C5, C6)
  • ESP32-C6 Wi-Fi 6 (802.11ax) support with the new ESP-IDF 5.x CSI API
  • Complete Python API: csi_enable(), csi_disable(), csi_read(), csi_available(), csi_dropped()

Example Usage

import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect("SSID", "password")

# Note: CSI capture requires an active Wi-Fi connection

# Configure and enable CSI capture (optional - default buffer is 16 frames)
# buffer_size: Number of CSI frames to store in circular buffer
# Each frame is ~552 bytes (metadata + up to 512 bytes of CSI data)
# Larger buffer = less frame drops, but more RAM usage
wlan.csi_enable(buffer_size=64)  # Store up to 64 frames (~35KB RAM)

# Read CSI frames
frame = wlan.csi_read()
if frame:
    print(f"RSSI: {frame['rssi']} dBm")
    print(f"Channel: {frame['channel']}")
    print(f"CSI samples: {len(frame['data'])}")  # CSI raw data
    print(f"MAC: {frame['mac'].hex()}")

# Monitor buffer statistics
print(f"Available frames: {wlan.csi_available()}")  # Frames ready to read
print(f"Dropped frames: {wlan.csi_dropped()}")     # Frames lost due to buffer overflow

# Disable when done
wlan.csi_disable()

Understanding the Circular Buffer

The CSI module uses a circular buffer to handle the asynchronous nature of Wi-Fi packet reception:

  • Producer: Wi-Fi hardware captures CSI frames and stores them in the buffer (ISR context)
  • Consumer: Python code reads frames from the buffer at its own pace
  • buffer_size: Maximum number of complete CSI frames that can be queued (default: 16)

Buffer sizing guidelines:

  • Small buffer (16-32): Lower RAM usage (~9-18KB), risk of frame drops if Python is slow
  • Default buffer (16): Balanced approach (~9KB RAM), handles moderate traffic bursts
  • Large buffer (64-128): Minimal frame drops (~35-70KB RAM), suitable for high-traffic scenarios

Memory calculation:

RAM usage ≈ buffer_size × 552 bytes per frame
Default: 16 frames × 552 bytes = ~9KB
Example: 64 frames × 552 bytes = ~35KB

Note: Each frame's data field can hold up to 512 bytes of CSI samples. The actual CSI data length varies based on Wi-Fi mode and configuration (typically 52-128 bytes for HT20).

Testing

Tested on:

  • ESP32-S3: All CSI functionality working correctly
  • ESP32-C6: All CSI functionality working correctly with Wi-Fi 6 support

Test results:

  • Successfully captured CSI frames with RSSI=-48 dBm, Channel=4
  • Circular buffer working correctly: 63 frames captured, 28 dropped during overflow test
  • All API methods verified: csi_enable(), csi_disable(), csi_read(), csi_available(), csi_dropped()
  • Wi-Fi 6 (802.11ax) CSI capture confirmed on ESP32-C6 using the new ESP-IDF 5.x API with acquire_csi_* configuration fields
  • Buffer overflow handling: csi_dropped() counter correctly tracks lost frames when buffer is full

Could not test on ESP32, ESP32-S2, ESP32-C3, ESP32-C5 due to hardware availability, but code includes conditional compilation for all variants based on ESP-IDF documentation and API compatibility.

Firmware compiles successfully for all ESP32_GENERIC boards (ESP32, S2, S3, C3, C5, C6).

API Design

The CSI API is implemented as direct methods on the WLAN object (not a singleton sub-object):

  • wlan.csi_enable(buffer_size=16, ...) - Configure and enable CSI capture
  • wlan.csi_disable() - Disable CSI capture and clean up resources
  • wlan.csi_read() - Read a CSI frame (returns dict or None)
  • wlan.csi_available() - Get number of frames ready to read
  • wlan.csi_dropped() - Get count of dropped frames

This design choice provides:

  • Consistency: CSI is Wi-Fi related, belongs with WLAN
  • Context: Requires active Wi-Fi connection
  • Discoverability: Users expect Wi-Fi features on WLAN object
  • Simplicity: No need for separate singleton object

Trade-offs and Alternatives

Code Size Impact:

  • Adds approximately 15KB to firmware when CSI is enabled
  • Total application size: ~1.87MB (fits in 2MB partition with 8% free space)
  • Conditional compilation via MICROPY_PY_NETWORK_WLAN_CSI allows disabling if needed

Trade-off Justification:

The 15KB overhead is justified because:

  1. CSI enables entirely new application categories (sensing, localization, gesture recognition)
  2. The feature is optional and can be disabled via build configuration
  3. The implementation is efficient with lock-free ISR-safe design
  4. Similar features (Bluetooth, network protocols) have similar code size impact

Memory Usage:

  • Default: 16 frames × 552 bytes = ~9KB RAM
  • Configurable: 1-1024 frames (user controls trade-off)
  • Can be reduced to minimum (1 frame = ~552 bytes) if needed

Files Changed

  • ports/esp32/modwifi_csi.c - Core implementation (705 lines)
  • ports/esp32/modwifi_csi.h - Headers and definitions (120 lines)
  • ports/esp32/network_wlan.c - CSI methods integrated into WLAN
  • ports/esp32/esp32_common.cmake - Build system integration
  • ports/esp32/mpconfigport.h - Feature flag (MICROPY_PY_NETWORK_WLAN_CSI)
  • ports/esp32/boards/sdkconfig.base - ESP-IDF config (CONFIG_ESP_WIFI_CSI_ENABLED=y)
  • docs/library/network.WLAN.rst - Complete API documentation (284 lines)
  • examples/csi/csi_basic.py - Basic CSI capture example
  • examples/csi/csi_turbolence_monitor.py - Advanced turbulence calculation example
  • examples/csi/README.md - Comprehensive guide with troubleshooting

Total: 10 files changed (+1632 lines, -1 line)

Documentation

Comprehensive documentation has been added:

  • API Reference: Complete method documentation in docs/library/network.WLAN.rst
  • Examples: Two working examples with different use cases
  • Guide: Detailed README with troubleshooting, memory usage, and platform-specific notes
  • Platform Support: Clear documentation of differences between ESP32 variants

Platform-Specific Notes

ESP32-C6 / ESP32-C5 (Wi-Fi 6)

  • Supports 802.11ax (Wi-Fi 6) CSI capture
  • Uses new ESP-IDF 5.x CSI API
  • Enhanced CSI capabilities with HE-LTF support
  • Only buffer_size parameter is configurable via Python API
  • Other parameters (lltf_en, htltf_en, etc.) are accepted but ignored (handled internally)

ESP32 / ESP32-S2 / ESP32-S3 / ESP32-C3

  • Supports 802.11b/g/n CSI capture
  • Uses legacy CSI API
  • All Python configuration parameters are supported
  • Full rx_ctrl structure available in frames

Known Limitations

  1. Wi-Fi Traffic Required: CSI data is extracted from received Wi-Fi packets. Without active traffic directed to the ESP32, no CSI frames will be captured. This is well documented with examples showing how to generate test traffic.

  2. Buffer Overflow: If Python code reads frames slower than they arrive, frames will be dropped. The csi_dropped() counter allows monitoring this, and buffer size is configurable.

  3. Platform Differences: ESP32-C5 and ESP32-C6 use a different ESP-IDF API internally, so some Python configuration parameters are not applicable. This is clearly documented.

  4. Memory Usage: Larger buffers use significant RAM. The default (16 frames) is conservative, but users can adjust based on their needs.

Future Enhancements (Out of Scope)

  • CSI data filtering/processing helpers
  • Higher-level motion detection algorithms
  • Multi-antenna support (currently single antenna)
  • HT40 support (currently HT20 only)

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied