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>
183 lines
9.1 KiB
Python
183 lines
9.1 KiB
Python
from contextlib import ExitStack
|
|
from dataclasses import replace
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from openpilot.cereal import log, messaging
|
|
from openpilot.common.prefix import OpenpilotPrefix
|
|
from openpilot.selfdrive.selfdrived import selfdrived as owner
|
|
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
|
|
from openpilot.starpilot.aol.tests.test_long_only_input_wait import intent
|
|
from openpilot.starpilot.aol.wire import decode_intent, encode_intent, decode_safety, encode_safety
|
|
|
|
|
|
SOURCE = 10_030_000_000
|
|
PUB = SOURCE + 4_837_661
|
|
DECISION = SOURCE + 31_625_140
|
|
|
|
|
|
def wire(stamp, seq):
|
|
message = intent(stamp, seq, pause_lat=False)
|
|
value = decode_intent(message.aolIntentWire)
|
|
message.aolIntentWire = encode_intent(replace(value, allowedLatch=True, lateralArmed=True))
|
|
return message
|
|
|
|
|
|
def prepare(stack):
|
|
case = fixture.IoniqAvailabilityTests(methodName='runTest')
|
|
stack.callback(case.doCleanups)
|
|
stack.enter_context(OpenpilotPrefix())
|
|
sd, controls, drive, card, receive = case._ioniq_availability_callers(full_op=True)
|
|
axis, state, command = drive(2, car_output_valid=False)
|
|
assert str(axis.faultReason) == 'availabilityPause'
|
|
assert state.alertType == 'commIssue/softDisable'
|
|
assert not state.enabled and not state.active and not command.latActive and not command.longActive
|
|
assert sd.state_machine.state == log.SelfdriveState.OpenpilotState.disabled
|
|
assert sd.state_machine.soft_disable_timer == 300
|
|
axis, state, command = drive(3, state_stamp=SOURCE, offset_ns=4_837_661,
|
|
intent_message=wire(SOURCE, 4), native_request_long=False)
|
|
assert axis.lateralActive and command.latActive
|
|
assert not axis.desiredLongitudinal and not axis.longitudinalActive and not command.longActive
|
|
assert not state.enabled and not state.active and sd.aol_availability_long_inhibit
|
|
assert str(axis.faultReason) == 'none' and axis.nativeAcknowledged and axis.qualified
|
|
assert axis.sourceCarStateMonoTime == SOURCE and axis.observedMonoTime == PUB
|
|
assert axis.validUntilMonoTime == PUB + 30_000_000
|
|
assert sd.state_machine.soft_disable_timer == 299
|
|
return sd, controls, drive, axis
|
|
|
|
|
|
def test_first_pause_then_recovered_lateral_source_cutoff():
|
|
with ExitStack() as stack:
|
|
sd, controls, drive, original = prepare(stack)
|
|
previous_axis = original.to_dict()
|
|
timer_before = sd.state_machine.soft_disable_timer
|
|
attempts = []
|
|
actual_defer = sd.defer_expired_axis_publication
|
|
now = [SOURCE + 29_900_000]
|
|
actual_events = sd.update_events
|
|
|
|
def events(cs):
|
|
result = actual_events(cs)
|
|
now[0] = DECISION
|
|
return result
|
|
|
|
sd.car_state_poller = mock.Mock()
|
|
sd.car_state_poller.poll.return_value = []
|
|
|
|
def defer(cs, initial, **kwargs):
|
|
result = actual_defer(cs, initial, **kwargs)
|
|
attempts.append(result)
|
|
return result
|
|
|
|
with mock.patch.object(owner.time, 'monotonic_ns', side_effect=lambda: now[0]), \
|
|
mock.patch.object(sd, 'defer_expired_axis_publication', side_effect=defer), \
|
|
mock.patch.object(sd, 'update_events', side_effect=events):
|
|
axis, state, command = drive(6, timeout=True, offset_ns=-100_000,
|
|
intent_message=wire(SOURCE, 4), native_request_long=False)
|
|
assert sd.aol_axis_publication_deferred
|
|
assert not state.enabled and not state.active and not command.longActive and not axis.longitudinalActive
|
|
assert sd.state_machine.soft_disable_timer == timer_before == 299
|
|
assert axis.to_dict() == previous_axis and command.latActive
|
|
assert attempts == [True]
|
|
# One tick only, even before the original proof's unchanged deadline.
|
|
with mock.patch.object(owner.time, 'monotonic_ns', return_value=SOURCE + 33_000_000):
|
|
next_axis, _, next_command = drive(6, timeout=True, offset_ns=3_000_000,
|
|
intent_message=wire(SOURCE, 4), native_request_long=False)
|
|
assert not sd.aol_axis_publication_deferred
|
|
assert sd.state_machine.soft_disable_timer == 298
|
|
assert not next_axis.lateralActive and not next_command.latActive and not next_command.longActive
|
|
|
|
|
|
@pytest.mark.parametrize('condition', ['current_soft', 'current_immediate', 'current_enable', 'current_user',
|
|
'deadline', 'initial_state_changed', 'invalid_carstate', 'settings_revoked', 'intent_pause',
|
|
'intent_unlatched', 'producer_changed', 'session_changed', 'active_soft_disabling',
|
|
'native_denial', 'native_invalid', 'can_invalid', 'can_timeout', 'eps_temp', 'eps_perm',
|
|
'dm_event_changed', 'lagging', 'thermal', 'wrong_gear', 'physical_cancel',
|
|
'no_inhibit', 'authority_lost', 'proof_changed', 'integrity_epoch'])
|
|
def test_timer_fix_retains_fault_identity_and_original_deadline_gates(condition):
|
|
with ExitStack() as stack:
|
|
sd, _, _, axis = prepare(stack)
|
|
previous = axis.to_dict()
|
|
source_proof = sd.aol_published_input_proof
|
|
sd.aol_step_input_proof = source_proof
|
|
sd.aol_step_deferral_checked = False
|
|
sd.aol_step_intent_drain_count = 0
|
|
now = DECISION
|
|
cs = sd.CS_prev
|
|
event = {'current_soft': owner.EventName.commIssue,
|
|
'current_immediate': owner.EventName.controlsMismatch,
|
|
'current_enable': owner.EventName.buttonEnable,
|
|
'current_user': owner.EventName.buttonCancel}.get(condition)
|
|
if event is not None:
|
|
sd.events.add(event)
|
|
# Keep event-set equality so the actual event-kind guards are exercised.
|
|
sd.events_prev = sd.events.names.copy()
|
|
elif condition == 'deadline':
|
|
now = source_proof[3]
|
|
elif condition == 'invalid_carstate':
|
|
sd.conditional_car_state_valid = False
|
|
elif condition in ('settings_revoked', 'intent_pause', 'intent_unlatched', 'producer_changed'):
|
|
latest = sd.aol_intent_companion.latest
|
|
changes = {'settings_revoked': {'settingsQualified': False}, 'intent_pause': {'pauseLateral': True},
|
|
'intent_unlatched': {'allowedLatch': False}, 'producer_changed': {'producerSessionId': 'other-card'}}[condition]
|
|
sd.aol_intent_companion.latest = (latest[0], latest[1], replace(latest[2], **changes))
|
|
elif condition == 'session_changed':
|
|
sd.aol_session_id = 'other-owner'
|
|
elif condition == 'active_soft_disabling':
|
|
# Reach the genuine production soft-disabling state and countdown.
|
|
sd.state_machine.state = log.SelfdriveState.OpenpilotState.enabled
|
|
sd.events.add(owner.EventName.commIssue)
|
|
sd.enabled, sd.active = sd.state_machine.update(sd.events)
|
|
assert sd.enabled and sd.active and sd.state_machine.soft_disable_timer == 300
|
|
assert sd.state_machine.state == log.SelfdriveState.OpenpilotState.softDisabling
|
|
sd.events_prev = sd.events.names.copy()
|
|
if condition in ('can_invalid', 'can_timeout', 'eps_temp', 'eps_perm', 'wrong_gear', 'physical_cancel'):
|
|
from opendbc.car.structs import car
|
|
cs = sd.CS_prev.as_builder()
|
|
if condition == 'can_invalid':
|
|
cs.canValid = False
|
|
elif condition == 'can_timeout':
|
|
cs.canTimeout = True
|
|
elif condition == 'eps_temp':
|
|
cs.steerFaultTemporary = True
|
|
elif condition == 'eps_perm':
|
|
cs.steerFaultPermanent = True
|
|
elif condition == 'wrong_gear':
|
|
cs.gearShifter = car.CarState.GearShifter.park
|
|
else:
|
|
cs.buttonEvents = [car.CarState.ButtonEvent(type=car.CarState.ButtonEvent.Type.cancel, pressed=True)]
|
|
sd.car_state_samples = ((cs, True),)
|
|
elif condition == 'native_denial':
|
|
native = decode_safety(sd.sm['aolSafetyWire'])
|
|
message = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=PUB)
|
|
message.aolSafetyWire = encode_safety(replace(native, lateralAllowed=False))
|
|
sd.sm.update_msgs(PUB / 1e9, [message.as_reader()])
|
|
elif condition == 'native_invalid':
|
|
sd.sm.valid['aolSafetyWire'] = False
|
|
elif condition == 'dm_event_changed':
|
|
sd.events.add(owner.EventName.driverDistracted3)
|
|
elif condition == 'lagging':
|
|
stack.enter_context(mock.patch.object(type(sd.rk), 'lagging', new_callable=mock.PropertyMock, return_value=True))
|
|
elif condition == 'thermal':
|
|
message = messaging.new_message('deviceState', valid=True, logMonoTime=PUB)
|
|
message.deviceState = sd.sm['deviceState']
|
|
message.deviceState.thermalStatus = 'overheated'
|
|
sd.sm.update_msgs(PUB / 1e9, [message.as_reader()])
|
|
elif condition == 'no_inhibit':
|
|
sd.aol_availability_long_inhibit = False
|
|
elif condition == 'authority_lost':
|
|
sd.aol_authority_lost = True
|
|
elif condition == 'proof_changed':
|
|
sd.aol_step_input_proof = (*source_proof[:3], source_proof[3] + 1, *source_proof[4:])
|
|
elif condition == 'integrity_epoch':
|
|
sd.aol_intent_companion.integrity_epoch += 1
|
|
initial = (sd.state_machine.state, sd.state_machine.soft_disable_timer, sd.enabled, sd.active)
|
|
if condition == 'initial_state_changed':
|
|
sd.state_machine.soft_disable_timer += 1
|
|
with mock.patch.object(owner.time, 'monotonic_ns', return_value=now), \
|
|
mock.patch.object(messaging, 'recv_one_or_none', return_value=None):
|
|
result = sd.defer_expired_axis_publication(cs, initial, now_ns=now)
|
|
assert not result
|
|
assert sd.pm.messages['aolAxisState'].aolAxisState.to_dict() == previous
|
|
assert sd.aol_published_input_proof == source_proof
|