From 27db663579adbdd99e88f76c6a3b49008dcd9f1c Mon Sep 17 00:00:00 2001 From: Brian Harrison Date: Mon, 13 Apr 2026 18:46:23 -0400 Subject: [PATCH] fix: update event count retrieval logic in AchSession and MiniMateClient --- bridges/ach_server.py | 49 ++++++++++++++++++++---------------------- minimateplus/client.py | 17 +++++++++++++-- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/bridges/ach_server.py b/bridges/ach_server.py index d7363cf..afafe4c 100644 --- a/bridges/ach_server.py +++ b/bridges/ach_server.py @@ -257,29 +257,11 @@ class AchSession: # Used to detect post-erase key reuse — see comment block above. max_seen_key: str = unit_state.get("max_downloaded_key", "00000000") - # Use the event count already read from the event index during connect(). - # This is fast (no extra round-trips) and confirmed accurate (matches LCD). - # Falls back to count_events() only if connect() wasn't called. - if device_info is not None: - current_count = device_info.event_count or 0 - else: - try: - current_count = client.count_events() - except Exception as exc: - log.error(" [FAIL] count_events failed: %s", exc) - return - - log.info(" Unit has %d stored event(s); %d key(s) previously downloaded", - current_count, len(seen_keys)) - - if current_count == 0: - log.info(" [OK] No events on device -- nothing to download") - log.info("Session complete (no events) -> %s", session_dir) - return - - # Fast pre-check: walk the event index (browse-mode, no 5A) to get - # the current key list, then bail early if everything is already seen. - # This avoids calling get_events() at all when there's nothing new. + # Walk the event index (browse-mode, no 5A) to get the actual current + # key list. The SUB 08 event_count field is a lifetime "total events + # ever recorded" counter that does NOT decrement on erase — confirmed + # 2026-04-13. list_event_keys() via the 1E/1F chain is the only + # reliable way to know what is actually stored on the device right now. log.info(" Checking device key list (browse walk, no waveform download)...") try: device_keys = client.list_event_keys() @@ -287,6 +269,17 @@ class AchSession: log.warning(" list_event_keys failed: %s -- falling back to full download", exc) device_keys = None + # Use the walk result as our authoritative current count. + current_count = len(device_keys) if device_keys is not None else 0 + + log.info(" Unit has %d stored event(s); %d key(s) previously downloaded", + current_count, len(seen_keys)) + + if device_keys is not None and current_count == 0: + log.info(" [OK] No events on device -- nothing to download") + log.info("Session complete (no events) -> %s", session_dir) + return + if device_keys is not None: # ── Post-erase detection ────────────────────────────────────── # After the device memory is erased, new events start from key @@ -359,10 +352,14 @@ class AchSession: new_key_set = None # unknown; proceed with full download # Apply max_events cap - stop_idx = current_count - 1 + # stop_idx: when we know the count from list_event_keys, use it as + # an upper bound. When list_event_keys failed (device_keys is None), + # pass None — get_events will run until the null sentinel naturally. + stop_idx: Optional[int] = (current_count - 1) if device_keys is not None else None if self.max_events is not None: - stop_idx = min(stop_idx, self.max_events - 1) - if self.max_events < current_count: + cap = self.max_events - 1 + stop_idx = cap if stop_idx is None else min(stop_idx, cap) + if device_keys is not None and self.max_events < current_count: log.warning( " max_events=%d cap: will download events 0-%d only " "(unit has %d total)", diff --git a/minimateplus/client.py b/minimateplus/client.py index 28b5433..9ba7c22 100644 --- a/minimateplus/client.py +++ b/minimateplus/client.py @@ -181,8 +181,21 @@ class MiniMateClient: log.info("connect: reading event index (SUB 08)") try: idx_raw = proto.read_event_index() - device_info.event_count = _decode_event_count(idx_raw) - log.info("connect: device has %d stored event(s)", device_info.event_count) + # NOTE: _decode_event_count reads data[10:12] from the SUB 08 payload, + # which was believed to be the stored event count. Empirically it turns + # out to be a monotonically-increasing "total events ever recorded" counter + # that does NOT decrement when events are erased — confirmed 2026-04-13: + # device reported 6 via SUB 08 while list_event_keys() returned 0 (empty). + # We preserve the raw read here for the index data but do NOT use this + # count for logic; ach_server uses list_event_keys() as the authoritative + # source instead. + _raw_idx_count = _decode_event_count(idx_raw) + log.info( + "connect: SUB 08 index count=%d (lifetime counter, not current storage)", + _raw_idx_count, + ) + # Leave device_info.event_count as None — callers should use + # list_event_keys() to get the actual current event count. except ProtocolError as exc: log.warning("connect: event index read failed: %s — continuing", exc)