fix: add new helper (_recv_5a_batch()) that helps with assembling chunks over TCP

This commit is contained in:
2026-04-27 18:39:34 -04:00
parent a7585cb5e0
commit f5c81f2cab
4 changed files with 148 additions and 97 deletions
+18 -48
View File
@@ -685,64 +685,34 @@ def write_blastware_file(
body_frames = a5_frames
term_frame = None
# ── Identify first metadata frame and skip "extra chunks" ───────────────
# When extra_chunks_after_metadata=1 in read_bulk_waveform_stream(), the
# frame list is: [probe, data..., metadata, extra_chunk, terminator].
# The extra_chunk is downloaded to prime the TCP terminator response — its
# ADC data is NOT part of the Blastware file body. Skip it.
#
# Rule: any frame at index strictly between first_metadata_fi and last_fi
# (the final frame) is an extra chunk and must be excluded.
#
# If no metadata frame exists (e.g. full_waveform download), first_metadata_fi
# is None and no frames are skipped — all frames contribute normally.
first_metadata_fi: Optional[int] = None
for _fi_scan, _frame_scan in enumerate(body_frames):
if _fi_scan > 0 and any(m in bytes(_frame_scan.data) for m in _METADATA_FRAME_MARKERS):
first_metadata_fi = _fi_scan
break
last_fi = len(body_frames) - 1
log.warning(
"write_blastware_file: %d body_frames first_metadata_fi=%s last_fi=%d",
"write_blastware_file: %d body_frames term_idx=%s",
len(body_frames),
str(first_metadata_fi) if first_metadata_fi is not None else "None",
last_fi,
str(term_idx) if term_idx is not None else "None",
)
all_bytes = bytearray()
for fi, frame in enumerate(body_frames):
# Skip "extra chunk" frames: frames after the first metadata frame but
# before the last frame (terminator). These prime the TCP terminator but
# their ADC data must NOT appear in the Blastware file body.
if (first_metadata_fi is not None
and fi > first_metadata_fi
and fi < last_fi):
log.warning(
"write_blastware_file: fi=%d SKIP (extra chunk after metadata fi=%d last_fi=%d)",
fi, first_metadata_fi, last_fi,
)
continue
# All body frames contribute to the waveform body — no frames are skipped.
#
# Over TCP via cellular modem, _recv_5a_batch() correctly collects all
# A5 frames per chunk request (the device's ~1100-byte RS-232 response
# is forwarded as ~2 TCP segments of ~550 bytes each, each parsed as a
# separate S3 frame). ALL of these frames contain ADC body data and
# must be included in the file — confirmed from 4-27-26 TCP capture
# analysis: contributions from all 14 frames → 6821 bytes → file 6864 bytes.
#
# Skip amounts (offsets into frame.data):
# fi=0 (probe): probe_skip — skips the type_tag header + STRT record
# fi=1: 13 — 7-byte frame.data prefix + 6 inner header bytes
# fi>=2: 12 — 7-byte frame.data prefix + 5 inner header bytes
if fi == 0:
# Probe frame: always process regardless of classification.
# It holds the STRT record; probe_skip positions us past it.
skip = probe_skip
elif fi == 1:
skip = 13
else:
# ALL subsequent frames are included unconditionally — no filtering on
# frame type. In the A5 stream, frame 0 is always the probe response;
# frames 1+ are always data (waveform chunks, compliance config, or
# compliance continuation). Classification is for logging only.
#
# DO NOT gate on classify_frame() here:
# - "probe_or_strt" at fi>0 is always a false positive — ADC binary
# data can coincidentally contain b"STRT\xff\xfe" (confirmed from
# live capture: frames 1 and 5 matched on event key=01110000).
# - "metadata" frames must be included (compliance config body).
# - The compliance block spans 2 frames; skipping either produces a
# truncated file that Blastware rejects.
skip = 13 if fi == 1 else 12
skip = 12
contribution = _frame_body_bytes(frame, skip)
log.warning("write_blastware_file: fi=%d skip=%d raw_data=%d contribution=%d",