diff --git a/opendbc_repo/opendbc/car/toyota/interface.py b/opendbc_repo/opendbc/car/toyota/interface.py index 5089832f0c..cdb37c90ed 100644 --- a/opendbc_repo/opendbc/car/toyota/interface.py +++ b/opendbc_repo/opendbc/car/toyota/interface.py @@ -150,6 +150,8 @@ class CarInterface(CarInterfaceBase): ret.openpilotLongitudinalControl = ((bool(ret.flags & ToyotaFlags.TSS2) and not (ret.flags & ToyotaFlags.RADAR_ACC)) or bool(ret.flags & ToyotaFlags.DISABLE_RADAR.value) or (radar_filter and alpha_long)) + from opendbc.car.toyota import matrix_filter + manual_matrix_filter = any(matrix_filter.hardware_supported(candidate, fingerprint, car_fw, length=size) for size in (4, 8)) if candidate in (CAR.TOYOTA_MATRIX_RETROFIT, CAR.TOYOTA_PRIUS_RETROFIT): # These manually selected retrofits currently own stock PCM longitudinal. # Observed takeover hardware needs its paired controller/native port. @@ -167,7 +169,7 @@ class CarInterface(CarInterfaceBase): if smart_dsu: ret.flags |= int(ToyotaFlags.LONG_FILTER) ret.safetyConfigs[0].safetyParam |= int(ToyotaSafetyFlags.LONG_FILTER) - elif takeover: + elif takeover and not manual_matrix_filter: ret.dashcamOnly = True ret.openpilotLongitudinalControl = smart_dsu ret.alphaLongitudinalAvailable = False diff --git a/opendbc_repo/opendbc/car/toyota/legacy_advert_filter.py b/opendbc_repo/opendbc/car/toyota/legacy_advert_filter.py index 6a608989c7..39af781f42 100644 --- a/opendbc_repo/opendbc/car/toyota/legacy_advert_filter.py +++ b/opendbc_repo/opendbc/car/toyota/legacy_advert_filter.py @@ -14,6 +14,9 @@ AUTO_RESUME_CARS = frozenset((CAR.TOYOTA_RAV4H, CAR.TOYOTA_HIGHLANDER, CAR.TOYOT def hardware_supported(candidate, fingerprint, car_fw): + if candidate == CAR.TOYOTA_MATRIX_RETROFIT: + from opendbc.car.toyota import matrix_filter + return matrix_filter.hardware_supported(candidate, fingerprint, car_fw, length=4) if (candidate not in PROFILES or fingerprint.get(0, {}).get(0x2FF) != 4 or any(address in messages for messages in fingerprint.values() for address in (0x201, 0x2AA)) or any(bus not in (0, 2) or messages[0x2FF] != 4 for bus, messages in fingerprint.items() if 0x2FF in messages) or @@ -44,6 +47,9 @@ def apply(cp): def configured(cp): + if cp is not None and cp.carFingerprint == CAR.TOYOTA_MATRIX_RETROFIT: + from opendbc.car.toyota import matrix_filter + return matrix_filter.configured(cp, length=4) if (cp is None or cp.brand != 'toyota' or cp.carFingerprint not in PROFILES or cp.notCar or cp.passive or cp.dashcamOnly or cp.secOcRequired or not cp.pcmCruise or len(cp.safetyConfigs) != 1 or cp.steerControlType != structs.CarParams.SteerControlType.torque): diff --git a/opendbc_repo/opendbc/car/toyota/legacy_sdsu.py b/opendbc_repo/opendbc/car/toyota/legacy_sdsu.py index 1e4f4e189d..b475d7deb9 100644 --- a/opendbc_repo/opendbc/car/toyota/legacy_sdsu.py +++ b/opendbc_repo/opendbc/car/toyota/legacy_sdsu.py @@ -6,6 +6,7 @@ from opendbc.car.toyota.values import CAR, ToyotaFlags PROFILES = { CAR.TOYOTA_COROLLA: (0x9158, 0x9358), + CAR.TOYOTA_MATRIX_RETROFIT: (0x9158, 0x9358), CAR.TOYOTA_RAV4: (0x9149, 0x9349), CAR.LEXUS_ES: (0x9149, 0x9349), CAR.TOYOTA_PRIUS_V: (0x9164, 0x9364), @@ -19,6 +20,9 @@ ALT_MAIN_CARS = frozenset((CAR.LEXUS_GS_F, CAR.LEXUS_IS, CAR.LEXUS_RC)) def hardware_supported(candidate, fingerprint, car_fw): + if candidate == CAR.TOYOTA_MATRIX_RETROFIT: + from opendbc.car.toyota import matrix_filter + return matrix_filter.hardware_supported(candidate, fingerprint, car_fw, length=8) if ( candidate not in PROFILES or fingerprint.get(0, {}).get(0x2FF) != 8 @@ -54,6 +58,9 @@ def apply(cp): def configured(cp): + if cp is not None and cp.carFingerprint == CAR.TOYOTA_MATRIX_RETROFIT: + from opendbc.car.toyota import matrix_filter + return matrix_filter.configured(cp, length=8) if ( cp is None or cp.brand != "toyota" diff --git a/opendbc_repo/opendbc/car/toyota/matrix_filter.py b/opendbc_repo/opendbc/car/toyota/matrix_filter.py new file mode 100644 index 0000000000..647f14ec78 --- /dev/null +++ b/opendbc_repo/opendbc/car/toyota/matrix_filter.py @@ -0,0 +1,37 @@ +"""Explicit manual Matrix enrollment into existing EPS88 filter protocols. + +This identity is manually selected, not a firmware-catalog match. The filter +never supplies physical MAIN or EPS authority. Their current native sources, +checksums, health checks and permissions remain required. +""" +from opendbc.car import structs +from opendbc.car.toyota.values import CAR, ToyotaFlags + +PROTOCOLS = {8: (0x9158, 0x9358), 4: (0x9558, 0x9758)} + + +def hardware_supported(candidate, fingerprint, car_fw, *, length): + if (candidate != CAR.TOYOTA_MATRIX_RETROFIT or length not in PROTOCOLS or + fingerprint.get(0, {}).get(0x2FF) != length or + any(0x2FF in messages and (bus not in (0, 2) or messages[0x2FF] != length) + for bus, messages in fingerprint.items()) or + any(address in messages for messages in fingerprint.values() for address in (0x201, 0x2AA)) or + any(fingerprint.get(0, {}).get(address, size) != size for address, size in ((0x1D3, 8), (0x262, 5))) or + any(fingerprint.get(2, {}).get(address, 8) != 8 for address in (0x343, 0x4CB))): + return False + # Preserve the manual-selection boundary without inventing catalog replies. + critical = (structs.CarParams.Ecu.eps, structs.CarParams.Ecu.fwdCamera, structs.CarParams.Ecu.fwdRadar) + return not any(fw.ecu in critical and (fw.logging or fw.bus != 0 or fw.brand != 'toyota' or not bytes(fw.fwVersion)) + for fw in car_fw) + + +def configured(cp, *, length): + if (cp is None or cp.brand != 'toyota' or cp.carFingerprint != CAR.TOYOTA_MATRIX_RETROFIT or + length not in PROTOCOLS or cp.notCar or cp.passive or cp.dashcamOnly or cp.secOcRequired or + not cp.pcmCruise or cp.steerControlType != structs.CarParams.SteerControlType.torque or + len(cp.safetyConfigs) != 1 or cp.autoResumeSng or cp.alphaLongitudinalAvailable): + return False + flags = ToyotaFlags.LONG_FILTER if length == 8 else ToyotaFlags.RADAR_CAN_FILTER + config = cp.safetyConfigs[0] + return (cp.flags == int(flags) and config.safetyModel == structs.CarParams.SafetyModel.toyota and + config.safetyParam == PROTOCOLS[length][not cp.openpilotLongitudinalControl]) diff --git a/opendbc_repo/opendbc/car/toyota/tests/test_matrix_filter.py b/opendbc_repo/opendbc/car/toyota/tests/test_matrix_filter.py new file mode 100644 index 0000000000..3284411264 --- /dev/null +++ b/opendbc_repo/opendbc/car/toyota/tests/test_matrix_filter.py @@ -0,0 +1,80 @@ +"""Manual Matrix filter enrollment and unchanged factory values.""" +import struct + +import pytest +from opendbc.car import gen_empty_fingerprint, structs +from opendbc.car.toyota import legacy_advert_filter, legacy_sdsu, matrix_filter +from opendbc.car.toyota.interface import CarInterface +from opendbc.car.toyota.values import CAR + + +def factory(fingerprint, firmware=(), alpha=False): + return CarInterface.get_params(CAR.TOYOTA_MATRIX_RETROFIT, fingerprint, list(firmware), alpha, False, False) + + +@pytest.mark.parametrize('length', (4, 8)) +@pytest.mark.parametrize('alpha', (False, True)) +@pytest.mark.parametrize('stock', (False, True)) +def test_manual_matrix_filter_factory_preserves_tunes(length, alpha, stock): + baseline = factory(gen_empty_fingerprint(), alpha=alpha) + fingerprint = gen_empty_fingerprint() + fingerprint[0][0x2FF] = length + cp = factory(fingerprint, alpha=alpha) + assert not cp.dashcamOnly, 'MANUAL_MATRIX_FILTER_NOT_ADMITTED' + assert cp.openpilotLongitudinalControl + assert matrix_filter.configured(cp, length=length) + if stock: + owner = legacy_sdsu if length == 8 else legacy_advert_filter + assert owner.prepare_stock(cp) + assert cp.safetyConfigs[0].safetyParam == matrix_filter.PROTOCOLS[length][stock] + assert cp.openpilotLongitudinalControl == (not stock) + assert not cp.autoResumeSng and not cp.alphaLongitudinalAvailable + assert cp.lateralTuning.torque.latAccelFactor == struct.unpack('= 220: + assert cc.latActive and cc.longActive == (not stock) + assert safety.aol_get_permission_mask() == (1 if stock else 3) + if scenario == 'physical' and tick == 425: + assert str(cs.gearShifter) == 'reverse' + assert not cc.latActive and not cc.longActive and safety.aol_get_permission_mask() == 0 + + helpers.run_case(stock, scenario, candidate=CAR.TOYOTA_MATRIX_RETROFIT, + bridge=host_heartbeat_bridge, input_clock=True, brake_stop_check_start=360, + sdsu_words=matrix_filter.PROTOCOLS[length], firmware_override=[], + fingerprint_frames=[(0x2FF, bytes(length), 0)], manual_selection=True, manual_filter_length=length, + filter_advert=length == 4, physical_frames=physical, on_state=state, on_sample=observe) diff --git a/openpilot/starpilot/car/toyota/tests/test_rav4h_caller_native.py b/openpilot/starpilot/car/toyota/tests/test_rav4h_caller_native.py index 81a68c2818..32ca747782 100644 --- a/openpilot/starpilot/car/toyota/tests/test_rav4h_caller_native.py +++ b/openpilot/starpilot/car/toyota/tests/test_rav4h_caller_native.py @@ -228,7 +228,8 @@ def control_inputs(controls, published, state, sd_events, native, now): def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, bridge=None, input_clock=False, brake_stop_check_start=350, sdsu_words=(0x9049, 0x9249), alpha_long=False, disable_setting=None, physical_frames=None, angle=False, firmware_override=None, assert_hybrid_release=False, sdsu_mirror=False, on_sample=None, camera_messages=False, filter_advert=False, - brake_pause_mps=None, native_rx_expected=None, on_state=None, fingerprint_frames=None, manual_selection=False, native_tx_expected=None): + brake_pause_mps=None, native_rx_expected=None, on_state=None, fingerprint_frames=None, manual_selection=False, native_tx_expected=None, + manual_filter_length=None): SYNTHETIC_LAST.clear() NATIVE_LAST.clear() safety = libsafety_py.libsafety @@ -248,13 +249,18 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, params.put('AolBrakePauseSpeedMps', float(brake_pause_mps), block=True) fw = firmware(candidate) if firmware_override is None else firmware_override if manual_selection: - assert stock and fingerprint_frames == [] - if candidate == CAR.TOYOTA_MATRIX_RETROFIT: + if manual_filter_length is not None: + assert candidate == CAR.TOYOTA_MATRIX_RETROFIT and manual_filter_length in (4, 8) assert not fw and firmware_override == [] + assert fingerprint_frames == [(0x2FF, bytes(manual_filter_length), 0)] else: - assert candidate == CAR.TOYOTA_PRIUS_RETROFIT and len(fw) == 1 - assert fw[0].ecu == structs.CarParams.Ecu.eps and fw[0].address == 0x7A1 and fw[0].bus == 0 - assert bytes(fw[0].fwVersion) == b'8965B47070' + bytes(6) + assert stock and fingerprint_frames == [] + if candidate == CAR.TOYOTA_MATRIX_RETROFIT: + assert not fw and firmware_override == [] + else: + assert candidate == CAR.TOYOTA_PRIUS_RETROFIT and len(fw) == 1 + assert fw[0].ecu == structs.CarParams.Ecu.eps and fw[0].address == 0x7A1 and fw[0].bus == 0 + assert bytes(fw[0].fwVersion) == b'8965B47070' + bytes(6) else: exact, found = match_fw_to_car(fw, '', log=False) assert exact and found == {candidate} diff --git a/prebuilt.json b/prebuilt.json index 9e9159fec9..eaf51eb4e2 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "f66ce7991c8b276625982b5f71707ca60643482518ba9cc76b415cae47e59edc", "schema_version": 1} +{"protected_inventory_sha256": "bdf5369d5a70cda8e2cdb146d9cbb9021beb511c89c6d5f936a5366e5e8e2647", "schema_version": 1} diff --git a/tools/ci/run_vehicle_tests.py b/tools/ci/run_vehicle_tests.py index 1a86785096..ebb4a5b4f4 100644 --- a/tools/ci/run_vehicle_tests.py +++ b/tools/ci/run_vehicle_tests.py @@ -27,6 +27,8 @@ AOL_LIFECYCLE_TARGETS = ( 'openpilot/starpilot/aol/tests/test_hkg_early_long_withdrawal.py', 'openpilot/starpilot/car/toyota/tests/test_prius_retrofit_stock.py', 'openpilot/starpilot/car/toyota/tests/test_prius_retrofit_stock_callers.py', + 'opendbc_repo/opendbc/car/toyota/tests/test_matrix_filter.py', + 'openpilot/starpilot/car/toyota/tests/test_matrix_filter_callers.py', 'openpilot/starpilot/aol/tests/test_crossmake_native_admission.py', 'openpilot/starpilot/aol/tests/test_hkg_source_policy_cache.py', 'openpilot/starpilot/aol/tests/test_hkg_native_admission.py', @@ -211,6 +213,8 @@ DEBUG_SAFETY_CLASS_TARGETS = { RELEASE_TARGETS = ( 'openpilot/starpilot/car/toyota/tests/test_prius_retrofit_stock.py', 'openpilot/starpilot/car/toyota/tests/test_prius_retrofit_stock_callers.py', + 'opendbc_repo/opendbc/car/toyota/tests/test_matrix_filter.py', + 'openpilot/starpilot/car/toyota/tests/test_matrix_filter_callers.py', 'opendbc_repo/opendbc/car/hyundai/tests/test_long_cp_copy.py', 'opendbc_repo/opendbc/car/ford/tests/test_mache_display_handoff.py', 'opendbc_repo/opendbc/safety/tests/test_ford_mache_display.py',