From 204a6f8534cb46bbd7229f894a8d1e2d285397eb Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Thu, 22 Oct 2026 19:09:43 -0500 Subject: [PATCH] Restore EPS8 Toyota smartDSU ownership --- .../opendbc/car/toyota/carcontroller.py | 7 +- opendbc_repo/opendbc/car/toyota/carstate.py | 4 +- opendbc_repo/opendbc/car/toyota/eps8_sdsu.py | 109 +++++++++++++ opendbc_repo/opendbc/car/toyota/interface.py | 8 +- .../opendbc/car/toyota/rav4h_filter.py | 6 + opendbc_repo/opendbc/safety/modes/toyota.h | 20 ++- .../opendbc/safety/modes/toyota_aol.h | 12 +- .../safety/tests/test_toyota_eps8_sdsu.py | 148 ++++++++++++++++++ .../pandad/tests/test_aol_protocol.cc | 21 ++- openpilot/starpilot/car/toyota/aol.py | 2 +- openpilot/starpilot/car/toyota/aol_policy.h | 2 +- .../car/toyota/tests/test_eps8_sdsu.py | 81 ++++++++++ .../toyota/tests/test_eps8_sdsu_callers.py | 89 +++++++++++ .../toyota/tests/test_rav4h_caller_native.py | 32 ++-- prebuilt.json | 2 +- 15 files changed, 505 insertions(+), 38 deletions(-) create mode 100644 opendbc_repo/opendbc/car/toyota/eps8_sdsu.py create mode 100644 opendbc_repo/opendbc/safety/tests/test_toyota_eps8_sdsu.py create mode 100644 openpilot/starpilot/car/toyota/tests/test_eps8_sdsu.py create mode 100644 openpilot/starpilot/car/toyota/tests/test_eps8_sdsu_callers.py diff --git a/opendbc_repo/opendbc/car/toyota/carcontroller.py b/opendbc_repo/opendbc/car/toyota/carcontroller.py index 6ed5d96262..bcc3a35829 100644 --- a/opendbc_repo/opendbc/car/toyota/carcontroller.py +++ b/opendbc_repo/opendbc/car/toyota/carcontroller.py @@ -8,7 +8,7 @@ from opendbc.car.common.pid import PIDController from opendbc.car.secoc import add_mac, build_sync_mac from opendbc.car.interfaces import CarControllerBase from opendbc.car.toyota import toyotacan -from opendbc.car.toyota import prius_longitudinal, rav4h_filter, pedal +from opendbc.car.toyota import prius_longitudinal, rav4h_filter, pedal, eps8_sdsu from opendbc.car.toyota.door_locks import DoorLocks from opendbc.car.toyota.values import CAR, CarControllerParams, ToyotaFlags, TOYOTA_AUTO_HOLD_CARS, uses_toyota_auto_hold_aeb from opendbc.can import CANPacker @@ -82,6 +82,7 @@ class CarController(CarControllerBase): self.standstill_req = False self.last_standstill = False self.prius_longitudinal = prius_longitudinal.enabled(CP) + self.eps8_hybrid = eps8_sdsu.configured(CP) and bool(CP.flags & ToyotaFlags.HYBRID) self.permit_braking = True self.steer_rate_counter = 0 self.distance_button = 0 @@ -271,6 +272,10 @@ class CarController(CarControllerBase): self.last_standstill = CS.out.standstill if not long_active and CS.out.standstill: self.standstill_req = True + elif self.eps8_hybrid: + # Retain the existing hybrid resume behavior while active. The shared + # owner guard below still holds neutral when LONG or its source is lost. + self.standstill_req = not long_active and CS.out.standstill elif rav4h_long or not self.CP.flags & ToyotaFlags.HYBRID.value: should_resume = long_active and actuators.accel > 0 if rav4h_long else actuators.accel > 0 if should_resume: diff --git a/opendbc_repo/opendbc/car/toyota/carstate.py b/opendbc_repo/opendbc/car/toyota/carstate.py index 7cf0de2be1..9852648bd2 100644 --- a/opendbc_repo/opendbc/car/toyota/carstate.py +++ b/opendbc_repo/opendbc/car/toyota/carstate.py @@ -178,7 +178,7 @@ class CarState(CarStateBase): if self.CP.flags & ToyotaFlags.TSS2 and not self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: # The radar filter intercepts this message. Preserve stock PCS/AEB # observations without making intercepted ACC_CONTROL a required input. - if not self.CP.flags & ToyotaFlags.RADAR_CAN_FILTER: + if not self.CP.flags & ToyotaFlags.RADAR_CAN_FILTER and not rav4h_filter_configured(self.CP): self.acc_type = cp_acc.vl["ACC_CONTROL"]["ACC_TYPE"] ret.stockFcw = bool(cp_acc.vl["PCS_HUD"]["FCW"]) @@ -226,7 +226,7 @@ class CarState(CarStateBase): buttonEvents.extend(create_button_events(1, 0, {1: ButtonType.lkas}) + create_button_events(0, 1, {1: ButtonType.lkas})) - if self.CP.flags & ToyotaFlags.TSS2 and not (self.CP.flags & (ToyotaFlags.RADAR_ACC | ToyotaFlags.SECOC)): + if self.CP.flags & ToyotaFlags.TSS2 and not (self.CP.flags & (ToyotaFlags.RADAR_ACC | ToyotaFlags.SECOC | ToyotaFlags.LONG_FILTER)): # distance button is wired to the ACC module (camera or radar) prev_distance_button = self.distance_button self.distance_button = cp_acc.vl["ACC_CONTROL"]["DISTANCE"] diff --git a/opendbc_repo/opendbc/car/toyota/eps8_sdsu.py b/opendbc_repo/opendbc/car/toyota/eps8_sdsu.py new file mode 100644 index 0000000000..002f2439ed --- /dev/null +++ b/opendbc_repo/opendbc/car/toyota/eps8_sdsu.py @@ -0,0 +1,109 @@ +"""Eight-byte EPS smartDSU ownership, separate from EPS5 and radar advertisements.""" + +from opendbc.car import structs +from opendbc.car.toyota.values import CAR, ToyotaFlags + +CARS = frozenset( + ( + CAR.TOYOTA_ALPHARD_TSS2, + CAR.TOYOTA_AVALON_TSS2, + CAR.TOYOTA_CAMRY_TSS2, + CAR.TOYOTA_COROLLA_TSS2, + CAR.TOYOTA_HIGHLANDER_TSS2, + CAR.TOYOTA_PRIUS_TSS2, + CAR.TOYOTA_RAV4_TSS2, + CAR.TOYOTA_MIRAI, + CAR.LEXUS_ES_TSS2, + CAR.LEXUS_NX_TSS2, + CAR.LEXUS_LC_TSS2, + CAR.LEXUS_RX_TSS2, + CAR.LEXUS_IS_TSS2, + CAR.LEXUS_RC_TSS2, + CAR.TOYOTA_AVALON_2019, + CAR.LEXUS_LS, + CAR.TOYOTA_CAMRY, + CAR.TOYOTA_CHR, + CAR.TOYOTA_CHR_TSS2, + CAR.TOYOTA_RAV4_TSS2_2022, + CAR.TOYOTA_RAV4_TSS2_2023, + ) +) +WORDS = frozenset((0x8049, 0x8249, 0x8449, 0x8649)) + + +def words(candidate): + return (0x8449, 0x8649) if candidate == CAR.TOYOTA_RAV4_TSS2_2023 else (0x8049, 0x8249) + + +def hardware_supported(candidate, fingerprint, car_fw): + if ( + candidate not in CARS + or fingerprint.get(0, {}).get(0x2FF) != 8 + or any(0x2FF in messages for bus, messages in fingerprint.items() if bus != 0) + or any(address in messages for messages in fingerprint.values() for address in (0x201, 0x2AA)) + ): + return False + # TSS2 camera ACC is normally on bus2. It is not evidence of a DSU bypass. + if not CAR(candidate).config.flags & ToyotaFlags.TSS2 and any(address in fingerprint.get(2, {}) for address in (0x343, 0x4CB)): + return False + from opendbc.car.toyota.fingerprints import FW_VERSIONS + + known = FW_VERSIONS[candidate] + required = {structs.CarParams.Ecu.eps, structs.CarParams.Ecu.fwdCamera} + if any(ecu == structs.CarParams.Ecu.fwdRadar for ecu, _, _ in known): + required.add(structs.CarParams.Ecu.fwdRadar) + for ecu in required: + observed = [fw for fw in car_fw if fw.ecu == ecu] + if not observed or any( + fw.logging or fw.bus != 0 or fw.brand != "toyota" or bytes(fw.fwVersion) not in known.get((ecu, fw.address, fw.subAddress or None), ()) for fw in observed + ): + return False + return True + + +def apply(cp, alpha_long): + cp.flags |= int(ToyotaFlags.LONG_FILTER) + cp.flags &= ~int(ToyotaFlags.DISABLE_RADAR) + gated = bool(cp.flags & (ToyotaFlags.NO_DSU | ToyotaFlags.RADAR_ACC)) + cp.alphaLongitudinalAvailable = gated + cp.openpilotLongitudinalControl = bool(alpha_long or not gated or cp.flags & ToyotaFlags.TSS2 and not cp.flags & ToyotaFlags.RADAR_ACC) + cp.safetyConfigs[0].safetyParam = words(cp.carFingerprint)[not cp.openpilotLongitudinalControl] + + +def configured(cp): + if ( + cp is None + or cp.brand != "toyota" + or cp.carFingerprint not in CARS + or cp.notCar + or cp.passive + or cp.dashcamOnly + or cp.secOcRequired + or not cp.pcmCruise + or len(cp.safetyConfigs) != 1 + ): + return False + required = int(CAR(cp.carFingerprint).config.flags | ToyotaFlags.LONG_FILTER) + if CAR(cp.carFingerprint).config.flags & ToyotaFlags.TSS2: + required |= int(ToyotaFlags.RAISED_ACCEL_LIMIT) + allowed = required | int(ToyotaFlags.HYBRID | ToyotaFlags.HAS_BSM) + if cp.carFingerprint in (CAR.TOYOTA_RAV4_TSS2_2022, CAR.TOYOTA_RAV4_TSS2_2023): + allowed |= int(ToyotaFlags.ARS510_RADAR | ToyotaFlags.ARS510_FUSED) + expected_type = structs.CarParams.SteerControlType.angle if cp.carFingerprint == CAR.TOYOTA_RAV4_TSS2_2023 else structs.CarParams.SteerControlType.torque + return bool( + cp.flags & required == required + and not int(cp.flags) & ~allowed + and (not cp.flags & ToyotaFlags.ARS510_FUSED or cp.flags & ToyotaFlags.ARS510_RADAR) + and cp.steerControlType == expected_type + and cp.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.toyota + and cp.safetyConfigs[0].safetyParam == words(cp.carFingerprint)[not cp.openpilotLongitudinalControl] + ) + + +def prepare_stock(cp): + if not configured(cp): + return False + cp.openpilotLongitudinalControl = False + cp.safetyConfigs[0].safetyParam = words(cp.carFingerprint)[1] + cp.autoResumeSng = False + return True diff --git a/opendbc_repo/opendbc/car/toyota/interface.py b/opendbc_repo/opendbc/car/toyota/interface.py index 4973698e85..41de7405dd 100644 --- a/opendbc_repo/opendbc/car/toyota/interface.py +++ b/opendbc_repo/opendbc/car/toyota/interface.py @@ -189,12 +189,16 @@ class CarInterface(CarInterfaceBase): stop_and_go = True elif candidate == CAR.TOYOTA_RAV4H and 0x2FF in fingerprint.get(0, {}): ret.dashcamOnly = True + from opendbc.car.toyota import eps8_sdsu + eps8_filter = eps8_sdsu.hardware_supported(candidate, fingerprint, car_fw) + if eps8_filter: + eps8_sdsu.apply(ret, alpha_long) # Toyota EPS accepts lateral commands down to zero. Keep the existing # native/axis/fault gates; a stock cruise stop does not withdraw steering. ret.steerAtStandstill = ret.minSteerSpeed <= 0. ret.autoResumeSng = ret.openpilotLongitudinalControl and ( (rav4h_sdsu and candidate in (CAR.TOYOTA_RAV4H, CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_SIENNA)) or - not ret.flags & ToyotaFlags.LONG_FILTER) + (eps8_filter and bool(ret.flags & ToyotaFlags.TSS2)) or not ret.flags & ToyotaFlags.LONG_FILTER) if radar_filter and not ret.flags & ToyotaFlags.TSS2: ret.autoResumeSng = False # Preserve the pre-TSS2 stock stop timer. @@ -206,7 +210,7 @@ class CarInterface(CarInterfaceBase): # to a negative value, so it won't matter. ret.minEnableSpeed = -1. if stop_and_go else MIN_ACC_SPEED - if ret.flags & (ToyotaFlags.TSS2 | ToyotaFlags.LONG_FILTER) and not rav4h_sdsu: + if ret.flags & ToyotaFlags.TSS2 or ret.flags & ToyotaFlags.LONG_FILTER and not (rav4h_sdsu or eps8_filter): ret.flags |= ToyotaFlags.RAISED_ACCEL_LIMIT.value # Hybrids have much quicker longitudinal actuator response diff --git a/opendbc_repo/opendbc/car/toyota/rav4h_filter.py b/opendbc_repo/opendbc/car/toyota/rav4h_filter.py index 2c77179a96..344b40fad2 100644 --- a/opendbc_repo/opendbc/car/toyota/rav4h_filter.py +++ b/opendbc_repo/opendbc/car/toyota/rav4h_filter.py @@ -34,6 +34,9 @@ def hardware_supported(candidate, fingerprint, car_fw): def configured(cp): + from opendbc.car.toyota import eps8_sdsu + if eps8_sdsu.configured(cp): + return True if (cp is None or cp.brand != "toyota" or cp.carFingerprint not in EPS5_SDSU_CARS or not cp.pcmCruise or cp.dashcamOnly or cp.notCar or cp.passive or cp.steerControlType != structs.CarParams.SteerControlType.torque or len(cp.safetyConfigs) != 1): @@ -58,6 +61,9 @@ def enabled(cp): def prepare_stock(cp): + from opendbc.car.toyota import eps8_sdsu + if eps8_sdsu.prepare_stock(cp): + return True if not configured(cp): return False cp.flags &= ~int(ToyotaFlags.AUTO_BRAKE_HOLD) diff --git a/opendbc_repo/opendbc/safety/modes/toyota.h b/opendbc_repo/opendbc/safety/modes/toyota.h index b333529c40..064d63cb58 100644 --- a/opendbc_repo/opendbc/safety/modes/toyota.h +++ b/opendbc_repo/opendbc/safety/modes/toyota.h @@ -156,7 +156,7 @@ static bool toyota_get_quality_flag_valid(const CANPacket_t *msg) { } } else { } - if (toyota_aol_rav4h_filter && (msg->addr == 0x2FFU)) { + if (toyota_sdsu_status && (msg->addr == 0x2FFU)) { const unsigned int state = msg->data[2] >> 4U; valid = (GET_LEN(msg) == 8U) && ((state == 0U) || (state == 7U)); } @@ -211,7 +211,7 @@ static void toyota_rx_hook(const CANPacket_t *msg) { ((unsigned int)msg->data[2] << 8U) + msg->data[3]) / 2U; gas_pressed = gas > 805U; } - if (toyota_aol_rav4h_filter && (msg->addr == 0x2FFU) && (GET_LEN(msg) == 8U)) { + if (toyota_sdsu_status && (msg->addr == 0x2FFU) && (GET_LEN(msg) == 8U)) { // Only normal sDSU state owns replacement LONG. AEB stays healthy and retains independent LAT. toyota_sdsu_control = (msg->data[2] >> 4U) == 0U; } @@ -353,8 +353,8 @@ static bool toyota_tx_hook(const CANPacket_t *msg) { !gas_pressed && !brake_pressed && GET_BIT(msg, 39U) && (gas1 >= 475U) && (gas1 <= 1275U) && (gas2 >= 949U) && (gas2 <= 1749U) && (gas2 >= (gas1 + 473U)) && (gas2 <= (gas1 + 475U)))); } - const bool filter_ready = !(toyota_aol_rav4h_filter || toyota_pedal) || aol_rx_healthy(); - const bool filter_long_ready = filter_ready && (!toyota_aol_rav4h_filter || toyota_sdsu_control); + const bool filter_ready = !(toyota_sdsu_status || toyota_pedal) || aol_rx_healthy(); + const bool filter_long_ready = filter_ready && (!toyota_sdsu_status || toyota_sdsu_control); // Check if msg is sent on BUS 0 if (msg->bus == 0U) { @@ -580,7 +580,7 @@ static safety_config toyota_init(uint16_t param) { toyota_alt_brake = GET_FLAG(param, TOYOTA_PARAM_ALT_BRAKE); toyota_stock_longitudinal = GET_FLAG(param, TOYOTA_PARAM_STOCK_LONGITUDINAL); - toyota_radar_diagnostics_allowed = !toyota_stock_longitudinal && !toyota_secoc && (param != 0x1049U) && (param != 0x1449U); + toyota_radar_diagnostics_allowed = !toyota_stock_longitudinal && !toyota_secoc && (param != 0x1049U) && (param != 0x1449U) && !toyota_eps8_sdsu; toyota_lta = GET_FLAG(param, TOYOTA_PARAM_LTA); toyota_dbc_eps_torque_factor = param & TOYOTA_EPS_FACTOR; @@ -601,7 +601,7 @@ static safety_config toyota_init(uint16_t param) { if (toyota_stock_longitudinal) { SET_TX_MSGS(TOYOTA_TX_MSGS, ret); } else { - if ((param == 0x1049U) || (param == 0x9049U) || (param == 0x1449U)) { + if ((param == 0x1049U) || (param == 0x9049U) || (param == 0x1449U) || toyota_eps8_sdsu) { SET_TX_MSGS(TOYOTA_FILTER_LONG_TX_MSGS_SET, ret); } else { SET_TX_MSGS(TOYOTA_LONG_TX_MSGS, ret); @@ -619,9 +619,13 @@ static safety_config toyota_init(uint16_t param) { // Check the quality flag for angle measurement when using LTA, since it's not set on TSS-P cars static RxCheck toyota_lta_rx_checks[] = { TOYOTA_RX_CHECKS(true) + {.msg = {{0x2FF, 0, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = false}, { 0 }, { 0 }}}, }; SET_RX_CHECKS(toyota_lta_rx_checks, ret); + if (!toyota_sdsu_status) { + ret.rx_checks_len -= 1; + } } else { // The final entry belongs only to the exact RAV4H filter profile. Share // the ordinary checks instead of duplicating their mutable status table. @@ -637,12 +641,12 @@ static safety_config toyota_init(uint16_t param) { .ignore_checksum = !toyota_pedal, .ignore_counter = !toyota_pedal, .max_counter = toyota_pedal ? 15U : 0U, .ignore_quality_flag = toyota_alt_cruise}; SET_RX_CHECKS(toyota_lka_rx_checks, ret); - if (!toyota_pedal && !toyota_alt_cruise && (param != 0x9049U) && (param != 0x9249U)) { + if (!toyota_pedal && !toyota_alt_cruise && !toyota_sdsu_status) { ret.rx_checks_len -= 1; // Ordinary profiles do not require smartDSU. } } - if (((param & 0xC000U) != 0U) && !toyota_alt_cruise && (param != 0x9049U) && (param != 0x9249U)) { + if (((param & 0xC000U) != 0U) && !toyota_alt_cruise && !toyota_sdsu_status) { ret.tx_msgs_len = 0; } if (((param & 0x2000U) != 0U) && !toyota_pedal) { diff --git a/opendbc_repo/opendbc/safety/modes/toyota_aol.h b/opendbc_repo/opendbc/safety/modes/toyota_aol.h index ce1156ec88..48ba51a67b 100644 --- a/opendbc_repo/opendbc/safety/modes/toyota_aol.h +++ b/opendbc_repo/opendbc/safety/modes/toyota_aol.h @@ -8,6 +8,8 @@ static bool toyota_aol_secoc = false; static bool toyota_aol_stock = false; static bool toyota_aol_lta = false; static bool toyota_aol_rav4h_filter = false; +static bool toyota_sdsu_status = false; +static bool toyota_eps8_sdsu = false; static bool toyota_sdsu_control = false; static bool toyota_aol_main = false; static bool toyota_aol_drive = false; @@ -51,7 +53,7 @@ static uint8_t toyota_aol_permission(void) { if (fresh && toyota_aol_main && toyota_aol_drive && toyota_aol_doors_closed && toyota_aol_eps) { permission = request & 1U; if (!toyota_aol_stock && controls_allowed && toyota_aol_belt && !gas_pressed && !brake_pressed && - (!toyota_aol_rav4h_filter || toyota_sdsu_control)) { + (!toyota_sdsu_status || toyota_sdsu_control)) { permission |= request & 2U; } } @@ -123,17 +125,19 @@ static void toyota_aol_observe(const CANPacket_t *msg) { static void toyota_aol_configure(uint16_t param) { toyota_aol_reset(); + toyota_eps8_sdsu = (param == 0x8049U) || (param == 0x8249U) || (param == 0x8449U) || (param == 0x8649U); toyota_alt_cruise = param == 0x424DU; - toyota_aol_lta = (param == 0x449U) || (param == 0x649U) || (param == 0x1449U) || (param == 0x1649U); + toyota_aol_lta = (param == 0x449U) || (param == 0x649U) || (param == 0x1449U) || (param == 0x1649U) || (param == 0x8449U) || (param == 0x8649U); toyota_pedal = (param == 0x2049U) || (param == 0x2249U); toyota_aol_rav4h_filter = (param == 0x9049U) || (param == 0x9249U); - toyota_aol_stock = (param == 585U) || (param == 4681U) || (param == 0x9249U) || (param == 0x2249U) || toyota_alt_cruise || (param == 0x649U) || (param == 0x1649U); + toyota_sdsu_status = toyota_aol_rav4h_filter || toyota_eps8_sdsu; + toyota_aol_stock = (param == 585U) || (param == 4681U) || (param == 0x9249U) || (param == 0x2249U) || toyota_alt_cruise || (param == 0x649U) || (param == 0x1649U) || (param == 0x8249U) || (param == 0x8649U); #ifdef ALLOW_DEBUG toyota_aol_secoc = (param == 2121U); #else toyota_aol_secoc = false; #endif - toyota_aol_enabled = (toyota_aol_lta && (alternative_experience == 32)) || (toyota_aol_stock && (alternative_experience == 32)) || + toyota_aol_enabled = (toyota_eps8_sdsu && (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_eps8_sdsu.py b/opendbc_repo/opendbc/safety/tests/test_toyota_eps8_sdsu.py new file mode 100644 index 0000000000..9bee0f6b3b --- /dev/null +++ b/opendbc_repo/opendbc/safety/tests/test_toyota_eps8_sdsu.py @@ -0,0 +1,148 @@ +import pytest +from opendbc.can import CANPacker +from opendbc.car import structs +from opendbc.safety.tests.libsafety import libsafety_py +from openpilot.starpilot.car.toyota.tests import test_pedal_caller_native as helpers + +source_matched_native = helpers.source_matched_native +WORDS = (0x8049, 0x8249, 0x8449, 0x8649) + + +class Physical: + def __init__(self, word, experience=32): + self.native = libsafety_py.libsafety + self.native.init_tests() + self.native.set_alternative_experience(experience) + assert self.native.set_safety_hooks(structs.CarParams.SafetyModel.toyota, word) == 0 + self.native.set_aol_test_heartbeat(True) + self.word, self.now = word, 1_000_000 + self.packer = CANPacker("toyota_nodsu_pt_generated") + + def tick(self, *, cruise=True, status=0, bad=None, **fields): + self.now += 10_000 + self.native.set_timer(self.now) + frames = helpers.physical(self.packer, cruise=cruise, **fields) + frames = [f for f in frames if f[0] not in (0x260, 0x262)] + frames += [ + self.packer.make_can_msg("STEER_TORQUE_SENSOR", 0, {"STEER_ANGLE": 0.0573}), + self.packer.make_can_msg("EPS_STATUS", 0, {"LKA_STATE": fields.get("eps", 1), "LTA_STATE": fields.get("eps", 1)}), + ] + if status is not None: + frames.append(self.packer.make_can_msg("SDSU", 0, {"STATE": status})) + for address, data, bus in frames: + if address == 0x262 and bad == "eps_checksum": + data = data[:-1] + bytes([data[-1] ^ 1]) + if address == 0x262 and bad == "eps_length": + data = data[:5] + if address == 0x2FF and bad == "status_length": + data = data[:7] + if address == 0x2FF and bad == "status_bus": + bus = 2 + self.native.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, data)) + self.native.safety_tick() + self.native.aol_set_host_request(3) + + def accel(self, value=0.1, cancel=0): + a, d, b = self.packer.make_can_msg("ACC_CONTROL", 0, {"ACCEL_CMD": value, "CANCEL_REQ": cancel}) + return self.native.safety_tx_hook(libsafety_py.make_CANPacket(a, b, d)) + + def close(self): + self.native.set_alternative_experience(0) + self.native.set_safety_hooks(structs.CarParams.SafetyModel.noOutput, 0) + + +@pytest.mark.parametrize("word", WORDS) +def test_status_lease_state_aeb_and_recovery(word): + case = Physical(word) + try: + assert case.native.aol_get_permission_mask() == 0 + for _ in range(25): + case.tick(cruise=False, status=None) + assert case.native.aol_get_permission_mask() == 0 + for _ in range(25): + case.tick(cruise=False) + assert case.native.aol_get_permission_mask() == 1 + for _ in range(25): + case.tick() + expected = 1 if word & 0x200 else 3 + assert case.native.aol_get_permission_mask() == expected + assert bool(case.accel()) == (expected == 3) + case.tick(status=7) + assert case.native.safety_config_valid() + assert case.native.aol_get_permission_mask() == 1 and not case.accel() + case.tick(status=2) + assert not case.native.safety_config_valid() + assert case.native.aol_get_permission_mask() == 0 and not case.accel() + for _ in range(25): + case.tick(cruise=False) + for _ in range(25): + case.tick() + assert case.native.aol_get_permission_mask() == expected + finally: + case.close() + + +@pytest.mark.parametrize("word", WORDS) +@pytest.mark.parametrize("bad", ["eps_checksum", "eps_length", "status_length", "status_bus"]) +def test_malformed_or_wrong_bus_cannot_refresh_required_sources(word, bad): + case = Physical(word) + try: + for _ in range(25): + case.tick(cruise=False) + for _ in range(25): + case.tick() + assert case.native.aol_get_permission_mask() == (1 if word & 0x200 else 3) + for _ in range(25): + case.tick(bad=bad) + assert case.native.aol_get_permission_mask() == 0 and not case.accel() + finally: + case.close() + + +@pytest.mark.parametrize("word", WORDS) +@pytest.mark.parametrize("fault", ["main", "gear", "eps", "brake", "gas"]) +def test_physical_permission_withdrawal(word, fault): + case = Physical(word) + try: + for _ in range(25): + case.tick(cruise=False) + for _ in range(25): + case.tick() + case.tick(**{"main": {"main": False}, "gear": {"gear": 16}, "eps": {"eps": 9}, "brake": {"brake": True}, "gas": {"gas": True}}[fault]) + assert case.native.aol_get_permission_mask() == (1 if fault in ("brake", "gas") else 0) + assert not case.accel() + finally: + case.close() + + +@pytest.mark.parametrize("word", WORDS) +def test_ordinary_cruise_and_stock_cancel_only(word): + case = Physical(word, 0) + try: + for _ in range(25): + case.tick(cruise=False) + for _ in range(25): + case.tick() + assert case.native.get_controls_allowed() + assert bool(case.accel()) == (not word & 0x200) + assert case.accel(0, 1) + for value in (-3.501, 2.001): + assert not case.accel(value) + for _ in range(25): + case.tick(status=None) + assert not case.accel() + finally: + case.close() + + +@pytest.mark.parametrize("word", [0x8048, 0x804A, 0x8149, 0x8248, 0x8448, 0x844A, 0x8648, 0x8749, 0xA049]) +def test_unqualified_adjacent_words_have_no_commands(word): + case = Physical(word) + try: + for _ in range(25): + case.tick(cruise=False) + for _ in range(25): + case.tick() + assert case.native.aol_get_permission_mask() == 0 and not case.accel() + finally: + case.close() diff --git a/openpilot/selfdrive/pandad/tests/test_aol_protocol.cc b/openpilot/selfdrive/pandad/tests/test_aol_protocol.cc index bfc8d20584..232fb179c1 100644 --- a/openpilot/selfdrive/pandad/tests/test_aol_protocol.cc +++ b/openpilot/selfdrive/pandad/tests/test_aol_protocol.cc @@ -167,7 +167,7 @@ int main() { } for (uint32_t word = 0U; word <= 65535U; ++word) { - const bool expected = (word == 73U) || (word == 585U) || (word == 4169U) || (word == 4681U) || (word == 2121U) || (word == 0x9049U) || (word == 0x9249U) || (word == 0x2049U) || (word == 0x2249U) || (word == 0x424DU) || (word == 0x449U) || (word == 0x649U) || (word == 0x1449U) || (word == 0x1649U); + const bool expected = (word == 73U) || (word == 585U) || (word == 4169U) || (word == 4681U) || (word == 2121U) || (word == 0x9049U) || (word == 0x9249U) || (word == 0x2049U) || (word == 0x2249U) || (word == 0x424DU) || (word == 0x449U) || (word == 0x649U) || (word == 0x1449U) || (word == 0x1649U) || (word == 0x8049U) || (word == 0x8249U) || (word == 0x8449U) || (word == 0x8649U); auto toyota = status(); toyota.safety_mode = 2U; toyota.safety_param = static_cast(word); @@ -432,14 +432,19 @@ int main() { assert(!aol_capable(absent, GM_AOL_PROFILE.mode, VEHICLE_REGISTRY)); } - // One mode28 registry entry admits the ordinary PE transport as well as - // the unchanged Ioniq6 profiles; this is not an independent AOL grant. + // Ordinary PE transport and the separately qualified LONG/AOL profile + // both require the native capability; adjacent unqualified words stay denied. auto pe = status(); pe.safety_mode = HYUNDAI_AOL_PROFILE.mode; - pe.safety_param = 0x5491U; - assert(aol_capable(pe, HYUNDAI_AOL_PROFILE.mode, VEHICLE_REGISTRY)); - assert(aol_runtime_enabled(false, pe, VEHICLE_REGISTRY)); - for (uint16_t denied : {0x5490U, 0x5493U, 0x5495U, 0x5411U, 0x54B1U, 0x5691U}) { + for (uint16_t admitted : {0x5491U, 0x5495U}) { + pe.safety_param = admitted; + assert(aol_capable(pe, HYUNDAI_AOL_PROFILE.mode, VEHICLE_REGISTRY)); + assert(aol_runtime_enabled(false, pe, VEHICLE_REGISTRY)); + pe.capability_flags = 0U; + assert(!aol_capable(pe, HYUNDAI_AOL_PROFILE.mode, VEHICLE_REGISTRY)); + pe.capability_flags = 1U; + } + for (uint16_t denied : {0x5490U, 0x5493U, 0x5497U, 0x5411U, 0x54B1U, 0x5691U}) { pe.safety_param = denied; assert(!aol_capable(pe, HYUNDAI_AOL_PROFILE.mode, VEHICLE_REGISTRY)); } @@ -481,7 +486,7 @@ int main() { 0x7090U, 0x7092U, 0x7208U, 0x720AU, 0x7228U, 0x722AU, 0x7809U, 0x7829U, }; for (uint32_t word = 0U; word <= 0xFFFFU; ++word) { - bool expected = word == 0x0815U || word == 0x0895U || word == 0x8815U || word == 0x8895U || word == 0x5491U || word == 0x5C91U || word == 0x5C95U; + bool expected = word == 0x0815U || word == 0x0895U || word == 0x8815U || word == 0x8895U || word == 0x5491U || word == 0x5C91U || word == 0x5C95U || word == 0x5495U; for (uint16_t stock_word : stock_canfd_words) { expected = expected || word == stock_word; } diff --git a/openpilot/starpilot/car/toyota/aol.py b/openpilot/starpilot/car/toyota/aol.py index ce2ccfdf21..ddd3094f5c 100644 --- a/openpilot/starpilot/car/toyota/aol.py +++ b/openpilot/starpilot/car/toyota/aol.py @@ -5,7 +5,7 @@ from opendbc.car.toyota.values import CAR, ToyotaFlags, ToyotaSafetyFlags, EPS_S from openpilot.starpilot.aol.policy import AolVehiclePolicy from openpilot.starpilot.aol.intent import AolCardIntent, AOL_TOGGLE -AOL_WORDS = frozenset((73, 585, 4169, 4681, 2121, 0x9049, 0x9249, 0x2049, 0x2249, 0x424D, 0x449, 0x649, 0x1449, 0x1649)) +AOL_WORDS = frozenset((73, 585, 4169, 4681, 2121, 0x9049, 0x9249, 0x2049, 0x2249, 0x424D, 0x449, 0x649, 0x1449, 0x1649, 0x8049, 0x8249, 0x8449, 0x8649)) SECOC_TORQUE_CARS = frozenset((CAR.TOYOTA_SIENNA_4TH_GEN,)) diff --git a/openpilot/starpilot/car/toyota/aol_policy.h b/openpilot/starpilot/car/toyota/aol_policy.h index 14d96a94ca..4af52b221e 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 == 73U) || (param == 585U) || (param == 4169U) || (param == 4681U) || (param == 2121U) || (param == 0x9049U) || (param == 0x9249U) || (param == 0x2049U) || (param == 0x2249U) || (param == 0x424DU) || (param == 0x449U) || (param == 0x649U) || (param == 0x1449U) || (param == 0x1649U); + return (param == 73U) || (param == 585U) || (param == 4169U) || (param == 4681U) || (param == 2121U) || (param == 0x9049U) || (param == 0x9249U) || (param == 0x2049U) || (param == 0x2249U) || (param == 0x424DU) || (param == 0x449U) || (param == 0x649U) || (param == 0x1449U) || (param == 0x1649U) || (param == 0x8049U) || (param == 0x8249U) || (param == 0x8449U) || (param == 0x8649U); } inline constexpr AolSafetyProfile TOYOTA_AOL_PROFILE{2U, toyota_aol_param, true}; diff --git a/openpilot/starpilot/car/toyota/tests/test_eps8_sdsu.py b/openpilot/starpilot/car/toyota/tests/test_eps8_sdsu.py new file mode 100644 index 0000000000..fd59edfff9 --- /dev/null +++ b/openpilot/starpilot/car/toyota/tests/test_eps8_sdsu.py @@ -0,0 +1,81 @@ +import pytest +from opendbc.car import gen_empty_fingerprint +from opendbc.car.toyota import eps8_sdsu +from opendbc.car.toyota.interface import CarInterface +from opendbc.car.toyota.values import CAR, ToyotaFlags +from openpilot.starpilot.car.toyota.tests.test_pedal_caller_native import firmware +from openpilot.starpilot.car.toyota.aol import qualified + + +@pytest.mark.parametrize('candidate', sorted(eps8_sdsu.CARS)) +@pytest.mark.parametrize('alpha', [False, True]) +def test_eps8_sdsu_factory_and_saved_off(candidate, alpha): + fp = gen_empty_fingerprint() + fp[0][0x2FF] = 8 + cp = CarInterface.get_params(candidate, fp, firmware(candidate), alpha, False, False) + assert eps8_sdsu.configured(cp) and qualified(cp) + long = ( + alpha + or not candidate.config.flags & (ToyotaFlags.NO_DSU | ToyotaFlags.RADAR_ACC) + or bool(candidate.config.flags & ToyotaFlags.TSS2 and not candidate.config.flags & ToyotaFlags.RADAR_ACC) + ) + assert cp.openpilotLongitudinalControl == long + assert not cp.flags & ToyotaFlags.DISABLE_RADAR + assert cp.safetyConfigs[0].safetyParam == eps8_sdsu.words(candidate)[not long] + assert eps8_sdsu.prepare_stock(cp) + assert eps8_sdsu.configured(cp) and qualified(cp) and not cp.openpilotLongitudinalControl + + +@pytest.mark.parametrize('candidate', sorted(eps8_sdsu.CARS)) +@pytest.mark.parametrize('bad', ['length', 'mirror', 'pedal', 'mixed', 'firmware']) +def test_eps8_hardware_boundaries(candidate, bad): + fp = gen_empty_fingerprint() + fp[0][0x2FF] = 8 + fw = firmware(candidate) + if bad == 'length': + fp[0][0x2FF] = 4 + if bad == 'mirror': + fp[2][0x2FF] = 8 + if bad == 'pedal': + fp[0][0x201] = 6 + if bad == 'mixed': + fp[0][0x2AA] = 8 + if bad == 'firmware': + fw = [] + assert not eps8_sdsu.hardware_supported(candidate, fp, fw) + + +@pytest.mark.parametrize('candidate', [CAR.TOYOTA_RAV4_PRIME, CAR.TOYOTA_SIENNA_4TH_GEN, CAR.TOYOTA_RAV4H, CAR.TOYOTA_PRIUS]) +def test_other_native_topologies_not_claimed(candidate): + fp = gen_empty_fingerprint() + fp[0][0x2FF] = 8 + assert not eps8_sdsu.hardware_supported(candidate, fp, firmware(candidate)) + + +@pytest.mark.parametrize("candidate", sorted(eps8_sdsu.CARS)) +@pytest.mark.parametrize("alpha", [False, True]) +@pytest.mark.parametrize("hybrid", [False, True]) +def test_sdsu_keeps_existing_vehicle_configuration_and_tune(candidate, alpha, hybrid): + from opendbc.car import structs + from opendbc.car.toyota.values import CarControllerParams + from opendbc.car.toyota.radar_filter import PROFILES + + fw = firmware(candidate) + if hybrid: + fw.append( + structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.hybrid, address=0x7E2, fwVersion=b"synthetic hybrid ECU response", brand="toyota", bus=0, logging=True) + ) + existing = gen_empty_fingerprint() + if candidate in PROFILES: + existing[0][0x2AA] = 8 + new = gen_empty_fingerprint() + new[0][0x2FF] = 8 + reference = CarInterface.get_params(candidate, existing, fw, alpha, False, False) + actual = CarInterface.get_params(candidate, new, fw, alpha, False, False) + before, after = reference.to_dict(), actual.to_dict() + for field in ("flags", "safetyConfigs", "openpilotLongitudinalControl", "alphaLongitudinalAvailable", "autoResumeSng"): + before.pop(field, None) + after.pop(field, None) + assert before == after + baseline_limits, new_limits = CarControllerParams(reference), CarControllerParams(actual) + assert (baseline_limits.ACCEL_MIN, baseline_limits.ACCEL_MAX) == (new_limits.ACCEL_MIN, new_limits.ACCEL_MAX) diff --git a/openpilot/starpilot/car/toyota/tests/test_eps8_sdsu_callers.py b/openpilot/starpilot/car/toyota/tests/test_eps8_sdsu_callers.py new file mode 100644 index 0000000000..3f30f1b618 --- /dev/null +++ b/openpilot/starpilot/car/toyota/tests/test_eps8_sdsu_callers.py @@ -0,0 +1,89 @@ +import pytest +from opendbc.car.toyota import eps8_sdsu +from opendbc.car.toyota.values import CAR, ToyotaFlags +from openpilot.starpilot.car.toyota.tests import test_rav4h_caller_native 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 + + +def run(candidate, stock, bridge, scenario='nominal', aol=True, alpha=True, disable_setting=None, firmware=None, hybrid_release=False): + angle = candidate == CAR.TOYOTA_RAV4_TSS2_2023 + camera = bool(candidate.config.flags & ToyotaFlags.TSS2 and not candidate.config.flags & ToyotaFlags.RADAR_ACC) + + def physical(packer, **fields): + frames = helpers.physical(packer, **fields) + frames = [f for f in frames if f[0] not in (0x260, 0x262)] + frames += [ + packer.make_can_msg('STEER_TORQUE_SENSOR', 0, {'STEER_ANGLE': 0.0573}), + packer.make_can_msg('EPS_STATUS', 0, {'LKA_STATE': fields.get('eps', 1), 'LTA_STATE': fields.get('eps', 1)}), + packer.make_can_msg('PCS_HUD', 2 if camera else 0, {}), + ] + if camera: + frames.append(packer.make_can_msg('PRE_COLLISION', 2, {})) + return frames + + helpers.run_case( + stock, + scenario, + candidate=candidate, + bridge=bridge, + input_clock=True, + aol=aol, + sdsu_words=eps8_sdsu.words(candidate), + alpha_long=alpha, + disable_setting=disable_setting, + physical_frames=physical, + angle=angle, + brake_stop_check_start=355, + firmware_override=firmware, + assert_hybrid_release=hybrid_release, + ) + + +@pytest.mark.parametrize('candidate', sorted(eps8_sdsu.CARS)) +@pytest.mark.parametrize('stock', [False, True]) +def test_each_eps8_full_caller(candidate, stock, host_heartbeat_bridge): + run(candidate, stock, host_heartbeat_bridge) + + +@pytest.mark.parametrize('candidate', [CAR.TOYOTA_CAMRY, CAR.TOYOTA_COROLLA_TSS2, CAR.TOYOTA_RAV4_TSS2_2023]) +@pytest.mark.parametrize('stock', [False, True]) +@pytest.mark.parametrize('scenario', ['physical', 'faults', 'bad_dlc', 'off']) +def test_eps8_fault_source_and_preference_callers(candidate, stock, scenario, host_heartbeat_bridge): + run(candidate, stock, host_heartbeat_bridge, scenario) + + +@pytest.mark.parametrize('candidate', [CAR.TOYOTA_CAMRY, CAR.TOYOTA_COROLLA_TSS2, CAR.TOYOTA_RAV4_TSS2_2023]) +@pytest.mark.parametrize('stock', [False, True]) +def test_eps8_ordinary_cruise_callers(candidate, stock, host_heartbeat_bridge): + run(candidate, stock, host_heartbeat_bridge, aol=False) + + +@pytest.mark.parametrize( + 'candidate', [CAR.TOYOTA_CAMRY, CAR.TOYOTA_CHR, CAR.TOYOTA_CHR_TSS2, CAR.TOYOTA_RAV4_TSS2_2022, CAR.TOYOTA_RAV4_TSS2_2023, CAR.TOYOTA_COROLLA_TSS2] +) +def test_eps8_alpha_off_actual_callers(candidate, host_heartbeat_bridge): + stock = bool(candidate.config.flags & ToyotaFlags.RADAR_ACC or not candidate.config.flags & ToyotaFlags.TSS2) + run(candidate, stock, host_heartbeat_bridge, alpha=False, disable_setting=False) + + +@pytest.mark.parametrize("candidate", [CAR.TOYOTA_CAMRY, CAR.TOYOTA_CAMRY_TSS2, CAR.TOYOTA_RAV4_TSS2_2023]) +def test_detected_hybrid_preserves_active_resume(candidate, host_heartbeat_bridge): + from opendbc.car import structs + from opendbc.car.toyota.values import FW_QUERY_CONFIG + + # Firmware acquisition boundary: model a response from Toyota's real logging + # hybrid query. Keep the catalog EPS/camera/radar identity used for enrollment. + assert (structs.CarParams.Ecu.hybrid, 0x7E2, None) in FW_QUERY_CONFIG.extra_ecus + fw = helpers.firmware(candidate) + fw.append( + structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.hybrid, address=0x7E2, fwVersion=b"synthetic hybrid ECU response", brand="toyota", bus=0, logging=True) + ) + run(candidate, False, host_heartbeat_bridge, scenario="hybrid_stop", firmware=fw, hybrid_release=True) + + +@pytest.mark.parametrize("candidate", [CAR.TOYOTA_AVALON_2019, CAR.LEXUS_LS]) +def test_nongated_legacy_sdsu_alpha_off(candidate, host_heartbeat_bridge): + run(candidate, False, host_heartbeat_bridge, alpha=False, disable_setting=False) 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 cfdd844b8f..71916a4de7 100644 --- a/openpilot/starpilot/car/toyota/tests/test_rav4h_caller_native.py +++ b/openpilot/starpilot/car/toyota/tests/test_rav4h_caller_native.py @@ -23,7 +23,7 @@ from opendbc.can import CANPacker from opendbc.car import Bus, car_helpers, gen_empty_fingerprint, structs from opendbc.car.fw_versions import match_fw_to_car from opendbc.car.toyota.fingerprints import FW_VERSIONS -from opendbc.car.toyota.values import CAR, DBC +from opendbc.car.toyota.values import CAR, DBC, ToyotaFlags from opendbc.car.toyota.tests.test_auto_hold import decode from opendbc.safety.tests.libsafety import libsafety_py from openpilot.cereal import messaging @@ -224,7 +224,9 @@ def control_inputs(controls, published, state, sd_events, native, now): return values -def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, bridge=None, input_clock=False, brake_stop_check_start=350): +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): SYNTHETIC_LAST.clear() NATIVE_LAST.clear() safety = libsafety_py.libsafety @@ -235,11 +237,12 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, ('OpenpilotEnabledToggle', True), ('SafeMode', False), ('AlwaysOnLateral', aol), - ('DisableOpenpilotLongitudinal', stock), + ('DisableOpenpilotLongitudinal', stock if disable_setting is None else disable_setting), + ('AlphaLongitudinalEnabled', alpha_long), ('ToyotaAutoHold', False), ): params.put_bool(k, v, block=True) - fw = firmware(candidate) + fw = firmware(candidate) if firmware_override is None else firmware_override exact, found = match_fw_to_car(fw, '', log=False) assert exact and found == {candidate} fp = gen_empty_fingerprint() @@ -252,7 +255,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, ): host = card_module.Car() assert host.CP.openpilotLongitudinalControl == (not stock) - assert host.CP.safetyConfigs[0].safetyParam == (0x9249 if stock else 0x9049) + assert host.CP.safetyConfigs[0].safetyParam == sdsu_words[stock] assert host.CP.alternativeExperience == 32 controls, sd = Controls(), sd_module.SelfdriveD(CP=host.CP) assert controls.CP.to_dict() == host.CP.to_dict() @@ -307,7 +310,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, patch.object(time, 'clock_gettime_ns', side_effect=lambda c: clock[0] if c in (time.CLOCK_MONOTONIC, time.CLOCK_BOOTTIME) else real_clock(c)) ) try: - for tick in range(1, {'nominal': 251, 'physical': 601, 'faults': 801, 'bad_dlc': 801, 'off': 401, 'cold_low': 251}[scenario]): + for tick in range(1, {'nominal': 251, 'physical': 601, 'faults': 801, 'bad_dlc': 801, 'off': 401, 'cold_low': 251, 'hybrid_stop': 251}[scenario]): if deterministic: clock[0] += 10_000_000 else: @@ -316,6 +319,8 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, now = time.monotonic_ns() safety.set_timer((now // 1000) & 0xFFFFFFFF) fields = {'cruise': tick >= 150} + if scenario == 'hybrid_stop': + fields.update(stopped=200 <= tick < 230) if scenario == 'cold_low': fields.update(cruise=False, stopped=True) if scenario == 'physical': @@ -335,7 +340,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, ) elif scenario == 'off' and tick == 250: params.put_bool('DisableOpenpilotLongitudinal', not stock, block=True) - frames = physical(packer, **fields) + frames = (physical if physical_frames is None else physical_frames)(packer, **fields) if scenario == 'bad_dlc' and 630 <= tick < 665: malformed = packer.make_can_msg('SDSU', 0, {'STATE': 0}) frames.append((malformed[0], malformed[1][:7], malformed[2])) @@ -397,8 +402,15 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, if f.address == 0x2E4: decoded = decode(host.CP, (f.address, bytes(f.dat), f.src), 'STEERING_LKA') positive_steering |= bool(decoded['STEER_REQUEST'] and decoded['STEER_TORQUE_CMD']) + elif f.address == 0x191 and angle: + decoded = decode(host.CP, (f.address, bytes(f.dat), f.src), 'STEERING_LTA') + positive_steering |= bool(decoded['STEER_REQUEST'] and decoded['STEER_ANGLE_CMD']) elif f.address == 0x343: - positive_accel |= decode(host.CP, (f.address, bytes(f.dat), f.src), 'ACC_CONTROL')['ACCEL_CMD'] > 0 + acc = decode(host.CP, (f.address, bytes(f.dat), f.src), 'ACC_CONTROL') + positive_accel |= acc['ACCEL_CMD'] > 0 + if assert_hybrid_release and cc.longActive: + assert host.CP.flags & ToyotaFlags.HYBRID + assert acc['RELEASE_STANDSTILL'] == 1, ('active hybrid resume behavior changed', tick, acc) trace.append( { 'tick': tick, @@ -422,7 +434,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, } ) nominal = tick >= 220 and ( - scenario in ('nominal', 'cold_low') + scenario in ('nominal', 'cold_low', 'hybrid_stop') or scenario == 'off' and tick < 250 or scenario == 'physical' @@ -458,7 +470,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, if scenario == 'off' and tick >= 310: if stock: assert not host.CP.openpilotLongitudinalControl and not cc.longActive - assert host.CP.safetyConfigs[0].safetyParam == 0x9249 + assert host.CP.safetyConfigs[0].safetyParam == sdsu_words[1] else: assert not host.CI.CC.rav4h_filter_input.update(time.monotonic_ns()) for f in outgoing.sendcan: diff --git a/prebuilt.json b/prebuilt.json index a49b05d995..9139cf7363 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "7ad958f6080cde1c5e80081ee1cd24927df9a703189143fcb8d15c8918cc5564", "schema_version": 1} +{"protected_inventory_sha256": "8ea2d6a19e2bab4b32310b820f3061f94c9dd66957fc2c1ba8686131932c1949", "schema_version": 1}