Files
StarPilot/openpilot/starpilot/aol/tests/test_gm.py
T

1662 lines
99 KiB
Python

"""Finalized GM startup ownership and independent-axis caller lifecycle."""
import json
import os
from types import SimpleNamespace
import unittest
from unittest.mock import patch
from opendbc.can import CANPacker
from opendbc.car import Bus, structs
from opendbc.car.gm.aol import GM_AOL_WORDS, GM_BASE_GATEWAY_IDS, qualified_gm
from opendbc.car.gm.lateral import lane_centering_supported
from opendbc.car.gm.interface import CarInterface
from opendbc.car.gm.tests.test_bolt_cc import params as bolt_params, feed as feed_car, control, native
from opendbc.safety.tests.libsafety import libsafety_py
from opendbc.car.gm.tests.test_cc_gateway_stock import silverado_stock_params
from opendbc.car.gm.tests.test_bolt_factory_acc import factory_params
from opendbc.car.gm.tests.test_bolt_volt_configurations import ordinary_params
from opendbc.car.gm.tests.test_volt_camera_control import camera_params
from opendbc.car.gm.tests.test_volt_camera_removed import removed_params
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
from opendbc.car.gm.values import CAR, DBC, ORDINARY_ASCM_CAR, ORDINARY_SDGM_CAR, ORDINARY_CAMERA_CAR, ORDINARY_CC_CAR, is_volt_one_pedal
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.car.card import Car
from openpilot.selfdrive.controls.controlsd import Controls
from openpilot.starpilot.aol.runtime import decide_axes
from openpilot.starpilot.aol.transport_pause import TransportPause
from openpilot.starpilot.aol.vehicle import policy_for
from openpilot.starpilot.aol.wire import IntentState, SafetyState, encode_safety
from openpilot.starpilot.lateral.tests.test_lane_runtime import feed
from openpilot.starpilot.tests.test_volt_disable_longitudinal import configured
from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences
from openpilot.starpilot.aol.tests.gm_bolt_recovery import exercise_bolt_cancel_recovery
BOLT_IDS = (CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2018_2021,
CAR.CHEVROLET_BOLT_CC_2022_2023, CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL)
def _configurations():
from opendbc.car.gm.tests.test_ordinary_cc import malibu_hybrid_params
from opendbc.car.gm.startup_preferences import prepare_disable_longitudinal
for pedal in (False, True):
for removed in (False, True):
for alternate in (False, True):
for radar in ((False, True) if pedal else (False,)):
cp = malibu_hybrid_params(pedal=pedal, removed=removed, alternate=alternate, radar=radar)
yield cp
disabled = cp.as_reader().as_builder()
prepare_disable_longitudinal(disabled, True)
yield disabled
from opendbc.car.gm.tests.test_bolt_pedal import params as pedal_params
for identity in BOLT_IDS:
cp = pedal_params(identity, pedal=True, removed=True)
yield cp
disabled = cp.as_reader().as_builder()
VehicleStartupPreferences(disable_bolt_long=True).prepare(disabled)
yield disabled
for identity in GM_BASE_GATEWAY_IDS:
cp = ordinary_params(identity, radar=True)
yield cp
if identity == CAR.BUICK_LACROSSE:
disabled = cp.as_reader().as_builder()
VehicleStartupPreferences(disable_bolt_long=True).prepare(disabled)
yield disabled
held = cp.as_reader().as_builder()
VehicleStartupPreferences(gm_auto_hold=True).prepare(held)
yield held
for identity in BOLT_IDS:
for pedal in (False, True):
for removed in (False, True):
if identity == BOLT_IDS[-1] and pedal and removed:
continue
cp = bolt_params(identity, present=pedal, pedal=pedal, removed=removed)
yield cp
if pedal:
disabled = cp.as_reader().as_builder()
VehicleStartupPreferences(disable_bolt_long=True).prepare(disabled, fingerprints={2: {0x180: 4}})
yield disabled
for alpha in (False, True):
yield factory_params(alpha=alpha)
yield camera_params(alpha=alpha)
yield removed_params(alpha=alpha)
yield removed_params(alpha=alpha, alternate=True)
for c9 in (False, True):
yield sdgm_params(alpha=alpha, brake_c9=c9)
for c9 in (False, True):
for radar in (False, True):
yield ordinary_params(CAR.CHEVROLET_VOLT_ASCM, alpha=alpha, sascm=True,
accelerator=not c9, radar=radar)
for variant in ('be', 'f1', 'cc'):
cp = configured(variant)
yield cp
disabled = cp.as_reader().as_builder()
VehicleStartupPreferences(disable_bolt_long=True).prepare(disabled)
yield disabled
from opendbc.car.gm.tests.test_ascm_intercept import params as intercept_params
from opendbc.car.gm.tests.test_ordinary_camera import params as ordinary_camera
from opendbc.car.gm.tests.test_ordinary_camera_removed import params as ordinary_removed
from opendbc.car.gm.tests.test_conventional_pedal import params as conventional_pedal
from opendbc.car.gm.tests.test_silverado_cc_pedal import params as silverado_pedal
from opendbc.car.gm.tests.test_camera_acc_pedal import params as camera_pedal
for identity in ORDINARY_ASCM_CAR | ORDINARY_SDGM_CAR:
for alpha in (False, True):
for c9 in (False, True):
for radar in (False, True):
yield intercept_params(identity, sascm=True, accelerator=not c9, radar=radar, alpha=alpha)
for identity in ORDINARY_CAMERA_CAR:
for alpha in (False, True):
for analog in (False, True):
yield ordinary_camera(identity, alpha=alpha, be=analog)
yield ordinary_removed(identity, alpha=alpha, analog=analog)
for camera in (False, True):
for be in (False, True):
for release in (False, True):
yield camera_pedal(identity, camera=camera, be=be, release=release)
from opendbc.car.gm.tests.test_ordinary_cc import malibu_f1_params
for release in (False, True):
cp = malibu_f1_params(release=release)
yield cp
disabled = cp.as_reader().as_builder()
VehicleStartupPreferences(disable_bolt_long=True).prepare(disabled)
yield disabled
for identity in ORDINARY_CC_CAR:
yield intercept_params(identity)
for removed in (False, True):
for disabled in (False, True):
yield conventional_pedal(identity, removed=removed, disabled=disabled)
from opendbc.car.gm.tests.test_camera_acc_pedal import fingerprint as pedal_fingerprint
from opendbc.car.gm.tests.test_volt_transitions import (volt_ascm_pedal_params, volt_cc_pedal_params,
volt_gateway_pedal_params, volt_sdgm_pedal_params)
for camera in (False, True):
for be in (False, True):
observed = pedal_fingerprint(camera=camera, be=be)
observed[0].update({0xBD: 7, 0x232: 8})
for release in (False, True):
yield CarInterface.get_params(CAR.CHEVROLET_VOLT_CAMERA, observed, [], False, release, False)
yield volt_gateway_pedal_params(camera=camera, be=be, release=release)
for be in (False, True):
for radar in (False, True):
for release in (False, True):
yield volt_ascm_pedal_params(be=be, radar=radar, release=release)
yield volt_sdgm_pedal_params(be=be, radar=radar, release=release)
for removed in (False, True):
yield volt_cc_pedal_params(be=be, radar=radar, release=release, removed=removed)
for removed in (False, True):
for disabled in (False, True):
yield silverado_pedal(removed=removed, disabled=disabled)
def configurations():
from opendbc.car.gm.tests.test_volt_cc_f1 import params as f1_params
yield f1_params()
yield silverado_stock_params()
for cp in _configurations():
yield cp
selected = cp.as_reader().as_builder()
VehicleStartupPreferences(gm_auto_hold=True).prepare(selected)
if selected.safetyConfigs[0].safetyParam != cp.safetyConfigs[0].safetyParam:
yield selected
for paired in (False, True):
selected = cp.as_reader().as_builder()
VehicleStartupPreferences(volt_one_pedal=True, gm_auto_hold=paired).prepare(selected)
if is_volt_one_pedal(selected):
yield selected
class TestGmAol(unittest.TestCase):
def test_actual_final_cp_registry_and_isolation(self):
words = set()
for cp in configurations():
with self.subTest(identity=cp.carFingerprint, word=hex(cp.safetyConfigs[0].safetyParam)):
self.assertTrue(qualified_gm(cp))
words.add(cp.safetyConfigs[0].safetyParam)
self.assertTrue(policy_for(cp).normal_runtime_supported)
for field in ('passive', 'dashcamOnly', 'notCar'):
denied = cp.as_reader().as_builder()
setattr(denied, field, True)
self.assertFalse(qualified_gm(denied))
denied = cp.as_reader().as_builder()
denied.safetyConfigs[0].safetyModel = 'noOutput'
self.assertFalse(qualified_gm(denied))
denied = cp.as_reader().as_builder()
denied.alternativeExperience = 33
self.assertFalse(qualified_gm(denied))
self.assertEqual(words, GM_AOL_WORDS, {"missing": GM_AOL_WORDS - words, "unexpected": words - GM_AOL_WORDS})
def test_base_gateway_aol_preserves_tuning_and_lane_capability(self):
for identity in GM_BASE_GATEWAY_IDS:
cp = ordinary_params(identity, radar=True)
with self.subTest(identity=identity):
self.assertTrue(qualified_gm(cp))
self.assertFalse(lane_centering_supported(cp))
selected = cp.as_reader().as_builder()
selected.alternativeExperience = 32
self.assertTrue(qualified_gm(selected))
self.assertFalse(lane_centering_supported(selected))
for field, value in (('pcmCruise', True), ('radarUnavailable', True), ('alphaLongitudinalAvailable', True),
('networkLocation', 'fwdCamera'), ('transmissionType', 'direct'), ('flags', 1)):
denied = cp.as_reader().as_builder()
setattr(denied, field, value)
self.assertFalse(qualified_gm(denied))
denied = cp.as_reader().as_builder()
denied.safetyConfigs[0].safetyParam = 0x80
self.assertEqual(qualified_gm(denied), identity == CAR.BUICK_LACROSSE)
denied = cp.as_reader().as_builder()
denied.openpilotLongitudinalControl = False
self.assertEqual(qualified_gm(denied), identity == CAR.BUICK_LACROSSE)
@staticmethod
def card(cp, settings):
settings.put_bool('OpenpilotEnabledToggle', True, block=True)
def get_car(*args, pre_create_hook, **kwargs):
selected = pre_create_hook(cp.as_reader().as_builder(), cp.carFingerprint, {}, [])
return CarInterface(selected)
with patch('openpilot.selfdrive.car.card.messaging.recv_one_retry', return_value=SimpleNamespace(can=[1])), \
patch('openpilot.selfdrive.car.card.get_car', side_effect=get_car):
return Car()
def test_automatic_bolt_identity_reaches_real_card_factory_and_native(self):
from opendbc.car.can_definitions import CanData
from opendbc.car import car_helpers
from opendbc.car.gm.values import CarControllerParams
from opendbc.car.gm.pedal_capability import automatic_bolt_candidate
from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.gm.tests.test_bolt_cc import setup
from openpilot.starpilot.vehicle_selection import encode
# Recorded pre-controller PT fingerprints: 2020 nonACC pedal and Zik Gen2
# ACC pedal. DLCs are literal; stock370 bytes are the first recorded true
# camera observation, not a fabricated native/control grant.
gen1 = {
170: 8, 188: 8, 189: 7, 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2,
241: 6, 257: 8, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8,
320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 368: 3, 381: 8, 384: 4, 386: 8,
388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8,
456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8,
499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 513: 6, 528: 5, 532: 6, 546: 7,
550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 564: 5, 566: 7, 567: 5, 568: 1,
569: 3, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8,
711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8,
848: 4, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 975: 2, 977: 8,
979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1013: 3,
1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 5, 1187: 4, 1217: 8, 1221: 5, 1223: 3,
1225: 7, 1227: 4, 1233: 8, 1236: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1275: 3,
1279: 4, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1904: 7, 1905: 7, 1906: 7,
1907: 7, 1912: 7, 1913: 7, 1922: 7, 1927: 7, 2020: 8, 2028: 8,
}
gen2 = {
189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 288: 5,
289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1,
352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5,
463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 513: 6,
528: 5, 532: 6, 560: 8, 562: 8, 566: 8, 608: 8, 609: 6, 610: 6, 611: 6,
612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6,
810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8,
1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4,
1300: 8, 1930: 7,
}
safety = libsafety_py.libsafety
release = safety.set_safety_hooks(int(structs.CarParams.SafetyModel.allOutput), 0) != 0
stock_acc = (0x370, bytes.fromhex('000221820000'), 2)
def receive_for(pt, camera_messages):
packets = [CanData(address, bytes(length), 0) for address, length in pt.items()]
packets += [CanData(0x180, bytes(4), 2), CanData(0x320, bytes(pt.get(0x320, 8)), 2)]
packets += [CanData(address, data, bus) for address, data, bus in camera_messages]
return lambda wait_for_one=False: [packets] if wait_for_one else []
# Unknown, mixed stock modes, wrong bus/echo and malformed lengths cannot
# manufacture ACC capability from the Gen2 alias.
for camera in ([], [(0x370, bytes(6), 2)], [(0x370, b'\0\4' + bytes(4), 2)],
[(0x370, b'\0\5' + bytes(4), 2)], [stock_acc, (0x370, b'\0\4' + bytes(4), 2)],
[(stock_acc[0], stock_acc[1], 0)], [(stock_acc[0], stock_acc[1], 130)],
[(stock_acc[0], stock_acc[1][:5], 2)]):
with self.subTest(camera=camera):
candidate, fp = car_helpers.can_fingerprint(receive_for(gen2, camera))
self.assertIsNone(candidate)
self.assertEqual(fp[0], gen2)
fp = {0: gen2}
self.assertEqual(automatic_bolt_candidate(CAR.CHEVROLET_BOLT_CC_2022_2023, fp, {4}),
CAR.CHEVROLET_BOLT_CC_2022_2023)
malformed = {0: dict(gen2)}
malformed[0][0x201] = 5
self.assertIsNone(automatic_bolt_candidate(CAR.CHEVROLET_BOLT_CC_2018_2021, malformed, {2}))
malformed[0][0x201] = 6
malformed[0][0x236] = 7
self.assertIsNone(automatic_bolt_candidate(CAR.CHEVROLET_BOLT_CC_2018_2021, malformed, {2}))
no_pedal = {0: {address: length for address, length in gen2.items() if address != 0x201}}
for alias in (CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2018_2021, CAR.CHEVROLET_BOLT_EUV):
self.assertIsNone(automatic_bolt_candidate(alias, no_pedal, set()))
self.assertIsNone(automatic_bolt_candidate(alias, no_pedal, {4, 5}))
self.assertEqual(automatic_bolt_candidate(CAR.CHEVROLET_BOLT_ACC_2022_2023, no_pedal, set()),
CAR.CHEVROLET_BOLT_ACC_2022_2023)
self.assertIsNone(automatic_bolt_candidate(None, fp, {2}))
self.assertEqual(automatic_bolt_candidate(MOCK.MOCK, fp, {2}), MOCK.MOCK)
def startup(pt, forced, camera):
with OpenpilotPrefix(), patch.dict(os.environ, {'SKIP_FW_QUERY': '1', 'FINGERPRINT': ''}):
settings = Params()
settings.put_bool('OpenpilotEnabledToggle', True, block=True)
settings.put_bool('AlwaysOnLateral', True, block=True)
settings.put_bool('AlphaLongitudinalEnabled', True, block=True)
settings.put_bool('IsReleaseBranch', release, block=True)
settings.put('VehicleSelection', json.loads(encode(None if forced is None else forced.value)), block=True)
receive = receive_for(pt, camera)
def discover(*args, **kwargs):
return car_helpers.get_car(receive, *args[1:], **kwargs)
with patch('openpilot.selfdrive.car.card.messaging.recv_one_retry', return_value=SimpleNamespace(can=[1])), \
patch('openpilot.selfdrive.car.card.get_car', side_effect=discover):
card = Car()
self.assertTrue(settings.get_bool('FirmwareQueryDone'))
self.assertFalse(card.ci_initialized)
self.assertEqual(card.CP.alternativeExperience, 32)
return card.CP
stock_pt = dict(gen2)
del stock_pt[0x201]
cases = ((gen1, None, CAR.CHEVROLET_BOLT_CC_2018_2021, 0x9D, []),
(gen2, None, CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, 0x1CD, [stock_acc]),
(gen2, CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2017, 0xBD, [stock_acc]),
(gen2, CAR.CHEVROLET_BOLT_CC_2018_2021, CAR.CHEVROLET_BOLT_CC_2018_2021, 0x9D, [stock_acc]),
(gen2, CAR.CHEVROLET_BOLT_CC_2022_2023, CAR.CHEVROLET_BOLT_CC_2022_2023, 0x19D, [stock_acc]),
(stock_pt, CAR.CHEVROLET_BOLT_ACC_2022_2023, CAR.CHEVROLET_BOLT_ACC_2022_2023,
5 if release else 7, [stock_acc]),
(gen2, CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, 0x1CD, [stock_acc]))
for pt, forced, identity, word, camera in cases:
with self.subTest(forced=forced, identity=identity, release=release):
cp = startup(pt, forced, camera)
self.assertEqual(cp.carFingerprint, identity)
self.assertEqual(cp.safetyConfigs[0].safetyParam, word)
self.assertEqual(cp.fingerprintSource, structs.CarParams.FingerprintSource.fixed if forced else
structs.CarParams.FingerprintSource.can)
self.assertTrue(cp.flags & 16) # actual recorded BSM presence, not a manual flag assignment
self.assertFalse(cp.dashcamOnly)
self.assertFalse(cp.passive)
self.assertAlmostEqual(cp.lateralTuning.torque.latAccelFactor, 1.2 if identity == CAR.CHEVROLET_BOLT_CC_2017 else 2.)
self.assertAlmostEqual(cp.lateralTuning.torque.friction, .21 if identity == CAR.CHEVROLET_BOLT_CC_2017 else .13)
self.assertEqual(CarControllerParams(cp).STEER_MAX, 450 if identity == CAR.CHEVROLET_BOLT_CC_2017 else 300)
pedal = 0x201 in pt
self.assertEqual(cp.openpilotLongitudinalControl, pedal or not release)
self.assertEqual(cp.pcmCruise, not pedal and release)
setup(cp)
safety.set_alternative_experience(cp.alternativeExperience)
self.assertEqual(safety.get_current_safety_mode(), int(structs.CarParams.SafetyModel.gm))
self.assertEqual(safety.get_current_safety_param(), word)
self.assertFalse(safety.get_controls_allowed())
def test_marked_gm_transport_survives_master_off_restart(self):
from openpilot.starpilot.feature_runtime import enabled
with OpenpilotPrefix():
settings = Params()
settings.put_bool('AlwaysOnLateral', False, block=True)
for cp in configurations():
cp = cp.as_reader().as_builder()
with self.subTest(identity=cp.carFingerprint, word=cp.safetyConfigs[0].safetyParam):
cp.alternativeExperience = 0
self.assertFalse(policy_for(cp).full_axis_runtime_required)
self.assertFalse(enabled(settings, cp, 'aol', {'AOL_REPLAY_RUNTIME': '0'}))
cp.alternativeExperience = 32
self.assertTrue(policy_for(cp).full_axis_runtime_required)
self.assertTrue(enabled(settings, cp, 'aol', {'AOL_REPLAY_RUNTIME': '0'}))
marked = silverado_stock_params()
marked.alternativeExperience = 32
card = self.card(marked, settings)
self.assertEqual(card.CP.alternativeExperience, 0)
if card.aol_card_intent is not None:
self.assertFalse(card.aol_card_intent.allowed_latch)
def test_silverado_stock_card_publishes_lateral_only_aol_owner(self):
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
card = self.card(silverado_stock_params(), settings)
self.assertFalse(card.CP.dashcamOnly)
self.assertTrue(card.CP.pcmCruise)
self.assertFalse(card.CP.openpilotLongitudinalControl)
self.assertEqual(card.CP.safetyConfigs[0].safetyParam, 16)
self.assertEqual(card.CP.alternativeExperience, 32)
self.assertIsNotNone(card.aol_card_intent)
cs = structs.CarState(canValid=True, gearShifter='drive', vEgo=20.)
cs.cruiseState.available = True
cs.cruiseState.enabled = False
card.aol_card_intent.update(cs)
self.assertTrue(card.aol_card_intent.allowed_latch)
cs.gearShifter = 'reverse'
card.aol_card_intent.update(cs)
self.assertTrue(card.aol_card_intent.allowed_latch)
cs.cruiseState.available = False
card.aol_card_intent.update(cs)
self.assertFalse(card.aol_card_intent.allowed_latch)
def test_bolt_main_intent_ignores_unavailable_lkas_assignment(self):
from openpilot.starpilot.aol.intent import read_settings
from opendbc.car.gm.tests.test_bolt_pedal import params as pedal_params
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
settings.put('LKASButtonControl', 9, block=True)
cp = pedal_params(CAR.CHEVROLET_BOLT_CC_2018_2021, pedal=True, camera=True)
self.assertEqual(cp.safetyConfigs[0].safetyParam, 0x9D)
selected = self.card(cp, settings)
owner = selected.aol_card_intent
cs = structs.CarState(canValid=True, gearShifter='drive', vEgo=20.)
cs.cruiseState.available = True
for _ in range(2):
owner.settings = read_settings(settings)
owner.update(cs)
self.assertTrue(owner.allowed_latch)
self.assertEqual(settings.get('LKASButtonControl'), 9)
settings.put('MainCruiseButtonControl', 9, block=True)
owner = self.card(cp, settings).aol_card_intent
owner.update(cs)
self.assertTrue(owner.allowed_latch)
self.assertEqual(settings.get('MainCruiseButtonControl'), 9)
cs.buttonEvents = [structs.CarState.ButtonEvent(type='mainCruise', pressed=True)]
owner.update(cs)
self.assertTrue(owner.allowed_latch)
owner.update(cs, fault_active=True)
self.assertFalse(owner.allowed_latch)
def test_actual_card_startup_default_off_and_main_level_fault_recovery(self):
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', False, block=True) # Explicit Off survives new-install On enrollment.
cp = factory_params(alpha=False)
ordinary = self.card(cp, settings)
self.assertIsNone(ordinary.aol_card_intent)
self.assertEqual(ordinary.CP.alternativeExperience, 0)
settings.put_bool('AlwaysOnLateral', True, block=True)
selected = self.card(cp, settings)
self.assertEqual(selected.CP.safetyConfigs[0].safetyParam, cp.safetyConfigs[0].safetyParam)
self.assertEqual(selected.CP.alternativeExperience, 32)
self.assertFalse(selected.aol_card_intent.explicit_latch)
cs = structs.CarState(canValid=True, gearShifter='drive', vEgo=20.)
cs.cruiseState.available = True
selected.aol_card_intent.update(cs)
self.assertTrue(selected.aol_card_intent.allowed_latch)
cs.steerFaultTemporary = True
selected.aol_card_intent.update(cs, fault_active=False)
self.assertTrue(selected.aol_card_intent.allowed_latch)
cs.steerFaultTemporary = False
cs.gearShifter = 'reverse'
selected.aol_card_intent.update(cs, fault_active=False)
self.assertTrue(selected.aol_card_intent.allowed_latch)
self.assertFalse(selected.aol_card_intent.output(cs)[0])
cs.gearShifter = 'drive'
cs.steerFaultPermanent = True
selected.aol_card_intent.update(cs, fault_active=True)
self.assertFalse(selected.aol_card_intent.allowed_latch)
cs.steerFaultPermanent = False
selected.aol_card_intent.update(cs, fault_active=False)
self.assertTrue(selected.aol_card_intent.allowed_latch)
cs.cruiseState.available = False
cs.canValid = False
selected.aol_card_intent.update(cs, fault_active=False)
cs.cruiseState.available = True
cs.canValid = True
selected.aol_card_intent.update(cs, fault_active=False)
self.assertTrue(selected.aol_card_intent.allowed_latch)
cs.cruiseState.available = False
selected.aol_card_intent.update(cs, fault_active=False)
cs.cruiseState.available = True
selected.aol_card_intent.update(cs, fault_active=False)
self.assertTrue(selected.aol_card_intent.allowed_latch)
def test_actual_bolt_card_lagging_preserves_intent_but_withdraws_both_axes(self):
from opendbc.car.gm.tests.test_bolt_pedal import params as pedal_params
from openpilot.cereal import log
from openpilot.selfdrive.selfdrived.events import Events, ET
from openpilot.starpilot.aol.intent import disarming_fault
from openpilot.starpilot.nostalgia import aol_no_entry
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
selected = self.card(pedal_params(CAR.CHEVROLET_BOLT_CC_2018_2021, pedal=True, camera=True), settings)
self.assertEqual(selected.CP.safetyConfigs[0].safetyParam, 0x9D)
cs = structs.CarState(canValid=True, gearShifter='drive', vEgo=20.)
cs.cruiseState.available = True
owner = selected.aol_card_intent
owner.update(cs)
self.assertTrue(owner.allowed_latch)
events = Events()
events.add(log.OnroadEvent.EventName.selfdrivedLagging)
self.assertTrue(events.contains(ET.NO_ENTRY))
self.assertTrue(events.contains(ET.SOFT_DISABLE))
self.assertFalse(disarming_fault(events.to_msg(), cs)) # SOFT withdraws output while retaining driver intent.
for standard in (False, True):
with patch.object(selected, 'sm', {'onroadEvents': events.to_msg()}):
fault = selected.aol_disarming_fault(cs, 1_000_000_000, 1_000_000_001)
self.assertFalse(fault)
owner.update(cs, fault_active=fault)
self.assertTrue(owner.allowed_latch)
allowed, pause_lat, pause_long = owner.output(cs)
intent = SimpleNamespace(allowedLatch=allowed, pauseLateral=pause_lat, pauseLongitudinal=pause_long)
native = SimpleNamespace(requestedLateral=True, requestedLongitudinal=True,
lateralAllowed=True, longitudinalAllowed=True)
blocked = decide_axes(standard_lateral=standard, standard_longitudinal=standard, intent=intent,
native=native, car_state=cs, initialized=True, model_ready=True,
no_entry=aol_no_entry(events.names, cs, paddle_only_cancel=False), immediate_disable=False,
dm_lockout=False, pause_brake_mps=0.)
self.assertFalse(blocked.desired_lateral or blocked.desired_longitudinal)
self.assertFalse(blocked.lateral_active or blocked.longitudinal_active)
events.clear()
with patch.object(selected, 'sm', {'onroadEvents': events.to_msg()}):
owner.update(cs, fault_active=selected.aol_disarming_fault(cs, 1_100_000_000, 1_100_000_001))
self.assertTrue(owner.allowed_latch) # No main cycle, standard engagement, or gesture.
allowed, pause_lat, pause_long = owner.output(cs)
intent = SimpleNamespace(allowedLatch=allowed, pauseLateral=pause_lat, pauseLongitudinal=pause_long)
for native, expected_active in ((None, False), (SimpleNamespace(requestedLateral=False,
requestedLongitudinal=False, lateralAllowed=True, longitudinalAllowed=True), False),
(SimpleNamespace(requestedLateral=True, requestedLongitudinal=False,
lateralAllowed=True, longitudinalAllowed=False), True)):
recovered = decide_axes(standard_lateral=False, standard_longitudinal=False, intent=intent,
native=native, car_state=cs, initialized=True, model_ready=True, no_entry=False,
immediate_disable=False, dm_lockout=False, pause_brake_mps=0.)
self.assertTrue(recovered.desired_lateral)
self.assertEqual(recovered.lateral_active, expected_active)
self.assertFalse(recovered.desired_longitudinal or recovered.longitudinal_active)
events.add(log.OnroadEvent.EventName.selfdrivedLagging)
denied = selected.CP.as_reader().as_builder()
denied.passive = True
with patch.object(selected, 'CP', denied), patch.object(selected, 'sm', {'onroadEvents': events.to_msg()}):
self.assertTrue(selected.aol_disarming_fault(cs, 1_200_000_000, 1_200_000_001))
def test_actual_bolt_card_mixed_faults_block_until_explicit_healthy_observation(self):
from opendbc.car.gm.tests.test_bolt_pedal import params as pedal_params
from openpilot.cereal import log
from openpilot.selfdrive.selfdrived.events import Events
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
for extra in (log.OnroadEvent.EventName.controlsMismatch, log.OnroadEvent.EventName.steerUnavailable, None):
with self.subTest(extra=extra):
selected = self.card(pedal_params(CAR.CHEVROLET_BOLT_CC_2018_2021, pedal=True, camera=True), settings)
cs = structs.CarState(canValid=True, gearShifter='drive', vEgo=20.)
cs.cruiseState.available = True
owner = selected.aol_card_intent
owner.update(cs)
events = Events()
events.add(log.OnroadEvent.EventName.selfdrivedLagging)
if extra is not None:
events.add(extra)
else:
cs.steerFaultPermanent = True
with patch.object(selected, 'sm', {'onroadEvents': events.to_msg()}):
fault = selected.aol_disarming_fault(cs, 1_000_000_000, 1_000_000_001)
self.assertTrue(fault)
owner.update(cs, fault_active=fault)
self.assertFalse(owner.allowed_latch)
cs.steerFaultPermanent = False
owner.update(cs, fault_active=False)
self.assertTrue(owner.allowed_latch)
cs.canValid = False
cs.cruiseState.available = False
owner.update(cs, fault_active=False)
cs.canValid = True
cs.cruiseState.available = True
owner.update(cs, fault_active=False)
self.assertTrue(owner.allowed_latch)
cs.cruiseState.available = False
owner.update(cs, fault_active=False)
cs.cruiseState.available = True
owner.update(cs, fault_active=False)
self.assertTrue(owner.allowed_latch)
def test_actual_controls_axis_receipt_and_fault_gates(self):
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'REPLAY': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
selected = self.card(factory_params(alpha=False), settings)
controls = Controls()
cases = ((None, True), ('temporary', False), (None, True), ('reverse', False),
('brake_allowed', True), ('brake_paused', False), ('regen', True),
('permanent', False), (None, True), ('off', False), ('on', True))
for tick, (fault, expected) in enumerate(cases):
now = 1_000_000_000 + tick * 10_000_000
feed(controls, now, tick, active=False, enabled=False)
cs = structs.CarState(canValid=True, vEgo=20., gearShifter='drive')
cs.cruiseState.available = fault != 'off'
cs.steerFaultTemporary = fault == 'temporary'
cs.steerFaultPermanent = fault == 'permanent'
cs.gearShifter = 'reverse' if fault == 'reverse' else 'drive'
cs.brakePressed = fault in ('brake_allowed', 'brake_paused')
cs.regenBraking = fault == 'regen'
selected.aol_card_intent.update(cs, fault_active=cs.steerFaultPermanent)
intent = IntentState('card', tick + 1, now, now, now + 30_000_000,
selected.aol_card_intent.allowed_latch, False, False, True, True)
native = SafetyState(1, True, now, now + 200_000_000, int(structs.CarParams.SafetyModel.gm),
selected.CP.safetyConfigs[0].safetyParam, True, False, True, False, 'panda', 'gm-test')
decision = decide_axes(standard_lateral=False, standard_longitudinal=False, intent=intent, native=native,
car_state=cs, initialized=True, model_ready=True, no_entry=False,
immediate_disable=False, dm_lockout=False,
pause_brake_mps=25. if fault == 'brake_paused' else 0.)
axis = messaging.new_message('aolAxisState', valid=True, logMonoTime=now)
axis.aolAxisState.qualified = True
axis.aolAxisState.nativeAcknowledged = decision.native_acknowledged
axis.aolAxisState.desiredLateral = decision.desired_lateral
axis.aolAxisState.lateralActive = decision.lateral_active
axis.aolAxisState.sessionId = 'gm-test'
axis.aolAxisState.observedMonoTime = now
axis.aolAxisState.validUntilMonoTime = now + 30_000_000
receipt = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=now)
receipt.aolSafetyWire = encode_safety(native)
state = messaging.new_message('carState', valid=True, logMonoTime=now)
state.carState = cs
controls.sm.update_msgs((now + 1_000) / 1e9, [axis.as_reader(), receipt.as_reader(), state.as_reader()])
command, _ = controls.state_control()
self.assertEqual(command.latActive, expected)
self.assertFalse(command.longActive)
def test_base_gateway_actual_card_controls_parser_and_native_axes(self):
cases = [(identity, False, False) for identity in GM_BASE_GATEWAY_IDS]
cases += [(CAR.BUICK_LACROSSE, True, False), (CAR.BUICK_LACROSSE, False, True)]
safety = libsafety_py.libsafety
for identity, held, disabled in cases:
with self.subTest(identity=identity, held=held, disabled=disabled), OpenpilotPrefix(), \
patch.dict(os.environ, {'SIMULATION': '1', 'REPLAY': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', False, block=True) # Test the preserved explicit-Off startup branch.
settings.put_bool('GMAutoHold', held, block=True)
settings.put_bool('DisableOpenpilotLongitudinal', disabled, block=True)
factory = ordinary_params(identity, radar=True)
default = self.card(factory, settings)
self.assertIsNone(default.aol_card_intent)
self.assertEqual(default.CP.alternativeExperience, 0)
settings.put_bool('AlwaysOnLateral', True, block=True)
selected = self.card(factory, settings)
self.assertEqual(selected.CP.safetyConfigs[0].safetyParam, default.CP.safetyConfigs[0].safetyParam)
self.assertEqual(selected.CP.lateralTuning.to_dict(), default.CP.lateralTuning.to_dict())
self.assertEqual(selected.CP.alternativeExperience, 32)
self.assertEqual(selected.CP.openpilotLongitudinalControl, not disabled)
self.assertFalse(selected.CP.pcmCruise)
self.assertEqual(selected.CP.safetyConfigs[0].safetyParam, 0x80 if held else 0)
self.assertEqual(selected.CP.lateralTuning.to_dict(), factory.lateralTuning.to_dict())
self.assertFalse(lane_centering_supported(selected.CP))
controls = Controls()
self.assertEqual(controls.CP.lateralTuning.to_dict(), factory.lateralTuning.to_dict())
self.assertEqual(controls.CP.lateralTuning.which(), factory.lateralTuning.which())
ci = selected.CI
packer = CANPacker(DBC[identity][Bus.pt])
safety.set_alternative_experience(32)
self.assertEqual(safety.set_safety_hooks(structs.CarParams.SafetyModel.gm, selected.CP.safetyConfigs[0].safetyParam), 0)
safety.init_tests()
seen_lateral = seen_longitudinal = False
for tick in range(150):
now = 1_000_000_000 + tick * 10_000_000
ordinary = 41 <= tick < 60 and not disabled
main = not 20 <= tick < 30
gas, brake, reverse = 70 <= tick < 80, 80 <= tick < 90, 90 <= tick < 100
_, packets = feed_car(SimpleNamespace(update=lambda _: None), packer, now, counter=tick % 4,
active=ordinary, speed=20., camera=False)
packets = [packet for packet in packets if packet[0] not in (0xC9, 0x1C4, 0xBE, 0x1E1, 0x1F5, 0xBD)]
packets += [packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 2 if ordinary else 0,
'AcceleratorPedal2': 30 if gas else 0}),
packer.make_can_msg('ECMAcceleratorPos', 0, {'BrakePedalPos': 20 if brake else 0}),
packer.make_can_msg('ECMPRDNL2', 0, {'PRNDL2': 2 if reverse else 4}),
packer.make_can_msg('ASCMSteeringButton', 0, {'ACCButtons': 3 if tick == 40 else 6 if tick == 60 else 1,
'RollingCounter': tick % 4})]
if not 100 <= tick < 140:
packets.append(packer.make_can_msg('ECMEngineStatus', 0, {'CruiseMainOn': int(main)}))
ci.update([(now - 1_000_000, packets)])
cs = ci.update([(now, packets)])
for packet in packets:
native('rx', packet, now // 1000)
safety.safety_tick()
safety.set_aol_test_heartbeat(True)
selected.aol_card_intent.update(cs, now_ns=now, standard_enabled=ordinary)
intent = IntentState('card', tick + 1, now, now, now + 30_000_000,
selected.aol_card_intent.allowed_latch, False, False, True, True)
wanted = decide_axes(standard_lateral=ordinary, standard_longitudinal=ordinary, intent=intent, native=None,
car_state=cs, initialized=True, model_ready=True, no_entry=False,
immediate_disable=False, dm_lockout=False, pause_brake_mps=25.)
safety.aol_set_host_request(int(wanted.desired_lateral) | (int(wanted.desired_longitudinal) << 1))
safety.set_timer(now // 1000 + 1)
mask = safety.aol_get_permission_mask()
receipt_state = SafetyState(1, True, now, now + 200_000_000, int(structs.CarParams.SafetyModel.gm),
selected.CP.safetyConfigs[0].safetyParam,
bool(mask & 1), bool(mask & 2),
wanted.desired_lateral, wanted.desired_longitudinal, 'panda', 'gm-gateway-test')
decision = decide_axes(standard_lateral=ordinary, standard_longitudinal=ordinary, intent=intent, native=receipt_state,
car_state=cs, initialized=True, model_ready=True, no_entry=False,
immediate_disable=False, dm_lockout=False, pause_brake_mps=25.)
feed(controls, now, tick, active=ordinary, enabled=ordinary)
axis = messaging.new_message('aolAxisState', valid=True, logMonoTime=now)
axis.aolAxisState.qualified = True
axis.aolAxisState.nativeAcknowledged = True
axis.aolAxisState.desiredLateral = decision.desired_lateral
axis.aolAxisState.desiredLongitudinal = decision.desired_longitudinal
axis.aolAxisState.lateralActive = decision.lateral_active
axis.aolAxisState.longitudinalActive = decision.longitudinal_active
axis.aolAxisState.sessionId = 'gm-gateway-test'
axis.aolAxisState.observedMonoTime = now
axis.aolAxisState.validUntilMonoTime = now + 30_000_000
receipt = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=now)
from dataclasses import replace
transport_receipt = receipt_state
if tick == 11:
transport_receipt = replace(receipt_state, safetyParam=5)
elif tick == 12:
transport_receipt = replace(receipt_state, axisSessionId='wrong-session')
elif tick == 13:
transport_receipt = replace(receipt_state, observedMonoTime=now + 1)
elif tick == 14:
transport_receipt = replace(receipt_state, validUntilMonoTime=now - 1)
receipt.aolSafetyWire = encode_safety(transport_receipt)
state = messaging.new_message('carState', valid=True, logMonoTime=now)
state.carState = cs
controls.sm.update_msgs((now + 1_000) / 1e9, [axis.as_reader(), receipt.as_reader(), state.as_reader()])
command, _ = controls.state_control()
self.assertEqual(command.latActive, decision.lateral_active and tick not in (11, 12, 13, 14))
self.assertEqual(command.longActive, ordinary)
self.assertEqual(bool(safety.get_controls_allowed()), 41 <= tick < 60)
if not main or brake or reverse or 131 <= tick < 140:
self.assertFalse(command.latActive)
if tick == 10:
self.assertTrue(command.latActive)
command.actuators.torque = .02 if command.latActive else 0.
_, messages = ci.apply(command.as_reader(), now + 2)
for message in messages:
self.assertTrue(native('tx', message, now // 1000 + 1), (tick, message))
seen_lateral |= command.latActive and not command.longActive
seen_longitudinal |= command.longActive
self.assertTrue(seen_lateral)
self.assertEqual(seen_longitudinal, not disabled)
safety.set_alternative_experience(0)
def test_camera_interceptor_actual_controls_and_native_axis_ownership(self):
from opendbc.car.gm.tests.test_camera_acc_pedal import params
from opendbc.car.gm.tests.test_cc_gateway_stock import pt_frames
from opendbc.car.gm import gmcan
safety = libsafety_py.libsafety
release = safety.set_safety_hooks(structs.CarParams.SafetyModel.allOutput, 0) != 0
for camera in (False, True):
for be in (False, True):
with self.subTest(camera=camera, be=be), OpenpilotPrefix(), \
patch.dict(os.environ, {'SIMULATION': '1', 'REPLAY': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
settings.put_bool('IsReleaseBranch', release, block=True)
factory = params(camera=camera, be=be, release=release)
selected = self.card(factory, settings)
cp, ci = selected.CP, selected.CI
self.assertEqual(cp.alternativeExperience, 32)
controls = Controls()
self.assertEqual(controls.CP.to_dict(), cp.to_dict())
safety.set_alternative_experience(32)
self.assertEqual(safety.set_safety_hooks(structs.CarParams.SafetyModel.gm, cp.safetyConfigs[0].safetyParam), 0)
safety.init_tests()
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
loopback = []
for tick in range(70):
now = 1_000_000_000 + tick * 10_000_000
ordinary = tick >= 42 and not release
packets = pt_frames(packer, counter=tick % 4, acc_cruise=2 if ordinary else 0)
packets = [packet for packet in packets if packet[0] not in (0xBE, 0xF1, 0x1E1)]
packets += [packer.make_can_msg('ECMAcceleratorPos' if be else 'EBCMBrakePedalPosition', 0, {}),
packer.make_can_msg('ASCMSteeringButton', 0, {'ACCButtons': 3 if tick == 40 else 1, 'RollingCounter': tick % 4})]
sensor = bytearray.fromhex('0279012a0000')
sensor[4] = tick % 16
sensor[5] = gmcan.pedal_crc(sensor)
packets.append((0x201, bytes(sensor), 0))
if camera:
packets += [packer.make_can_msg('ASCMLKASteeringCmd', 2, {'RollingCounter': tick % 4}),
packer.make_can_msg('AEBCmd', 2, {}),
packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {'ACCCruiseState': 2 if ordinary else 0})]
safety.set_timer(now // 1000)
for packet in packets:
self.assertTrue(native('rx', packet, now // 1000))
safety.safety_tick()
state = ci.update([(now, packets + loopback)])
selected.aol_card_intent.update(state, now_ns=now, standard_enabled=ordinary)
requested = (1 if selected.aol_card_intent.allowed_latch else 0) | (2 if ordinary else 0)
safety.set_aol_test_heartbeat(True)
safety.aol_set_host_request(requested)
safety.set_timer(now // 1000 + 1)
mask = safety.aol_get_permission_mask()
feed(controls, now, tick, active=ordinary, enabled=ordinary)
axis = messaging.new_message('aolAxisState', valid=True, logMonoTime=now)
axis.aolAxisState.qualified = axis.aolAxisState.nativeAcknowledged = True
axis.aolAxisState.desiredLateral = bool(requested & 1)
axis.aolAxisState.desiredLongitudinal = bool(requested & 2)
axis.aolAxisState.lateralActive = bool(mask & 1)
axis.aolAxisState.longitudinalActive = bool(mask & 2)
axis.aolAxisState.sessionId = 'camera-interceptor'
axis.aolAxisState.observedMonoTime, axis.aolAxisState.validUntilMonoTime = now, now + 30_000_000
receipt = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=now)
receipt.aolSafetyWire = encode_safety(SafetyState(1, True, now, now + 200_000_000,
int(structs.CarParams.SafetyModel.gm), cp.safetyConfigs[0].safetyParam,
bool(mask & 1), bool(mask & 2), bool(requested & 1), bool(requested & 2), 'panda', 'camera-interceptor'))
actual = messaging.new_message('carState', valid=True, logMonoTime=now)
actual.carState = state
controls.sm.update_msgs((now + 1_000) / 1e9, [axis.as_reader(), receipt.as_reader(), actual.as_reader()])
command, lateral_log = controls.state_control()
controls.publish(command, lateral_log)
if tick >= 42:
self.assertTrue(command.latActive,
(tick, mask, requested, state.canValid, safety.safety_config_valid(), selected.CP.safetyConfigs[0].safetyParam))
self.assertEqual(command.longActive, not release)
_, commands = ci.apply(command.as_reader(), now + 2)
loopback = [(addr, data, 128) for addr, data, _ in commands if addr == 0x180]
for packet in commands:
self.assertTrue(native('tx', packet, now // 1000 + 1), (tick, packet))
safety.set_alternative_experience(0)
def test_actual_card_forwards_shared_disarming_events(self):
class IntentUpdated(Exception):
pass
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
selected = self.card(factory_params(alpha=False), settings)
cs = structs.CarState(canValid=True, vEgo=20., gearShifter='drive')
cs.cruiseState.available = True
selected.aol_card_intent.update(cs)
self.assertTrue(selected.aol_card_intent.allowed_latch)
now = 1_000_000_000
events = messaging.new_message('onroadEvents', 1, valid=True, logMonoTime=now)
events.onroadEvents[0].name = 'controlsMismatch'
events.onroadEvents[0].immediateDisable = True
control = messaging.new_message('carControl', valid=True, logMonoTime=now)
selected.sm.update_msgs(now / 1e9, [events.as_reader(), control.as_reader()])
with patch('openpilot.selfdrive.car.card.messaging.drain_sock_raw', return_value=[b'fixture']), \
patch('openpilot.selfdrive.car.card.can_capnp_to_list', return_value=[]), \
patch('openpilot.selfdrive.car.card.time.monotonic_ns', return_value=now), \
patch('openpilot.selfdrive.car.card.REPLAY', False), \
patch.object(selected.CI, 'update', return_value=cs), \
patch.object(selected.RI, 'update', return_value=None), \
patch.object(selected.sm, 'update'), \
patch.object(selected, 'observe_distance_personality', side_effect=IntentUpdated):
with self.assertRaises(IntentUpdated):
selected.state_update()
self.assertFalse(selected.aol_card_intent.allowed_latch)
selected.aol_card_intent.update(cs, fault_active=False)
self.assertTrue(selected.aol_card_intent.allowed_latch)
def test_actual_controller_inactive_cruise_brake_and_regen_lateral_only(self):
safety = libsafety_py.libsafety
release = safety.set_safety_hooks(structs.CarParams.SafetyModel.allOutput, 0) != 0
cases = [bolt_params(BOLT_IDS[0]), configured('cc'), camera_params(alpha=False),
camera_params(alpha=True), removed_params(alpha=False), removed_params(alpha=True),
sdgm_params(alpha=False), sdgm_params(alpha=True)]
for variant in ('be', 'f1'):
cp = configured(variant)
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp)
cases.append(cp)
for cp in cases:
with self.subTest(identity=cp.carFingerprint, word=hex(cp.safetyConfigs[0].safetyParam)):
cp.alternativeExperience = 32
ci = CarInterface(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
safety.init_tests()
safety.set_alternative_experience(32)
safety.set_safety_hooks(structs.CarParams.SafetyModel.gm, cp.safetyConfigs[0].safetyParam)
safety.set_aol_test_heartbeat(True)
supported = not release or cp.safetyConfigs[0].safetyParam not in (20, 0x4007, 0x5007, 0xC151)
requested = []
for tick in range(18):
now = 1_000_000_000 + tick * 10_000_000
out, sources = feed_car(ci, packer, now, counter=tick % 4, active=False, brake=True, regen=True, eps_status=1)
sources = [source for source in sources if source[0] not in (0x1C4, 0xBE, 0xF1)]
sources.extend([packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 0}),
packer.make_can_msg('ECMAcceleratorPos', 0, {'BrakePedalPos': 12}),
packer.make_can_msg('EBCMBrakePedalPosition', 0, {'BrakePedalPosition': 6}),
packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {'ACCCruiseState': 0})])
out = ci.update([(now + 1, sources)])
for source in sources:
native('rx', source, now // 1000)
safety.safety_tick()
safety.aol_set_host_request(1)
permission = safety.aol_get_permission_mask()
cc = control(enabled=False, long_active=False)
cc.latActive = bool(permission & 1)
ci.CC.frame = tick
_, messages = ci.apply(cc.as_reader(), now + 2)
self.assertFalse(cc.longActive)
for message in messages:
if message[0] == 0x180:
requested.append(bool(message[1][0] & 8))
self.assertEqual(native('tx', message, now // 1000 + 1), supported)
self.assertTrue(out.canValid)
self.assertFalse(out.cruiseState.enabled)
self.assertTrue(out.brakePressed)
self.assertEqual(any(requested), supported)
def test_calibration_pause_retains_intent_and_recovers_without_new_main_edge(self):
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
from openpilot.starpilot.aol.tests.test_runtime import sample_car_state
from openpilot.selfdrive.selfdrived.state import StateMachine
from openpilot.selfdrive.selfdrived.events import Events, EventName, ET
from openpilot.starpilot.aol.runtime import AxisDecision
from openpilot.starpilot.aol.wire import encode_intent
for calibration_event in (EventName.calibrationInvalid, EventName.calibrationIncomplete, EventName.calibrationRecalibrating):
with self.subTest(calibration=calibration_event), OpenpilotPrefix(), patch.dict(
os.environ, {'SIMULATION': '1', 'REPLAY': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
selected = self.card(factory_params(alpha=False), settings)
controls = Controls()
sd = SelfdriveD.__new__(SelfdriveD)
sd.CP, sd.initialized, sd.aol_replay = selected.CP, True, True
sd.aol_session_id, sd.aol_axis_decision = 'calibration-test', AxisDecision()
sd.aol_authority_lost = False
sd.aol_transport_pause = TransportPause(sd.CP, sd.aol_session_id)
sd.aol_dm_lateral_inhibit, sd.aol_settings = False, selected.aol_settings
sd.nostalgia_paddle_cancel = False
sd.state_machine, sd.events = StateMachine(), Events()
sd.sm = messaging.SubMaster(['aolIntentWire', 'aolSafetyWire', 'modelV2',
'extrinsicsCalibration', 'driverMonitoringState', 'pandaStates'])
cs = structs.CarState(canValid=True, vEgo=20., gearShifter='drive')
for tick, (phase, expected) in enumerate((('unbuckled', True), ('unbuckled', True), ('healthy', True), ('invalid', False), ('recovered', True),
('held', True), ('unmapped_lkas', True), ('unmapped_main', True),
('release_lkas', True), ('main_off', False), ('fault_edge', False),
('recovered_again', True), ('main_off', False), ('new_main', True),
('temporary', False), ('healthy', True), ('reverse', False),
('healthy', True), ('model_stale', False), ('healthy', True))):
now = 1_000_000_000 + tick * 10_000_000
cs.cruiseState.available = phase != 'main_off'
cs.seatbeltUnlatched = phase == 'unbuckled'
cs.buttonEvents = []
if phase in ('unmapped_lkas', 'unmapped_main', 'release_lkas'):
cs.buttonEvents = [structs.CarState.ButtonEvent(
type='mainCruise' if phase == 'unmapped_main' else 'lkas', pressed=phase != 'release_lkas')]
cs.steerFaultTemporary = phase == 'temporary'
cs.gearShifter = 'reverse' if phase == 'reverse' else 'drive'
events = Events()
if cs.seatbeltUnlatched:
events.add(EventName.seatbeltNotLatched)
if phase == 'invalid':
events.add(calibration_event)
if phase == 'fault_edge':
events.add(EventName.overheat)
onroad = messaging.new_message('onroadEvents', 0, valid=True, logMonoTime=now)
onroad.onroadEvents = events.to_msg()
selected.sm.update_msgs(now / 1e9, [onroad.as_reader()])
selected.aol_card_intent.update(cs)
selected.observe_aol_calibration(cs, now, False)
self.assertEqual(selected.aol_card_intent.allowed_latch,
phase != 'main_off')
intent = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=now)
allowed, pause_lat, pause_long = selected.aol_card_intent.output(cs)
intent.aolIntentWire = encode_intent(IntentState('card', tick + 1, now, now, now + 30_000_000,
allowed, pause_lat, pause_long, True, True))
native = SafetyState(1, True, now, now + 200_000_000, int(structs.CarParams.SafetyModel.gm),
selected.CP.safetyConfigs[0].safetyParam, expected, False, expected, False,
'panda', 'calibration-test')
receipt = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=now)
receipt.aolSafetyWire = encode_safety(native)
model = messaging.new_message('modelV2', valid=phase != 'model_stale', logMonoTime=now)
calibration = messaging.new_message('extrinsicsCalibration', valid=True, logMonoTime=now)
calibration.extrinsicsCalibration.calStatus = 'uncalibrated' if phase == 'invalid' else 'calibrated'
monitoring = messaging.new_message('driverMonitoringState', valid=True, logMonoTime=now)
sd.sm.update_msgs(now / 1e9, [intent.as_reader(), receipt.as_reader(), model.as_reader(),
calibration.as_reader(), monitoring.as_reader()])
sd.aol_car_state_log_ns, sd.enabled, sd.active = now, False, False
sd.events = events
if cs.seatbeltUnlatched:
sd.events.add(EventName.seatbeltNotLatched)
self.assertTrue(sd.events.contains(ET.NO_ENTRY))
self.assertTrue(sd.events.contains(ET.SOFT_DISABLE))
with patch('openpilot.selfdrive.selfdrived.selfdrived.REPLAY', True), \
patch.object(sd, 'data_sample', side_effect=lambda sd=sd, cs=cs, now=now: sample_car_state(sd, cs, now)), patch.object(sd, 'update_events'), \
patch.object(sd, 'update_alerts'), patch.object(sd, 'update_conditional_mode'), \
patch.object(sd, 'publish_selfdriveState'):
sd.step()
self.assertEqual(sd.aol_axis_decision.lateral_active, expected, phase)
feed(controls, now, tick, active=False, enabled=False)
axis = messaging.new_message('aolAxisState', valid=True, logMonoTime=now)
axis.aolAxisState.qualified = axis.aolAxisState.nativeAcknowledged = True
axis.aolAxisState.desiredLateral = sd.aol_axis_decision.desired_lateral
axis.aolAxisState.lateralActive = sd.aol_axis_decision.lateral_active
axis.aolAxisState.sessionId = 'calibration-test'
axis.aolAxisState.observedMonoTime = now
axis.aolAxisState.validUntilMonoTime = now + 30_000_000
state = messaging.new_message('carState', valid=True, logMonoTime=now)
state.carState = cs
controls.sm.update_msgs((now + 1_000) / 1e9, [axis.as_reader(), receipt.as_reader(), state.as_reader()])
command, _ = controls.state_control()
self.assertEqual(command.latActive, expected, phase)
self.assertFalse(command.longActive)
def test_actual_selfdrived_gas_override_keeps_longitudinal_request(self):
self.exercise_actual_selfdrived_override()
def test_sustained_unbuckling_keeps_aol_through_standard_soft_disable(self):
self.exercise_actual_selfdrived_override(unlatched=True)
def exercise_actual_selfdrived_override(self, *, unlatched=False):
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
from openpilot.starpilot.aol.tests.test_runtime import sample_car_state
from openpilot.selfdrive.selfdrived.state import StateMachine
from openpilot.selfdrive.selfdrived.events import Events, EventName
from openpilot.starpilot.aol.runtime import AxisDecision
from openpilot.starpilot.aol.wire import encode_intent
from openpilot.starpilot.aol.intent import AolSettings
from openpilot.cereal import log
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'REPLAY': '1'}):
cp = configured('be')
cp.alternativeExperience = 32
now = 1_000_000_000
cs = structs.CarState(canValid=True, vEgo=20., gearShifter='drive', gasPressed=True)
cs.cruiseState.available = cs.cruiseState.enabled = True
sd = SelfdriveD.__new__(SelfdriveD)
sd.CP, sd.initialized, sd.aol_replay = cp, True, True
sd.enabled = sd.active = True
sd.aol_car_state_log_ns = now
sd.aol_session_id = 'gm-test'
sd.aol_authority_lost = False
sd.aol_transport_pause = TransportPause(sd.CP, sd.aol_session_id)
sd.aol_axis_decision = AxisDecision()
sd.aol_dm_lateral_inhibit = False
sd.aol_settings = AolSettings(True, 0., 0, 0, (0, 0, 0), (0, 0, 0))
sd.nostalgia_paddle_cancel = False
sd.state_machine = StateMachine()
sd.state_machine.state = log.SelfdriveState.OpenpilotState.enabled
sd.events = Events()
sd.events.add(EventName.gasPressedOverride)
sd.sm = messaging.SubMaster(['aolIntentWire', 'aolSafetyWire', 'modelV2',
'extrinsicsCalibration', 'driverMonitoringState', 'pandaStates'])
intent = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=now)
intent.aolIntentWire = encode_intent(IntentState('card', 1, now, now, now + 30_000_000,
True, False, False, True, True))
receipt = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=now)
receipt.aolSafetyWire = encode_safety(SafetyState(1, True, now, now + 200_000_000,
int(structs.CarParams.SafetyModel.gm), cp.safetyConfigs[0].safetyParam,
True, True, True, True, 'panda', 'gm-test'))
model = messaging.new_message('modelV2', valid=True, logMonoTime=now)
calibration = messaging.new_message('extrinsicsCalibration', valid=True, logMonoTime=now)
calibration.extrinsicsCalibration.calStatus = 'calibrated'
monitoring = messaging.new_message('driverMonitoringState', valid=True, logMonoTime=now)
controls = None
if unlatched:
params = Params()
params.put_bool('OpenpilotEnabledToggle', True, block=True)
params.put_bool('AlwaysOnLateral', True, block=True)
params.put('CarParams', cp.to_bytes(), block=True)
controls = Controls()
for tick in range(325 if unlatched else 1):
now = 1_000_000_000 + tick * 10_000_000
cs.gasPressed = not unlatched
cs.seatbeltUnlatched = unlatched and tick > 0
sd.events.clear()
if cs.seatbeltUnlatched:
sd.events.add(EventName.seatbeltNotLatched)
elif not unlatched:
sd.events.add(EventName.gasPressedOverride)
intent.aolIntentWire = encode_intent(IntentState('card', tick + 1, now, now, now + 30_000_000,
True, False, False, True, True))
receipt.aolSafetyWire = encode_safety(SafetyState(1, True, now, now + 200_000_000,
int(structs.CarParams.SafetyModel.gm), cp.safetyConfigs[0].safetyParam,
True, not cs.seatbeltUnlatched, True, not cs.seatbeltUnlatched, 'panda', 'gm-test'))
for event in (intent, receipt, model, calibration, monitoring):
event.logMonoTime = now
sd.aol_car_state_log_ns = now
sd.sm.update_msgs(now / 1e9, [intent.as_reader(), receipt.as_reader(), model.as_reader(),
calibration.as_reader(), monitoring.as_reader()])
with patch('openpilot.selfdrive.selfdrived.selfdrived.REPLAY', True), \
patch.object(sd, 'data_sample', side_effect=lambda sd=sd, cs=cs, now=now: sample_car_state(sd, cs, now)), patch.object(sd, 'update_events'), \
patch.object(sd, 'update_alerts'), patch.object(sd, 'update_conditional_mode'), \
patch.object(sd, 'publish_selfdriveState'):
sd.step()
self.assertTrue(sd.aol_axis_decision.lateral_active)
self.assertEqual(sd.aol_axis_decision.desired_longitudinal, not cs.seatbeltUnlatched)
if unlatched:
self.assertEqual(sd.enabled, tick <= 300)
feed(controls, now, tick, active=sd.active, enabled=sd.enabled)
axis = messaging.new_message('aolAxisState', valid=True, logMonoTime=now)
axis.aolAxisState.qualified = axis.aolAxisState.nativeAcknowledged = True
axis.aolAxisState.desiredLateral = axis.aolAxisState.lateralActive = True
axis.aolAxisState.desiredLongitudinal = axis.aolAxisState.longitudinalActive = not cs.seatbeltUnlatched
axis.aolAxisState.sessionId = 'gm-test'
axis.aolAxisState.observedMonoTime, axis.aolAxisState.validUntilMonoTime = now, now + 30_000_000
state = messaging.new_message('carState', valid=True, logMonoTime=now)
state.carState = cs
controls.sm.update_msgs((now + 1_000) / 1e9, [axis.as_reader(), receipt.as_reader(), state.as_reader()])
command, lateral_log = controls.state_control()
controls.publish(command, lateral_log)
self.assertTrue(command.latActive)
self.assertEqual(command.longActive, not cs.seatbeltUnlatched)
self.assertEqual(command.cruiseControl.cancel, cs.cruiseState.enabled and (not command.enabled or not cp.pcmCruise))
else:
self.assertTrue(sd.enabled)
def test_existing_vehicle_factories_preserve_intent_lifecycle(self):
from opendbc.car import gen_empty_fingerprint
from opendbc.car.honda.interface import CarInterface as HondaInterface
from opendbc.car.honda.values import CAR as HondaCars
from openpilot.starpilot.lateral.tests.test_lane_runtime import ioniq_candidate
from openpilot.starpilot.aol.intent import AolSettings, AolCardIntent
from openpilot.starpilot.aol.vehicle import create_intent
honda = HondaInterface.get_params(HondaCars.HONDA_ACCORD, gen_empty_fingerprint(), [], True, False, False)
_, hyundai = ioniq_candidate()
# Existing Card startup adds AOL bit 11 inside the qualified Ioniq namespace.
hyundai.safetyConfigs[-1].safetyParam |= 0x0800
settings = AolSettings(True, 0., 0, 0, (0, 0, 0), (0, 0, 0))
for cp in (honda, hyundai):
policy = policy_for(cp)
self.assertTrue(policy.intent_supported, (cp.carFingerprint, cp.safetyConfigs[-1].safetyParam))
selected = create_intent(cp, settings, policy)
from openpilot.starpilot.car.hyundai.aol import independent_cruise_buttons_supported
reference = AolCardIntent(settings, explicit_latch=policy.explicit_latch,
independent_cruise_buttons=cp.brand == 'hyundai' and independent_cruise_buttons_supported(cp))
self.assertIs(type(selected), AolCardIntent)
for main, permanent, temporary, gear in ((True, False, False, 'drive'),
(True, False, True, 'drive'),
(True, False, False, 'reverse'),
(True, True, False, 'drive'),
(True, False, False, 'drive'),
(False, False, False, 'drive'),
(True, False, False, 'drive')):
cs = structs.CarState(canValid=True, gearShifter=gear, steerFaultPermanent=permanent,
steerFaultTemporary=temporary)
cs.cruiseState.available = main
selected.update(cs, fault_active=permanent)
reference.update(cs, fault_active=permanent)
self.assertEqual(selected.__dict__.keys(), reference.__dict__.keys())
for key, value in vars(selected).items():
expected = vars(reference)[key]
self.assertEqual(vars(value) if hasattr(value, '__dict__') else value,
vars(expected) if hasattr(expected, '__dict__') else expected, key)
self.assertEqual(selected.output(cs), reference.output(cs))
def test_bolt_cancel_lateral_recovery(self):
for identity in (CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2018_2021,
CAR.CHEVROLET_BOLT_CC_2022_2023, CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL):
for removed in (False, True):
with self.subTest(identity=identity, removed=removed):
self.exercise_bolt_cancel_recovery(identity, removed)
def test_bolt_cancel_fault_rearm(self):
for critical in ('controlsMismatch', 'steerUnavailable', 'permanentEPS'):
with self.subTest(critical=critical):
self.exercise_bolt_cancel_recovery(CAR.CHEVROLET_BOLT_CC_2018_2021, False, critical=critical)
def test_bolt_communications_pause_preserves_intent(self):
for event in ('commIssueAvgFreq', 'commIssue'):
with self.subTest(event=event):
self.exercise_bolt_cancel_recovery(CAR.CHEVROLET_BOLT_CC_2018_2021, False, communications=event)
def test_bolt_communications_preserves_critical_rearm(self):
for critical in ('controlsMismatch', 'steerUnavailable', 'permanentEPS'):
with self.subTest(critical=critical):
self.exercise_bolt_cancel_recovery(CAR.CHEVROLET_BOLT_CC_2018_2021, False,
critical=critical, communications='commIssueAvgFreq')
def test_bolt_pedal_cancel_braking_and_physical_paddle_recovery(self):
for identity in (CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2018_2021,
CAR.CHEVROLET_BOLT_CC_2022_2023):
with self.subTest(identity=identity):
self.exercise_bolt_cancel_recovery(identity, False, pedal_scene=True)
def test_bolt_pedal_current_inventory_and_published_control_join(self):
exercise_bolt_cancel_recovery(self, CAR.CHEVROLET_BOLT_CC_2018_2021, False,
pedal_scene=True, inventory_publish=True)
def exercise_bolt_cancel_recovery(self, identity, removed, *, critical=None, communications: str | None = None, pedal_scene=False):
exercise_bolt_cancel_recovery(self, identity, removed, critical=critical,
communications=communications, pedal_scene=pedal_scene)
def test_volt_brake_lateral_recovery(self):
from dataclasses import replace
from openpilot.selfdrive.car.car_events import CarEvents
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
from openpilot.selfdrive.selfdrived.state import StateMachine
from openpilot.selfdrive.selfdrived.events import Events, EventName, ET
from openpilot.starpilot.aol.runtime import AxisDecision
from openpilot.starpilot.aol.wire import encode_intent, decode_intent
from openpilot.starpilot.nostalgia import aol_no_entry
from opendbc.car.gm.tests.test_ascm_intercept import params as ascm_params
from opendbc.car.gm.values import GMFlags
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'REPLAY': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
for key in ('AlwaysOnLateral', 'AlphaLongitudinalEnabled', 'GMAutoHold', 'VoltOnePedalMode'):
settings.put_bool(key, True, block=True)
settings.put('AolBrakePauseSpeedMps', 10., block=True)
selected = self.card(ascm_params(CAR.CHEVROLET_VOLT_ASCM, sascm=True, alpha=True, accelerator=True, radar=True), settings)
cp, ci = selected.CP, selected.CI
self.assertEqual(cp.safetyConfigs[0].safetyParam, 0xD114)
self.assertEqual(cp.alternativeExperience, 32)
self.assertFalse(cp.flags & GMFlags.PEDAL_LONG)
self.assertTrue(cp.openpilotLongitudinalControl)
self.assertFalse(cp.pcmCruise)
self.assertEqual(selected.aol_settings.pause_brake_mps, 10.)
controls = Controls()
self.assertEqual(controls.CP.to_dict(), cp.to_dict())
sd = SelfdriveD.__new__(SelfdriveD)
sd.CP, sd.initialized, sd.aol_replay = cp, True, True
sd.enabled = sd.active = False
sd.aol_session_id, sd.aol_axis_decision = 'volt-brake', AxisDecision()
sd.aol_authority_lost = False
sd.aol_transport_pause = TransportPause(sd.CP, sd.aol_session_id)
sd.aol_dm_lateral_inhibit, sd.aol_settings = False, selected.aol_settings
sd.nostalgia_paddle_cancel = False
sd.state_machine, sd.events = StateMachine(), Events()
sd.sm = messaging.SubMaster(['aolIntentWire', 'aolSafetyWire', 'modelV2',
'extrinsicsCalibration', 'driverMonitoringState'])
car_events = CarEvents(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
safety = libsafety_py.libsafety
safety.set_alternative_experience(32)
self.assertEqual(safety.set_safety_hooks(structs.CarParams.SafetyModel.gm, 0xD114), 0)
safety.init_tests()
resumed = paused = 0
previous_sd_onroad = None
ctx = {'identity': str(cp.carFingerprint), 'word': '0xd114', 'phase': 'prime'}
try:
# Warm all selected parser/native sources, with stock cruise inactive.
for warmup in range(20):
stamp = 800_000_000 + warmup * 10_000_000
_, frames = feed_car(SimpleNamespace(update=lambda _: None), packer, stamp,
counter=warmup % 4, active=False, speed=8., camera=True)
frames = [frame for frame in frames if frame[0] != 0x184]
frames.append(packer.make_can_msg('PSCMStatus', 0, {'LKATorqueDeliveredStatus': 1}))
frames.append(packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {'ACCCruiseState': 0}))
previous = ci.update([(stamp, frames)])
for frame in frames:
self.assertTrue(native('rx', frame, stamp // 1000), (frame, ctx))
safety.safety_tick()
self.assertTrue(previous.canValid, msg=ctx)
self.assertTrue(safety.safety_config_valid(), msg=ctx)
self.assertFalse(safety.get_controls_allowed(), msg=ctx)
for tick in range(146):
now = 1_000_000_000 + tick * 10_000_000
brake = any(start <= tick < start + 12 for start in (20, 60, 100))
fault = tick == 138
main = tick not in (140, 141)
ctx.update(phase='scenario', tick=tick, brake=brake, fault=fault, main=main)
_, packets = feed_car(SimpleNamespace(update=lambda _: None), packer, now,
counter=tick % 4, active=False, speed=8., camera=True)
packets = [frame for frame in packets if frame[0] not in (0xC9, 0x1C4, 0xBE, 0x184)]
packets += [packer.make_can_msg('PSCMStatus', 0, {'LKATorqueDeliveredStatus': 1}),
packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {'ACCCruiseState': 0}),
packer.make_can_msg('ECMEngineStatus', 0, {'CruiseMainOn': int(main)}),
packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 0}),
packer.make_can_msg('ECMAcceleratorPos', 0, {'BrakePedalPos': 30 if brake else 0})]
cs = ci.update([(now, packets)])
self.assertTrue(cs.canValid, msg=ctx)
self.assertEqual(cs.brakePressed, brake, msg=ctx)
self.assertFalse(cs.cruiseState.enabled, msg=ctx)
self.assertEqual(cs.cruiseState.available, main, msg=ctx)
for frame in packets:
self.assertTrue(native('rx', frame, now // 1000), (frame, ctx))
safety.safety_tick()
# The native test heartbeat isolates transport negotiation.
safety.set_aol_test_heartbeat(True)
events = car_events.update(cs, previous, structs.CarControl())
if cs.brakePressed and (not previous.brakePressed or not cs.standstill):
events.add(EventName.pedalPressed)
if fault:
events.add(EventName.controlsMismatch)
ctx['events'] = [int(event) for event in events.names]
self.assertEqual(EventName.pedalPressed in events.names, brake, msg=ctx)
self.assertEqual(events.contains(ET.IMMEDIATE_DISABLE), fault, msg=ctx)
onroad = messaging.new_message('onroadEvents', 0, valid=True, logMonoTime=now)
onroad.onroadEvents = events.to_msg()
card_onroad = onroad.as_reader() if fault or previous_sd_onroad is None else previous_sd_onroad
selected.sm.update_msgs(now / 1e9, [card_onroad])
disarm = selected.aol_disarming_fault(cs, int(card_onroad.logMonoTime), now)
self.assertEqual(disarm, tick in (136, 138, 139), msg=ctx)
selected.aol_card_intent.update(cs, fault_active=disarm, now_ns=now, standard_enabled=False)
expected_latch = main and not disarm
self.assertEqual(selected.aol_card_intent.allowed_latch, expected_latch, msg=ctx)
self.assertEqual(selected.aol_card_intent._main_cycle_required, disarm, msg=ctx)
allowed, pause_lat, pause_long = selected.aol_card_intent.output(cs)
intent_value = IntentState('card', tick + 1, now, now, now + 200_000_000,
allowed, pause_lat, pause_long, selected.aol_qualified,
selected.aol_card_intent.allowed_latch)
intent = decode_intent(encode_intent(intent_value))
self.assertIsNotNone(intent, msg=ctx)
self.assertEqual(intent.allowedLatch, expected_latch, msg=ctx)
self.assertFalse(intent.pauseLateral, msg=ctx)
self.assertFalse(intent.pauseLongitudinal, msg=ctx)
no_entry = aol_no_entry(events.names, cs, paddle_only_cancel=False, allow_below_engage_speed=True)
immediate_disable = events.contains(ET.IMMEDIATE_DISABLE)
wanted = decide_axes(native=None, standard_lateral=False, standard_longitudinal=False,
intent=intent, car_state=cs, initialized=True, model_ready=True,
no_entry=no_entry, immediate_disable=immediate_disable,
dm_lockout=False, pause_brake_mps=10.)
expected_lat = not brake and expected_latch
self.assertEqual(wanted.desired_lateral, expected_lat, msg=ctx)
self.assertFalse(wanted.desired_longitudinal, msg=ctx)
safety.aol_set_host_request(int(wanted.desired_lateral))
mask = safety.aol_get_permission_mask()
ctx['native_permission_mask'] = int(mask)
self.assertEqual(mask, int(expected_lat), msg=ctx)
self.assertFalse(safety.get_controls_allowed(), msg=ctx)
receipt_state = SafetyState(1, True, now, now + 200_000_000, int(structs.CarParams.SafetyModel.gm),
0xD114, bool(mask & 1), False, wanted.desired_lateral, False, 'panda', 'volt-brake')
receipt = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=now)
transport = replace(receipt_state, validUntilMonoTime=now - 1) if tick == 135 else receipt_state
receipt.aolSafetyWire = encode_safety(transport)
intent_message = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=now)
intent_message.aolIntentWire = encode_intent(intent_value)
model = messaging.new_message('modelV2', valid=True, logMonoTime=now)
calibration = messaging.new_message('extrinsicsCalibration', valid=True, logMonoTime=now)
calibration.extrinsicsCalibration.calStatus = 'calibrated'
monitoring = messaging.new_message('driverMonitoringState', valid=True, logMonoTime=now)
sd.sm.update_msgs(now / 1e9, [intent_message.as_reader(), receipt.as_reader(), model.as_reader(),
calibration.as_reader(), monitoring.as_reader()])
sd.aol_car_state_log_ns = now
sd.conditional_car_state_valid = cs.canValid and not cs.canTimeout
sd.events = events
with patch('openpilot.selfdrive.selfdrived.selfdrived.REPLAY', True), \
patch.object(sd, 'data_sample', return_value=cs), patch.object(sd, 'update_events'), \
patch.object(sd, 'update_alerts'), patch.object(sd, 'update_conditional_mode'), \
patch.object(sd, 'publish_selfdriveState'):
sd.step()
actual_lat = expected_lat and tick != 135
actual_onroad = messaging.new_message('onroadEvents', 0, valid=True, logMonoTime=now)
actual_onroad.onroadEvents = sd.events.to_msg()
previous_sd_onroad = actual_onroad.as_reader()
self.assertFalse(sd.enabled or sd.active, msg=ctx)
self.assertEqual(sd.aol_axis_decision.lateral_active, actual_lat, msg=ctx)
self.assertFalse(sd.aol_axis_decision.longitudinal_active, msg=ctx)
if tick == 135:
self.assertTrue(sd.aol_authority_lost, msg=ctx)
self.assertIn(EventName.controlsMismatch, sd.events.names, msg=ctx)
self.assertFalse(sd.aol_axis_decision.native_acknowledged, msg=ctx)
if tick == 136:
self.assertFalse(sd.aol_axis_decision.lateral_active, msg=ctx)
self.assertFalse(selected.aol_card_intent.allowed_latch, msg=ctx)
if tick == 137:
self.assertTrue(selected.aol_card_intent.allowed_latch, msg=ctx)
self.assertTrue(sd.aol_axis_decision.lateral_active, msg=ctx)
if tick == 142:
self.assertTrue(sd.aol_axis_decision.lateral_active, msg=ctx)
self.assertFalse(selected.aol_card_intent._main_cycle_required, msg=ctx)
feed(controls, now, tick, active=sd.active, enabled=sd.enabled)
axis = messaging.new_message('aolAxisState', valid=True, logMonoTime=now)
axis.aolAxisState.qualified = True
axis.aolAxisState.nativeAcknowledged = sd.aol_axis_decision.native_acknowledged
axis.aolAxisState.desiredLateral = sd.aol_axis_decision.desired_lateral
axis.aolAxisState.desiredLongitudinal = sd.aol_axis_decision.desired_longitudinal
axis.aolAxisState.lateralActive = sd.aol_axis_decision.lateral_active
axis.aolAxisState.longitudinalActive = sd.aol_axis_decision.longitudinal_active
axis.aolAxisState.sessionId = 'volt-brake'
axis.aolAxisState.observedMonoTime = now
axis.aolAxisState.validUntilMonoTime = now + 30_000_000
state = messaging.new_message('carState', valid=True, logMonoTime=now)
state.carState = cs
controls.sm.update_msgs((now + 1_000) / 1e9, [axis.as_reader(), receipt.as_reader(), state.as_reader()])
command, _ = controls.state_control()
self.assertEqual(command.latActive, actual_lat, msg=ctx)
self.assertFalse(command.longActive, msg=ctx)
command.actuators.torque = .02 if command.latActive else 0.
_, messages = ci.apply(command.as_reader(), now + 2)
for frame in messages:
self.assertTrue(native('tx', frame, now // 1000 + 1), (frame, ctx))
if brake:
paused += 1
if tick in (32, 72, 112):
self.assertTrue(command.latActive, msg=ctx)
resumed += 1
if tick == 134:
# These are policy-unit boundaries; the three cycles use real 8m/s CAN.
probe = cs.as_reader().as_builder()
probe.brakePressed = True
for speed in (9.99, 10., 10.01):
probe.vEgo = speed
self.assertEqual(decide_axes(native=receipt_state, standard_lateral=False, standard_longitudinal=False,
intent=intent, car_state=probe, initialized=True, model_ready=True,
no_entry=no_entry, immediate_disable=immediate_disable,
dm_lockout=False, pause_brake_mps=10.).desired_lateral,
speed >= 10., (speed, ctx))
previous = cs
self.assertEqual(resumed, 3)
self.assertEqual(paused, 36)
finally:
safety.set_alternative_experience(0)
selected.vehicle_startup.close()
def test_volt_ascm_transient_acc_real_parser_card_controls_native(self):
self.exercise_volt_ascm_acc_intent()
def test_volt_ascm_mixed_critical_acc_fault_requires_main_cycle(self):
for critical in ('controlsMismatch', 'steerUnavailable', 'permanentEPS'):
with self.subTest(critical=critical):
self.exercise_volt_ascm_acc_intent(critical=critical)
def exercise_volt_ascm_acc_intent(self, *, critical=None):
from openpilot.cereal import log
from openpilot.selfdrive.car.car_events import CarEvents
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
from openpilot.selfdrive.selfdrived.state import StateMachine
from openpilot.selfdrive.selfdrived.events import Events, EventName, ET
from openpilot.starpilot.aol.runtime import AxisDecision
from openpilot.starpilot.aol.wire import encode_intent
from opendbc.can import CANParser
from opendbc.car.gm.bolt_cc import button_bytes
from opendbc.car.gm.values import AccState, CruiseButtons, GMFlags
from opendbc.car.gm.tests.test_ascm_intercept import params as ascm_params
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'REPLAY': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
for key in ('AlwaysOnLateral', 'AlphaLongitudinalEnabled', 'GMAutoHold', 'VoltOnePedalMode'):
settings.put_bool(key, True, block=True)
factory = ascm_params(CAR.CHEVROLET_VOLT_ASCM, sascm=True, alpha=True, accelerator=True, radar=True)
selected = self.card(factory, settings)
cp, ci = selected.CP, selected.CI
self.assertEqual(cp.safetyConfigs[0].safetyParam, 0xD114)
self.assertEqual(cp.alternativeExperience, 32)
self.assertTrue(cp.openpilotLongitudinalControl)
self.assertFalse(cp.pcmCruise)
self.assertFalse(cp.flags & GMFlags.PEDAL_LONG)
controls = Controls()
self.assertEqual(controls.CP.to_dict(), cp.to_dict())
sd = SelfdriveD.__new__(SelfdriveD)
sd.CP, sd.initialized, sd.aol_replay = cp, True, True
sd.enabled = sd.active = False
sd.aol_session_id, sd.aol_axis_decision = 'ascm-acc-fault', AxisDecision()
sd.aol_authority_lost = False
sd.aol_transport_pause = TransportPause(sd.CP, sd.aol_session_id)
sd.aol_dm_lateral_inhibit, sd.aol_settings = False, selected.aol_settings
sd.nostalgia_paddle_cancel = False
sd.state_machine, sd.events = StateMachine(), Events()
sd.state_machine.state = log.SelfdriveState.OpenpilotState.enabled
sd.sm = messaging.SubMaster(['aolIntentWire', 'aolSafetyWire', 'modelV2',
'extrinsicsCalibration', 'driverMonitoringState'])
car_events = CarEvents(cp)
previous = structs.CarState()
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
output = CANParser(DBC[cp.carFingerprint][Bus.pt], [('ASCMGasRegenCmd', float('nan'))], 0)
brake_output = CANParser(DBC[cp.carFingerprint][Bus.chassis], [('EBCMFrictionBrakeCmd', float('nan'))], 2)
neutral_gas_seen = neutral_brake_seen = False
safety = libsafety_py.libsafety
safety.set_alternative_experience(32)
self.assertEqual(safety.set_safety_hooks(structs.CarParams.SafetyModel.gm, 0xD114), 0)
safety.init_tests()
try:
# The recorded window is already engaged: precede it with real physical SET release.
for offset, counter, button in ((-20_000_000, 2, CruiseButtons.DECEL_SET),
(-10_000_000, 3, CruiseButtons.UNPRESS)):
prime_now = 102_289_000_000 + offset
_, prime_packets = feed_car(SimpleNamespace(update=lambda _: None), packer, prime_now,
counter=counter, active=True, speed=20., camera=True)
prime_packets = [packet for packet in prime_packets if packet[0] not in (0x1C4, 0x1E1)]
prime_packets += [packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': AccState.ACTIVE}),
(0x1E1, button_bytes(button, counter), 0)]
previous = ci.update([(prime_now, prime_packets)])
for packet in prime_packets:
self.assertTrue(native('rx', packet, prime_now // 1000), packet)
safety.safety_tick()
self.assertTrue(safety.get_controls_allowed())
# Fault begins102.389s and clears102.469s: eight100Hz samples, no button/MAIN edge.
for tick in range(36):
now = 102_289_000_000 + tick * 10_000_000
fault = 10 <= tick < 18
main = tick not in (26, 27)
ordinary = tick < 10
_, packets = feed_car(SimpleNamespace(update=lambda _: None), packer, now,
counter=tick % 4, active=ordinary, speed=20., camera=True)
packets = [packet for packet in packets if packet[0] not in (0x1C4, 0x3D1, 0x184)]
packets += [packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': AccState.FAULTED if fault else AccState.ACTIVE if ordinary else 0}),
packer.make_can_msg('ECMEngineStatus', 0, {'CruiseMainOn': int(main)}),
packer.make_can_msg('PSCMStatus', 0, {'LKATorqueDeliveredStatus': 3 if critical == 'permanentEPS' and fault else 2 if tick == 20 else 1}),
packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {'ACCCruiseState': 0})]
self.assertFalse(any(address == 0x201 for address, _, _ in packets))
cs = ci.update([(now, packets)])
self.assertTrue(cs.canValid, tick)
self.assertEqual(cs.accFaulted, fault)
self.assertEqual(cs.cruiseState.available, main)
for packet in packets:
self.assertTrue(native('rx', packet, now // 1000), (tick, packet))
safety.safety_tick()
safety.set_aol_test_heartbeat(True)
events = car_events.update(cs, previous, structs.CarControl())
if fault and critical in ('controlsMismatch', 'steerUnavailable'):
events.add(getattr(EventName, critical))
self.assertEqual(EventName.accFaulted in events.names, fault)
if fault:
self.assertTrue(events.contains(ET.IMMEDIATE_DISABLE))
onroad = messaging.new_message('onroadEvents', 0, valid=True, logMonoTime=now)
onroad.onroadEvents = events.to_msg()
selected.sm.update_msgs(now / 1e9, [onroad.as_reader()])
disarm = selected.aol_disarming_fault(cs, now, now)
self.assertEqual(disarm, bool(fault and critical), (tick, critical))
selected.aol_card_intent.update(cs, fault_active=disarm, now_ns=now, standard_enabled=ordinary)
expected_latch = main and not disarm
self.assertEqual(selected.aol_card_intent.allowed_latch, expected_latch, (tick, critical))
allowed, pause_lat, pause_long = selected.aol_card_intent.output(cs)
intent_state = IntentState('card', tick + 1, now, now, now + 30_000_000,
allowed, pause_lat, pause_long, True, True)
desired = decide_axes(standard_lateral=ordinary, standard_longitudinal=ordinary,
intent=intent_state, native=None, car_state=cs, initialized=True,
model_ready=True, no_entry=events.contains(ET.NO_ENTRY),
immediate_disable=events.contains(ET.IMMEDIATE_DISABLE),
dm_lockout=False, pause_brake_mps=5.)
safety.aol_set_host_request(int(desired.desired_lateral) | (int(desired.desired_longitudinal) << 1))
mask = safety.aol_get_permission_mask()
receipt_state = SafetyState(1, True, now, now + 200_000_000,
int(structs.CarParams.SafetyModel.gm), 0xD114,
bool(mask & 1), bool(mask & 2), desired.desired_lateral,
desired.desired_longitudinal, 'panda', 'ascm-acc-fault')
intent = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=now)
intent.aolIntentWire = encode_intent(intent_state)
receipt = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=now)
receipt.aolSafetyWire = encode_safety(receipt_state)
model = messaging.new_message('modelV2', valid=True, logMonoTime=now)
calibration = messaging.new_message('extrinsicsCalibration', valid=True, logMonoTime=now)
calibration.extrinsicsCalibration.calStatus = 'calibrated'
monitoring = messaging.new_message('driverMonitoringState', valid=True, logMonoTime=now)
sd.sm.update_msgs(now / 1e9, [intent.as_reader(), receipt.as_reader(), model.as_reader(),
calibration.as_reader(), monitoring.as_reader()])
sd.aol_car_state_log_ns = now
sd.conditional_car_state_valid = cs.canValid and not cs.canTimeout
sd.events = events
with patch('openpilot.selfdrive.selfdrived.selfdrived.REPLAY', True), \
patch.object(sd, 'data_sample', return_value=cs), patch.object(sd, 'update_events'), \
patch.object(sd, 'update_alerts'), patch.object(sd, 'update_conditional_mode'), \
patch.object(sd, 'publish_selfdriveState'):
sd.step()
expected_lat = expected_latch and not fault and tick != 20
self.assertEqual(sd.aol_axis_decision.lateral_active, expected_lat, (tick, critical))
self.assertEqual(sd.aol_axis_decision.longitudinal_active, ordinary, (tick, critical))
if tick >= 10:
self.assertFalse(sd.enabled) # Clearing ACC fault never silently resumes longitudinal.
feed(controls, now, tick, active=sd.active, enabled=sd.enabled)
axis = messaging.new_message('aolAxisState', valid=tick != 23, logMonoTime=now)
axis.aolAxisState.qualified = axis.aolAxisState.nativeAcknowledged = True
axis.aolAxisState.desiredLateral = sd.aol_axis_decision.desired_lateral
axis.aolAxisState.desiredLongitudinal = sd.aol_axis_decision.desired_longitudinal
axis.aolAxisState.lateralActive = sd.aol_axis_decision.lateral_active
axis.aolAxisState.longitudinalActive = sd.aol_axis_decision.longitudinal_active
axis.aolAxisState.sessionId = 'ascm-acc-fault'
axis.aolAxisState.observedMonoTime = now
axis.aolAxisState.validUntilMonoTime = now + 30_000_000
state = messaging.new_message('carState', valid=True, logMonoTime=now)
state.carState = cs
controls.sm.update_msgs((now + 1_000) / 1e9, [axis.as_reader(), receipt.as_reader(), state.as_reader()])
command, _ = controls.state_control()
self.assertEqual(command.latActive, expected_lat and tick != 23, (tick, critical))
self.assertEqual(command.longActive, ordinary, (tick, critical))
command.actuators.torque = .02 if command.latActive else 0.
_, messages = ci.apply(command.as_reader(), now + 2)
output.update([(now, messages)])
brake_output.update([(now, messages)])
if fault:
if any(address == 0x2CB for address, _, _ in messages):
self.assertEqual(output.vl['ASCMGasRegenCmd']['GasRegenCmd'], -650.)
self.assertFalse(output.vl['ASCMGasRegenCmd']['GasRegenCmdActive'])
neutral_gas_seen = True
if any(address == 0x315 for address, _, _ in messages):
self.assertEqual(brake_output.vl['EBCMFrictionBrakeCmd']['FrictionBrakeCmd'], 0)
neutral_brake_seen = True
for message in messages:
self.assertTrue(native('tx', message, now // 1000 + 1), (tick, message))
previous = cs
self.assertTrue(neutral_gas_seen)
self.assertTrue(neutral_brake_seen)
stale_now = now + 2_000_000_000
for _ in range(10):
stale_now += 10_000_000
stale_cs = ci.update([(stale_now, [])])
self.assertFalse(stale_cs.canValid)
safety.set_timer(stale_now // 1000)
safety.safety_tick()
stale_intent = IntentState('card', 100, stale_now, stale_now, stale_now + 30_000_000,
selected.aol_card_intent.allowed_latch, False, False, True, True)
stale_decision = decide_axes(standard_lateral=False, standard_longitudinal=False,
intent=stale_intent, native=receipt_state, car_state=stale_cs,
initialized=True, model_ready=True, no_entry=False,
immediate_disable=False, dm_lockout=False, pause_brake_mps=5.)
self.assertFalse(stale_decision.lateral_active)
self.assertFalse(stale_decision.longitudinal_active)
safety.aol_set_host_request(0)
feed(controls, stale_now, 100, active=False, enabled=False, can_valid=False, can_timeout=True)
# Last axis/receipt are deliberately not restamped: their exact bytes have expired.
stale_state = messaging.new_message('carState', valid=False, logMonoTime=stale_now)
stale_state.carState = stale_cs
controls.sm.update_msgs(stale_now / 1e9, [stale_state.as_reader()])
withdrawn, _ = controls.state_control()
self.assertFalse(withdrawn.latActive)
self.assertFalse(withdrawn.longActive)
_, neutral_messages = ci.apply(withdrawn.as_reader(), stale_now + 2)
for message in neutral_messages:
self.assertTrue(native('tx', message, stale_now // 1000 + 1), message)
finally:
safety.set_alternative_experience(0)
selected.vehicle_startup.close()
def test_transient_acc_intent_exception_stays_inside_nonpedal_ascm_long(self):
from openpilot.selfdrive.selfdrived.events import Events, EventName
from opendbc.car.gm.tests.test_volt_transitions import volt_ascm_pedal_params
cases = (ordinary_params(CAR.CHEVROLET_VOLT_ASCM, alpha=False, sascm=True, accelerator=True, radar=True),
volt_ascm_pedal_params(be=True, radar=True, sascm=True, alpha=True),
ordinary_params(CAR.GMC_ACADIA_ASCM, alpha=True, sascm=True, accelerator=True, radar=True))
for factory in cases:
with self.subTest(identity=factory.carFingerprint, flags=factory.flags), OpenpilotPrefix(), \
patch.dict(os.environ, {'SIMULATION': '1', 'REPLAY': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
selected = self.card(factory, settings)
try:
cs = structs.CarState(canValid=True, gearShifter='drive', vEgo=20., accFaulted=True)
cs.cruiseState.available = True
events = Events()
events.add(EventName.accFaulted)
onroad = messaging.new_message('onroadEvents', 0, valid=True, logMonoTime=1_000_000_000)
onroad.onroadEvents = events.to_msg()
selected.sm.update_msgs(1., [onroad.as_reader()])
self.assertTrue(selected.aol_disarming_fault(cs, 1_000_000_000, 1_000_000_000))
selected.aol_card_intent.update(cs, fault_active=True)
cs.accFaulted = False
selected.aol_card_intent.update(cs, fault_active=False)
self.assertTrue(selected.aol_card_intent.allowed_latch)
finally:
selected.vehicle_startup.close()
def test_sdgm_optional_saved_selection_actual_card_controls_cap(self):
from opendbc.car.gm.values import CarControllerParams, is_gm_auto_hold, is_volt_one_pedal, volt_sdgm_accepted_accel_max
from openpilot.starpilot.saved_source import read_saved
for c9 in (False, True):
for held, one_pedal, word in ((True, False, 0x5487 if c9 else 0x5087),
(False, True, 0xD108 if c9 else 0xD107),
(True, True, 0xD118 if c9 else 0xD117)):
with self.subTest(word=hex(word)), OpenpilotPrefix(), \
patch.dict(os.environ, {'SIMULATION': '1', 'REPLAY': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
for key, value in (('AlwaysOnLateral', True), ('AlphaLongitudinalEnabled', True),
('DisableOpenpilotLongitudinal', False), ('SafeMode', False),
('GMAutoHold', held), ('VoltOnePedalMode', one_pedal)):
settings.put_bool(key, value, block=True)
saved = {key: read_saved(settings, key, 8) for key in ('GMAutoHold', 'VoltOnePedalMode')}
selected = self.card(sdgm_params(brake_c9=c9), settings)
try:
cp = selected.CP
self.assertEqual(cp.safetyConfigs[0].safetyParam, word)
self.assertTrue(cp.openpilotLongitudinalControl)
self.assertFalse(cp.pcmCruise)
self.assertEqual(cp.alternativeExperience, 32)
self.assertTrue(is_gm_auto_hold(cp))
self.assertEqual(is_volt_one_pedal(cp), one_pedal)
self.assertEqual(selected.CI.CC.gm_auto_hold, held)
self.assertEqual(selected.CI.CC.volt_one_pedal, one_pedal)
self.assertEqual(CarControllerParams(cp).MAX_GAS, 2041)
self.assertEqual(selected.CI.CC.params.MAX_GAS, 2041)
controls = Controls()
self.assertEqual(controls.CP.to_dict(), cp.to_dict())
upper = volt_sdgm_accepted_accel_max(cp, 20.)
limits = controls.CI.get_pid_accel_limits(cp, 20., 20.)
self.assertEqual(limits[1], upper)
cs = structs.CarState(vEgo=20., canValid=True, gearShifter='drive')
controls.LoC.update(True, cs, upper + 10., False, limits)
self.assertEqual(controls.LoC.pid.pos_limit, upper)
self.assertLessEqual(controls.LoC.last_output_accel, upper)
self.assertEqual({key: read_saved(settings, key, 8) for key in saved}, saved)
finally:
selected.vehicle_startup.close()