diff --git a/opendbc_repo/opendbc/car/tests/routes.py b/opendbc_repo/opendbc/car/tests/routes.py index 8f89e30..13d2e39 100644 --- a/opendbc_repo/opendbc/car/tests/routes.py +++ b/opendbc_repo/opendbc/car/tests/routes.py @@ -1,5 +1,6 @@ from typing import NamedTuple +from opendbc.car.byd.values import CAR as BYD from opendbc.car.chrysler.values import CAR as CHRYSLER from opendbc.car.gm.values import CAR as GM from opendbc.car.ford.values import CAR as FORD @@ -20,6 +21,8 @@ from opendbc.car.psa.values import CAR as PSA # FIXME: add routes for these cars non_tested_cars = [ MOCK.MOCK, + BYD.BYD_HAN_EV_23, + BYD.BYD_SEAL_PERFORMANCE_25, GM.CADILLAC_ATS, GM.HOLDEN_ASTRA, GM.CHEVROLET_MALIBU, diff --git a/tools/byd_can_dump.py b/tools/byd_can_dump.py new file mode 100755 index 0000000..fe63821 --- /dev/null +++ b/tools/byd_can_dump.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""BYD CAN bus dump — figures out which panda bus each BYD message is on. + +Usage (on the comma device, SSH'd in): + + # Mode 1 (safest): analyze the most recent recorded route — no CAN hardware needed + cd /data/openpilot + python3 tools/byd_can_dump.py + + # Mode 2: live sniff via panda (manager must be stopped first!) + tmux kill-server + python3 tools/byd_can_dump.py --live 15 + # then reboot + +Key BYD addresses it highlights: + 287 STEER_MODULE 289 ESC 482 MPC_LKAS_CMD_ANGLE + 508 STEER_TORQUE_ANGLE 544 BRAKE_APPLIED + 578 DRIVE_STATE 660 METER_CLUSTER 790 MPC_LKAS_CMD + 792 STEERING_TORQUE 813 ACC_HUD_ADAS + 814 ACC_CMD 834 PEDAL 944 PCM_BUTTONS +""" + +import argparse +import os +import time +from collections import defaultdict + +KEY_ADDRS = { + 287: "STEER_MODULE", + 289: "ESC", + 482: "MPC_LKAS_CMD_ANGLE", + 508: "STEERING_TORQUE_ANGLE", + 544: "BRAKE_APPLIED", + 578: "DRIVE_STATE", + 660: "METER_CLUSTER", + 790: "MPC_LKAS_CMD", + 792: "STEERING_TORQUE", + 813: "ACC_HUD_ADAS", + 814: "ACC_CMD", + 834: "PEDAL", + 944: "PCM_BUTTONS", +} + + +def print_table(stats, started_mono=None): + """stats: {(bus, addr): [len, count, first_t, last_t]}""" + buses = defaultdict(dict) + for (bus, addr), (length, count, first_t, last_t) in sorted(stats.items()): + buses[bus][addr] = (length, count, first_t, last_t) + + for bus in sorted(buses): + print(f"\n=== bus {bus} ({len(buses[bus])} addresses) ===") + print(f"{'addr':>6} {'len':>3} {'count':>7} {'rate_Hz':>7} name") + for addr, (length, count, first_t, last_t) in sorted(buses[bus].items()): + dt = last_t - first_t + rate = (count - 1) / dt if dt > 0 and count > 1 else 0.0 + name = KEY_ADDRS.get(addr, "") + marker = " <<<" if addr in KEY_ADDRS else "" + print(f"{addr:>6} {length:>3} {count:>7} {rate:>7.1f} {name}{marker}") + + +def dump_from_route(route_dir=None): + from openpilot.tools.lib.logreader import LogReader + + base = "/data/media/0/realdata" + if route_dir is None: + routes = sorted(d for d in os.listdir(base) if os.path.isdir(os.path.join(base, d))) + assert routes, f"no routes found in {base}" + route_dir = os.path.join(base, routes[-1]) + print(f"using latest route: {route_dir}") + + log_path = None + for name in ("rlog", "rlog.bz2", "rlog.zst", "qlog", "qlog.bz2", "qlog.zst"): + p = os.path.join(route_dir, name) + if os.path.exists(p): + log_path = p + break + assert log_path, f"no rlog/qlog found in {route_dir}" + print(f"reading {log_path} ...") + + stats = {} + n_frames = 0 + for m in LogReader(log_path): + if m.which() != "can": + continue + t = m.logMonoTime * 1e-9 + for f in m.can: + if f.src >= 128: + continue + key = (f.src, f.address) + if key not in stats: + stats[key] = [len(f.dat), 0, t, t] + s = stats[key] + s[1] += 1 + s[3] = t + n_frames += 1 + + print(f"{n_frames} CAN frames") + print_table(stats) + + +def dump_live(seconds): + from panda import Panda + from opendbc.car.structs import CarParams + + print("connecting to panda (manager must be stopped: tmux kill-server) ...") + p = Panda() + p.set_safety_mode(CarParams.SafetyModel.noOutput) + print(f"sniffing for {seconds}s ...") + + stats = {} + t_end = time.monotonic() + seconds + t0 = time.monotonic() + n_frames = 0 + while time.monotonic() < t_end: + for addr, dat, bus in p.can_recv(): + if bus >= 128: + continue + t = time.monotonic() - t0 + key = (bus, addr) + if key not in stats: + stats[key] = [len(dat), 0, t, t] + s = stats[key] + s[1] += 1 + s[3] = t + n_frames += 1 + + print(f"{n_frames} CAN frames") + print_table(stats) + + +if __name__ == "__main__": + ap = argparse.ArgumentParser() + ap.add_argument("--live", type=int, metavar="SECONDS", default=0, + help="sniff live via panda for N seconds (stop manager first!)") + ap.add_argument("--route", type=str, default=None, help="route dir to analyze instead of latest") + args = ap.parse_args() + + if args.live: + dump_live(args.live) + else: + dump_from_route(args.route)