diff --git a/minimateplus/protocol.py b/minimateplus/protocol.py index 578f11a..a578297 100644 --- a/minimateplus/protocol.py +++ b/minimateplus/protocol.py @@ -652,8 +652,14 @@ class MiniMateProtocol: # and primes the device to return a valid footer in the termination # response. Without it, termination returns an empty ack with no # footer bytes (confirmed 2026-04-23 from HxD comparison). - log.debug("5A A5[%d] metadata found — fetching %d more chunk(s) then stopping", - chunk_num, extra_chunks_after_metadata) + # Download extra chunks until we find the footer chunk (contains + # 0x0e 0x08 near its end) or hit the extra_chunks_after_metadata + # cap. The footer bytes are embedded in the last meaningful data + # chunk, NOT in the termination response. For short events (1-sec) + # the footer is in extra chunk 1. For longer events it is in a + # later chunk. Confirmed 2026-04-23 from BW file comparison. + log.debug("5A A5[%d] metadata found — fetching extra chunks until footer found", + chunk_num) for _extra_n in range(extra_chunks_after_metadata): chunk_num += 1 counter = chunk_num * _BULK_COUNTER_STEP @@ -661,13 +667,20 @@ class MiniMateProtocol: self._send(build_5a_frame(_BULK_CHUNK_OFFSET, params)) try: extra = self._recv_one(expected_sub=rsp_sub, timeout=10.0) - log.debug("5A A5[%d] extra chunk page_key=0x%04X data_len=%d", - chunk_num, extra.page_key, len(extra.data)) + log.debug("5A A5[%d] extra chunk page_key=0x%04X data_len=%d has_footer=%s", + chunk_num, extra.page_key, len(extra.data), + b'\x0e\x08' in extra.data[-50:]) if extra.page_key == 0x0000: if include_terminator: frames_data.append(extra) return frames_data frames_data.append(extra) + # Stop as soon as we find the footer marker in the tail + # of this chunk — the body is complete. + if b'\x0e\x08' in extra.data[-50:]: + log.debug("5A A5[%d] footer marker found — stopping extra chunks", + chunk_num) + break except TimeoutError: log.debug("5A extra chunk %d timed out — end of stream", _extra_n + 1) break diff --git a/sfm/server.py b/sfm/server.py index 56b4b5c..13d9c10 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -885,17 +885,17 @@ def device_event_blastware_file( def _do(): with _build_client(port, baud, host, tcp_port, timeout=120.0) as client: info = client.connect() - # Calculate extra ADC chunks to download after finding "Project:". - # BW downloads ~2 extra chunks per second of record time. - # Without enough extra chunks the termination response contains no - # footer bytes and Blastware rejects the file. + # Download extra chunks after "Project:" until the footer marker + # 0x0e 0x08 is detected in a chunk tail. The cap prevents + # accidentally downloading into post-event silence. + # Cap = rectime * 4 + 4 covers up to ~10 sec events safely. rectime = 1.0 try: rectime = float(info.compliance_config.record_time or 1.0) except (AttributeError, TypeError, ValueError): pass - extra_chunks = max(1, int(rectime)) - log.info("blastware_file: rectime=%.1fs → extra_chunks=%d", rectime, extra_chunks) + extra_chunks = max(4, int(rectime * 4) + 4) + log.info("blastware_file: rectime=%.1fs → extra_chunks_cap=%d", rectime, extra_chunks) events = client.get_events( full_waveform=False, stop_after_index=index,