From 480cde877d49346f5077bcfa7dce7d5d8f04da8b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Apr 2026 23:07:57 -0400 Subject: [PATCH 01/11] Hyundai: delay cancel button send (#3305) * Hyundai: prioritize brake over pause/cancel button for factory SCC force-disengage * try the gm method * comment * more * comment * per code review * mt * cmt * right before use * this is redund --------- Co-authored-by: Shane Smiskol --- opendbc/car/hyundai/carcontroller.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/opendbc/car/hyundai/carcontroller.py b/opendbc/car/hyundai/carcontroller.py index 603e57f2..16abe2e2 100644 --- a/opendbc/car/hyundai/carcontroller.py +++ b/opendbc/car/hyundai/carcontroller.py @@ -17,6 +17,12 @@ MAX_ANGLE = 85 MAX_ANGLE_FRAMES = 89 MAX_ANGLE_CONSECUTIVE_FRAMES = 2 +# On some HKG CAN and CAN FD non-CANFD_ALT_BUTTONS, the cancel button (CF_Clu_CruiseSwState / CRUISE_BUTTONS = 4) is +# a pause/resume toggle, not a dedicated cancel. Firing it mid-brake inadvertently can cause a re-enable attempt +# and triggers the "SCC Conditions Not Met" alert. Delaying the button send lets factory SCC disengage +# naturally on brake press. We send ~100 ms later if it fails to do so, or if we want to cancel for another reason. +CANCEL_BUTTON_DELAY_FRAMES = 10 + def process_hud_alert(enabled, fingerprint, hud_control): sys_warning = (hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw)) @@ -54,6 +60,7 @@ class CarController(CarControllerBase): self.apply_torque_last = 0 self.car_fingerprint = CP.carFingerprint self.last_button_frame = 0 + self.cancel_counter = 0 def update(self, CC, CS, now_nanos): actuators = CC.actuators @@ -94,6 +101,10 @@ class CarController(CarControllerBase): if self.CP.flags & HyundaiFlags.CANFD_ENABLE_BLINKERS: can_sends.append(make_tester_present_msg(0x7b1, self.CAN.ECAN, suppress_response=True)) + # Delay the cancel button send so the brake can disengage factory SCC first. + # Reset whenever openpilot is no longer requesting cancel. + self.cancel_counter = self.cancel_counter + 1 if CC.cruiseControl.cancel else 0 + # *** CAN/CAN FD specific *** if self.CP.flags & HyundaiFlags.CANFD: can_sends.extend(self.create_canfd_msgs(apply_steer_req, apply_torque, set_speed_in_units, accel, @@ -128,7 +139,7 @@ class CarController(CarControllerBase): # Button messages if not self.CP.openpilotLongitudinalControl: - if CC.cruiseControl.cancel: + if self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES: can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP)) elif CC.cruiseControl.resume: # send resume at a max freq of 10Hz @@ -196,10 +207,11 @@ class CarController(CarControllerBase): if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: # cruise cancel if CC.cruiseControl.cancel: + # Here we send ACC message to cancel, not buttons. Don't delay if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info)) self.last_button_frame = self.frame - else: + elif self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES: for _ in range(20): can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.CANCEL)) self.last_button_frame = self.frame From f30f8cfa65a5a6600b95c993ed711d51fb30b326 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 21 Apr 2026 21:41:16 -0700 Subject: [PATCH 02/11] Fix car diff race condition (#3344) --- .github/workflows/car_diff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/car_diff.yml b/.github/workflows/car_diff.yml index bb8d8832..b9ecebb2 100644 --- a/.github/workflows/car_diff.yml +++ b/.github/workflows/car_diff.yml @@ -31,7 +31,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} workflow: tests.yml workflow_conclusion: '' - pr: ${{ github.event.number }} + commit: ${{ github.event.pull_request.head.sha }} name: car_diff_${{ github.event.number }} path: . allow_forks: true From 36edee381dd5550396e1b63f9e217a76f9e5be46 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 21 Apr 2026 22:52:43 -0700 Subject: [PATCH 03/11] Tesla: more FW understanding (#3346) * parse fw * fix fsd14 check * clean up * clean up * clean up * test --- opendbc/car/tesla/tests/test_tesla.py | 79 +++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/opendbc/car/tesla/tests/test_tesla.py b/opendbc/car/tesla/tests/test_tesla.py index 946ca399..f08a9dc9 100644 --- a/opendbc/car/tesla/tests/test_tesla.py +++ b/opendbc/car/tesla/tests/test_tesla.py @@ -1,3 +1,4 @@ +import re from opendbc.car import gen_empty_fingerprint from opendbc.car.structs import CarParams from opendbc.car.tesla.interface import CarInterface @@ -7,23 +8,77 @@ from opendbc.car.tesla.values import CAR, FSD_14_FW Ecu = CarParams.Ecu +# Fields prefixed unknown_* we observe structurally but don't know the meaning of. +# Only `platform` has evidence-backed semantic meaning (matches car_model in FW_VERSIONS). +# +# unknown_prefix is everything before the comma; we don't split it because we don't know what its +# parts mean, but observed shape is: __ (), e.g. +# TeMYG4 _ Main _ 0.0.0 (78) or TeM3 _ SP_XP002p2 _ 0.0.0 (23) +# family package triplet build family package triplet build +# +# After the comma, the version string decomposes into: +# platform : E/Y/X = car model (Model 3 / Y / X). The only field with known meaning. +# variant_code : differentiator WITHIN a platform — hardware/trim/calibration bits packed +# into <3-digit series>, e.g. '4HP015', '4003', 'L014', +# 'PR003'. We don't fully know what the parts mean individually, but the +# whole string identifies a specific variant within the car model. +# software_major/minor : numeric components after the first '.' — conventional release numbers. +# minor is optional (e.g. 'E4S014.27' has no minor). +# +# Example full parse of 'TeMYG4_Main_0.0.0 (78),E4HP015.05.0': +# unknown_prefix='TeMYG4_Main_0.0.0 (78)' +# platform=E variant_code=4HP015 software_major=05 software_minor=0 +FW_RE = re.compile( + rb'^(?P.+),' + + rb'(?P[EYX])' + + rb'(?P\d?[A-Z]*\d{3})' + + rb'\.(?P\d+)' + + rb'(?:\.(?P\d+))?$' +) + +PLATFORM_TO_CAR = { + b'E': CAR.TESLA_MODEL_3, + b'Y': CAR.TESLA_MODEL_Y, + b'X': CAR.TESLA_MODEL_X, +} + +# Hypothesized FSD 14 profile, in terms of variant_code bookends (given software_major >= 4): +# M3: variant_code starts with '4H', ends with '015' +# MY: variant_code starts with '4', ends with '003' +# Older series (M3 '014', MY '002') are never FSD 14. +FSD_14_FW_RULE = { + CAR.TESLA_MODEL_3: (b'4H', b'015'), + CAR.TESLA_MODEL_Y: (b'4', b'003'), +} + class TestTeslaFingerprint: - def test_fsd_14_fw(self): + def test_fw_platform_code(self): + # Every EPS FW must parse and its platform letter must match the car it's filed under. for car_model, ecus in FW_VERSIONS.items(): for fw in ecus.get((Ecu.eps, 0x730, None), []): - _, _, version = fw.partition(b',') - is_fsd_14 = fw in FSD_14_FW.get(car_model, []) + m = FW_RE.match(fw) - if car_model == CAR.TESLA_MODEL_3: - # Model 3: FSD 14 FW has 'P' in the Highland suffix (E4HP vs E4H) - assert is_fsd_14 == version.startswith(b'E4HP'), f"{fw}" - elif car_model == CAR.TESLA_MODEL_Y: - # Model Y: FSD 14 FW is Y4x version >= 003.04 - prefix, _, ver = version.partition(b'.') - y4_003 = prefix.startswith(b'Y4') and prefix.endswith(b'003') # Y4=HW4, 003=version series (002 is never FSD 14) - high_version = y4_003 and int(ver.split(b'.')[0]) >= 4 - assert is_fsd_14 == high_version, f"{fw}" + assert m is not None, f"Unparsable FW: {fw}" + assert PLATFORM_TO_CAR[m['platform']] == car_model, f"Platform letter {m['platform']!r} != {car_model.value}: {fw}" + + def test_fsd_14_fw(self): + for car_model, ecus in FW_VERSIONS.items(): + if car_model not in FSD_14_FW_RULE: + continue + + variant_prefix, variant_suffix = FSD_14_FW_RULE[car_model] + for fw in ecus.get((Ecu.eps, 0x730, None), []): + m = FW_RE.match(fw) + assert m is not None, f"Unparsable FW: {fw}" + + is_fsd_14 = fw in FSD_14_FW.get(car_model, []) + expected = ( + m['variant_code'].startswith(variant_prefix) + and m['variant_code'].endswith(variant_suffix) + and int(m['software_major']) >= 4 + ) + assert is_fsd_14 == expected, f"{fw}" def test_radar_detection(self): # Test radar availability detection for cars with radar DBC defined From c64b060c8082eec2a35ffc818d7ccf7cda9f140f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 21 Apr 2026 22:56:25 -0700 Subject: [PATCH 04/11] unittest-parallel: add back missing tests (#3345) test missing stuff --- opendbc/can/tests/test_define.py | 8 +++++--- opendbc/car/honda/tests/test_honda.py | 3 ++- opendbc/car/rivian/tests/test_rivian.py | 8 +++++--- opendbc/car/subaru/tests/test_subaru.py | 4 +++- opendbc/car/tesla/tests/test_tesla.py | 4 +++- opendbc/car/toyota/tests/test_toyota.py | 26 +++++++++++++------------ 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/opendbc/can/tests/test_define.py b/opendbc/can/tests/test_define.py index 41cfa789..92ad6e28 100644 --- a/opendbc/can/tests/test_define.py +++ b/opendbc/can/tests/test_define.py @@ -1,8 +1,10 @@ +import unittest + from opendbc.can import CANDefine from opendbc.can.tests import ALL_DBCS -class TestCANDefine: +class TestCANDefine(unittest.TestCase): def test_civic(self): dbc_file = "honda_civic_touring_2016_can_generated" @@ -20,8 +22,8 @@ class TestCANDefine: 0: 'NORMAL'} } - def test_all_dbcs(self, subtests): + def test_all_dbcs(self): # Asserts no exceptions on all DBCs for dbc in ALL_DBCS: - with subtests.test(dbc=dbc): + with self.subTest(dbc=dbc): CANDefine(dbc) diff --git a/opendbc/car/honda/tests/test_honda.py b/opendbc/car/honda/tests/test_honda.py index e72e6479..c6301e67 100644 --- a/opendbc/car/honda/tests/test_honda.py +++ b/opendbc/car/honda/tests/test_honda.py @@ -1,4 +1,5 @@ import re +import unittest from opendbc.car.honda.fingerprints import FW_VERSIONS from opendbc.car.honda.values import HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL @@ -6,7 +7,7 @@ from opendbc.car.honda.values import HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL HONDA_FW_VERSION_RE = br"[A-Z0-9]{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$" -class TestHondaFingerprint: +class TestHondaFingerprint(unittest.TestCase): def test_fw_version_format(self): # Asserts all FW versions follow an expected format for fw_by_ecu in FW_VERSIONS.values(): diff --git a/opendbc/car/rivian/tests/test_rivian.py b/opendbc/car/rivian/tests/test_rivian.py index 7780638d..37eca991 100644 --- a/opendbc/car/rivian/tests/test_rivian.py +++ b/opendbc/car/rivian/tests/test_rivian.py @@ -1,11 +1,13 @@ +import unittest + from opendbc.car.rivian.fingerprints import FW_VERSIONS from opendbc.car.rivian.values import CAR, FW_QUERY_CONFIG, WMI, ModelLine, ModelYear -class TestRivian: - def test_custom_fuzzy_fingerprinting(self, subtests): +class TestRivian(unittest.TestCase): + def test_custom_fuzzy_fingerprinting(self): for platform in CAR: - with subtests.test(platform=platform.name): + with self.subTest(platform=platform.name): for wmi in WMI: for line in ModelLine: for year in ModelYear: diff --git a/opendbc/car/subaru/tests/test_subaru.py b/opendbc/car/subaru/tests/test_subaru.py index 7f3d9bb3..39948664 100644 --- a/opendbc/car/subaru/tests/test_subaru.py +++ b/opendbc/car/subaru/tests/test_subaru.py @@ -1,7 +1,9 @@ +import unittest + from opendbc.car.subaru.fingerprints import FW_VERSIONS -class TestSubaruFingerprint: +class TestSubaruFingerprint(unittest.TestCase): def test_fw_version_format(self): for platform, fws_per_ecu in FW_VERSIONS.items(): for (ecu, _, _), fws in fws_per_ecu.items(): diff --git a/opendbc/car/tesla/tests/test_tesla.py b/opendbc/car/tesla/tests/test_tesla.py index f08a9dc9..2c7bc977 100644 --- a/opendbc/car/tesla/tests/test_tesla.py +++ b/opendbc/car/tesla/tests/test_tesla.py @@ -1,4 +1,6 @@ import re +import unittest + from opendbc.car import gen_empty_fingerprint from opendbc.car.structs import CarParams from opendbc.car.tesla.interface import CarInterface @@ -52,7 +54,7 @@ FSD_14_FW_RULE = { } -class TestTeslaFingerprint: +class TestTeslaFingerprint(unittest.TestCase): def test_fw_platform_code(self): # Every EPS FW must parse and its platform letter must match the car it's filed under. for car_model, ecus in FW_VERSIONS.items(): diff --git a/opendbc/car/toyota/tests/test_toyota.py b/opendbc/car/toyota/tests/test_toyota.py index 2d07af54..f6443b20 100644 --- a/opendbc/car/toyota/tests/test_toyota.py +++ b/opendbc/car/toyota/tests/test_toyota.py @@ -1,3 +1,5 @@ +import unittest + from hypothesis import given, settings, strategies as st from opendbc.car import Bus @@ -16,7 +18,7 @@ def check_fw_version(fw_version: bytes) -> bool: return b'?' not in fw_version and b'!' not in fw_version -class TestToyotaInterfaces: +class TestToyotaInterfaces(unittest.TestCase): def test_car_sets(self): # Angle and radar-ACC cars are always TSS2 cars assert len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0 @@ -33,11 +35,11 @@ class TestToyotaInterfaces: if car_model in TSS2_CAR and car_model not in SECOC_CAR: assert dbc[Bus.pt] == "toyota_nodsu_pt_generated" - def test_essential_ecus(self, subtests): + def test_essential_ecus(self): # Asserts standard ECUs exist for each platform common_ecus = {Ecu.fwdRadar, Ecu.fwdCamera} for car_model, ecus in FW_VERSIONS.items(): - with subtests.test(car_model=car_model.value): + with self.subTest(car_model=car_model.value): present_ecus = {ecu[0] for ecu in ecus} missing_ecus = common_ecus - present_ecus assert len(missing_ecus) == 0 @@ -53,19 +55,19 @@ class TestToyotaInterfaces: assert Ecu.eps in present_ecus -class TestToyotaFingerprint: - def test_non_essential_ecus(self, subtests): +class TestToyotaFingerprint(unittest.TestCase): + def test_non_essential_ecus(self): # Ensures only the cars that have multiple engine ECUs are in the engine non-essential ECU list for car_model, ecus in FW_VERSIONS.items(): - with subtests.test(car_model=car_model.value): + with self.subTest(car_model=car_model.value): engine_ecus = {ecu for ecu in ecus if ecu[0] == Ecu.engine} assert (len(engine_ecus) > 1) == (car_model in FW_QUERY_CONFIG.non_essential_ecus[Ecu.engine]), \ f"Car model unexpectedly {'not ' if len(engine_ecus) > 1 else ''}in non-essential list" - def test_valid_fw_versions(self, subtests): + def test_valid_fw_versions(self): # Asserts all FW versions are valid for car_model, ecus in FW_VERSIONS.items(): - with subtests.test(car_model=car_model.value): + with self.subTest(car_model=car_model.value): for fws in ecus.values(): for fw in fws: assert check_fw_version(fw), fw @@ -79,10 +81,10 @@ class TestToyotaFingerprint: fws = data.draw(fw_strategy) get_platform_codes(fws) - def test_platform_code_ecus_available(self, subtests): + def test_platform_code_ecus_available(self): # Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms for car_model, ecus in FW_VERSIONS.items(): - with subtests.test(car_model=car_model.value): + with self.subTest(car_model=car_model.value): for platform_code_ecu in PLATFORM_CODE_ECUS: if platform_code_ecu == Ecu.eps and car_model in (CAR.TOYOTA_PRIUS_V, CAR.LEXUS_CTH,): continue @@ -90,14 +92,14 @@ class TestToyotaFingerprint: continue assert platform_code_ecu in [e[0] for e in ecus] - def test_fw_format(self, subtests): + def test_fw_format(self): # Asserts: # - every supported ECU FW version returns one platform code # - every supported ECU FW version has a part number # - expected parsing of ECU sub-versions for car_model, ecus in FW_VERSIONS.items(): - with subtests.test(car_model=car_model.value): + with self.subTest(car_model=car_model.value): for ecu, fws in ecus.items(): if ecu[0] not in PLATFORM_CODE_ECUS: continue From eb1da68115baa71df43801ecd7ee9693f5c19f5f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 21 Apr 2026 22:57:56 -0700 Subject: [PATCH 05/11] Tesla: new Model 3 FW from minor update (#3343) * New Model 3 FW from minor update * passes test --- opendbc/car/tesla/fingerprints.py | 1 + opendbc/car/tesla/values.py | 1 + 2 files changed, 2 insertions(+) diff --git a/opendbc/car/tesla/fingerprints.py b/opendbc/car/tesla/fingerprints.py index 9b33fd00..6c1bb66e 100644 --- a/opendbc/car/tesla/fingerprints.py +++ b/opendbc/car/tesla/fingerprints.py @@ -22,6 +22,7 @@ FW_VERSIONS = { b'TeMYG4_Main_0.0.0 (77),E4HP015.04.5', b'TeMYG4_Main_0.0.0 (78),E4HP015.05.0', b'TeMYG4_SingleECU_0.0.0 (33),E4S014.27', + b'TeMYG4_Main_0.0.0 (78),E4H015.05.0', ], }, CAR.TESLA_MODEL_Y: { diff --git a/opendbc/car/tesla/values.py b/opendbc/car/tesla/values.py index 873abfea..fb018006 100644 --- a/opendbc/car/tesla/values.py +++ b/opendbc/car/tesla/values.py @@ -80,6 +80,7 @@ FSD_14_FW = { b'TeMYG4_Main_0.0.0 (77),E4HP015.04.5', b'TeMYG4_Main_0.0.0 (78),E4HP015.05.0', b'TeMYG4_Main_0.0.0 (77),E4H015.04.5', + b'TeMYG4_Main_0.0.0 (78),E4H015.05.0', ], CAR.TESLA_MODEL_Y: [ b'TeMYG4_Legacy3Y_0.0.0 (6),Y4003.04.0', From d2f3306a46bbb4d330281ded1c0d03ee5bd6e804 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 21 Apr 2026 23:07:46 -0700 Subject: [PATCH 06/11] Tesla: hw3/4 docs --- opendbc/car/tesla/tests/test_tesla.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/opendbc/car/tesla/tests/test_tesla.py b/opendbc/car/tesla/tests/test_tesla.py index 2c7bc977..7981f03b 100644 --- a/opendbc/car/tesla/tests/test_tesla.py +++ b/opendbc/car/tesla/tests/test_tesla.py @@ -27,6 +27,9 @@ Ecu = CarParams.Ecu # software_major/minor : numeric components after the first '.' — conventional release numbers. # minor is optional (e.g. 'E4S014.27' has no minor). # +# Suspected (not confirmed): for M3/MY, `TeM3_*` outer + no-leading-digit variant_code == HW3, and +# `TeMYG4_*` outer + leading-'4' variant_code == HW4 (the 'G4' in TeMYG4 likely denotes Gen 4). +# # Example full parse of 'TeMYG4_Main_0.0.0 (78),E4HP015.05.0': # unknown_prefix='TeMYG4_Main_0.0.0 (78)' # platform=E variant_code=4HP015 software_major=05 software_minor=0 From e2a10c6c9da687fba7e99ff1aedf1799a494244d Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:09:25 -0700 Subject: [PATCH 07/11] Tesla: Fingerprint 2022 Model Y Performance (2026.8.6) (#3321) Tesla: 2022 Model Y Performance (2026.8.6) --- opendbc/car/tesla/fingerprints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opendbc/car/tesla/fingerprints.py b/opendbc/car/tesla/fingerprints.py index 6c1bb66e..53de0384 100644 --- a/opendbc/car/tesla/fingerprints.py +++ b/opendbc/car/tesla/fingerprints.py @@ -29,6 +29,7 @@ FW_VERSIONS = { (Ecu.eps, 0x730, None): [ b'TeM3_E014p10_0.0.0 (16),Y002.18.00', b'TeM3_E014p10_0.0.0 (16),YP002.18.00', + b'TeM3_E014p10_0.0.0 (24),YP002.21.2', b'TeM3_ES014p11_0.0.0 (16),YS002.17', b'TeM3_ES014p11_0.0.0 (25),YS002.19.0', b'TeMYG4_DCS_Update_0.0.0 (13),Y4002.27.1', From 7d40d89089b1c7b71c2b62e43e949162b01ce4ee Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Apr 2026 00:18:45 -0700 Subject: [PATCH 08/11] 0.3.0 (#3347) * 0.3.0 * notes * update * update --- RELEASES.md | 24 ++++++++++++++++++++++++ pyproject.toml | 2 +- uv.lock | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 2eab19fe..04e2319a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,27 @@ +Version 0.3.0 (2026-04-22) +======================== +* Supported car count: 345 → 397 +* Tesla Model 3, Model Y, and Model X (HW3 and HW4) support thanks to lukasloetkolben and greatgitsby! +* Rivian R1S and R1T (Gen 1 and Gen 2) support thanks to lukasloetkolben! +* Porsche Macan and Audi Q5 (VW MLB) support thanks to jyoung8607 and Dennis-NL! +* VW MEB (ID.x) architecture support thanks to jyoung8607! +* PSA AEE2010_R3 platform support thanks to elkoled! +* Honda Accord 2023-25, CR-V 2023-25, Pilot 2023-25, Passport 2026, and Acura MDX 2025 support thanks to vanillagorillaa and MVL! +* Honda Odyssey 2021-25 support thanks to csouers and MVL! +* Acura TLX 2021 support thanks to MVL! +* Honda City 2023 support thanks to vanillagorillaa and drFritz! +* Honda N-Box 2018 support thanks to miettal! +* Ford F-150, F-150 Hybrid, Mach-E, and Ranger support +* Ford Escape 2023-24 and Kuga 2024 support thanks to incognitojam! +* Hyundai Nexo 2021 support thanks to sunnyhaibin! +* Kia K7 2017 support thanks to royjr! +* Lexus LS 2018 support thanks to Hacheoy! +* Lexus RC 2023 support thanks to nelsonjchen! +* CANParser and CANPacker rewritten in pure Python — the installed package is pure Python, no compilation required +* Safety code, `isotp.py`/`ccp.py`/`xcp.py`, and `vehicle_model.py` moved into opendbc from panda/openpilot — opendbc is now the self-contained car API package +* `mull` replaced with a faster custom mutation test runner +* Safety hardening: per-brand message-block config, missing RX checks, relay-malfunction config + Version 0.2.1 (2025-02-10) ======================== * Fix missing files making car/ package not importable diff --git a/pyproject.toml b/pyproject.toml index fe70034c..860007ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "opendbc" -version = "0.2.1" +version = "0.3.0" description = "a Python API for your car" license = "MIT" authors = [{ name = "Vehicle Researcher", email = "user@comma.ai" }] diff --git a/uv.lock b/uv.lock index 9f6fe981..396986b8 100644 --- a/uv.lock +++ b/uv.lock @@ -382,7 +382,7 @@ wheels = [ [[package]] name = "opendbc" -version = "0.2.1" +version = "0.3.0" source = { editable = "." } dependencies = [ { name = "numpy" }, From b69cb4d0306b0c9063689a4395a84bbf7bef5450 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Apr 2026 00:33:28 -0700 Subject: [PATCH 09/11] packaging: move direct-URL deps to dependency-groups (#3348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PyPI rejects wheel uploads whose Requires-Dist entries contain direct URLs, which was blocking the 0.3.0 release with a 400 Bad Request. Move comma-car-segments and cppcheck from [project.optional-dependencies].testing to a PEP 735 [dependency-groups].testing — groups are not published in the wheel. Update setup.sh to install them via --all-groups. Co-authored-by: Claude Opus 4.7 (1M context) --- pyproject.toml | 11 +++++++++-- setup.sh | 2 +- uv.lock | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 860007ee..f517d03c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,6 @@ dependencies = [ [project.optional-dependencies] testing = [ - "comma-car-segments @ https://huggingface.co/datasets/commaai/commaCarSegments/resolve/main/dist/comma_car_segments-0.1.0-py3-none-any.whl", "cffi", "tree-sitter", "tree-sitter-c", @@ -33,7 +32,6 @@ testing = [ "lefthook", "cpplint", "codespell", - "cppcheck @ git+https://github.com/commaai/dependencies.git@release-cppcheck#subdirectory=cppcheck", ] docs = [ "Jinja2", @@ -42,6 +40,15 @@ examples = [ "inputs", ] +# Dependency groups (PEP 735) are not included in the published wheel. +# Direct URL dependencies must live here because PyPI rejects uploads whose +# `Requires-Dist` contains direct URLs. +[dependency-groups] +testing = [ + "comma-car-segments @ https://huggingface.co/datasets/commaai/commaCarSegments/resolve/main/dist/comma_car_segments-0.1.0-py3-none-any.whl", + "cppcheck @ git+https://github.com/commaai/dependencies.git@release-cppcheck#subdirectory=cppcheck", +] + [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" diff --git a/setup.sh b/setup.sh index 6966339b..e82a46b4 100755 --- a/setup.sh +++ b/setup.sh @@ -18,5 +18,5 @@ if ! command -v uv &>/dev/null; then fi export UV_PROJECT_ENVIRONMENT="$BASEDIR/.venv" -uv sync --all-extras --inexact +uv sync --all-extras --all-groups --inexact source "$PYTHONPATH/.venv/bin/activate" diff --git a/uv.lock b/uv.lock index 396986b8..2ed43eea 100644 --- a/uv.lock +++ b/uv.lock @@ -401,8 +401,6 @@ examples = [ testing = [ { name = "cffi" }, { name = "codespell" }, - { name = "comma-car-segments" }, - { name = "cppcheck" }, { name = "cpplint" }, { name = "gcovr" }, { name = "hypothesis" }, @@ -415,12 +413,16 @@ testing = [ { name = "zstandard" }, ] +[package.dev-dependencies] +testing = [ + { name = "comma-car-segments" }, + { name = "cppcheck" }, +] + [package.metadata] requires-dist = [ { name = "cffi", marker = "extra == 'testing'" }, { name = "codespell", marker = "extra == 'testing'" }, - { name = "comma-car-segments", marker = "extra == 'testing'", url = "https://huggingface.co/datasets/commaai/commaCarSegments/resolve/main/dist/comma_car_segments-0.1.0-py3-none-any.whl" }, - { name = "cppcheck", marker = "extra == 'testing'", git = "https://github.com/commaai/dependencies.git?subdirectory=cppcheck&rev=release-cppcheck" }, { name = "cpplint", marker = "extra == 'testing'" }, { name = "gcovr", marker = "extra == 'testing'" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, @@ -440,6 +442,12 @@ requires-dist = [ ] provides-extras = ["testing", "docs", "examples"] +[package.metadata.requires-dev] +testing = [ + { name = "comma-car-segments", url = "https://huggingface.co/datasets/commaai/commaCarSegments/resolve/main/dist/comma_car_segments-0.1.0-py3-none-any.whl" }, + { name = "cppcheck", git = "https://github.com/commaai/dependencies.git?subdirectory=cppcheck&rev=release-cppcheck" }, +] + [[package]] name = "pycapnp" version = "2.1.0" From ae12e2d4da66e3d8e16ce3e5c82718459ce9ece1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Apr 2026 01:26:37 -0700 Subject: [PATCH 10/11] packaging: ship DBC source files and runtime data in the wheel (#3349) * packaging: ship DBC source files and runtime data in the wheel The 0.3.0 wheel only shipped safety headers via package-data. Every other data file (.dbc, .capnp, .toml, CARS_template.md) was missing, which meant the published package couldn't load any DBC or initialize car interfaces out of the box. Declare explicit package-data patterns for opendbc.dbc and opendbc.car so the generator's source .dbc files, rlog.capnp, the torque_data TOMLs, and the docs template are all included. After this, a clean \`pip install opendbc\` successfully round-trips a CAN message through CANPacker + CANParser. Co-Authored-By: Claude Opus 4.7 (1M context) * bump --------- Co-authored-by: Claude Opus 4.7 (1M context) --- RELEASES.md | 4 ++++ pyproject.toml | 6 ++++-- uv.lock | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 04e2319a..6d64c979 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,7 @@ +Version 0.3.1 (2026-04-22) +======================== +* Ship DBCs, capnp schemas, and torque data in the wheel + Version 0.3.0 (2026-04-22) ======================== * Supported car count: 345 → 397 diff --git a/pyproject.toml b/pyproject.toml index f517d03c..ddfa2599 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "opendbc" -version = "0.3.0" +version = "0.3.1" description = "a Python API for your car" license = "MIT" authors = [{ name = "Vehicle Researcher", email = "user@comma.ai" }] @@ -134,4 +134,6 @@ too-many-positional-arguments = "ignore" include-package-data = true [tool.setuptools.package-data] -"opendbc.safety" = ["*.h", "board/*.h", "board/drivers/*.h", "modes/*.h"] +"opendbc.car" = ["**/*.capnp", "**/*.toml"] +"opendbc.dbc" = ["**/*.dbc"] +"opendbc.safety" = ["*.h", "modes/*.h"] diff --git a/uv.lock b/uv.lock index 2ed43eea..4302fe2d 100644 --- a/uv.lock +++ b/uv.lock @@ -382,7 +382,7 @@ wheels = [ [[package]] name = "opendbc" -version = "0.3.0" +version = "0.3.1" source = { editable = "." } dependencies = [ { name = "numpy" }, From f15cc7673a4f1bd87436eb4ce1e6bad3bdb8272d Mon Sep 17 00:00:00 2001 From: Keenin Krehbiel <52955020+keenin@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:15:00 -0700 Subject: [PATCH 11/11] Tesla: Fingerprint 2019 Model 3 AWD Longrange (2026.8.6) (#3323) Update fingerprints.py added 2026.8.6 fingerprint --- opendbc/car/tesla/fingerprints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opendbc/car/tesla/fingerprints.py b/opendbc/car/tesla/fingerprints.py index 53de0384..95e367b5 100644 --- a/opendbc/car/tesla/fingerprints.py +++ b/opendbc/car/tesla/fingerprints.py @@ -10,6 +10,7 @@ FW_VERSIONS = { b'TeM3_E014p10_0.0.0 (16),E014.17.00', b'TeM3_E014p10_0.0.0 (16),EL014.17.00', b'TeM3_ES014p11_0.0.0 (25),ES014.19.0', + b'TeM3_E014p10_0.0.0 (24),E014.20.2', b'TeMYG4_DCS_Update_0.0.0 (13),E4014.28.1', b'TeMYG4_DCS_Update_0.0.0 (9),E4014.26.0', b'TeMYG4_Legacy3Y_0.0.0 (2),E4015.02.0',