#!/usr/bin/env python3 """Summarize fixed-size hex frames from serial_sniff.py logs.""" from __future__ import annotations import argparse import collections import re from pathlib import Path FRAME_RE = re.compile(r"\b(?:(RX|TX)\s+)?frame\s+\d+\s+((?:[0-9A-Fa-f]{2}\s*)+)$") def frames_from_log(path: Path) -> list[tuple[str, str]]: frames: list[tuple[str, str]] = [] for line in path.read_text(encoding="utf-8").splitlines(): match = FRAME_RE.search(line.strip()) if match: direction = match.group(1) or "RX" frame = " ".join(match.group(2).upper().split()) frames.append((direction, frame)) return frames def checksum_note(frame: str) -> str: values = [int(part, 16) for part in frame.split()] if len(values) != 6: return "" checksum = 0x5A for value in values[:5]: checksum ^= value if checksum == values[5]: return " checksum ok" return f" checksum expected {checksum:02X}" def main() -> int: parser = argparse.ArgumentParser(description="Count frames in capture logs.") parser.add_argument("logs", nargs="+", type=Path) args = parser.parse_args() for log in args.logs: frames = frames_from_log(log) counts = collections.Counter(frames) print(f"{log}: {len(frames)} frames, {len(counts)} unique direction/frame pairs") for (direction, frame), count in counts.most_common(): print(f" {count:5d} {direction:<2} {frame}{checksum_note(frame)}") return 0 if __name__ == "__main__": raise SystemExit(main())