mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-29 09:33:48 +08:00
Restore legacy Toyota smartDSU control paths
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 = ¤t_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);
|
||||
|
||||
@@ -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,))
|
||||
|
||||
@@ -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
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "74afd213d9a908bdda84394f6eb7485f856ce018fb9e3c4aee9345851669a752", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "e5c87e2eda80b8782d1c74fa7a592b1ac9074153641e9275ce1d110abbff8636", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user