feat: implement reliable event counting via 1E/1F chain and update device info
This commit is contained in:
@@ -163,6 +163,43 @@ class MiniMateClient:
|
|||||||
log.info("connect: %s", device_info)
|
log.info("connect: %s", device_info)
|
||||||
return device_info
|
return device_info
|
||||||
|
|
||||||
|
def count_events(self) -> int:
|
||||||
|
"""
|
||||||
|
Count stored events by iterating the 1E → 1F key chain.
|
||||||
|
|
||||||
|
This is the only reliable way to get the true event count. The SUB 08
|
||||||
|
event index payload has a field that was assumed to be an event count
|
||||||
|
(uint32 BE at offset +3) but empirically always returns 1 regardless of
|
||||||
|
how many events are stored — Blastware appears to download one event per
|
||||||
|
TCP session, so the index may reflect session-scoped state rather than
|
||||||
|
device-wide storage.
|
||||||
|
|
||||||
|
This method issues 1E (first key) then 1F repeatedly until the null key
|
||||||
|
b'\\x00\\x00\\x00\\x00', counting as it goes. No 0A/0C/5A reads are
|
||||||
|
performed, so it is much faster than get_events().
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Number of stored waveform events (0 if device is empty).
|
||||||
|
"""
|
||||||
|
proto = self._require_proto()
|
||||||
|
try:
|
||||||
|
key4, _ = proto.read_event_first()
|
||||||
|
except ProtocolError as exc:
|
||||||
|
log.warning("count_events: 1E failed: %s — returning 0", exc)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
while key4 != b"\x00\x00\x00\x00":
|
||||||
|
count += 1
|
||||||
|
try:
|
||||||
|
key4 = proto.advance_event()
|
||||||
|
except ProtocolError as exc:
|
||||||
|
log.warning("count_events: 1F failed after %d events: %s", count, exc)
|
||||||
|
break
|
||||||
|
|
||||||
|
log.info("count_events: %d event(s) found via 1E/1F chain", count)
|
||||||
|
return count
|
||||||
|
|
||||||
def get_events(self, full_waveform: bool = False, debug: bool = False) -> list[Event]:
|
def get_events(self, full_waveform: bool = False, debug: bool = False) -> list[Event]:
|
||||||
"""
|
"""
|
||||||
Download all stored events from the device using the confirmed
|
Download all stored events from the device using the confirmed
|
||||||
@@ -475,11 +512,13 @@ def _decode_event_count(data: bytes) -> int:
|
|||||||
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 raw bytes so we can verify this decode against known event counts
|
# Log the full payload so we can reverse-engineer the format
|
||||||
log.warning(
|
log.warning("event_index raw (%d bytes total):", len(data))
|
||||||
"event_index raw (first 16 bytes): %s",
|
for off in range(0, len(data), 16):
|
||||||
" ".join(f"{b:02x}" for b in 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
|
# Try the uint32 at +3 first
|
||||||
count = struct.unpack_from(">I", data, 3)[0]
|
count = struct.unpack_from(">I", data, 3)[0]
|
||||||
|
|||||||
@@ -264,7 +264,11 @@ def device_info(
|
|||||||
try:
|
try:
|
||||||
def _do():
|
def _do():
|
||||||
with _build_client(port, baud, host, tcp_port) as client:
|
with _build_client(port, baud, host, tcp_port) as client:
|
||||||
return client.connect()
|
info = client.connect()
|
||||||
|
# SUB 08 event_count is unreliable (always returns 1 regardless of
|
||||||
|
# actual storage). Count via 1E/1F chain instead.
|
||||||
|
info.event_count = client.count_events()
|
||||||
|
return info
|
||||||
info = _run_with_retry(_do, is_tcp=_is_tcp(host))
|
info = _run_with_retry(_do, is_tcp=_is_tcp(host))
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
|
|||||||
Reference in New Issue
Block a user