fix: update STRT record parsing to reflect confirmed offsets and derive total/pretrig_samples from compliance config
This commit is contained in:
+84
-93
@@ -1338,15 +1338,22 @@ def _decode_a5_waveform(
|
||||
|
||||
── Frame structure ──────────────────────────────────────────────────────────
|
||||
A5[0] (probe response):
|
||||
db[7:] = [11-byte header] [21-byte STRT record] [6-byte preamble] [waveform ...]
|
||||
db[7:] = [11-byte header] [21-byte STRT record] [waveform ...]
|
||||
STRT: b'STRT' at offset 11, total 21 bytes
|
||||
+8 uint16 BE: total_samples (expected full-record sample-sets)
|
||||
+16 uint16 BE: pretrig_samples (pre-trigger sample count)
|
||||
+18 uint8: rectime_seconds (record duration)
|
||||
Preamble: 6 bytes after the STRT record (confirmed from 4-2-26 blast capture):
|
||||
bytes 21-22: 0x00 0x00 (null padding)
|
||||
bytes 23-26: 0xFF × 4 (sync sentinel / alignment marker)
|
||||
Waveform starts at strt_pos + 27 within db[7:].
|
||||
+4..5 0xFF 0xFE (single-shot) or 0xFF 0xFD (continuous)
|
||||
+6..9 next_event_key4 (device pre-allocates next slot)
|
||||
+10..13 prev_event_key4
|
||||
+14..15 UNKNOWN (not total_samples)
|
||||
+16..17 UNKNOWN (not pretrig_samples)
|
||||
+18 mode byte: 0x46 single-shot, 0x0E continuous
|
||||
+19..20 0x00 0x00
|
||||
Waveform starts immediately at strt_pos + 21 (no preamble).
|
||||
NOTE: The original 4-2-26 blast capture appeared to show a 6-byte
|
||||
"preamble" (00 00 ff ff ff ff) after the STRT record, but this was
|
||||
actually the first ~0.75 sample-sets of quiet pre-trigger ADC data
|
||||
misread as padding. Confirmed 2026-04-14: bytes 21+ are raw waveform.
|
||||
total_samples and pretrig_samples are NOT stored in the STRT record;
|
||||
they are derived from the compliance config (the correct permanent source).
|
||||
|
||||
A5[1..N] (chunk responses):
|
||||
db[7:] = [8-byte per-frame header] [waveform bytes ...]
|
||||
@@ -1380,91 +1387,48 @@ def _decode_a5_waveform(
|
||||
|
||||
# STRT record layout (21 bytes, offsets relative to b'STRT'):
|
||||
# +0..3 magic b'STRT'
|
||||
# +4..5 0xFF 0xFE (flags)
|
||||
# +6..9 key4 (4-byte event key)
|
||||
# +10..13 prev_key4
|
||||
# +14..15 uint16 BE total_samples (full-record expected sample-set count)
|
||||
# +16..17 uint16 BE pretrig_samples
|
||||
# +18 uint8 rectime_seconds
|
||||
#
|
||||
# NOTE: strt[8:10] is the LOWER 2 bytes of key4, NOT total_samples.
|
||||
# Confirmed from raw_rx capture (4-9-26): strt[14:16] = total_samples. ✅
|
||||
# Extract 32 bytes so we can see past the first 21 — the true total_samples
|
||||
# and pretrig_samples offsets have not yet been confirmed and may be > +20.
|
||||
strt = w0[strt_pos : strt_pos + 32]
|
||||
# +4..5 0xFF 0xFE (single-shot) or 0xFF 0xFD (continuous)
|
||||
# +6..9 next_event_key4 (NOT current key — device pre-allocates next)
|
||||
# +10..13 prev_event_key4
|
||||
# +14..15 UNKNOWN — confirmed NOT total_samples (confirmed 2026-04-14)
|
||||
# +16..17 UNKNOWN — confirmed NOT pretrig_samples (confirmed 2026-04-14)
|
||||
# +18 mode byte: 0x46='F' single-shot, 0x0E continuous — NOT rectime
|
||||
# +19..20 0x00 0x00
|
||||
# Bytes 21+ are raw ADC waveform samples — no preamble.
|
||||
# total_samples / pretrig_samples are NOT stored in STRT at all.
|
||||
# The compliance config fallback is the correct permanent source.
|
||||
strt = w0[strt_pos : strt_pos + 21]
|
||||
if len(strt) < 21:
|
||||
log.warning("_decode_a5_waveform: STRT record truncated (%dB)", len(strt))
|
||||
return
|
||||
|
||||
log.info(
|
||||
"_decode_a5_waveform: STRT raw[0:32]: %s",
|
||||
strt[:32].hex(' '),
|
||||
"_decode_a5_waveform: STRT raw[0:21]: %s",
|
||||
strt.hex(' '),
|
||||
)
|
||||
|
||||
total_samples = struct.unpack_from(">H", strt, 14)[0]
|
||||
pretrig_samples = struct.unpack_from(">H", strt, 16)[0]
|
||||
|
||||
# ── STRT sanity checks ───────────────────────────────────────────────────
|
||||
# Check 1: pretrig must be strictly less than total_samples.
|
||||
# Check 2 (cross-check): if compliance_config is available, the STRT-derived
|
||||
# post-trigger record time must be within 2× of the configured record_time.
|
||||
# A 3-second record at 1024 sps = 3072 post-trig samples; if STRT gives
|
||||
# e.g. 21 s, something is wrong with the byte offsets.
|
||||
#
|
||||
# Both failures trigger the same fallback: derive pretrig from the compliance
|
||||
# monitoring standard (0.25 s) and total from compliance record_time.
|
||||
_strt_invalid = False
|
||||
# STRT bytes +14..17 are unknown fields — confirmed NOT total/pretrig_samples
|
||||
# (2026-04-14). total_samples and pretrig_samples are derived from the
|
||||
# compliance config, which is the correct permanent source.
|
||||
_strt_invalid = True
|
||||
_sample_rate_default = 1024
|
||||
|
||||
if pretrig_samples >= total_samples:
|
||||
log.warning(
|
||||
"_decode_a5_waveform: STRT check1 FAIL — pretrig_samples=%d >= "
|
||||
"total_samples=%d. Raw strt[0:21]: %s "
|
||||
"Will derive pretrig from compliance config.",
|
||||
pretrig_samples, total_samples, strt[0:21].hex(' '),
|
||||
)
|
||||
pretrig_samples = 0
|
||||
total_samples = 0
|
||||
_strt_invalid = True
|
||||
elif compliance_config is not None and compliance_config.record_time:
|
||||
cc_rt = compliance_config.record_time
|
||||
cc_sr = compliance_config.sample_rate or _sample_rate_default
|
||||
strt_post_trig = (total_samples - pretrig_samples) / cc_sr
|
||||
# Allow up to 2× tolerance to account for zero-padding beyond the window.
|
||||
# Anything more than that is a layout error, not zero-padding.
|
||||
if strt_post_trig > cc_rt * 2.0 or strt_post_trig < cc_rt * 0.5:
|
||||
log.warning(
|
||||
"_decode_a5_waveform: STRT check2 FAIL — STRT-derived rectime "
|
||||
"%.1fs is implausible vs compliance record_time=%.1fs (ratio=%.1f×). "
|
||||
"Raw strt[0:21]: %s Falling back to compliance-config values.",
|
||||
strt_post_trig, cc_rt, strt_post_trig / cc_rt,
|
||||
strt[0:21].hex(' '),
|
||||
)
|
||||
pretrig_samples = 0
|
||||
total_samples = 0
|
||||
_strt_invalid = True
|
||||
|
||||
# strt[18] is a record-mode/type byte, NOT rectime in seconds.
|
||||
# Confirmed from analysis of 4-9-26 ACH capture (15 distinct events):
|
||||
# flags=0xFFFE (single-shot) → strt[18]=0x46 ('F') for all events regardless of duration
|
||||
# flags=0xFFFD (continuous) → strt[18]=0x0E for all events regardless of duration
|
||||
# The actual post-trigger record time must be derived from total_samples and pretrig_samples.
|
||||
rectime_seconds = int(round(
|
||||
max(0, total_samples - pretrig_samples) / _sample_rate_default
|
||||
))
|
||||
|
||||
event.total_samples = total_samples
|
||||
event.pretrig_samples = pretrig_samples
|
||||
event.rectime_seconds = rectime_seconds
|
||||
total_samples = 0
|
||||
pretrig_samples = 0
|
||||
rectime_seconds = 0
|
||||
|
||||
log.info(
|
||||
"_decode_a5_waveform: STRT total_samples=%d pretrig=%d "
|
||||
"strt[18]=0x%02X (mode byte) computed_rectime=%ds "
|
||||
"raw strt[0:21]: %s",
|
||||
total_samples, pretrig_samples, strt[18], rectime_seconds,
|
||||
strt[0:21].hex(' '),
|
||||
"_decode_a5_waveform: STRT flags=0x%04X next_key=%s prev_key=%s "
|
||||
"mode=0x%02X → using compliance-config for total/pretrig",
|
||||
struct.unpack_from(">H", strt, 4)[0],
|
||||
strt[6:10].hex(),
|
||||
strt[10:14].hex(),
|
||||
strt[18],
|
||||
)
|
||||
|
||||
event.total_samples = 0 # will be overwritten by compliance-config fallback below
|
||||
event.pretrig_samples = 0
|
||||
event.rectime_seconds = 0
|
||||
|
||||
# ── Collect per-frame waveform bytes with global offset tracking ─────────
|
||||
# global_offset is the cumulative byte count across all frames, used to
|
||||
# compute the channel alignment at each frame boundary.
|
||||
@@ -1474,13 +1438,21 @@ def _decode_a5_waveform(
|
||||
for fi, db in enumerate(frames_data):
|
||||
w = db[7:]
|
||||
|
||||
# A5[0]: waveform begins after the 21-byte STRT record and 6-byte preamble.
|
||||
# Layout: STRT(21B) + null-pad(2B) + 0xFF sentinel(4B) = 27 bytes total.
|
||||
# A5[0]: waveform begins immediately after the 21-byte STRT record.
|
||||
# Confirmed 2026-04-14: there is NO preamble after STRT — bytes 21+
|
||||
# are raw ADC sample data. The earlier sp+27 skip was eating 6 bytes
|
||||
# of real waveform, misaligning the channel decode for all subsequent
|
||||
# frames.
|
||||
if fi == 0:
|
||||
sp = w.find(b"STRT")
|
||||
if sp < 0:
|
||||
continue
|
||||
wave = w[sp + 27 :]
|
||||
wave = w[sp + 21 :]
|
||||
log.info(
|
||||
"_decode_a5_waveform: A5[0] waveform starts at sp+21; "
|
||||
"first 24 wave bytes: %s",
|
||||
wave[:24].hex(' '),
|
||||
)
|
||||
|
||||
# Frame 7 carries event-time metadata strings ("Project:", "Client:", …)
|
||||
# and no waveform ADC data.
|
||||
@@ -1556,11 +1528,29 @@ def _decode_a5_waveform(
|
||||
|
||||
running_offset += len(wave)
|
||||
|
||||
n_decoded_total = len(tran)
|
||||
log.debug(
|
||||
"_decode_a5_waveform: decoded %d alignment-corrected sample-sets "
|
||||
"(skipped %d due to frame boundary misalignment)",
|
||||
len(tran), n_sets - len(tran),
|
||||
n_decoded_total, n_sets - n_decoded_total,
|
||||
)
|
||||
# Log first 16 and last 8 samples for every channel — essential for
|
||||
# validating decoder output and diagnosing flatline / misalignment issues.
|
||||
_N = min(16, n_decoded_total)
|
||||
_L = max(0, n_decoded_total - 8)
|
||||
log.info(
|
||||
"_decode_a5_waveform: first %d samples — "
|
||||
"Tran=%s Vert=%s Long=%s Mic=%s",
|
||||
_N,
|
||||
tran[:_N], vert[:_N], long_[:_N], mic[:_N],
|
||||
)
|
||||
if n_decoded_total > 16:
|
||||
log.info(
|
||||
"_decode_a5_waveform: last 8 samples (idx %d–%d) — "
|
||||
"Tran=%s Vert=%s Long=%s Mic=%s",
|
||||
_L, n_decoded_total - 1,
|
||||
tran[_L:], vert[_L:], long_[_L:], mic[_L:],
|
||||
)
|
||||
|
||||
event.raw_samples = {
|
||||
"Tran": tran,
|
||||
@@ -1569,10 +1559,10 @@ def _decode_a5_waveform(
|
||||
"Mic": mic,
|
||||
}
|
||||
|
||||
# ── Correct STRT-invalid pretrig/total from decoded count + compliance config ──
|
||||
# When the STRT layout was suspect (pretrig >= total_samples), both values were
|
||||
# zeroed out earlier. Now that we know the actual decoded sample count, use it
|
||||
# together with the compliance config record_time to derive a meaningful pretrig.
|
||||
# ── Derive pretrig/total from compliance config (always — STRT doesn't store them) ──
|
||||
# total_samples and pretrig_samples are not stored in the STRT record (confirmed
|
||||
# 2026-04-14). They are derived from compliance config record_time × sample_rate.
|
||||
# _strt_invalid is always True; this block always runs.
|
||||
#
|
||||
# Formula:
|
||||
# post_trig = record_time (s) × sample_rate (sps)
|
||||
@@ -1604,11 +1594,12 @@ def _decode_a5_waveform(
|
||||
event.pretrig_samples = derived_pretrig
|
||||
event.rectime_seconds = int(round(cc_rt))
|
||||
log.info(
|
||||
"_decode_a5_waveform: STRT was invalid — using pretrig=%d "
|
||||
"(%.2fs default) from compliance standard; record_time=%.1fs "
|
||||
"sr=%d sps decoded=%d samples display_total=%d",
|
||||
"_decode_a5_waveform: pretrig=%d (%.2fs compliance default) "
|
||||
"post_trig=%d total=%d record_time=%.1fs "
|
||||
"sr=%d sps decoded=%d samples",
|
||||
derived_pretrig, _PRETRIG_SECONDS_DEFAULT,
|
||||
cc_rt, cc_sr, n_decoded, event.total_samples,
|
||||
post_trig_samples, event.total_samples,
|
||||
cc_rt, cc_sr, n_decoded,
|
||||
)
|
||||
else:
|
||||
event.total_samples = n_decoded
|
||||
|
||||
Reference in New Issue
Block a user