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:
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user