1
0
Files
h8-536-decoder/h8536/serial_scenario_unexpected.py
2026-05-26 17:44:16 +10:00

137 lines
4.5 KiB
Python

from __future__ import annotations
import argparse
import re
import sys
from collections import Counter
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, TextIO
DETECT_RE = re.compile(
r"^(?P<time>\d\d:\d\d:\d\d\.\d{3})\s+DETECT\s+"
r"(?:(?P<label>\S+)\s+)?(?P<frame>(?:[0-9A-Fa-f]{2}\s*){6})\s*$"
)
DEFAULT_IGNORED_LABELS = {
"heartbeat",
"connect_ok_path_response_candidate",
"connect_c0_path_response_candidate",
"table_readback_candidate",
"gated_active_0004_response_candidate",
"gated_active_0004_transition_candidate",
}
KNOWN_FRAME_LABELS = {
"00 00 15 80 00 CF": "known_call_button_active_report",
"00 00 15 00 00 4F": "known_call_button_inactive_report",
"00 00 07 80 00 DD": "known_cam_power_button_report",
"01 00 04 00 00 5F": "gated_active_0004_response_candidate",
"02 00 04 00 00 5C": "gated_active_0004_transition_candidate",
}
@dataclass(frozen=True)
class DetectedFrame:
timestamp: str
label: str
frame: str
line_number: int
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Show unexpected DETECT frames from a serial_scenario capture log."
)
parser.add_argument("log", type=Path, help="serial_scenario capture log")
parser.add_argument(
"--include-refresh",
action="store_true",
help="include heartbeat, table-readback, and CONNECT OK refresh frames",
)
parser.add_argument(
"--show-all",
action="store_true",
help="print every matching DETECT frame after the summary",
)
return parser
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
args = build_arg_parser().parse_args(argv)
text = args.log.read_text(encoding="utf-8")
frames = parse_detected_frames(text.splitlines())
interesting = frames if args.include_refresh else filter_expected_refresh(frames)
print(format_report(frames, interesting, include_refresh=args.include_refresh, show_all=args.show_all), file=stdout)
return 0
def parse_detected_frames(lines: Iterable[str]) -> list[DetectedFrame]:
frames: list[DetectedFrame] = []
for line_number, line in enumerate(lines, start=1):
match = DETECT_RE.match(line.strip())
if not match:
continue
frame = " ".join(match.group("frame").upper().split())
label = KNOWN_FRAME_LABELS.get(frame, match.group("label") or "checksum_ok_unlabeled")
frames.append(
DetectedFrame(
timestamp=match.group("time"),
label=label,
frame=frame,
line_number=line_number,
)
)
return frames
def filter_expected_refresh(frames: Iterable[DetectedFrame]) -> list[DetectedFrame]:
return [frame for frame in frames if frame.label not in DEFAULT_IGNORED_LABELS]
def format_report(
frames: list[DetectedFrame],
interesting: list[DetectedFrame],
*,
include_refresh: bool,
show_all: bool,
) -> str:
lines = [
"Serial scenario unexpected-frame summary",
f"detected_frames={len(frames)} mode={'all' if include_refresh else 'unexpected-only'}",
]
label_counts = Counter(frame.label for frame in frames)
if label_counts:
lines.append("labels:")
for label, count in sorted(label_counts.items()):
lines.append(f" {label}: {count}")
ignored = len(frames) - len(interesting)
if not include_refresh:
lines.append(f"ignored_expected_refresh={ignored}")
lines.append(f"interesting_frames={len(interesting)}")
frame_counts = Counter((frame.frame, frame.label) for frame in interesting)
if frame_counts:
lines.append("interesting frame counts:")
for (frame_text, label), count in sorted(frame_counts.items(), key=lambda item: (-item[1], item[0][0])):
first = next(frame for frame in interesting if frame.frame == frame_text and frame.label == label)
lines.append(f" {count:4d}x {frame_text} label={label} first={first.timestamp} line={first.line_number}")
else:
lines.append("no unexpected checksum-valid frames found")
if show_all and interesting:
lines.append("timeline:")
for frame in interesting:
lines.append(f" {frame.timestamp} line={frame.line_number} {frame.frame} label={frame.label}")
return "\n".join(lines)
__all__ = [
"DetectedFrame",
"filter_expected_refresh",
"format_report",
"main",
"parse_detected_frames",
]