Support camera Toyota radar filter configurations

This commit is contained in:
firestar5683
2026-10-23 03:55:56 -05:00
parent ecc1db481b
commit af84074472
18 changed files with 5048 additions and 30 deletions
@@ -12,6 +12,9 @@ STOCK_WORD = 0x1249
def hardware_supported(candidate, fingerprint, car_fw):
from opendbc.car.toyota import camera_2aa_filter
if camera_2aa_filter.hardware_supported(candidate, fingerprint, car_fw):
return True
from opendbc.car.toyota import legacy_advert_filter
if candidate in legacy_advert_filter.PROFILES:
return legacy_advert_filter.hardware_supported(candidate, fingerprint, car_fw)
@@ -38,7 +41,10 @@ def hardware_supported(candidate, fingerprint, car_fw):
return True
def apply(cp):
def apply(cp, *, camera_twoaa=False):
if camera_twoaa:
from opendbc.car.toyota import camera_2aa_filter
return camera_2aa_filter.apply(cp)
from opendbc.car.toyota import legacy_advert_filter
if cp is not None and cp.carFingerprint in legacy_advert_filter.PROFILES:
return legacy_advert_filter.apply(cp)
@@ -51,6 +57,9 @@ def apply(cp):
def configured(cp):
from opendbc.car.toyota import camera_2aa_filter
if camera_2aa_filter.configured(cp):
return True
from opendbc.car.toyota import legacy_advert_filter
if cp is not None and cp.carFingerprint in legacy_advert_filter.PROFILES:
return legacy_advert_filter.configured(cp)
@@ -85,6 +94,9 @@ def enabled(cp):
def prepare_stock(cp):
from opendbc.car.toyota import camera_2aa_filter
if camera_2aa_filter.configured(cp):
return camera_2aa_filter.prepare_stock(cp)
from opendbc.car.toyota import legacy_advert_filter
if cp is not None and cp.carFingerprint in legacy_advert_filter.PROFILES:
return legacy_advert_filter.prepare_stock(cp)
@@ -0,0 +1,107 @@
"""Exact known camera2AA hardware; 2AA is never a status or button lease."""
from opendbc.car import structs
from opendbc.car.toyota.values import CAR, ToyotaFlags
from opendbc.car.toyota.advert_filter import AdvertFilterCANParser
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,
))
LONG_WORD = 0xC049
STOCK_WORD = 0xC249
WORDS = frozenset((LONG_WORD, STOCK_WORD))
def hardware_supported(candidate, fingerprint, car_fw):
if (candidate not in CARS or fingerprint.get(0, {}).get(0x2AA) != 8 or
any(address in messages for messages in fingerprint.values() for address in (0x201, 0x2FF)) or
any(bus not in (0, 2) or messages[0x2AA] != 8 for bus, messages in fingerprint.items() if 0x2AA in messages) or
any(fingerprint.get(2, {}).get(address, 8) != 8 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):
if cp is None or cp.brand != 'toyota' or cp.carFingerprint not in CARS or len(cp.safetyConfigs) != 1:
return False
cp.flags |= int(ToyotaFlags.RADAR_CAN_FILTER)
cp.safetyConfigs[0].safetyParam = LONG_WORD
cp.openpilotLongitudinalControl = True
cp.alphaLongitudinalAvailable = True
return True
def configured(cp):
try:
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 or
cp.steerControlType != structs.CarParams.SteerControlType.torque):
return False
required = int(CAR(cp.carFingerprint).config.flags | ToyotaFlags.RADAR_CAN_FILTER | ToyotaFlags.RAISED_ACCEL_LIMIT)
config = cp.safetyConfigs[0]
return bool(cp.flags & required == required and not int(cp.flags) & ~int(required | ToyotaFlags.HYBRID | ToyotaFlags.HAS_BSM) and
config.safetyModel == structs.CarParams.SafetyModel.toyota and
config.safetyParam == (LONG_WORD if cp.openpilotLongitudinalControl else STOCK_WORD) and
cp.autoResumeSng == cp.openpilotLongitudinalControl)
except (AttributeError, KeyError, IndexError, TypeError, ValueError, OverflowError):
return False
def enabled(cp):
return configured(cp) and cp.openpilotLongitudinalControl
def prepare_stock(cp):
if not configured(cp):
return False
cp.openpilotLongitudinalControl = False
cp.safetyConfigs[0].safetyParam = STOCK_WORD
cp.autoResumeSng = False
return True
class CameraTwoAACANParser(AdvertFilterCANParser):
"""Real MAIN/EPS authority; contradictory hardware identity stays refused."""
def __init__(self, *args):
super().__init__(*args)
self.hardware_ambiguous = False
def update(self, strings, sendcan=False):
batches = []
for stamp, frames in strings:
accepted = []
for address, data, bus in frames:
if address in (0x201, 0x2FF):
self.hardware_ambiguous = True
continue
if address == 0x2AA:
self.hardware_ambiguous |= bus not in (0, 2) or len(data) != 8
continue
accepted.append((address, data, bus))
batches.append((stamp, accepted))
result = super().update(batches, sendcan)
if self.hardware_ambiguous:
self.main_valid = self.eps_valid = False
return result
@@ -9,6 +9,7 @@ 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, legacy_sdsu, advert_filter, legacy_stop_timer, legacy_advert_filter
from opendbc.car.toyota import camera_2aa_filter
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
@@ -86,6 +87,7 @@ class CarController(CarControllerBase):
self.sdsu_hybrid = (eps8_sdsu.configured(CP) or legacy_sdsu.configured(CP)) and bool(CP.flags & ToyotaFlags.HYBRID)
self.legacy_stop_timer = legacy_stop_timer.enabled(CP)
self.legacy_advert_filter = legacy_advert_filter.configured(CP)
self.camera_twoaa_filter = camera_2aa_filter.configured(CP)
self.permit_braking = True
self.steer_rate_counter = 0
self.distance_button = 0
@@ -158,15 +160,16 @@ class CarController(CarControllerBase):
filter_allowed = bool(self.advert_filter_input is not None and self.advert_filter_input.update(now_nanos) and
CS.out.canValid and not CS.out.canTimeout and CS.out.cruiseState.available and
not CS.out.steerFaultTemporary and not CS.out.steerFaultPermanent and not CS.out.accFaulted and not CS.out.stockAeb)
legacy_advert_source_ready = not self.legacy_advert_filter or (
guarded_advert = self.legacy_advert_filter or self.camera_twoaa_filter
legacy_advert_source_ready = not guarded_advert or (
CS.out.gearShifter in (structs.CarState.GearShifter.drive, structs.CarState.GearShifter.sport) and not CS.out.doorOpen)
if self.legacy_advert_filter:
if guarded_advert:
filter_allowed = filter_allowed and legacy_advert_source_ready
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)
long_active = CC.longActive and filter_allowed
if self.legacy_advert_filter:
if guarded_advert:
long_active = long_active and not (CS.out.seatbeltUnlatched or CS.out.gasPressed or CS.out.brakePressed)
actuators = CC.actuators
stopping = actuators.longControlState == LongCtrlState.stopping
@@ -178,7 +181,7 @@ class CarController(CarControllerBase):
if advert_owner:
lat_active = lat_active and CS.out.canValid and not CS.out.canTimeout and CS.out.cruiseState.available and not (
CS.out.steerFaultTemporary or CS.out.steerFaultPermanent)
if self.legacy_advert_filter:
if guarded_advert:
lat_active = lat_active and legacy_advert_source_ready
CS.zss_lateral_active = bool(CC.latActive)
+4 -1
View File
@@ -311,6 +311,8 @@ class CarState(CarStateBase):
if CP.carFingerprint == CAR.TOYOTA_SIENNA_4TH_GEN:
pt_messages.append(("PCM_CRUISE_4", 1))
from opendbc.car.toyota import camera_2aa_filter
camera_twoaa = camera_2aa_filter.configured(CP)
legacy_advert = legacy_advert_filter.configured(CP)
alternate_sdsu_main = (legacy_sdsu.configured(CP) or legacy_advert) and CP.carFingerprint in legacy_sdsu.ALT_MAIN_CARS
if CP.carFingerprint == CAR.LEXUS_IS or alternate_sdsu_main:
@@ -325,7 +327,8 @@ class CarState(CarStateBase):
if pedal.configured(CP):
pt_messages.append(("GAS_SENSOR", 50))
pt_parser = (legacy_advert_filter.LegacyAdvertCANParser if legacy_advert else
pt_parser = (camera_2aa_filter.CameraTwoAACANParser if camera_twoaa else
legacy_advert_filter.LegacyAdvertCANParser if legacy_advert else
legacy_sdsu.AlternateMainSDSUCANParser if alternate_sdsu_main else
LexusIsCANParser if CP.carFingerprint == CAR.LEXUS_IS else pedal.PedalCANParser if pedal.configured(CP) else
advert_filter.AdvertFilterCANParser if advert_filter.configured(CP) else
+2 -1
View File
@@ -202,7 +202,8 @@ class CarInterface(CarInterfaceBase):
from opendbc.car.toyota import advert_filter
advertised_filter = advert_filter.hardware_supported(candidate, fingerprint, car_fw)
if advertised_filter:
advert_filter.apply(ret)
from opendbc.car.toyota import camera_2aa_filter
advert_filter.apply(ret, camera_twoaa=camera_2aa_filter.hardware_supported(candidate, fingerprint, car_fw))
if legacy_advert and candidate == CAR.TOYOTA_RAV4H:
stop_and_go = True
# Toyota EPS accepts lateral commands down to zero. Keep the existing
+17 -10
View File
@@ -110,10 +110,11 @@ static bool toyota_legacy_main_current(void) {
static bool toyota_legacy_advert_ready(void) {
bool ready = true;
if (toyota_legacy_advert()) {
if (toyota_legacy_advert() || toyota_camera_2aa()) {
static const uint32_t timeout[4] = {303031U, 10000000U, 3333334U, 400000U};
const uint32_t now = microsecond_timer_get();
ready = aol_rx_healthy() && toyota_aol_main && toyota_aol_drive && toyota_aol_doors_closed && toyota_aol_eps;
ready = aol_rx_healthy() && toyota_aol_main && toyota_aol_drive && toyota_aol_doors_closed && toyota_aol_eps &&
(!toyota_camera_2aa() || !toyota_camera_2aa_ambiguous);
for (unsigned int i = 0U; i < 4U; i++) {
ready &= toyota_aol_seen[i] && (safety_get_ts_elapsed(now, toyota_aol_ts[i]) <= timeout[i]);
}
@@ -191,11 +192,17 @@ static bool toyota_get_quality_flag_valid(const CANPacket_t *msg) {
}
static void toyota_optional_rx_hook(const CANPacket_t *msg) {
if (toyota_camera_2aa() && ((msg->addr == 0x2FFU) || (msg->addr == 0x201U) ||
((msg->addr == 0x2AAU) && (((msg->bus != 0U) && (msg->bus != 2U)) || (GET_LEN(msg) != 8U))))) {
// A discriminator cannot authorize a contradictory status/pedal receiver.
// Keep the refusal until a new safety configuration, including with AOL off.
toyota_camera_2aa_ambiguous = true;
}
const bool aol_checksum_source = (!toyota_alt_cruise && (msg->addr == 0x1D3U)) || (msg->addr == 0x262U) || (toyota_aol_secoc && (msg->addr == 0x127U));
const unsigned int aol_source_len = ((toyota_aol_rav4h_filter || toyota_alt_cruise || toyota_legacy_sdsu || toyota_legacy_advert()) && (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 || toyota_legacy_advert()) && (msg->bus == 0U)) {
} else if ((toyota_aol_enabled || toyota_legacy_advert() || toyota_camera_2aa()) && (msg->bus == 0U)) {
unsigned int index = 3U;
if (msg->addr == 0x1D3U) {
index = 0U;
@@ -230,7 +237,7 @@ static void toyota_optional_rx_hook(const CANPacket_t *msg) {
static void toyota_rx_hook(const CANPacket_t *msg) {
// Required sources have already passed RX length/checksum validation here.
// Optional ingress only runs for messages outside the required RX catalog.
if (toyota_legacy_advert() && ((msg->addr == 0x262U) || (!toyota_alt_cruise && (msg->addr == 0x1D3U)))) {
if ((toyota_legacy_advert() || toyota_camera_2aa()) && ((msg->addr == 0x262U) || (!toyota_alt_cruise && (msg->addr == 0x1D3U)))) {
toyota_aol_observe(msg);
}
if (toyota_alt_cruise && msg_matches(msg, 0x365U, 0U) && (GET_LEN(msg) == 7U)) {
@@ -616,7 +623,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_legacy_sdsu && !toyota_legacy_advert_param(param);
toyota_radar_diagnostics_allowed = !toyota_stock_longitudinal && !toyota_secoc && (param != 0x1049U) && (param != 0x1449U) && !toyota_eps8_sdsu && !toyota_legacy_sdsu && !toyota_legacy_advert_param(param) && !toyota_camera_2aa_param(param);
toyota_lta = !toyota_legacy_advert_param(param) && GET_FLAG(param, TOYOTA_PARAM_LTA);
toyota_dbc_eps_torque_factor = param & TOYOTA_EPS_FACTOR;
@@ -637,7 +644,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 || toyota_legacy_sdsu || toyota_legacy_advert_param(param)) {
if ((param == 0x1049U) || (param == 0x9049U) || (param == 0x1449U) || toyota_eps8_sdsu || toyota_legacy_sdsu || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param)) {
SET_TX_MSGS(TOYOTA_FILTER_LONG_TX_MSGS_SET, ret);
} else {
SET_TX_MSGS(TOYOTA_LONG_TX_MSGS, ret);
@@ -675,8 +682,8 @@ static safety_config toyota_init(uint16_t param) {
// Restore the sixth row on every warm switch before specializing the new owner.
toyota_rx_checks[5].msg[0] = (CanMsgCheck){.addr = 0x365, .bus = 0U, .len = 7, .frequency = 10U,
.ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true};
if (toyota_legacy_advert_param(param)) {
toyota_rx_checks[4].msg[0] = (CanMsgCheck){.addr = 0x262, .bus = 0U, .len = 5, .frequency = 25U,
if (toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param)) {
toyota_rx_checks[4].msg[0] = (CanMsgCheck){.addr = 0x262, .bus = 0U, .len = toyota_camera_2aa_param(param) ? 8 : 5, .frequency = 25U,
.ignore_counter = true, .ignore_quality_flag = true};
if (!toyota_alt_cruise) {
toyota_rx_checks[5].msg[0] = (CanMsgCheck){.addr = 0x1D3, .bus = 0U, .len = 8, .frequency = 10U,
@@ -685,7 +692,7 @@ static safety_config toyota_init(uint16_t param) {
}
ret.rx_checks = toyota_rx_checks;
ret.disable_forwarding = false;
if ((toyota_legacy_sdsu && toyota_alt_cruise) || toyota_legacy_advert_param(param)) {
if ((toyota_legacy_sdsu && toyota_alt_cruise) || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param)) {
ret.rx_checks_len = 6;
} else if (toyota_pedal || toyota_alt_cruise || toyota_sdsu_status) {
ret.rx_checks_len = 5;
@@ -694,7 +701,7 @@ static safety_config toyota_init(uint16_t param) {
}
}
if (((param & 0xC000U) != 0U) && !toyota_alt_cruise && !toyota_sdsu_status && !toyota_legacy_advert_param(param)) {
if (((param & 0xC000U) != 0U) && !toyota_alt_cruise && !toyota_sdsu_status && !toyota_legacy_advert_param(param) && !toyota_camera_2aa_param(param)) {
ret.tx_msgs_len = 0;
}
if (((param & 0x2000U) != 0U) && !toyota_pedal) {
+17 -5
View File
@@ -1,5 +1,15 @@
#pragma once
static bool toyota_camera_2aa_param(uint16_t param) {
return (param == 0xC049U) || (param == 0xC249U);
}
static bool toyota_camera_2aa(void) {
return toyota_camera_2aa_param(current_safety_param);
}
static bool toyota_camera_2aa_ambiguous = false;
static bool toyota_legacy_advert_param(uint16_t param) {
return (param == 0x9449U) || (param == 0x9549U) || (param == 0x9558U) || (param == 0x9564U) || (param == 0x9649U) || (param == 0x9749U) || (param == 0x9758U) || (param == 0x9764U) || (param == 0xD44DU) || (param == 0xD549U) || (param == 0xD64DU) || (param == 0xD749U);
}
@@ -60,7 +70,8 @@ static uint8_t toyota_aol_permission(void) {
fresh &= toyota_aol_seen[i] && (safety_get_ts_elapsed(microsecond_timer_get(), toyota_aol_ts[i]) <= timeout);
}
uint8_t permission = 0U;
if (fresh && toyota_aol_main && toyota_aol_drive && toyota_aol_doors_closed && toyota_aol_eps) {
if (fresh && toyota_aol_main && toyota_aol_drive && toyota_aol_doors_closed && toyota_aol_eps &&
(!toyota_camera_2aa() || !toyota_camera_2aa_ambiguous)) {
permission = request & 1U;
if (!toyota_aol_stock && controls_allowed && toyota_aol_belt && !gas_pressed && !brake_pressed &&
(!toyota_sdsu_status || toyota_sdsu_control)) {
@@ -80,7 +91,7 @@ static void toyota_aol_observe(const CANPacket_t *msg) {
expected_len = 5U;
} else {
}
if ((toyota_aol_enabled || toyota_legacy_advert()) && (msg->bus == 0U) && (GET_LEN(msg) != expected_len)) {
if ((toyota_aol_enabled || toyota_legacy_advert() || toyota_camera_2aa()) && (msg->bus == 0U) && (GET_LEN(msg) != expected_len)) {
unsigned int index = 4U;
if (msg->addr == main_addr) {
index = 0U;
@@ -95,7 +106,7 @@ static void toyota_aol_observe(const CANPacket_t *msg) {
if (index < 4U) {
toyota_aol_seen[index] = false;
}
} else if ((toyota_aol_enabled || toyota_legacy_advert()) && (msg->bus == 0U) && (GET_LEN(msg) == expected_len)) {
} else if ((toyota_aol_enabled || toyota_legacy_advert() || toyota_camera_2aa()) && (msg->bus == 0U) && (GET_LEN(msg) == expected_len)) {
unsigned int index = 4U;
if (msg->addr == main_addr) {
index = 0U;
@@ -135,6 +146,7 @@ static void toyota_aol_observe(const CANPacket_t *msg) {
static void toyota_aol_configure(uint16_t param) {
toyota_aol_reset();
toyota_camera_2aa_ambiguous = false;
toyota_eps8_sdsu = (param == 0x8049U) || (param == 0x8249U) || (param == 0x8449U) || (param == 0x8649U);
toyota_legacy_sdsu = (param == 0x9158U) || (param == 0x9358U) || (param == 0x9149U) || (param == 0x9349U) || (param == 0x9164U) || (param == 0x9364U) || (param == 0xd149U) || (param == 0xd349U) || (param == 0xd04dU) || (param == 0xd24dU);
toyota_alt_cruise = (param == 0x424DU) || ((toyota_legacy_sdsu || toyota_legacy_advert_param(param)) && ((param & 0x4000U) != 0U));
@@ -142,13 +154,13 @@ static void toyota_aol_configure(uint16_t param) {
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_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 || toyota_legacy_advert_param(param)) && ((param & 0x200U) != 0U));
toyota_aol_stock = (param == 585U) || (param == 4681U) || (param == 0x9249U) || (param == 0x2249U) || (param == 0x424DU) || (param == 0x649U) || (param == 0x1649U) || (param == 0x8249U) || (param == 0x8649U) || ((toyota_legacy_sdsu || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param)) && ((param & 0x200U) != 0U));
#ifdef ALLOW_DEBUG
toyota_aol_secoc = (param == 2121U);
#else
toyota_aol_secoc = false;
#endif
toyota_aol_enabled = ((toyota_eps8_sdsu || toyota_legacy_sdsu || toyota_legacy_advert_param(param)) && (alternative_experience == 32)) || (toyota_aol_lta && (alternative_experience == 32)) || (toyota_aol_stock && (alternative_experience == 32)) ||
toyota_aol_enabled = ((toyota_eps8_sdsu || toyota_legacy_sdsu || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param)) && (alternative_experience == 32)) || (toyota_aol_lta && (alternative_experience == 32)) || (toyota_aol_stock && (alternative_experience == 32)) ||
#ifdef ALLOW_DEBUG
(toyota_aol_secoc && (alternative_experience == 32)) ||
#endif
@@ -179,7 +179,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) || (word == 0x9158U) || (word == 0x9358U) || (word == 0x9149U) || (word == 0x9349U) || (word == 0x9164U) || (word == 0x9364U) || (word == 0xd149U) || (word == 0xd349U) || (word == 0xd04dU) || (word == 0xd24dU) || (word == 0x9449U) || (word == 0x9549U) || (word == 0x9558U) || (word == 0x9564U) || (word == 0x9649U) || (word == 0x9749U) || (word == 0x9758U) || (word == 0x9764U) || (word == 0xD44DU) || (word == 0xD549U) || (word == 0xD64DU) || (word == 0xD749U);
const bool expected = (word == 0xC049U) || (word == 0xC249U) || (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) || (word == 0x9449U) || (word == 0x9549U) || (word == 0x9558U) || (word == 0x9564U) || (word == 0x9649U) || (word == 0x9749U) || (word == 0x9758U) || (word == 0x9764U) || (word == 0xD44DU) || (word == 0xD549U) || (word == 0xD64DU) || (word == 0xD749U);
auto toyota = status();
toyota.safety_mode = 2U;
toyota.safety_param = static_cast<uint16_t>(word);
+1
View File
@@ -6,6 +6,7 @@ from openpilot.starpilot.aol.policy import AolVehiclePolicy
from openpilot.starpilot.aol.intent import AolCardIntent, AOL_TOGGLE
AOL_WORDS = frozenset((
0xC049, 0xC249,
0x9449, 0x9549, 0x9558, 0x9564, 0x9649, 0x9749, 0x9758, 0x9764, 0xD44D, 0xD549, 0xD64D, 0xD749,
73, 585, 4169, 4681, 2121, 0x9049, 0x9249,
0x2049, 0x2249, 0x424D, 0x449, 0x649, 0x1449, 0x1649,
+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) || (param == 0x9158U) || (param == 0x9358U) || (param == 0x9149U) || (param == 0x9349U) || (param == 0x9164U) || (param == 0x9364U) || (param == 0xd149U) || (param == 0xd349U) || (param == 0xd04dU) || (param == 0xd24dU) || (param == 0x9449U) || (param == 0x9549U) || (param == 0x9558U) || (param == 0x9564U) || (param == 0x9649U) || (param == 0x9749U) || (param == 0x9758U) || (param == 0x9764U) || (param == 0xD44DU) || (param == 0xD549U) || (param == 0xD64DU) || (param == 0xD749U);
return (param == 0xC049U) || (param == 0xC249U) || (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) || (param == 0x9449U) || (param == 0x9549U) || (param == 0x9558U) || (param == 0x9564U) || (param == 0x9649U) || (param == 0x9749U) || (param == 0x9758U) || (param == 0x9764U) || (param == 0xD44DU) || (param == 0xD549U) || (param == 0xD64DU) || (param == 0xD749U);
}
inline constexpr AolSafetyProfile TOYOTA_AOL_PROFILE{2U, toyota_aol_param, true};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,207 @@
"""Known camera2AA configurations through the actual serial caller/native chain."""
from contextlib import nullcontext
from dataclasses import replace
from unittest.mock import patch
import pytest
from openpilot.starpilot.aol.wire import decode_safety, encode_safety
from opendbc.car import Bus, structs
from opendbc.car.toyota import camera_2aa_filter
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, fault=None, mirror=False):
tick = 0
def physical(packer, **fields):
nonlocal tick
tick += 1
if scenario == 'physical':
if 410 <= tick < 415:
fields['gear'] = 32
if 475 <= tick < 480:
fields.update(main=False, cruise=False)
if scenario == 'identity_refusal':
fields.update(cruise=tick >= 150 and not 330 <= tick < 360, main=not 340 <= tick < 350)
if fault in ('eps-temporary', 'eps-permanent') and 260 <= tick < 311:
fields['eps'] = 9 if fault == 'eps-temporary' else 3
if fault == 'configured-brake' and 260 <= tick < 311:
fields.update(brake=True, cruise=False, stopped=False)
frames = [f for f in helpers.physical(packer, **fields) if f[0] not in (0x260, 0x262, 0x2FF, 0x283, 0x412)]
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('LKAS_HUD', 2, {'LDA_ON_MESSAGE': 1 if 220 <= tick < 225 else 0}),
packer.make_can_msg('PCS_HUD', 2, {}),
packer.make_can_msg('PRE_COLLISION', 2, {}),
(0x2AA, bytes.fromhex('8000700000000000' if 220 <= tick < 240 else '0000000000000000'), 0),
]
if mirror:
frames.append((0x2AA, bytes(8), 2))
if scenario == 'physical' and 470 <= tick < 475:
frames = [f for f in frames if f[0] != 0x620]
frames.append(packer.make_can_msg('BODY_CONTROL_STATE', 0, {'DOOR_OPEN_FL': 1}))
if fault == 'aeb' and 260 <= tick < 311:
frames = [frame for frame in frames if frame[0] != 0x283]
aeb = packer.make_can_msg('PRE_COLLISION', 2, {'PRECOLLISION_ACTIVE': 1, 'FORCE': -20})
assert int.from_bytes(aeb[1][2:4], 'big', signed=True) < 0 and aeb[1][5] & 2
frames.append(aeb)
if fault is not None and fault.startswith('identity-') and tick == 260:
markers = {
'identity-status': (0x2FF, bytes.fromhex('0000700000000000'), 0),
'identity-advert': (0x2FF, bytes.fromhex('80000409'), 0),
'identity-pedal': (0x201, bytes(6), 0),
'identity-dlc': (0x2AA, bytes(7), 0),
'identity-bus': (0x2AA, bytes(8), 1),
}
frames.append(markers[fault])
if fault is not None and fault.startswith('identity-') and tick == 275:
# A genuine invalid-source reset must not erase contradictory identity.
frames = [frame for frame in frames if frame[0] != 0x262]
a, data, bus = packer.make_can_msg('EPS_STATUS', 0, {'LKA_STATE': 1, 'LTA_STATE': 1})
frames.append((a, data[:-1] + bytes([data[-1] ^ 1]), bus))
if fault is not None and fault.split('-')[0] in ('main', 'eps') and fault not in ('eps-temporary', 'eps-permanent') and 260 <= tick < 311:
source, kind = fault.split('-')
address = 0x1D3 if source == 'main' else 0x262
kept = []
for a, data, bus in frames:
if a == address and bus == 0:
if kind == 'drop':
continue
if kind == 'length':
data = data[:-1]
elif kind == 'checksum':
data = data[:-1] + bytes([data[-1] ^ 1])
else:
assert kind == 'bus'
bus = 2
kept.append((a, data, bus))
frames = kept
return frames
def parsed_state(tick, host, cs):
if fault == 'aeb' and 260 <= tick < 311:
assert cs.stockAeb
assert host.CI.can_parsers[Bus.cam].vl['PRE_COLLISION']['FORCE'] < 0
def observe(tick, host, cs, cc, safety, native):
del native
parser = host.CI.can_parsers[Bus.pt]
assert isinstance(parser, camera_2aa_filter.CameraTwoAACANParser)
assert host.CP.safetyConfigs[0].safetyParam == (camera_2aa_filter.STOCK_WORD if stock else camera_2aa_filter.LONG_WORD)
assert 0x2FF not in parser.message_states and 0x2AA not in parser.message_states
assert parser.advert_distance_button == 0 and host.CI.CS.distance_button == 0
assert not any(e.type == structs.CarState.ButtonEvent.Type.gapAdjustCruise for e in cs.buttonEvents)
assert safety.safety_fwd_hook(2, 0x343) == 0 and safety.safety_fwd_hook(2, 0x4CB) == 0
if tick == 220:
assert any(e.type == structs.CarState.ButtonEvent.Type.lkas and e.pressed for e in cs.buttonEvents)
if scenario == 'physical' and (410 <= tick < 430 or 470 <= tick < 475):
assert host.CI.CC.last_torque == 0
if not stock:
assert host.CI.CC.standstill_req
if fault is not None and 301 <= tick < 308:
assert not cc.latActive and not cc.longActive and safety.aol_get_permission_mask() == 0
if fault == 'aeb':
assert cs.stockAeb and not cs.accFaulted
if scenario == 'identity_refusal' and tick >= 270:
assert parser.hardware_ambiguous and not parser.main_valid and not parser.eps_valid
assert not cc.latActive and not cc.longActive and safety.aol_get_permission_mask() == 0
if tick >= 390:
assert safety.safety_config_valid(), 'all real required sources recovered, but identity must stay refused'
def rx_expected(address, data, bus, default, cp):
if fault is not None and fault.startswith('identity-') and tick == 275 and address == 0x262 and bus == 0:
from opendbc.car.toyota.toyotacan import toyota_checksum
assert camera_2aa_filter.configured(cp) and len(data) == 8 and data[-1] != toyota_checksum(address, None, bytearray(data))
return False
if fault == 'eps-checksum' and address == 0x262 and bus == 0 and 260 <= tick < 311:
assert camera_2aa_filter.configured(cp) and len(data) == 8
return False
if fault == 'main-checksum' and address == 0x1D3 and bus == 0 and 260 <= tick < 311:
assert camera_2aa_filter.configured(cp) and len(data) == 8
return False
return default
original_native = helpers.native_events
def native_events(*args, **kwargs):
messages = original_native(*args, **kwargs)
if fault in ('native-identity', 'native-session') and 260 <= tick < 311:
original = next(event for event in messages if event.which() == 'aolSafetyWire')
reader = original.as_reader() if hasattr(original, 'as_reader') else original
wire = reader.as_builder()
state = decode_safety(bytes(wire.aolSafetyWire))
assert state is not None
# USB producer boundary: change identity/session only, preserving all actual native bits.
wrong = replace(state, safetyParam=state.safetyParam ^ 1) if fault == 'native-identity' else replace(state, axisSessionId='f' * 32)
wire.aolSafetyWire = encode_safety(wrong)
messages = [wire.as_reader() if event.which() == 'aolSafetyWire' else event for event in messages]
return messages
advertisements = [(0x2AA, bytes(8), 0)] + ([(0x2AA, bytes(8), 2)] if mirror else [])
context = patch.object(helpers, 'native_events', native_events) if fault in ('native-identity', 'native-session') else nullcontext()
with context:
helpers.run_case(
stock,
scenario,
candidate=candidate,
bridge=bridge,
input_clock=True,
aol=aol,
sdsu_words=(camera_2aa_filter.LONG_WORD, camera_2aa_filter.STOCK_WORD),
physical_frames=physical,
fingerprint_frames=advertisements,
on_sample=observe,
native_rx_expected=rx_expected,
brake_stop_check_start=355,
brake_pause_mps=25.0 if fault == 'configured-brake' else None,
on_state=parsed_state,
)
@pytest.mark.parametrize('candidate', sorted(camera_2aa_filter.CARS))
@pytest.mark.parametrize('stock', (False, True))
def test_each_camera_twoaa_factory_caller(candidate, stock, host_heartbeat_bridge):
run(candidate, stock, host_heartbeat_bridge)
@pytest.mark.parametrize('candidate', (CAR.TOYOTA_COROLLA_TSS2, CAR.LEXUS_LC_TSS2))
@pytest.mark.parametrize('stock', (False, True))
@pytest.mark.parametrize('scenario', ('physical', 'off', 'cold_low'))
def test_camera_twoaa_physical_off_cold(candidate, stock, scenario, host_heartbeat_bridge):
run(candidate, stock, host_heartbeat_bridge, scenario)
@pytest.mark.parametrize('candidate', (CAR.TOYOTA_COROLLA_TSS2, CAR.LEXUS_LC_TSS2))
@pytest.mark.parametrize('fault', ('main-drop', 'main-length', 'main-checksum', 'main-bus', 'eps-drop', 'eps-length', 'eps-checksum', 'eps-bus'))
def test_camera_twoaa_source_refusals(candidate, fault, host_heartbeat_bridge):
run(candidate, False, host_heartbeat_bridge, 'main_lease', fault=fault)
@pytest.mark.parametrize('candidate', (CAR.TOYOTA_COROLLA_TSS2, CAR.LEXUS_LC_TSS2))
def test_camera_twoaa_ordinary_and_forwarded(candidate, host_heartbeat_bridge):
run(candidate, False, host_heartbeat_bridge, aol=False, mirror=True)
@pytest.mark.parametrize('candidate', (CAR.TOYOTA_COROLLA_TSS2, CAR.LEXUS_LC_TSS2))
@pytest.mark.parametrize('stock', (False, True))
def test_camera_twoaa_actual_camera_aeb(candidate, stock, host_heartbeat_bridge):
run(candidate, stock, host_heartbeat_bridge, 'main_lease', fault='aeb')
@pytest.mark.parametrize('candidate', (CAR.TOYOTA_COROLLA_TSS2, CAR.LEXUS_LC_TSS2))
@pytest.mark.parametrize('fault', ('eps-temporary', 'eps-permanent', 'native-identity', 'native-session', 'configured-brake'))
def test_camera_twoaa_native_session_eps_and_brake(candidate, fault, host_heartbeat_bridge):
run(candidate, False, host_heartbeat_bridge, 'main_lease', fault=fault)
@pytest.mark.parametrize('stock', (False, True))
@pytest.mark.parametrize('fault', ('identity-status', 'identity-advert', 'identity-pedal', 'identity-dlc', 'identity-bus'))
def test_camera_twoaa_identity_survives_source_reset_and_physical_cycle(stock, fault, host_heartbeat_bridge):
run(CAR.TOYOTA_COROLLA_TSS2, stock, host_heartbeat_bridge, 'identity_refusal', fault=fault)
@@ -0,0 +1,91 @@
"""Factory-owned camera filters preserve full existing CP/tune fields."""
import json
from copy import deepcopy
from pathlib import Path
import pytest
from opendbc.car import gen_empty_fingerprint
from opendbc.car.toyota import camera_2aa_filter, advert_filter
from opendbc.car.toyota.interface import CarInterface
from openpilot.starpilot.car.toyota.tests.test_rav4h_caller_native import firmware
BASE = json.loads((Path(__file__).parent / 'data/camera-2aa-factory-before.json').read_text())
def factory(candidate, *, mirror=False):
fp = gen_empty_fingerprint()
fp[0][0x2AA] = 8
if mirror:
fp[2][0x2AA] = 8
return CarInterface.get_params(candidate, fp, firmware(candidate), False, False, False)
@pytest.mark.parametrize('candidate', sorted(camera_2aa_filter.CARS))
def test_complete_factory_cp_tunes_and_stock(candidate):
cp = factory(candidate)
assert camera_2aa_filter.configured(cp) and advert_filter.configured(cp)
data = cp.to_dict()
data['safetyConfigs'][0]['safetyParam'] = advert_filter.LONG_WORD
assert data == BASE[candidate.name]['2FF4']
original = cp.to_dict()
assert advert_filter.prepare_stock(cp) and camera_2aa_filter.configured(cp)
expected = deepcopy(original)
expected['openpilotLongitudinalControl'] = False
expected['safetyConfigs'][0]['safetyParam'] = camera_2aa_filter.STOCK_WORD
expected['autoResumeSng'] = False
assert cp.to_dict() == expected
assert factory(candidate, mirror=True).to_dict() == original
@pytest.mark.parametrize('candidate', sorted(camera_2aa_filter.CARS))
@pytest.mark.parametrize('size', (4, 8))
def test_previous_filter_factory_cp_exact(candidate, size):
fp = gen_empty_fingerprint()
fp[0][0x2FF] = size
assert CarInterface.get_params(candidate, fp, firmware(candidate), False, False, False).to_dict() == BASE[candidate.name]['2FF' + str(size)]
@pytest.mark.parametrize('candidate', sorted(camera_2aa_filter.CARS))
@pytest.mark.parametrize(
'bad', ('missing', 'copy_only', 'primary4', 'primary7', 'copy7', 'bus1', 'pedal', 'status8', 'advert4', 'camera343', 'camera4cb', 'firmware')
)
def test_twoaa_identity_topology_rejects_unqualified_owner(candidate, bad):
fp = gen_empty_fingerprint()
fp[0][0x2AA] = 8
fw = firmware(candidate)
if bad == 'missing':
del fp[0][0x2AA]
elif bad == 'copy_only':
del fp[0][0x2AA]
fp[2][0x2AA] = 8
elif bad.startswith('primary'):
fp[0][0x2AA] = int(bad[-1])
elif bad == 'copy7':
fp[2][0x2AA] = 7
elif bad == 'bus1':
fp[1][0x2AA] = 8
elif bad == 'pedal':
fp[0][0x201] = 6
elif bad in ('status8', 'advert4'):
fp[0][0x2FF] = 8 if bad == 'status8' else 4
elif bad.startswith('camera'):
fp[2][0x343 if bad == 'camera343' else 0x4CB] = 7
else:
fw = []
assert not camera_2aa_filter.hardware_supported(candidate, fp, fw)
assert not camera_2aa_filter.configured(CarInterface.get_params(candidate, fp, fw, False, False, False))
@pytest.mark.parametrize('candidate', sorted(camera_2aa_filter.CARS))
@pytest.mark.parametrize('stock', (False, True))
def test_twoaa_wheel_scope_uses_only_actual_lkas_receiver(candidate, stock):
from openpilot.starpilot.controllers.wheel_actions import button_reported, KEYS
from openpilot.starpilot.car.toyota.aol import policy_for
cp = factory(candidate)
if stock:
assert camera_2aa_filter.prepare_stock(cp)
assert button_reported(cp, 'LKASButtonControl')
assert not any(button_reported(cp, key) for key in KEYS[2:8])
assert not button_reported(cp, 'MainCruiseButtonControl')
assert policy_for(cp).fixed_cruise_buttons and policy_for(cp).auxiliary_lkas_supported
@@ -0,0 +1,205 @@
"""Native controls/permissions are earned from actual decoded physical CAN."""
from typing import cast
import pytest
from opendbc.can import CANPacker
from opendbc.car import Bus
from opendbc.car.toyota import camera_2aa_filter
from opendbc.car.toyota.values import CAR, DBC
from opendbc.safety.tests.libsafety import libsafety_py
from openpilot.starpilot.car.toyota.tests import test_rav4h_caller_native as helpers
from openpilot.starpilot.car.toyota.tests.test_camera_2aa_filter import factory
source_matched_native = helpers.source_matched_native
@pytest.mark.parametrize('stock', (False, True))
@pytest.mark.parametrize('experience', (0, 32))
def test_native_camera_sources_limits_and_identity(stock, experience):
cp = factory(CAR.TOYOTA_COROLLA_TSS2)
if stock:
assert camera_2aa_filter.prepare_stock(cp)
safety = libsafety_py.libsafety
safety.init_tests()
safety.set_alternative_experience(experience)
assert safety.set_safety_hooks(cp.safetyConfigs[0].safetyModel.raw, cp.safetyConfigs[0].safetyParam) == 0
safety.set_aol_test_heartbeat(True)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
now = 1_000_000
def tx(name, values):
a, data, bus = packer.make_can_msg(name, 0, values)
return bool(safety.safety_tx_hook(libsafety_py.make_CANPacket(a, bus, data)))
def tick(cruise=True, missing=None):
nonlocal now
now += 10_000
safety.set_timer(now)
frames = [f for f in helpers.physical(packer, cruise=cruise) if f[0] != 0x2FF]
frames.append((0x2AA, bytes(8), 0))
for a, data, bus in frames:
if a != missing:
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(a, bus, data))
safety.safety_tick()
safety.aol_set_host_request(3)
assert not tx('STEERING_LKA', {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3})
for _ in range(25):
tick(False)
for _ in range(25):
tick()
assert safety.get_controls_allowed() and safety.safety_config_valid()
assert safety.aol_get_permission_mask() == ((1 if stock else 3) if experience else 0)
assert tx('STEERING_LKA', {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3})
assert tx('ACC_CONTROL', {'ACCEL_CMD': 0.1}) == (not stock)
for accel in (-3.501, 2.001):
assert not tx('ACC_CONTROL', {'ACCEL_CMD': accel})
for values in ({'STEER_REQUEST': 1}, {'STEER_REQUEST_2': 1}, {'STEER_ANGLE_CMD': 0.1}, {'TORQUE_WIND_DOWN': 100}):
assert not tx('STEERING_LTA', values)
for data in ('0f023e0000000000', '0f02280100000000'):
assert not safety.safety_tx_hook(libsafety_py.make_CANPacket(0x750, 0, bytes.fromhex(data)))
for source in (0x1D3, 0x262):
for _ in range(45):
tick(missing=source)
assert not tx('STEERING_LKA', {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3})
assert not tx('ACC_CONTROL', {'ACCEL_CMD': 0.1})
assert not tx('ACC_CONTROL', {'ACCEL_CMD': 0, 'RELEASE_STANDSTILL': 1})
assert tx('ACC_CONTROL', {'ACCEL_CMD': 0, 'CANCEL_REQ': 1})
for _ in range(25):
tick(False)
for _ in range(25):
tick()
assert tx('ACC_CONTROL', {'ACCEL_CMD': 0.1}) == (not stock)
# A status/pedal contradiction is refused even if every independent source stays fresh.
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x2FF, 0, bytes(8)))
a, data, bus = packer.make_can_msg('EPS_STATUS', 0, {'LKA_STATE': 1})
invalid = data[:-1] + bytes([data[-1] ^ 1])
assert not safety.safety_rx_hook(libsafety_py.make_CANPacket(a, bus, invalid))
for _ in range(25):
tick(False)
for _ in range(25):
tick()
assert safety.aol_get_permission_mask() == 0
assert not tx('STEERING_LKA', {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3}) and not tx('ACC_CONTROL', {'ACCEL_CMD': 0.1})
assert tx('ACC_CONTROL', {'ACCEL_CMD': 0, 'CANCEL_REQ': 1})
safety.set_safety_hooks(cp.safetyConfigs[0].safetyModel.raw, cp.safetyConfigs[0].safetyParam)
assert not tx('STEERING_LKA', {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3})
for _ in range(25):
tick(False)
for _ in range(25):
tick()
assert tx('STEERING_LKA', {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3})
safety.set_alternative_experience(0)
@pytest.mark.parametrize('stock', (False, True))
@pytest.mark.parametrize('experience', (0, 32))
@pytest.mark.parametrize('identity', ('pedal', 'bad-dlc', 'wrong-bus', 'advert'))
def test_native_identity_refusal_independent_of_aol(stock, experience, identity):
cp = factory(CAR.TOYOTA_COROLLA_TSS2)
if stock:
assert camera_2aa_filter.prepare_stock(cp)
safety = libsafety_py.libsafety
safety.init_tests()
safety.set_alternative_experience(experience)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
now = 1_000_000
def tx(name, values):
a, data, bus = packer.make_can_msg(name, 0, values)
return bool(safety.safety_tx_hook(libsafety_py.make_CANPacket(a, bus, data)))
def tick(cruise):
nonlocal now
now += 10_000
safety.set_timer(now)
for a, data, bus in helpers.physical(packer, cruise=cruise):
if a != 0x2FF:
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(a, bus, data))
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x2AA, 0, bytes(8)))
safety.safety_tick()
safety.aol_set_host_request(3)
def configure_and_earn():
assert safety.set_safety_hooks(cp.safetyConfigs[0].safetyModel.raw, cp.safetyConfigs[0].safetyParam) == 0
safety.set_aol_test_heartbeat(True)
assert not tx('STEERING_LKA', {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3})
for _ in range(25):
tick(False)
for _ in range(25):
tick(True)
assert safety.get_controls_allowed() and safety.safety_config_valid()
assert safety.aol_get_permission_mask() == ((1 if stock else 3) if experience else 0)
assert safety.safety_fwd_hook(2, 0x343) == 0
assert tx('STEERING_LKA', {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3})
assert tx('ACC_CONTROL', {'ACCEL_CMD': 0.1}) == (not stock)
configure_and_earn()
address, bus, data = {'pedal': (0x201, 0, bytes(6)), 'bad-dlc': (0x2AA, 0, bytes(7)),
'wrong-bus': (0x2AA, 1, bytes(8)), 'advert': (0x2FF, 0, bytes(4))}[identity]
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, data))
for cruise in (False, True):
for _ in range(25):
tick(cruise)
# Fresh valid required sources and a real cruise cycle cannot clear identity refusal.
assert safety.safety_config_valid()
assert safety.aol_get_permission_mask() == 0
assert not tx('STEERING_LKA', {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3})
assert not tx('ACC_CONTROL', {'ACCEL_CMD': 0.1})
assert tx('ACC_CONTROL', {'ACCEL_CMD': 0, 'CANCEL_REQ': 1})
configure_and_earn()
safety.set_alternative_experience(0)
@pytest.mark.parametrize('stock', (False, True))
@pytest.mark.parametrize('experience', (0, 32))
def test_native_optional_wrong_bus_source_does_not_replace_required_source(stock, experience):
cp = factory(CAR.TOYOTA_COROLLA_TSS2)
if stock:
assert camera_2aa_filter.prepare_stock(cp)
safety = libsafety_py.libsafety
safety.init_tests()
safety.set_alternative_experience(experience)
assert safety.set_safety_hooks(cp.safetyConfigs[0].safetyModel.raw, cp.safetyConfigs[0].safetyParam) == 0
safety.set_aol_test_heartbeat(True)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
now = 1_000_000
def tick(cruise):
nonlocal now
now += 10_000
safety.set_timer(now)
for address, data, bus in helpers.physical(packer, cruise=cruise):
if address != 0x2FF:
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, data))
safety.safety_tick()
safety.aol_set_host_request(3)
def positive():
address, data, bus = packer.make_can_msg('STEERING_LKA', 0, {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 3})
return bool(safety.safety_tx_hook(libsafety_py.make_CANPacket(address, bus, data)))
for _ in range(25):
tick(False)
for _ in range(25):
tick(True)
assert positive()
for name, values in (('PCM_CRUISE_2', {'MAIN_ON': 1}), ('EPS_STATUS', {'LKA_STATE': 1})):
address, data, bus = packer.make_can_msg(name, 0, cast(dict[str, float], values))
assert address in (0x1D3, 0x262) and len(data) == 8 and bus == 0
invalid = data[:-1] + bytes([data[-1] ^ 1])
# Optional bus2 packets have no source authority; bad CRC cannot replace bus0.
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(address, 2, invalid))
assert safety.safety_config_valid() and safety.get_controls_allowed() and positive()
assert safety.aol_get_permission_mask() == ((1 if stock else 3) if experience else 0)
# The same corrupt packet on required bus0 is rejected and denies output.
assert not safety.safety_rx_hook(libsafety_py.make_CANPacket(address, 0, invalid))
safety.safety_tick()
assert not safety.get_controls_allowed()
assert not positive() and safety.aol_get_permission_mask() == 0
for _ in range(25):
tick(False)
for _ in range(25):
tick(True)
assert positive()
safety.set_alternative_experience(0)
@@ -227,7 +227,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, on_sample=None, camera_messages=False, filter_advert=False,
brake_pause_mps=None, native_rx_expected=None, on_state=None):
brake_pause_mps=None, native_rx_expected=None, on_state=None, fingerprint_frames=None):
SYNTHETIC_LAST.clear()
NATIVE_LAST.clear()
safety = libsafety_py.libsafety
@@ -249,7 +249,11 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H,
exact, found = match_fw_to_car(fw, '', log=False)
assert exact and found == {candidate}
fp = gen_empty_fingerprint()
fp[0][0x2FF] = 4 if filter_advert else 8
if fingerprint_frames is None:
fp[0][0x2FF] = 4 if filter_advert else 8
else:
for address, data, bus in fingerprint_frames:
fp[bus][address] = len(data)
if sdsu_mirror:
fp[2][0x2FF] = 8
if camera_messages:
@@ -319,7 +323,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H,
try:
duration = {
'nominal': 251, 'physical': 601, 'faults': 801, 'bad_dlc': 801,
'off': 401, 'cold_low': 251, 'hybrid_stop': 251, 'main_lease': 501,
'off': 401, 'cold_low': 251, 'hybrid_stop': 251, 'main_lease': 501, 'identity_refusal': 501,
}[scenario]
for tick in range(1, duration):
if deterministic:
@@ -465,6 +469,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 == 'identity_refusal' and tick < 250
or scenario == 'main_lease' and (tick < 250 or tick >= 480)
or scenario == 'off'
and tick < 250
@@ -480,6 +485,8 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H,
assert cc.latActive, trace[-1]
assert cc.longActive == (not stock and scenario != 'cold_low'), trace[-1]
seen_active = True
if scenario == 'identity_refusal' and tick >= 270:
assert not cc.latActive and not cc.longActive and safety.aol_get_permission_mask() == 0, trace[-1]
if scenario == 'physical':
if 340 <= tick < 366:
assert not cc.longActive, trace[-1]
@@ -503,7 +510,8 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_RAV4H,
assert not host.CP.openpilotLongitudinalControl and not cc.longActive
assert host.CP.safetyConfigs[0].safetyParam == sdsu_words[1]
else:
preference = host.CI.CC.advert_filter_input if filter_advert else host.CI.CC.rav4h_filter_input
preference = host.CI.CC.advert_filter_input if filter_advert or fingerprint_frames is not None and any(
address == 0x2AA for address, _, _ in fingerprint_frames) else host.CI.CC.rav4h_filter_input
assert preference is not None and not preference.update(time.monotonic_ns())
for f in outgoing.sendcan:
if f.address == 0x343:
@@ -47,11 +47,13 @@ def button_reported(cp, key):
if brand in ('body', 'byd', 'mg', 'mock', 'psa', 'rivian', 'subaru'):
return False
if brand == 'toyota':
from opendbc.car.toyota import legacy_advert_filter
from opendbc.car.toyota import legacy_advert_filter, camera_2aa_filter
from opendbc.car.toyota.values import CAR, ToyotaFlags
legacy_advert = bool(cp.flags & ToyotaFlags.RADAR_CAN_FILTER) and legacy_advert_filter.configured(cp)
if key == 'MainCruiseButtonControl' or key in KEYS[5:8]:
return False
if key in KEYS[2:5] and bool(cp.flags & ToyotaFlags.RADAR_CAN_FILTER) and camera_2aa_filter.configured(cp):
return False # No verified FD_BUTTON receiver is implied by a 2AA discriminator.
if key == 'LKASButtonControl':
return bool(cp.flags & (ToyotaFlags.TSS2 | ToyotaFlags.LONG_FILTER) or
cp.carFingerprint == CAR.TOYOTA_PRIUS_RETROFIT or legacy_advert)
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "a4d381b8657230b38fe1a23ee4fc01717dc84be82e6f136daa88f24e88d851e5", "schema_version": 1}
{"protected_inventory_sha256": "183f21feacffb472c6da8ed2d174065b42792bbaa281a60ead4aca32f4c8ed14", "schema_version": 1}
+3
View File
@@ -89,6 +89,9 @@ AOL_LIFECYCLE_TARGETS = (
"openpilot/starpilot/car/toyota/tests/test_lexus_is_caller_native.py",
"openpilot/starpilot/car/toyota/tests/test_legacy_stop_timer.py",
"openpilot/starpilot/car/toyota/tests/test_legacy_stop_timer_callers.py",
"openpilot/starpilot/car/toyota/tests/test_camera_2aa_filter.py",
"openpilot/starpilot/car/toyota/tests/test_camera_2aa_callers.py",
"openpilot/starpilot/car/toyota/tests/test_camera_2aa_native.py",
"openpilot/starpilot/car/toyota/tests/test_legacy_advert_filter.py",
"openpilot/starpilot/car/toyota/tests/test_legacy_advert_callers.py",
"openpilot/starpilot/car/toyota/tests/test_legacy_advert_native.py",