From 227c481022a92b0cac38d5475a3f7abbceb2c9bc Mon Sep 17 00:00:00 2001 From: Brian Harrison Date: Mon, 6 Apr 2026 14:37:36 -0400 Subject: [PATCH] fix: update MiniMateClient to correctly handle 1F calls for event iteration and 5A arming --- CLAUDE.md | 14 +++++++++++--- minimateplus/client.py | 39 ++++++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bafcf4e..af5ff2f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -192,18 +192,26 @@ 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 -1F(all zeros / browse=False) → key1 ← MUST come BEFORE 5A (BW sequence, confirmed 4-2-26) +1F(token=0xFE) → [discard key] ← REQUIRED: arms 5A bulk stream state machine POLL × 3 ← REQUIRED: 3 full POLL cycles before 5A (BW frames 68-73) 5A(key0) ← bulk stream; key0 used even though 1F already advanced +1F(all zeros / browse=True) → key1 ← USE THIS for loop iteration (browse=True returns correct key) 0A(key1) 1E(token=0xFE) ← re-arm for next event's 5A 0C(key1) -1F(browse=False) → key2 +1F(token=0xFE) → [discard key] ← arm 5A POLL × 3 5A(key1) -1F(browse=False) → null ← done +1F(browse=True) → null ← done ``` +**IMPORTANT — two separate 1F calls per event (CONFIRMED 2026-04-06):** +`1F(token=0xFE)` (browse=False) BEFORE POLL+5A arms the device's bulk stream state machine +but returns inconsistent iteration keys after the first event — do NOT use its returned key +for loop control. `1F(browse=True)` AFTER 5A returns the correct next event key for +iteration. The reason is device-side: `1F(0xFE)` appears to arm 5A and peek the next key +without reliably advancing the internal iteration pointer; `1F(all-zero)` advances it. + **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 diff --git a/minimateplus/client.py b/minimateplus/client.py index 40050a5..a7cc063 100644 --- a/minimateplus/client.py +++ b/minimateplus/client.py @@ -325,22 +325,17 @@ class MiniMateClient: "get_events: 0C failed for key=%s: %s", cur_key.hex(), exc ) - # SUB 1F — advance BEFORE 5A (matches BW sequence from 4-2-26 capture). - # MUST use browse=False (token=0xFE) — BW always sends 1F(token=0xFE) in - # download mode. The device's 5A state machine requires the download-mode - # token here to arm the bulk stream; browse-mode (all-zero) token causes - # the device to ignore the subsequent 5A probe. - # Confirmed from 4-2-26 capture frames 66-67 vs frame 74. - # Save next key now; 5A will still use cur_key. + # 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. + # Confirmed from 4-2-26 capture frames 66-67 (1F before frames 68-73 POLL). try: - key4, data8 = proto.advance_event(browse=False) - log.info( - "get_events: 1F(download) → key=%s trailing=%s", - key4.hex(), data8[4:8].hex(), - ) + proto.advance_event(browse=False) # arm 5A — discard returned key + log.info("get_events: 1F(download) — 5A armed") 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" + log.warning("get_events: 1F(download) arm failed: %s", exc) # 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). @@ -351,7 +346,7 @@ class MiniMateClient: 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). + # 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. try: @@ -389,6 +384,20 @@ 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" + events.append(ev) idx += 1