#!/usr/bin/env python3 # ----------------------------------------------------------------------------- # This file was fully generated by Microsoft Copilot (Smart mode). # # Copilot is an AI-based coding assistant designed to support software # development by generating code, explanations, and transformations based on # user instructions. This script contains no human-written logic. # # License: MIT # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # ----------------------------------------------------------------------------- # from __future__ import annotations import argparse, re, sys from typing import Iterable, Optional LINE_RE = re.compile(r"^\(?\s*(?P\d+\.\d+)\)?\s+(?P\S+)\s+(?P\S+)(?:\s+(?P.*))?$") # int64_t typePunSigned(uint64_t in, uint8_t len) # { # if (len == 0 || len >= 64) { # return 0; # } # # // mask # uint64_t mask = (1ULL << len) - 1ULL; # uint64_t value = in & mask; # # // sign # uint64_t signBit = 1ULL << (len - 1); # # // sign extension # if (value & signBit) { # value |= ~mask; # } # # return (int64_t)value; # } def typePunSigned(in_value: int, len_bits: int) -> int: if len_bits <= 0 or len_bits >= 64: raise ValueError("len_bits must be between 1 and 63") mask = (1 << len_bits) - 1 value = in_value & mask sign_bit = 1 << (len_bits - 1) if value & sign_bit: value -= (1 << len_bits) return value def parse_line(line: str): line = line.strip() if not line: return None m = LINE_RE.match(line) if not m: return None ts = m.group("ts") adapter = m.group("adapter") msg = m.group("msg") rest = m.group("rest") or "" if "#" in msg: msg_id, data = msg.split("#", 1) else: msg_id, data = msg, "" try: pid_val = int(msg_id, 16) & 0xFF pid_str = f"0x{pid_val:02X}" except: pid_str = "" data_clean = data.strip() if data_clean and len(data_clean) % 2 != 0: data_clean = "0" + data_clean bytes_list = [data_clean[i:i+2].upper() for i in range(0, len(data_clean), 2)] if data_clean else [] return { "timestamp": ts, "adapter": adapter, "id": msg_id, "data": data, "pid": pid_str, "bytes": bytes_list, "info": rest, "raw": line, } def parse_files(paths: Iterable[str], adapter_filter: Optional[str] = None): for path in paths: try: with open(path, "r", encoding="utf-8") as f: for lineno, line in enumerate(f, start=1): parsed = parse_line(line) if parsed and (not adapter_filter or parsed["adapter"] == adapter_filter): parsed["source"] = path parsed["lineno"] = lineno yield parsed except Exception as e: print(f"Error reading {path}: {e}", file=sys.stderr) def group_8bit(bitstring: str): """Group a bitstring into 8-bit chunks from left to right.""" return [bitstring[i:i+8] for i in range(0, len(bitstring), 8)] def main(argv=None): raw_argv = list(argv) if argv else sys.argv[1:] p = argparse.ArgumentParser(description="Parse LIN log files.") p.add_argument("file", nargs="+") p.add_argument("-p", "--pid") p.add_argument("-o", "--offset", type=int, default=0) p.add_argument("-l", "--length", type=int, default=0) args = p.parse_args(raw_argv) pid_filter = None if args.pid: try: pid_filter = f"0x{(int(args.pid, 16) & 0xFF):02X}" except: pid_filter = None rows = list(parse_files(args.file)) if pid_filter: off = args.offset length = args.length prepared = [] left_width = 0 mid_width = 0 dec_width = 0 signed_width = 0 # FIRST PASS for r in rows: if r.get("pid") != pid_filter: continue b = r["bytes"] if not b: continue b_le = list(reversed(b)) num_bytes = len(b_le) bitstream = "".join(f"{int(x,16):08b}" for x in b_le) # CASE 1: length == 0 if length == 0: left = f"{r['pid']}: {num_bytes}: {' '.join(b_le)}" mid = " ".join(group_8bit(bitstream)) sel_dec = int(bitstream, 2) sel_signed = typePunSigned(sel_dec, num_bytes * 8) left_width = max(left_width, len(left)) mid_width = max(mid_width, len(mid)) dec_width = max(dec_width, len(str(sel_dec))) signed_width = max(signed_width, len(str(sel_signed))) prepared.append((left, mid, sel_dec, sel_signed, num_bytes, False)) continue # CASE 2: length > 0 groups = [] idx = 0 selected_index = None if off > 0: g1 = bitstream[:off] groups.extend(group_8bit(g1)) idx = off selected_bits = "" if length > 0: g2 = bitstream[idx:idx+length] groups.append(g2) selected_bits = g2 selected_index = len(groups) - 1 idx += length if idx < len(bitstream): tail = bitstream[idx:] rem = [] while len(tail) > 8: rem.insert(0, tail[-8:]) tail = tail[:-8] if tail: groups.append(tail) groups.extend(rem) left = f"{r['pid']}: {num_bytes}: {' '.join(b_le)}" mid = " ".join( f"[{g}]" if gi == selected_index else g for gi, g in enumerate(groups) ) sel_dec = int(selected_bits, 2) if selected_bits else 0 sel_signed = typePunSigned(sel_dec, length) left_width = max(left_width, len(left)) mid_width = max(mid_width, len(mid)) dec_width = max(dec_width, len(str(sel_dec))) signed_width = max(signed_width, len(str(sel_signed))) prepared.append((left, mid, sel_dec, sel_signed, num_bytes, True)) # SECOND PASS for left, mid, sel_dec, sel_signed, num_bytes, has_selection in prepared: print( left.ljust(left_width), "->", mid.ljust(mid_width), "->", str(sel_dec).rjust(dec_width), "->", str(sel_signed).rjust(signed_width) ) # FOOTER footer_left = "(Little-endian)" footer_mid = "(MSB)" footer_dec = "(uint)" footer_signed = "(signed)" print( footer_left.ljust(left_width), "->", footer_mid.ljust(mid_width), "->", footer_dec.rjust(dec_width), "->", footer_signed.rjust(signed_width) ) if length == 0: return 0 total_bits = num_bytes * 8 lsb_offset = max(0, total_bits - off - length) shift = lsb_offset mask = (1 << length) - 1 if length > 0 else 0 print() print(f"(uint64 >> shift) & mask;") print(f"(uint64 >> {shift}u) & 0x{mask:X};") return 0 # Default summary for r in rows: print(f"{r['timestamp']} {r['adapter']} {r['id']}#{r['data']}") return 0 if __name__ == "__main__": raise SystemExit(main())