You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ESP32-S3: edge DSP drops every STBC CSI frame (192 entries > EDGE_MAX_SUBCARRIERS), which starves the vitals/feature packets #2286
On ESP32-S3, process_frame() in edge_processing.c returns early when n_subcarriers > EDGE_MAX_SUBCARRIERS (128 on pre-HE parts). With the S3 CSI
config (lltf_en, htltf_en, stbc_htltf2_en all true), every frame that
the AP sends with STBC arrives as info->len == 384, i.e. 192 entries, and is
discarded before Step 13. When the AP uses STBC for most frames, the 1 Hz
vitals (0xC5110002) and feature (0xC5110003) packets almost stop, even
though raw CSI (0xC5110001) keeps streaming at full rate.
Still present on main at a1ca023 (edge_processing.c:1065).
AP: Cudy WiFi 6 router (MediaTek), 2.4 GHz channel 1, HT20. It uses STBC
for unicast data to the node.
Evidence (MEASURED)
Host-side capture of the node's UDP stream (30 s and 60 s windows, unicast UDP traffic
to the node at 50 Hz to drive CSI):
Build
Raw CSI
Frame mix
Vitals 0xC5110002
Features 0xC5110003
v0.8.8 (edge path unmodified)
42.4/s
1266/1272 iq_len 384 (STBC)
2 per 30 s
2 per 30 s
patched (below)
40.1/s
2271/2404 iq_len 384 (STBC)
59 per 60 s
59 per 60 s
The STBC flag comes from byte 19 bit 2 of the ADR-018 header
(CONFIG_CSI_FRAME_HE_TAGGING). The few vitals emitted before the patch are
consistent with the rare non-STBC (iq_len 256) frames getting through.
Proposed fix
Analyse the first 128 entries (LLTF + HT-LTF), which line up with plain HT20
frames, and ignore the extra STBC HT-LTF block:
uint16_t n_subcarriers = slot->iq_len / 2;
- if (n_subcarriers == 0 || n_subcarriers > EDGE_MAX_SUBCARRIERS) return;+ if (n_subcarriers == 0) return;+ /* STBC frames carry LLTF + HT-LTF + a second STBC HT-LTF (192 entries).+ * The first 128 line up with plain HT20 frames; analyse those. */+ if (n_subcarriers > EDGE_MAX_SUBCARRIERS) n_subcarriers = EDGE_MAX_SUBCARRIERS;
update_multi_person_vitals() already clamps the same way (line 1386), so
this makes the two paths consistent. Alternatives: set stbc_htltf2_en = false (loses the STBC block for raw CSI consumers too), or
raise EDGE_MAX_SUBCARRIERS for S3 (costs RAM and changes the DSP's
subcarrier set from frame to frame).
Hardware-verified on one ESP32-S3 node (boot log + host capture as above).
Fixed upstream. The change is in main and in the firmware release that carries it.
firmware/esp32-csi-node/main/edge_processing.c now bounds the subcarrier count instead of dropping the frame — the exact diff proposed in this issue:
uint16_tn_subcarriers=slot->iq_len / 2;
if (n_subcarriers==0) return;
/* S3 STBC frames append a second HT-LTF block (192 entries total). * Process the bounded LLTF + HT-LTF prefix instead of starving edge * output (#2286). Raw CSI streaming retains the complete payload. */if (n_subcarriers>EDGE_MAX_SUBCARRIERS) {
n_subcarriers=EDGE_MAX_SUBCARRIERS;
}
Landed in bf46d113 ("fix(firmware): analyse first 128 entries of STBC CSI frames instead of dropping them (#2286)"), merged through #2289 and #2309 on 2026-10-22, and shipped in firmware 0.8.15 (v0.8.15-esp32).
The regression test is in the tree and runs in CI: firmware/esp32-csi-node/test/test_edge_stbc.c executes the production DSP with alternating HT20 (256) and STBC (384) shapes and asserts that every complete frame advances s_frame_count, that the bounded prefix is analysed (s_subcarrier_var[bins - 1].count > 0) and that s_pkt_valid is set, ending with PASS: production DSP accepts HT20/STBC and bounds oversized input. It is wired into make host_tests (firmware/esp32-csi-node/test/Makefile:196), which the firmware CI job runs.
One boundary worth keeping visible: docs/validation/2026-10-20-esp32-csi-input-release-candidate.md states that the C6 check "does not qualify the S3-specific STBC correction". The hardware evidence for the S3 path is the reporter's own capture in this issue, not that validation run.
The account that ran this check has read-only access to this repository, so it cannot close the issue itself — a maintainer closing it is all that remains.
Activity
Merge pull request #2289 from ruvnet/integrate/release-20261017
fix(firmware): analyse first 128 entries of STBC CSI frames instead o…
three.js demos: Merge pull request ruvnet#2289 from ruvnet/integrate/…
danalec commentedon Oct 22, 2026
Fixed upstream. The change is in
mainand in the firmware release that carries it.firmware/esp32-csi-node/main/edge_processing.cnow bounds the subcarrier count instead of dropping the frame — the exact diff proposed in this issue:Landed in
bf46d113("fix(firmware): analyse first 128 entries of STBC CSI frames instead of dropping them (#2286)"), merged through #2289 and #2309 on 2026-10-22, and shipped in firmware0.8.15(v0.8.15-esp32).The regression test is in the tree and runs in CI:
firmware/esp32-csi-node/test/test_edge_stbc.cexecutes the production DSP with alternating HT20 (256) and STBC (384) shapes and asserts that every complete frame advancess_frame_count, that the bounded prefix is analysed (s_subcarrier_var[bins - 1].count > 0) and thats_pkt_validis set, ending withPASS: production DSP accepts HT20/STBC and bounds oversized input. It is wired intomake host_tests(firmware/esp32-csi-node/test/Makefile:196), which the firmware CI job runs.One boundary worth keeping visible:
docs/validation/2026-10-20-esp32-csi-input-release-candidate.mdstates that the C6 check "does not qualify the S3-specific STBC correction". The hardware evidence for the S3 path is the reporter's own capture in this issue, not that validation run.The account that ran this check has read-only access to this repository, so it cannot close the issue itself — a maintainer closing it is all that remains.
three.js demos: Merge pull request ruvnet#2289 from ruvnet/integrate/…
three.js demos: Merge pull request ruvnet#2289 from ruvnet/integrate/…
three.js demos: Merge pull request ruvnet#2289 from ruvnet/integrate/…
three.js demos: Merge pull request ruvnet#2289 from ruvnet/integrate/…