mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
80fbb4f6db
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>
239 lines
13 KiB
Python
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)
|