Skip to content

fix(scripts): emit the ADR-018 header layout in synth-csi-udp.py - #1953

Open
buraga-kyo wants to merge 1 commit into
ruvnet:mainfrom
buraga-kyo:contrib/synth-csi-udp-adr018-header
Open

buraga-kyo wants to merge 1 commit into
ruvnet:mainfrom
buraga-kyo:contrib/synth-csi-udp-adr018-header

Conversation

@buraga-kyo

Copy link
Copy Markdown

Summary

scripts/synth-csi-udp.py packs the 20-byte header as <I B B B B H I b b I — n_subcarriers u8 @6, freq u16 @8, sequence @10, rssi @14. That is the layout of the first calibrate.rs parser (#838). The parser was aligned with the firmware in #1005 (d0e27e65); the script was not. csi_collector.c and all three Rust readers (esp32_parser.rs, calibrate.rs, sensing-server csi.rs) read n_subcarriers u16 @6, freq u32 @8, sequence @12, rssi @16, ppdu_type @18, flags @19.

One struct string and its field comments change. Amplitude/phase synthesis, rate, and CLI flags are untouched.

Effect — MEASURED

Decoding one emitted frame with the firmware layout (struct.unpack_from('<IBBHIIbbBB', pkt)):

field before after
n_subcarriers 52 52
freq_mhz 461164 2412
sequence 2714304512, constant (0xA1C90000 = the rssi and noise bytes) increments
rssi_dbm 0 -55

wifi-densepose calibrate (the script's stated target) is unaffected by coincidence: the I/Q block starts at offset 20 in both layouts, the old reserved byte was zero so n_subcarriers decodes as 52 either way, and the baseline ignores sequence/rssi/freq. Baselines produced with both versions are byte-identical apart from captured_at.

The sensing-server calibration is broken by the constant sequence — it admits one observation per forward node sequence (calibration_sequence_order, Duplicate when delta == 0).

POST /api/v1/calibration/start?source_node_id=42, emitter at 20 Hz, status read after 12 s:

before after
frame_count 1 239
frames_per_second 0.08 19.9
last_sequence_by_node["42"] 2714304512 advancing
grid_binding.status stale bound

/api/v1/nodes also reported rssi_dbm: 0.0 for the node before the change.

Reproduce

cd v2 && cargo build --release -p wifi-densepose-sensing-server
./target/release/sensing-server --udp-port 5005 --ui-path ../ui &
python3 ../scripts/synth-csi-udp.py --port 5005 --duration-s 40 &
sleep 14; curl -s -X POST 'localhost:8080/api/v1/calibration/start?source_node_id=42'
sleep 12; curl -s localhost:8080/api/v1/calibration/status | jq '.frame_count, .frames_per_second, .last_sequence_by_node'

Header decode, no server needed:

python3 -c "import struct, importlib.util as u; s=u.spec_from_file_location('m','scripts/synth-csi-udp.py'); m=u.module_from_spec(s); s.loader.exec_module(m); p=m.build_packet(42,7,2412,-55,[40.0]*52,[0.0]*52); print(struct.unpack_from('<IBBHIIbbBB',p))"

Scope

One file, header struct only. Synthetic emitter; no CSI or person data. scripts/csi-data-policy-check.sh --staged passes. No other file in the tree references the script.

Out of scope, noted for a separate look: scripts/collect-training-data.py labels a 16-byte <IBBHbbBxI header "ADR-018 raw CSI frame"; I did not verify whether that reader is intentional legacy or a similar drift.

Test plan

  • python3 -m py_compile scripts/synth-csi-udp.py
  • Header decodes to n_sc=52, freq=2412, seq=7, rssi=-55, noise=-95 with the firmware layout
  • aggregator --bind 127.0.0.1:5005 shows seq:0..159 rssi=-55 (was seq:2714304512 rssi=0)
  • wifi-densepose calibrate --min-frames 100 succeeds with both versions; baselines differ only at captured_at
  • sensing-server calibration: frame_count 1 → 239 over 12 s at 20 Hz
  • bash scripts/csi-data-policy-check.sh --staged → OK

The emitter was written alongside the first calibrate.rs parser (ruvnet#838),
which read n_subcarriers as a u8 at offset 6 and freq_mhz as a u16 at
offset 8. That parser was aligned with the firmware layout in ruvnet#1005
(d0e27e6); the script kept the old struct string.

Decoded by the firmware layout (csi_collector.c), every emitted frame
carried freq_mhz=461164, sequence=0xA1C90000 (constant: the rssi and
noise bytes landing in the sequence slot) and rssi=0.

`wifi-densepose calibrate` kept working by coincidence: the I/Q block
starts at offset 20 in both layouts, n_subcarriers=52 decodes the same
because the old reserved byte was zero, and the baseline ignores
sequence/rssi/freq (baselines differ only in captured_at). The
sensing-server calibration does not: it admits one observation per
forward sequence, so with a constant sequence every frame after the
first is a Duplicate. Measured over 12 s at 20 Hz: frame_count=1 before,
239 after; frames_per_second 0.08 before, 19.9 after.

The header now matches firmware/esp32-csi-node/main/csi_collector.c and
the three Rust readers (esp32_parser.rs, calibrate.rs, sensing-server
csi.rs): n_subcarriers u16 @6, freq_mhz u32 @8, sequence u32 @12,
rssi i8 @16, noise_floor i8 @17, ppdu_type @18, flags @19. Amplitude
and phase synthesis are unchanged.

Feito por buraga-kyo <sprtj@protonmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant