diff --git a/opendbc_repo/opendbc/car/car_helpers.py b/opendbc_repo/opendbc/car/car_helpers.py index 0e4a5fc2fd..e6d1e18b0c 100644 --- a/opendbc_repo/opendbc/car/car_helpers.py +++ b/opendbc_repo/opendbc/car/car_helpers.py @@ -278,6 +278,10 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip carlog.error({"event": "car doesn't match any fingerprints", "fingerprints": repr(fingerprints)}) candidate = "MOCK" + if source != CarParams.FingerprintSource.fixed and candidate == 'TOYOTA_PRIUS': + from opendbc.car.toyota.values import automatic_prius_candidate + candidate = automatic_prius_candidate(candidate, car_fw) + CarInterface = interfaces[candidate] params_options = {'auto_pedal': False} if source == CarParams.FingerprintSource.fixed and candidate in interface_names['gm'] else {} CP: CarParams = CarInterface.get_params(candidate, fingerprints, car_fw, alpha_long_allowed, is_release, docs=False, **params_options) diff --git a/opendbc_repo/opendbc/car/toyota/tests/test_retrofit_discovery.py b/opendbc_repo/opendbc/car/toyota/tests/test_retrofit_discovery.py new file mode 100644 index 0000000000..7852a4043b --- /dev/null +++ b/opendbc_repo/opendbc/car/toyota/tests/test_retrofit_discovery.py @@ -0,0 +1,83 @@ +from unittest.mock import patch + +import pytest + +from opendbc.car import car_helpers, structs +from opendbc.car.toyota.values import CAR + + +def rack(**changes): + values = {'ecu': structs.CarParams.Ecu.eps, 'fwVersion': b'8965B47070' + bytes(6), + 'address': 0x7A1, 'bus': 0, 'brand': 'toyota', 'subAddress': 0, 'logging': False} + values.update(changes) + return structs.CarParams.CarFw.new_message(**values) + + +def identify(fw, *, release=False, forced=None, adapter=None, source=None): + # Native DSU commands are present on bus0 and mirrored on the camera bus. + fp = {0: {0x343: 8, 0x4CB: 8, 0x1D2: 8, 0x1D3: 8, 0x262: 8}, 1: {}, + 2: {0x343: 8, 0x4CB: 8}} + if adapter is not None: + fp[0][0x2FF] = adapter + detected = (CAR.TOYOTA_PRIUS, fp, '00000000000000000', fw, + structs.CarParams.FingerprintSource.fw if source is None else source, True) + with patch.object(car_helpers, 'fingerprint', return_value=detected): + return car_helpers.get_car(lambda wait_for_one=False: [], lambda _: None, lambda _: None, + False, release, forced_candidate=forced).CP + + +@pytest.mark.parametrize('release', [False, True]) +@pytest.mark.parametrize('source', [structs.CarParams.FingerprintSource.fw, structs.CarParams.FingerprintSource.can]) +def test_automatic_stock_retrofit_matches_explicit_profile(release, source): + automatic = identify([rack()], release=release, source=source) + explicit = identify([rack()], release=release, forced=CAR.TOYOTA_PRIUS_RETROFIT) + assert automatic.carFingerprint == CAR.TOYOTA_PRIUS_RETROFIT, 'PRIUS_RETROFIT_AUTO_IDENTITY' + assert not automatic.dashcamOnly + assert automatic.safetyConfigs[0].safetyParam == 585 + assert not automatic.openpilotLongitudinalControl + assert automatic.pcmCruise + assert not automatic.alphaLongitudinalAvailable + assert automatic.fingerprintSource == source + a, b = automatic.to_dict(), explicit.to_dict() + a.pop('fingerprintSource') + b.pop('fingerprintSource') + assert a == b + + +@pytest.mark.parametrize('release', [False, True]) +@pytest.mark.parametrize('length', [4, 8, 7]) +def test_automatic_retrofit_retains_existing_adapter_admission(release, length): + automatic = identify([rack()], release=release, adapter=length) + explicit = identify([rack()], release=release, adapter=length, forced=CAR.TOYOTA_PRIUS_RETROFIT) + assert automatic.carFingerprint == CAR.TOYOTA_PRIUS_RETROFIT, 'PRIUS_RETROFIT_AUTO_IDENTITY' + a, b = automatic.to_dict(), explicit.to_dict() + a.pop('fingerprintSource') + b.pop('fingerprintSource') + assert a == b + assert automatic.dashcamOnly == (length == 7) + + +@pytest.mark.parametrize('changes', [{'fwVersion': b'8965B47060' + bytes(6)}, {'bus': 1}, {'brand': 'honda'}, + {'address': 0x7A0}, {'subAddress': 1}, {'logging': True}, + {'fwVersion': b'8965B47070'}]) +def test_unproven_rack_does_not_change_identity(changes): + cp = identify([rack(**changes)]) + assert cp.carFingerprint == CAR.TOYOTA_PRIUS + + +def test_missing_or_conflicting_eps_does_not_change_identity(): + for fw in ([], [rack(), rack(fwVersion=b'8965B47060' + bytes(6))]): + assert identify(fw).carFingerprint == CAR.TOYOTA_PRIUS + + +def test_manual_original_choice_is_preserved_and_stays_blocked(): + cp = identify([rack()], forced=CAR.TOYOTA_PRIUS) + assert cp.carFingerprint == CAR.TOYOTA_PRIUS + assert cp.dashcamOnly + assert cp.fingerprintSource == structs.CarParams.FingerprintSource.fixed + + +def test_developer_fixed_fingerprint_is_preserved(): + cp = identify([rack()], source=structs.CarParams.FingerprintSource.fixed) + assert cp.carFingerprint == CAR.TOYOTA_PRIUS + assert cp.dashcamOnly diff --git a/opendbc_repo/opendbc/car/toyota/values.py b/opendbc_repo/opendbc/car/toyota/values.py index e0ea229275..deeef02119 100644 --- a/opendbc_repo/opendbc/car/toyota/values.py +++ b/opendbc_repo/opendbc/car/toyota/values.py @@ -476,6 +476,16 @@ def get_platform_codes(fw_versions: list[bytes]) -> dict[bytes, set[bytes]]: return dict(codes) +def automatic_prius_candidate(candidate, car_fw): + if candidate != CAR.TOYOTA_PRIUS: + return candidate + eps = [fw for fw in car_fw if fw.ecu == Ecu.eps] + if eps and all(not fw.logging and fw.bus == 0 and fw.brand == 'toyota' and fw.address == 0x7A1 and + not fw.subAddress and bytes(fw.fwVersion) == b'8965B47070' + bytes(6) for fw in eps): + return CAR.TOYOTA_PRIUS_RETROFIT + return candidate + + def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str]: candidates = set() diff --git a/prebuilt.json b/prebuilt.json index 40ffb34a39..a74819ff5a 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "72fdf29ec9a457fecef91db32719ed60947bdc93c11598a4e3b0f32b0e5bc188", "schema_version": 1} +{"protected_inventory_sha256": "68d6880928852d9065453f4015fdfb139ea8759d2ce8d12c4d23cf9cd6285244", "schema_version": 1} diff --git a/tools/ci/run_vehicle_tests.py b/tools/ci/run_vehicle_tests.py index 9047dd9873..13f782f133 100644 --- a/tools/ci/run_vehicle_tests.py +++ b/tools/ci/run_vehicle_tests.py @@ -154,6 +154,7 @@ AOL_LIFECYCLE_TARGETS = ( "openpilot/starpilot/car/toyota/tests/test_camera_2aa_native.py", "openpilot/starpilot/car/toyota/tests/test_prius_sdsu_filter.py", "openpilot/starpilot/car/toyota/tests/test_prius_sdsu_callers.py", + "opendbc_repo/opendbc/car/toyota/tests/test_retrofit_discovery.py", "openpilot/starpilot/galaxy/tests/test_prius_sng_settings.py", "openpilot/starpilot/car/toyota/tests/test_sng_resume.py", "openpilot/starpilot/car/toyota/tests/test_sng.py",