Skip to content

fix(esp32): make CSI callback actually fire (connected-STA CSI + self-ping)#955

Closed
merajmehrabi wants to merge 1 commit into
ruvnet:mainfrom
merajmehrabi:fix/esp32-csi-callback-never-fires
Closed

fix(esp32): make CSI callback actually fire (connected-STA CSI + self-ping)#955
merajmehrabi wants to merge 1 commit into
ruvnet:mainfrom
merajmehrabi:fix/esp32-csi-callback-never-fires

Conversation

@merajmehrabi

Copy link
Copy Markdown
Contributor

Problem

On ESP32-S3, wifi_csi_callback never firespkt_yield=0pps, the ADR-081 adaptive controller stays in ADAPT_STATE_DEGRADED, and every rv_feature_state_t packet reads motion=0, presence=0, DEGRADED. The node streams but senses nothing. Reproduced on v0.6.5 and v0.7.0, on 3 boards, on both HT40 and HT20 APs. (Same symptom as #954 and the closed #521.)

Root cause

The ESP32 CSI engine only produces CSI for received OFDM frames (it measures the L-LTF/HT-LTF training fields). csi_collector_init() captured CSI only through a MGMT-only promiscuous filter (the #396 Core-0 DATA-sniff crash workaround), so the only CSI-eligible frames were beacons — which APs almost always transmit at non-OFDM DSSS basic rates (1–2 Mbps) that carry no training field. Net: zero CSI-eligible frames → zero callbacks.

The OFDM traffic source the comments describe ("probe request injection at 10 Hz") does not exist — csi_inject_ndp_frame() has no callers and is a TX-only null-data stub anyway (TX does not generate CSI).

The prior #521 fix — esp_wifi_set_ps(WIFI_PS_NONE) — was a wrong diagnosis (modem sleep) committed without a hardware check; it is present in current firmware and the symptom persists.

Fix

Mirror Espressif's reference (esp-csi/examples/get-started/csi_recv_router): use plain connected-STA CSI and generate received OFDM frames by pinging the gateway at 50 Hz (esp_ping). Each ICMP echo reply is a unicast OFDM data frame addressed to the station → the CSI engine fires.

Disabling promiscuous mode also removes the #396 Core-0 crash surface entirely (we no longer sniff DATA frames; we only receive our own low-rate replies).

Verification (ESP32-S3 N16R8, ESP-IDF v5.4)

csi_collector: Promiscuous DISABLED — connected-STA CSI + self-ping (fix #521/#954)
csi_collector: self-ping started -> 192.168.0.1 @50Hz
csi_collector: CSI cb #1: len=256 rssi=-65 ch=3      <- callback now fires
adaptive_ctrl: state=6 yield=32pps motion=1.00 presence=10.48   <- SENSE_ACTIVE

Host-side decode of 0xC5110006: DEGRADED 0/70, PRES_VALID|RESP_VALID|HB_VALID, breathing ≈ 9–13 bpm and heart rate ≈ 40–46 bpm computed.

Notes / follow-ups

  • motion saturates at 1.00 in early testing — likely the constant self-ping baseline; the motion threshold/normalisation may want a follow-up tune.
  • Ping rate (50 Hz) and target (gateway) could be made NVS-configurable.
  • 1 file changed (firmware/esp32-csi-node/main/csi_collector.c); builds clean for esp32s3, 47% app partition free.

Closes #954. Supersedes the unverified #521 fix.

…-ping)

The ESP32 CSI engine only produces CSI for received OFDM frames (L-LTF/
HT-LTF). csi_collector_init() captured CSI only via a MGMT-only promiscuous
filter (the ruvnet#396 Core-0 DATA-sniff crash workaround), so the only
CSI-eligible frames were beacons -- which APs transmit at non-OFDM DSSS
rates carrying no training field. Result: wifi_csi_callback never fired,
pkt_yield=0pps, adaptive controller stuck in DEGRADED, motion/presence=0
(ruvnet#521, ruvnet#954). The probe/NDP "injection" in the comments is an uncalled
TX-only stub, and the prior ruvnet#521 fix (esp_wifi_set_ps(WIFI_PS_NONE)) was
committed without hardware verification and does not resolve it.

Fix: mirror Espressif's esp-csi csi_recv_router reference -- disable
promiscuous mode and ping the gateway at 50 Hz so received OFDM replies
drive the CSI engine. Running without promiscuous also sidesteps the ruvnet#396
Core-0 DATA-sniff crash entirely.

Verified on ESP32-S3 (N16R8), ESP-IDF v5.4: CSI cb fires, yield ~30 pps,
adaptive state -> SENSE_ACTIVE, DEGRADED cleared, breathing + heart rate
computed with PRES/RESP/HB_VALID flags set.

Closes ruvnet#954. Supersedes the unverified ruvnet#521 fix.

Co-Authored-By: claude-flow <ruv@ruv.net>
ruvnet added a commit that referenced this pull request Jun 9, 2026
The ESP32 CSI engine only produces CSI for received OFDM frames (L-LTF/
HT-LTF). On a quiet network — or on a display-enabled build where the
#893 MGMT->MGMT+DATA promiscuous upgrade is skipped (has_display=true) —
the only CSI-eligible frames are sparse beacons (often non-OFDM DSSS),
so wifi_csi_callback can starve to yield=0pps -> DEGRADED -> motion=0
(#521, #954).

Fix (additive): pin a ~50 Hz OFDM unicast floor by pinging the STA's own
DHCP gateway. The router's ICMP echo replies are OFDM frames destined to
this station and drive the CSI engine regardless of promiscuous filter
state or ambient traffic. Mirrors Espressif's esp-csi csi_recv_router
reference. Promiscuous capture (#396/#893) is left fully intact so
multistatic/multi-node sensing still hears other stations' frames.

Reconciles PR #955 (which removed promiscuous entirely and conflicted
with the already-shipped #893 DATA-capture path) into an additive change
on current main.

Verified on ESP32-S3 (N16R8, COM8), ESP-IDF v5.4:
  Promiscuous mode enabled (MGMT-only, RuView#396)
  self-ping started -> 192.168.1.1 @50Hz (CSI OFDM source, fix #521/#954)
  CSI cb #1: len=128 rssi=-40 ch=5
  adaptive_ctrl: state=6 yield=13-19pps motion=1.00 presence>0  (SENSE_ACTIVE)
DEGRADED cleared; CSI yield stable ~15 pps over 60 s.

Co-authored-by: Meraj <merajmehrabi@gmail.com>
@ruvnet

ruvnet commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Thanks for the root-cause and the fix, @merajmehrabi — your self-ping diagnosis was correct and verified on our ESP32-S3 (COM8).

We landed it as #985 (merged to main) as an additive variant rather than merging this PR directly. The reason: since you branched (2026-06-05) main shipped #893 (898c536ea), which fixes the same starvation a different way — it keeps promiscuous on and upgrades MGMT→MGMT+DATA on display-less boards via csi_collector_enable_data_capture() (called from main.c). Removing promiscuous here would have left that path calling esp_wifi_set_promiscuous_filter() on a disabled interface, and would have dropped the promiscuous capture multistatic sensing relies on.

#985 keeps #396/#893 intact and adds your gateway self-ping as a guaranteed ~50 Hz OFDM floor — which also fixes the display-build edge case from the #954 thread. You're credited as co-author on the squashed commit. Closing this as superseded by #985. 🙏

@ruvnet ruvnet closed this Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants