Files
StarPilot/openpilot/starpilot/tests/test_bolt_disable_active_pedal.py
T
2026-10-24 21:30:33 -05:00

293 lines
17 KiB
Python

import unittest
from unittest.mock import patch
from opendbc.can import CANPacker
from opendbc.car import Bus
from opendbc.car.gm.interface import CarInterface
from opendbc.car.gm.tests.test_bolt_cc import params, feed, control, setup, native
from opendbc.car.gm.tests.test_bolt_pedal import TestBoltPedalMessages
from opendbc.car.gm.bolt_cc import button_bytes
from opendbc.car.gm.values import CAR, DBC, is_bolt_pedal_profile
from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences, bolt_disable_supported
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)
class TestBoltDisableActivePedal(unittest.TestCase):
def test_exact_startup_authority_reduction_and_siblings(self):
for identity in IDS:
for alpha in (False, True):
cp = params(identity, alpha=alpha, present=True, pedal=True)
self.assertTrue(is_bolt_pedal_profile(cp))
word, flags = cp.safetyConfigs[0].safetyParam, cp.flags
self.assertTrue(bolt_disable_supported(cp))
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp, fingerprints={2: {0x180: 4}})
self.assertFalse(cp.openpilotLongitudinalControl)
self.assertTrue(cp.pcmCruise)
self.assertTrue(is_bolt_pedal_profile(cp, stock_only=True))
self.assertTrue(bolt_disable_supported(cp))
self.assertEqual((cp.safetyConfigs[0].safetyParam, cp.flags),
(5 if identity == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL else word, flags))
VehicleStartupPreferences(disable_bolt_long=True).finalize(cp)
VehicleStartupPreferences().prepare(cp, fingerprints={2: {0x180: 4}})
self.assertFalse(cp.openpilotLongitudinalControl)
for identity in (CAR.CHEVROLET_BOLT_EUV, CAR.CHEVROLET_BOLT_ACC_2022_2023):
cp = params(identity, alpha=True)
before = cp.to_dict()
self.assertFalse(bolt_disable_supported(cp))
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp, fingerprints={2: {0x180: 4}})
self.assertEqual(cp.to_dict(), before)
def test_actual_parser_stock_cruise_no_takeover_and_native_steering(self):
for identity in IDS:
for alpha in (False, True):
cp = params(identity, alpha=alpha, present=True, pedal=True)
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp, fingerprints={2: {0x180: 4}})
ci = CarInterface(cp)
packer = CANPacker(DBC[identity][Bus.pt])
setup(cp)
original = ci.update
for frame in range(16):
now = 1_000_000_000 + frame * 10_000_000
sensor = TestBoltPedalMessages.sensor(packer, 0, frame % 16)
extra = [sensor]
if identity == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL:
extra += [packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 1}),
packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {'ACCCruiseState': 2, 'ACCCmdActive': 1})]
def update(packets, original=original, extra=extra):
return original([(stamp, [*messages, *extra]) for stamp, messages in packets])
with patch.object(ci, 'update', side_effect=update):
out, messages = feed(ci, packer, now, counter=frame % 4)
self.assertTrue(out.canValid, (identity, alpha, frame, is_bolt_pedal_profile(ci.CP, stock_only=True), cp.pcmCruise))
self.assertTrue(out.cruiseState.enabled)
self.assertFalse(out.cruiseState.nonAdaptive)
if frame == 0:
from openpilot.selfdrive.car.car_events import CarEvents, EventName
from opendbc.car import structs
events = CarEvents(cp).update(out, structs.CarState(), control())
self.assertNotIn(EventName.wrongCruiseMode, events.names)
self.assertIn(EventName.pcmEnable, events.names)
for message in [*messages, *extra]:
native('rx', message, now // 1000)
if frame == 0:
native('rx', (0x1E1, button_bytes(3, 1), 0), now // 1000 + 1)
native('rx', (0x1E1, button_bytes(1, 2), 0), now // 1000 + 2)
ci.CC.frame = frame
# Even an erroneous active demand cannot acquire disabled startup ownership.
cc = control()
_, commands = ci.apply(cc.as_reader(), now + 3_000)
self.assertFalse(any(m[0] in (0x200, 0x315, 0xBD, 0x1F5, 0x1E1, 0x370, 0x3D1) for m in commands))
for message in commands:
if message[0] == 0x180:
self.assertTrue(native('tx', message, now // 1000 + 3))
def test_actual_card_before_constructor_and_parked_owner(self):
import os
from types import SimpleNamespace
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.car.card import Car
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
for identity in IDS:
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1'}):
settings = Params()
settings.put_bool('OpenpilotEnabledToggle', True, block=True)
settings.put_bool('DisableOpenpilotLongitudinal', True, block=True)
observed = []
def get_car(*args, pre_create_hook, identity=identity, observed=observed, **kwargs):
cp = params(identity, present=True, pedal=True)
original_word = cp.safetyConfigs[0].safetyParam
cp = pre_create_hook(cp, identity, {2: {0x180: 4}}, [])
self.assertTrue(is_bolt_pedal_profile(cp, stock_only=True))
self.assertEqual(cp.safetyConfigs[0].safetyParam,
5 if identity == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL else original_word)
ci = CarInterface(cp)
observed.append(ci.CP.to_dict())
return ci
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):
card = Car()
self.assertEqual(len(observed), 1)
self.assertFalse(card.CP.openpilotLongitudinalControl)
self.assertTrue(card.CP.pcmCruise)
owner = FeatureSettingsOwner(settings, lambda group: True,
vehicle_fingerprint=lambda identity=identity: identity, vehicle_params=lambda card=card: card.CP)
self.assertIsNotNone(owner._bolt_disable_capability())
before = card.CP.to_dict()
settings.put_bool('DisableOpenpilotLongitudinal', False, block=True)
self.assertEqual(card.CP.to_dict(), before)
def test_parked_saved_owner_no_live_mutation(self):
import tempfile
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
from openpilot.starpilot.ui.feature_settings_state import FeatureSettingsRequest
for identity in IDS:
# A private root isolates native Params locking from parallel host tests.
with OpenpilotPrefix(), tempfile.TemporaryDirectory() as params_root:
settings = Params(params_root)
cp = params(identity, present=True, pedal=True)
allowed = [False]
owner = FeatureSettingsOwner(settings, lambda group, allowed=allowed: allowed[0],
vehicle_fingerprint=lambda identity=identity: identity, vehicle_params=lambda cp=cp: cp)
request = FeatureSettingsRequest('DisableOpenpilotLongitudinal', None, 'On', confirmation=True,
vehicle_fingerprint=identity, capability=owner._bolt_disable_capability())
self.assertFalse(owner.apply(request))
allowed[0] = True
view = owner.snapshot('vehicle', parked=True, system_long=True, lateral_context=True, metric=False)
row = next(row for row in view.rows if row.key == 'DisableOpenpilotLongitudinal')
self.assertTrue(row.available)
before = cp.to_dict()
self.assertTrue(owner.apply(request))
self.assertFalse(owner.apply(request))
self.assertEqual(cp.to_dict(), before)
def test_disabled_acc_native_stock_friction_forwarding(self):
from opendbc.safety.tests.libsafety import libsafety_py
cp = params(CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, present=True, pedal=True)
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp, fingerprints={2: {0x180: 4}})
setup(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
active = packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 1})
native('rx', active, 1_000_000)
self.assertEqual(libsafety_py.libsafety.safety_fwd_hook(2, 0x315), 0)
inactive = packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 0})
native('rx', inactive, 1_010_000)
self.assertEqual(libsafety_py.libsafety.safety_fwd_hook(2, 0x315), 0)
def test_missing_camera_denial_keeps_recovery_toggle_and_next_startup_off(self):
import os
from types import SimpleNamespace
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.car.card import Car
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
from openpilot.starpilot.ui.feature_settings_state import FeatureSettingsRequest
from opendbc.car import structs
from opendbc.car.gm.values import is_bolt_pedal_stock_denied
identity = CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL
for fingerprint in ({}, {2: {0x180: 3}}, {0: {0x180: 4}}):
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1'}):
settings = Params()
settings.put_bool('OpenpilotEnabledToggle', True, block=True)
settings.put_bool('DisableOpenpilotLongitudinal', True, block=True)
def get_car(*args, pre_create_hook, fingerprint=fingerprint, **kwargs):
cp = params(identity, present=True, pedal=True)
cp = pre_create_hook(cp, identity, fingerprint, [])
self.assertTrue(is_bolt_pedal_stock_denied(cp))
return CarInterface(cp)
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):
card = Car()
# Params destruction joins Card's asynchronous startup cache writes before the parked save.
card.params = Params()
self.assertTrue(is_bolt_pedal_stock_denied(card.CP))
self.assertEqual(card.CP.safetyConfigs[0].safetyModel, structs.CarParams.SafetyModel.noOutput)
owner = FeatureSettingsOwner(settings, lambda group: True,
vehicle_fingerprint=lambda: identity, vehicle_params=lambda card=card: card.CP)
view = owner.snapshot('vehicle', parked=True, system_long=True, lateral_context=True, metric=False)
row = next(row for row in view.rows if row.key == 'DisableOpenpilotLongitudinal')
self.assertTrue(row.available)
self.assertIn('Factory camera not detected', row.reason)
request = FeatureSettingsRequest('DisableOpenpilotLongitudinal', b'1', 'Off', confirmation=True,
vehicle_fingerprint=identity, capability=owner._bolt_disable_capability())
import fcntl
from pathlib import Path
lock = Path(settings.get_param_path('DisableOpenpilotLongitudinal')).parent.parent / '.lock'
with lock.open('rb') as writer_lock:
fcntl.flock(writer_lock, fcntl.LOCK_EX)
self.assertFalse(owner.apply(request))
self.assertTrue(settings.get_bool('DisableOpenpilotLongitudinal'))
self.assertTrue(owner.apply(request))
self.assertFalse(owner.apply(request))
self.assertTrue(is_bolt_pedal_stock_denied(card.CP))
next_cp = params(identity, present=True, pedal=True)
VehicleStartupPreferences.read(settings, enabled=True).prepare(next_cp)
self.assertTrue(is_bolt_pedal_profile(next_cp))
cp = params(identity, present=True, pedal=True)
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp)
self.assertTrue(is_bolt_pedal_stock_denied(cp))
def test_reduced_acc_user_cancel_native_stock_forward_and_sources(self):
from opendbc.safety.tests.libsafety import libsafety_py
cp = params(CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, present=True, pedal=True)
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp, fingerprints={2: {0x180: 4}})
self.assertEqual(cp.safetyConfigs[0].safetyParam, 5)
setup(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
active = packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 1})
native('rx', active, 1_000_000)
self.assertTrue(libsafety_py.libsafety.get_controls_allowed())
self.assertEqual(libsafety_py.libsafety.safety_fwd_hook(2, 0x315), 0)
cancel = (0x1E1, button_bytes(6, 1), 2)
self.assertTrue(native('tx', cancel, 1_001_000))
for address, length in ((0x200, 6), (0x315, 5), (0xBD, 7), (0x1F5, 8), (0x370, 6)):
self.assertFalse(native('tx', (address, bytes(length), 0), 1_001_001))
native('rx', packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 0}), 1_010_000)
self.assertFalse(libsafety_py.libsafety.get_controls_allowed())
self.assertEqual(libsafety_py.libsafety.safety_fwd_hook(2, 0x315), 0)
self.assertFalse(native('tx', cancel, 1_011_000))
def test_reduced_acc_missing_stale_pt_and_physical_regen_suppress_steering(self):
identity = CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL
for fault in ('missing_camera', 'stale', 'stale_pt', 'regen'):
cp = params(identity, present=True, pedal=True)
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp, fingerprints={2: {0x180: 4}})
ci = CarInterface(cp)
packer = CANPacker(DBC[identity][Bus.pt])
extra = [TestBoltPedalMessages.sensor(packer, 0, 0),
packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 1})]
if fault != 'missing_camera':
extra.append(packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {'ACCCruiseState': 2, 'ACCCmdActive': 1}))
original = ci.update
def update(packets, original=original, extra=extra):
return original([(stamp, [*messages, *extra]) for stamp, messages in packets])
with patch.object(ci, 'update', side_effect=update):
out, _ = feed(ci, packer, 1_000_000_000)
if fault == 'missing_camera':
self.assertFalse(out.canValid)
elif fault == 'stale_pt':
for now in range(1_400_000_000, 1_461_000_000, 10_000_000):
out = ci.update([(now, [packer.make_can_msg('ASCMActiveCruiseControlStatus', 2,
{'ACCCruiseState': 2, 'ACCCmdActive': 1})])])
self.assertFalse(out.canValid)
elif fault == 'regen':
out = ci.update([(1_001_000_000, [packer.make_can_msg('EBCMRegenPaddle', 0, {'RegenPaddle': 1})])])
self.assertTrue(out.regenBraking)
now = 1_402_000_000 if fault in ('stale', 'stale_pt') else 1_002_000_000
ci.CC.frame = 104
_, messages = ci.apply(control().as_reader(), now)
steering = next(m for m in messages if m[0] == 0x180)
self.assertEqual(steering[1][0] & 8, 0, fault)
self.assertFalse(any(m[0] in (0x200, 0x315, 0x1E1, 0xBD, 0x1F5) for m in messages))
def test_vehicle_snapshot_provider_expiry_does_not_read_denial_twice(self):
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
cp = params(CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, present=True, pedal=True)
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp)
for expired in (None, object()):
with OpenpilotPrefix():
samples = iter((cp, expired))
reads = []
def provider(reads=reads, samples=samples):
reads.append(True)
return next(samples)
owner = FeatureSettingsOwner(Params(), lambda _: True, vehicle_fingerprint=lambda: cp.carFingerprint,
vehicle_params=provider)
view = owner.snapshot('vehicle', parked=True, system_long=True, lateral_context=True, metric=False)
self.assertEqual(len(reads), 1)
row = next(row for row in view.rows if row.key == 'DisableOpenpilotLongitudinal')
self.assertTrue(row.available)
self.assertIn('Factory camera not detected', row.reason)
# A displayed recovery row is not retained commit authority.
self.assertIsNone(owner._bolt_disable_capability())
self.assertEqual(len(reads), 2)
owner.vehicle_params = lambda: cp
refreshed = owner.snapshot('vehicle', parked=True, system_long=True, lateral_context=True, metric=False)
self.assertTrue(next(row for row in refreshed.rows if row.key == 'DisableOpenpilotLongitudinal').available)