Files
StarPilot/openpilot/starpilot/tests/test_gm_feature_runtime.py
T
firestar5683 80fbb4f6db Feature registration
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

239 lines
13 KiB
Python

"""Finalized GM driving owners start optional hosts without granting authority."""
import json
from pathlib import Path
import unittest
from opendbc.car import gen_empty_fingerprint
from opendbc.car.gm.feature_capabilities import display_supported, longitudinal_supported
from opendbc.car.gm.interface import CarInterface
from opendbc.car.gm.values import CAR
from opendbc.car.gm.tests.test_camera_acc_pedal import CAMERA_IDS, fingerprint as camera_pedal_fingerprint
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.feature_runtime import enabled, slc_runtime_settings, vision_control_enabled
from openpilot.starpilot.longitudinal.profile_document import default_personality_profiles, profile_document
from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences
def configured(params, identity, *, alpha=False, release=False, removed=False, alternate=False, pedal=False, disable=False):
params.put_bool('GMPedalLongitudinal', pedal, block=True)
fp = gen_empty_fingerprint()
if not alternate:
fp[0][0xBE] = 6
if identity in (CAR.CHEVROLET_VOLT, CAR.CHEVROLET_SUBURBAN):
fp[1][0x460] = 8
if identity in (CAR.CHEVROLET_VOLT_ASCM, CAR.CHEVROLET_VOLT_2019):
fp[0][0x2FF] = 8
if identity == CAR.CHEVROLET_VOLT_CC:
fp[0].update({0xBE: 6, 0x3D1: 8, 0xC9: 8, 0x1E1: 7, 0x1F5: 8, 0x34A: 5, 0x1C4: 8, 0xBD: 7})
elif identity == CAR.CHEVROLET_VOLT_CAMERA:
if removed:
fp[0].update({0x184: 8, 0x34A: 5, 0x1C4: 8, 0xC9: 8, 0x1E1: 7, 0xF1 if alternate else 0xBE: 6})
else:
fp[2][0x320] = 6
elif not removed:
fp[2][0x320] = 8
fp[2][0x180] = 4
if pedal:
fp[0][0x201] = 6
if identity in CAMERA_IDS:
fp = camera_pedal_fingerprint(camera=not removed, be=not alternate)
cp = CarInterface.get_params(identity, fp, [], alpha, release, False)
if disable:
VehicleStartupPreferences(disable_bolt_long=True).prepare(cp)
return cp
LONG_CASES = (
(CAR.CHEVROLET_VOLT, {}), (CAR.CHEVROLET_VOLT, {'alternate': True}),
(CAR.CHEVROLET_VOLT_ASCM, {'alpha': True}), (CAR.CHEVROLET_VOLT_CAMERA, {'alpha': True}),
(CAR.CHEVROLET_VOLT_CAMERA, {'alpha': True, 'removed': True}),
(CAR.CHEVROLET_VOLT_2019, {'alpha': True}), (CAR.CHEVROLET_VOLT_2019, {'alpha': True, 'alternate': True}),
(CAR.CHEVROLET_VOLT_CC, {}), (CAR.CHEVROLET_BOLT_EUV, {'alpha': True}),
(CAR.CHEVROLET_BOLT_ACC_2022_2023, {'alpha': True}), (CAR.CHEVROLET_SUBURBAN, {}),
(CAR.CHEVROLET_BOLT_CC_2017, {}), (CAR.CHEVROLET_BOLT_CC_2018_2021, {'removed': True}),
(CAR.CHEVROLET_BOLT_CC_2022_2023, {}), (CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, {}),
(CAR.CHEVROLET_BOLT_CC_2017, {'pedal': True}),
*((identity, {'pedal': True}) for identity in CAMERA_IDS),
)
STOCK_CASES = (
(CAR.CHEVROLET_VOLT, {'disable': True}), (CAR.CHEVROLET_VOLT, {'disable': True, 'alternate': True}),
(CAR.CHEVROLET_VOLT_CC, {'disable': True}), (CAR.CHEVROLET_VOLT_CAMERA, {}),
(CAR.CHEVROLET_VOLT_CAMERA, {'removed': True}), (CAR.CHEVROLET_VOLT_2019, {}),
(CAR.CHEVROLET_VOLT_ASCM, {}), (CAR.CHEVROLET_BOLT_EUV, {}),
(CAR.CHEVROLET_BOLT_ACC_2022_2023, {'release': True, 'alpha': True}),
(CAR.CHEVROLET_BOLT_CC_2022_2023, {'disable': True}),
*((identity, {'pedal': True, 'disable': True}) for identity in CAMERA_IDS),
*((identity, {'pedal': True, 'release': True}) for identity in CAMERA_IDS),
)
class TestGmFeatureRuntime(unittest.TestCase):
def save_requests(self, params):
params.put_bool('CurveSpeedController', True, block=True)
params.put_bool('SpeedLimitController', True, block=True)
params.put('LongitudinalPersonalityProfiles', profile_document(default_personality_profiles(False), enabled=False,
global_braking_response='sport'), block=True)
def test_actual_finalized_long_and_stock_startup_matrix(self):
with OpenpilotPrefix():
params = Params()
self.save_requests(params)
for identity, kwargs in LONG_CASES:
with self.subTest(identity=identity, kwargs=kwargs):
cp = configured(params, identity, **kwargs)
self.assertTrue(cp.openpilotLongitudinalControl, (identity, kwargs, cp.safetyConfigs, cp.flags))
self.assertTrue(longitudinal_supported(cp))
for feature in ('conditional', 'curve', 'slc', 'profile'):
self.assertTrue(enabled(params, cp, feature, {}), feature)
self.assertTrue(enabled(params, cp, 'vision', {}))
for identity, kwargs in STOCK_CASES:
with self.subTest(identity=identity, kwargs=kwargs):
cp = configured(params, identity, **kwargs)
self.assertFalse(cp.openpilotLongitudinalControl, (identity, kwargs, cp.safetyConfigs, cp.flags))
self.assertFalse(longitudinal_supported(cp))
self.assertTrue(display_supported(cp), (identity, kwargs, cp.safetyConfigs, cp.flags, cp.dashcamOnly))
for feature in ('conditional', 'curve', 'profile'):
self.assertFalse(enabled(params, cp, feature, {}), feature)
self.assertTrue(enabled(params, cp, 'vision', {}))
self.assertFalse(vision_control_enabled(params, cp))
self.assertTrue(enabled(params, cp, 'slc', {}))
settings = slc_runtime_settings(params, cp, {})
self.assertTrue(settings.display) # Saved controller-on also requests display.
self.assertFalse(settings.enabled)
self.assertTrue(settings.acceptance.display_only)
def test_default_off_saved_stock_and_invalid_cp_preserve_admission_boundaries(self):
from openpilot.starpilot.conditional_mode.policy import ModeChoice
from openpilot.starpilot.conditional_mode.preferences import SavedPreferences, encode_preferences
with OpenpilotPrefix():
params = Params()
cp = configured(params, CAR.CHEVROLET_VOLT_CC)
self.assertTrue(enabled(params, cp, 'conditional', {})) # Readable factory CEM.
self.assertTrue(enabled(params, cp, 'profile', {})) # Fresh Comfort is the readable missing-document default.
self.assertFalse(enabled(params, cp, 'curve', {}))
self.assertTrue(enabled(params, cp, 'slc', {}))
self.assertTrue(enabled(params, cp, 'vision', {}))
settings = slc_runtime_settings(params, cp, {})
self.assertTrue(settings.display)
self.assertFalse(settings.enabled)
self.assertTrue(settings.acceptance.display_only)
self.assertFalse(vision_control_enabled(params, cp))
params.put('ConditionalModeConfig', json.loads(encode_preferences(SavedPreferences(mode=ModeChoice.STOCK))), block=True)
self.assertTrue(enabled(params, cp, 'conditional', {})) # Owner retains Stock session lifecycle.
params.put_bool('SafeMode', True, block=True)
self.assertFalse(enabled(params, cp, 'conditional', {}))
params.put_bool('SafeMode', False, block=True)
Path(params.get_param_path('ConditionalModeConfig')).write_bytes(b'{broken')
self.assertFalse(enabled(params, cp, 'conditional', {}))
self.save_requests(params)
for mutation in ('passive', 'dashcamOnly', 'notCar', 'word', 'extra_config', 'brand'):
bad = cp.as_reader().as_builder()
if mutation in ('passive', 'dashcamOnly', 'notCar'):
setattr(bad, mutation, True)
elif mutation == 'word':
bad.safetyConfigs[0].safetyParam |= 0x8000
elif mutation == 'brand':
bad.brand = 'unknown'
else:
bad.safetyConfigs = list(bad.safetyConfigs) * 2
self.assertFalse(longitudinal_supported(bad), mutation)
self.assertFalse(display_supported(bad), mutation)
release = configured(params, CAR.CHEVROLET_VOLT_CC, release=True)
self.assertTrue(release.dashcamOnly)
self.assertFalse(display_supported(release))
for feature, flag in (('conditional', 'CONDITIONAL_MODE_REPLAY_RUNTIME'), ('curve', 'CURVE_REPLAY_RUNTIME'),
('slc', 'SLC_REPLAY_RUNTIME'), ('profile', 'LONG_PLANNER_REPLAY_RUNTIME')):
self.assertTrue(enabled(params, None, feature, {flag: '1'}))
def test_real_curve_owner_keeps_current_authority_and_freshness(self):
from openpilot.starpilot.curve_speed.preferences import PreferenceHost
from openpilot.starpilot.curve_speed.tests.test_host import Bus, NOW, sample_messages
with OpenpilotPrefix():
params = Params()
self.save_requests(params)
cp = configured(params, CAR.CHEVROLET_VOLT_CC)
preferences = PreferenceHost(params)
try:
host = preferences.make_host()
preferences.refresh(host, NOW)
stamp = [NOW]
host.clock_pair = lambda: (stamp[0], stamp[0] + 2_000_000_000)
sm = Bus(sample_messages())
self.assertIsNone(host.sample(sm, cp, stamp[0], follow_time_s=1.45).ceiling_mps) # Clock epoch arms first.
stamp[0] += 50_000_000
sm.logMonoTime = dict.fromkeys(sm.messages, stamp[0])
sm.messages['modelV2'].timestampEof = stamp[0] + 2_000_000_000
self.assertIsNotNone(host.sample(sm, cp, stamp[0], follow_time_s=1.45).ceiling_mps)
sm.logMonoTime['radarState'] = NOW - 1_000_000_000
self.assertIsNone(host.sample(sm, cp, stamp[0], follow_time_s=1.45).ceiling_mps)
sm = Bus(sample_messages())
sm.messages['carControl'].longActive = False
self.assertIsNone(host.sample(sm, cp, stamp[0], follow_time_s=1.45).ceiling_mps)
disabled = configured(params, CAR.CHEVROLET_VOLT_CC, disable=True)
self.assertIsNone(host.sample(Bus(sample_messages()), disabled, NOW, follow_time_s=1.45).ceiling_mps)
finally:
preferences.close()
def test_actual_gm_unknown_sign_source_never_creates_speed_command(self):
from openpilot.cereal import messaging
from opendbc.car.gm.carstate import CarState
from openpilot.starpilot.longitudinal.tests.test_cruise_ceiling import messages
from openpilot.starpilot.speed_limits.runtime import Runtime
from openpilot.starpilot.speed_limits.tests.test_runtime_replay import ReplaySM
with OpenpilotPrefix():
params = Params()
self.save_requests(params)
for disabled in (False, True):
cp = configured(params, CAR.CHEVROLET_VOLT_CC, disable=disabled)
self.assertFalse(hasattr(CarState(cp), 'dashboard_limit'))
data, envelopes = messages()
data['carState'].canValid = True
data['carState'].canTimeout = False
data['carControl'].longActive = not disabled
data['carControl'].enabled = True
dashboard = messaging.new_message('slcDashboardObservation')
data['slcDashboardObservation'] = dashboard.slcDashboardObservation
sm = ReplaySM(data, 2_000_000_000)
runtime = Runtime(slc_runtime_settings(params, cp, {}), session_id='gm-source')
output = runtime.step(sm, cp, now_ns=2_000_000_000)
self.assertFalse(output.message.slcState.hasAccepted)
self.assertFalse(output.message.slcState.hasPending)
self.assertFalse(output.message.slcState.hasCeiling)
self.assertIsNone(output.command)
self.assertIsNone(output.result.ceiling)
def test_actual_planner_startup_constructs_long_hosts_and_display_only_stock_host(self):
from unittest.mock import Mock, patch
from openpilot.selfdrive.controls import plannerd
class EndLoop(Exception):
pass
with OpenpilotPrefix():
params = Params()
self.save_requests(params)
for disabled in (False, True):
cp = configured(params, CAR.CHEVROLET_VOLT_CC, disable=disabled)
params.put('CarParams', cp.to_bytes(), block=True)
sm = Mock()
sm.update.side_effect = EndLoop
with (patch.object(plannerd, 'Params', return_value=params),
patch.object(plannerd, 'config_realtime_process'),
patch.object(plannerd, 'ConditionalPlannerHost', wraps=plannerd.ConditionalPlannerHost) as conditional,
patch.object(plannerd, 'CurvePreferenceHost', wraps=plannerd.CurvePreferenceHost) as curve,
patch.object(plannerd, 'ProfileHost', wraps=plannerd.ProfileHost) as profile,
patch.object(plannerd, 'SlcRuntime', wraps=plannerd.SlcRuntime) as slc,
patch.object(plannerd.messaging, 'sub_sock'),
patch.object(plannerd.messaging, 'SubMaster', return_value=sm),
patch.object(plannerd.messaging, 'PubMaster'),
patch.dict('os.environ', {}, clear=True)):
with self.assertRaises(EndLoop):
plannerd.main()
self.assertEqual(conditional.call_count, int(not disabled))
self.assertEqual(curve.call_count, int(not disabled))
self.assertEqual(profile.call_count, int(not disabled))
slc.assert_called_once()
settings = slc.call_args.args[0]
self.assertEqual(settings.enabled, not disabled)
self.assertTrue(settings.display)
self.assertEqual(settings.acceptance.display_only, disabled)