diff --git a/opendbc_repo/opendbc/car/toyota/carcontroller.py b/opendbc_repo/opendbc/car/toyota/carcontroller.py index 1100463913..9cac90ccdf 100644 --- a/opendbc_repo/opendbc/car/toyota/carcontroller.py +++ b/opendbc_repo/opendbc/car/toyota/carcontroller.py @@ -142,6 +142,8 @@ class CarController(CarControllerBase): self.brake_hold_active = False def update(self, CC, CS, now_nanos): + from opendbc.car.toyota import dsu_bypass + bypass_long = dsu_bypass.enabled(self.CP) _projection = self.learning_projection_observer if _projection is not None: _projection.begin(CC.actuators.torque, self.frame) @@ -177,6 +179,14 @@ class CarController(CarControllerBase): pedal_owner = pedal.configured(self.CP) if pedal_owner: filter_allowed = filter_allowed and bool(self.pedal_input is not None and self.pedal_input.update(now_nanos) and CS.pedal_ready) + if bypass_long: + preference = getattr(self, 'bypass_input', None) + filter_allowed = filter_allowed and bool( + preference is not None and preference.update(now_nanos) and CS.out.canValid and not CS.out.canTimeout and + CS.out.cruiseState.available and not CS.out.accFaulted and not CS.out.stockAeb and + not CS.out.steerFaultTemporary and not CS.out.steerFaultPermanent and not CS.out.doorOpen and + not CS.out.seatbeltUnlatched and not CS.out.gasPressed and not CS.out.brakePressed and + CS.out.gearShifter in (structs.CarState.GearShifter.drive, structs.CarState.GearShifter.sport)) long_active = CC.longActive and filter_allowed if guarded_advert: long_active = long_active and not (CS.out.seatbeltUnlatched or CS.out.gasPressed or CS.out.brakePressed) @@ -184,9 +194,12 @@ class CarController(CarControllerBase): stopping = actuators.longControlState == LongCtrlState.stopping hud_control = CC.hudControl pcm_cancel_cmd = CC.cruiseControl.cancel - if (rav4h_long or pedal_owner or advert_long or original_prius_long) and not filter_allowed and CS.out.cruiseState.enabled: + if (bypass_long or rav4h_long or pedal_owner or advert_long or original_prius_long) and not filter_allowed and CS.out.cruiseState.enabled: pcm_cancel_cmd = True lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE + if dsu_bypass.configured(self.CP): + lat_active = lat_active and CS.out.canValid and not CS.out.canTimeout and CS.out.cruiseState.available and not ( + CS.out.steerFaultTemporary or CS.out.steerFaultPermanent or CS.out.doorOpen) if self.original_prius_filter: lat_active = lat_active and CS.out.canValid and not CS.out.canTimeout and CS.out.cruiseState.available and not ( CS.out.accFaulted or CS.out.steerFaultTemporary or CS.out.steerFaultPermanent or CS.out.doorOpen) @@ -329,7 +342,7 @@ class CarController(CarControllerBase): if not should_resume and CS.out.cruiseState.standstill: self.standstill_req = True - if (rav4h_long or pedal_owner or advert_long or original_prius_long) and (not filter_allowed or not long_active and CS.out.standstill): + if (bypass_long or rav4h_long or pedal_owner or advert_long or original_prius_long) and (not filter_allowed or not long_active and CS.out.standstill): self.standstill_req = True if pedal_owner and (self.CP.carFingerprint in pedal.RADAR_PEDAL_CARS or pedal_composite.configured(self.CP)) and not long_active: @@ -348,7 +361,7 @@ class CarController(CarControllerBase): # internal PCM gas command can get stuck unwinding from negative accel so we apply a generous rate limit pcm_accel_cmd = actuators.accel if filter_allowed else 0. - if (self.prius_longitudinal or rav4h_long or advert_long) and not long_active: + if (bypass_long or self.prius_longitudinal or rav4h_long or advert_long) and not long_active: pcm_accel_cmd = 0. if long_active: pcm_accel_cmd = rate_limit(pcm_accel_cmd, self.prev_accel, ACCEL_WINDDOWN_LIMIT, ACCEL_WINDUP_LIMIT) @@ -419,7 +432,7 @@ class CarController(CarControllerBase): self.permit_braking = True self.standstill_req = True - if (rav4h_long or pedal_owner or advert_long or original_prius_long) and not long_active: + if (bypass_long or rav4h_long or pedal_owner or advert_long or original_prius_long) and not long_active: pcm_accel_cmd = 0. main_accel_cmd = 0. if self.CP.flags & ToyotaFlags.SECOC.value else pcm_accel_cmd can_sends.append(toyotacan.create_accel_command(self.packer, main_accel_cmd, pcm_cancel_cmd, self.permit_braking, self.standstill_req, lead, diff --git a/opendbc_repo/opendbc/car/toyota/carstate.py b/opendbc_repo/opendbc/car/toyota/carstate.py index 35daa1dae4..ca927605c6 100644 --- a/opendbc_repo/opendbc/car/toyota/carstate.py +++ b/opendbc_repo/opendbc/car/toyota/carstate.py @@ -2,7 +2,7 @@ import copy import math from opendbc.can import CANDefine, CANParser -from opendbc.car.toyota import pedal_composite, pedal, legacy_sdsu, advert_filter, legacy_advert_filter, prius_sdsu, prius_advert_filter +from opendbc.car.toyota import dsu_bypass, pedal_composite, pedal, legacy_sdsu, advert_filter, legacy_advert_filter, prius_sdsu, prius_advert_filter from opendbc.car.toyota.lexus_is import LexusIsCANParser from opendbc.car import Bus, DT_CTRL, create_button_events, structs from opendbc.car.common.conversions import Conversions as CV @@ -78,7 +78,8 @@ class CarState(CarStateBase): self.pre_collision_2 = copy.copy(cp_cam.vl["PRE_COLLISION_2"]) ret = structs.CarState() - cp_acc = cp_cam if (self.CP.flags & ToyotaFlags.TSS2) and not (self.CP.flags & ToyotaFlags.RADAR_ACC) else cp + bypass = dsu_bypass.configured(self.CP) + cp_acc = cp_cam if bypass or (self.CP.flags & ToyotaFlags.TSS2) and not (self.CP.flags & ToyotaFlags.RADAR_ACC) else cp if not self.CP.flags & ToyotaFlags.SECOC.value: self.gvc = cp.vl["VSC1S07"]["GVC"] @@ -177,6 +178,18 @@ class CarState(CarStateBase): ret.cruiseState.speed = cp.vl["PCM_CRUISE_2"]["SET_SPEED"] * CV.KPH_TO_MS cluster_set_speed = cp.vl["PCM_CRUISE_SM"]["UI_SET_SPEED"] + if bypass: + main = 'DSU_CRUISE' if self.CP.carFingerprint in dsu_bypass.ALT_MAIN_CARS else 'PCM_CRUISE_2' + stamp = cp.ts_nanos[main]['MAIN_ON'] + ret.cruiseState.available &= bool(cp.main_valid and stamp > 0 and 0 <= cp._last_update_nanos - stamp <= 303_031_000) + stamp = cp.ts_nanos['EPS_STATUS']['LKA_STATE'] + ret.steerFaultTemporary |= not (cp.eps_valid and stamp > 0 and 0 <= cp._last_update_nanos - stamp <= 400_000_000) + stamp = cp_cam.ts_nanos['ACC_CONTROL']['ACC_TYPE'] + ret.accFaulted |= not (cp_cam.acc_valid and stamp > 0 and 0 <= cp_cam._last_update_nanos - stamp <= 303_031_000) + ret.accFaulted |= bool(cp_acc.vl['ACC_CONTROL']['ACC_MALFUNCTION'] or cp_acc.vl['ACC_CONTROL']['RADAR_DIRTY'] or cp.ambiguous or cp_cam.ambiguous) + self.acc_type = cp_acc.vl['ACC_CONTROL']['ACC_TYPE'] + ret.stockFcw = bool(cp_acc.vl['PCS_HUD']['FCW']) + # UI_SET_SPEED is always non-zero when main is on, hide until first enable is_metric = cp.vl["BODY_CONTROL_STATE_2"]["UNITS"] in (1, 2) if ret.cruiseState.speed != 0: @@ -317,6 +330,15 @@ class CarState(CarStateBase): parent[Bus.pt] = pedal_composite.ParentPedalParser(parent[Bus.pt]) parent[Bus.alt] = pedal.PedalCANParser(DBC[CP.carFingerprint][Bus.pt], [('GAS_SENSOR', 50)], 0) return parent + if dsu_bypass.configured(CP): + alternate = CP.carFingerprint in dsu_bypass.ALT_MAIN_CARS + main = 'DSU_CRUISE' if alternate else 'PCM_CRUISE_2' + kwargs = {'eps_length': 8 if CP.carFingerprint in dsu_bypass.EPS8_CARS or CP.carFingerprint == CAR.TOYOTA_PRIUS else 5, + 'alternate_main': alternate} + return { + Bus.pt: dsu_bypass.BypassCANParser(DBC[CP.carFingerprint][Bus.pt], [(main, 10), ('EPS_STATUS', 25)], 0, **kwargs), + Bus.cam: dsu_bypass.BypassCANParser(DBC[CP.carFingerprint][Bus.pt], [('ACC_CONTROL', 33)], 2, **kwargs), + } dbc_name = DBC[CP.carFingerprint][Bus.pt] cam_messages = [("RSA1", math.nan)] if "RSA1" in CANDBC(dbc_name).name_to_msg else [] if CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD and uses_toyota_auto_hold_aeb(CP): diff --git a/opendbc_repo/opendbc/car/toyota/dsu_bypass.py b/opendbc_repo/opendbc/car/toyota/dsu_bypass.py new file mode 100644 index 0000000000..4df5973280 --- /dev/null +++ b/opendbc_repo/opendbc/car/toyota/dsu_bypass.py @@ -0,0 +1,149 @@ +"""Camera-bus DSU bypass identity and independent physical source ownership.""" + +from opendbc.can import CANParser +from opendbc.car import structs +from opendbc.car.toyota.toyotacan import toyota_checksum +from opendbc.car.toyota.values import CAR, ToyotaFlags + +EPS5_CARS = frozenset( + (CAR.TOYOTA_AVALON, CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H, CAR.TOYOTA_SIENNA, CAR.LEXUS_ES, CAR.LEXUS_NX, CAR.LEXUS_RX) +) +EPS8_CARS = frozenset((CAR.TOYOTA_AVALON_2019, CAR.TOYOTA_CAMRY, CAR.TOYOTA_CHR, CAR.LEXUS_LS, CAR.TOYOTA_PRIUS_RETROFIT)) +ALT_MAIN_CARS = frozenset((CAR.LEXUS_IS, CAR.LEXUS_RC, CAR.LEXUS_GS_F)) +PROFILES = dict.fromkeys(EPS5_CARS, 0x8849) +PROFILES.update(dict.fromkeys(EPS8_CARS, 0x9849)) +PROFILES.update( + { + CAR.TOYOTA_PRIUS: 0x9842, + CAR.TOYOTA_COROLLA: 0x8958, + CAR.TOYOTA_MATRIX_RETROFIT: 0x8958, + CAR.TOYOTA_PRIUS_V: 0x8964, + CAR.LEXUS_CTH: 0x8964, + CAR.LEXUS_IS: 0xC84D, + CAR.LEXUS_RC: 0xC84D, + CAR.LEXUS_GS_F: 0xC949, + } +) +PROFILES.update({CAR.TOYOTA_RAV4: 0x8949, CAR.LEXUS_ES: 0x8949}) +AUTO_RESUME_CARS = frozenset((CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_PRIUS_V, CAR.TOYOTA_RAV4H, CAR.TOYOTA_SIENNA)) +WORDS = frozenset(word | stock for word in PROFILES.values() for stock in (0, 0x200)) + + +def hardware_supported(candidate, fingerprint, car_fw): + if candidate not in PROFILES or any(address in frames for frames in fingerprint.values() for address in (0x201, 0x2FF, 0x2AA)): + return False + camera = fingerprint.get(2, {}) + if not any(camera.get(address) == 8 for address in (0x343, 0x4CB)) or any(camera.get(address, 8) != 8 for address in (0x343, 0x4CB)): + return False + late_prius = candidate in (CAR.TOYOTA_PRIUS, CAR.TOYOTA_PRIUS_RETROFIT) and any( + fw.ecu == structs.CarParams.Ecu.fwdCamera and bytes(fw.fwVersion).startswith(b'8646F4705') for fw in car_fw + ) + if candidate in (CAR.LEXUS_IS, CAR.TOYOTA_CAMRY) or late_prius: + if not ( + (camera.get(0x343) == 8 and not any(0x343 in fingerprint.get(bus, {}) for bus in (0, 1))) + or (camera.get(0x4CB) == 8 and 0x4CB not in fingerprint.get(0, {})) + ): + return False + if candidate == CAR.TOYOTA_MATRIX_RETROFIT: + # This is a manual fingerprint, not a firmware-catalog identity. + return not car_fw and all(fingerprint.get(0, {}).get(a) == length for a, length in ((0x260, 8), (0x262, 5), (0x224, 8), (0x1D2, 8), (0x1D3, 8))) + from opendbc.car.toyota.fingerprints import FW_VERSIONS + + known = FW_VERSIONS[CAR.TOYOTA_PRIUS if candidate == CAR.TOYOTA_PRIUS_RETROFIT else candidate] + for ecu in (structs.CarParams.Ecu.eps, structs.CarParams.Ecu.fwdCamera, structs.CarParams.Ecu.fwdRadar): + if not any(key[0] == ecu for key in known) and candidate != CAR.TOYOTA_PRIUS_RETROFIT: + continue + observed = [fw for fw in car_fw if fw.ecu == ecu] + if not observed: + return False + for fw in observed: + version = bytes(fw.fwVersion) + allowed = ( + (version == b'8965B47070' + bytes(6)) + if ecu == structs.CarParams.Ecu.eps and candidate == CAR.TOYOTA_PRIUS_RETROFIT + else version in known.get((ecu, fw.address, fw.subAddress or None), ()) + ) + if fw.logging or fw.bus != 0 or fw.brand != 'toyota' or not allowed: + return False + return True + + +def apply(cp): + cp.flags |= int(ToyotaFlags.DSU_BYPASS) + cp.safetyConfigs[0].safetyParam = PROFILES[cp.carFingerprint] + cp.openpilotLongitudinalControl = True + cp.alphaLongitudinalAvailable = False + cp.autoResumeSng = cp.carFingerprint in AUTO_RESUME_CARS + if cp.carFingerprint in (CAR.TOYOTA_MATRIX_RETROFIT, CAR.TOYOTA_PRIUS_RETROFIT): + cp.dashcamOnly = False + + +def configured(cp): + 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 cp.steerControlType != structs.CarParams.SteerControlType.torque + or len(cp.safetyConfigs) != 1 + ): + return False + required = int(CAR[cp.carFingerprint].config.flags | ToyotaFlags.DSU_BYPASS) + allowed = required | int(ToyotaFlags.HYBRID | ToyotaFlags.ZSS) + cfg = cp.safetyConfigs[0] + return ( + cp.flags & required == required + and not cp.flags & ~allowed + and not cp.alphaLongitudinalAvailable + and cp.autoResumeSng == (cp.openpilotLongitudinalControl and cp.carFingerprint in AUTO_RESUME_CARS) + and cfg.safetyModel == structs.CarParams.SafetyModel.toyota + and cfg.safetyParam == PROFILES[cp.carFingerprint] | (0 if cp.openpilotLongitudinalControl else 0x200) + ) + + +def enabled(cp): + return configured(cp) and cp.openpilotLongitudinalControl + + +def prepare_stock(cp): + if not configured(cp): + return False + cp.openpilotLongitudinalControl = False + cp.autoResumeSng = False + cp.safetyConfigs[0].safetyParam = PROFILES[cp.carFingerprint] | 0x200 + return True + + +class BypassCANParser(CANParser): + def __init__(self, *args, eps_length=8, alternate_main=False): + super().__init__(*args) + self.eps_length = eps_length + self.main_address = 0x365 if alternate_main else 0x1D3 + self.main_length = 7 if alternate_main else 8 + self.main_valid = False + self.eps_valid = False + self.acc_valid = False + self.ambiguous = False + + def update(self, strings, sendcan=False): + batches = [] + for stamp, frames in strings: + accepted = [] + for address, data, bus in frames: + if address in (0x201, 0x2FF, 0x2AA): + self.ambiguous = True + if bus == self.bus: + if address in (self.main_address, 0x262, 0x343): + size = self.main_length if address == self.main_address else self.eps_length if address == 0x262 else 8 + valid = len(data) == size and (address == 0x365 or data[-1] == toyota_checksum(address, None, bytearray(data))) + field = 'main_valid' if address == self.main_address else 'eps_valid' if address == 0x262 else 'acc_valid' + setattr(self, field, valid) + if not valid: + continue + accepted.append((address, data, bus)) + batches.append((stamp, accepted)) + return super().update(batches, sendcan) diff --git a/opendbc_repo/opendbc/car/toyota/interface.py b/opendbc_repo/opendbc/car/toyota/interface.py index 67731565bf..895319f563 100644 --- a/opendbc_repo/opendbc/car/toyota/interface.py +++ b/opendbc_repo/opendbc/car/toyota/interface.py @@ -252,6 +252,9 @@ class CarInterface(CarInterfaceBase): from opendbc.car.toyota import prius_advert_filter if prius_advert_filter.hardware_supported(candidate, fingerprint, car_fw): prius_advert_filter.apply(ret) + from opendbc.car.toyota import dsu_bypass + if dsu_bypass.hardware_supported(candidate, fingerprint, car_fw): + dsu_bypass.apply(ret) from opendbc.car.toyota import pedal if ret.openpilotLongitudinalControl and pedal.hardware_supported(candidate, fingerprint, car_fw): ret.flags |= int(ToyotaFlags.GAS_INTERCEPTOR) diff --git a/opendbc_repo/opendbc/car/toyota/legacy_stop_timer.py b/opendbc_repo/opendbc/car/toyota/legacy_stop_timer.py index 9a6155814f..711f4f92d0 100644 --- a/opendbc_repo/opendbc/car/toyota/legacy_stop_timer.py +++ b/opendbc_repo/opendbc/car/toyota/legacy_stop_timer.py @@ -6,6 +6,9 @@ CARS = frozenset((CAR.TOYOTA_CAMRY, CAR.TOYOTA_CHR, CAR.TOYOTA_AVALON_2019, def enabled(cp): + from opendbc.car.toyota import dsu_bypass + if dsu_bypass.enabled(cp): + return cp.carFingerprint not in dsu_bypass.AUTO_RESUME_CARS if cp is None or cp.carFingerprint not in CARS or not cp.openpilotLongitudinalControl: return False from opendbc.car.toyota import rav4h_filter, advert_filter, radar_filter diff --git a/opendbc_repo/opendbc/car/toyota/values.py b/opendbc_repo/opendbc/car/toyota/values.py index 3ea35b917f..405213cd96 100644 --- a/opendbc_repo/opendbc/car/toyota/values.py +++ b/opendbc_repo/opendbc/car/toyota/values.py @@ -84,6 +84,7 @@ class ToyotaFlags(IntFlag): AUTO_BRAKE_HOLD = 4096 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 + DSU_BYPASS = 1 << 19 # Camera-bus DSU adapter; independent of smartDSU/filter ZSS = 1 << 16 # Detected Prius secondary steering sensor; host-only, no safetyParam allocation # deprecated flags diff --git a/opendbc_repo/opendbc/safety/modes/toyota.h b/opendbc_repo/opendbc/safety/modes/toyota.h index 2a16b372d9..d4c67aa08b 100644 --- a/opendbc_repo/opendbc/safety/modes/toyota.h +++ b/opendbc_repo/opendbc/safety/modes/toyota.h @@ -110,12 +110,12 @@ static bool toyota_legacy_main_current(void) { static bool toyota_legacy_advert_ready(void) { bool ready = true; - if (toyota_pedal_composite || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter()) { + if (toyota_dsu_bypass || toyota_pedal_composite || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter()) { static const uint32_t timeout[4] = {303031U, 10000000U, 3333334U, 400000U}; const uint32_t now = microsecond_timer_get(); ready = aol_rx_healthy() && toyota_aol_main && toyota_aol_drive && toyota_aol_doors_closed && toyota_aol_eps && (!toyota_camera_2aa() || !toyota_camera_2aa_ambiguous) && - (!toyota_prius_filter() || !toyota_prius_sdsu_ambiguous); + (!toyota_prius_filter() || !toyota_prius_sdsu_ambiguous) && (!toyota_dsu_bypass || !toyota_bypass_ambiguous); for (unsigned int i = 0U; i < 4U; i++) { ready &= toyota_aol_seen[i] && (safety_get_ts_elapsed(now, toyota_aol_ts[i]) <= timeout[i]); } @@ -193,6 +193,19 @@ static bool toyota_get_quality_flag_valid(const CANPacket_t *msg) { } static void toyota_optional_rx_hook(const CANPacket_t *msg) { + if (toyota_dsu_bypass) { + if (msg_matches(msg, 0x343U, 2U)) { + // A malformed packet outside the required catalog cannot retain LONG. + toyota_bypass_control = false; + } + if ((msg->addr == 0x201U) || (msg->addr == 0x2FFU) || (msg->addr == 0x2AAU)) { + toyota_bypass_ambiguous = true; + } + if (msg_matches(msg, 0x283U, 2U)) { + const bool valid = (GET_LEN(msg) == 7U) && (toyota_get_checksum(msg) == toyota_compute_checksum(msg)); + toyota_bypass_aeb = !valid || (GET_BIT(msg, 41U) && GET_BIT(msg, 23U)); + } + } if (toyota_prius_advert() && ((msg->addr == 0x201U) || (msg->addr == 0x2AAU) || ((msg->addr == 0x2FFU) && (((msg->bus != 0U) && (msg->bus != 2U)) || (GET_LEN(msg) != 4U))))) { toyota_prius_sdsu_ambiguous = true; @@ -208,10 +221,10 @@ static void toyota_optional_rx_hook(const CANPacket_t *msg) { toyota_camera_2aa_ambiguous = true; } const bool aol_checksum_source = (!toyota_alt_cruise && (msg->addr == 0x1D3U)) || (msg->addr == 0x262U) || (toyota_aol_secoc && (msg->addr == 0x127U)); - const unsigned int aol_source_len = ((toyota_aol_rav4h_filter || toyota_alt_cruise || toyota_legacy_sdsu || toyota_legacy_advert() || (current_safety_param == 856U)) && (msg->addr == 0x262U)) ? 5U : 8U; + const unsigned int aol_source_len = (((toyota_dsu_bypass && toyota_bypass_eps5) || toyota_aol_rav4h_filter || toyota_alt_cruise || toyota_legacy_sdsu || toyota_legacy_advert() || (current_safety_param == 856U)) && (msg->addr == 0x262U)) ? 5U : 8U; if (!aol_checksum_source || ((GET_LEN(msg) == aol_source_len) && (toyota_get_checksum(msg) == toyota_compute_checksum(msg)))) { toyota_aol_observe(msg); - } else if ((toyota_pedal_composite || toyota_aol_enabled || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter()) && (msg->bus == 0U)) { + } else if ((toyota_dsu_bypass || toyota_pedal_composite || toyota_aol_enabled || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter()) && (msg->bus == 0U)) { unsigned int index = 3U; if (msg->addr == 0x1D3U) { index = 0U; @@ -244,9 +257,12 @@ static void toyota_optional_rx_hook(const CANPacket_t *msg) { } static void toyota_rx_hook(const CANPacket_t *msg) { + if (toyota_dsu_bypass && msg_matches(msg, 0x343U, 2U)) { + toyota_bypass_control = !GET_BIT(msg, 18U) && !GET_BIT(msg, 19U); + } // Required sources have already passed RX length/checksum validation here. // Optional ingress only runs for messages outside the required RX catalog. - if ((toyota_pedal_composite || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter() || (current_safety_param == 856U)) && ((msg->addr == 0x262U) || (!toyota_alt_cruise && (msg->addr == 0x1D3U)))) { + if ((toyota_dsu_bypass || toyota_pedal_composite || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter() || (current_safety_param == 856U)) && ((msg->addr == 0x262U) || (!toyota_alt_cruise && (msg->addr == 0x1D3U)))) { toyota_aol_observe(msg); } if (toyota_alt_cruise && msg_matches(msg, 0x365U, 0U) && (GET_LEN(msg) == 7U)) { @@ -398,7 +414,8 @@ static bool toyota_tx_hook(const CANPacket_t *msg) { }; const bool filter_ready = (!(toyota_sdsu_status || toyota_pedal) || aol_rx_healthy()) && toyota_legacy_main_current() && toyota_legacy_advert_ready(); - const bool filter_long_ready = filter_ready && (!toyota_sdsu_status || toyota_sdsu_control); + const bool filter_long_ready = filter_ready && (!toyota_sdsu_status || toyota_sdsu_control) && + (!toyota_dsu_bypass || (toyota_bypass_control && !toyota_bypass_aeb)); bool tx = true; if (toyota_pedal && (msg->addr == 0x200U)) { @@ -580,14 +597,20 @@ static bool toyota_tx_hook(const CANPacket_t *msg) { } // Required physical DSU MAIN cannot be manufactured through host TX. - if ((toyota_legacy_sdsu || toyota_legacy_advert()) && toyota_alt_cruise && msg_matches(msg, 0x365U, 0U)) { + if ((toyota_dsu_bypass || toyota_legacy_sdsu || toyota_legacy_advert()) && toyota_alt_cruise && msg_matches(msg, 0x365U, 0U)) { tx = false; } return tx; } static safety_config toyota_init(uint16_t param) { - const uint16_t base_param = toyota_pedal_parent_param(param); + const uint16_t base_param = toyota_bypass_base_param(toyota_pedal_parent_param(param)); + toyota_dsu_bypass = toyota_bypass_param(param); + toyota_bypass_eps5 = toyota_dsu_bypass && ((param & 0x1000U) == 0U); + toyota_bypass_alt_main = toyota_dsu_bypass && ((param & 0x4000U) != 0U); + toyota_bypass_ambiguous = false; + toyota_bypass_control = false; + toyota_bypass_aeb = false; toyota_pedal_composite = toyota_pedal_composite_param(param); toyota_composite_pcm_gas = false; toyota_composite_pedal_gas = false; @@ -653,7 +676,7 @@ static safety_config toyota_init(uint16_t param) { toyota_alt_brake = GET_FLAG(base_param, TOYOTA_PARAM_ALT_BRAKE); toyota_stock_longitudinal = GET_FLAG(base_param, TOYOTA_PARAM_STOCK_LONGITUDINAL); - toyota_radar_diagnostics_allowed = !toyota_stock_longitudinal && !toyota_secoc && (base_param != 0x1049U) && (base_param != 0x1449U) && !toyota_eps8_sdsu && !toyota_legacy_sdsu && !toyota_legacy_advert_param(base_param) && !toyota_camera_2aa_param(base_param) && !toyota_prius_filter_param(base_param); + toyota_radar_diagnostics_allowed = !toyota_dsu_bypass && !toyota_stock_longitudinal && !toyota_secoc && (base_param != 0x1049U) && (base_param != 0x1449U) && !toyota_eps8_sdsu && !toyota_legacy_sdsu && !toyota_legacy_advert_param(base_param) && !toyota_camera_2aa_param(base_param) && !toyota_prius_filter_param(base_param); toyota_lta = !toyota_legacy_advert_param(base_param) && GET_FLAG(base_param, TOYOTA_PARAM_LTA); toyota_dbc_eps_torque_factor = base_param & TOYOTA_EPS_FACTOR; @@ -742,6 +765,16 @@ static safety_config toyota_init(uint16_t param) { } else { ret.rx_checks_len = 4; } + if (toyota_dsu_bypass) { + toyota_rx_checks[4].msg[0] = (CanMsgCheck){.addr = 0x343, .bus = 2U, .len = 8, .frequency = 33U, + .ignore_counter = true, .ignore_quality_flag = true}; + toyota_rx_checks[5].msg[0] = (CanMsgCheck){.addr = 0x262, .bus = 0U, .len = toyota_bypass_eps5 ? 5 : 8, .frequency = 25U, + .ignore_counter = true, .ignore_quality_flag = true}; + toyota_rx_checks[6].msg[0] = (CanMsgCheck){.addr = toyota_alt_cruise ? 0x365 : 0x1D3, .bus = 0U, + .len = toyota_alt_cruise ? 7 : 8, .frequency = 10U, .ignore_checksum = toyota_alt_cruise, + .ignore_counter = true, .ignore_quality_flag = true}; + ret.rx_checks_len = 7; + } if (toyota_pedal_composite) { // Keep all parent checks, including smartDSU status, independently required. toyota_rx_checks[5].msg[0] = (CanMsgCheck){.addr = 0x201, .bus = 0U, .len = 6, .frequency = 50U, @@ -764,8 +797,9 @@ static safety_config toyota_init(uint16_t param) { } static bool toyota_fwd_hook(int bus_num, int addr) { - return (bus_num == 2) && (addr == 0x344) && toyota_hold_ready(ALT_EXP_TOYOTA_AEB_HOLD) && - toyota_hold_host_seen && (safety_get_ts_elapsed(microsecond_timer_get(), toyota_hold_host_ts) <= 100000U); + return (toyota_dsu_bypass && !toyota_stock_longitudinal && (bus_num == 2) && (addr == 0x343)) || + ((bus_num == 2) && (addr == 0x344) && toyota_hold_ready(ALT_EXP_TOYOTA_AEB_HOLD) && + toyota_hold_host_seen && (safety_get_ts_elapsed(microsecond_timer_get(), toyota_hold_host_ts) <= 100000U)); } const safety_hooks toyota_hooks = { diff --git a/opendbc_repo/opendbc/safety/modes/toyota_aol.h b/opendbc_repo/opendbc/safety/modes/toyota_aol.h index 2160cfa1fd..dd51bf7d86 100644 --- a/opendbc_repo/opendbc/safety/modes/toyota_aol.h +++ b/opendbc_repo/opendbc/safety/modes/toyota_aol.h @@ -1,5 +1,20 @@ #pragma once +static bool toyota_dsu_bypass = false; +static bool toyota_bypass_eps5 = false; +static bool toyota_bypass_alt_main = false; +static bool toyota_bypass_ambiguous = false; +static bool toyota_bypass_control = false; +static bool toyota_bypass_aeb = false; + +static bool toyota_bypass_param(uint16_t param) { + return (param == 0x8949U) || (param == 0x8B49U) || (param == 0x8849U) || (param == 0x8A49U) || (param == 0x9849U) || (param == 0x9A49U) || (param == 0x9842U) || (param == 0x9A42U) || (param == 0x8958U) || (param == 0x8B58U) || (param == 0x8964U) || (param == 0x8B64U) || (param == 0xC84DU) || (param == 0xCA4DU) || (param == 0xC949U) || (param == 0xCB49U); +} + +static uint16_t toyota_bypass_base_param(uint16_t param) { + return toyota_bypass_param(param) ? (uint16_t)(param & 0x3FFU) : param; +} + static bool toyota_prius_sdsu_param(uint16_t param) { return (param == 0x8042U) || (param == 0x8242U); } @@ -115,10 +130,10 @@ static uint8_t toyota_aol_permission(void) { uint8_t permission = 0U; if (fresh && toyota_aol_main && toyota_aol_drive && toyota_aol_doors_closed && toyota_aol_eps && (!toyota_camera_2aa() || !toyota_camera_2aa_ambiguous) && - (!toyota_prius_filter() || !toyota_prius_sdsu_ambiguous)) { + (!toyota_prius_filter() || !toyota_prius_sdsu_ambiguous) && (!toyota_dsu_bypass || !toyota_bypass_ambiguous)) { permission = request & 1U; if (!toyota_aol_stock && controls_allowed && toyota_aol_belt && !gas_pressed && !brake_pressed && - (!toyota_sdsu_status || toyota_sdsu_control)) { + (!toyota_sdsu_status || toyota_sdsu_control) && (!toyota_dsu_bypass || (toyota_bypass_control && !toyota_bypass_aeb))) { permission |= request & 2U; } } @@ -131,11 +146,11 @@ static void toyota_aol_observe(const CANPacket_t *msg) { unsigned int expected_len = 8U; if (toyota_alt_cruise && (msg->addr == main_addr)) { expected_len = 7U; - } else if ((toyota_aol_rav4h_filter || toyota_alt_cruise || toyota_legacy_sdsu || toyota_legacy_advert() || (current_safety_param == 856U)) && (msg->addr == 0x262U)) { + } else if (((toyota_dsu_bypass && toyota_bypass_eps5) || toyota_aol_rav4h_filter || toyota_alt_cruise || toyota_legacy_sdsu || toyota_legacy_advert() || (current_safety_param == 856U)) && (msg->addr == 0x262U)) { expected_len = 5U; } else { } - if ((toyota_pedal_composite || toyota_aol_enabled || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter()) && (msg->bus == 0U) && (GET_LEN(msg) != expected_len)) { + if ((toyota_dsu_bypass || toyota_pedal_composite || toyota_aol_enabled || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter()) && (msg->bus == 0U) && (GET_LEN(msg) != expected_len)) { unsigned int index = 4U; if (msg->addr == main_addr) { index = 0U; @@ -150,7 +165,7 @@ static void toyota_aol_observe(const CANPacket_t *msg) { if (index < 4U) { toyota_aol_seen[index] = false; } - } else if ((toyota_pedal_composite || toyota_aol_enabled || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter()) && (msg->bus == 0U) && (GET_LEN(msg) == expected_len)) { + } else if ((toyota_dsu_bypass || toyota_pedal_composite || toyota_aol_enabled || toyota_legacy_advert() || toyota_camera_2aa() || toyota_prius_filter()) && (msg->bus == 0U) && (GET_LEN(msg) == expected_len)) { unsigned int index = 4U; if (msg->addr == main_addr) { index = 0U; @@ -194,18 +209,18 @@ static void toyota_aol_configure(uint16_t param) { toyota_prius_sdsu_ambiguous = false; toyota_eps8_sdsu = (param == 0x8049U) || (param == 0x8249U) || (param == 0x8449U) || (param == 0x8649U); toyota_legacy_sdsu = (param == 0x9158U) || (param == 0x9358U) || (param == 0x9149U) || (param == 0x9349U) || (param == 0x9164U) || (param == 0x9364U) || (param == 0xd149U) || (param == 0xd349U) || (param == 0xd04dU) || (param == 0xd24dU); - toyota_alt_cruise = (param == 0x424DU) || ((toyota_legacy_sdsu || toyota_legacy_advert_param(param)) && ((param & 0x4000U) != 0U)); + toyota_alt_cruise = toyota_bypass_alt_main || (param == 0x424DU) || ((toyota_legacy_sdsu || toyota_legacy_advert_param(param)) && ((param & 0x4000U) != 0U)); toyota_aol_lta = (param == 0x2449U) || (param == 0x2649U) || (param == 0x449U) || (param == 0x649U) || (param == 0x1449U) || (param == 0x1649U) || (param == 0x8449U) || (param == 0x8649U); toyota_pedal = (param == 0x2049U) || (param == 0x2249U) || (param == 0x2449U) || (param == 0x2649U); toyota_aol_rav4h_filter = (param == 0x9049U) || (param == 0x9249U); toyota_sdsu_status = toyota_aol_rav4h_filter || toyota_eps8_sdsu || toyota_legacy_sdsu || toyota_prius_sdsu_param(param); - toyota_aol_stock = (param == 0x2649U) || (param == 585U) || (param == 856U) || (param == 4681U) || (param == 0x9249U) || (param == 0x2249U) || (param == 0x424DU) || (param == 0x649U) || (param == 0x1649U) || (param == 0x8249U) || (param == 0x8649U) || ((toyota_legacy_sdsu || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param) || toyota_prius_filter_param(param)) && ((param & 0x200U) != 0U)); + toyota_aol_stock = (toyota_dsu_bypass && ((param & 0x200U) != 0U)) || (param == 0x2649U) || (param == 585U) || (param == 856U) || (param == 4681U) || (param == 0x9249U) || (param == 0x2249U) || (param == 0x424DU) || (param == 0x649U) || (param == 0x1649U) || (param == 0x8249U) || (param == 0x8649U) || ((toyota_legacy_sdsu || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param) || toyota_prius_filter_param(param)) && ((param & 0x200U) != 0U)); #ifdef ALLOW_DEBUG toyota_aol_secoc = (param == 2121U); #else toyota_aol_secoc = false; #endif - toyota_aol_enabled = ((toyota_eps8_sdsu || toyota_legacy_sdsu || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param) || toyota_prius_filter_param(param)) && (alternative_experience == 32)) || (toyota_aol_lta && (alternative_experience == 32)) || (toyota_aol_stock && (alternative_experience == 32)) || + toyota_aol_enabled = (toyota_dsu_bypass && (alternative_experience == 32)) || ((toyota_eps8_sdsu || toyota_legacy_sdsu || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param) || toyota_prius_filter_param(param)) && (alternative_experience == 32)) || (toyota_aol_lta && (alternative_experience == 32)) || (toyota_aol_stock && (alternative_experience == 32)) || #ifdef ALLOW_DEBUG (toyota_aol_secoc && (alternative_experience == 32)) || #endif diff --git a/opendbc_repo/opendbc/safety/tests/test_toyota_dsu_bypass.py b/opendbc_repo/opendbc/safety/tests/test_toyota_dsu_bypass.py new file mode 100644 index 0000000000..ab707bd18e --- /dev/null +++ b/opendbc_repo/opendbc/safety/tests/test_toyota_dsu_bypass.py @@ -0,0 +1,126 @@ +"""Actual camera ACC, physical MAIN/EPS and native TX/forwarding ownership.""" + +import pytest +from opendbc.can import CANPacker +from opendbc.car import Bus, structs +from opendbc.car.toyota import dsu_bypass +from opendbc.car.toyota.values import CAR, DBC +from opendbc.safety.tests.libsafety import libsafety_py +from openpilot.starpilot.car.toyota.tests import test_rav4h_caller_native as helpers + +source_matched_native = helpers.source_matched_native +REPRESENTATIVES = (CAR.TOYOTA_RAV4H, CAR.LEXUS_ES, CAR.TOYOTA_CAMRY, CAR.TOYOTA_PRIUS, CAR.TOYOTA_COROLLA, CAR.TOYOTA_PRIUS_V, CAR.LEXUS_IS, CAR.LEXUS_GS_F) + + +class Sources: + def __init__(self, candidate, stock=False): + self.candidate, self.stock = candidate, stock + self.safety = libsafety_py.libsafety + self.safety.init_tests() + self.safety.set_alternative_experience(32) + assert self.safety.set_safety_hooks(structs.CarParams.SafetyModel.toyota, dsu_bypass.PROFILES[candidate] | (0x200 if stock else 0)) == 0 + self.safety.set_aol_test_heartbeat(True) + self.packer = CANPacker(DBC[candidate][Bus.pt]) + self.now = 1_000_000 + for _ in range(30): + self.tick() + + def rx(self, frame): + a, d, b = frame + return bool(self.safety.safety_rx_hook(libsafety_py.make_CANPacket(a, b, d))) + + def tick(self, fault=None, cruise=True): + self.now += 10_000 + self.safety.set_timer(self.now) + fields = { + 'cruise': cruise, + 'sdsu': None, + 'main': fault != 'main-off', + 'eps': 9 if fault == 'eps-state' else 1, + 'gear': 16 if fault == 'reverse' else 0, + 'gas': fault == 'gas', + 'brake': fault == 'brake', + } + frames = helpers.physical(self.packer, **fields) + if self.candidate in dsu_bypass.ALT_MAIN_CARS: + frames += [self.packer.make_can_msg('DSU_CRUISE', 0, {'MAIN_ON': fields['main'], 'SET_SPEED': 90})] + frames += [ + self.packer.make_can_msg('ACC_CONTROL', 2, {'ACC_TYPE': 1, 'ACC_MALFUNCTION': fault == 'acc-fault'}), + self.packer.make_can_msg('PRE_COLLISION', 2, {'PRECOLLISION_ACTIVE': fault == 'aeb', 'FORCE': -20 if fault == 'aeb' else 0}), + ] + selected = ( + 0x343 + if fault and fault.startswith('acc-') + else (0x365 if self.candidate in dsu_bypass.ALT_MAIN_CARS else 0x1D3) + if fault and fault.startswith('main-') + else 0x262 + ) + for a, d, b in frames: + if fault and a == selected and b == (2 if selected == 0x343 else 0): + if fault.endswith('-drop'): + continue + if fault.endswith('-dlc'): + d = d[:-1] + elif fault.endswith('-crc'): + d = d[:-1] + bytes([d[-1] ^ 1]) + accepted = self.rx((a, d, b)) + if fault and fault.endswith('-crc') and a == selected and b == (2 if selected == 0x343 else 0): + assert not accepted + else: + assert accepted, (hex(a), d.hex(), b) + if fault == 'mixed-pedal': + self.rx((0x201, bytes(6), 0)) + self.safety.safety_tick() + self.safety.aol_set_host_request(3) + + def accel(self, value): + return self.rx_tx('ACC_CONTROL', {'ACCEL_CMD': value, 'PERMIT_BRAKING': 1, 'RELEASE_STANDSTILL': value != 0}) + + def rx_tx(self, name, fields): + a, d, b = self.packer.make_can_msg(name, 0, fields) + return bool(self.safety.safety_tx_hook(libsafety_py.make_CANPacket(a, b, d))) + + def close(self): + self.safety.set_alternative_experience(0) + self.safety.set_safety_hooks(structs.CarParams.SafetyModel.noOutput, 0) + + +@pytest.mark.parametrize('candidate', REPRESENTATIVES) +@pytest.mark.parametrize('stock', (False, True)) +def test_exact_native_health_permission_accel_and_forwarding(candidate, stock): + src = Sources(candidate, stock) + try: + assert src.safety.safety_config_valid() + assert src.safety.aol_get_permission_mask() == (1 if stock else 3) + assert src.accel(0.05) == (not stock) + assert src.safety.safety_fwd_hook(2, 0x343) == (0 if stock else -1) + assert src.safety.safety_fwd_hook(2, 0x4CB) == 0 + assert src.safety.safety_fwd_hook(2, 0x283) == 0 + finally: + src.close() + + +@pytest.mark.parametrize('candidate', REPRESENTATIVES) +@pytest.mark.parametrize( + 'fault', + ('acc-drop', 'acc-dlc', 'acc-crc', 'acc-fault', 'main-off', 'eps-drop', 'eps-dlc', 'eps-crc', 'eps-state', 'reverse', 'gas', 'brake', 'aeb', 'mixed-pedal'), +) +def test_independent_sources_and_physical_vetoes(candidate, fault): + src = Sources(candidate) + try: + assert src.accel(0.05) + for _ in range(41): # Strictly exceed the existing 400ms EPS lease. + src.tick(fault) + assert not src.accel(0.05), fault + if fault != 'mixed-pedal': + for _ in range(30): + src.tick(cruise=False) + for _ in range(30): + src.tick() + assert src.accel(0.05), fault + else: + src.tick(cruise=False) + src.tick() + assert not src.accel(0.05) + finally: + src.close() diff --git a/openpilot/selfdrive/pandad/tests/test_aol_protocol.cc b/openpilot/selfdrive/pandad/tests/test_aol_protocol.cc index d8f733018c..cff170c17f 100644 --- a/openpilot/selfdrive/pandad/tests/test_aol_protocol.cc +++ b/openpilot/selfdrive/pandad/tests/test_aol_protocol.cc @@ -179,7 +179,7 @@ int main() { } for (uint32_t word = 0U; word <= 65535U; ++word) { - const bool expected = (word == 0xB049U) || (word == 0xB249U) || (word == 0xD049U) || (word == 0xD249U) || (word == 0xC042U) || (word == 0xC242U) || (word == 0x8042U) || (word == 0x8242U) || (word == 0xC049U) || (word == 0xC249U) || (word == 73U) || (word == 585U) || (word == 856U) || (word == 4169U) || (word == 4681U) || (word == 2121U) || (word == 0x9049U) || (word == 0x9249U) || (word == 0x2449U) || (word == 0x2649U) || (word == 0x2049U) || (word == 0x2249U) || (word == 0x424DU) || (word == 0x449U) || (word == 0x649U) || (word == 0x1449U) || (word == 0x1649U) || (word == 0x8049U) || (word == 0x8249U) || (word == 0x8449U) || (word == 0x8649U) || (word == 0x9158U) || (word == 0x9358U) || (word == 0x9149U) || (word == 0x9349U) || (word == 0x9164U) || (word == 0x9364U) || (word == 0xd149U) || (word == 0xd349U) || (word == 0xd04dU) || (word == 0xd24dU) || (word == 0x9449U) || (word == 0x9549U) || (word == 0x9558U) || (word == 0x9564U) || (word == 0x9649U) || (word == 0x9749U) || (word == 0x9758U) || (word == 0x9764U) || (word == 0xD44DU) || (word == 0xD549U) || (word == 0xD64DU) || (word == 0xD749U); + const bool expected = (word == 0x8949U) || (word == 0x8B49U) || (word == 0x8849U) || (word == 0x8A49U) || (word == 0x9849U) || (word == 0x9A49U) || (word == 0x9842U) || (word == 0x9A42U) || (word == 0x8958U) || (word == 0x8B58U) || (word == 0x8964U) || (word == 0x8B64U) || (word == 0xC84DU) || (word == 0xCA4DU) || (word == 0xC949U) || (word == 0xCB49U) || (word == 0xB049U) || (word == 0xB249U) || (word == 0xD049U) || (word == 0xD249U) || (word == 0xC042U) || (word == 0xC242U) || (word == 0x8042U) || (word == 0x8242U) || (word == 0xC049U) || (word == 0xC249U) || (word == 73U) || (word == 585U) || (word == 856U) || (word == 4169U) || (word == 4681U) || (word == 2121U) || (word == 0x9049U) || (word == 0x9249U) || (word == 0x2449U) || (word == 0x2649U) || (word == 0x2049U) || (word == 0x2249U) || (word == 0x424DU) || (word == 0x449U) || (word == 0x649U) || (word == 0x1449U) || (word == 0x1649U) || (word == 0x8049U) || (word == 0x8249U) || (word == 0x8449U) || (word == 0x8649U) || (word == 0x9158U) || (word == 0x9358U) || (word == 0x9149U) || (word == 0x9349U) || (word == 0x9164U) || (word == 0x9364U) || (word == 0xd149U) || (word == 0xd349U) || (word == 0xd04dU) || (word == 0xd24dU) || (word == 0x9449U) || (word == 0x9549U) || (word == 0x9558U) || (word == 0x9564U) || (word == 0x9649U) || (word == 0x9749U) || (word == 0x9758U) || (word == 0x9764U) || (word == 0xD44DU) || (word == 0xD549U) || (word == 0xD64DU) || (word == 0xD749U); auto toyota = status(); toyota.safety_mode = 2U; toyota.safety_param = static_cast(word); diff --git a/openpilot/starpilot/car/toyota/aol.py b/openpilot/starpilot/car/toyota/aol.py index cd36bf63d5..f8dc25b22b 100644 --- a/openpilot/starpilot/car/toyota/aol.py +++ b/openpilot/starpilot/car/toyota/aol.py @@ -6,6 +6,8 @@ from openpilot.starpilot.aol.policy import AolVehiclePolicy from openpilot.starpilot.aol.intent import AolCardIntent, AOL_TOGGLE AOL_WORDS = frozenset(( + 0x8949, 0x8B49, + 0x8849, 0x8A49, 0x9849, 0x9A49, 0x9842, 0x9A42, 0x8958, 0x8B58, 0x8964, 0x8B64, 0xC84D, 0xCA4D, 0xC949, 0xCB49, 0x8042, 0x8242, 0xC042, 0xC242, 0xD049, 0xD249, 0xC049, 0xC249, 0x9449, 0x9549, 0x9558, 0x9564, 0x9649, 0x9749, 0x9758, 0x9764, 0xD44D, 0xD549, 0xD64D, 0xD749, @@ -97,6 +99,9 @@ def stock_prius_retrofit_qualified(cp, *, marked_only=False): def qualified(cp, *, marked_only=False): + from opendbc.car.toyota import dsu_bypass + if dsu_bypass.configured(cp): + return cp.alternativeExperience in ((32,) if marked_only else (0, 32)) from opendbc.car.toyota import pedal_composite if pedal_composite.configured(cp): return qualified(pedal_composite.parent_cp(cp), marked_only=marked_only) diff --git a/openpilot/starpilot/car/toyota/aol_policy.h b/openpilot/starpilot/car/toyota/aol_policy.h index 7b5d025b6a..bc876a420b 100644 --- a/openpilot/starpilot/car/toyota/aol_policy.h +++ b/openpilot/starpilot/car/toyota/aol_policy.h @@ -3,7 +3,7 @@ #include "selfdrive/pandad/aol_protocol.h" inline bool toyota_aol_param(uint16_t param) { - return (param == 0xB049U) || (param == 0xB249U) || (param == 0xD049U || param == 0xD249U || param == 0xC042U || param == 0xC242U || param == 0x8042U || param == 0x8242U || param == 0xC049U) || (param == 0xC249U) || (param == 73U) || (param == 585U) || (param == 856U) || (param == 4169U) || (param == 4681U) || (param == 2121U) || (param == 0x9049U) || (param == 0x9249U) || (param == 0x2449U) || (param == 0x2649U) || (param == 0x2049U) || (param == 0x2249U) || (param == 0x424DU) || (param == 0x449U) || (param == 0x649U) || (param == 0x1449U) || (param == 0x1649U) || (param == 0x8049U) || (param == 0x8249U) || (param == 0x8449U) || (param == 0x8649U) || (param == 0x9158U) || (param == 0x9358U) || (param == 0x9149U) || (param == 0x9349U) || (param == 0x9164U) || (param == 0x9364U) || (param == 0xd149U) || (param == 0xd349U) || (param == 0xd04dU) || (param == 0xd24dU) || (param == 0x9449U) || (param == 0x9549U) || (param == 0x9558U) || (param == 0x9564U) || (param == 0x9649U) || (param == 0x9749U) || (param == 0x9758U) || (param == 0x9764U) || (param == 0xD44DU) || (param == 0xD549U) || (param == 0xD64DU) || (param == 0xD749U); + return (param == 0x8949U) || (param == 0x8B49U) || (param == 0x8849U) || (param == 0x8A49U) || (param == 0x9849U) || (param == 0x9A49U) || (param == 0x9842U) || (param == 0x9A42U) || (param == 0x8958U) || (param == 0x8B58U) || (param == 0x8964U) || (param == 0x8B64U) || (param == 0xC84DU) || (param == 0xCA4DU) || (param == 0xC949U) || (param == 0xCB49U) || (param == 0xB049U) || (param == 0xB249U) || (param == 0xD049U || param == 0xD249U || param == 0xC042U || param == 0xC242U || param == 0x8042U || param == 0x8242U || param == 0xC049U) || (param == 0xC249U) || (param == 73U) || (param == 585U) || (param == 856U) || (param == 4169U) || (param == 4681U) || (param == 2121U) || (param == 0x9049U) || (param == 0x9249U) || (param == 0x2449U) || (param == 0x2649U) || (param == 0x2049U) || (param == 0x2249U) || (param == 0x424DU) || (param == 0x449U) || (param == 0x649U) || (param == 0x1449U) || (param == 0x1649U) || (param == 0x8049U) || (param == 0x8249U) || (param == 0x8449U) || (param == 0x8649U) || (param == 0x9158U) || (param == 0x9358U) || (param == 0x9149U) || (param == 0x9349U) || (param == 0x9164U) || (param == 0x9364U) || (param == 0xd149U) || (param == 0xd349U) || (param == 0xd04dU) || (param == 0xd24dU) || (param == 0x9449U) || (param == 0x9549U) || (param == 0x9558U) || (param == 0x9564U) || (param == 0x9649U) || (param == 0x9749U) || (param == 0x9758U) || (param == 0x9764U) || (param == 0xD44DU) || (param == 0xD549U) || (param == 0xD64DU) || (param == 0xD749U); } inline constexpr AolSafetyProfile TOYOTA_AOL_PROFILE{2U, toyota_aol_param, true}; diff --git a/openpilot/starpilot/car/toyota/bypass_preferences.py b/openpilot/starpilot/car/toyota/bypass_preferences.py new file mode 100644 index 0000000000..14de28429a --- /dev/null +++ b/openpilot/starpilot/car/toyota/bypass_preferences.py @@ -0,0 +1,19 @@ +from opendbc.car.toyota.dsu_bypass import enabled +from openpilot.starpilot.car.toyota.prius_preferences import filter_preference_enabled + + +class BypassPreference: + def __init__(self, cp, params): + self.cp, self.params = cp, params + self.read_ns = 0 + self.enabled = False + + def update(self, now_ns): + if type(now_ns) is not int or now_ns <= 0 or now_ns < self.read_ns or not enabled(self.cp): + self.read_ns = 0 + self.enabled = False + return False + if self.read_ns == 0 or now_ns - self.read_ns >= 250_000_000: + self.read_ns = now_ns + self.enabled = filter_preference_enabled(self.params) + return self.enabled diff --git a/openpilot/starpilot/car/toyota/legacy_lateral.py b/openpilot/starpilot/car/toyota/legacy_lateral.py index dfce6cffe3..22aa777019 100644 --- a/openpilot/starpilot/car/toyota/legacy_lateral.py +++ b/openpilot/starpilot/car/toyota/legacy_lateral.py @@ -2,15 +2,17 @@ from opendbc.car import structs from opendbc.car.toyota.values import CAR from opendbc.car.toyota.rav4h_filter import configured -from opendbc.car.toyota import legacy_sdsu, legacy_advert_filter +from opendbc.car.toyota import dsu_bypass, legacy_sdsu, legacy_advert_filter from openpilot.starpilot.aol.runtime import AXIS_MAX_AGE_NS, INTENT_MAX_AGE_NS, SAFETY_MAX_AGE_NS, native_inventory_matches_cp def below_engage_lateral_allowed(cp, cs, intent, native, *, source_ns, now_ns, axis_session_id): try: + bypass = dsu_bypass.configured(cp) legacy_advert = legacy_advert_filter.configured(cp) - return bool((configured(cp) or legacy_advert) and - (cp.carFingerprint in (CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_AVALON) or legacy_sdsu.configured(cp) or legacy_advert) and cp.alternativeExperience == 32 and + return bool((bypass or configured(cp) or legacy_advert) and + (bypass or cp.carFingerprint in (CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_AVALON) or legacy_sdsu.configured(cp) or legacy_advert) and + cp.alternativeExperience == 32 and cp.steerAtStandstill and cp.minSteerSpeed <= 0 and cp.minEnableSpeed > 0 and 0 <= cs.vEgo < cp.minEnableSpeed and intent is not None and intent.settingsQualified and intent.allowedLatch and intent.lateralArmed and not intent.pauseLateral and intent.producerSessionId and intent.carStateLogMonoTime == source_ns and diff --git a/openpilot/starpilot/car/toyota/tests/test_dsu_bypass.py b/openpilot/starpilot/car/toyota/tests/test_dsu_bypass.py new file mode 100644 index 0000000000..87317a1627 --- /dev/null +++ b/openpilot/starpilot/car/toyota/tests/test_dsu_bypass.py @@ -0,0 +1,109 @@ +"""Dom bypass topology, unchanged tune projection and exact stock pairing.""" + +import pytest +from opendbc.car import structs +from opendbc.car.toyota import dsu_bypass +from opendbc.car.toyota.interface import CarInterface +from opendbc.car.toyota.values import CAR, ToyotaFlags +from openpilot.starpilot.car.toyota.aol import qualified +from openpilot.starpilot.car.toyota.tests.test_rav4h_caller_native import firmware + + +def acquired(candidate): + if candidate == CAR.TOYOTA_MATRIX_RETROFIT: + return [] + fw = firmware(CAR.TOYOTA_PRIUS if candidate == CAR.TOYOTA_PRIUS_RETROFIT else candidate) + if candidate == CAR.TOYOTA_PRIUS_RETROFIT: + for row in fw: + if row.ecu == structs.CarParams.Ecu.eps: + row.fwVersion = b'8965B47070' + bytes(6) + return fw + + +def fingerprint(candidate, address=0x343): + pt = {0x260: 8, 0x262: 8 if candidate in dsu_bypass.EPS8_CARS or candidate == CAR.TOYOTA_PRIUS else 5, 0x224: 8, 0x226: 8, 0x1D2: 8, 0x1D3: 8} + return {0: pt, 1: {}, 2: {address: 8}} + + +def factory(candidate, address=0x343): + return CarInterface.get_params(candidate, fingerprint(candidate, address), acquired(candidate), False, False, False) + + +@pytest.mark.parametrize('candidate', sorted(dsu_bypass.PROFILES)) +@pytest.mark.parametrize('address', (0x343, 0x4CB)) +@pytest.mark.parametrize('stock', (False, True)) +def test_exact_identity_current_tunes_and_stock_pair(candidate, address, stock): + fp = fingerprint(candidate, address) + cp = factory(candidate, address) + baseline = CarInterface.get_params(candidate, {**fp, 2: {}}, acquired(candidate), False, False, False) + assert dsu_bypass.configured(cp) and qualified(cp) and not cp.dashcamOnly and cp.pcmCruise + assert cp.flags & ToyotaFlags.DSU_BYPASS and not cp.flags & ToyotaFlags.LONG_FILTER + assert cp.safetyConfigs[0].safetyParam == dsu_bypass.PROFILES[candidate] + for field in ( + 'lateralTuning', + 'longitudinalTuning', + 'steerActuatorDelay', + 'steerLimitTimer', + 'longitudinalActuatorDelay', + 'minEnableSpeed', + 'minSteerSpeed', + 'wheelSpeedFactor', + 'mass', + 'wheelbase', + 'steerRatio', + 'centerToFront', + ): + assert cp.to_dict()[field] == baseline.to_dict()[field], field + if stock: + assert dsu_bypass.prepare_stock(cp) and dsu_bypass.configured(cp) and qualified(cp) + assert not cp.openpilotLongitudinalControl and cp.safetyConfigs[0].safetyParam == dsu_bypass.PROFILES[candidate] | 0x200 + + +@pytest.mark.parametrize('candidate', (CAR.TOYOTA_CAMRY, CAR.LEXUS_IS, CAR.TOYOTA_PRIUS, CAR.TOYOTA_PRIUS_RETROFIT)) +@pytest.mark.parametrize('address', (0x343, 0x4CB)) +def test_native_bus_mirror_is_not_adapter_identity(candidate, address): + fp = fingerprint(candidate, address) + fw = acquired(candidate) + if candidate in (CAR.TOYOTA_PRIUS, CAR.TOYOTA_PRIUS_RETROFIT): + for row in fw: + if row.ecu == structs.CarParams.Ecu.fwdCamera: + row.fwVersion = b'8646F47050' + fp[0][address] = 8 + assert not dsu_bypass.hardware_supported(candidate, fp, fw) + + +@pytest.mark.parametrize('kind', ('foreign-pedal', 'filter', 'sdsu', 'camera-dlc', 'unknown-eps', 'logging', 'wrong-fw-bus')) +def test_mixed_topology_or_unknown_firmware_does_not_enroll(kind): + fp = fingerprint(CAR.TOYOTA_RAV4H) + fw = acquired(CAR.TOYOTA_RAV4H) + if kind in ('foreign-pedal', 'filter', 'sdsu'): + fp[0][{'foreign-pedal': 0x201, 'filter': 0x2AA, 'sdsu': 0x2FF}[kind]] = 6 if kind == 'foreign-pedal' else 8 + elif kind == 'camera-dlc': + fp[2][0x343] = 7 + else: + row = next(f for f in fw if f.ecu == structs.CarParams.Ecu.eps) + if kind == 'unknown-eps': + row.fwVersion = b'unknown' + elif kind == 'logging': + row.logging = True + else: + row.bus = 2 + assert not dsu_bypass.hardware_supported(CAR.TOYOTA_RAV4H, fp, fw) + + +@pytest.mark.parametrize('candidate', sorted(dsu_bypass.PROFILES)) +def test_native_eps_and_brake_basis_matches_current_car(candidate): + fp = fingerprint(candidate) + baseline = CarInterface.get_params(candidate, {**fp, 2: {}}, acquired(candidate), False, False, False) + cp = factory(candidate) + assert cp.safetyConfigs[0].safetyParam & 0x1FF == baseline.safetyConfigs[0].safetyParam & 0x1FF + + +@pytest.mark.parametrize('candidate', sorted(dsu_bypass.PROFILES)) +def test_dom_auto_resume_and_stop_timer_are_model_specific(candidate): + from opendbc.car.toyota import legacy_stop_timer + cp = factory(candidate) + assert cp.autoResumeSng == (candidate in dsu_bypass.AUTO_RESUME_CARS) + assert legacy_stop_timer.enabled(cp) == (candidate not in dsu_bypass.AUTO_RESUME_CARS) + assert dsu_bypass.prepare_stock(cp) + assert not cp.autoResumeSng and not legacy_stop_timer.enabled(cp) diff --git a/openpilot/starpilot/car/toyota/tests/test_dsu_bypass_callers.py b/openpilot/starpilot/car/toyota/tests/test_dsu_bypass_callers.py new file mode 100644 index 0000000000..f949c3e298 --- /dev/null +++ b/openpilot/starpilot/car/toyota/tests/test_dsu_bypass_callers.py @@ -0,0 +1,63 @@ +"""Factory, Card, SelfdriveD, Controls, CI, C++ heartbeat and native bypass.""" + +import pytest +from opendbc.car.toyota import dsu_bypass +from opendbc.car.toyota.values import CAR +from openpilot.starpilot.car.toyota.tests import test_rav4h_caller_native as helpers +from openpilot.starpilot.car.toyota.tests.test_dsu_bypass import acquired, fingerprint +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 +REPRESENTATIVES = (CAR.TOYOTA_RAV4H, CAR.LEXUS_ES, CAR.TOYOTA_CAMRY, CAR.TOYOTA_PRIUS, CAR.TOYOTA_COROLLA, CAR.TOYOTA_PRIUS_V, CAR.LEXUS_IS, CAR.LEXUS_GS_F) + + +def run(candidate, stock, bridge, scenario='nominal', aol=True): + def physical(packer, **fields): + fields['sdsu'] = None + frames = helpers.physical(packer, **fields) + if candidate in dsu_bypass.ALT_MAIN_CARS: + frames += [ + packer.make_can_msg('DSU_CRUISE', 0, {'MAIN_ON': fields.get('main', True), 'SET_SPEED': 90}), + packer.make_can_msg('PCM_CRUISE_ALT', 0, {'UI_SET_SPEED': 90}), + ] + frames += [packer.make_can_msg('ACC_CONTROL', 2, {'ACC_TYPE': 1}), packer.make_can_msg('PRE_COLLISION', 2, {}), packer.make_can_msg('PCS_HUD', 2, {})] + return frames + + def observe(tick, host, cs, cc, safety, native): + del tick, cs, cc, native + assert dsu_bypass.configured(host.CP) + assert safety.safety_fwd_hook(2, 0x343) == (0 if stock else -1) + assert safety.safety_fwd_hook(2, 0x283) == 0 + + fp = [(a, bytes(length), bus) for bus, messages in fingerprint(candidate).items() for a, length in messages.items()] + manual = candidate in (CAR.TOYOTA_MATRIX_RETROFIT, CAR.TOYOTA_PRIUS_RETROFIT) + helpers.run_case( + stock, + scenario, + candidate=candidate, + bridge=bridge, + input_clock=True, + sdsu_words=(dsu_bypass.PROFILES[candidate], dsu_bypass.PROFILES[candidate] | 0x200), + physical_frames=physical, + on_sample=observe, + firmware_override=acquired(candidate), + fingerprint_frames=fp, + manual_selection=manual, + manual_bypass=manual, + bypass_owner=True, + aol=aol, + brake_stop_check_start=355, + ) + + +@pytest.mark.parametrize('candidate', sorted(dsu_bypass.PROFILES)) +@pytest.mark.parametrize('stock', (False, True)) +def test_each_bypass_identity_full_caller(candidate, stock, host_heartbeat_bridge): + run(candidate, stock, host_heartbeat_bridge) + + +@pytest.mark.parametrize('candidate', REPRESENTATIVES) +@pytest.mark.parametrize('scenario', ('physical', 'off', 'ordinary')) +def test_profile_physical_saved_off_and_ordinary_paths(candidate, scenario, host_heartbeat_bridge): + run(candidate, False, host_heartbeat_bridge, 'nominal' if scenario == 'ordinary' else scenario, aol=scenario != 'ordinary') 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 3c8f2a5225..b9aefa6fc8 100644 --- a/openpilot/starpilot/car/toyota/tests/test_rav4h_caller_native.py +++ b/openpilot/starpilot/car/toyota/tests/test_rav4h_caller_native.py @@ -232,7 +232,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, 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, - manual_filter_length=None): + manual_filter_length=None, manual_bypass=False, bypass_owner=False): SYNTHETIC_LAST.clear() NATIVE_LAST.clear() safety = libsafety_py.libsafety @@ -252,7 +252,16 @@ 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: - if manual_filter_length is not None: + if manual_bypass: + assert fingerprint_frames is not None + assert candidate in (CAR.TOYOTA_MATRIX_RETROFIT, CAR.TOYOTA_PRIUS_RETROFIT) and bypass_owner + assert any(bus == 2 and address in (0x343, 0x4CB) and len(data) == 8 for address, data, bus in fingerprint_frames) + assert not any(address in (0x201, 0x2FF, 0x2AA) for address, _, _ in fingerprint_frames) + if candidate == CAR.TOYOTA_MATRIX_RETROFIT: + assert not fw and firmware_override == [] + else: + assert any(f.ecu == structs.CarParams.Ecu.eps and f.address == 0x7A1 and f.bus == 0 and bytes(f.fwVersion) == b'8965B47070' + bytes(6) for f in fw) + elif manual_filter_length is not None: if candidate == CAR.TOYOTA_MATRIX_RETROFIT: assert manual_filter_length in (4, 8) and not fw and firmware_override == [] else: @@ -327,7 +336,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, ) ) rows = {r.key: r for r in wheel.rows()} - if candidate == CAR.TOYOTA_RAV4H: + if candidate == CAR.TOYOTA_RAV4H and not bypass_owner: assert 'Bookmark' in rows['wheel:LKASButtonControl'].choices assert 'Toggle AOL' not in rows['wheel:LKASButtonControl'].choices assert not any('Cancel' in k or 'MainCruise' in k for k in rows) @@ -574,7 +583,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, assert not host.CP.openpilotLongitudinalControl and not cc.longActive assert host.CP.safetyConfigs[0].safetyParam == sdsu_words[1] else: - preference = host.CI.CC.prius_filter_input if candidate == CAR.TOYOTA_PRIUS else ( + preference = host.CI.CC.bypass_input if bypass_owner else host.CI.CC.prius_filter_input if candidate == CAR.TOYOTA_PRIUS else ( host.CI.CC.advert_filter_input if filter_advert or fingerprint_frames is not None and any( address == 0x2AA for address, _, _ in fingerprint_frames) else host.CI.CC.rav4h_filter_input) assert preference is not None and not preference.update(time.monotonic_ns()) diff --git a/openpilot/starpilot/controller_extensions.py b/openpilot/starpilot/controller_extensions.py index d43e9573b5..2577978dc9 100644 --- a/openpilot/starpilot/controller_extensions.py +++ b/openpilot/starpilot/controller_extensions.py @@ -175,6 +175,14 @@ def configure_controller(CI, params): prepare_rav4h_stock(cp) elif controller is not None: controller.rav4h_filter_input = Rav4hFilterPreference(cp, params) + from opendbc.car.toyota import dsu_bypass + if dsu_bypass.enabled(cp): + from openpilot.starpilot.car.toyota.bypass_preferences import BypassPreference + from openpilot.starpilot.car.toyota.prius_preferences import filter_preference_enabled + if not filter_preference_enabled(params): + dsu_bypass.prepare_stock(cp) + elif controller is not None: + controller.bypass_input = BypassPreference(cp, params) from opendbc.car.toyota import advert_filter if advert_filter.enabled(cp): from openpilot.starpilot.car.toyota.advert_preferences import AdvertFilterPreference diff --git a/openpilot/starpilot/vehicle_preferences.py b/openpilot/starpilot/vehicle_preferences.py index 71cc253380..fa8b50d2e8 100644 --- a/openpilot/starpilot/vehicle_preferences.py +++ b/openpilot/starpilot/vehicle_preferences.py @@ -206,6 +206,8 @@ class VehicleStartupPreferences: prepare_advert_filter_stock(cp) from opendbc.car.toyota.angle_control import prepare_stock as prepare_angle_stock prepare_angle_stock(cp) + from opendbc.car.toyota.dsu_bypass import prepare_stock as prepare_bypass_stock + prepare_bypass_stock(cp) self._prepare_bolt(cp, fingerprints) prepare_disable_longitudinal(cp, self.disable_bolt_long) self._prepare_camera_pedal(cp) @@ -247,6 +249,8 @@ class VehicleStartupPreferences: prepare_advert_filter_stock(cp) from opendbc.car.toyota.angle_control import prepare_stock as prepare_angle_stock prepare_angle_stock(cp) + from opendbc.car.toyota.dsu_bypass import prepare_stock as prepare_bypass_stock + prepare_bypass_stock(cp) self._prepare_bolt(cp) prepare_disable_longitudinal(cp, self.disable_bolt_long) self._prepare_camera_pedal(cp) diff --git a/prebuilt.json b/prebuilt.json index 262d73c4cf..8e6a06e2a7 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "de3ffa5eadfc93f3696ceba58a0260f2578fdc8f99862f6004354df661a83ba4", "schema_version": 1} +{"protected_inventory_sha256": "b9097bfbbea2a3eae03349fb84e39dda591f5f60678075d491ef6e4cf1aa3f97", "schema_version": 1} diff --git a/tools/ci/run_vehicle_tests.py b/tools/ci/run_vehicle_tests.py index 60b08935a8..fa5dbdbf99 100644 --- a/tools/ci/run_vehicle_tests.py +++ b/tools/ci/run_vehicle_tests.py @@ -40,6 +40,9 @@ AOL_LIFECYCLE_TARGETS = ( 'openpilot/starpilot/car/toyota/tests/test_rav4h_pedal_composite_callers.py', 'opendbc_repo/opendbc/safety/tests/test_toyota_rav4h_pedal_composite.py', 'opendbc_repo/opendbc/safety/tests/test_toyota_rav4h_pedal_order.py', + 'openpilot/starpilot/car/toyota/tests/test_dsu_bypass.py', + 'openpilot/starpilot/car/toyota/tests/test_dsu_bypass_callers.py', + 'opendbc_repo/opendbc/safety/tests/test_toyota_dsu_bypass.py', 'opendbc_repo/opendbc/safety/tests/test_toyota_radar_lta_pedal.py', 'opendbc_repo/opendbc/safety/tests/test_toyota_prius_retrofit_advert.py', 'openpilot/starpilot/aol/tests/test_crossmake_native_admission.py', @@ -242,6 +245,9 @@ RELEASE_TARGETS = ( 'openpilot/starpilot/car/toyota/tests/test_rav4h_pedal_composite_callers.py', 'opendbc_repo/opendbc/safety/tests/test_toyota_rav4h_pedal_composite.py', 'opendbc_repo/opendbc/safety/tests/test_toyota_rav4h_pedal_order.py', + 'openpilot/starpilot/car/toyota/tests/test_dsu_bypass.py', + 'openpilot/starpilot/car/toyota/tests/test_dsu_bypass_callers.py', + 'opendbc_repo/opendbc/safety/tests/test_toyota_dsu_bypass.py', 'opendbc_repo/opendbc/safety/tests/test_toyota_radar_lta_pedal.py', 'opendbc_repo/opendbc/safety/tests/test_toyota_prius_retrofit_advert.py', 'opendbc_repo/opendbc/car/hyundai/tests/test_long_cp_copy.py',