#!/usr/bin/env python3 """ makeRxList.py – CAN-RAW Dump → sortierte RX-Liste Entspricht dem AWK-Skript dump2RxListe.awk, mit aufgeräumter Datenbyte-Darstellung. Usage: python3 makeRxList.py [interface] [--min-changes N] [--sort-changes asc|desc] Optional: Interface-Filter, z.B. can0, vcan0 Ohne Angabe oder '*' werden alle Interfaces berücksichtigt. --min-changes N Nur IDs anzeigen, die sich mehr als N-mal geändert haben. --sort-changes asc Sortierung nach Änderungsanzahl aufsteigend. --sort-changes desc Sortierung nach Änderungsanzahl absteigend. Ohne diesen Parameter: Sortierung nach CAN-ID (Standard). Beispiele: python3 makeRxList.py candump.log can0 python3 makeRxList.py candump.log can0 --min-changes 10 python3 makeRxList.py candump.log can0 --sort-changes desc python3 makeRxList.py candump.log can0 --min-changes 5 --sort-changes asc Log-Format (can-utils candump): (timestamp) interface ID#DATA [R] z.B.: (1780451649.707000) can0 326#0198023401D000C8 R """ import sys import re import argparse from datetime import datetime # Matches: (timestamp) interface ID#DATA [optional trailing flags] LOG_LINE_RE = re.compile( r'^\s*\([0-9]+\.[0-9]+\)\s+' r'(?P\S+)\s+' r'(?P[0-9A-Fa-f]{3,8})#(?P[0-9A-Fa-f]*)' r'(?:\s+.*)?$' ) def format_data_bytes(hex_str: str) -> str: """ Stellt die Datenbytes lesbar dar (je Byte getrennt, auf 8 Bytes aufgefüllt). Leerer Payload (RTR) → '-- -- -- -- -- -- -- --' """ if not hex_str: return "-- -- -- -- -- -- -- --" pairs = [hex_str[i:i+2].upper() for i in range(0, len(hex_str), 2)] while len(pairs) < 8: pairs.append(" ") return " ".join(pairs[:8]) def format_ascii(hex_str: str) -> str: """ ASCII-Darstellung der Datenbytes (wie in einem Hex-Editor). Druckbare Zeichen (0x20–0x7E) werden angezeigt, alle anderen als '.'. Nur tatsächlich vorhandene Bytes werden angezeigt (kein Auffüllen), damit z.B. ein einzelnes 0x20 als ' ' erscheint und nicht als ' '. Leerer Payload (RTR) → '' (leerer String, Spalte bleibt frei). """ if not hex_str: return "--" chars = [] for i in range(0, min(len(hex_str), 16), 2): byte = int(hex_str[i:i+2], 16) chars.append(chr(byte) if 0x20 <= byte <= 0x7E else ".") return "".join(chars) def parse_log(filepath: str, iface_filter: str, min_changes: int = 0, sort_changes: str = None) -> None: filter_all = (iface_filter == "*") counts = {} # norm_id → Empfangsanzahl last_data = {} # norm_id → letzter Datenhex-String id_type = {} # norm_id → 'SFF' | 'EFF' data_changes = {} # norm_id → Anzahl Datenänderungen total_lines = 0 matched_lines = 0 with open(filepath, "r", encoding="utf-8", errors="replace") as f: for line in f: total_lines += 1 line = line.strip() if not line or line.startswith("#"): continue m = LOG_LINE_RE.match(line) if not m: continue matched_lines += 1 iface = m.group("iface") id_str = m.group("id").upper() data_str = m.group("data").upper() if not filter_all and iface != iface_filter: continue raw_id = int(id_str, 16) if raw_id <= 0x7FF: norm_id = f"{raw_id:03X}" frame_type = "SFF" else: norm_id = f"{raw_id:08X}" frame_type = "EFF" counts[norm_id] = counts.get(norm_id, 0) + 1 if norm_id not in last_data: data_changes[norm_id] = 0 elif last_data[norm_id] != data_str: data_changes[norm_id] = data_changes.get(norm_id, 0) + 1 last_data[norm_id] = data_str id_type[norm_id] = frame_type # --- Filter: nur IDs mit mehr als min_changes Änderungen --- all_ids = sorted(counts.keys()) filtered = [i for i in all_ids if data_changes.get(i, 0) > min_changes] \ if min_changes > 0 else all_ids # --- Sortierung --- if sort_changes == "asc": display_ids = sorted(filtered, key=lambda i: data_changes.get(i, 0)) elif sort_changes == "desc": display_ids = sorted(filtered, key=lambda i: data_changes.get(i, 0), reverse=True) else: display_ids = filtered # Standard: CAN-ID aufsteigend # --- Ausgabe --- now = datetime.now().strftime("%d.%m.%Y") iface_label = iface_filter if not filter_all else "alle" sort_label = {"asc": "Änd. aufsteigend", "desc": "Änd. absteigend"}.get(sort_changes, "CAN-ID aufsteigend") print("Konvertiere CAN-RAW Dump zur sortierten RX-Liste") print(f"Interface : {iface_label}") print(f"Sortierung : {sort_label}") if min_changes > 0: print(f"Filter : Änd. > {min_changes}") print() print(f"Start RX-Liste {now}") COL_DATA = 23 # "D0 D1 D2 D3 D4 D5 D6 D7" = 23 Zeichen sep = "─" * 77 print(sep) print(f" {'CAN-ID':<10} {'Typ':<4} {'D0 D1 D2 D3 D4 D5 D6 D7':<{COL_DATA}} {'ASCII':<10} {'Anz.':>7} {'Änd.':>7}") print(sep) for norm_id in display_ids: data_fmt = format_data_bytes(last_data[norm_id]) frame_type = id_type[norm_id] count = counts[norm_id] changes = data_changes.get(norm_id, 0) ascii_fmt = format_ascii(last_data[norm_id]) ascii_col = f"'{ascii_fmt}'" print(f" {norm_id:<10} {frame_type:<4} {data_fmt:<{COL_DATA}} {ascii_col:<10} {count:>7} {changes:>7}") print(sep) print("Ende RX-Liste\n") print(f"Anzahl CAN-IDs (gesamt) = {len(all_ids)}") print(f"Anzahl CAN-IDs (angezeigt) = {len(display_ids)}") print(f"Zeilen gesamt = {total_lines}") print(f"CAN-Frames = {matched_lines}") def main(): parser = argparse.ArgumentParser( description="CAN-RAW Dump → sortierte RX-Liste", formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument("logfile", help="CAN-Log-Datei (candump-Format)") parser.add_argument("interface", nargs="?", default="*", help="Interface-Filter, z.B. can0 (Default: alle)") parser.add_argument("--min-changes", type=int, default=0, metavar="N", help="Nur IDs anzeigen mit Änd. > N (Default: 0 = alle)") parser.add_argument("--sort-changes", choices=["asc", "desc"], default=None, metavar="asc|desc", help="Sortierung nach Änderungsanzahl:\n" " asc = aufsteigend\n" " desc = absteigend\n" " (ohne Angabe: nach CAN-ID)") args = parser.parse_args() try: parse_log(args.logfile, args.interface, min_changes=args.min_changes, sort_changes=args.sort_changes) except FileNotFoundError: print(f"Fehler: Datei nicht gefunden: {args.logfile}") sys.exit(1) except PermissionError: print(f"Fehler: Keine Leseberechtigung: {args.logfile}") sys.exit(1) if __name__ == "__main__": main()