fix: multistatic canonicalization, csi_fps burst inflation, control-packet starvation (#1170, #1180, #1183) (#1193)

#1170 — live multistatic bridge fed raw, un-canonicalized per-node CSI
(64/128/192 bins) to MultistaticFuser, tripping DimensionMismatch every
cycle and silently disabling fusion on mixed HT20/HT40 meshes. Add
HardwareNormalizer::resample_to_canonical (resample-only, no z-score) and
canonicalize every node frame onto the 56-tone grid before fusion.

#1180 — update_csi_fps_ema only rejected dt<=0 or >=1s, so sub-ms UDP-burst
arrivals (36us -> ~27kHz) inflated csi_fps_ema 40-840x. Add a 5ms plausibility
floor and stop re-anchoring observe_csi_frame_arrival on burst deltas.

#1183 — global ENOMEM backoff (CSI flood) starved <=48B/<=1Hz control packets.
Add stream_sender_send_priority() bypassing the backoff gate without touching
the streak; route feature_state/HEALTH/sync through it. Fix the misleading
"HEALTH sent" log that printed even on rv_mesh_send failure.

Verified: signal 501, sensing-server 677 tests (0 failed); firmware builds clean.


Claude-Session: https://claude.ai/code/session_01AgpTcBLRJ32hUsKWxDXf36
This commit is contained in:
rUv
2026-06-27 13:04:44 -04:00
committed by GitHub
parent 7831f29436
commit fca5e6f0a0
10 changed files with 267 additions and 22 deletions
@@ -319,7 +319,9 @@ static void emit_feature_state(void)
(uint64_t)esp_timer_get_time(),
profile);
int sent = stream_sender_send((const uint8_t *)&pkt, sizeof(pkt));
/* feature_state is ~1 Hz and small — priority path so the CSI ENOMEM
* backoff can't starve it (#1183). */
int sent = stream_sender_send_priority((const uint8_t *)&pkt, sizeof(pkt));
if (sent < 0) {
ESP_LOGW(TAG, "feature_state emit failed");
}
@@ -333,11 +335,14 @@ static void slow_loop_cb(TimerHandle_t t)
* detect sync-error drift. */
uint8_t nid[8];
node_id_bytes(nid);
rv_mesh_send_health(s_role, s_mesh_epoch, nid);
/* #1183: report the actual send result — the old log printed "HEALTH sent"
* unconditionally even when rv_mesh_send returned ESP_FAIL. */
esp_err_t health_rc = rv_mesh_send_health(s_role, s_mesh_epoch, nid);
ESP_LOGI(TAG, "slow tick (state=%u, feature_state_seq=%u, role=%u, epoch=%u) HEALTH sent",
ESP_LOGI(TAG, "slow tick (state=%u, feature_state_seq=%u, role=%u, epoch=%u) HEALTH %s",
(unsigned)s_state, (unsigned)s_feature_state_seq,
(unsigned)s_role, (unsigned)s_mesh_epoch);
(unsigned)s_role, (unsigned)s_mesh_epoch,
health_rc == ESP_OK ? "sent" : "FAILED");
}
/* ---- Public API ---- */
+3 -1
View File
@@ -341,7 +341,9 @@ static void wifi_csi_callback(void *ctx, wifi_csi_info_t *info)
memcpy(&sync[24], &s_sequence, 4); /* high-water seq for pairing */
uint32_t zero32 = 0;
memcpy(&sync[28], &zero32, 4); /* reserved (room for leader_id low32) */
int sr = stream_sender_send(sync, sizeof(sync));
/* Sync packets are 32 B at ~0.5 Hz — priority path so the CSI
* ENOMEM backoff can't starve cross-node time alignment (#1183). */
int sr = stream_sender_send_priority(sync, sizeof(sync));
static uint32_t s_sync_count = 0;
s_sync_count++;
if (s_sync_count <= 3 || (s_sync_count % 60) == 0) {
+3 -1
View File
@@ -188,7 +188,9 @@ size_t rv_mesh_encode_calibration_start(uint8_t sender_role,
esp_err_t rv_mesh_send(const uint8_t *frame, size_t len)
{
if (frame == NULL || len == 0) return ESP_ERR_INVALID_ARG;
int sent = stream_sender_send(frame, len);
/* Mesh control packets (HEALTH, anomaly) are low-rate and tiny — send them
* on the priority path so the CSI ENOMEM backoff can't starve them (#1183). */
int sent = stream_sender_send_priority(frame, len);
if (sent < 0) {
ESP_LOGW(TAG, "rv_mesh_send: stream_sender failed (len=%u)",
(unsigned)len);
@@ -121,6 +121,34 @@ int stream_sender_send(const uint8_t *data, size_t len)
return sent;
}
int stream_sender_send_priority(const uint8_t *data, size_t len)
{
if (s_sock < 0) {
return -1;
}
/* Priority path (#1183): low-rate control packets (feature_state, HEALTH,
* mesh sync) bypass the global ENOMEM backoff gate so the high-rate CSI
* stream cannot starve them. These are ≤48 B at ≤1 Hz — negligible pbuf
* pressure, so they won't re-trigger the crash cascade that the backoff
* (driven by the 50 Hz CSI flood) exists to prevent.
*
* Crucially, an ENOMEM here is reported quietly and does NOT extend the
* global streak/backoff: a tiny control packet failing is a symptom of
* the bulk-stream pressure, not a cause, so it must not feed the cooldown
* that suppresses the next CSI frame. Likewise a success does not reset
* the streak — the bulk path owns that signal. */
int sent = sendto(s_sock, data, len, 0,
(struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr));
if (sent < 0) {
if (errno != ENOMEM) {
ESP_LOGW(TAG, "priority sendto failed: errno %d", errno);
}
return -1;
}
return sent;
}
void stream_sender_deinit(void)
{
if (s_sock >= 0) {
@@ -36,6 +36,20 @@ int stream_sender_init_with(const char *ip, uint16_t port);
*/
int stream_sender_send(const uint8_t *data, size_t len);
/**
* Send a low-rate control packet, bypassing the ENOMEM backoff gate (#1183).
*
* Intended for ≤48 B, ≤1 Hz control traffic (feature_state, HEALTH, mesh
* sync) that must not be starved by the global backoff the high-rate CSI
* stream triggers. An ENOMEM on this path is reported quietly and does NOT
* extend or reset the global backoff streak.
*
* @param data Frame data buffer.
* @param len Length of data to send.
* @return Number of bytes sent, or -1 on error.
*/
int stream_sender_send_priority(const uint8_t *data, size_t len);
/**
* Close the UDP sender socket.
*/