Files
StarPilot/openpilot/starpilot/conditional_mode/tests/test_gm_distance_source.py
T
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

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: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
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: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

202 lines
10 KiB
Python

"""Physical GM Traffic packets, gestures and current-drive ownership."""
from opendbc.can import CANPacker
from opendbc.car.gm.distance_button import GMDistanceButtons
from openpilot.starpilot.conditional_mode.manual import Button, ButtonTracker, Gesture, Press
def test_distance_original_counter_thresholds_and_no_repeated_release():
packer = CANPacker('gm_global_a_powertrain_generated')
for count, expected in ((1, [Press.SHORT]), (49, [Press.SHORT]), (50, [Press.LONG]),
(249, [Press.LONG]), (250, [Press.LONG, Press.VERY_LONG])):
source, tracker = GMDistanceButtons(), ButtonTracker()
gestures = []
for tick, held in enumerate([False] + [True] * count + [False, False]):
packet = packer.make_can_msg('ASCMSteeringButton', 0, {'DistanceButton': int(held)})
gestures.extend(tracker.observe_distance_source(source.update([(1_000_000_000 + tick * 10_000_000, [packet])])))
assert gestures == [Gesture(Button.DISTANCE, press) for press in expected]
def test_distance_loss_and_held_start_require_new_neutral():
packer = CANPacker('gm_global_a_powertrain_generated')
source, tracker = GMDistanceButtons(), ButtonTracker()
def observe(stamp, held):
packet = packer.make_can_msg('ASCMSteeringButton', 0, {'DistanceButton': int(held)})
return tracker.observe_distance_source(source.update([(stamp, [packet])]))
assert not observe(1_000_000_000, True)
assert not observe(1_030_000_000, False)
assert not observe(1_060_000_000, True)
assert not tracker.observe_distance_source(source.update([]))
assert not observe(1_090_000_000, False)
assert not observe(1_120_000_000, True)
assert observe(1_150_000_000, False) == (Gesture(Button.DISTANCE, Press.SHORT),)
def test_card_distance_packet_to_traffic_owner_and_launch_revocation(tmp_path, monkeypatch):
from types import SimpleNamespace
from openpilot.common.params import Params
from openpilot.cereal import messaging
from opendbc.car import structs
from opendbc.car.gm.tests.test_bolt_pedal import params as pedal_params
from opendbc.car.gm.values import CAR
from openpilot.selfdrive.car.card import Car
from openpilot.starpilot.conditional_mode.card_input import conditional_traffic_candidate
from openpilot.starpilot.conditional_mode.runtime_settings import ConditionalSettingsOwner
from openpilot.starpilot.conditional_mode.traffic import TrafficOwner
from openpilot.starpilot.conditional_mode.traffic_launch import TrafficLaunchState
from openpilot.starpilot.conditional_mode.tests.test_traffic import FakeSM, NOW, DRIVE
params = Params(str(tmp_path))
params.put('DistanceButtonControl', 6, block=True)
cp = pedal_params(CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, setting=True, pedal=True)
source, tracker, owner, settings = GMDistanceButtons(), ButtonTracker(), TrafficOwner(), ConditionalSettingsOwner(params)
sm = FakeSM()
sm.payloads['deviceState'].startedMonoTime = DRIVE
cs = structs.CarState(canValid=True)
sent = []
card = Car.__new__(Car)
card.conditional_replay = True
card.slc_cruise_event_id = card.traffic_event_sequence = 0
card.slc_producer_session = 'a' * 32
monkeypatch.setattr(card, 'pm', SimpleNamespace(send=lambda service, event: sent.append(event)), raising=False)
packer = CANPacker('gm_global_a_powertrain_generated')
for tick, held in enumerate((False, True, False)):
now = NOW + tick * 30_000_000
boot = now + 2_000_000_000
sm.advance(now)
sm.logMonoTime['carState'] = now
packet = packer.make_can_msg('ASCMSteeringButton', 0, {'DistanceButton': int(held)})
observation = source.update([(boot, [packet])])
card.traffic_receipt = conditional_traffic_candidate(cs, tracker, params, settings, cp, sm, now_ns=now, distance=observation)
assert card.traffic_receipt is not None
cs_send = messaging.new_message('carState', valid=True)
cs_send.logMonoTime = now
card.publish_traffic_receipt(cs_send)
verdict = owner.sample(sent[-1], params=params, settings=settings, sm=sm, cp=cp,
drive_id=DRIVE, now_mono_ns=now, now_boot_ns=boot)
assert verdict.effective is (tick == 2)
replayed = owner.sample(sent[-1], params=params, settings=settings, sm=sm, cp=cp,
drive_id=DRIVE, now_mono_ns=now, now_boot_ns=boot)
assert replayed.effective is True # Repeated toggle cannot toggle twice.
sm.payloads['carControl'].longActive = False
paused = owner.sample(None, params=params, settings=settings, sm=sm, cp=cp,
drive_id=DRIVE, now_mono_ns=now, now_boot_ns=boot)
assert paused.effective is None
sm.payloads['carControl'].longActive = True
status = owner.attach(None, verdict, now_ns=now, drive_id=DRIVE, profile_target_ready=True, profile_reason='qualified')
sm.payloads['slcState'] = status.slcState
for table in (sm.seen, sm.valid, sm.alive):
table['slcState'] = True
sm.logMonoTime['slcState'] = now
sm.recv_time['slcState'] = now / 1e9
launch = TrafficLaunchState(params)
assert launch.sample(sm, now_ns=now, boot_ns=boot, drive_id=DRIVE) is True
assert launch.sample(sm, now_ns=now + 101_000_000, boot_ns=boot + 101_000_000, drive_id=DRIVE) is None
sm.advance(now + 301_000_000)
lost = owner.sample(None, params=params, settings=settings, sm=sm, cp=cp,
drive_id=DRIVE, now_mono_ns=now + 301_000_000, now_boot_ns=boot + 301_000_000)
assert lost.effective is True
assert lost.requested
assert lost.source_boot_ns == 0
sm.valid['carControl'] = False
denied = owner.sample(None, params=params, settings=settings, sm=sm, cp=cp,
drive_id=DRIVE, now_mono_ns=now + 301_000_000, now_boot_ns=boot + 301_000_000)
assert denied.effective is None
# A saved assignment cannot create a new toggle after a source loss.
assert conditional_traffic_candidate(cs, tracker, params, settings, cp, sm,
now_ns=now + 301_000_000, distance=source.update([])) is None
def test_gm_traffic_planner_lifecycle_starts_factory_conditional_owner(tmp_path):
from unittest.mock import Mock, patch
from openpilot.common.params import Params
from opendbc.car.gm.tests.test_bolt_pedal import params as pedal_params
from opendbc.car.gm.values import CAR
from openpilot.selfdrive.controls import plannerd
params = Params(str(tmp_path))
cp = pedal_params(CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, setting=True, pedal=True)
params.put('CarParams', cp.to_bytes(), block=True)
class EndLoop(Exception):
pass
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') as conditional,
patch.object(plannerd.messaging, 'sub_sock') as sockets,
patch.object(plannerd.messaging, 'SubMaster', return_value=sm) as subscriber,
patch.object(plannerd.messaging, 'PubMaster') as publisher,
patch.dict('os.environ', {}, clear=True)):
try:
plannerd.main()
except EndLoop:
pass
else:
raise AssertionError('planner loop did not start')
conditional.assert_called_once()
assert 'deviceState' in subscriber.call_args.args[0]
assert 'slcState' in publisher.call_args.args[0]
assert any(call.args[0] == 'slcCruiseEvent' for call in sockets.call_args_list)
def test_gm_map_cache_reads_edges_thresholds_epoch_and_one_second(tmp_path):
from unittest.mock import patch
from openpilot.common.params import Params
from openpilot.starpilot.conditional_mode.manual import WheelMapCache, read_button_map
params = Params(str(tmp_path))
source, cache = GMDistanceButtons(), WheelMapCache(include_ioniq_media=False)
packer = CANPacker('gm_global_a_powertrain_generated')
with patch('openpilot.starpilot.conditional_mode.manual.read_button_map', wraps=read_button_map) as read:
for tick in range(34):
packet = packer.make_can_msg('ASCMSteeringButton', 0, {'DistanceButton': 0})
now = 1_000_000_000 + tick * 30_000_000
cache.sample(params, source.update([(now, [packet])]), now)
assert read.call_count == 1
now += 30_000_000
cache.sample(params, source.update([(now, [packet])]), now)
assert read.call_count == 2 # One-second audit while continuously neutral.
params.put('DistanceButtonControl', 6, block=True)
packet = packer.make_can_msg('ASCMSteeringButton', 0, {'DistanceButton': 1})
now += 30_000_000
assert cache.sample(params, source.update([(now, [packet])]), now).distance == 6
assert read.call_count == 3
for tick in range(1, 18):
cache.sample(params, source.update([(now + tick * 30_000_000, [packet])]), now + tick * 30_000_000)
assert read.call_count == 4 # First held duration boundary, not every physical packet.
def test_qualified_cc_and_factory_distance_source_reaches_traffic_owner(tmp_path):
from openpilot.common.params import Params
from opendbc.car import structs
from opendbc.car.gm.profiles import profiles_supported
from opendbc.car.gm.values import CAR
from openpilot.starpilot.tests.test_gm_feature_runtime import configured
from openpilot.starpilot.conditional_mode.card_input import conditional_traffic_candidate
from openpilot.starpilot.conditional_mode.runtime_settings import ConditionalSettingsOwner
from openpilot.starpilot.conditional_mode.tests.test_traffic import FakeSM, NOW, DRIVE
params = Params(str(tmp_path))
params.put('DistanceButtonControl', 6, block=True)
packer = CANPacker('gm_global_a_powertrain_generated')
for identity, alpha in ((CAR.CHEVROLET_VOLT_CC, False), (CAR.CHEVROLET_BOLT_CC_2017, False),
(CAR.CHEVROLET_BOLT_ACC_2022_2023, True)):
cp = configured(params, identity, alpha=alpha)
assert profiles_supported(cp)
source, tracker = GMDistanceButtons(), ButtonTracker()
settings = ConditionalSettingsOwner(params)
sm = FakeSM()
sm.payloads['deviceState'].startedMonoTime = DRIVE
cs = structs.CarState(canValid=True)
receipts = []
for tick, held in enumerate((False, True, False)):
now = NOW + tick * 30_000_000
sm.advance(now)
sm.logMonoTime['carState'] = now
packet = packer.make_can_msg('ASCMSteeringButton', 0, {'DistanceButton': int(held)})
observation = source.update([(now + 2_000_000_000, [packet])])
receipts.append(conditional_traffic_candidate(cs, tracker, params, settings, cp, sm,
now_ns=now, distance=observation))
assert all(receipt is not None for receipt in receipts)
assert receipts[-1][0] is True
assert receipts[0][0] is False