fix: update event handling in MiniMateClient and protocol to ensure correct sequence for waveform downloads

This commit is contained in:
2026-04-06 13:19:51 -04:00
parent 41090a9346
commit d0d5a18d5c
3 changed files with 102 additions and 54 deletions
+61 -49
View File
@@ -277,68 +277,85 @@ class MiniMateClient:
idx = 0
while data8[4:8] != b"\x00\x00\x00\x00":
log.info("get_events: record %d key=%s", idx, key4.hex())
cur_key = key4 # key for this event's 0A/1E-arm/0C/5A calls
log.info("get_events: record %d key=%s", idx, cur_key.hex())
ev = Event(index=idx)
ev._waveform_key = key4 # stored so download_waveform() can re-use it
ev._waveform_key = cur_key
# Always call 0A before 0C to establish device waveform context.
# The device requires 0A context for both 0C and the subsequent 1F.
# (Earlier code skipped 0A for events after the first — confirmed wrong.)
# SUB 0A — MUST be called first to establish device waveform context.
# Required before 0C, 1E-arm, and 1F.
proceed = True
try:
_hdr, rec_len = proto.read_waveform_header(key4)
_hdr, rec_len = proto.read_waveform_header(cur_key)
if rec_len < 0x30:
log.warning(
"get_events: key=%s is partial (len=0x%02X) — skipping",
key4.hex(), rec_len,
cur_key.hex(), rec_len,
)
proceed = False
except ProtocolError as exc:
log.warning(
"get_events: 0A failed for key=%s: %s — skipping 0C",
key4.hex(), exc,
"get_events: 0A failed for key=%s: %s — skipping",
cur_key.hex(), exc,
)
proceed = False
if proceed:
# SUB 1E (download-arm) — MUST be sent between 0A and 0C.
# The device ignores 5A probe frames unless this second 1E with
# token=0xFE has been issued first. Confirmed from both 4-2-26
# and 4-3-26 BW TX captures (2026-04-06).
# The returned key is the same event key we already hold — ignore it.
log.info("get_events: 1E download-arm (token=0xFE) for key=%s", key4.hex())
# Device ignores 5A probe frames without this second 1E(token=0xFE).
# Confirmed from both 4-2-26 and 4-3-26 BW TX captures (2026-04-06).
log.info("get_events: 1E download-arm (token=0xFE) for key=%s", cur_key.hex())
try:
proto.read_event_first(token=0xFE)
log.info("get_events: 1E download-arm OK")
except ProtocolError as exc:
log.warning(
"get_events: 1E download-arm failed for key=%s: %s",
key4.hex(), exc,
cur_key.hex(), exc,
)
# SUB 0C — full waveform record (peak values, timestamp, "Project:" string)
# SUB 0C — full waveform record (peak values, timestamp, project string)
try:
record = proto.read_waveform_record(key4)
record = proto.read_waveform_record(cur_key)
if debug:
ev._raw_record = record
_decode_waveform_record_into(record, ev)
except ProtocolError as exc:
log.warning(
"get_events: 0C failed for key=%s: %s", key4.hex(), exc
"get_events: 0C failed for key=%s: %s", cur_key.hex(), exc
)
# SUB 5A — bulk waveform stream.
# By default (full_waveform=False): stop early after frame 7 ("Project:")
# is found — fetches only ~8 frames for event-time metadata.
# When full_waveform=True: fetch the complete stream (stop_after_metadata=False,
# max_chunks=128) and decode raw ADC samples into ev.raw_samples.
# SUB 1F — advance BEFORE 5A (matches BW sequence from 4-2-26 capture).
# Save next key now; 5A will still use cur_key.
try:
key4, data8 = proto.advance_event(browse=True)
log.info(
"get_events: 1F → key=%s trailing=%s",
key4.hex(), data8[4:8].hex(),
)
except ProtocolError as exc:
log.warning("get_events: 1F failed: %s — stopping after this event", exc)
key4, data8 = b"\x00\x00\x00\x00", b"\x00\x00\x00\x00\x00\x00\x00\x00"
# POLL × 3 — BW sends 3 full POLL cycles between 1F and 5A.
# Confirmed from 4-2-26 BW TX capture (frames 68-73 before 5A at 74).
log.info("get_events: POLL × 3 before 5A")
for _p in range(3):
try:
proto.poll()
except ProtocolError as exc:
log.warning("get_events: POLL %d failed: %s", _p, exc)
# SUB 5A — bulk waveform stream (uses cur_key, NOT the advanced key4).
# By default (full_waveform=False): stop after frame 7 for metadata only.
# When full_waveform=True: fetch all chunks and decode raw ADC samples.
try:
if full_waveform:
log.info(
"get_events: 5A full waveform download for key=%s", key4.hex()
"get_events: 5A full waveform download for key=%s", cur_key.hex()
)
a5_frames = proto.read_bulk_waveform_stream(
key4, stop_after_metadata=False, max_chunks=128
cur_key, stop_after_metadata=False, max_chunks=128
)
if a5_frames:
_decode_a5_metadata_into(a5_frames, ev)
@@ -348,8 +365,11 @@ class MiniMateClient:
len((ev.raw_samples or {}).get("Tran", [])),
)
else:
log.info(
"get_events: 5A metadata-only download for key=%s", cur_key.hex()
)
a5_frames = proto.read_bulk_waveform_stream(
key4, stop_after_metadata=True
cur_key, stop_after_metadata=True
)
if a5_frames:
_decode_a5_metadata_into(a5_frames, ev)
@@ -360,36 +380,28 @@ class MiniMateClient:
)
except ProtocolError as exc:
log.warning(
"get_events: 5A failed for key=%s: %s event-time metadata unavailable",
key4.hex(), exc,
"get_events: 5A failed for key=%s: %s — metadata unavailable",
cur_key.hex(), exc,
)
# Include all full records regardless of sub_code / record_type.
# Partial records (proceed=False, rec_len < 0x30 or 0A failed) are
# the only thing we skip — we have no data to decode for those.
if proceed:
events.append(ev)
idx += 1
else:
log.info(
"get_events: key=%s — skipping partial/failed record (rec_len < 0x30)",
key4.hex(),
)
# SUB 1F — advance to the next record key.
# Uses browse mode (all-zero params) — empirically confirmed to work
# with 2+ events stored (4-3-26 browse-mode capture). BW itself uses
# token=0xFE here in download mode, but browse mode is simpler and
# avoids complications with the download-arm state machine.
try:
key4, data8 = proto.advance_event(browse=True)
else:
# Partial/failed record — skip 5A, just advance with 1F.
log.info(
"get_events: 1F → key=%s trailing=%s",
key4.hex(), data8[4:8].hex(),
"get_events: key=%s — skipping partial/failed record",
cur_key.hex(),
)
except ProtocolError as exc:
log.warning("get_events: 1F failed: %s — stopping iteration", exc)
break
try:
key4, data8 = proto.advance_event(browse=True)
log.info(
"get_events: 1F → key=%s trailing=%s",
key4.hex(), data8[4:8].hex(),
)
except ProtocolError as exc:
log.warning("get_events: 1F failed: %s — stopping iteration", exc)
break
log.info("get_events: downloaded %d event(s)", len(events))
return events
+23
View File
@@ -241,6 +241,29 @@ class MiniMateProtocol:
log.debug("read SUB=0x%02X: received %d data bytes", sub, len(data_rsp.data))
return data_rsp.data
def poll(self) -> S3Frame:
"""
Send a single POLL (SUB 5B) probe+data cycle and return the data response.
This is a bare POLL cycle with no boot-string drain — use during an active
session (contrast with startup(), which drains the "Operating System" boot
string first).
Confirmed from 4-2-26 BW TX capture: BW sends exactly 3 of these POLL
cycles between the last 1F and the first 5A probe frame during every
waveform download. Without them the device ignores the 5A probe.
"""
self._send(POLL_PROBE)
self._recv_one(
expected_sub=_expected_rsp_sub(SUB_POLL),
timeout=self._recv_timeout,
)
self._send(POLL_DATA)
return self._recv_one(
expected_sub=_expected_rsp_sub(SUB_POLL),
timeout=self._recv_timeout,
)
def send_keepalive(self) -> None:
"""
Send a single POLL_PROBE keepalive without waiting for a response.