diff --git a/opendbc_repo/opendbc/car/toyota/radar_filter.py b/opendbc_repo/opendbc/car/toyota/radar_filter.py index ce3c173581..88ffaf3587 100644 --- a/opendbc_repo/opendbc/car/toyota/radar_filter.py +++ b/opendbc_repo/opendbc/car/toyota/radar_filter.py @@ -16,6 +16,13 @@ PROFILES = { def hardware_supported(candidate, fingerprint): # The filter's advertisement is a hardware discriminator, not an actuation # permission or a copy of ACC_CONTROL to decode under a different checksum. + # The 2025 radar filter observed in the field uses a four-byte 2FF + # advertisement. It is distinct from the legacy eight-byte SDSU state. + # Panda discovery may also expose its forwarded camera-bus copy. + if (candidate == CAR.TOYOTA_RAV4_TSS2_2023 and fingerprint.get(0, {}).get(0x2FF) == 4 and + not any(address in messages for messages in fingerprint.values() for address in (0x201, 0x2AA)) and + all(bus in (0, 2) and messages[0x2FF] == 4 for bus, messages in fingerprint.items() if 0x2FF in messages)): + return True return (candidate in PROFILES and fingerprint.get(0, {}).get(0x2AA) == 8 and not any(0x2AA in frames for bus, frames in fingerprint.items() if bus != 0) and not any(addr in frames for frames in fingerprint.values() for addr in (0x201, 0x2FF))) diff --git a/opendbc_repo/opendbc/car/toyota/values.py b/opendbc_repo/opendbc/car/toyota/values.py index d43049be31..3ea35b917f 100644 --- a/opendbc_repo/opendbc/car/toyota/values.py +++ b/opendbc_repo/opendbc/car/toyota/values.py @@ -82,7 +82,7 @@ class ToyotaFlags(IntFlag): RAISED_ACCEL_LIMIT = 1024 SECOC = 2048 AUTO_BRAKE_HOLD = 4096 - RADAR_CAN_FILTER = 1 << 18 # Bus-0 0x2AA radar adapter; distinct from smartDSU + RADAR_CAN_FILTER = 1 << 18 # Exact radar adapter; includes four-byte 2FF, distinct from legacy SDSU status GAS_INTERCEPTOR = 1 << 17 # Exact paired pedal owner ZSS = 1 << 16 # Detected Prius secondary steering sensor; host-only, no safetyParam allocation diff --git a/openpilot/starpilot/car/toyota/tests/data/rav4_2025_filter_discovery.json b/openpilot/starpilot/car/toyota/tests/data/rav4_2025_filter_discovery.json new file mode 100644 index 0000000000..a693945de9 --- /dev/null +++ b/openpilot/starpilot/car/toyota/tests/data/rav4_2025_filter_discovery.json @@ -0,0 +1,63 @@ +{ + "platform": "TOYOTA_RAV4_TSS2_2023", + "firmware": [ + { + "ecu": "eps", + "fwVersion": "38393635423432333731000000000000", + "address": 1953, + "subAddress": 0, + "brand": "toyota", + "bus": 0, + "logging": false + }, + { + "ecu": "engine", + "fwVersion": "0138393636333441533830303100000000", + "address": 1792, + "subAddress": 0, + "brand": "toyota", + "bus": 0, + "logging": false + }, + { + "ecu": "abs", + "fwVersion": "0146313532363432463831303000000000", + "address": 1968, + "subAddress": 0, + "brand": "toyota", + "bus": 0, + "logging": false + }, + { + "ecu": "fwdCamera", + "fwVersion": "023836343646305230353330300000000038363436473052303233303000000000", + "address": 1872, + "subAddress": 109, + "brand": "toyota", + "bus": 0, + "logging": false + }, + { + "ecu": "fwdRadar", + "fwVersion": "0138383231463052303331303000000000", + "address": 1872, + "subAddress": 15, + "brand": "toyota", + "bus": 0, + "logging": false + } + ], + "advertisements": [ + { + "bus": 0, + "address": 767, + "data": "00000409" + }, + { + "bus": 2, + "address": 767, + "data": "00000409" + } + ], + "scope": "Anonymized 2025 RAV4 Hybrid discovery evidence. Four-byte filter presence is not the legacy eight-byte SDSU status signal. No VIN, GPS, credentials or driver data." +} diff --git a/openpilot/starpilot/car/toyota/tests/test_rav4_2025_filter_advert.py b/openpilot/starpilot/car/toyota/tests/test_rav4_2025_filter_advert.py new file mode 100644 index 0000000000..718bf6d60f --- /dev/null +++ b/openpilot/starpilot/car/toyota/tests/test_rav4_2025_filter_advert.py @@ -0,0 +1,60 @@ +import json +from pathlib import Path + +import pytest +from opendbc.car import gen_empty_fingerprint, structs +from opendbc.car.fw_versions import match_fw_to_car +from opendbc.car.toyota.interface import CarInterface +from opendbc.car.toyota.radar_filter import hardware_supported, configured +from opendbc.car.toyota.values import CAR, ToyotaFlags +from openpilot.starpilot.car.toyota.aol import qualified + +DATA=Path(__file__).with_name('data')/'rav4_2025_filter_discovery.json' + + +def observed(): + evidence=json.loads(DATA.read_text()) + fw=[structs.CarParams.CarFw(**{**f,'fwVersion':bytes.fromhex(f['fwVersion'])}) for f in evidence['firmware']] + fp=gen_empty_fingerprint() + for f in evidence['advertisements']: + fp[f['bus']][f['address']]=len(bytes.fromhex(f['data'])) + return fw,fp + + +@pytest.mark.parametrize('alpha',[False,True]) +def test_observed_firmware_and_filter_keep_radar_enabled(alpha): + fw,fp=observed() + exact,found=match_fw_to_car(fw,'',log=False) + assert exact and found=={CAR.TOYOTA_RAV4_TSS2_2023} + cp=CarInterface.get_params(CAR.TOYOTA_RAV4_TSS2_2023,fp,fw,alpha,False,False) + old=gen_empty_fingerprint() + old[0][0x2AA]=8 + canonical=CarInterface.get_params(CAR.TOYOTA_RAV4_TSS2_2023,old,fw,alpha,False,False) + assert cp.to_dict()==canonical.to_dict() + assert hardware_supported(CAR.TOYOTA_RAV4_TSS2_2023,fp) and configured(cp) and qualified(cp) + assert cp.flags & ToyotaFlags.RADAR_CAN_FILTER and not cp.flags & ToyotaFlags.DISABLE_RADAR + assert cp.safetyConfigs[0].safetyParam==(0x1449 if alpha else 0x1649) + + +@pytest.mark.parametrize('bad',['missing_bus0','wrong_bus','bad_dlc0','bad_dlc2','pedal_mix','mixed_advertisement','legacy_sdsu','other_model']) +def test_observed_filter_admission_rejects_other_topologies(bad): + _,fp=observed() + candidate=CAR.TOYOTA_RAV4_TSS2_2023 + if bad=='missing_bus0': + fp[0].clear() + elif bad=='wrong_bus': + fp[1][0x2FF]=4 + elif bad=='bad_dlc0': + fp[0][0x2FF]=3 + elif bad=='bad_dlc2': + fp[2][0x2FF]=8 + elif bad=='pedal_mix': + fp[0][0x201]=6 + elif bad=='mixed_advertisement': + fp[0][0x2AA]=8 + elif bad=='legacy_sdsu': + fp[0][0x2FF]=8 + fp[2].clear() + elif bad=='other_model': + candidate=CAR.TOYOTA_RAV4H + assert not hardware_supported(candidate,fp) diff --git a/openpilot/starpilot/car/toyota/tests/test_rav4_2025_filter_callers.py b/openpilot/starpilot/car/toyota/tests/test_rav4_2025_filter_callers.py new file mode 100644 index 0000000000..15c4502a67 --- /dev/null +++ b/openpilot/starpilot/car/toyota/tests/test_rav4_2025_filter_callers.py @@ -0,0 +1,17 @@ +import pytest +from opendbc.car.toyota.values import CAR +from openpilot.starpilot.car.toyota.tests.test_rav4_2025_filter_advert import observed +from openpilot.starpilot.car.toyota.tests import test_rav4_angle_filter_callers as helpers +from openpilot.starpilot.car.toyota.tests.test_toyota_cpp_transport import host_heartbeat_bridge as host_heartbeat_bridge + +source_matched_native=helpers.source_matched_native +daemon_gc_scope=helpers.daemon_gc_scope + + +@pytest.mark.parametrize('alpha,disable,radar_mode',[(False,False,0),(True,False,0),(True,False,1),(True,False,2),(True,True,1)]) +@pytest.mark.parametrize('aol',[False,True]) +def test_recorded_2025_filter_actual_callers(alpha,disable,radar_mode,aol,tmp_path,host_heartbeat_bridge): + fw,_=observed() + advertisements=[(0x2FF,bytes.fromhex('00000409'),bus) for bus in (0,2)] + helpers.run_case(CAR.TOYOTA_RAV4_TSS2_2023,alpha,disable,radar_mode,tmp_path,aol=aol, + bridge=host_heartbeat_bridge,filter_advertisements=advertisements,recorded_firmware=fw) diff --git a/openpilot/starpilot/car/toyota/tests/test_rav4_angle_filter_callers.py b/openpilot/starpilot/car/toyota/tests/test_rav4_angle_filter_callers.py index 1540ea4676..53ab4abc5d 100644 --- a/openpilot/starpilot/car/toyota/tests/test_rav4_angle_filter_callers.py +++ b/openpilot/starpilot/car/toyota/tests/test_rav4_angle_filter_callers.py @@ -36,7 +36,8 @@ def test_rav4_filter_actual_callers(alpha, disable, radar_mode, tmp_path): run_case(CAR.TOYOTA_RAV4_TSS2_2023, alpha, disable, radar_mode, tmp_path) -def run_case(candidate, alpha, disable, radar_mode, tmp_path, *, aol=True, has_filter=True, bridge=None): +def run_case(candidate, alpha, disable, radar_mode, tmp_path, *, aol=True, has_filter=True, bridge=None, + filter_advertisements=None, recorded_firmware=None): helpers.SYNTHETIC_LAST.clear() helpers.NATIVE_LAST.clear() safety = libsafety_py.libsafety @@ -77,12 +78,13 @@ def run_case(candidate, alpha, disable, radar_mode, tmp_path, *, aol=True, has_f ('DisableOpenpilotLongitudinal', disable), ('AlwaysOnLateral', aol), ('SafeMode', False)]: params.put_bool(key, value, block=True) params.put('ToyotaRadarMode', radar_mode, block=True) - fw = helpers.firmware(candidate) + fw = helpers.firmware(candidate) if recorded_firmware is None else recorded_firmware exact, found = match_fw_to_car(fw, '', log=False) assert exact and found == {candidate} fp = gen_empty_fingerprint() - if has_filter: - fp[0][0x2AA] = 8 + advertisements = ([(0x2AA, bytes(8), 0)] if has_filter else []) if filter_advertisements is None else filter_advertisements + for address, data, bus in advertisements: + fp[bus][address] = len(data) observed = (candidate, fp, '0' * 17, fw, structs.CarParams.FingerprintSource.fw, True) with (patch.object(car_helpers, 'fingerprint', return_value=observed), patch.object(card_module, 'can_comm_callbacks', return_value=(diag_recv, diag_send)), @@ -143,7 +145,7 @@ def run_case(candidate, alpha, disable, radar_mode, tmp_path, *, aol=True, has_f packer.make_can_msg('EPS_STATUS', 0, {'LKA_STATE': 9 if fault else 1, 'LTA_STATE': 9 if fault else 1}), packer.make_can_msg('PCS_HUD', 0, {}), # The physical filter advertises itself; it does not grant controls. - *([(0x2AA, bytes(8), 0)] if has_filter else [])] + *advertisements] if not has_filter and not long: frames.append(packer.make_can_msg('ACC_CONTROL', 0, {'ACC_TYPE': 1})) # Stock AEB/PCS traffic must not trip the ACC relay check. diff --git a/prebuilt.json b/prebuilt.json index 3ec7a918b6..20e3ae303c 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "408bff97273ca6b8bb9f4ce499b11438df2c64c5e2f8fa922c3c641d6da56a97", "schema_version": 1} +{"protected_inventory_sha256": "9fa186b72e7a6036ed6d85896b79c0919308108e1b71d7ccad3d44dbf6c28c5a", "schema_version": 1}