Skip to content

Commit 5789351

Browse files
ruvnetmerajmehrabi
andauthored
fix(esp32): add connected-STA self-ping CSI traffic source (ruvnet#954) (ruvnet#985)
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 ruvnet#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 (ruvnet#521, ruvnet#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 (ruvnet#396/ruvnet#893) is left fully intact so multistatic/multi-node sensing still hears other stations' frames. Reconciles PR ruvnet#955 (which removed promiscuous entirely and conflicted with the already-shipped ruvnet#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 ruvnet#521/ruvnet#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>
1 parent b6420ac commit 5789351

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

firmware/esp32-csi-node/main/csi_collector.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
#include "esp_wifi.h"
2424
#include "esp_timer.h"
2525
#include "sdkconfig.h"
26+
#include "esp_netif.h" /* #954: STA gateway lookup for self-ping CSI source */
27+
#include "ping/ping_sock.h" /* #954: esp_ping gateway traffic generator */
28+
#include "lwip/ip_addr.h" /* #954: ip_addr_t target for esp_ping */
2629

2730
/* ADR-060: Access the global NVS config for MAC filter and channel override. */
2831
extern nvs_config_t g_nvs_config;
@@ -365,6 +368,67 @@ static void wifi_promiscuous_cb(void *buf, wifi_promiscuous_pkt_type_t type)
365368
(void)type;
366369
}
367370

371+
/* ---- RuView#521/#954: connected-STA CSI traffic source (additive) ----
372+
*
373+
* The ESP32 CSI engine only produces CSI for received OFDM frames (L-LTF/HT-LTF).
374+
* On a quiet network — or on a display-enabled build where the #893 MGMT->MGMT+DATA
375+
* promiscuous upgrade is skipped (has_display=true) — the only CSI-eligible frames
376+
* are sparse beacons (often non-OFDM DSSS), so wifi_csi_callback can starve to
377+
* yield=0pps -> DEGRADED -> motion/presence=0 (#521, #954).
378+
*
379+
* This guarantees a ~50 Hz OFDM unicast floor by pinging the STA's own gateway:
380+
* the router's ICMP echo replies are OFDM frames destined to this station, which
381+
* drive the CSI engine regardless of promiscuous filter state or ambient traffic.
382+
* It is ADDITIVE — promiscuous capture (#396/#893) is left fully intact so
383+
* multistatic/multi-node sensing still hears other stations' frames. Mirrors
384+
* Espressif's esp-csi csi_recv_router reference.
385+
*/
386+
static esp_ping_handle_t s_self_ping = NULL;
387+
static void csi_ping_cb_noop(esp_ping_handle_t hdl, void *args) { (void)hdl; (void)args; }
388+
389+
static void csi_start_self_ping(void)
390+
{
391+
if (s_self_ping != NULL) {
392+
return; /* already running */
393+
}
394+
395+
esp_netif_t *sta = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
396+
esp_netif_ip_info_t ip;
397+
if (sta == NULL || esp_netif_get_ip_info(sta, &ip) != ESP_OK || ip.gw.addr == 0) {
398+
ESP_LOGW(TAG, "self-ping: no gateway IP yet; CSI relies on ambient frames (#954)");
399+
return;
400+
}
401+
402+
char gw_str[16];
403+
esp_ip4addr_ntoa(&ip.gw, gw_str, sizeof(gw_str));
404+
405+
ip_addr_t target;
406+
memset(&target, 0, sizeof(target));
407+
ipaddr_aton(gw_str, &target);
408+
409+
esp_ping_config_t cfg = ESP_PING_DEFAULT_CONFIG();
410+
cfg.target_addr = target;
411+
cfg.count = ESP_PING_COUNT_INFINITE;
412+
cfg.interval_ms = 20; /* 50 Hz -> ~50 received OFDM replies/sec */
413+
cfg.data_size = 1;
414+
cfg.task_stack_size = 4096;
415+
416+
esp_ping_callbacks_t cbs = {
417+
.cb_args = NULL,
418+
.on_ping_success = csi_ping_cb_noop,
419+
.on_ping_timeout = csi_ping_cb_noop,
420+
.on_ping_end = csi_ping_cb_noop,
421+
};
422+
423+
if (esp_ping_new_session(&cfg, &cbs, &s_self_ping) == ESP_OK && s_self_ping != NULL) {
424+
esp_ping_start(s_self_ping);
425+
ESP_LOGI(TAG, "self-ping started -> %s @50Hz (CSI OFDM source, fix #521/#954)", gw_str);
426+
} else {
427+
ESP_LOGW(TAG, "self-ping: esp_ping_new_session failed");
428+
s_self_ping = NULL;
429+
}
430+
}
431+
368432
void csi_collector_set_node_id(uint8_t node_id)
369433
{
370434
s_node_id = node_id;
@@ -526,6 +590,11 @@ void csi_collector_init(void)
526590

527591
ESP_LOGI(TAG, "CSI collection initialized (node_id=%u, channel=%u)",
528592
(unsigned)s_node_id, (unsigned)csi_channel);
593+
594+
/* RuView#521/#954: start the connected-STA traffic source so the CSI engine
595+
* receives a guaranteed OFDM unicast floor even when promiscuous capture is
596+
* starved (display builds / quiet networks). Additive to #396/#893. */
597+
csi_start_self_ping();
529598
}
530599

531600
/* Accessor for other modules that need the authoritative runtime node_id. */

0 commit comments

Comments
 (0)