mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 17:13:54 +08:00
Support camera Toyota radar filter configurations
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "a4d381b8657230b38fe1a23ee4fc01717dc84be82e6f136daa88f24e88d851e5", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "183f21feacffb472c6da8ed2d174065b42792bbaa281a60ead4aca32f4c8ed14", "schema_version": 1}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user