Restore legacy Toyota smartDSU control paths

This commit is contained in:
firestar5683
2026-10-22 21:14:16 -05:00
parent 9983d3a4cb
commit 4d8467ce37
19 changed files with 577 additions and 36 deletions
@@ -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, eps8_sdsu
from opendbc.car.toyota import prius_longitudinal, rav4h_filter, pedal, eps8_sdsu, legacy_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,7 +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.sdsu_hybrid = (eps8_sdsu.configured(CP) or legacy_sdsu.configured(CP)) and bool(CP.flags & ToyotaFlags.HYBRID)
self.permit_braking = True
self.steer_rate_counter = 0
self.distance_button = 0
@@ -145,6 +145,10 @@ class CarController(CarControllerBase):
filter_allowed = bool(self.rav4h_filter_input is not None and self.rav4h_filter_input.update(now_nanos) and
CS.out.canValid and not CS.out.canTimeout and CS.sdsu_state == 0 and
not CS.out.accFaulted and not CS.out.stockAeb)
if legacy_sdsu.configured(self.CP) and self.CP.carFingerprint in legacy_sdsu.ALT_MAIN_CARS:
# The native owner requires fresh physical DSU MAIN independently of
# status. Neutral output must also withdraw its standstill release.
filter_allowed = filter_allowed and bool(CS.out.cruiseState.available)
pedal_owner = pedal.configured(self.CP)
if pedal_owner:
filter_allowed = bool(self.pedal_input is not None and self.pedal_input.update(now_nanos) and CS.pedal_ready)
@@ -272,7 +276,7 @@ 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:
elif self.sdsu_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
+6 -4
View File
@@ -2,7 +2,7 @@ import copy
import math
from opendbc.can import CANDefine, CANParser
from opendbc.car.toyota import pedal
from opendbc.car.toyota import pedal, legacy_sdsu
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
@@ -154,7 +154,7 @@ class CarState(CarStateBase):
if self.CP.flags & ToyotaFlags.UNSUPPORTED_DSU:
# TODO: find the bit likely in DSU_CRUISE that describes an ACC fault. one may also exist in CLUTCH
ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0
if self.CP.carFingerprint == CAR.LEXUS_IS:
if self.CP.carFingerprint == CAR.LEXUS_IS or legacy_sdsu.configured(self.CP) and self.CP.carFingerprint in legacy_sdsu.ALT_MAIN_CARS:
stamp = cp.ts_nanos["DSU_CRUISE"]["MAIN_ON"]
ret.cruiseState.available &= bool(cp.main_valid and stamp > 0 and 0 <= cp._last_update_nanos - stamp <= 303_031_000)
ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS
@@ -298,7 +298,8 @@ class CarState(CarStateBase):
if CP.carFingerprint == CAR.TOYOTA_SIENNA_4TH_GEN:
pt_messages.append(("PCM_CRUISE_4", 1))
if CP.carFingerprint == CAR.LEXUS_IS:
alternate_sdsu_main = legacy_sdsu.configured(CP) and CP.carFingerprint in legacy_sdsu.ALT_MAIN_CARS
if CP.carFingerprint == CAR.LEXUS_IS or alternate_sdsu_main:
pt_messages.append(("DSU_CRUISE", 10))
if CP.flags & ToyotaFlags.LONG_FILTER:
@@ -308,7 +309,8 @@ class CarState(CarStateBase):
if pedal.configured(CP):
pt_messages.append(("GAS_SENSOR", 50))
pt_parser = (LexusIsCANParser if CP.carFingerprint == CAR.LEXUS_IS else pedal.PedalCANParser if pedal.configured(CP) else
pt_parser = (legacy_sdsu.AlternateMainSDSUCANParser if alternate_sdsu_main else
LexusIsCANParser if CP.carFingerprint == CAR.LEXUS_IS else pedal.PedalCANParser if pedal.configured(CP) else
Rav4hCANParser if rav4h_filter_configured(CP) else CANParser)
return {
Bus.pt: pt_parser(DBC[CP.carFingerprint][Bus.pt], pt_messages, 0),
+7 -2
View File
@@ -193,12 +193,17 @@ class CarInterface(CarInterfaceBase):
eps8_filter = eps8_sdsu.hardware_supported(candidate, fingerprint, car_fw)
if eps8_filter:
eps8_sdsu.apply(ret, alpha_long)
from opendbc.car.toyota import legacy_sdsu
legacy_filter = legacy_sdsu.hardware_supported(candidate, fingerprint, car_fw)
if legacy_filter:
legacy_sdsu.apply(ret)
# 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
(eps8_filter and bool(ret.flags & ToyotaFlags.TSS2)) or not ret.flags & ToyotaFlags.LONG_FILTER)
(eps8_filter and bool(ret.flags & ToyotaFlags.TSS2)) or
(legacy_filter and candidate == CAR.TOYOTA_PRIUS_V) 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.
@@ -210,7 +215,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 or ret.flags & ToyotaFlags.LONG_FILTER and not (rav4h_sdsu or eps8_filter):
if ret.flags & ToyotaFlags.TSS2 or ret.flags & ToyotaFlags.LONG_FILTER and not (rav4h_sdsu or eps8_filter or legacy_filter):
ret.flags |= ToyotaFlags.RAISED_ACCEL_LIMIT.value
# Hybrids have much quicker longitudinal actuator response
@@ -0,0 +1,107 @@
"""Exact EPS5 smartDSU profiles with alternate brake and DSU MAIN sources."""
from opendbc.can import CANParser
from opendbc.car import structs
from opendbc.car.toyota.values import CAR, ToyotaFlags
PROFILES = {
CAR.TOYOTA_COROLLA: (0x9158, 0x9358),
CAR.TOYOTA_RAV4: (0x9149, 0x9349),
CAR.LEXUS_ES: (0x9149, 0x9349),
CAR.TOYOTA_PRIUS_V: (0x9164, 0x9364),
CAR.LEXUS_CTH: (0x9164, 0x9364),
CAR.LEXUS_GS_F: (0xD149, 0xD349),
CAR.LEXUS_IS: (0xD04D, 0xD24D),
CAR.LEXUS_RC: (0xD04D, 0xD24D),
}
WORDS = frozenset(word for pair in PROFILES.values() for word in pair)
ALT_MAIN_CARS = frozenset((CAR.LEXUS_GS_F, CAR.LEXUS_IS, CAR.LEXUS_RC))
def hardware_supported(candidate, fingerprint, car_fw):
if (
candidate not in PROFILES
or fingerprint.get(0, {}).get(0x2FF) != 8
or any(0x2FF in messages and (bus != 2 or messages[0x2FF] != 8) for bus, messages in fingerprint.items() if bus != 0)
or any(address in messages for messages in fingerprint.values() for address in (0x201, 0x2AA))
or 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]
eps = structs.CarParams.Ecu.eps
required = {structs.CarParams.Ecu.fwdCamera, structs.CarParams.Ecu.fwdRadar}
# CT/PriusV do not expose an EPS catalog response. Bind their catalog engine
# identity instead; never invent an EPS response to pass enrollment.
required.add(eps if any(ecu == eps for ecu, _, _ in known) else structs.CarParams.Ecu.engine)
for ecu in required | {eps}:
observed = [fw for fw in car_fw if fw.ecu == ecu]
if ecu in required and not observed:
return False
if 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):
cp.flags |= int(ToyotaFlags.LONG_FILTER)
cp.alphaLongitudinalAvailable = False
cp.openpilotLongitudinalControl = True
cp.safetyConfigs[0].safetyParam = PROFILES[cp.carFingerprint][0]
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 len(cp.safetyConfigs) != 1
or cp.steerControlType != structs.CarParams.SteerControlType.torque
):
return False
required = int(CAR(cp.carFingerprint).config.flags | ToyotaFlags.LONG_FILTER)
if cp.flags & required != required or int(cp.flags) & ~int(required | ToyotaFlags.HYBRID):
return False
config = cp.safetyConfigs[0]
return config.safetyModel == structs.CarParams.SafetyModel.toyota and config.safetyParam == PROFILES[cp.carFingerprint][not cp.openpilotLongitudinalControl]
def prepare_stock(cp):
if not configured(cp):
return False
cp.openpilotLongitudinalControl = False
cp.safetyConfigs[0].safetyParam = PROFILES[cp.carFingerprint][1]
cp.autoResumeSng = False
return True
class AlternateMainSDSUCANParser(CANParser):
"""Neither malformed DSU MAIN nor malformed status can renew its source."""
def __init__(self, *args):
super().__init__(*args)
self.main_valid = False
def update(self, strings, sendcan=False):
batches = []
for stamp, frames in strings:
accepted = []
for address, data, bus in frames:
if bus == self.bus:
if address == 0x365:
self.main_valid = len(data) == 7
if not self.main_valid:
continue
if address == 0x2FF and len(data) != 8:
continue
accepted.append((address, data, bus))
batches.append((stamp, accepted))
return super().update(batches, sendcan)
@@ -34,8 +34,8 @@ def hardware_supported(candidate, fingerprint, car_fw):
def configured(cp):
from opendbc.car.toyota import eps8_sdsu
if eps8_sdsu.configured(cp):
from opendbc.car.toyota import eps8_sdsu, legacy_sdsu
if legacy_sdsu.configured(cp) or 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
@@ -61,8 +61,8 @@ def enabled(cp):
def prepare_stock(cp):
from opendbc.car.toyota import eps8_sdsu
if eps8_sdsu.prepare_stock(cp):
from opendbc.car.toyota import eps8_sdsu, legacy_sdsu
if legacy_sdsu.prepare_stock(cp) or eps8_sdsu.prepare_stock(cp):
return True
if not configured(cp):
return False
@@ -1,3 +1,4 @@
CM_ "IMPORT _community.dbc";
CM_ "IMPORT _toyota_2017.dbc";
CM_ "IMPORT _toyota_adas_standard.dbc";
+36 -7
View File
@@ -94,6 +94,20 @@ static bool toyota_rx_ready(uint32_t now) {
return rx_valid;
}
static bool toyota_legacy_main_current(void) {
bool ready = true;
if (toyota_legacy_sdsu && toyota_alt_cruise) {
ready = false;
if (current_safety_config.rx_checks_len == 6) {
const RxStatus *main_status = &current_safety_config.rx_checks[5].status;
ready = main_status->msg_seen && main_status->valid_checksum && main_status->valid_quality_flag &&
!main_status->lagging && (main_status->wrong_counters < MAX_WRONG_COUNTERS) && acc_main_on &&
(safety_get_ts_elapsed(microsecond_timer_get(), main_status->last_timestamp) <= 303031U);
}
}
return ready;
}
static bool toyota_hold_ready(int permission) {
const int hold_mask = ALT_EXP_TOYOTA_AUTO_HOLD | ALT_EXP_TOYOTA_AEB_HOLD;
const uint32_t now = microsecond_timer_get();
@@ -165,7 +179,7 @@ static bool toyota_get_quality_flag_valid(const CANPacket_t *msg) {
static void toyota_optional_rx_hook(const CANPacket_t *msg) {
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) && (msg->addr == 0x262U)) ? 5U : 8U;
const unsigned int aol_source_len = ((toyota_aol_rav4h_filter || toyota_alt_cruise || toyota_legacy_sdsu) && (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_aol_enabled && (msg->bus == 0U)) {
@@ -353,7 +367,7 @@ 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_sdsu_status || toyota_pedal) || aol_rx_healthy();
const bool filter_ready = (!(toyota_sdsu_status || toyota_pedal) || aol_rx_healthy()) && toyota_legacy_main_current();
const bool filter_long_ready = filter_ready && (!toyota_sdsu_status || toyota_sdsu_control);
// Check if msg is sent on BUS 0
@@ -519,6 +533,10 @@ static bool toyota_tx_hook(const CANPacket_t *msg) {
}
}
// Required physical DSU MAIN cannot be manufactured through host TX.
if (toyota_legacy_sdsu && toyota_alt_cruise && msg_matches(msg, 0x365U, 0U)) {
tx = false;
}
return tx;
}
@@ -580,7 +598,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_eps8_sdsu;
toyota_radar_diagnostics_allowed = !toyota_stock_longitudinal && !toyota_secoc && (param != 0x1049U) && (param != 0x1449U) && !toyota_eps8_sdsu && !toyota_legacy_sdsu;
toyota_lta = GET_FLAG(param, TOYOTA_PARAM_LTA);
toyota_dbc_eps_torque_factor = param & TOYOTA_EPS_FACTOR;
@@ -601,7 +619,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) || toyota_eps8_sdsu) {
if ((param == 0x1049U) || (param == 0x9049U) || (param == 0x1449U) || toyota_eps8_sdsu || toyota_legacy_sdsu) {
SET_TX_MSGS(TOYOTA_FILTER_LONG_TX_MSGS_SET, ret);
} else {
SET_TX_MSGS(TOYOTA_LONG_TX_MSGS, ret);
@@ -621,6 +639,7 @@ static safety_config toyota_init(uint16_t param) {
static RxCheck toyota_rx_checks[] = {
TOYOTA_RX_CHECKS(false)
{.msg = {{0x2FF, 0, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = false}, { 0 }, { 0 }}},
{.msg = {{0x365, 0, 7, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
};
// LTA alone validates initialized measured angle. Preserve the existing
// alternate-brake selection only on non-LTA profiles.
@@ -630,9 +649,19 @@ static safety_config toyota_init(uint16_t param) {
.bus = 0U, .len = toyota_alt_cruise ? 7 : (toyota_pedal ? 6 : 8), .frequency = toyota_alt_cruise ? 10U : 50U,
.ignore_checksum = !toyota_pedal, .ignore_counter = !toyota_pedal,
.max_counter = toyota_pedal ? 15U : 0U, .ignore_quality_flag = toyota_alt_cruise};
SET_RX_CHECKS(toyota_rx_checks, ret);
if (!toyota_pedal && !toyota_alt_cruise && !toyota_sdsu_status) {
ret.rx_checks_len -= 1; // Ordinary profiles do not require an auxiliary source.
if (toyota_legacy_sdsu) {
// Alternate DSU MAIN and smartDSU state are independent mandatory sources.
toyota_rx_checks[4].msg[0] = (CanMsgCheck){.addr = 0x2FF, .bus = 0U, .len = 8, .frequency = 50U,
.ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = false};
}
ret.rx_checks = toyota_rx_checks;
ret.disable_forwarding = false;
if (toyota_legacy_sdsu && toyota_alt_cruise) {
ret.rx_checks_len = 6;
} else if (toyota_pedal || toyota_alt_cruise || toyota_sdsu_status) {
ret.rx_checks_len = 5;
} else {
ret.rx_checks_len = 4;
}
}
@@ -10,6 +10,7 @@ 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_legacy_sdsu = false;
static bool toyota_sdsu_control = false;
static bool toyota_aol_main = false;
static bool toyota_aol_drive = false;
@@ -66,7 +67,7 @@ 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) && (msg->addr == 0x262U)) {
} else if ((toyota_aol_rav4h_filter || toyota_alt_cruise || toyota_legacy_sdsu) && (msg->addr == 0x262U)) {
expected_len = 5U;
} else {
}
@@ -126,18 +127,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_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 && ((param & 0x4000U) != 0U));
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_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);
toyota_sdsu_status = toyota_aol_rav4h_filter || toyota_eps8_sdsu || toyota_legacy_sdsu;
toyota_aol_stock = (param == 585U) || (param == 4681U) || (param == 0x9249U) || (param == 0x2249U) || (param == 0x424DU) || (param == 0x649U) || (param == 0x1649U) || (param == 0x8249U) || (param == 0x8649U) || (toyota_legacy_sdsu && ((param & 0x200U) != 0U));
#ifdef ALLOW_DEBUG
toyota_aol_secoc = (param == 2121U);
#else
toyota_aol_secoc = false;
#endif
toyota_aol_enabled = (toyota_eps8_sdsu && (alternative_experience == 32)) || (toyota_aol_lta && (alternative_experience == 32)) || (toyota_aol_stock && (alternative_experience == 32)) ||
toyota_aol_enabled = ((toyota_eps8_sdsu || toyota_legacy_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
@@ -0,0 +1,178 @@
import pytest
from opendbc.can import CANPacker
from opendbc.car import structs
from opendbc.car.toyota import legacy_sdsu
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
WORDS = sorted(legacy_sdsu.WORDS)
class Physical:
def __init__(self, word, experience=32, request=3, *, reset=True, start=1_000_000):
self.native = libsafety_py.libsafety
if reset:
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, self.request = word, start, request
self.packer = CANPacker("toyota_new_mc_pt_generated" if word & 0x100 else "toyota_tnga_k_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, sdsu=status, **fields)
if self.word & 0x4000:
main = self.packer.make_can_msg("DSU_CRUISE", 0, {"MAIN_ON": fields.get("main", True), "SET_SPEED": 90})
if bad != "main_drop":
frames.append((main[0], main[1][:6] if bad == "main_length" else main[1], 2 if bad == "main_bus" else 0))
for address, data, bus in frames:
if address == 0x2FF and bad == "status_length":
data = data[:7]
if address == 0x2FF and bad == "status_bus":
bus = 2
if address == 0x262 and bad == "eps_checksum":
data = data[:-1] + bytes([data[-1] ^ 1])
if address == 0x262 and bad == "eps_length":
data = data + bytes(3)
if address == 0x224 and bad == "brake_address":
address = 0x226
self.native.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, data))
self.native.safety_tick()
self.native.aol_set_host_request(self.request)
def accel(self, value=0.1, cancel=0):
return self.tx(self.packer.make_can_msg("ACC_CONTROL", 0, {"ACCEL_CMD": value, "CANCEL_REQ": cancel}))
def steer(self, value=1):
return self.tx(self.packer.make_can_msg("STEERING_LKA", 0, {"STEER_TORQUE_CMD": value, "STEER_REQUEST": value != 0}))
def tx(self, frame):
return bool(self.native.safety_tx_hook(libsafety_py.make_CANPacket(frame[0], frame[2], frame[1])))
def warm(self):
for _ in range(25):
self.tick(cruise=False)
for _ in range(25):
self.tick()
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_exact_paired_sources_stock_and_aeb(word):
case = Physical(word)
try:
case.warm()
expected = 1 if word & 0x200 else 3
assert case.native.aol_get_permission_mask() == expected
assert case.steer()
assert case.accel() == (not bool(word & 0x200))
assert case.accel(0, 1)
case.tick(status=7)
assert case.native.aol_get_permission_mask() == 1
assert not case.accel()
case.tick(status=2)
assert case.native.aol_get_permission_mask() == 0
assert not case.accel() and not case.steer()
case.tick()
# A healthy source restores independent LAT. LONG still needs a real
# disengage/reengage edge after a required-source fault.
assert case.native.aol_get_permission_mask() == 1
case.tick(cruise=False)
case.tick(cruise=True)
assert case.native.aol_get_permission_mask() == expected
finally:
case.close()
@pytest.mark.parametrize("word", WORDS)
@pytest.mark.parametrize("bad", ["status_length", "status_bus", "eps_checksum", "eps_length"])
def test_malformed_sources_cannot_renew_ownership(word, bad):
case = Physical(word)
try:
case.warm()
assert case.native.aol_get_permission_mask() == (1 if word & 0x200 else 3)
for _ in range(45):
case.tick(bad=bad)
assert case.native.aol_get_permission_mask() == 0
assert not case.accel()
finally:
case.close()
@pytest.mark.parametrize("word", [w for w in WORDS if w & 0x4000])
@pytest.mark.parametrize("bad", ["main_drop", "main_length", "main_bus"])
@pytest.mark.parametrize("experience,host_request", [(0, 0), (32, 3)])
def test_main_deadline_holds_even_with_ordinary_cruise_permission(word, bad, experience, host_request):
case = Physical(word, experience, host_request)
try:
case.warm()
assert case.native.get_controls_allowed()
assert case.steer()
for _ in range(45):
case.tick(bad=bad)
# PCM_CRUISE is still fresh and engaged. Missing physical DSU MAIN must
# independently veto TX in both ordinary and protocol-owned control.
assert case.native.get_controls_allowed()
assert not case.steer() and not case.accel()
assert not case.tx(case.packer.make_can_msg("DSU_CRUISE", 0, {"MAIN_ON": 1, "SET_SPEED": 90}))
finally:
case.close()
@pytest.mark.parametrize("word", [w for w in WORDS if w & 0x100])
def test_alternate_brake_source_is_mandatory(word):
case = Physical(word)
try:
case.warm()
for _ in range(40):
case.tick(bad="brake_address")
assert not case.accel() and not case.steer()
finally:
case.close()
@pytest.mark.parametrize("word", WORDS)
def test_actual_eps_measurement_uses_original_scale(word):
case = Physical(word)
try:
frame = case.packer.make_can_msg("STEER_TORQUE_SENSOR", 0, {"STEER_TORQUE_EPS": 100})
for _ in range(8):
case.native.safety_rx_hook(libsafety_py.make_CANPacket(frame[0], frame[2], frame[1]))
assert case.native.get_torque_meas_min() == (word & 0xFF) - 1
assert case.native.get_torque_meas_max() == (word & 0xFF) + 1
finally:
case.close()
def test_warm_switches_clear_both_required_sources():
now = 1_000_000
sequence = [0x9049, *WORDS, 0x9249, *reversed(WORDS), 0x9049]
case = None
try:
for index, word in enumerate(sequence):
case = Physical(word, reset=index == 0, start=now)
assert case.native.aol_get_permission_mask() == 0
assert not case.steer() and not case.accel()
case.warm()
assert case.native.aol_get_permission_mask() == (1 if word & 0x200 else 3)
now = case.now
finally:
if case is not None:
case.close()
@pytest.mark.parametrize("word", WORDS)
def test_no_radar_diagnostics(word):
case = Physical(word)
try:
case.warm()
for payload in ("0f023e0000000000", "0f02280100000000"):
assert not case.tx((0x750, bytes.fromhex(payload), 0))
finally:
case.close()
@@ -40,7 +40,7 @@ class TestRav4hFilterNative(unittest.TestCase):
bad.data[4] ^= 1
self.safety.safety_rx_hook(bad)
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
for word in (0x5049, 0x5249, 0x9048, 0x904A, 0x9149, 0x9449, 0xB049, 0xD049):
for word in (0x5049, 0x5249, 0x9048, 0x904A, 0x914A, 0x9449, 0xB049, 0xD049):
self.init(word, 32)
self.physical(cruise=True)
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
@@ -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) || (word == 0x8049U) || (word == 0x8249U) || (word == 0x8449U) || (word == 0x8649U);
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) || (word == 0x9158U) || (word == 0x9358U) || (word == 0x9149U) || (word == 0x9349U) || (word == 0x9164U) || (word == 0x9364U) || (word == 0xd149U) || (word == 0xd349U) || (word == 0xd04dU) || (word == 0xd24dU);
auto toyota = status();
toyota.safety_mode = 2U;
toyota.safety_param = static_cast<uint16_t>(word);
+6 -1
View File
@@ -5,7 +5,12 @@ 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, 0x8049, 0x8249, 0x8449, 0x8649))
AOL_WORDS = frozenset((
73, 585, 4169, 4681, 2121, 0x9049, 0x9249,
0x2049, 0x2249, 0x424D, 0x449, 0x649, 0x1449, 0x1649,
0x8049, 0x8249, 0x8449, 0x8649, 0x9158, 0x9358, 0x9149,
0x9349, 0x9164, 0x9364, 0xd149, 0xd349, 0xd04d, 0xd24d,
))
SECOC_TORQUE_CARS = frozenset((CAR.TOYOTA_SIENNA_4TH_GEN,))
+1 -1
View File
@@ -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) || (param == 0x8049U) || (param == 0x8249U) || (param == 0x8449U) || (param == 0x8649U);
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) || (param == 0x9158U) || (param == 0x9358U) || (param == 0x9149U) || (param == 0x9349U) || (param == 0x9164U) || (param == 0x9364U) || (param == 0xd149U) || (param == 0xd349U) || (param == 0xd04dU) || (param == 0xd24dU);
}
inline constexpr AolSafetyProfile TOYOTA_AOL_PROFILE{2U, toyota_aol_param, true};
@@ -2,12 +2,14 @@
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
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:
return bool(configured(cp) and cp.carFingerprint in (CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_AVALON) and cp.alternativeExperience == 32 and
return bool(configured(cp) and
(cp.carFingerprint in (CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_AVALON) or legacy_sdsu.configured(cp)) 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
@@ -0,0 +1,86 @@
import pytest
from opendbc.car import gen_empty_fingerprint, structs
from opendbc.car.toyota import legacy_sdsu
from opendbc.car.toyota.carstate import CarState
from opendbc.car.toyota.interface import CarInterface, apply_toyota_auto_hold
from opendbc.car.toyota.values import CAR, CarControllerParams, EPS_SCALE
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(legacy_sdsu.PROFILES))
@pytest.mark.parametrize('alpha', [False, True])
@pytest.mark.parametrize('hybrid', [False, True])
def test_full_configuration_and_tune_preserved(candidate, alpha, hybrid):
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)
)
fp = gen_empty_fingerprint()
baseline = CarInterface.get_params(candidate, fp, fw, alpha, False, False)
fp[0][0x2FF] = 8
actual = CarInterface.get_params(candidate, fp, fw, alpha, False, False)
assert legacy_sdsu.configured(actual) and qualified(actual)
assert actual.openpilotLongitudinalControl and not actual.alphaLongitudinalAvailable
assert actual.autoResumeSng == (candidate == CAR.TOYOTA_PRIUS_V)
assert CarState(actual).eps_torque_scale == EPS_SCALE[candidate] / 100.0
before, after = baseline.to_dict(), actual.to_dict()
for name in ('flags', 'safetyConfigs', 'openpilotLongitudinalControl', 'alphaLongitudinalAvailable', 'autoResumeSng'):
before.pop(name)
after.pop(name)
assert before == after
old_limits, new_limits = CarControllerParams(baseline), CarControllerParams(actual)
assert (old_limits.ACCEL_MIN, old_limits.ACCEL_MAX) == (new_limits.ACCEL_MIN, new_limits.ACCEL_MAX)
assert not apply_toyota_auto_hold(actual, True)
assert legacy_sdsu.configured(actual)
assert legacy_sdsu.prepare_stock(actual) and legacy_sdsu.configured(actual) and qualified(actual)
assert actual.safetyConfigs[0].safetyParam == legacy_sdsu.PROFILES[candidate][1]
@pytest.mark.parametrize('candidate', sorted(legacy_sdsu.PROFILES))
@pytest.mark.parametrize('bad', ['source7', 'copy7', 'only_copy', 'bus1', 'pedal', 'mixed', 'bypass', 'firmware'])
def test_hardware_boundaries(candidate, bad):
fp = gen_empty_fingerprint()
fp[0][0x2FF] = fp[2][0x2FF] = 8
fw = firmware(candidate)
if bad == 'source7':
fp[0][0x2FF] = 7
elif bad == 'copy7':
fp[2][0x2FF] = 7
elif bad == 'only_copy':
del fp[0][0x2FF]
elif bad == 'bus1':
fp[1][0x2FF] = 8
elif bad == 'pedal':
fp[0][0x201] = 6
elif bad == 'mixed':
fp[0][0x2AA] = 8
elif bad == 'bypass':
fp[2][0x343] = 8
else:
fw = []
assert not legacy_sdsu.hardware_supported(candidate, fp, fw)
@pytest.mark.parametrize('candidate', sorted(legacy_sdsu.PROFILES))
@pytest.mark.parametrize('field,value', [('fwVersion', b'unknown'), ('bus', 1), ('brand', 'ford'), ('logging', True), ('address', 0x123), ('subAddress', 1)])
def test_catalog_identity_is_required(candidate, field, value):
fp = gen_empty_fingerprint()
fp[0][0x2FF] = 8
fw = firmware(candidate)
camera = next(item for item in fw if item.ecu == structs.CarParams.Ecu.fwdCamera)
setattr(camera, field, value)
assert not legacy_sdsu.hardware_supported(candidate, fp, fw)
@pytest.mark.parametrize('candidate', [CAR.TOYOTA_PRIUS_V, CAR.LEXUS_CTH])
def test_missing_eps_catalog_never_fabricates_a_response(candidate):
fp = gen_empty_fingerprint()
fp[0][0x2FF] = 8
fw = firmware(candidate)
assert not any(item.ecu == structs.CarParams.Ecu.eps for item in fw)
assert legacy_sdsu.hardware_supported(candidate, fp, fw)
assert not legacy_sdsu.hardware_supported(candidate, fp, [item for item in fw if item.ecu != structs.CarParams.Ecu.engine])
fw.append(structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.eps, address=0x7A1, fwVersion=b'unknown EPS response', brand='toyota', bus=0))
assert not legacy_sdsu.hardware_supported(candidate, fp, fw)
@@ -0,0 +1,111 @@
import pytest
from opendbc.car import structs
from openpilot.starpilot.aol.wire import decode_safety
from opendbc.car.toyota import legacy_sdsu
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_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=False, hybrid=False, main_fault=None):
tick = 0
def physical(packer, **fields):
nonlocal tick
tick += 1
frames = helpers.physical(packer, **fields)
frames.append(packer.make_can_msg('PCS_HUD', 0, {}))
if candidate in legacy_sdsu.ALT_MAIN_CARS:
main = packer.make_can_msg('DSU_CRUISE', 0, {'MAIN_ON': fields.get('main', True), 'SET_SPEED': 90})
if main_fault is not None and 260 <= tick < 311:
if main_fault == 'length':
frames.append((main[0], main[1][:6], main[2]))
elif main_fault == 'bus':
frames.append((main[0], main[1], 2))
else:
assert main_fault == 'drop'
else:
frames.append(main)
frames.append(packer.make_can_msg('PCM_CRUISE_ALT', 0, {'UI_SET_SPEED': 90}))
return frames
def observe(tick, host, cs, cc, safety, native_messages):
if scenario == 'physical' and 340 <= tick < 355:
feedback = decode_safety(bytes(native_messages[0].aolSafetyWire))
assert feedback is not None
if not feedback.requestedLateral or not feedback.lateralAllowed:
assert not cc.latActive, 'LAT must wait for its real native acknowledgement'
if main_fault is not None and 301 <= tick < 308:
assert not cc.latActive and not cc.longActive
assert not cs.cruiseState.available
assert safety.aol_get_permission_mask() == 0
fw = helpers.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)
)
helpers.run_case(
stock,
scenario,
candidate=candidate,
bridge=bridge,
input_clock=True,
aol=aol,
sdsu_words=legacy_sdsu.PROFILES[candidate],
alpha_long=alpha,
physical_frames=physical,
brake_stop_check_start=355,
firmware_override=fw,
assert_hybrid_release=hybrid,
on_sample=observe,
)
@pytest.mark.parametrize('candidate', sorted(legacy_sdsu.PROFILES))
@pytest.mark.parametrize('stock', [False, True])
def test_each_legacy_sdsu_actual_callers(candidate, stock, host_heartbeat_bridge):
run(candidate, stock, host_heartbeat_bridge)
@pytest.mark.parametrize('candidate', [CAR.TOYOTA_COROLLA, CAR.TOYOTA_RAV4, CAR.LEXUS_CTH, CAR.LEXUS_GS_F, CAR.LEXUS_IS])
@pytest.mark.parametrize('stock', [False, True])
@pytest.mark.parametrize('scenario', ['physical', 'faults', 'bad_dlc', 'off'])
def test_legacy_sdsu_faults_physical_and_saved_off(candidate, stock, scenario, host_heartbeat_bridge):
run(candidate, stock, host_heartbeat_bridge, scenario)
@pytest.mark.parametrize('candidate', sorted(legacy_sdsu.PROFILES))
@pytest.mark.parametrize('stock', [False, True])
def test_legacy_ordinary_cruise_without_aol(candidate, stock, host_heartbeat_bridge):
run(candidate, stock, host_heartbeat_bridge, aol=False)
@pytest.mark.parametrize('candidate', [CAR.TOYOTA_COROLLA, CAR.LEXUS_CTH, CAR.LEXUS_GS_F])
@pytest.mark.parametrize('stock', [False, True])
def test_legacy_alpha_does_not_replace_nongated_owner(candidate, stock, host_heartbeat_bridge):
run(candidate, stock, host_heartbeat_bridge, alpha=True)
@pytest.mark.parametrize('candidate', sorted(legacy_sdsu.PROFILES))
def test_legacy_hybrid_active_resume(candidate, host_heartbeat_bridge):
# CT supports active stop/start. Other models retain their original minimum
# engagement speed; test their hybrid command behavior above that speed.
scenario = "hybrid_stop" if candidate == CAR.LEXUS_CTH else "nominal"
run(candidate, False, host_heartbeat_bridge, scenario, hybrid=True)
@pytest.mark.parametrize('candidate', sorted(legacy_sdsu.ALT_MAIN_CARS))
@pytest.mark.parametrize('stock', [False, True])
@pytest.mark.parametrize('bad', ['drop', 'length', 'bus'])
def test_dsu_main_and_status_are_independent_sources(candidate, stock, bad, host_heartbeat_bridge):
run(candidate, stock, host_heartbeat_bridge, 'main_lease', main_fault=bad)
@pytest.mark.parametrize('candidate', sorted(legacy_sdsu.PROFILES))
@pytest.mark.parametrize('stock', [False, True])
def test_legacy_lateral_below_stock_engagement_speed(candidate, stock, host_heartbeat_bridge):
run(candidate, stock, host_heartbeat_bridge, 'cold_low')
@@ -35,7 +35,7 @@ def test_filter_preserves_radar_and_existing_tune(alpha):
disable.assert_not_called()
@pytest.mark.parametrize('fault', ['length', 'camera', 'radar_bus', 'duplicate', 'pedal', 'sdsu', 'sibling'])
@pytest.mark.parametrize('fault', ['length', 'camera', 'radar_bus', 'mismatched_copy', 'pedal', 'sdsu', 'sibling'])
def test_filter_detection_rejects_wrong_topology(fault):
fp = gen_empty_fingerprint()
fp[0][0x2AA] = 8
@@ -44,8 +44,8 @@ def test_filter_detection_rejects_wrong_topology(fault):
fp[0][0x2AA] = 7
elif fault in ('camera', 'radar_bus'):
fp[2 if fault == 'camera' else 1][0x2AA] = fp[0].pop(0x2AA)
elif fault == 'duplicate':
fp[2][0x2AA] = 8
elif fault == 'mismatched_copy':
fp[2][0x2AA] = 7
elif fault in ('pedal', 'sdsu'):
fp[0][0x201 if fault == 'pedal' else 0x2FF] = 8
else:
@@ -226,7 +226,7 @@ def control_inputs(controls, published, state, sd_events, native, now):
def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H, bridge=None, input_clock=False, brake_stop_check_start=350,
sdsu_words=(0x9049, 0x9249), alpha_long=False, disable_setting=None, physical_frames=None, angle=False,
firmware_override=None, assert_hybrid_release=False, sdsu_mirror=False):
firmware_override=None, assert_hybrid_release=False, sdsu_mirror=False, on_sample=None):
SYNTHETIC_LAST.clear()
NATIVE_LAST.clear()
safety = libsafety_py.libsafety
@@ -312,7 +312,11 @@ 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, 'hybrid_stop': 251}[scenario]):
duration = {
'nominal': 251, 'physical': 601, 'faults': 801, 'bad_dlc': 801,
'off': 401, 'cold_low': 251, 'hybrid_stop': 251, 'main_lease': 501,
}[scenario]
for tick in range(1, duration):
if deterministic:
clock[0] += 10_000_000
else:
@@ -321,6 +325,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 == 'main_lease':
fields.update(cruise=tick >= 150 and not 330 <= tick < 360, main=not 340 <= tick < 350)
if scenario == 'hybrid_stop':
fields.update(stopped=200 <= tick < 230)
if scenario == 'cold_low':
@@ -441,6 +447,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H,
)
nominal = tick >= 220 and (
scenario in ('nominal', 'cold_low', 'hybrid_stop')
or scenario == 'main_lease' and (tick < 250 or tick >= 480)
or scenario == 'off'
and tick < 250
or scenario == 'physical'
@@ -484,6 +491,8 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H,
acc = decode(host.CP, (f.address, bytes(f.dat), f.src), 'ACC_CONTROL')
assert acc['ACCEL_CMD'] == 0 and acc['CANCEL_REQ'] and not acc['RELEASE_STANDSTILL']
if on_sample is not None:
on_sample(tick, host, cs, cc, safety, native)
prev = current_sd
assert seen_active
if scenario != 'cold_low':
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "74afd213d9a908bdda84394f6eb7485f856ce018fb9e3c4aee9345851669a752", "schema_version": 1}
{"protected_inventory_sha256": "e5c87e2eda80b8782d1c74fa7a592b1ac9074153641e9275ce1d110abbff8636", "schema_version": 1}