From 5a389003c5940a53460a11a0a21ede140be81397 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:49:00 -0700 Subject: [PATCH 01/20] update() on init --- opendbc/car/body/interface.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opendbc/car/body/interface.py b/opendbc/car/body/interface.py index 24e571ee952..e8c79a885e5 100644 --- a/opendbc/car/body/interface.py +++ b/opendbc/car/body/interface.py @@ -4,12 +4,18 @@ from opendbc.car.body.carstate import CarState from opendbc.car.body.values import SPEED_FROM_RPM from opendbc.car.interfaces import CarInterfaceBase +from panda.board.body.v1.flash import update class CarInterface(CarInterfaceBase): CarState = CarState CarController = CarController + @staticmethod + def init(CP, can_recv, can_send, communication_control=None): + # run update script to check and update firmware + update() + @staticmethod def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams: ret.notCar = True From 13b280a5dd5f97ea6adfc58574c5b716c1c518b4 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:54:33 -0700 Subject: [PATCH 02/20] lazy import to fix ci --- opendbc/car/body/interface.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opendbc/car/body/interface.py b/opendbc/car/body/interface.py index e8c79a885e5..b4206c9ed74 100644 --- a/opendbc/car/body/interface.py +++ b/opendbc/car/body/interface.py @@ -4,7 +4,6 @@ from opendbc.car.body.carstate import CarState from opendbc.car.body.values import SPEED_FROM_RPM from opendbc.car.interfaces import CarInterfaceBase -from panda.board.body.v1.flash import update class CarInterface(CarInterfaceBase): @@ -13,7 +12,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, can_recv, can_send, communication_control=None): - # run update script to check and update firmware + from panda.board.body.v1.flash import update update() @staticmethod From c6df953478e45e4c82098fd51593f54f09cad972 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:11:33 -0700 Subject: [PATCH 03/20] move can flasher to standalone in opendbc --- opendbc/car/body/flash.py | 139 ++++++++++++++++++++++++++++++++++ opendbc/car/body/interface.py | 9 ++- 2 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 opendbc/car/body/flash.py diff --git a/opendbc/car/body/flash.py b/opendbc/car/body/flash.py new file mode 100644 index 00000000000..f99d765c12a --- /dev/null +++ b/opendbc/car/body/flash.py @@ -0,0 +1,139 @@ +import os +import time +import struct +import subprocess +from itertools import accumulate + +from uds import UdsClient, CanClient, IsoTpMessage, DATA_IDENTIFIER_TYPE, MessageTimeoutError + +REQUEST_IN = 0xC0 +REQUEST_OUT = 0x40 + +F4Config = { + "sector_sizes": [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)], +} + +class CanHandle(): + def __init__(self, can_send, can_recv, bus): + self.client = CanClient(can_send, can_recv, tx_addr=1, rx_addr=2, bus=bus) + + def transact(self, dat): + try: + msg = IsoTpMessage(self.client, timeout=2) + msg.send(dat) + ret, _ = msg.recv + return ret + except (MessageTimeoutError): raise + + def controlWrite(self, request_type, request, value, index, timeout=0): + return self.controlRead(request_type, request, value, index, 0, timeout) + + def controlRead(self, request_type, request, value, index, length): + dat = struct.pack("HHBBHHH", 0, 0, request_type, request, value, index, length) + return self.transact(dat) + + def bulkWrite(self, endpoint, data): + dat = struct.pack("HH", endpoint, len(data)) + data + return self.transact(dat) + + def bulkRead(self, endpoint): + dat = struct.pack("HH", endpoint, 0) + return self.transact(dat) + +def flush_recv_buffer(can_recv): + while(1): + if len(can_recv()) == 0: + break + + +def fetch_bin(bin_path, update_url): + result = subprocess.run( + ["curl", "-L", "-o", bin_path, "-w", "%{http_code}", "--silent", update_url], + capture_output=True, text=True + ) + status = result.stdout.strip() + if status == "200": + print("downloaded latest body firmware binary") + else: + raise RuntimeError(f"download failed with HTTP {status}") + + +def flash_can(handle, code, mcu_config): + assert mcu_config is not None, "must set valid mcu_type to flash" + + # confirm flasher is present + fr = handle.controlRead(REQUEST_IN, 0xb0, 0, 0, 0xc) + assert fr[4:8] == b"\xde\xad\xd0\x0d" + + apps_sectors_cumsum = accumulate(mcu_config["sector_sizes"][1:]) + last_sector = next((i + 1 for i, v in enumerate(apps_sectors_cumsum) if v > len(code)), -1) + assert last_sector >= 1, "Binary too small? No sector to erase." + assert last_sector < 7, "Binary too large! Risk of overwriting provisioning chunk." + + print("flash: unlocking") + handle.controlWrite(REQUEST_IN, 0xb1, 0, 0, b'') + + print(f"flash: erasing sectors 1 - {last_sector}") + for i in range(1, last_sector + 1): + handle.controlWrite(REQUEST_IN, 0xb2, i, 0, b'') + + STEP = 0x10 + print("flash: flashing") + for i in range(0, len(code), STEP): + handle.bulkWrite(2, code[i:i + STEP]) + + print("flash: resetting") + try: + handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) + except Exception: + pass + + +def update(bootloader_addr, file, update_url, can_send, can_recv, uds_base, uds_signature_offset): + if not os.path.exists(file): + print("local bin is not up-to-date, fetching latest") + fetch_bin(file, update_url) + + if uds_base and uds_signature_offset: + print("checking body firmware signature") + uds_client = UdsClient({can_send: can_send, can_recv: can_recv}, uds_base, uds_signature_offset, bus=bus, timeout=2) + + try: + current_signature = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) + except MessageTimeoutError: + current_signature = b"" + + with open(file, "rb") as f: + expected_signature = f.read()[-128:] + + print(f"expected body signature: {expected_signature.hex()}") + print(f"current body signature: {current_signature.hex()}") + + if not current_signature or current_signature != expected_signature: + print("flashing motherboard") + can_send(bootloader_addr, b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", 0) + time.sleep(0.1) + msgs = can_recv() + for ids, _, bus in msgs: + if ids == bootloader_addr and bus == 128: + raise RuntimeError("flash failed: device likely did not enter bootloader") + + print("flashing", file) + flush_recv_buffer(can_recv) + code = open(file, "rb").read() + handle = CanHandle(can_send, can_recv, 0) + retries = 3 + for i in range(retries): + try: + flash_can(handle, code, F4Config) + except (TimeoutError): + print(f"flash failed (attempt {i + 1}/{retries}), trying again...") + else: + print("successfully flashed") + return + + # on fail: attempt to exit bootloader + handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) + raise RuntimeError(f"flash failed after {retries} attempts") + else: + print("body firmware is up to date") diff --git a/opendbc/car/body/interface.py b/opendbc/car/body/interface.py index b4206c9ed74..566111491bc 100644 --- a/opendbc/car/body/interface.py +++ b/opendbc/car/body/interface.py @@ -1,10 +1,16 @@ +import os import math from opendbc.car import get_safety_config, structs from opendbc.car.body.carcontroller import CarController from opendbc.car.body.carstate import CarState from opendbc.car.body.values import SPEED_FROM_RPM from opendbc.car.interfaces import CarInterfaceBase +from opendbc.car.body.flash import update +from opendbc.car.common.basedir import BASEDIR +FIRMWARE_VERSION = "v0.3.1" +BIN_URL = f"https://github.com/commaai/body/releases/download/{FIRMWARE_VERSION}/body.bin.signed" +BIN_NAME = f"body-v1-{FIRMWARE_VERSION}.bin.signed" class CarInterface(CarInterfaceBase): CarState = CarState @@ -12,8 +18,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, can_recv, can_send, communication_control=None): - from panda.board.body.v1.flash import update - update() + update(0x250, os.path.join(BASEDIR, "../", f"panda/board/obj/{BIN_NAME}"), BIN_URL, can_recv, can_send, 0x720, 0x728) @staticmethod def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams: From bc20c8ab5a71e54c71b07adf30aa205e73b6b807 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:34:32 -0700 Subject: [PATCH 04/20] fix flash script, allow UDS querys through body safety --- opendbc/car/body/flash.py | 143 +++++++++++++++++++++------------- opendbc/car/body/interface.py | 5 +- opendbc/safety/modes/body.h | 9 ++- 3 files changed, 99 insertions(+), 58 deletions(-) diff --git a/opendbc/car/body/flash.py b/opendbc/car/body/flash.py index f99d765c12a..98618364a80 100644 --- a/opendbc/car/body/flash.py +++ b/opendbc/car/body/flash.py @@ -4,41 +4,69 @@ import subprocess from itertools import accumulate -from uds import UdsClient, CanClient, IsoTpMessage, DATA_IDENTIFIER_TYPE, MessageTimeoutError +from opendbc.car.can_definitions import CanData +from opendbc.car.uds import UdsClient, CanClient, IsoTpMessage, DATA_IDENTIFIER_TYPE, MessageTimeoutError REQUEST_IN = 0xC0 REQUEST_OUT = 0x40 +DEFAULT_ISOTP_TIMEOUT = 2 F4Config = { "sector_sizes": [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)], } + +class CanCallbacks: + def __init__(self, can_send, can_recv): + self._can_send_cb = can_send + self._can_recv_cb = can_recv + + def can_send(self, addr: int, dat: bytes, bus: int, timeout: int = 0): + del timeout + self._can_send_cb([CanData(address=addr, dat=dat, src=bus)]) + + def can_recv(self): + return [(msg.address, msg.dat, msg.src) for packet in self._can_recv_cb() for msg in packet] + + class CanHandle(): def __init__(self, can_send, can_recv, bus): - self.client = CanClient(can_send, can_recv, tx_addr=1, rx_addr=2, bus=bus) + callbacks = CanCallbacks(can_send, can_recv) + self.client = CanClient(callbacks.can_send, callbacks.can_recv, tx_addr=1, rx_addr=2, bus=bus) - def transact(self, dat): + def transact(self, dat, timeout=DEFAULT_ISOTP_TIMEOUT, expect_disconnect=False): try: - msg = IsoTpMessage(self.client, timeout=2) + msg = IsoTpMessage(self.client, timeout=timeout) msg.send(dat) - ret, _ = msg.recv + if expect_disconnect: + deadline = time.monotonic() + timeout + while not msg.tx_done: + msg.recv(timeout=0) + if not msg.tx_done and time.monotonic() > deadline: + raise MessageTimeoutError("timeout waiting for flow control") + time.sleep(0.01) + return b"" + ret, _ = msg.recv() return ret - except (MessageTimeoutError): raise + except MessageTimeoutError as e: + raise TimeoutError from e - def controlWrite(self, request_type, request, value, index, timeout=0): - return self.controlRead(request_type, request, value, index, 0, timeout) + def controlWrite(self, request_type, request, value, index, data, timeout=DEFAULT_ISOTP_TIMEOUT, expect_disconnect=False): + dat = struct.pack("HHBBHHH", 0, 0, request_type, request, value, index, 0) + return self.transact(dat, timeout=timeout, expect_disconnect=expect_disconnect) - def controlRead(self, request_type, request, value, index, length): + def controlRead(self, request_type, request, value, index, length, timeout=DEFAULT_ISOTP_TIMEOUT): dat = struct.pack("HHBBHHH", 0, 0, request_type, request, value, index, length) - return self.transact(dat) + return self.transact(dat, timeout=timeout) - def bulkWrite(self, endpoint, data): + def bulkWrite(self, endpoint, data, timeout=DEFAULT_ISOTP_TIMEOUT): dat = struct.pack("HH", endpoint, len(data)) + data - return self.transact(dat) + return self.transact(dat, timeout=timeout) - def bulkRead(self, endpoint): + def bulkRead(self, endpoint, timeout=DEFAULT_ISOTP_TIMEOUT): dat = struct.pack("HH", endpoint, 0) - return self.transact(dat) + return self.transact(dat, timeout=timeout) + def flush_recv_buffer(can_recv): while(1): @@ -47,6 +75,7 @@ def flush_recv_buffer(can_recv): def fetch_bin(bin_path, update_url): + os.makedirs(os.path.dirname(bin_path), exist_ok=True) result = subprocess.run( ["curl", "-L", "-o", bin_path, "-w", "%{http_code}", "--silent", update_url], capture_output=True, text=True @@ -58,50 +87,56 @@ def fetch_bin(bin_path, update_url): raise RuntimeError(f"download failed with HTTP {status}") +def get_firmware_signature(can_send, can_recv, uds_base, uds_signature_offset, uds_bus, timeout=DEFAULT_ISOTP_TIMEOUT): + callbacks = CanCallbacks(can_send, can_recv) + uds_client = UdsClient(callbacks, uds_base, uds_signature_offset, bus=uds_bus, timeout=timeout) + + try: + return uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) + except MessageTimeoutError: + return b"" + + def flash_can(handle, code, mcu_config): - assert mcu_config is not None, "must set valid mcu_type to flash" + assert mcu_config is not None, "must set valid mcu_type to flash" - # confirm flasher is present - fr = handle.controlRead(REQUEST_IN, 0xb0, 0, 0, 0xc) - assert fr[4:8] == b"\xde\xad\xd0\x0d" + # confirm flasher is present + fr = handle.controlRead(REQUEST_IN, 0xb0, 0, 0, 0xc) + assert fr[4:8] == b"\xde\xad\xd0\x0d" - apps_sectors_cumsum = accumulate(mcu_config["sector_sizes"][1:]) - last_sector = next((i + 1 for i, v in enumerate(apps_sectors_cumsum) if v > len(code)), -1) - assert last_sector >= 1, "Binary too small? No sector to erase." - assert last_sector < 7, "Binary too large! Risk of overwriting provisioning chunk." + apps_sectors_cumsum = accumulate(mcu_config["sector_sizes"][1:]) + last_sector = next((i + 1 for i, v in enumerate(apps_sectors_cumsum) if v > len(code)), -1) + assert last_sector >= 1, "Binary too small? No sector to erase." + assert last_sector < 7, "Binary too large! Risk of overwriting provisioning chunk." - print("flash: unlocking") - handle.controlWrite(REQUEST_IN, 0xb1, 0, 0, b'') + print("flash: unlocking") + handle.controlWrite(REQUEST_IN, 0xb1, 0, 0, b'') - print(f"flash: erasing sectors 1 - {last_sector}") - for i in range(1, last_sector + 1): - handle.controlWrite(REQUEST_IN, 0xb2, i, 0, b'') + print(f"flash: erasing sectors 1 - {last_sector}") + for i in range(1, last_sector + 1): + handle.controlWrite(REQUEST_IN, 0xb2, i, 0, b'') - STEP = 0x10 - print("flash: flashing") - for i in range(0, len(code), STEP): - handle.bulkWrite(2, code[i:i + STEP]) + STEP = 0x10 + print("flash: flashing") + for i in range(0, len(code), STEP): + handle.bulkWrite(2, code[i:i + STEP]) - print("flash: resetting") - try: - handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) - except Exception: - pass + +def reset_body(handle): + print("flash: resetting") + handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) -def update(bootloader_addr, file, update_url, can_send, can_recv, uds_base, uds_signature_offset): +def update(bootloader_addr, bootloader_bus, file, update_url, can_send, can_recv, uds_base, uds_signature_offset, uds_bus): if not os.path.exists(file): print("local bin is not up-to-date, fetching latest") fetch_bin(file, update_url) - if uds_base and uds_signature_offset: + current_signature = None + expected_signature = None + if None not in (uds_base, uds_signature_offset, uds_bus): print("checking body firmware signature") - uds_client = UdsClient({can_send: can_send, can_recv: can_recv}, uds_base, uds_signature_offset, bus=bus, timeout=2) - - try: - current_signature = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) - except MessageTimeoutError: - current_signature = b"" + current_signature = get_firmware_signature(can_send, can_recv, uds_base, uds_signature_offset, uds_bus) with open(file, "rb") as f: expected_signature = f.read()[-128:] @@ -109,31 +144,29 @@ def update(bootloader_addr, file, update_url, can_send, can_recv, uds_base, uds_ print(f"expected body signature: {expected_signature.hex()}") print(f"current body signature: {current_signature.hex()}") - if not current_signature or current_signature != expected_signature: + if current_signature is None or current_signature != expected_signature: print("flashing motherboard") - can_send(bootloader_addr, b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", 0) + can_send([CanData(address=bootloader_addr, dat=b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", src=bootloader_bus)]) time.sleep(0.1) - msgs = can_recv() - for ids, _, bus in msgs: - if ids == bootloader_addr and bus == 128: - raise RuntimeError("flash failed: device likely did not enter bootloader") print("flashing", file) flush_recv_buffer(can_recv) - code = open(file, "rb").read() - handle = CanHandle(can_send, can_recv, 0) + with open(file, "rb") as f: + code = f.read() + handle = CanHandle(can_send, can_recv, bootloader_bus) retries = 3 for i in range(retries): try: flash_can(handle, code, F4Config) - except (TimeoutError): - print(f"flash failed (attempt {i + 1}/{retries}), trying again...") + reset_body(handle) + except (TimeoutError, RuntimeError) as e: + print(f"flash failed (attempt {i + 1}/{retries}): {e}, trying again...") else: print("successfully flashed") return # on fail: attempt to exit bootloader - handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) + reset_body(handle) raise RuntimeError(f"flash failed after {retries} attempts") else: print("body firmware is up to date") diff --git a/opendbc/car/body/interface.py b/opendbc/car/body/interface.py index 566111491bc..2c419000388 100644 --- a/opendbc/car/body/interface.py +++ b/opendbc/car/body/interface.py @@ -6,11 +6,12 @@ from opendbc.car.body.values import SPEED_FROM_RPM from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.body.flash import update -from opendbc.car.common.basedir import BASEDIR FIRMWARE_VERSION = "v0.3.1" BIN_URL = f"https://github.com/commaai/body/releases/download/{FIRMWARE_VERSION}/body.bin.signed" BIN_NAME = f"body-v1-{FIRMWARE_VERSION}.bin.signed" +BODY_DIR = os.path.dirname(os.path.realpath(__file__)) +BIN_PATH = os.path.join(BODY_DIR, BIN_NAME) class CarInterface(CarInterfaceBase): CarState = CarState @@ -18,7 +19,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, can_recv, can_send, communication_control=None): - update(0x250, os.path.join(BASEDIR, "../", f"panda/board/obj/{BIN_NAME}"), BIN_URL, can_recv, can_send, 0x720, 0x728) + update(0x250, 0, BIN_PATH, BIN_URL, can_send, can_recv, 0x720, 0x728, 0) @staticmethod def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams: diff --git a/opendbc/safety/modes/body.h b/opendbc/safety/modes/body.h index e3d5c212ca0..190cd507aee 100644 --- a/opendbc/safety/modes/body.h +++ b/opendbc/safety/modes/body.h @@ -21,6 +21,12 @@ static bool body_tx_hook(const CANPacket_t *msg) { tx = true; } + // Allow UDS firmware queries through for fingerprinting + bool uds_msg = (msg->addr == 0x720U); + if (!controls_allowed && uds_msg) { + tx = true; + } + return tx; } @@ -30,7 +36,8 @@ static safety_config body_init(uint16_t param) { }; static const CanMsg BODY_TX_MSGS[] = {{0x250, 0, 8, .check_relay = false}, {0x250, 0, 6, .check_relay = false}, {0x251, 0, 5, .check_relay = false}, // body - {0x1, 0, 8, .check_relay = false}}; // CAN flasher + {0x1, 0, 8, .check_relay = false}, // CAN flasher + {0x720, 0, 8, .check_relay = false}}; // UDS firmware query SAFETY_UNUSED(param); safety_config ret = BUILD_SAFETY_CFG(body_rx_checks, BODY_TX_MSGS); From f200718748415f2c77f0553bca85a4fced353f50 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:57:55 -0700 Subject: [PATCH 05/20] move constants to body values --- opendbc/car/body/flash.py | 10 +++++----- opendbc/car/body/interface.py | 11 ++--------- opendbc/car/body/values.py | 10 ++++++++++ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/opendbc/car/body/flash.py b/opendbc/car/body/flash.py index 98618364a80..f33237293b6 100644 --- a/opendbc/car/body/flash.py +++ b/opendbc/car/body/flash.py @@ -87,9 +87,9 @@ def fetch_bin(bin_path, update_url): raise RuntimeError(f"download failed with HTTP {status}") -def get_firmware_signature(can_send, can_recv, uds_base, uds_signature_offset, uds_bus, timeout=DEFAULT_ISOTP_TIMEOUT): +def get_firmware_signature(can_send, can_recv, uds_tx, uds_rx, uds_bus, timeout=DEFAULT_ISOTP_TIMEOUT): callbacks = CanCallbacks(can_send, can_recv) - uds_client = UdsClient(callbacks, uds_base, uds_signature_offset, bus=uds_bus, timeout=timeout) + uds_client = UdsClient(callbacks, uds_tx, uds_rx, bus=uds_bus, timeout=timeout) try: return uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) @@ -127,16 +127,16 @@ def reset_body(handle): handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) -def update(bootloader_addr, bootloader_bus, file, update_url, can_send, can_recv, uds_base, uds_signature_offset, uds_bus): +def update(bootloader_addr, bootloader_bus, file, update_url, can_send, can_recv, uds_tx, uds_rx, uds_bus): if not os.path.exists(file): print("local bin is not up-to-date, fetching latest") fetch_bin(file, update_url) current_signature = None expected_signature = None - if None not in (uds_base, uds_signature_offset, uds_bus): + if None not in (uds_tx, uds_rx, uds_bus): print("checking body firmware signature") - current_signature = get_firmware_signature(can_send, can_recv, uds_base, uds_signature_offset, uds_bus) + current_signature = get_firmware_signature(can_send, can_recv, uds_tx, uds_rx, uds_bus) with open(file, "rb") as f: expected_signature = f.read()[-128:] diff --git a/opendbc/car/body/interface.py b/opendbc/car/body/interface.py index 2c419000388..af6ae7cbc3e 100644 --- a/opendbc/car/body/interface.py +++ b/opendbc/car/body/interface.py @@ -1,25 +1,18 @@ -import os import math from opendbc.car import get_safety_config, structs from opendbc.car.body.carcontroller import CarController from opendbc.car.body.carstate import CarState -from opendbc.car.body.values import SPEED_FROM_RPM +from opendbc.car.body.values import SPEED_FROM_RPM, BIN_PATH, BIN_URL, FLASH_ADDR, UDS_TX, UDS_RX, BUS from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.body.flash import update -FIRMWARE_VERSION = "v0.3.1" -BIN_URL = f"https://github.com/commaai/body/releases/download/{FIRMWARE_VERSION}/body.bin.signed" -BIN_NAME = f"body-v1-{FIRMWARE_VERSION}.bin.signed" -BODY_DIR = os.path.dirname(os.path.realpath(__file__)) -BIN_PATH = os.path.join(BODY_DIR, BIN_NAME) - class CarInterface(CarInterfaceBase): CarState = CarState CarController = CarController @staticmethod def init(CP, can_recv, can_send, communication_control=None): - update(0x250, 0, BIN_PATH, BIN_URL, can_send, can_recv, 0x720, 0x728, 0) + update(FLASH_ADDR, BUS, BIN_PATH, BIN_URL, can_send, can_recv, UDS_TX, UDS_RX, BUS) @staticmethod def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams: diff --git a/opendbc/car/body/values.py b/opendbc/car/body/values.py index 27490aba4f8..4da93199ad1 100644 --- a/opendbc/car/body/values.py +++ b/opendbc/car/body/values.py @@ -1,3 +1,4 @@ +import os from opendbc.car import Bus, CarSpecs, PlatformConfig, Platforms from opendbc.car.structs import CarParams from opendbc.car.docs_definitions import CarDocs @@ -7,6 +8,15 @@ SPEED_FROM_RPM = 0.008587 +FLASH_ADDR = 0x250 +UDS_TX = 0x720 +UDS_RX = 0x728 +BUS = 0 +FIRMWARE_VERSION = "v0.3.1" +BIN_URL = f"https://github.com/commaai/body/releases/download/{FIRMWARE_VERSION}/body.bin.signed" +BIN_NAME = f"body-v1-{FIRMWARE_VERSION}.bin.signed" +BODY_DIR = os.path.dirname(os.path.realpath(__file__)) +BIN_PATH = os.path.join(BODY_DIR, BIN_NAME) class CarControllerParams: ANGLE_DELTA_BP = [0., 5., 15.] From 45cee7bccbd259a2f70f3007e01e9853a23ae184 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:51:44 -0700 Subject: [PATCH 06/20] fix linter and tests --- opendbc/car/body/flash.py | 4 ++-- opendbc/safety/tests/common.py | 5 +++++ opendbc/safety/tests/test_body.py | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/opendbc/car/body/flash.py b/opendbc/car/body/flash.py index f33237293b6..7ff010e7640 100644 --- a/opendbc/car/body/flash.py +++ b/opendbc/car/body/flash.py @@ -29,7 +29,7 @@ def can_recv(self): return [(msg.address, msg.dat, msg.src) for packet in self._can_recv_cb() for msg in packet] -class CanHandle(): +class CanHandle: def __init__(self, can_send, can_recv, bus): callbacks = CanCallbacks(can_send, can_recv) self.client = CanClient(callbacks.can_send, callbacks.can_recv, tx_addr=1, rx_addr=2, bus=bus) @@ -69,7 +69,7 @@ def bulkRead(self, endpoint, timeout=DEFAULT_ISOTP_TIMEOUT): def flush_recv_buffer(can_recv): - while(1): + while (1): if len(can_recv()) == 0: break diff --git a/opendbc/safety/tests/common.py b/opendbc/safety/tests/common.py index 267736cfac6..1f3661af869 100644 --- a/opendbc/safety/tests/common.py +++ b/opendbc/safety/tests/common.py @@ -937,6 +937,11 @@ def test_tx_hook_on_wrong_safety_mode(self): if attr.startswith('TestHyundaiLongitudinal'): # exceptions for common msgs across different Hyundai CAN platforms tx = list(filter(lambda m: m[0] not in [0x420, 0x50A, 0x389, 0x4A2], tx)) + + # Body allows UDS firmware query, overlaps with ELM327/diagnostic modes + if current_test.startswith('TestBody'): + tx = list(filter(lambda m: m[0] not in [0x720, ], tx)) + all_tx.append([[m[0], m[1], attr] for m in tx]) # make sure we got all the msgs diff --git a/opendbc/safety/tests/test_body.py b/opendbc/safety/tests/test_body.py index ae98712ce45..7b9b69a70d9 100755 --- a/opendbc/safety/tests/test_body.py +++ b/opendbc/safety/tests/test_body.py @@ -9,7 +9,8 @@ class TestBody(common.SafetyTest): TX_MSGS = [[0x250, 0], [0x251, 0], - [0x1, 0], [0x1, 1], [0x1, 2], [0x1, 3]] + [0x1, 0], [0x1, 1], [0x1, 2], [0x1, 3], + [0x720, 0]] FWD_BUS_LOOKUP = {} def setUp(self): @@ -55,6 +56,5 @@ def test_can_flasher(self): self.assertFalse(self._tx(common.make_msg(0, 0x250, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0'))) # not correct data/len self.assertFalse(self._tx(common.make_msg(0, 0x251, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0\x0a'))) # wrong address - if __name__ == "__main__": unittest.main() From 5c1162fb9c29438d7c81370dbd7a934365df7de2 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:58:31 -0700 Subject: [PATCH 07/20] add uds test --- opendbc/safety/tests/test_body.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opendbc/safety/tests/test_body.py b/opendbc/safety/tests/test_body.py index 7b9b69a70d9..1c6927d245a 100755 --- a/opendbc/safety/tests/test_body.py +++ b/opendbc/safety/tests/test_body.py @@ -56,5 +56,10 @@ def test_can_flasher(self): self.assertFalse(self._tx(common.make_msg(0, 0x250, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0'))) # not correct data/len self.assertFalse(self._tx(common.make_msg(0, 0x251, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0\x0a'))) # wrong address + def test_uds_firmware_query(self): + # UDS firmware query allowed without controls + self.safety.set_controls_allowed(False) + self.assertTrue(self._tx(common.make_msg(0, 0x720, 8))) + if __name__ == "__main__": unittest.main() From eb80c88d28bb04c01f393c7df5fe4e22a9b93c3a Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:24:58 -0700 Subject: [PATCH 08/20] update body v1 fingerprint --- opendbc/car/body/fingerprints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc/car/body/fingerprints.py b/opendbc/car/body/fingerprints.py index 5806112fb15..f5669418a2a 100644 --- a/opendbc/car/body/fingerprints.py +++ b/opendbc/car/body/fingerprints.py @@ -9,7 +9,7 @@ FINGERPRINTS = { CAR.COMMA_BODY: [{ - 513: 8, 516: 8, 514: 3, 515: 4 + 513: 8, 516: 8, 514: 3, 515: 4, 517: 8, 518: 6, 519: 6 }], } From 4235675d3fd1f98b8ec5110ccaa271e71fdda936 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:29:07 -0700 Subject: [PATCH 09/20] rearrange values --- opendbc/car/body/values.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/opendbc/car/body/values.py b/opendbc/car/body/values.py index 4da93199ad1..13c110c7535 100644 --- a/opendbc/car/body/values.py +++ b/opendbc/car/body/values.py @@ -8,15 +8,15 @@ SPEED_FROM_RPM = 0.008587 -FLASH_ADDR = 0x250 -UDS_TX = 0x720 -UDS_RX = 0x728 -BUS = 0 FIRMWARE_VERSION = "v0.3.1" BIN_URL = f"https://github.com/commaai/body/releases/download/{FIRMWARE_VERSION}/body.bin.signed" BIN_NAME = f"body-v1-{FIRMWARE_VERSION}.bin.signed" BODY_DIR = os.path.dirname(os.path.realpath(__file__)) BIN_PATH = os.path.join(BODY_DIR, BIN_NAME) +FLASH_ADDR = 0x250 +UDS_TX = 0x720 +UDS_RX = 0x728 +BUS = 0 class CarControllerParams: ANGLE_DELTA_BP = [0., 5., 15.] From 9670bdc9d12f41225d9209e05dbb5878f4565aea Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:49:19 -0700 Subject: [PATCH 10/20] refactor to use existing uds firmware query route --- opendbc/car/body/flash.py | 24 +++++------------------- opendbc/car/body/interface.py | 11 +++++++++-- opendbc/car/body/values.py | 6 +++--- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/opendbc/car/body/flash.py b/opendbc/car/body/flash.py index 7ff010e7640..ce03a79ec37 100644 --- a/opendbc/car/body/flash.py +++ b/opendbc/car/body/flash.py @@ -5,7 +5,7 @@ from itertools import accumulate from opendbc.car.can_definitions import CanData -from opendbc.car.uds import UdsClient, CanClient, IsoTpMessage, DATA_IDENTIFIER_TYPE, MessageTimeoutError +from opendbc.car.uds import CanClient, IsoTpMessage, MessageTimeoutError REQUEST_IN = 0xC0 REQUEST_OUT = 0x40 @@ -87,16 +87,6 @@ def fetch_bin(bin_path, update_url): raise RuntimeError(f"download failed with HTTP {status}") -def get_firmware_signature(can_send, can_recv, uds_tx, uds_rx, uds_bus, timeout=DEFAULT_ISOTP_TIMEOUT): - callbacks = CanCallbacks(can_send, can_recv) - uds_client = UdsClient(callbacks, uds_tx, uds_rx, bus=uds_bus, timeout=timeout) - - try: - return uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) - except MessageTimeoutError: - return b"" - - def flash_can(handle, code, mcu_config): assert mcu_config is not None, "must set valid mcu_type to flash" @@ -127,17 +117,13 @@ def reset_body(handle): handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) -def update(bootloader_addr, bootloader_bus, file, update_url, can_send, can_recv, uds_tx, uds_rx, uds_bus): +def update(can_send, can_recv, addr, bus, file, update_url, current_signature): if not os.path.exists(file): print("local bin is not up-to-date, fetching latest") fetch_bin(file, update_url) - current_signature = None - expected_signature = None - if None not in (uds_tx, uds_rx, uds_bus): + if current_signature is not None: print("checking body firmware signature") - current_signature = get_firmware_signature(can_send, can_recv, uds_tx, uds_rx, uds_bus) - with open(file, "rb") as f: expected_signature = f.read()[-128:] @@ -146,14 +132,14 @@ def update(bootloader_addr, bootloader_bus, file, update_url, can_send, can_recv if current_signature is None or current_signature != expected_signature: print("flashing motherboard") - can_send([CanData(address=bootloader_addr, dat=b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", src=bootloader_bus)]) + can_send([CanData(address=addr, dat=b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", src=bus)]) time.sleep(0.1) print("flashing", file) flush_recv_buffer(can_recv) with open(file, "rb") as f: code = f.read() - handle = CanHandle(can_send, can_recv, bootloader_bus) + handle = CanHandle(can_send, can_recv, bus) retries = 3 for i in range(retries): try: diff --git a/opendbc/car/body/interface.py b/opendbc/car/body/interface.py index af6ae7cbc3e..2d1ca89d454 100644 --- a/opendbc/car/body/interface.py +++ b/opendbc/car/body/interface.py @@ -2,9 +2,11 @@ from opendbc.car import get_safety_config, structs from opendbc.car.body.carcontroller import CarController from opendbc.car.body.carstate import CarState -from opendbc.car.body.values import SPEED_FROM_RPM, BIN_PATH, BIN_URL, FLASH_ADDR, UDS_TX, UDS_RX, BUS +from opendbc.car.body.values import SPEED_FROM_RPM, FIRMWARE_VERSION, BIN_PATH, BIN_URL, FLASH_ADDR, UDS_TX, UDS_RX, BUS from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.body.flash import update +from opendbc.car.fw_query_definitions import StdQueries + class CarInterface(CarInterfaceBase): CarState = CarState @@ -12,7 +14,12 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, can_recv, can_send, communication_control=None): - update(FLASH_ADDR, BUS, BIN_PATH, BIN_URL, can_send, can_recv, UDS_TX, UDS_RX, BUS) + fw_signature = next( + fw.fwVersion for fw in CP.carFw + if fw.ecu == structs.CarParams.Ecu.engine + and fw.request[1] == StdQueries.APPLICATION_SOFTWARE_FINGERPRINT_REQUEST + ) + update(can_send, can_recv, FLASH_ADDR, BUS, BIN_PATH, BIN_URL, fw_signature) @staticmethod def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams: diff --git a/opendbc/car/body/values.py b/opendbc/car/body/values.py index 13c110c7535..86b6a287a24 100644 --- a/opendbc/car/body/values.py +++ b/opendbc/car/body/values.py @@ -12,12 +12,12 @@ BIN_URL = f"https://github.com/commaai/body/releases/download/{FIRMWARE_VERSION}/body.bin.signed" BIN_NAME = f"body-v1-{FIRMWARE_VERSION}.bin.signed" BODY_DIR = os.path.dirname(os.path.realpath(__file__)) -BIN_PATH = os.path.join(BODY_DIR, BIN_NAME) +_PANDA_BIN_DIR = os.path.join(BODY_DIR, "../../../../panda/board/body/v1") +BIN_PATH = os.path.join(_PANDA_BIN_DIR, BIN_NAME) if os.path.isdir(_PANDA_BIN_DIR) else os.path.join(BODY_DIR, BIN_NAME) FLASH_ADDR = 0x250 -UDS_TX = 0x720 -UDS_RX = 0x728 BUS = 0 + class CarControllerParams: ANGLE_DELTA_BP = [0., 5., 15.] ANGLE_DELTA_V = [5., .8, .15] # windup limit From 15344200c23299c9bd89b8a6c64ab80aa97e88ca Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:54:32 -0700 Subject: [PATCH 11/20] make current_signature optional --- opendbc/car/body/flash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc/car/body/flash.py b/opendbc/car/body/flash.py index ce03a79ec37..a4297a578bc 100644 --- a/opendbc/car/body/flash.py +++ b/opendbc/car/body/flash.py @@ -117,7 +117,7 @@ def reset_body(handle): handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) -def update(can_send, can_recv, addr, bus, file, update_url, current_signature): +def update(can_send, can_recv, addr, bus, file, update_url, current_signature=None): if not os.path.exists(file): print("local bin is not up-to-date, fetching latest") fetch_bin(file, update_url) From 5b6f70e30278f1817954115bd9d745ffe2586ea3 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:56:09 -0700 Subject: [PATCH 12/20] remove unused imports --- opendbc/car/body/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc/car/body/interface.py b/opendbc/car/body/interface.py index 2d1ca89d454..9b4eb187224 100644 --- a/opendbc/car/body/interface.py +++ b/opendbc/car/body/interface.py @@ -2,7 +2,7 @@ from opendbc.car import get_safety_config, structs from opendbc.car.body.carcontroller import CarController from opendbc.car.body.carstate import CarState -from opendbc.car.body.values import SPEED_FROM_RPM, FIRMWARE_VERSION, BIN_PATH, BIN_URL, FLASH_ADDR, UDS_TX, UDS_RX, BUS +from opendbc.car.body.values import SPEED_FROM_RPM, BIN_PATH, BIN_URL, FLASH_ADDR, BUS from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.body.flash import update from opendbc.car.fw_query_definitions import StdQueries From 10f1551fae3bf2e379d39679616c4252f5c2b4f7 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:57:55 -0700 Subject: [PATCH 13/20] add back uds software fingerprint definitions --- opendbc/car/body/values.py | 5 +++++ opendbc/car/fw_query_definitions.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/opendbc/car/body/values.py b/opendbc/car/body/values.py index 86b6a287a24..2193ca44aec 100644 --- a/opendbc/car/body/values.py +++ b/opendbc/car/body/values.py @@ -44,6 +44,11 @@ class CAR(Platforms): [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], bus=0, ), + Request( + [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.APPLICATION_SOFTWARE_FINGERPRINT_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.APPLICATION_SOFTWARE_FINGERPRINT_RESPONSE], + bus=0, + ), ], ) diff --git a/opendbc/car/fw_query_definitions.py b/opendbc/car/fw_query_definitions.py index 2d0656fcbd3..969c6705e21 100644 --- a/opendbc/car/fw_query_definitions.py +++ b/opendbc/car/fw_query_definitions.py @@ -65,6 +65,11 @@ class StdQueries: UDS_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + APPLICATION_SOFTWARE_FINGERPRINT_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) + APPLICATION_SOFTWARE_FINGERPRINT_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) + OBD_VERSION_REQUEST = b'\x09\x04' OBD_VERSION_RESPONSE = b'\x49\x04' From ceb84dcbff69eec9254037d228af4501e1f89ee5 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:09:04 -0700 Subject: [PATCH 14/20] add missing software fingerprint fallback --- opendbc/car/body/interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/opendbc/car/body/interface.py b/opendbc/car/body/interface.py index 9b4eb187224..37f05fb9b31 100644 --- a/opendbc/car/body/interface.py +++ b/opendbc/car/body/interface.py @@ -15,9 +15,10 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, can_recv, can_send, communication_control=None): fw_signature = next( - fw.fwVersion for fw in CP.carFw + (fw.fwVersion for fw in CP.carFw if fw.ecu == structs.CarParams.Ecu.engine - and fw.request[1] == StdQueries.APPLICATION_SOFTWARE_FINGERPRINT_REQUEST + and fw.request[1] == StdQueries.APPLICATION_SOFTWARE_FINGERPRINT_REQUEST), + b"" ) update(can_send, can_recv, FLASH_ADDR, BUS, BIN_PATH, BIN_URL, fw_signature) From 25cd6cfac99e702c3df6e7298ae3748997320a3a Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:13:19 -0700 Subject: [PATCH 15/20] uds bypass in body safety no longer needed --- opendbc/safety/modes/body.h | 9 +-------- opendbc/safety/tests/common.py | 4 ---- opendbc/safety/tests/test_body.py | 8 +------- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/opendbc/safety/modes/body.h b/opendbc/safety/modes/body.h index 190cd507aee..409cfca03ec 100644 --- a/opendbc/safety/modes/body.h +++ b/opendbc/safety/modes/body.h @@ -21,12 +21,6 @@ static bool body_tx_hook(const CANPacket_t *msg) { tx = true; } - // Allow UDS firmware queries through for fingerprinting - bool uds_msg = (msg->addr == 0x720U); - if (!controls_allowed && uds_msg) { - tx = true; - } - return tx; } @@ -36,8 +30,7 @@ static safety_config body_init(uint16_t param) { }; static const CanMsg BODY_TX_MSGS[] = {{0x250, 0, 8, .check_relay = false}, {0x250, 0, 6, .check_relay = false}, {0x251, 0, 5, .check_relay = false}, // body - {0x1, 0, 8, .check_relay = false}, // CAN flasher - {0x720, 0, 8, .check_relay = false}}; // UDS firmware query + {0x1, 0, 8, .check_relay = false}}; // CAN flasher SAFETY_UNUSED(param); safety_config ret = BUILD_SAFETY_CFG(body_rx_checks, BODY_TX_MSGS); diff --git a/opendbc/safety/tests/common.py b/opendbc/safety/tests/common.py index 1f3661af869..b8935d9dc98 100644 --- a/opendbc/safety/tests/common.py +++ b/opendbc/safety/tests/common.py @@ -938,10 +938,6 @@ def test_tx_hook_on_wrong_safety_mode(self): # exceptions for common msgs across different Hyundai CAN platforms tx = list(filter(lambda m: m[0] not in [0x420, 0x50A, 0x389, 0x4A2], tx)) - # Body allows UDS firmware query, overlaps with ELM327/diagnostic modes - if current_test.startswith('TestBody'): - tx = list(filter(lambda m: m[0] not in [0x720, ], tx)) - all_tx.append([[m[0], m[1], attr] for m in tx]) # make sure we got all the msgs diff --git a/opendbc/safety/tests/test_body.py b/opendbc/safety/tests/test_body.py index 1c6927d245a..5ea1b00f17a 100755 --- a/opendbc/safety/tests/test_body.py +++ b/opendbc/safety/tests/test_body.py @@ -9,8 +9,7 @@ class TestBody(common.SafetyTest): TX_MSGS = [[0x250, 0], [0x251, 0], - [0x1, 0], [0x1, 1], [0x1, 2], [0x1, 3], - [0x720, 0]] + [0x1, 0], [0x1, 1], [0x1, 2], [0x1, 3]] FWD_BUS_LOOKUP = {} def setUp(self): @@ -56,10 +55,5 @@ def test_can_flasher(self): self.assertFalse(self._tx(common.make_msg(0, 0x250, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0'))) # not correct data/len self.assertFalse(self._tx(common.make_msg(0, 0x251, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0\x0a'))) # wrong address - def test_uds_firmware_query(self): - # UDS firmware query allowed without controls - self.safety.set_controls_allowed(False) - self.assertTrue(self._tx(common.make_msg(0, 0x720, 8))) - if __name__ == "__main__": unittest.main() From a0048718879a78b8fbb7e2fa40dfa92f81ee8a69 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:14:13 -0700 Subject: [PATCH 16/20] clean diff --- opendbc/safety/modes/body.h | 2 +- opendbc/safety/tests/common.py | 1 - opendbc/safety/tests/test_body.py | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc/safety/modes/body.h b/opendbc/safety/modes/body.h index 409cfca03ec..e3d5c212ca0 100644 --- a/opendbc/safety/modes/body.h +++ b/opendbc/safety/modes/body.h @@ -30,7 +30,7 @@ static safety_config body_init(uint16_t param) { }; static const CanMsg BODY_TX_MSGS[] = {{0x250, 0, 8, .check_relay = false}, {0x250, 0, 6, .check_relay = false}, {0x251, 0, 5, .check_relay = false}, // body - {0x1, 0, 8, .check_relay = false}}; // CAN flasher + {0x1, 0, 8, .check_relay = false}}; // CAN flasher SAFETY_UNUSED(param); safety_config ret = BUILD_SAFETY_CFG(body_rx_checks, BODY_TX_MSGS); diff --git a/opendbc/safety/tests/common.py b/opendbc/safety/tests/common.py index b8935d9dc98..267736cfac6 100644 --- a/opendbc/safety/tests/common.py +++ b/opendbc/safety/tests/common.py @@ -937,7 +937,6 @@ def test_tx_hook_on_wrong_safety_mode(self): if attr.startswith('TestHyundaiLongitudinal'): # exceptions for common msgs across different Hyundai CAN platforms tx = list(filter(lambda m: m[0] not in [0x420, 0x50A, 0x389, 0x4A2], tx)) - all_tx.append([[m[0], m[1], attr] for m in tx]) # make sure we got all the msgs diff --git a/opendbc/safety/tests/test_body.py b/opendbc/safety/tests/test_body.py index 5ea1b00f17a..ae98712ce45 100755 --- a/opendbc/safety/tests/test_body.py +++ b/opendbc/safety/tests/test_body.py @@ -55,5 +55,6 @@ def test_can_flasher(self): self.assertFalse(self._tx(common.make_msg(0, 0x250, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0'))) # not correct data/len self.assertFalse(self._tx(common.make_msg(0, 0x251, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0\x0a'))) # wrong address + if __name__ == "__main__": unittest.main() From 2c0b932064d5bee85e469049b593c03579b10869 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:19:20 -0700 Subject: [PATCH 17/20] increase timout on test_fw_query_timing for body --- opendbc/car/tests/test_fw_fingerprint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc/car/tests/test_fw_fingerprint.py b/opendbc/car/tests/test_fw_fingerprint.py index 811347bb365..c837e36d520 100644 --- a/opendbc/car/tests/test_fw_fingerprint.py +++ b/opendbc/car/tests/test_fw_fingerprint.py @@ -262,10 +262,10 @@ def fake_get_ecu_addrs(*_, timeout): print(f'get_vin {name} case, query time={self.total_time / self.N} seconds') def test_fw_query_timing(self): - total_ref_time = 7.4 + total_ref_time = 7.5 brand_ref_times = { 'gm': 1.0, - 'body': 0.1, + 'body': 0.2, 'chrysler': 0.3, 'ford': 1.5, 'honda': 0.45, From dcf614c2fdf04a1d9143dd84a46f7f2393fc1bd8 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:53:21 -0700 Subject: [PATCH 18/20] clear carparams cache after flash --- opendbc/car/body/flash.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opendbc/car/body/flash.py b/opendbc/car/body/flash.py index a4297a578bc..b6afb1917bd 100644 --- a/opendbc/car/body/flash.py +++ b/opendbc/car/body/flash.py @@ -149,6 +149,12 @@ def update(can_send, can_recv, addr, bus, file, update_url, current_signature=No print(f"flash failed (attempt {i + 1}/{retries}): {e}, trying again...") else: print("successfully flashed") + + # Clear cached CarParams so FW queries run fresh after flash + car_params_cache = "/data/params/d/CarParamsCache" + if os.path.isfile(car_params_cache): + os.remove(car_params_cache) + print("cleared CarParamsCache") return # on fail: attempt to exit bootloader From d42142dec00bae09c21e152405626009e36fc719 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:14:31 -0700 Subject: [PATCH 19/20] move can send/recv adapter to init() --- opendbc/car/body/flash.py | 20 ++------------------ opendbc/car/body/interface.py | 10 +++++++++- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/opendbc/car/body/flash.py b/opendbc/car/body/flash.py index b6afb1917bd..3286ddcc2c4 100644 --- a/opendbc/car/body/flash.py +++ b/opendbc/car/body/flash.py @@ -4,7 +4,6 @@ import subprocess from itertools import accumulate -from opendbc.car.can_definitions import CanData from opendbc.car.uds import CanClient, IsoTpMessage, MessageTimeoutError REQUEST_IN = 0xC0 @@ -15,24 +14,9 @@ "sector_sizes": [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)], } - -class CanCallbacks: - def __init__(self, can_send, can_recv): - self._can_send_cb = can_send - self._can_recv_cb = can_recv - - def can_send(self, addr: int, dat: bytes, bus: int, timeout: int = 0): - del timeout - self._can_send_cb([CanData(address=addr, dat=dat, src=bus)]) - - def can_recv(self): - return [(msg.address, msg.dat, msg.src) for packet in self._can_recv_cb() for msg in packet] - - class CanHandle: def __init__(self, can_send, can_recv, bus): - callbacks = CanCallbacks(can_send, can_recv) - self.client = CanClient(callbacks.can_send, callbacks.can_recv, tx_addr=1, rx_addr=2, bus=bus) + self.client = CanClient(can_send, can_recv, tx_addr=1, rx_addr=2, bus=bus) def transact(self, dat, timeout=DEFAULT_ISOTP_TIMEOUT, expect_disconnect=False): try: @@ -132,7 +116,7 @@ def update(can_send, can_recv, addr, bus, file, update_url, current_signature=No if current_signature is None or current_signature != expected_signature: print("flashing motherboard") - can_send([CanData(address=addr, dat=b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", src=bus)]) + can_send(addr, b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", bus) time.sleep(0.1) print("flashing", file) diff --git a/opendbc/car/body/interface.py b/opendbc/car/body/interface.py index 37f05fb9b31..26b4938d576 100644 --- a/opendbc/car/body/interface.py +++ b/opendbc/car/body/interface.py @@ -6,6 +6,7 @@ from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.body.flash import update from opendbc.car.fw_query_definitions import StdQueries +from opendbc.car.can_definitions import CanData class CarInterface(CarInterfaceBase): @@ -20,7 +21,14 @@ def init(CP, can_recv, can_send, communication_control=None): and fw.request[1] == StdQueries.APPLICATION_SOFTWARE_FINGERPRINT_REQUEST), b"" ) - update(can_send, can_recv, FLASH_ADDR, BUS, BIN_PATH, BIN_URL, fw_signature) + + def p_can_send(addr: int, dat: bytes, bus: int): + can_send([CanData(address=addr, dat=dat, src=bus)]) + + def p_can_recv(): + return [(msg.address, msg.dat, msg.src) for packet in can_recv() for msg in packet] + + update(p_can_send, p_can_recv, FLASH_ADDR, BUS, BIN_PATH, BIN_URL, fw_signature) @staticmethod def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams: From ac8d60043cde06105f595f2fd8dfb383f52b1552 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:34:58 -0700 Subject: [PATCH 20/20] remove extra fingerprints --- opendbc/car/body/fingerprints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc/car/body/fingerprints.py b/opendbc/car/body/fingerprints.py index f5669418a2a..5806112fb15 100644 --- a/opendbc/car/body/fingerprints.py +++ b/opendbc/car/body/fingerprints.py @@ -9,7 +9,7 @@ FINGERPRINTS = { CAR.COMMA_BODY: [{ - 513: 8, 516: 8, 514: 3, 515: 4, 517: 8, 518: 6, 519: 6 + 513: 8, 516: 8, 514: 3, 515: 4 }], }