Support SecOC Yaris AOL with stock and alpha longitudinal

This commit is contained in:
firestar5683
2026-10-26 15:33:22 -05:00
parent ba87dd4c70
commit e672bd30c1
8 changed files with 166 additions and 55 deletions
@@ -159,6 +159,9 @@ class CarInterface(CarInterfaceBase):
ret.openpilotLongitudinalControl = ((bool(ret.flags & ToyotaFlags.TSS2) and not (ret.flags & ToyotaFlags.RADAR_ACC)) or
bool(ret.flags & ToyotaFlags.DISABLE_RADAR.value) or (radar_filter and alpha_long))
if candidate == CAR.TOYOTA_YARIS and ret.flags & ToyotaFlags.DISABLE_RADAR:
ret.safetyConfigs[0].safetyParam |= int(ToyotaSafetyFlags.SECOC_RADAR_LONG)
from opendbc.car.toyota import matrix_filter
manual_matrix_filter = any(matrix_filter.hardware_supported(candidate, fingerprint, car_fw, length=size) for size in (4, 8))
if candidate in (CAR.TOYOTA_MATRIX_RETROFIT, CAR.TOYOTA_PRIUS_RETROFIT):
@@ -57,6 +57,7 @@ class ToyotaSafetyFlags(IntFlag):
LTA = (4 << 8)
SECOC = (8 << 8)
LONG_FILTER = (16 << 8)
SECOC_RADAR_LONG = SECOC | LONG_FILTER # Exact radar-disable SecOC profile; separates its keepalive permission.
GAS_INTERCEPTOR = (32 << 8)
ALT_CRUISE = (64 << 8) # Exact Lexus IS DSU MAIN profile
RAV4H_FILTER_PROFILE = (128 << 8) # Exact smartDSU owner; bit14 remains reserved for ALT_CRUISE
+2 -2
View File
@@ -681,14 +681,14 @@ static safety_config toyota_init(uint16_t param) {
const uint32_t TOYOTA_PARAM_STOCK_LONGITUDINAL = 2UL << TOYOTA_PARAM_OFFSET;
const uint32_t TOYOTA_PARAM_LTA = 4UL << TOYOTA_PARAM_OFFSET;
#ifdef ALLOW_DEBUG
const uint32_t TOYOTA_PARAM_SECOC = 8UL << TOYOTA_PARAM_OFFSET;
#ifdef ALLOW_DEBUG
toyota_secoc = GET_FLAG(base_param, TOYOTA_PARAM_SECOC);
#endif
toyota_alt_brake = GET_FLAG(base_param, TOYOTA_PARAM_ALT_BRAKE);
toyota_stock_longitudinal = GET_FLAG(base_param, TOYOTA_PARAM_STOCK_LONGITUDINAL);
toyota_radar_diagnostics_allowed = !toyota_dsu_bypass && !toyota_stock_longitudinal && !toyota_secoc && (base_param != 0x1049U) && (base_param != 0x1449U) && !toyota_eps8_sdsu && !toyota_legacy_sdsu && !toyota_legacy_advert_param(base_param) && !toyota_camera_2aa_param(base_param) && !toyota_prius_filter_param(base_param);
toyota_radar_diagnostics_allowed = !toyota_dsu_bypass && !toyota_stock_longitudinal && (!GET_FLAG(base_param, TOYOTA_PARAM_SECOC) || (toyota_secoc && (base_param == 6217U))) && (base_param != 0x1049U) && (base_param != 0x1449U) && !toyota_eps8_sdsu && !toyota_legacy_sdsu && !toyota_legacy_advert_param(base_param) && !toyota_camera_2aa_param(base_param) && !toyota_prius_filter_param(base_param);
toyota_lta = !toyota_legacy_advert_param(base_param) && GET_FLAG(base_param, TOYOTA_PARAM_LTA);
toyota_dbc_eps_torque_factor = base_param & TOYOTA_EPS_FACTOR;
@@ -217,13 +217,13 @@ static void toyota_aol_configure(uint16_t param) {
toyota_pedal = (param == 0x2049U) || (param == 0x2249U) || (param == 0x2449U) || (param == 0x2649U);
toyota_aol_rav4h_filter = (param == 0x9049U) || (param == 0x9249U);
toyota_sdsu_status = toyota_aol_rav4h_filter || toyota_eps8_sdsu || toyota_legacy_sdsu || toyota_prius_sdsu_param(param);
toyota_aol_stock = (toyota_dsu_bypass && ((param & 0x200U) != 0U)) || (param == 0x2649U) || (param == 585U) || (param == 856U) || (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) || toyota_prius_filter_param(param)) && ((param & 0x200U) != 0U));
#ifdef ALLOW_DEBUG
toyota_aol_secoc = (param == 2121U);
toyota_aol_secoc = (param == 2121U) || (param == 2633U) || (param == 6217U);
#else
toyota_aol_secoc = false;
#endif
toyota_aol_enabled = (toyota_dsu_bypass && aol_requested) || ((toyota_eps8_sdsu || toyota_legacy_sdsu || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param) || toyota_prius_filter_param(param)) && aol_requested) || (toyota_aol_lta && aol_requested) || (toyota_aol_stock && aol_requested) ||
toyota_aol_stock = (toyota_dsu_bypass && ((param & 0x200U) != 0U)) || (param == 0x2649U) || (param == 585U) || (param == 856U) || (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) || toyota_prius_filter_param(param)) && ((param & 0x200U) != 0U)) || (toyota_aol_secoc && (param == 2633U));
toyota_aol_enabled = (toyota_dsu_bypass && aol_requested) || ((toyota_eps8_sdsu || toyota_legacy_sdsu || toyota_legacy_advert_param(param) || toyota_camera_2aa_param(param) || toyota_prius_filter_param(param)) && aol_requested) || (toyota_aol_lta && aol_requested) || (toyota_aol_stock && (!toyota_aol_secoc || (alternative_experience == 32)) && aol_requested) ||
#ifdef ALLOW_DEBUG
(toyota_aol_secoc && (alternative_experience == 32)) ||
#endif
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Always On Lateral on SecOC TSS2 Toyotas (2021+ Sienna, safety param 2121 = EPS scale 73 | SECOC).
"""Always On Lateral on SecOC TSS2 Toyotas: camera2121, Yaris stock2633 and radar takeover6217.
They have no 0x3BC GEAR_PACKET: the gear is read from 0x127 GEAR_PACKET_HYBRID (P 0, R 1, N 2, D 3, B 4; checksummed,
~60 Hz). Everything else is the TSS2 rule: main switch, doors, EPS state, and for longitudinal also cruise and belt.
@@ -25,6 +25,9 @@ def checksum(address, data):
class TestToyotaSecocAol(unittest.TestCase):
WORD = WORD
BOTH_PERMISSION = 3
def setUp(self):
self.safety = libsafety_py.libsafety
@@ -32,7 +35,8 @@ class TestToyotaSecocAol(unittest.TestCase):
self.safety.set_alternative_experience(0)
self.safety.set_safety_hooks(CarParams.SafetyModel.noOutput, 0)
def init(self, word=WORD, ae=32):
def init(self, word=None, ae=32):
word = self.WORD if word is None else word
self.omit_addresses = set()
self.safety.init_tests()
self.safety.set_alternative_experience(ae)
@@ -74,7 +78,7 @@ class TestToyotaSecocAol(unittest.TestCase):
self.physical(gear=g)
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
self.physical(gear=g, cruise=True)
self.assertEqual(self.safety.aol_get_permission_mask(), 3)
self.assertEqual(self.safety.aol_get_permission_mask(), self.BOTH_PERMISSION)
def test_park_reverse_neutral_deny(self):
for g in (P, R, N, 5, 15):
@@ -151,7 +155,7 @@ class TestToyotaSecocAol(unittest.TestCase):
for changes in ({"belt": False}, {"brake": True}, {"gas": True}):
self.init()
self.physical(cruise=True)
self.assertEqual(self.safety.aol_get_permission_mask(), 3)
self.assertEqual(self.safety.aol_get_permission_mask(), self.BOTH_PERMISSION)
self.physical(cruise=True, **changes)
self.assertEqual(self.safety.aol_get_permission_mask(), 1, changes)
@@ -172,11 +176,11 @@ class TestToyotaSecocAol(unittest.TestCase):
def test_lta_stays_blocked(self):
self.init()
self.physical(gear=D, cruise=True)
self.assertEqual(self.safety.aol_get_permission_mask(), 3)
self.assertEqual(self.safety.aol_get_permission_mask(), self.BOTH_PERMISSION)
self.assertFalse(self.safety.safety_tx_hook(libsafety_py.make_CANPacket(0x131, 0, bytes([8]) + bytes(7))))
def test_exact_admission(self):
for word, ae in ((WORD, 0), (WORD, 160), (WORD, 288), (WORD, 33), (WORD | 512, 32), (2120, 32), (2122, 32)):
for word, ae in ((self.WORD, 0), (self.WORD, 160), (self.WORD, 288), (self.WORD, 33), (self.WORD ^ 1, 32), (self.WORD ^ 2, 32)):
self.init(word, ae)
self.physical()
self.assertEqual(self.safety.aol_get_request_mask(), 0, (word, ae))
@@ -220,7 +224,7 @@ class TestToyotaSecocAol(unittest.TestCase):
def test_host_heartbeat_request_and_required_rx_health_are_independent(self):
self.init()
self.physical(cruise=True)
self.assertEqual(self.safety.aol_get_permission_mask(), 3)
self.assertEqual(self.safety.aol_get_permission_mask(), self.BOTH_PERMISSION)
self.safety.set_aol_test_heartbeat(False)
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
self.safety.set_aol_test_heartbeat(True)
@@ -241,17 +245,17 @@ class TestToyotaSecocAol(unittest.TestCase):
self.physical(cruise=True)
self.safety.set_timer(1_000_000 + elapsed)
self.physical(cruise=True, request=False)
self.assertEqual(self.safety.aol_get_permission_mask(), 3 if allowed else 0)
self.assertEqual(self.safety.aol_get_permission_mask(), self.BOTH_PERMISSION if allowed else 0)
def test_optional_source_corruption_revokes_until_fresh_recovery(self):
for address in (0x1D3, 0x127, 0x620, 0x262):
self.init()
self.physical(cruise=True)
self.assertEqual(self.safety.aol_get_permission_mask(), 3)
self.assertEqual(self.safety.aol_get_permission_mask(), self.BOTH_PERMISSION)
self.safety.safety_rx_hook(libsafety_py.make_CANPacket(address, 0, bytes(7)))
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
self.physical(cruise=True)
self.assertEqual(self.safety.aol_get_permission_mask(), 3)
self.assertEqual(self.safety.aol_get_permission_mask(), self.BOTH_PERMISSION)
if address != 0x620:
self.safety.safety_rx_hook(libsafety_py.make_CANPacket(address, 0, bytes(8)))
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
@@ -265,12 +269,59 @@ class TestToyotaSecocAol(unittest.TestCase):
self.init()
self.physical(cruise=True)
packet = libsafety_py.make_CANPacket(0x183, 0, (accel & 0xffff).to_bytes(2, 'big') + bytes(6))
self.assertEqual(bool(self.safety.safety_tx_hook(packet)), allowed)
self.assertEqual(bool(self.safety.safety_tx_hook(packet)), allowed and self.BOTH_PERMISSION == 3)
self.init()
self.physical(cruise=False)
self.assertFalse(self.safety.safety_tx_hook(libsafety_py.make_CANPacket(0x183, 0, bytes([0, 1]) + bytes(6))))
self.assertFalse(self.safety.safety_tx_hook(libsafety_py.make_CANPacket(0x343, 0, bytes([0, 1]) + bytes(6))))
class TestYarisStockSecocAol(TestToyotaSecocAol):
WORD = 2633
BOTH_PERMISSION = 1
def test_stock_never_requests_long_or_blocks_stock_accel_forwarding(self):
self.init()
self.physical(cruise=True)
self.assertEqual(self.safety.aol_get_request_mask(), 1)
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
for address in (0x183, 0x343):
self.assertEqual(self.safety.safety_fwd_hook(2, address), 0)
for value in (0, 100, -100):
self.assertFalse(self.safety.safety_tx_hook(libsafety_py.make_CANPacket(
0x183, 0, (value & 0xffff).to_bytes(2, 'big') + bytes(6))))
class TestYarisAlphaSecocAol(TestToyotaSecocAol):
WORD = 6217
def test_release_refuses_secoc_aol_and_radar_keepalive(self):
release = libsafety_py.ffi.dlopen(libsafety_py._build_libsafety(release=True))
native = TestToyotaSecocAol(methodName='runTest')
native.safety = release
for word in (2121, 2633, 6217):
native.init(word)
native.physical(cruise=True)
self.assertEqual(release.aol_get_request_mask(), 0)
self.assertEqual(release.aol_get_permission_mask(), 0)
self.assertFalse(release.safety_tx_hook(libsafety_py.make_CANPacket(
0x750, 0, bytes.fromhex('0f023e0000000000'))))
native.tearDown()
def test_radar_keepalive_is_exact_and_profile_scoped(self):
for word in (2121, 2633, 6217, 6216, 6218):
self.init(word)
for bus in (0, 1, 2):
for payload in (bytes.fromhex('0f023e0000000000'), bytes.fromhex('0f03280101000000'),
bytes.fromhex('6d023e0000000000'), bytes.fromhex('0f023e0000000001')):
packet = libsafety_py.make_CANPacket(0x750, bus, payload)
self.assertEqual(bool(self.safety.safety_tx_hook(packet)),
word == 6217 and bus == 0 and payload == bytes.fromhex('0f023e0000000000'))
self.init()
self.physical(cruise=True)
for address in (0x183, 0x343):
self.assertEqual(self.safety.safety_fwd_hook(2, address), -1)
if __name__ == "__main__":
unittest.main()
@@ -1,4 +1,4 @@
"""Always On Lateral on the SecOC Sienna (TOYOTA_SIENNA_4TH_GEN, safety param 2121)."""
"""SecOC AOL factories, key admission, and hybrid gear policy including radar Yaris."""
import unittest
from opendbc.car import gen_empty_fingerprint, structs
@@ -11,11 +11,11 @@ from openpilot.starpilot.longitudinal.toyota_output_policy import production_ena
TOYOTA = int(structs.CarParams.SafetyModel.toyota)
def secoc_params(vehicle, key=True):
def secoc_params(vehicle, key=True, *, alpha=False):
fp = gen_empty_fingerprint()
fp[0][0x3F6] = 8
cp = CarInterface.get_params(vehicle, fp, [structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.hybrid)],
False, False, False)
alpha, False, False)
cp.secOcKeyAvailable = key
return cp
@@ -108,7 +108,7 @@ TEST_KEY = '11' * 16
@contextmanager
def actual_card(raw_key=TEST_KEY, *, simulation=True, vehicle=CAR.TOYOTA_SIENNA_4TH_GEN):
def actual_card(raw_key=TEST_KEY, *, simulation=True, vehicle=CAR.TOYOTA_SIENNA_4TH_GEN, alpha=False):
with OpenpilotPrefix(), mock.patch.dict(os.environ, {'SIMULATION': '1' if simulation else '0', 'AOL_REPLAY_RUNTIME': '0',
'TOYOTA_LONG_OUTPUT_REPLAY_RUNTIME': '0'}):
params = Params()
@@ -118,7 +118,7 @@ def actual_card(raw_key=TEST_KEY, *, simulation=True, vehicle=CAR.TOYOTA_SIENNA_
params.put('LKASButtonControl', 8, block=True)
if raw_key is not None:
params.put('SecOCKey', raw_key, block=True)
cp = secoc_params(vehicle, key=False)
cp = secoc_params(vehicle, key=False, alpha=alpha)
interface = CarInterface(cp)
with mock.patch('openpilot.selfdrive.car.card.open', side_effect=FileNotFoundError, create=True):
@@ -130,7 +130,7 @@ def actual_card(raw_key=TEST_KEY, *, simulation=True, vehicle=CAR.TOYOTA_SIENNA_
del selected
def parsed_state(interface, *, gear=3, main=True, door=False, belt=True, eps=1, lda=0, now_ns=1_000_000_000, ticks=8):
def parsed_state(interface, *, gear=3, main=True, door=False, belt=True, eps=1, lda=0, now_ns=1_000_000_000, ticks=8, cruise=False):
interface.update([])
packer = getattr(interface, '_secoc_fixture_packer', None)
if packer is None:
@@ -138,7 +138,7 @@ def parsed_state(interface, *, gear=3, main=True, door=False, belt=True, eps=1,
interface._secoc_fixture_packer = packer
tracked = {bus: tuple(parser.vl) for bus, parser in interface.can_parsers.items()}
values: dict[str, dict[str, float]] = {
'PCM_CRUISE': {'CRUISE_ACTIVE': 0}, 'PCM_CRUISE_2': {'MAIN_ON': main},
'PCM_CRUISE': {'CRUISE_ACTIVE': cruise}, 'PCM_CRUISE_2': {'MAIN_ON': main},
'GEAR_PACKET_HYBRID': {'GEAR': gear}, 'EPS_STATUS': {'LKA_STATE': eps},
'BODY_CONTROL_STATE': {'DOOR_OPEN_FL': door, 'SEATBELT_DRIVER_UNLATCHED': not belt},
'WHEEL_SPEEDS': {f'WHEEL_SPEED_{wheel}': 72 for wheel in ('FR', 'FL', 'RR', 'RL')},
@@ -162,22 +162,24 @@ def parsed_state(interface, *, gear=3, main=True, door=False, belt=True, eps=1,
return state, frames
@pytest.mark.parametrize('vehicle', (CAR.TOYOTA_SIENNA_4TH_GEN, CAR.TOYOTA_RAV4_PRIME))
@pytest.mark.parametrize('vehicle,alpha', ((CAR.TOYOTA_SIENNA_4TH_GEN, False), (CAR.TOYOTA_RAV4_PRIME, False),
(CAR.TOYOTA_YARIS, False), (CAR.TOYOTA_YARIS, True)))
@pytest.mark.parametrize('key', (None, '', 'not-a-key', '11' * 15, TEST_KEY))
def test_actual_card_decodes_key_before_aol_admission_and_publishes_exact_profile(key, vehicle):
with actual_card(key, vehicle=vehicle) as (selected, saved):
def test_actual_card_decodes_key_before_aol_admission_and_publishes_exact_profile(key, vehicle, alpha):
with actual_card(key, vehicle=vehicle, alpha=alpha) as (selected, saved):
admitted = decode_key(key) is not None
assert selected.CP.secOcKeyAvailable == admitted
assert selected.aol_qualified == admitted
assert selected.CP.alternativeExperience == (32 if admitted else 0)
assert selected.CP.safetyConfigs[0].safetyParam == 2121
assert selected.CP.secOcRequired and selected.CP.openpilotLongitudinalControl
word = (6217 if alpha else 2633) if vehicle == CAR.TOYOTA_YARIS else 2121
assert selected.CP.safetyConfigs[0].safetyParam == word
assert selected.CP.secOcRequired and selected.CP.openpilotLongitudinalControl == (vehicle != CAR.TOYOTA_YARIS or alpha)
assert policy_for(selected.CP).auxiliary_lkas_supported
assert saved.get('LKASButtonControl') == 8
if admitted:
assert selected.aol_card_intent.settings.lkas_action == selected.aol_card_intent.settings.main_action == 0
assert selected.CI.CS.secoc_key == selected.CI.CC.secoc_key == bytes.fromhex(TEST_KEY)
assert native_matches_cp(selected.CP, TOYOTA, 2121)
assert native_matches_cp(selected.CP, TOYOTA, word)
assert production_enabled(selected.CP) == (vehicle == CAR.TOYOTA_SIENNA_4TH_GEN)
else:
assert selected.aol_card_intent is None
@@ -199,10 +201,11 @@ def test_actual_card_decodes_key_before_aol_admission_and_publishes_exact_profil
del controls
@pytest.mark.parametrize('vehicle', (CAR.TOYOTA_SIENNA_4TH_GEN, CAR.TOYOTA_RAV4_PRIME))
@pytest.mark.parametrize('vehicle,alpha', ((CAR.TOYOTA_SIENNA_4TH_GEN, False), (CAR.TOYOTA_RAV4_PRIME, False),
(CAR.TOYOTA_YARIS, False), (CAR.TOYOTA_YARIS, True)))
@pytest.mark.parametrize('gear,expected', ((0, 'park'), (1, 'reverse'), (2, 'neutral'), (3, 'drive'), (4, 'brake')))
def test_actual_secoc_factory_parser_uses_hybrid_gear_without_legacy_gear(gear, expected, vehicle):
cp = secoc_params(vehicle)
def test_actual_secoc_factory_parser_uses_hybrid_gear_without_legacy_gear(gear, expected, vehicle, alpha):
cp = secoc_params(vehicle, alpha=alpha)
interface = CarInterface(cp)
state, frames = parsed_state(interface, gear=gear)
assert state.canValid and not state.canTimeout
@@ -220,7 +223,7 @@ def test_actual_secoc_factory_parser_uses_hybrid_gear_without_legacy_gear(gear,
assert str(after_legacy.gearShifter) == expected
@pytest.mark.parametrize('vehicle', (CAR.TOYOTA_YARIS, CAR.TOYOTA_RAV4_TSS2_2023))
@pytest.mark.parametrize('vehicle', (CAR.TOYOTA_RAV4_TSS2_2023,))
def test_other_secoc_and_angle_factories_never_join_sienna_aol(vehicle):
cp = CarInterface.get_params(vehicle, gen_empty_fingerprint(), [], False, False, False)
cp.secOcKeyAvailable = True
@@ -316,12 +319,38 @@ def test_secoc_fixture_advances_real_counter_and_duplicate_counter_invalidates_c
assert not state.canValid and not state.canTimeout
@pytest.mark.parametrize('vehicle', (CAR.TOYOTA_SIENNA_4TH_GEN, CAR.TOYOTA_RAV4_PRIME))
@pytest.mark.parametrize('vehicle,alpha', ((CAR.TOYOTA_SIENNA_4TH_GEN, False), (CAR.TOYOTA_RAV4_PRIME, False),
(CAR.TOYOTA_YARIS, False), (CAR.TOYOTA_YARIS, True)))
@pytest.mark.parametrize('gear,drivable', ((0, False), (1, False), (2, False), (3, True), (4, True)))
def test_secoc_parsed_gear_matches_common_entry_policy(vehicle, gear, drivable):
def test_secoc_parsed_gear_matches_common_entry_policy(vehicle, alpha, gear, drivable):
from openpilot.selfdrive.car.car_events import CarEvents
from openpilot.cereal import log
cp = secoc_params(vehicle)
cp = secoc_params(vehicle, alpha=alpha)
current, _ = parsed_state(CarInterface(cp), gear=gear)
events = CarEvents(cp).create_common_events(current, current)
assert (log.OnroadEvent.EventName.wrongGear not in events.names) == drivable
def test_every_secoc_factory_is_accounted_for():
assert {vehicle for vehicle in CAR if vehicle.config.flags & ToyotaFlags.SECOC} == {CAR.TOYOTA_SIENNA_4TH_GEN, CAR.TOYOTA_RAV4_PRIME, CAR.TOYOTA_YARIS}
@pytest.mark.parametrize('alpha', (False, True))
@pytest.mark.parametrize('mutation', ('missing_radar', 'wrong_disable', 'wrong_long', 'wrong_word', 'no_key', 'release'))
def test_yaris_secoc_refuses_inconsistent_configuration(alpha, mutation):
cp = secoc_params(CAR.TOYOTA_YARIS, alpha=alpha)
assert qualified(cp)
if mutation == 'missing_radar':
cp.flags &= ~int(ToyotaFlags.RADAR_ACC)
elif mutation == 'wrong_disable':
cp.flags ^= int(ToyotaFlags.DISABLE_RADAR)
elif mutation == 'wrong_long':
cp.openpilotLongitudinalControl = not cp.openpilotLongitudinalControl
elif mutation == 'wrong_word':
cp.safetyConfigs[0].safetyParam = 2121
elif mutation == 'no_key':
cp.secOcKeyAvailable = False
else:
cp.dashcamOnly = True
assert not qualified(cp)
assert not policy_for(cp).intent_supported
@@ -27,18 +27,21 @@ class CapturePublisher:
self.messages[service] = messaging.log_from_bytes(raw)
@pytest.mark.parametrize('vehicle', (CAR.TOYOTA_SIENNA_4TH_GEN, CAR.TOYOTA_RAV4_PRIME))
def test_fresh_secoc_source_permission_sd_controls_card_sendcan_join(vehicle):
@pytest.mark.parametrize('vehicle,alpha,ordinary', ((CAR.TOYOTA_SIENNA_4TH_GEN, False, False),
(CAR.TOYOTA_RAV4_PRIME, False, False), (CAR.TOYOTA_YARIS, False, False),
(CAR.TOYOTA_YARIS, True, False), (CAR.TOYOTA_YARIS, False, True), (CAR.TOYOTA_YARIS, True, True)))
def test_fresh_secoc_source_permission_sd_controls_card_sendcan_join(vehicle, alpha, ordinary):
case = IoniqAvailabilityTests(methodName='runTest')
with actual_card(simulation=False, vehicle=vehicle) as (selected, _), ExitStack() as cleanup:
with actual_card(simulation=False, vehicle=vehicle, alpha=alpha) as (selected, _), ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
sd, controls, step = case._authority_callers(
False, cp=selected.CP.as_reader().as_builder(), genuine_events=True)
ordinary, cp=selected.CP.as_reader().as_builder(), genuine_events=True)
assert not selected.sm.simulation and not sd.sm.simulation and not controls.sm.simulation
capture = CapturePublisher()
cleanup.enter_context(mock.patch.object(selected, 'pm', capture))
cleanup.enter_context(mock.patch('openpilot.selfdrive.car.card.REPLAY', False))
transition = cleanup.enter_context(mock.patch.object(sd.state_machine, 'update', wraps=sd.state_machine.update))
radar_startup = cleanup.enter_context(mock.patch('opendbc.car.toyota.interface.disable_ecu'))
apply = cleanup.enter_context(mock.patch.object(selected.CI, 'apply', wraps=selected.CI.apply))
original_update_msgs = controls.sm.update_msgs
def model_target(timestamp, messages):
@@ -57,13 +60,14 @@ def test_fresh_secoc_source_permission_sd_controls_card_sendcan_join(vehicle):
safety = libsafety_py.libsafety
safety.init_tests()
safety.set_alternative_experience(32)
safety.set_safety_hooks(structs.CarParams.SafetyModel.toyota, 2121)
word = selected.CP.safetyConfigs[0].safetyParam
safety.set_safety_hooks(structs.CarParams.SafetyModel.toyota, word)
def reset_native():
safety.set_alternative_experience(0)
safety.set_safety_hooks(structs.CarParams.SafetyModel.noOutput, 0)
cleanup.callback(reset_native)
base = 10_000_000_000
warm, _ = parsed_state(selected.CI, now_ns=base - 80_000_000)
warm, _ = parsed_state(selected.CI, now_ns=base - 80_000_000, cruise=ordinary)
assert warm.canValid and not warm.canTimeout
original_message = messaging.new_message
driving_torques = []
@@ -78,16 +82,18 @@ def test_fresh_secoc_source_permission_sd_controls_card_sendcan_join(vehicle):
with mock.patch('openpilot.selfdrive.car.card.time.monotonic_ns', return_value=now), \
mock.patch('openpilot.selfdrive.car.card.messaging.new_message', side_effect=clocked_message):
physical, frames = parsed_state(selected.CI, gear=gear, now_ns=now, ticks=1)
physical, frames = parsed_state(selected.CI, gear=gear, now_ns=now, ticks=1, cruise=ordinary)
assert physical.canValid and not physical.canTimeout
safety.set_timer(now // 1000)
safety.set_aol_test_heartbeat(True)
for address, payload, bus in frames:
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, payload))
safety.aol_set_host_request(1)
want_long = ordinary and selected.CP.openpilotLongitudinalControl
wanted = 3 if want_long else 1
safety.aol_set_host_request(wanted)
request = safety.aol_get_request_mask()
permission = safety.aol_get_permission_mask()
assert request == 1 and permission == (1 if gear in (3, 4) else 0)
assert request == wanted and permission == (wanted if gear in (3, 4) else 0)
selected.aol_card_intent.update(physical.as_reader(), now_ns=now)
selected.state_publish(physical, None)
car_source = capture.messages['carState']
@@ -97,21 +103,22 @@ def test_fresh_secoc_source_permission_sd_controls_card_sendcan_join(vehicle):
before = transition.call_count
axis, _, command = step(
tick, car_state_message=car_source.as_builder(), intent_message=intent_source.as_builder(),
allowed=bool(permission & 1), long_allowed=False,
native_request_lat=bool(request & 1), native_request_long=False,
panda_controls_allowed=False)
allowed=bool(permission & 1), long_allowed=bool(permission & 2),
native_request_lat=bool(request & 1), native_request_long=bool(request & 2),
panda_controls_allowed=bool(safety.get_controls_allowed()))
assert transition.call_count == before + 1
assert axis.sourceCarStateMonoTime == car_source.logMonoTime == now
assert axis.observedMonoTime == now and axis.validUntilMonoTime == now + 30_000_000
assert axis.intentProducerSessionId == selected.slc_producer_session
assert command.latActive == (gear in (3, 4)) and not command.longActive
assert command.latActive == (gear in (3, 4))
assert command.longActive == (want_long and gear in (3, 4))
native = decode_safety(sd.sm['aolSafetyWire'])
assert native is not None and native.observedMonoTime == now
assert native.safetyParam == 2121 and native.axisSessionId == axis.sessionId
assert native.safetyParam == word and native.axisSessionId == axis.sessionId
assert native.requestedLateral == bool(request & 1)
assert native.lateralAllowed == bool(permission & 1)
assert len(native.pandaInventory) == 1
assert native.pandaInventory[0].safetyParam == 2121
assert native.pandaInventory[0].safetyParam == word
assert native.pandaInventory[0].alternativeExperience == 32
control_source = controls.pm.messages['carControl']
assert control_source.logMonoTime == now
@@ -132,10 +139,23 @@ def test_fresh_secoc_source_permission_sd_controls_card_sendcan_join(vehicle):
assert bool(steer[1][0] & 1) == command.latActive
assert steer == add_mac(bytes.fromhex(TEST_KEY), 1, 1, counter, (steer[0], steer[1][:4], steer[2]))
assert safety.safety_tx_hook(libsafety_py.make_CANPacket(steer[0], steer[2], steer[1]))
if alpha:
keepalive = [packet for packet in sent.sendcan if packet.address == 0x750]
for packet in keepalive:
assert bytes(packet.dat) == bytes.fromhex('0f023e0000000000')
assert safety.safety_tx_hook(libsafety_py.make_CANPacket(packet.address, packet.src, packet.dat))
accel_frames = [packet for packet in sent.sendcan if packet.address == 0x183]
if not selected.CP.openpilotLongitudinalControl:
assert not accel_frames
for packet in accel_frames:
assert safety.safety_tx_hook(libsafety_py.make_CANPacket(packet.address, packet.src, packet.dat))
torque = int.from_bytes(steer[1][1:3], 'big', signed=True)
if gear in (3, 4):
driving_torques.append(torque)
assert abs(torque) <= min(1500, 15 * (tick + 1))
else:
assert not axis.lateralActive and torque == 0
assert radar_startup.call_count == int(alpha)
if alpha:
assert radar_startup.call_args.kwargs == {'bus': 0, 'addr': 0x750, 'sub_addr': 0xf, 'com_cont_req': bytes.fromhex('280101')}
assert any(torque != 0 for torque in driving_torques), 'Fresh joined Controls must produce actual steering torque'
+11 -4
View File
@@ -17,14 +17,14 @@ AOL_WORDS = frozenset((
0x8042, 0x8242, 0xC042, 0xC242, 0xD049, 0xD249,
0xC049, 0xC249,
0x9449, 0x9549, 0x9558, 0x9564, 0x9649, 0x9749, 0x9758, 0x9764, 0xD44D, 0xD549, 0xD64D, 0xD749,
73, 585, 856, 4169, 4681, 2121, 0x9049, 0x9249,
73, 585, 856, 4169, 4681, 2121, 2633, 6217, 0x9049, 0x9249,
0xB049, 0xB249, 0x2449, 0x2649, 0x2049, 0x2249, 0x424D, 0x449, 0x649, 0x1449, 0x1649,
0x8049, 0x8249, 0x8449, 0x8649, 0x9158, 0x9358, 0x9149,
0x9349, 0x9164, 0x9364, 0xd149, 0xd349, 0xd04d, 0xd24d,
))
SECOC_TORQUE_CARS = frozenset((CAR.TOYOTA_SIENNA_4TH_GEN, CAR.TOYOTA_RAV4_PRIME))
SECOC_TORQUE_CARS = frozenset((CAR.TOYOTA_SIENNA_4TH_GEN, CAR.TOYOTA_RAV4_PRIME, CAR.TOYOTA_YARIS))
RADAR_TORQUE_CARS = frozenset((CAR.TOYOTA_CHR_TSS2, CAR.TOYOTA_RAV4_TSS2_2022))
CAMERA_TORQUE_CARS = frozenset((
CAR.TOYOTA_ALPHARD_TSS2, CAR.TOYOTA_AVALON_TSS2, CAR.TOYOTA_CAMRY_TSS2, CAR.TOYOTA_COROLLA_TSS2,
@@ -35,18 +35,25 @@ CAMERA_TORQUE_CARS = frozenset((
def secoc_qualified(cp, *, marked_only=False):
radar = cp is not None and cp.carFingerprint == CAR.TOYOTA_YARIS
required = ToyotaFlags.TSS2 | ToyotaFlags.NO_DSU | ToyotaFlags.SECOC | ToyotaFlags.RAISED_ACCEL_LIMIT
if radar:
required |= ToyotaFlags.RADAR_ACC
if cp.openpilotLongitudinalControl:
required |= ToyotaFlags.DISABLE_RADAR
allowed = required | ToyotaFlags.HYBRID | ToyotaFlags.HAS_BSM
if (cp is None or cp.brand != 'toyota' or cp.carFingerprint not in SECOC_TORQUE_CARS or
cp.notCar or cp.passive or cp.dashcamOnly or not cp.pcmCruise or not cp.openpilotLongitudinalControl or
cp.notCar or cp.passive or cp.dashcamOnly or not cp.pcmCruise or (not cp.openpilotLongitudinalControl and not radar) or
not cp.secOcRequired or not cp.secOcKeyAvailable or
cp.steerControlType != structs.CarParams.SteerControlType.torque or len(cp.safetyConfigs) != 1 or
cp.alternativeExperience not in ((32,) if marked_only else (0, 32)) or
cp.flags & required != required or int(cp.flags) & ~int(allowed)):
return False
config = cp.safetyConfigs[0]
mode = (ToyotaSafetyFlags.SECOC_RADAR_LONG if cp.openpilotLongitudinalControl else
ToyotaSafetyFlags.SECOC | ToyotaSafetyFlags.STOCK_LONGITUDINAL) if radar else ToyotaSafetyFlags.SECOC
return (config.safetyModel == structs.CarParams.SafetyModel.toyota and
config.safetyParam == EPS_SCALE[cp.carFingerprint] | int(ToyotaSafetyFlags.SECOC))
config.safetyParam == EPS_SCALE[cp.carFingerprint] | int(mode))
def lexus_is_qualified(cp, *, marked_only=False):