diff --git a/minimateplus/blastware_file.py b/minimateplus/blastware_file.py index 79a1e1e..265d0ca 100644 --- a/minimateplus/blastware_file.py +++ b/minimateplus/blastware_file.py @@ -639,6 +639,15 @@ def write_blastware_file( strt = b"STRT" + b"\xff\xfe" + key4 + bytes(14) + bytes([rectime & 0xFF]) probe_skip = 7 + 21 + log.warning( + "write_blastware_file: strt_pos_stripped=%d probe_skip=%d " + "probe_data_len=%d strt_hex=%s", + strt_pos_stripped if strt_pos_stripped >= 0 else -1, + probe_skip, + len(a5_frames[0].data), + strt.hex() if len(strt) >= 4 else "(short)", + ) + if len(strt) != 21: raise ValueError(f"STRT record must be 21 bytes, got {len(strt)}") @@ -701,13 +710,30 @@ def write_blastware_file( # truncated file that Blastware rejects. skip = 13 if fi == 1 else 12 - all_bytes.extend(_frame_body_bytes(frame, skip)) + contribution = _frame_body_bytes(frame, skip) + log.warning("write_blastware_file: fi=%d skip=%d raw_data=%d contribution=%d", + fi, skip, len(frame.data), len(contribution)) + all_bytes.extend(contribution) # Terminator contributes its content, which ends with the 26-byte footer. # skip=11 (not 12) because the terminator's inner frame header is 4 bytes, # one shorter than chunk frames' 5-byte inner header. Confirmed 2026-04-21. if term_frame is not None: - all_bytes.extend(_frame_body_bytes(term_frame, 11)) + term_contribution = _frame_body_bytes(term_frame, 11) + log.warning( + "write_blastware_file: term_frame data_len=%d skip=11 " + "contribution_len=%d first8=%s", + len(term_frame.data), + len(term_contribution), + term_contribution[:8].hex() if len(term_contribution) >= 8 else term_contribution.hex(), + ) + all_bytes.extend(term_contribution) + + log.warning( + "write_blastware_file: all_bytes total=%d last28=%s", + len(all_bytes), + bytes(all_bytes[-28:]).hex() if len(all_bytes) >= 28 else bytes(all_bytes).hex(), + ) if len(all_bytes) >= 26: body = bytes(all_bytes[:-26]) diff --git a/sfm/server.py b/sfm/server.py index 97dd7b2..6f96f00 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -885,21 +885,22 @@ def device_event_blastware_file( def _do(): with _build_client(port, baud, host, tcp_port, timeout=120.0) as client: info = client.connect() - # Use stop_after_metadata=True (full_waveform=False) with 1 extra - # chunk after "Project:". For any record time, the pre-metadata - # section of the 5A stream naturally carries proportionally more - # ADC data for longer events — so "1 extra chunk" produces the - # correct body length regardless of record time. + # Use stop_after_metadata=True (full_waveform=False) with 0 extra + # chunks after "Project:". Confirmed from 4-26-26 BW RS-232 capture + # of "copy event to file" on a 2-sec Continuous event (key=01110000): + # BW sends the termination frame IMMEDIATELY after the chunk that + # contains "Project:" — no extra chunk is downloaded first. + # extra_chunks_after_metadata=1 was WRONG: it downloaded one additional + # chunk (counter = last_data_counter + 0x0400) adding ~1053 spurious + # bytes to the body, causing Blastware to reject the file. # # full_waveform=True (natural end-of-stream) downloads ALL chunks # including post-event silence (35+ chunks for a 9-sec event at # 1024 sps) — this produces 24KB+ files that Blastware rejects. - # Confirmed from file size comparison: BW 1-sec=4400B, BW 3-sec=8114B, - # per-second delta 1857 bytes — matches pre-metadata frame scaling. events = client.get_events( full_waveform=False, stop_after_index=index, - extra_chunks_after_metadata=1, + extra_chunks_after_metadata=0, ) matching = [ev for ev in events if ev.index == index] return matching[0] if matching else None, info