fix: update MiniMateClient and protocol to ensure correct handling of 1F calls and improve event download sequence
This commit is contained in:
+57
-19
@@ -326,14 +326,13 @@ class MiniMateClient:
|
||||
)
|
||||
|
||||
# SUB 1F (download-arm) — send token=0xFE BEFORE POLL+5A to arm the
|
||||
# device's bulk stream state machine. The key returned by this call
|
||||
# is NOT used for loop iteration — 1F(download) returns inconsistent
|
||||
# keys after the first event (device-side state issue). A second
|
||||
# browse-mode 1F call after 5A handles actual key advancement.
|
||||
# device's bulk stream state machine. Cache the returned key as a
|
||||
# fallback for loop iteration when 5A fails (see iteration block below).
|
||||
# Confirmed from 4-2-26 capture frames 66-67 (1F before frames 68-73 POLL).
|
||||
arm_key4: Optional[bytes] = None
|
||||
try:
|
||||
proto.advance_event(browse=False) # arm 5A — discard returned key
|
||||
log.info("get_events: 1F(download) — 5A armed")
|
||||
arm_key4, _ = proto.advance_event(browse=False) # arm 5A
|
||||
log.info("get_events: 1F(download) — 5A armed, arm_key=%s", arm_key4.hex())
|
||||
except ProtocolError as exc:
|
||||
log.warning("get_events: 1F(download) arm failed: %s", exc)
|
||||
|
||||
@@ -349,6 +348,7 @@ class MiniMateClient:
|
||||
# SUB 5A — bulk waveform stream (uses cur_key, the event set up by 0A+1E+0C).
|
||||
# By default (full_waveform=False): stop after frame 7 for metadata only.
|
||||
# When full_waveform=True: fetch all chunks and decode raw ADC samples.
|
||||
a5_ok = False
|
||||
try:
|
||||
if full_waveform:
|
||||
log.info(
|
||||
@@ -358,6 +358,7 @@ class MiniMateClient:
|
||||
cur_key, stop_after_metadata=False, max_chunks=128
|
||||
)
|
||||
if a5_frames:
|
||||
a5_ok = True
|
||||
_decode_a5_metadata_into(a5_frames, ev)
|
||||
_decode_a5_waveform(a5_frames, ev)
|
||||
log.info(
|
||||
@@ -372,6 +373,7 @@ class MiniMateClient:
|
||||
cur_key, stop_after_metadata=True
|
||||
)
|
||||
if a5_frames:
|
||||
a5_ok = True
|
||||
_decode_a5_metadata_into(a5_frames, ev)
|
||||
log.debug(
|
||||
"get_events: 5A metadata client=%r operator=%r",
|
||||
@@ -384,19 +386,55 @@ class MiniMateClient:
|
||||
cur_key.hex(), exc,
|
||||
)
|
||||
|
||||
# SUB 1F (browse) — advance event pointer for loop iteration.
|
||||
# browse=True (all-zero params) confirmed correct for multi-event
|
||||
# iteration from 4-3-26 browse-mode S3 captures. Must come AFTER
|
||||
# 5A — the download-mode 1F above is for 5A arming only.
|
||||
try:
|
||||
key4, data8 = proto.advance_event(browse=True)
|
||||
log.info(
|
||||
"get_events: 1F(browse) → key=%s trailing=%s",
|
||||
key4.hex(), data8[4:8].hex(),
|
||||
)
|
||||
except ProtocolError as exc:
|
||||
log.warning("get_events: 1F(browse) failed: %s — stopping", exc)
|
||||
key4, data8 = b"\x00\x00\x00\x00", b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
# SUB 1F — loop iteration.
|
||||
#
|
||||
# IMPORTANT: browse 1F (all-zero params) is ONLY called when 5A
|
||||
# succeeded. If 5A timed out or failed, calling browse 1F disrupts
|
||||
# the device's internal state and causes the NEXT event's 5A to also
|
||||
# fail. In the failure path, use the key cached from 1F(download)
|
||||
# above as a best-effort fallback for iteration.
|
||||
#
|
||||
# Confirmed from 4-3-26 browse-mode captures: browse=True params
|
||||
# are correct for multi-event iteration. Conditional logic added
|
||||
# 2026-04-06 to avoid post-failure state disruption.
|
||||
if a5_ok:
|
||||
# 5A succeeded — use browse 1F for reliable key advancement.
|
||||
try:
|
||||
key4, data8 = proto.advance_event(browse=True)
|
||||
log.info(
|
||||
"get_events: 1F(browse) → key=%s trailing=%s",
|
||||
key4.hex(), data8[4:8].hex(),
|
||||
)
|
||||
except ProtocolError as exc:
|
||||
log.warning("get_events: 1F(browse) failed: %s — stopping", exc)
|
||||
key4 = b"\x00\x00\x00\x00"
|
||||
data8 = b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
else:
|
||||
# 5A failed — skip browse 1F to avoid further state disruption.
|
||||
# Use the arm_key4 returned by 1F(download) as the next-key hint.
|
||||
if arm_key4 is None or arm_key4 == cur_key:
|
||||
# 1F(download) returned no valid next key (or same key = stuck).
|
||||
# Stop iteration to prevent infinite loop.
|
||||
log.warning(
|
||||
"get_events: 5A failed and 1F(download) returned no valid "
|
||||
"next key (arm_key=%s, cur_key=%s) — stopping iteration",
|
||||
arm_key4.hex() if arm_key4 else "None",
|
||||
cur_key.hex(),
|
||||
)
|
||||
key4 = b"\x00\x00\x00\x00"
|
||||
data8 = b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
else:
|
||||
# arm_key4 is a valid non-stuck next key — use it.
|
||||
# Construct a synthetic data8 with non-null trailing so the
|
||||
# loop continues (the real trailing is unknown but non-null
|
||||
# since we have a valid arm_key4).
|
||||
key4 = arm_key4
|
||||
data8 = arm_key4 + b"\x00\x00\x00\x01"
|
||||
log.warning(
|
||||
"get_events: 5A failed — advancing via arm_key=%s "
|
||||
"(browse 1F skipped to preserve device state)",
|
||||
key4.hex(),
|
||||
)
|
||||
|
||||
events.append(ev)
|
||||
idx += 1
|
||||
|
||||
@@ -386,13 +386,15 @@ class S3FrameParser:
|
||||
_IN_FRAME_DLE = 3
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._state = self._IDLE
|
||||
self._body = bytearray() # accumulates de-stuffed frame bytes
|
||||
self._state = self._IDLE
|
||||
self._body = bytearray() # accumulates de-stuffed frame bytes
|
||||
self.frames: list[S3Frame] = []
|
||||
self.bytes_fed: int = 0 # cumulative raw bytes fed since last reset
|
||||
|
||||
def reset(self) -> None:
|
||||
self._state = self._IDLE
|
||||
self._state = self._IDLE
|
||||
self._body.clear()
|
||||
self.bytes_fed = 0
|
||||
|
||||
def feed(self, data: bytes) -> list[S3Frame]:
|
||||
"""
|
||||
@@ -401,6 +403,7 @@ class S3FrameParser:
|
||||
Returns a list of S3Frame objects completed during this call.
|
||||
All completed frames are also appended to self.frames.
|
||||
"""
|
||||
self.bytes_fed += len(data)
|
||||
completed: list[S3Frame] = []
|
||||
for b in data:
|
||||
frame = self._step(b)
|
||||
|
||||
@@ -494,7 +494,16 @@ class MiniMateProtocol:
|
||||
log.debug("5A probe key=%s", key4.hex())
|
||||
params = bulk_waveform_params(key4, 0, is_probe=True)
|
||||
self._send(build_5a_frame(_BULK_CHUNK_OFFSET, params))
|
||||
rsp = self._recv_one(expected_sub=rsp_sub)
|
||||
self._parser.reset() # reset bytes_fed counter before probe recv
|
||||
try:
|
||||
rsp = self._recv_one(expected_sub=rsp_sub, reset_parser=False)
|
||||
except TimeoutError:
|
||||
log.warning(
|
||||
"5A probe TIMED OUT for key=%s — "
|
||||
"%d raw bytes received (no complete A5 frame assembled)",
|
||||
key4.hex(), self._parser.bytes_fed,
|
||||
)
|
||||
raise
|
||||
frames_data.append(rsp.data)
|
||||
log.debug("5A A5[0] page_key=0x%04X %d bytes", rsp.page_key, len(rsp.data))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user