fix: correct event count field offset and eliminate count_events() walk

_decode_event_count: read uint16 BE at offset 10 (confirmed 2026-04-10 from
live BE11529 event index — data[10:12]=0x0006=6, matches device LCD).
Previous uint32 at offset 3 always returned 1 regardless of event count.

ach_server.py: use device_info.event_count (already fetched during connect())
instead of calling count_events() separately. This saves 2*N round-trips and
avoids the 1F linked-list walk which was overcounting on some devices.
count_events() kept as fallback when connect() is skipped (--events-only).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 01:10:49 -04:00
committed by serversdown
parent 1bfc6e4258
commit 41a14ca468
2 changed files with 24 additions and 28 deletions
+9 -2
View File
@@ -216,14 +216,21 @@ class AchSession:
unit_key = serial or self.peer # fall back to IP if no serial unit_key = serial or self.peer # fall back to IP if no serial
last_count = state.get(unit_key, {}).get("event_count", 0) last_count = state.get(unit_key, {}).get("event_count", 0)
# 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
else:
try: try:
current_count = client.count_events() current_count = client.count_events()
log.info(" Unit has %d stored event(s); last downloaded count: %d",
current_count, last_count)
except Exception as exc: except Exception as exc:
log.error(" [FAIL] count_events failed: %s", exc) log.error(" [FAIL] count_events failed: %s", exc)
return return
log.info(" Unit has %d stored event(s); last downloaded count: %d",
current_count, last_count)
if current_count <= last_count: if current_count <= last_count:
log.info(" [OK] No new events since last call-home -- nothing to download") log.info(" [OK] No new events since last call-home -- nothing to download")
log.info("Session complete (no new events) -> %s", session_dir) log.info("Session complete (no new events) -> %s", session_dir)
+10 -21
View File
@@ -910,36 +910,25 @@ def _decode_event_count(data: bytes) -> int:
""" """
Extract stored event count from SUB F7 (EVENT_INDEX_RESPONSE) payload. Extract stored event count from SUB F7 (EVENT_INDEX_RESPONSE) payload.
Layout per §7.4 (offsets from data section start): Confirmed 2026-04-10 from live BE11529 event index (88 bytes):
+00: 00 58 09 — total index size or record count ❓ data[10:12] uint16 BE = stored event count (confirmed: 0x0006 = 6, matches LCD)
+03: 00 00 00 01 — possibly stored event count = 1 ❓ data[3:7] uint32 BE = 0x00000001 (NOT the count — meaning TBD)
We use bytes +03..+06 interpreted as uint32 BE as the event count. Previous implementation read uint32 at offset 3, which returned 1 regardless
This is inferred (🔶) — the exact meaning of the first 3 bytes is unclear. of how many events were stored.
""" """
if len(data) < 7: if len(data) < 12:
log.warning("event index payload too short (%d bytes), assuming 0 events", len(data)) log.warning("event index payload too short (%d bytes), assuming 0 events", len(data))
return 0 return 0
# Log the full payload so we can reverse-engineer the format count = struct.unpack_from(">H", data, 10)[0]
log.warning("event_index raw (%d bytes total):", len(data))
for off in range(0, len(data), 16):
chunk = data[off:off+16]
hex_part = " ".join(f"{b:02x}" for b in chunk)
asc_part = "".join(chr(b) if 0x20 <= b < 0x7f else "." for b in chunk)
log.warning(" [%04x]: %-47s %s", off, hex_part, asc_part)
# Try the uint32 at +3 first # Sanity check: MiniMate Plus max storage is ~1000 events
count = struct.unpack_from(">I", data, 3)[0]
# Sanity check: MiniMate Plus manual says max ~1000 events
if count > 1000: if count > 1000:
log.warning( log.warning("event count %d looks unreasonably large — clamping to 0", count)
"event count %d looks unreasonably large — clamping to 0", count
)
return 0 return 0
log.warning("event_index decoded count=%d (uint32 BE at offset +3)", count) log.debug("event_index decoded count=%d (uint16 BE at offset 10)", count)
return count return count