From 781d21f1322958a77c6343b6dda6df7dc8c26a20 Mon Sep 17 00:00:00 2001 From: Brian Harrison Date: Mon, 6 Apr 2026 19:02:01 -0400 Subject: [PATCH] perf: reduce 5A chunk timeout to 10s and stop iteration at requested event index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two improvements to eliminate the ~2-min-per-event wait and unnecessary full-event-list download when only one event is requested: 1. protocol.py: pass timeout=10.0 to _recv_one in the 5A chunk loop. Device responds within ~1s per chunk; 10s gives a safe 10x buffer. End-of-stream detection (raw_bytes=1) now fires in 10s instead of 120s, cutting ~110s of dead wait per event. 2. client.py: add stop_after_index parameter to get_events(). When set, iteration stops immediately after the target event is collected — no further 0A/1E/0C/5A/1F cycles for events the caller doesn't need. 3. server.py: pass stop_after_index=index to both /device/event/{idx} and /device/event/{idx}/waveform endpoints so a single-event request only downloads that one event. Co-Authored-By: Claude Sonnet 4.6 --- minimateplus/client.py | 11 ++++++++++- minimateplus/protocol.py | 2 +- sfm/server.py | 7 +++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/minimateplus/client.py b/minimateplus/client.py index 3f5c9d2..d35c541 100644 --- a/minimateplus/client.py +++ b/minimateplus/client.py @@ -228,7 +228,7 @@ class MiniMateClient: 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, stop_after_index: Optional[int] = None) -> list[Event]: """ Download all stored events from the device using the confirmed 1E → 0A → 0C → 5A → 1F event-iterator protocol. @@ -439,6 +439,15 @@ class MiniMateClient: events.append(ev) idx += 1 + # Early exit: if the caller only wants events up to a specific + # index, stop iterating once we've collected it. + if stop_after_index is not None and idx > stop_after_index: + log.info( + "get_events: reached stop_after_index=%d — stopping early", + stop_after_index, + ) + break + else: # Partial/failed record — skip 5A, just advance with 1F. log.info( diff --git a/minimateplus/protocol.py b/minimateplus/protocol.py index 9a2041f..5e65924 100644 --- a/minimateplus/protocol.py +++ b/minimateplus/protocol.py @@ -520,7 +520,7 @@ class MiniMateProtocol: self._send(build_5a_frame(_BULK_CHUNK_OFFSET, params)) self._parser.reset() # reset bytes_fed for accurate per-chunk count try: - rsp = self._recv_one(expected_sub=rsp_sub, reset_parser=False) + rsp = self._recv_one(expected_sub=rsp_sub, reset_parser=False, timeout=10.0) except TimeoutError: raw = self._parser.bytes_fed log.warning( diff --git a/sfm/server.py b/sfm/server.py index 10c2a1d..5e9dd77 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -380,7 +380,7 @@ def device_event( def _do(): with _build_client(port, baud, host, tcp_port) as client: client.connect() - return client.get_events() + return client.get_events(stop_after_index=index) events = _run_with_retry(_do, is_tcp=_is_tcp(host)) except HTTPException: raise @@ -433,9 +433,8 @@ def device_event_waveform( def _do(): with _build_client(port, baud, host, tcp_port, timeout=120.0) as client: info = client.connect() - # full_waveform=True fetches the complete 5A stream inside the - # 1E→0A→0C→5A→1F loop. Issuing a second 5A after 1F times out. - events = client.get_events(full_waveform=True) + # stop_after_index avoids downloading events beyond the one requested. + events = client.get_events(full_waveform=True, stop_after_index=index) matching = [ev for ev in events if ev.index == index] return matching[0] if matching else None, info ev, info = _run_with_retry(_do, is_tcp=_is_tcp(host))