Merge branch 'upstream/master' into sync-20260425

This commit is contained in:
Jason Wen
2026-04-25 20:26:58 -04:00
14 changed files with 176 additions and 46 deletions

View File

@@ -31,7 +31,7 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: tests.yml workflow: tests.yml
workflow_conclusion: '' workflow_conclusion: ''
pr: ${{ github.event.number }} commit: ${{ github.event.pull_request.head.sha }}
name: car_diff_${{ github.event.number }} name: car_diff_${{ github.event.number }}
path: . path: .
allow_forks: true allow_forks: true

View File

@@ -1,3 +1,31 @@
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
* 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) Version 0.2.1 (2025-02-10)
======================== ========================
* Fix missing files making car/ package not importable * Fix missing files making car/ package not importable

View File

@@ -1,8 +1,10 @@
import unittest
from opendbc.can import CANDefine from opendbc.can import CANDefine
from opendbc.can.tests import ALL_DBCS from opendbc.can.tests import ALL_DBCS
class TestCANDefine: class TestCANDefine(unittest.TestCase):
def test_civic(self): def test_civic(self):
dbc_file = "honda_civic_touring_2016_can_generated" dbc_file = "honda_civic_touring_2016_can_generated"
@@ -20,8 +22,8 @@ class TestCANDefine:
0: 'NORMAL'} 0: 'NORMAL'}
} }
def test_all_dbcs(self, subtests): def test_all_dbcs(self):
# Asserts no exceptions on all DBCs # Asserts no exceptions on all DBCs
for dbc in ALL_DBCS: for dbc in ALL_DBCS:
with subtests.test(dbc=dbc): with self.subTest(dbc=dbc):
CANDefine(dbc) CANDefine(dbc)

View File

@@ -1,4 +1,5 @@
import re import re
import unittest
from opendbc.car.honda.fingerprints import FW_VERSIONS from opendbc.car.honda.fingerprints import FW_VERSIONS
from opendbc.car.honda.values import HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL 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}$" 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): def test_fw_version_format(self):
# Asserts all FW versions follow an expected format # Asserts all FW versions follow an expected format
for fw_by_ecu in FW_VERSIONS.values(): for fw_by_ecu in FW_VERSIONS.values():

View File

@@ -23,6 +23,12 @@ MAX_ANGLE = 85
MAX_ANGLE_FRAMES = 89 MAX_ANGLE_FRAMES = 89
MAX_ANGLE_CONSECUTIVE_FRAMES = 2 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): def process_hud_alert(enabled, fingerprint, hud_control):
sys_warning = (hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw)) sys_warning = (hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw))
@@ -66,6 +72,7 @@ class CarController(CarControllerBase, EsccCarController, LeadDataCarController,
self.apply_torque_last = 0 self.apply_torque_last = 0
self.car_fingerprint = CP.carFingerprint self.car_fingerprint = CP.carFingerprint
self.last_button_frame = 0 self.last_button_frame = 0
self.cancel_counter = 0
def update(self, CC, CC_SP, CS, now_nanos): def update(self, CC, CC_SP, CS, now_nanos):
EsccCarController.update(self, CS) EsccCarController.update(self, CS)
@@ -113,6 +120,10 @@ class CarController(CarControllerBase, EsccCarController, LeadDataCarController,
if self.CP.flags & HyundaiFlags.CANFD_ENABLE_BLINKERS: if self.CP.flags & HyundaiFlags.CANFD_ENABLE_BLINKERS:
can_sends.append(make_tester_present_msg(0x7b1, self.CAN.ECAN, suppress_response=True)) 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 *** # *** CAN/CAN FD specific ***
if self.CP.flags & HyundaiFlags.CANFD: if self.CP.flags & HyundaiFlags.CANFD:
can_sends.extend(self.create_canfd_msgs(apply_steer_req, apply_torque, set_speed_in_units, accel, can_sends.extend(self.create_canfd_msgs(apply_steer_req, apply_torque, set_speed_in_units, accel,
@@ -151,7 +162,7 @@ class CarController(CarControllerBase, EsccCarController, LeadDataCarController,
# Button messages # Button messages
if not self.CP.openpilotLongitudinalControl: 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)) can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
elif CC.cruiseControl.resume: elif CC.cruiseControl.resume:
# send resume at a max freq of 10Hz # send resume at a max freq of 10Hz
@@ -220,10 +231,11 @@ class CarController(CarControllerBase, EsccCarController, LeadDataCarController,
if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: if (self.frame - self.last_button_frame) * DT_CTRL > 0.25:
# cruise cancel # cruise cancel
if CC.cruiseControl.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: if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info)) can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info))
self.last_button_frame = self.frame self.last_button_frame = self.frame
else: elif self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
for _ in range(20): for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.CANCEL)) can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.CANCEL))
self.last_button_frame = self.frame self.last_button_frame = self.frame

View File

@@ -1,11 +1,13 @@
import unittest
from opendbc.car.rivian.fingerprints import FW_VERSIONS from opendbc.car.rivian.fingerprints import FW_VERSIONS
from opendbc.car.rivian.values import CAR, FW_QUERY_CONFIG, WMI, ModelLine, ModelYear from opendbc.car.rivian.values import CAR, FW_QUERY_CONFIG, WMI, ModelLine, ModelYear
class TestRivian: class TestRivian(unittest.TestCase):
def test_custom_fuzzy_fingerprinting(self, subtests): def test_custom_fuzzy_fingerprinting(self):
for platform in CAR: for platform in CAR:
with subtests.test(platform=platform.name): with self.subTest(platform=platform.name):
for wmi in WMI: for wmi in WMI:
for line in ModelLine: for line in ModelLine:
for year in ModelYear: for year in ModelYear:

View File

@@ -1,7 +1,9 @@
import unittest
from opendbc.car.subaru.fingerprints import FW_VERSIONS from opendbc.car.subaru.fingerprints import FW_VERSIONS
class TestSubaruFingerprint: class TestSubaruFingerprint(unittest.TestCase):
def test_fw_version_format(self): def test_fw_version_format(self):
for platform, fws_per_ecu in FW_VERSIONS.items(): for platform, fws_per_ecu in FW_VERSIONS.items():
for (ecu, _, _), fws in fws_per_ecu.items(): for (ecu, _, _), fws in fws_per_ecu.items():

View File

@@ -12,6 +12,7 @@ FW_VERSIONS = {
b'TeM3_E014p10_0.0.0 (16),E014.17.00', b'TeM3_E014p10_0.0.0 (16),E014.17.00',
b'TeM3_E014p10_0.0.0 (16),EL014.17.00', b'TeM3_E014p10_0.0.0 (16),EL014.17.00',
b'TeM3_ES014p11_0.0.0 (25),ES014.19.0', 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 (13),E4014.28.1',
b'TeMYG4_DCS_Update_0.0.0 (9),E4014.26.0', b'TeMYG4_DCS_Update_0.0.0 (9),E4014.26.0',
b'TeMYG4_Legacy3Y_0.0.0 (2),E4015.02.0', b'TeMYG4_Legacy3Y_0.0.0 (2),E4015.02.0',
@@ -24,12 +25,14 @@ FW_VERSIONS = {
b'TeMYG4_Main_0.0.0 (77),E4HP015.04.5', 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 (78),E4HP015.05.0',
b'TeMYG4_SingleECU_0.0.0 (33),E4S014.27', b'TeMYG4_SingleECU_0.0.0 (33),E4S014.27',
b'TeMYG4_Main_0.0.0 (78),E4H015.05.0',
], ],
}, },
CAR.TESLA_MODEL_Y: { CAR.TESLA_MODEL_Y: {
(Ecu.eps, 0x730, None): [ (Ecu.eps, 0x730, None): [
b'TeM3_E014p10_0.0.0 (16),Y002.18.00', 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 (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 (16),YS002.17',
b'TeM3_ES014p11_0.0.0 (25),YS002.19.0', b'TeM3_ES014p11_0.0.0 (25),YS002.19.0',
b'TeMYG4_DCS_Update_0.0.0 (13),Y4002.27.1', b'TeMYG4_DCS_Update_0.0.0 (13),Y4002.27.1',

View File

@@ -1,3 +1,6 @@
import re
import unittest
from opendbc.car import gen_empty_fingerprint from opendbc.car import gen_empty_fingerprint
from opendbc.car.structs import CarParams from opendbc.car.structs import CarParams
from opendbc.car.tesla.interface import CarInterface from opendbc.car.tesla.interface import CarInterface
@@ -7,23 +10,80 @@ from opendbc.car.tesla.values import CAR, FSD_14_FW
Ecu = CarParams.Ecu 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: <family>_<package>_<triplet> (<build>), 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 <digit?><letters?><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).
#
# 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
FW_RE = re.compile(
rb'^(?P<unknown_prefix>.+),' +
rb'(?P<platform>[EYX])' +
rb'(?P<variant_code>\d?[A-Z]*\d{3})' +
rb'\.(?P<software_major>\d+)' +
rb'(?:\.(?P<software_minor>\d+))?$'
)
class TestTeslaFingerprint: PLATFORM_TO_CAR = {
def test_fsd_14_fw(self): 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(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(): for car_model, ecus in FW_VERSIONS.items():
for fw in ecus.get((Ecu.eps, 0x730, None), []): for fw in ecus.get((Ecu.eps, 0x730, None), []):
_, _, version = fw.partition(b',') m = FW_RE.match(fw)
is_fsd_14 = fw in FSD_14_FW.get(car_model, [])
if car_model == CAR.TESLA_MODEL_3: assert m is not None, f"Unparsable FW: {fw}"
# Model 3: FSD 14 FW has 'P' in the Highland suffix (E4HP vs E4H) assert PLATFORM_TO_CAR[m['platform']] == car_model, f"Platform letter {m['platform']!r} != {car_model.value}: {fw}"
assert is_fsd_14 == version.startswith(b'E4HP'), f"{fw}"
elif car_model == CAR.TESLA_MODEL_Y: def test_fsd_14_fw(self):
# Model Y: FSD 14 FW is Y4x version >= 003.04 for car_model, ecus in FW_VERSIONS.items():
prefix, _, ver = version.partition(b'.') if car_model not in FSD_14_FW_RULE:
y4_003 = prefix.startswith(b'Y4') and prefix.endswith(b'003') # Y4=HW4, 003=version series (002 is never FSD 14) continue
high_version = y4_003 and int(ver.split(b'.')[0]) >= 4
assert is_fsd_14 == high_version, f"{fw}" 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): def test_radar_detection(self):
# Test radar availability detection for cars with radar DBC defined # Test radar availability detection for cars with radar DBC defined

View File

@@ -85,6 +85,7 @@ FSD_14_FW = {
b'TeMYG4_Main_0.0.0 (77),E4HP015.04.5', 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 (78),E4HP015.05.0',
b'TeMYG4_Main_0.0.0 (77),E4H015.04.5', b'TeMYG4_Main_0.0.0 (77),E4H015.04.5',
b'TeMYG4_Main_0.0.0 (78),E4H015.05.0',
], ],
CAR.TESLA_MODEL_Y: [ CAR.TESLA_MODEL_Y: [
b'TeMYG4_Legacy3Y_0.0.0 (6),Y4003.04.0', b'TeMYG4_Legacy3Y_0.0.0 (6),Y4003.04.0',

View File

@@ -1,3 +1,5 @@
import unittest
from hypothesis import given, settings, strategies as st from hypothesis import given, settings, strategies as st
from opendbc.car import Bus 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 return b'?' not in fw_version and b'!' not in fw_version
class TestToyotaInterfaces: class TestToyotaInterfaces(unittest.TestCase):
def test_car_sets(self): def test_car_sets(self):
assert len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0 assert len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0
assert len(RADAR_ACC_CAR - TSS2_CAR) == 0 assert len(RADAR_ACC_CAR - TSS2_CAR) == 0
@@ -32,11 +34,11 @@ class TestToyotaInterfaces:
if car_model in TSS2_CAR and car_model not in SECOC_CAR: if car_model in TSS2_CAR and car_model not in SECOC_CAR:
assert dbc[Bus.pt] == "toyota_nodsu_pt_generated" 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 # Asserts standard ECUs exist for each platform
common_ecus = {Ecu.fwdRadar, Ecu.fwdCamera} common_ecus = {Ecu.fwdRadar, Ecu.fwdCamera}
for car_model, ecus in FW_VERSIONS.items(): 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} present_ecus = {ecu[0] for ecu in ecus}
missing_ecus = common_ecus - present_ecus missing_ecus = common_ecus - present_ecus
assert len(missing_ecus) == 0 assert len(missing_ecus) == 0
@@ -52,19 +54,19 @@ class TestToyotaInterfaces:
assert Ecu.eps in present_ecus assert Ecu.eps in present_ecus
class TestToyotaFingerprint: class TestToyotaFingerprint(unittest.TestCase):
def test_non_essential_ecus(self, subtests): def test_non_essential_ecus(self):
# Ensures only the cars that have multiple engine ECUs are in the engine non-essential ECU list # 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(): 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} 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]), \ 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" 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 # Asserts all FW versions are valid
for car_model, ecus in FW_VERSIONS.items(): 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 fws in ecus.values():
for fw in fws: for fw in fws:
assert check_fw_version(fw), fw assert check_fw_version(fw), fw
@@ -78,10 +80,10 @@ class TestToyotaFingerprint:
fws = data.draw(fw_strategy) fws = data.draw(fw_strategy)
get_platform_codes(fws) 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 # Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms
for car_model, ecus in FW_VERSIONS.items(): 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: 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,): if platform_code_ecu == Ecu.eps and car_model in (CAR.TOYOTA_PRIUS_V, CAR.LEXUS_CTH,):
continue continue
@@ -89,14 +91,14 @@ class TestToyotaFingerprint:
continue continue
assert platform_code_ecu in [e[0] for e in ecus] assert platform_code_ecu in [e[0] for e in ecus]
def test_fw_format(self, subtests): def test_fw_format(self):
# Asserts: # Asserts:
# - every supported ECU FW version returns one platform code # - every supported ECU FW version returns one platform code
# - every supported ECU FW version has a part number # - every supported ECU FW version has a part number
# - expected parsing of ECU sub-versions # - expected parsing of ECU sub-versions
for car_model, ecus in FW_VERSIONS.items(): 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(): for ecu, fws in ecus.items():
if ecu[0] not in PLATFORM_CODE_ECUS: if ecu[0] not in PLATFORM_CODE_ECUS:
continue continue

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "opendbc" name = "opendbc"
version = "0.2.1" version = "0.3.1"
description = "a Python API for your car" description = "a Python API for your car"
license = "MIT" license = "MIT"
authors = [{ name = "Vehicle Researcher", email = "user@comma.ai" }] authors = [{ name = "Vehicle Researcher", email = "user@comma.ai" }]
@@ -18,7 +18,6 @@ dependencies = [
[project.optional-dependencies] [project.optional-dependencies]
testing = [ testing = [
"comma-car-segments @ https://huggingface.co/datasets/commaai/commaCarSegments/resolve/main/dist/comma_car_segments-0.1.0-py3-none-any.whl",
"cffi", "cffi",
"tree-sitter", "tree-sitter",
"tree-sitter-c", "tree-sitter-c",
@@ -33,7 +32,6 @@ testing = [
"lefthook", "lefthook",
"cpplint", "cpplint",
"codespell", "codespell",
"cppcheck @ git+https://github.com/commaai/dependencies.git@release-cppcheck#subdirectory=cppcheck",
] ]
docs = [ docs = [
"Jinja2", "Jinja2",
@@ -42,6 +40,15 @@ examples = [
"inputs", "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] [build-system]
requires = ["setuptools"] requires = ["setuptools"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
@@ -127,4 +134,6 @@ too-many-positional-arguments = "ignore"
include-package-data = true include-package-data = true
[tool.setuptools.package-data] [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"]

View File

@@ -18,5 +18,5 @@ if ! command -v uv &>/dev/null; then
fi fi
export UV_PROJECT_ENVIRONMENT="$BASEDIR/.venv" export UV_PROJECT_ENVIRONMENT="$BASEDIR/.venv"
uv sync --all-extras --inexact uv sync --all-extras --all-groups --inexact
source "$PYTHONPATH/.venv/bin/activate" source "$PYTHONPATH/.venv/bin/activate"

18
uv.lock generated
View File

@@ -382,7 +382,7 @@ wheels = [
[[package]] [[package]]
name = "opendbc" name = "opendbc"
version = "0.2.1" version = "0.3.1"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "numpy" }, { name = "numpy" },
@@ -401,8 +401,6 @@ examples = [
testing = [ testing = [
{ name = "cffi" }, { name = "cffi" },
{ name = "codespell" }, { name = "codespell" },
{ name = "comma-car-segments" },
{ name = "cppcheck" },
{ name = "cpplint" }, { name = "cpplint" },
{ name = "gcovr" }, { name = "gcovr" },
{ name = "hypothesis" }, { name = "hypothesis" },
@@ -415,12 +413,16 @@ testing = [
{ name = "zstandard" }, { name = "zstandard" },
] ]
[package.dev-dependencies]
testing = [
{ name = "comma-car-segments" },
{ name = "cppcheck" },
]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "cffi", marker = "extra == 'testing'" }, { name = "cffi", marker = "extra == 'testing'" },
{ name = "codespell", 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 = "cpplint", marker = "extra == 'testing'" },
{ name = "gcovr", marker = "extra == 'testing'" }, { name = "gcovr", marker = "extra == 'testing'" },
{ name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" },
@@ -440,6 +442,12 @@ requires-dist = [
] ]
provides-extras = ["testing", "docs", "examples"] 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]] [[package]]
name = "pycapnp" name = "pycapnp"
version = "2.1.0" version = "2.1.0"