#!/usr/bin/env python3 """Decode a validated or raw TRK1 byte stream into CSV.""" import argparse import csv from pathlib import Path from trikke_protocol import ( CSV_COLUMNS, PACKET_TYPE_METADATA, Frame, IntegrityTracker, StreamParser, sample_to_csv_row, ) def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("input", type=Path) parser.add_argument("output", type=Path) args = parser.parse_args() stream = StreamParser() frames: list[Frame] = [] with args.input.open("rb") as source: while chunk := source.read(64 * 1024): frames.extend(stream.feed(chunk)) metadata = next( (frame.metadata for frame in frames if frame.packet_type == PACKET_TYPE_METADATA), None, ) if metadata is None: print( "No valid metadata frame found; " f"trailing_partial_bytes={stream.buffered_bytes}" ) return 2 args.output.parent.mkdir(parents=True, exist_ok=True) sample_count = 0 integrity = IntegrityTracker() with args.output.open("w", encoding="utf-8", newline="") as target: writer = csv.writer(target) writer.writerow(CSV_COLUMNS) for frame in frames: integrity.observe(frame) if frame.packet_type == PACKET_TYPE_METADATA: if frame.metadata is not None: metadata = frame.metadata continue for sample in frame.samples: writer.writerow( sample_to_csv_row(sample, metadata, frame.loop_overrun_count) ) sample_count += 1 print( f"Decoded {sample_count} samples from {len(frames)} frames; " f"startup_crc_rejects={stream.startup_crc_errors}, " f"stream_crc_errors={stream.crc_errors}, header_errors={stream.header_errors}, " f"skipped_nonframe_bytes={stream.skipped_bytes}, " f"packet_gaps={integrity.packet_gap_count}, " f"packet_resets={integrity.packet_reset_count}, " f"sample_gaps={integrity.sample_gap_count}, " f"sample_resets={integrity.sample_reset_count}, " f"timing_anomalies={integrity.timing_anomaly_count}, " "timestamp_saturation_frames=" f"{integrity.timestamp_saturation_frame_count}, " f"accel_stale={integrity.accel_stale_count}, " f"accel_overrun={integrity.accel_overrun_count}, " f"gyro_stale={integrity.gyro_stale_count}, " f"gyro_overrun={integrity.gyro_overrun_count}, " f"dropped={integrity.final_dropped_sample_count}, " f"acquisition_loop_overruns={integrity.final_loop_overrun_count}, " f"trailing_partial_bytes={stream.buffered_bytes}; saved {args.output}" ) return 0 if __name__ == "__main__": raise SystemExit(main())