fix(protocol): refine extra chunk fetching logic for accurate termination response
This commit is contained in:
+10
-22
@@ -652,13 +652,14 @@ class MiniMateProtocol:
|
|||||||
# and primes the device to return a valid footer in the termination
|
# and primes the device to return a valid footer in the termination
|
||||||
# response. Without it, termination returns an empty ack with no
|
# response. Without it, termination returns an empty ack with no
|
||||||
# footer bytes (confirmed 2026-04-23 from HxD comparison).
|
# footer bytes (confirmed 2026-04-23 from HxD comparison).
|
||||||
# Download extra chunks until we hit post-event silence (all-FF
|
# Download extra_chunks_after_metadata more chunks past the
|
||||||
# ADC values) or the cap. The device returns the footer in the
|
# metadata. The caller calculates this from record_time and
|
||||||
# termination response only when we stop at the right point —
|
# sample_rate so we download exactly the right amount of ADC
|
||||||
# right after the last real data chunk, before silence starts.
|
# data — no more, no less — before terminating.
|
||||||
# Silence detection: >80% of payload bytes are 0xFF.
|
# The device returns the footer in the termination response only
|
||||||
log.debug("5A A5[%d] metadata found — fetching extra chunks until silence",
|
# after the right amount of data has been consumed.
|
||||||
chunk_num)
|
log.debug("5A A5[%d] metadata found — fetching %d more chunk(s)",
|
||||||
|
chunk_num, extra_chunks_after_metadata)
|
||||||
for _extra_n in range(extra_chunks_after_metadata):
|
for _extra_n in range(extra_chunks_after_metadata):
|
||||||
chunk_num += 1
|
chunk_num += 1
|
||||||
counter = chunk_num * _BULK_COUNTER_STEP
|
counter = chunk_num * _BULK_COUNTER_STEP
|
||||||
@@ -666,25 +667,12 @@ class MiniMateProtocol:
|
|||||||
self._send(build_5a_frame(_BULK_CHUNK_OFFSET, params))
|
self._send(build_5a_frame(_BULK_CHUNK_OFFSET, params))
|
||||||
try:
|
try:
|
||||||
extra = self._recv_one(expected_sub=rsp_sub, timeout=10.0)
|
extra = self._recv_one(expected_sub=rsp_sub, timeout=10.0)
|
||||||
payload = extra.data[7:] # skip 7-byte frame header
|
log.debug("5A A5[%d] extra chunk page_key=0x%04X data_len=%d",
|
||||||
ff_ratio = payload.count(0xFF) / max(len(payload), 1)
|
chunk_num, extra.page_key, len(extra.data))
|
||||||
is_silence = ff_ratio > 0.8
|
|
||||||
log.debug(
|
|
||||||
"5A A5[%d] extra chunk page_key=0x%04X data_len=%d "
|
|
||||||
"ff_ratio=%.2f silence=%s",
|
|
||||||
chunk_num, extra.page_key, len(extra.data),
|
|
||||||
ff_ratio, is_silence,
|
|
||||||
)
|
|
||||||
if extra.page_key == 0x0000:
|
if extra.page_key == 0x0000:
|
||||||
if include_terminator:
|
if include_terminator:
|
||||||
frames_data.append(extra)
|
frames_data.append(extra)
|
||||||
return frames_data
|
return frames_data
|
||||||
if is_silence:
|
|
||||||
# Don't include the silence chunk — terminate here.
|
|
||||||
# The termination response will contain the footer.
|
|
||||||
log.debug("5A A5[%d] silence detected — stopping before this chunk",
|
|
||||||
chunk_num)
|
|
||||||
break
|
|
||||||
frames_data.append(extra)
|
frames_data.append(extra)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
log.debug("5A extra chunk %d timed out — end of stream", _extra_n + 1)
|
log.debug("5A extra chunk %d timed out — end of stream", _extra_n + 1)
|
||||||
|
|||||||
+9
-13
@@ -885,21 +885,17 @@ def device_event_blastware_file(
|
|||||||
def _do():
|
def _do():
|
||||||
with _build_client(port, baud, host, tcp_port, timeout=120.0) as client:
|
with _build_client(port, baud, host, tcp_port, timeout=120.0) as client:
|
||||||
info = client.connect()
|
info = client.connect()
|
||||||
# Download extra chunks after "Project:" until the footer marker
|
# Use full_waveform=True (stop_after_metadata=False) so the device
|
||||||
# 0x0e 0x08 is detected in a chunk tail. The cap prevents
|
# streams until it naturally signals end-of-stream (1-raw-byte signal).
|
||||||
# accidentally downloading into post-event silence.
|
# BW does the same — the ACH download and manual pull both let the device
|
||||||
# Cap = rectime * 4 + 4 covers up to ~10 sec events safely.
|
# determine when to stop. The termination response at that point contains
|
||||||
rectime = 1.0
|
# the correct 0e 08 footer with monitoring timestamps.
|
||||||
try:
|
# For Continuous/Single-Shot events, end-of-stream comes after the real
|
||||||
rectime = float(info.compliance_config.record_time or 1.0)
|
# ADC data (not after 35+ silence chunks as in Histogram+Continuous mode).
|
||||||
except (AttributeError, TypeError, ValueError):
|
# max_chunks=32 is a safety cap; natural end-of-stream stops much earlier.
|
||||||
pass
|
|
||||||
extra_chunks = max(4, int(rectime * 6) + 8) # generous cap; silence detection stops early
|
|
||||||
log.info("blastware_file: rectime=%.1fs → extra_chunks_cap=%d", rectime, extra_chunks)
|
|
||||||
events = client.get_events(
|
events = client.get_events(
|
||||||
full_waveform=False,
|
full_waveform=True,
|
||||||
stop_after_index=index,
|
stop_after_index=index,
|
||||||
extra_chunks_after_metadata=extra_chunks,
|
|
||||||
)
|
)
|
||||||
matching = [ev for ev in events if ev.index == index]
|
matching = [ev for ev in events if ev.index == index]
|
||||||
return matching[0] if matching else None, info
|
return matching[0] if matching else None, info
|
||||||
|
|||||||
Reference in New Issue
Block a user