fix: waveform decode improved for accuracy.

feat: adds 5a diagnostic script to parse raw binary
This commit is contained in:
2026-04-15 16:36:41 -04:00
parent 8bfebadd46
commit a46961c124
5 changed files with 1474 additions and 1100 deletions
+43 -38
View File
@@ -1440,29 +1440,50 @@ def _decode_a5_waveform(
for fi, db in enumerate(frames_data):
w = db[7:]
# 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:
# ── Probe frames (fi==0 AND any re-probe the device sends mid-stream) ────
# A5[0] always contains the STRT record. For event key 0x01110000,
# chunk 4 (counter=0x1000) has 0x10 in the counter high byte; the device
# DLE-decodes the params and sees counter=0x0000 (probe), so it responds
# with a duplicate probe frame containing the same STRT. The diagnostic
# from 2026-04-15 confirmed this: fi=4 was byte-for-byte identical to fi=0
# (same db length 1101B, same STRT at w[10], same first 32 bytes).
#
# Handling: any frame — not just fi==0 — that contains the STRT magic is
# treated as a probe frame. Waveform starts at strt_pos + 21 (no preamble).
# Re-probe frames are complete duplicates of fi=0 (device re-sends the
# beginning of the event), so their post-STRT waveform bytes are DROPPED
# to avoid injecting duplicate data into the stream.
sp = w.find(b"STRT")
if sp >= 0:
if fi == 0:
wave = w[sp + 21 :]
log.info(
"_decode_a5_waveform: A5[0] probe — STRT at w[%d], "
"waveform starts at sp+21; first 24 wave bytes: %s",
sp, wave[:24].hex(' '),
)
else:
# Re-probe frame: device re-sent probe in response to a chunk
# request whose counter byte happened to be 0x10 (DLE).
# The post-STRT bytes are a duplicate of the initial waveform
# — drop this frame entirely to avoid double-counting data.
log.info(
"_decode_a5_waveform: fi=%d re-probe (STRT at w[%d]) — "
"skipped (duplicate probe response from device)",
fi, sp,
)
continue
wave = w[sp + 21 :]
log.info(
"_decode_a5_waveform: A5[0] waveform starts at sp+21; "
"first 24 wave bytes: %s",
wave[:24].hex(' '),
)
# Metadata frame: contains "Project:", "Client:", etc. strings.
# Originally assumed to be always fi==7 (A5[7] in 4-2-26 blast capture),
# but confirmed variable position — it appears at whatever chunk index the
# device places it (observed at fi=6 for desk-thump events 2026-04-14).
# Skip ANY frame whose raw bytes contain b"Project:" — this is the same
# anchor used by stop_after_metadata in read_bulk_waveform_stream.
elif b"Project:" in w:
# Metadata frame: contains BOTH "Project:" and "Client:" strings.
# Requiring two compliance anchors prevents false positives where ADC
# bytes accidentally spell "Project:" (confirmed false positive at fi=15
# in the 2026-04-15 desk-thump download — only "Project:" appeared there,
# not "Client:"). The real metadata frame always contains both.
# This is the same anchor used by stop_after_metadata in
# read_bulk_waveform_stream (which only checks "Project:" — see note
# there about the asymmetry: stopping early is fine with one anchor,
# but skipping a waveform frame requires higher confidence).
elif b"Project:" in w and b"Client:" in w:
log.info("_decode_a5_waveform: fi=%d skipped (metadata frame)", fi)
continue
@@ -2248,20 +2269,4 @@ def _decode_monitor_status(data: bytes) -> MonitorStatus:
# Payload length varies (4649 bytes) but the battery/memory block is always
# the last 10 bytes. No checksum byte — it was stripped by S3FrameParser.
#
# section[-10:-8] battery × 100 uint16 BE 0x02A8 = 6.80 V
# section[-8 :-4] memory_total uint32 BE ≈ 960 KB on BE11529
# section[-4:] memory_free uint32 BE decreases as events fill
#
# Confirmed stable across IDLE (46b), MONITORING (48-49b) variants.
if len(section) >= 10:
batt_raw = struct.unpack(">H", section[-10:-8])[0]
battery_v = batt_raw / 100.0
memory_total = struct.unpack(">I", section[-8:-4])[0]
memory_free = struct.unpack(">I", section[-4:])[0]
return MonitorStatus(
is_monitoring=is_monitoring,
battery_v=battery_v,
memory_total=memory_total,
memory_free=memory_free,
)
# section[-1