fix: update event handling in MiniMateClient and protocol to ensure correct sequence for waveform downloads
This commit is contained in:
@@ -194,20 +194,33 @@ there is only one event.
|
||||
0A(key0) ← REQUIRED: establishes device context
|
||||
1E(token=0xFE) ← REQUIRED: arms device for 5A; CONFIRMED 4-2-26 + 4-3-26
|
||||
0C(key0) ← read waveform record
|
||||
5A(key0) ← bulk stream (metadata or full waveform)
|
||||
1F(all zeros / browse=True) → key1
|
||||
1F(all zeros / browse=True) → key1 ← MUST come BEFORE 5A (BW sequence, confirmed 4-2-26)
|
||||
POLL × 3 ← REQUIRED: 3 full POLL cycles before 5A (BW frames 68-73)
|
||||
5A(key0) ← bulk stream; key0 used even though 1F already advanced
|
||||
0A(key1)
|
||||
1E(token=0xFE) ← re-arm for next event's 5A
|
||||
0C(key1)
|
||||
1F(all zeros) → key2
|
||||
POLL × 3
|
||||
5A(key1)
|
||||
1F(all zeros) → null ← done
|
||||
```
|
||||
|
||||
**The 1E(token=0xFE) arm step is the root cause of the 5A timeout (FIXED 2026-04-06):**
|
||||
**The 1E(token=0xFE) arm step is required (FIXED 2026-04-06):**
|
||||
The device silently ignores all 5A probe frames unless a second SUB 1E with token=0xFE
|
||||
has been issued between 0A and 0C. This step is present in EVERY download cycle in both
|
||||
the 4-2-26 and 4-3-26 BW TX captures. Without it, `read_bulk_waveform_stream()` blocks
|
||||
for the full 120 s timeout.
|
||||
the 4-2-26 and 4-3-26 BW TX captures.
|
||||
|
||||
**1F must come BEFORE 5A (FIXED 2026-04-06):**
|
||||
BW always calls 1F (advance event) before starting the 5A bulk stream. 5A still uses the
|
||||
pre-advance key — the device streams the waveform for the key that was set up with 0A+1E-arm+0C
|
||||
even after 1F has moved the internal pointer to the next event.
|
||||
|
||||
**POLL × 3 required before 5A (FIXED 2026-04-06):**
|
||||
BW sends exactly 3 complete POLL (SUB 5B) probe+data cycles between the last 1F and the
|
||||
first 5A probe frame. Confirmed from 4-2-26 BW TX capture frames 68-73. Without these
|
||||
POLLs the device does not respond to the 5A probe. Use `proto.poll()` (not `startup()` —
|
||||
`startup()` drains the boot string, which is only needed on initial connect).
|
||||
|
||||
`advance_event(browse=True)` sends all-zero params; `advance_event()` default (browse=False)
|
||||
sends token=0xFE and is NOT used by any caller.
|
||||
|
||||
+54
-42
@@ -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,27 +380,19 @@ 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.
|
||||
else:
|
||||
# Partial/failed record — skip 5A, just advance with 1F.
|
||||
log.info(
|
||||
"get_events: key=%s — skipping partial/failed record",
|
||||
cur_key.hex(),
|
||||
)
|
||||
try:
|
||||
key4, data8 = proto.advance_event(browse=True)
|
||||
log.info(
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user