Restore Lexus IS DSU MAIN lateral ownership

This commit is contained in:
firestar5683
2026-10-22 07:24:49 -05:00
parent 2b42135302
commit 5480ae3caa
14 changed files with 288 additions and 27 deletions
+8 -1
View File
@@ -3,6 +3,7 @@ import math
from opendbc.can import CANDefine, CANParser
from opendbc.car.toyota import pedal
from opendbc.car.toyota.lexus_is import LexusIsCANParser
from opendbc.car import Bus, DT_CTRL, create_button_events, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.common.filter_simple import FirstOrderFilter
@@ -153,6 +154,9 @@ class CarState(CarStateBase):
if self.CP.flags & ToyotaFlags.UNSUPPORTED_DSU:
# TODO: find the bit likely in DSU_CRUISE that describes an ACC fault. one may also exist in CLUTCH
ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0
if self.CP.carFingerprint == CAR.LEXUS_IS:
stamp = cp.ts_nanos["DSU_CRUISE"]["MAIN_ON"]
ret.cruiseState.available &= bool(cp.main_valid and stamp > 0 and 0 <= cp._last_update_nanos - stamp <= 303_031_000)
ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS
cluster_set_speed = cp.vl["PCM_CRUISE_ALT"]["UI_SET_SPEED"]
else:
@@ -288,6 +292,8 @@ class CarState(CarStateBase):
if CP.carFingerprint == CAR.TOYOTA_SIENNA_4TH_GEN:
pt_messages.append(("PCM_CRUISE_4", 1))
if CP.carFingerprint == CAR.LEXUS_IS:
pt_messages.append(("DSU_CRUISE", 10))
if CP.flags & ToyotaFlags.LONG_FILTER:
pt_messages.append(("SDSU", 50))
@@ -296,7 +302,8 @@ class CarState(CarStateBase):
if pedal.configured(CP):
pt_messages.append(("GAS_SENSOR", 50))
pt_parser = pedal.PedalCANParser if pedal.configured(CP) else Rav4hCANParser if rav4h_filter_configured(CP) else CANParser
pt_parser = (LexusIsCANParser if CP.carFingerprint == CAR.LEXUS_IS else pedal.PedalCANParser if pedal.configured(CP) else
Rav4hCANParser if rav4h_filter_configured(CP) else CANParser)
return {
Bus.pt: pt_parser(DBC[CP.carFingerprint][Bus.pt], pt_messages, 0),
Bus.cam: CANParser(dbc_name, cam_messages, 2),
@@ -52,6 +52,8 @@ class CarInterface(CarInterfaceBase):
ret.brand = "toyota"
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.toyota)]
ret.safetyConfigs[0].safetyParam = EPS_SCALE[candidate]
if candidate == CAR.LEXUS_IS:
ret.safetyConfigs[0].safetyParam |= int(ToyotaSafetyFlags.ALT_CRUISE)
# BRAKE_MODULE is on a different address for these cars
if DBC[candidate][Bus.pt] == "toyota_new_mc_pt_generated":
@@ -0,0 +1,21 @@
"""Exact Lexus IS DSU MAIN source, paired with the native 303031 us lease."""
from opendbc.can import CANParser
class LexusIsCANParser(CANParser):
def __init__(self, *args):
super().__init__(*args)
self.main_valid = False
def update(self, strings, sendcan=False):
batches = []
for stamp, frames in strings:
accepted = []
for address, data, bus in frames:
if address == 0x365 and bus == self.bus:
self.main_valid = len(data) == 7
if not self.main_valid:
continue
accepted.append((address, data, bus))
batches.append((stamp, accepted))
return super().update(batches, sendcan)
@@ -0,0 +1,62 @@
import unittest
from opendbc.car import Bus, structs, CanData
from opendbc.can import CANPacker
from opendbc.car.toyota.interface import CarInterface
from opendbc.car.toyota.values import CAR, DBC
from openpilot.starpilot.car.toyota.aol import qualified, policy_for
class TestLexusIsFactory(unittest.TestCase):
def test_exact_factory_stock_owner_and_sibling_boundary(self):
for alpha in (False, True):
cp = CarInterface.get_params(CAR.LEXUS_IS, {0: {}, 1: {}, 2: {}}, [], alpha, False, False)
self.assertEqual(cp.safetyConfigs[0].safetyParam, 0x424D)
self.assertFalse(cp.openpilotLongitudinalControl)
self.assertTrue(cp.pcmCruise)
self.assertTrue(qualified(cp))
self.assertEqual(policy_for(cp).alternative_experience_addition, 32)
cp.alternativeExperience = 32
self.assertTrue(qualified(cp, marked_only=True))
for mutation in ('carFingerprint', 'safetyParam', 'long', 'angle', 'passive'):
bad = cp.as_reader().as_builder()
if mutation == 'carFingerprint':
bad.carFingerprint = CAR.LEXUS_RC
elif mutation == 'safetyParam':
bad.safetyConfigs[0].safetyParam = 0x404D
elif mutation == 'long':
bad.openpilotLongitudinalControl = True
elif mutation == 'angle':
bad.steerControlType = structs.CarParams.SteerControlType.angle
else:
bad.passive = True
self.assertFalse(qualified(bad), mutation)
rc = CarInterface.get_params(CAR.LEXUS_RC, {0: {}, 1: {}, 2: {}}, [], False, False, False)
self.assertEqual(rc.safetyConfigs[0].safetyParam, 589)
def test_real_parser_dsu_main_and_pcm_cruise(self):
cp = CarInterface.get_params(CAR.LEXUS_IS, {0: {}, 1: {}, 2: {}}, [], False, False, False)
ci = CarInterface(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
ci.update([]) # Register signals used by the real CarState before the first CAN sample.
for tick in range(50):
frames = [packer.make_can_msg('DSU_CRUISE', 0, {'MAIN_ON': tick < 25, 'SET_SPEED': 80}),
packer.make_can_msg('PCM_CRUISE', 0, {'GAS_RELEASED': 1, 'CRUISE_ACTIVE': 1}),
packer.make_can_msg('PCM_CRUISE_2', 0, {'MAIN_ON': tick >= 25})]
state = ci.update([(1_000_000_000 + tick * 100_000_000, frames)])
self.assertEqual(state.cruiseState.available, tick < 25)
self.assertTrue(state.cruiseState.enabled)
self.assertAlmostEqual(state.cruiseState.speed, 80/3.6, places=5)
def test_dsu_main_lease_and_malformed_frames_cannot_refresh_it(self):
cp = CarInterface.get_params(CAR.LEXUS_IS, {0: {}, 1: {}, 2: {}}, [], False, False, False)
ci = CarInterface(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
ci.update([])
frame = packer.make_can_msg('DSU_CRUISE', 0, {'MAIN_ON': 1})
self.assertTrue(ci.update([(1_000_000_000, [frame])]).cruiseState.available)
self.assertTrue(ci.update([(1_303_031_000, [])]).cruiseState.available)
self.assertFalse(ci.update([(1_303_031_001, [])]).cruiseState.available)
self.assertTrue(ci.update([(1_400_000_000, [frame])]).cruiseState.available)
a, d, b = frame
self.assertFalse(ci.update([(1_410_000_000, [CanData(a, d + bytes(1), b)])]).cruiseState.available)
self.assertTrue(ci.update([(1_420_000_000, [frame])]).cruiseState.available)
@@ -58,6 +58,7 @@ class ToyotaSafetyFlags(IntFlag):
SECOC = (8 << 8)
LONG_FILTER = (16 << 8)
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
+11 -7
View File
@@ -164,8 +164,8 @@ static bool toyota_get_quality_flag_valid(const CANPacket_t *msg) {
}
static void toyota_optional_rx_hook(const CANPacket_t *msg) {
const bool aol_checksum_source = (msg->addr == 0x1D3U) || (msg->addr == 0x262U) || (toyota_aol_secoc && (msg->addr == 0x127U));
const unsigned int aol_source_len = (toyota_aol_rav4h_filter && (msg->addr == 0x262U)) ? 5U : 8U;
const bool aol_checksum_source = (!toyota_alt_cruise && (msg->addr == 0x1D3U)) || (msg->addr == 0x262U) || (toyota_aol_secoc && (msg->addr == 0x127U));
const unsigned int aol_source_len = ((toyota_aol_rav4h_filter || toyota_alt_cruise) && (msg->addr == 0x262U)) ? 5U : 8U;
if (!aol_checksum_source || ((GET_LEN(msg) == aol_source_len) && (toyota_get_checksum(msg) == toyota_compute_checksum(msg)))) {
toyota_aol_observe(msg);
} else if (toyota_aol_enabled && (msg->bus == 0U)) {
@@ -201,6 +201,10 @@ static void toyota_optional_rx_hook(const CANPacket_t *msg) {
}
static void toyota_rx_hook(const CANPacket_t *msg) {
if (toyota_alt_cruise && msg_matches(msg, 0x365U, 0U) && (GET_LEN(msg) == 7U)) {
acc_main_on = GET_BIT(msg, 0U);
toyota_aol_observe(msg);
}
if (msg->bus == 0U) {
if (toyota_pedal && (msg->addr == 0x201U) && (GET_LEN(msg) == 6U)) {
const unsigned int gas = (((unsigned int)msg->data[0] << 8U) + msg->data[1] +
@@ -628,17 +632,17 @@ static safety_config toyota_init(uint16_t param) {
// The alternate-brake profile differs only in this source address. Reuse
// the same workspace; set_safety_hooks resets all active mutable status.
toyota_lka_rx_checks[3].msg[0].addr = toyota_alt_brake ? 0x224U : 0x226U;
toyota_lka_rx_checks[4].msg[0] = (CanMsgCheck){.addr = toyota_pedal ? 0x201 : 0x2FF,
.bus = 0U, .len = toyota_pedal ? 6 : 8, .frequency = 50U,
toyota_lka_rx_checks[4].msg[0] = (CanMsgCheck){.addr = toyota_alt_cruise ? 0x365 : (toyota_pedal ? 0x201 : 0x2FF),
.bus = 0U, .len = toyota_alt_cruise ? 7 : (toyota_pedal ? 6 : 8), .frequency = toyota_alt_cruise ? 10U : 50U,
.ignore_checksum = !toyota_pedal, .ignore_counter = !toyota_pedal,
.max_counter = toyota_pedal ? 15U : 0U, .ignore_quality_flag = false};
.max_counter = toyota_pedal ? 15U : 0U, .ignore_quality_flag = toyota_alt_cruise};
SET_RX_CHECKS(toyota_lka_rx_checks, ret);
if (!toyota_pedal && (param != 0x9049U) && (param != 0x9249U)) {
if (!toyota_pedal && !toyota_alt_cruise && (param != 0x9049U) && (param != 0x9249U)) {
ret.rx_checks_len -= 1; // Ordinary profiles do not require smartDSU.
}
}
if (((param & 0xC000U) != 0U) && (param != 0x9049U) && (param != 0x9249U)) {
if (((param & 0xC000U) != 0U) && !toyota_alt_cruise && (param != 0x9049U) && (param != 0x9249U)) {
ret.tx_msgs_len = 0;
}
if (((param & 0x2000U) != 0U) && !toyota_pedal) {
+14 -5
View File
@@ -2,6 +2,7 @@
static bool toyota_aol_enabled = false;
static bool toyota_pedal = false;
static bool toyota_alt_cruise = false;
static bool toyota_aol_secoc = false;
static bool toyota_aol_stock = false;
@@ -58,10 +59,17 @@ static uint8_t toyota_aol_permission(void) {
static void toyota_aol_observe(const CANPacket_t *msg) {
const uint32_t gear_addr = toyota_aol_secoc ? 0x127U : 0x3BCU;
const unsigned int expected_len = (toyota_aol_rav4h_filter && (msg->addr == 0x262U)) ? 5U : 8U;
const uint32_t main_addr = toyota_alt_cruise ? 0x365U : 0x1D3U;
unsigned int expected_len = 8U;
if (toyota_alt_cruise && (msg->addr == main_addr)) {
expected_len = 7U;
} else if ((toyota_aol_rav4h_filter || toyota_alt_cruise) && (msg->addr == 0x262U)) {
expected_len = 5U;
} else {
}
if (toyota_aol_enabled && (msg->bus == 0U) && (GET_LEN(msg) != expected_len)) {
unsigned int index = 4U;
if (msg->addr == 0x1D3U) {
if (msg->addr == main_addr) {
index = 0U;
} else if (msg->addr == gear_addr) {
index = 1U;
@@ -76,9 +84,9 @@ static void toyota_aol_observe(const CANPacket_t *msg) {
}
} else if (toyota_aol_enabled && (msg->bus == 0U) && (GET_LEN(msg) == expected_len)) {
unsigned int index = 4U;
if (msg->addr == 0x1D3U) {
if (msg->addr == main_addr) {
index = 0U;
toyota_aol_main = GET_BIT(msg, 15U) && !GET_BIT(msg, 47U);
toyota_aol_main = toyota_alt_cruise ? GET_BIT(msg, 0U) : (GET_BIT(msg, 15U) && !GET_BIT(msg, 47U));
} else if (msg->addr == gear_addr) {
index = 1U;
if (toyota_aol_secoc) {
@@ -109,9 +117,10 @@ static void toyota_aol_observe(const CANPacket_t *msg) {
static void toyota_aol_configure(uint16_t param) {
toyota_aol_reset();
toyota_alt_cruise = param == 0x424DU;
toyota_pedal = (param == 0x2049U) || (param == 0x2249U);
toyota_aol_rav4h_filter = (param == 0x9049U) || (param == 0x9249U);
toyota_aol_stock = (param == 585U) || (param == 4681U) || (param == 0x9249U) || (param == 0x2249U);
toyota_aol_stock = (param == 585U) || (param == 4681U) || (param == 0x9249U) || (param == 0x2249U) || toyota_alt_cruise;
#ifdef ALLOW_DEBUG
toyota_aol_secoc = (param == 2121U);
#else
@@ -0,0 +1,93 @@
import unittest
from opendbc.safety.tests import common
from opendbc.safety.tests.libsafety import libsafety_py
from opendbc.safety.tests import test_toyota as toyota_tests
class TestLexusIsNative(unittest.TestCase):
init = toyota_tests.TestToyotaHighlanderAol.init
rx = toyota_tests.TestToyotaHighlanderAol.rx
tearDown = toyota_tests.TestToyotaHighlanderAol.tearDown
def setUp(self):
self.safety = libsafety_py.libsafety
self.packer = common.CANPackerSafety('toyota_tnga_k_pt_generated')
def physical(self, *, main=True, cruise=True, brake=False, gas=False, gear=0, eps=1, door=False):
self.rx(0xAA, bytes([0x1A, 0x6F] * 4))
self.rx(0x260, bytes(8), True)
self.rx(0x1D2, bytes([(0 if gas else 16) | (32 if cruise else 0)]) + bytes(7), True)
self.rx(0x226, bytes(4) + bytes([32 if brake else 0]) + bytes(3))
self.rx(0x365, bytes([1 if main else 0]) + bytes(6))
self.rx(0x3BC, bytes([0, gear]) + bytes(6))
self.rx(0x620, bytes(5) + bytes([4 if door else 0, 0, 0]))
self.assertTrue(self.safety.safety_rx_hook(self.packer.make_can_msg_safety('EPS_STATUS', 0, {'LKA_STATE': eps})))
self.safety.aol_set_host_request(3)
def test_real_sources_and_stock_longitudinal_withdrawal(self):
self.init(0x424D, 32)
self.physical()
self.assertEqual(self.safety.aol_get_request_mask(), 1)
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
self.assertTrue(self.safety.get_acc_main_on())
for fields, allowed in (({'ACCEL_CMD': 0, 'CANCEL_REQ': 1}, True),
({'ACCEL_CMD': 0, 'CANCEL_REQ': 0}, False),
({'ACCEL_CMD': .1, 'CANCEL_REQ': 1}, False)):
self.assertEqual(bool(self.safety.safety_tx_hook(self.packer.make_can_msg_safety('ACC_CONTROL', 0, fields))), allowed)
self.physical(brake=True)
self.assertFalse(self.safety.get_controls_allowed())
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
self.physical(main=False)
self.assertFalse(self.safety.get_acc_main_on())
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
# PCM_CRUISE_2 must never revive the DSU-owned MAIN source.
self.rx(0x1D3, bytes([0, 128]) + bytes(6), True)
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
def test_reverse_fault_source_length_bus_and_stale_denials(self):
for changes in ({'gear': 16}, {'eps': 9}, {'eps': 17}, {'door': True}):
self.init(0x424D, 32)
self.physical()
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
self.physical(**changes)
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
self.physical()
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
for address, length in ((0x365, 8), (0x262, 8)):
self.init(0x424D, 32)
self.physical()
self.safety.safety_rx_hook(libsafety_py.make_CANPacket(address, 0, bytes(length)))
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
self.init(0x424D, 32)
self.physical(main=False)
self.safety.safety_rx_hook(libsafety_py.make_CANPacket(0x365, 2, bytes([1]) + bytes(6)))
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
self.physical()
self.safety.set_timer(1_303_032)
self.safety.aol_set_host_request(1)
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
def test_exact_profile_and_explicit_off(self):
for word in (0x404D, 0x424C, 0x424E, 0x434D, 0x524D):
self.init(word, 32)
self.physical()
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
self.assertFalse(self.safety.safety_tx_hook(self.packer.make_can_msg_safety('STEERING_LKA', 0, {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 1})))
self.init(0x424D, 0)
self.physical()
self.assertTrue(self.safety.get_controls_allowed())
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
self.safety.set_controls_allowed(False)
self.assertFalse(self.safety.safety_tx_hook(self.packer.make_can_msg_safety('STEERING_LKA', 0, {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 1})))
def test_shared_workspace_cold_switch_and_positive_steering(self):
for word in (0x424D, 73, 0x9049, 0x2049, 0x424D):
self.init(word, 32)
self.assertFalse(self.safety.safety_config_valid())
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
if word == 0x424D:
self.physical()
self.assertTrue(self.safety.safety_config_valid())
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
self.assertTrue(self.safety.safety_tx_hook(self.packer.make_can_msg_safety(
'STEERING_LKA', 0, {'STEER_REQUEST': 1, 'STEER_TORQUE_CMD': 1})))
@@ -97,7 +97,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);
const bool expected = (word == 73U) || (word == 585U) || (word == 4169U) || (word == 4681U) || (word == 2121U) || (word == 0x9049U) || (word == 0x9249U) || (word == 0x2049U) || (word == 0x2249U) || (word == 0x424DU);
auto toyota = status();
toyota.safety_mode = 2U;
toyota.safety_param = static_cast<uint16_t>(word);
+15 -1
View File
@@ -5,7 +5,7 @@ from opendbc.car.toyota.values import CAR, ToyotaFlags, ToyotaSafetyFlags, EPS_S
from openpilot.starpilot.aol.policy import AolVehiclePolicy
from openpilot.starpilot.aol.intent import AolCardIntent, AOL_TOGGLE
AOL_WORDS = frozenset((73, 585, 4169, 4681, 2121, 0x9049, 0x9249, 0x2049, 0x2249))
AOL_WORDS = frozenset((73, 585, 4169, 4681, 2121, 0x9049, 0x9249, 0x2049, 0x2249, 0x424D))
SECOC_TORQUE_CARS = frozenset((CAR.TOYOTA_SIENNA_4TH_GEN,))
@@ -33,7 +33,21 @@ def secoc_qualified(cp, *, marked_only=False):
config.safetyParam == EPS_SCALE[cp.carFingerprint] | int(ToyotaSafetyFlags.SECOC))
def lexus_is_qualified(cp, *, marked_only=False):
if (cp is None or cp.brand != 'toyota' or cp.carFingerprint != CAR.LEXUS_IS or
cp.notCar or cp.passive or cp.dashcamOnly or not cp.pcmCruise or cp.openpilotLongitudinalControl or
cp.steerControlType != structs.CarParams.SteerControlType.torque or len(cp.safetyConfigs) != 1 or
cp.flags & ToyotaFlags.UNSUPPORTED_DSU == 0 or
int(cp.flags) & ~int(ToyotaFlags.UNSUPPORTED_DSU | ToyotaFlags.HYBRID) or
cp.alternativeExperience not in ((32,) if marked_only else (0, 32))):
return False
config = cp.safetyConfigs[0]
return config.safetyModel == structs.CarParams.SafetyModel.toyota and config.safetyParam == 0x424D
def qualified(cp, *, marked_only=False):
if lexus_is_qualified(cp, marked_only=marked_only):
return True
from opendbc.car.toyota import pedal
if pedal.configured(cp):
return cp.alternativeExperience in ((32,) if marked_only else (0, 32))
+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);
return (param == 73U) || (param == 585U) || (param == 4169U) || (param == 4681U) || (param == 2121U) || (param == 0x9049U) || (param == 0x9249U) || (param == 0x2049U) || (param == 0x2249U) || (param == 0x424DU);
}
inline constexpr AolSafetyProfile TOYOTA_AOL_PROFILE{2U, toyota_aol_param, true};
@@ -0,0 +1,17 @@
"""Lexus DSU MAIN automatic factory through actual daemon/native RX and TX."""
import pytest
from opendbc.car.toyota.values import CAR
from openpilot.starpilot.car.toyota.tests.test_pedal_caller_native import (
source_matched_native as source_matched_native, daemon_gc_scope as daemon_gc_scope, run_case,
)
pytestmark = pytest.mark.usefixtures('source_matched_native', 'daemon_gc_scope')
@pytest.mark.parametrize('scenario', ('nominal', 'physical', 'faults', 'bad_dlc', 'off'))
def test_stock_lexus_actual_callers(scenario):
run_case(True, scenario, candidate=CAR.LEXUS_IS)
def test_stock_lexus_aol_off():
run_case(True, aol=False, candidate=CAR.LEXUS_IS)
@@ -82,16 +82,16 @@ def firmware(candidate=CAR.TOYOTA_COROLLA_TSS2):
]
def physical(packer, *, cruise, main=True, gear=0, brake=False, gas=False, eps=1, sdsu=0, stopped=False):
def physical(packer, *, dsu_main=False, cruise, main=True, gear=0, brake=False, gas=False, eps=1, sdsu=0, stopped=False):
frames = [
packer.make_can_msg(name, 0, {})
for name in ('VSC1S07', 'BODY_CONTROL_STATE_2', 'ESP_CONTROL', 'PCM_CRUISE_SM', 'BLINKERS_STATE', 'PRE_COLLISION', 'LIGHT_STALK')
for name in ('VSC1S07', 'BODY_CONTROL_STATE_2', 'ESP_CONTROL', 'BLINKERS_STATE', 'PRE_COLLISION', 'LIGHT_STALK')
]
frames += [
packer.make_can_msg('BODY_CONTROL_STATE', 0, {'SEATBELT_DRIVER_UNLATCHED': 0}),
packer.make_can_msg('BRAKE_MODULE', 0, {'BRAKE_PRESSED': brake}),
packer.make_can_msg('GEAR_PACKET', 0, {'GEAR': gear}),
packer.make_can_msg('WHEEL_SPEEDS', 0, {f'WHEEL_SPEED_{w}': 0.0 if stopped else 18.0 for w in ('FL', 'FR', 'RL', 'RR')}),
packer.make_can_msg('WHEEL_SPEEDS', 0, {f'WHEEL_SPEED_{w}': 0.0 if stopped else 72.0 if dsu_main else 18.0 for w in ('FL', 'FR', 'RL', 'RR')}),
packer.make_can_msg('PCM_CRUISE', 0, {'CRUISE_ACTIVE': cruise, 'GAS_RELEASED': not gas, 'CRUISE_STATE': 7 if cruise and stopped else 8 if cruise else 0}),
packer.make_can_msg('PCM_CRUISE_2', 0, {'MAIN_ON': main, 'SET_SPEED': 90}),
packer.make_can_msg('STEER_ANGLE_SENSOR', 0, {}),
@@ -100,6 +100,12 @@ def physical(packer, *, cruise, main=True, gear=0, brake=False, gas=False, eps=1
packer.make_can_msg('LKAS_HUD', 2, {}),
packer.make_can_msg('RSA1', 2, {}),
]
if dsu_main:
frames += [packer.make_can_msg('PCM_CRUISE_ALT', 0, {}), packer.make_can_msg('DSU_CRUISE', 0, {'MAIN_ON': main, 'SET_SPEED': 90})]
# This source must not revive or withdraw DSU-owned MAIN.
frames = [f for f in frames if f[0] != 0x1D3] + [packer.make_can_msg('PCM_CRUISE_2', 0, {'MAIN_ON': not main})]
else:
frames.append(packer.make_can_msg('PCM_CRUISE_SM', 0, {}))
frames += [packer.make_can_msg('ACC_CONTROL', 2, {'ACC_TYPE': 1}), packer.make_can_msg('PCS_HUD', 2, {}), packer.make_can_msg('PRE_COLLISION', 2, {})]
return frames
@@ -195,7 +201,7 @@ def control_inputs(controls, published, state, sd_events, native, now):
values.append(sd_events[name])
elif name == 'carState':
values.append(state['carState'])
elif name == 'aolSafetyWire':
elif name in ('aolSafetyWire', 'pandaStates'):
values.append(next(e for e in native if e.which() == name))
elif name in published:
values.append(published[name])
@@ -211,6 +217,8 @@ def control_inputs(controls, published, state, sd_events, native, now):
v = getattr(e, name)
if name == 'modelV2':
v.from_dict(model().to_dict())
elif name == 'lateralManeuverPlan' and controls.CP.carFingerprint == CAR.LEXUS_IS:
v.desiredCurvature = .00005 # Synthetic plan exercises nonzero encoded steering.
elif name == 'vehicleParameters':
v.stiffnessFactor, v.steerRatio = 1.0, controls.CP.steerRatio
elif name == 'longitudinalPlan':
@@ -249,7 +257,8 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_COROLL
):
host = card_module.Car()
assert host.CP.openpilotLongitudinalControl == (not stock)
assert host.CP.safetyConfigs[0].safetyParam == (0x2249 if stock else 0x2049)
dsu_main = candidate == CAR.LEXUS_IS
assert host.CP.safetyConfigs[0].safetyParam == (0x424D if dsu_main else 0x2249 if stock else 0x2049)
assert host.CP.alternativeExperience == 32
controls, sd = Controls(), sd_module.SelfdriveD(CP=host.CP)
assert controls.CP.to_dict() == host.CP.to_dict()
@@ -281,13 +290,17 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_COROLL
)
)
rows = {r.key: r for r in wheel.rows()}
assert 'Bookmark' in rows['wheel:LKASButtonControl'].choices
assert 'Toggle AOL' not in rows['wheel:LKASButtonControl'].choices
if dsu_main:
assert 'wheel:LKASButtonControl' not in rows
else:
assert 'Bookmark' in rows['wheel:LKASButtonControl'].choices
assert 'Toggle AOL' not in rows['wheel:LKASButtonControl'].choices
assert not any('Cancel' in k or 'MainCruise' in k for k in rows)
assert display_supported(host.CP) and longitudinal_supported(host.CP) == (not stock)
params.put('CancelButtonControl', 9, block=True) # Unsupported saved remap cannot steal MAIN/physical Cancel ownership.
seen_active = False
seen_pedal = False
seen_steer = False
clock = [time.monotonic_ns()]
# Deterministic input-time vector for the three serially hosted daemon methods.
# Keep the real-time nominal/physical/OFF receipts separate; no scheduling/road claim here.
@@ -320,8 +333,15 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_COROLL
)
elif scenario == 'off' and tick == 250:
params.put_bool('DisableOpenpilotLongitudinal', not stock, block=True)
frames = physical(packer, **fields)
if fields.get('sdsu', 0) is not None:
if dsu_main and fields.get('sdsu'):
fields['eps'] = 9
frames = physical(packer, dsu_main=dsu_main, **fields)
if dsu_main and fields.get('sdsu', 0) is None:
if scenario == 'bad_dlc':
frames = [(a, d + bytes(1) if a == 0x365 else d, b) for a, d, b in frames]
else:
frames = [f for f in frames if f[0] != 0x365]
if not dsu_main and fields.get('sdsu', 0) is not None:
sensor = packer.make_can_msg(
'GAS_SENSOR',
0,
@@ -419,7 +439,10 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_COROLL
if 415 <= tick < 430:
assert not cc.latActive and not cc.longActive, trace[-1]
if scenario in ('faults', 'bad_dlc'):
if any(a <= tick < b for a, b in ((260, 269), (330, 339), (465, 469), (650, 661))):
dropout = ((635, 665) if scenario == 'bad_dlc' else (662, 665)) if dsu_main else (650, 661)
if dsu_main and scenario != 'bad_dlc' and 650 <= tick < 656:
assert cc.latActive, ('DSU MAIN is still inside its existing 303 ms lease', trace[-1])
if any(a <= tick < b for a, b in ((260, 269), (330, 339), (465, 469), dropout)):
assert not cc.latActive and not cc.longActive, trace[-1]
if 350 <= tick < 356:
assert cc.latActive and not cc.longActive, ('cleared EPS must not automatically reengage ordinary LONG', trace[-1])
@@ -428,7 +451,7 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_COROLL
if scenario == 'off' and tick >= 310:
if stock:
assert not host.CP.openpilotLongitudinalControl and not cc.longActive
assert host.CP.safetyConfigs[0].safetyParam == 0x2249
assert host.CP.safetyConfigs[0].safetyParam == (0x424D if dsu_main else 0x2249)
else:
assert not host.CI.CC.pedal_input.update(time.monotonic_ns())
for f in outgoing.sendcan:
@@ -437,6 +460,11 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_COROLL
assert acc['ACCEL_CMD'] == 0 and acc['CANCEL_REQ'] and not acc['RELEASE_STANDSTILL']
for frame in outgoing.sendcan:
if dsu_main and frame.address == 0x2E4:
steer = decode(host.CP, (frame.address, bytes(frame.dat), frame.src), 'STEERING_LKA')
if steer['STEER_TORQUE_CMD'] != 0:
assert cc.latActive and steer['STEER_REQUEST']
seen_steer = True
if frame.address == 0x200:
data = bytes(frame.dat)
assert len(data) == 6 and data[5] == pedal.checksum(data)
@@ -449,6 +477,8 @@ def run_case(stock, scenario='nominal', *, aol=True, candidate=CAR.TOYOTA_COROLL
prev = current_sd
assert seen_active
assert seen_pedal == (not stock)
if dsu_main:
assert seen_steer, "Actual nonzero steering must reach the fresh native TX hook"
finally:
(OUT / (str(candidate) + '-actual-' + scenario + ('-stock-trace.json' if stock else '-long-trace.json'))).write_text(json.dumps(trace, indent=2) + '\n')
safety.set_alternative_experience(0)
Executable → Regular
+1
View File
@@ -67,6 +67,7 @@ RELEASE_TARGETS = (
"opendbc_repo/opendbc/car/hyundai/tests/test_radar_hda1_startup_guards.py",
"opendbc_repo/opendbc/car/hyundai/tests/test_radar_hda1_startup_lifecycle.py::test_exact_factory_stock_copy_and_owner_preserve_all_params",
"opendbc_repo/opendbc/safety/tests/test_toyota_pedal.py",
"opendbc_repo/opendbc/safety/tests/test_toyota_lexus_is.py",
"opendbc_repo/opendbc/car/hyundai/tests/test_ordinary_classic_startup_contract.py",
"opendbc_repo/opendbc/car/hyundai/tests/test_ordinary_classic_startup_protocol.py",
"opendbc_repo/opendbc/car/hyundai/tests/test_ordinary_classic_stock_fallback.py",