mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 20:43:46 +08:00
Preserve GM lateral intent across brief Card source gaps
This commit is contained in:
@@ -123,10 +123,10 @@ def ordinary_source_availability(self, CS, now_ns, native=None, *, replay: bool,
|
||||
anchor = getattr(self, 'aol_ordinary_source_anchor', None)
|
||||
companion = getattr(self, 'aol_intent_companion', None)
|
||||
events = self.events.to_msg()
|
||||
from openpilot.starpilot.car.gm.aol import held_sdgm_lateral_pedal
|
||||
from openpilot.starpilot.car.gm.aol import held_gm_lateral_pedal
|
||||
from openpilot.starpilot.car.toyota.aol import held_lateral_pedal
|
||||
toyota_pedal = held_lateral_pedal(self, CS)
|
||||
held_pedal = held_sdgm_lateral_pedal(self, CS) or toyota_pedal
|
||||
held_pedal = held_gm_lateral_pedal(self, CS) or toyota_pedal
|
||||
if held_pedal:
|
||||
events = [event for event in events if not (event.name == EventName.pedalPressed and
|
||||
event.userDisable and not event.softDisable and not event.immediateDisable)]
|
||||
@@ -265,7 +265,7 @@ def ordinary_source_availability(self, CS, now_ns, native=None, *, replay: bool,
|
||||
return False
|
||||
native = native or native_provider(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
|
||||
lateral_intent_only = bool(proof[8] and not proof[9] and not self.enabled)
|
||||
if (native is None or held_pedal and (native.requestedLongitudinal or native.longitudinalAllowed) or
|
||||
if (native is None or toyota_pedal and (native.requestedLongitudinal or native.longitudinalAllowed) or
|
||||
lateral_intent_only and not (accepted.allowedLatch and accepted.lateralArmed and
|
||||
not accepted.pauseLateral and not getattr(self, 'aol_dm_lateral_inhibit', False) and
|
||||
native.requestedLateral and native.lateralAllowed) or
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
"""Bolt routes 25f676999e/f3031d6bc7: host gaps cannot become native faults.
|
||||
|
||||
Production SelfdriveD/Controls callers and serialized controlled native peers;
|
||||
this does not replay the device scheduler or drive a vehicle.
|
||||
"""
|
||||
from contextlib import ExitStack
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from opendbc.car.gm.tests.test_bolt_pedal import params
|
||||
from opendbc.car.gm.values import CAR
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.selfdrived.events import EventName
|
||||
from openpilot.starpilot.aol.intent import read_settings
|
||||
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
|
||||
from openpilot.starpilot.aol.tests.test_input_deadline_continuity import intent
|
||||
|
||||
BASE = 10_000_000_000
|
||||
|
||||
|
||||
def callers(cleanup, full=False, candidate=CAR.CHEVROLET_BOLT_CC_2018_2021):
|
||||
cleanup.enter_context(OpenpilotPrefix())
|
||||
case = fixture.IoniqAvailabilityTests(methodName='runTest')
|
||||
cleanup.callback(case.doCleanups)
|
||||
sd, controls, step = case._authority_callers(full, cp=params(candidate, pedal=True, camera=True), genuine_events=True)
|
||||
sd.aol_settings = read_settings(sd.params)
|
||||
return sd, controls, step
|
||||
|
||||
|
||||
def test_route29_missing_card_wire_keeps_only_original_intent_lease():
|
||||
with ExitStack() as cleanup:
|
||||
sd, controls, step = callers(cleanup)
|
||||
for tick in (0, 1):
|
||||
_, _, command = step(tick, gas=True, native_request_long=False)
|
||||
assert command.latActive and not command.longActive
|
||||
original = sd.sm.update_msgs
|
||||
def without_card(stamp, messages):
|
||||
return original(stamp, [m for m in messages if m.which() not in ('aolIntentWire', 'starpilotCarState')])
|
||||
with mock.patch.object(sd.sm, 'update_msgs', side_effect=without_card):
|
||||
for tick in (4, 7, 10, 13):
|
||||
axis, _, command = step(tick, timeout=True, gas=True, native_request_long=False,
|
||||
intent_message=intent(BASE + 10_000_000, 2), offset_ns=1_500_000)
|
||||
assert not axis.lateralActive and not command.latActive and not command.longActive
|
||||
assert not sd.aol_authority_lost and EventName.controlsMismatch not in sd.events.names
|
||||
assert str(axis.faultReason) == 'availabilityPause' and axis.sourceCarStateMonoTime == 0
|
||||
assert not sd.sm.alive['aolIntentWire']
|
||||
axis, _, command = step(14, gas=True, native_request_long=False)
|
||||
assert axis.lateralActive and command.latActive and not command.longActive
|
||||
assert not sd.aol_authority_lost
|
||||
with mock.patch('openpilot.selfdrive.controls.controlsd.time.monotonic_ns', return_value=axis.validUntilMonoTime + 1):
|
||||
expired, _ = controls.state_control()
|
||||
assert not expired.latActive and not expired.longActive
|
||||
|
||||
|
||||
@pytest.mark.parametrize('candidate', (CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
CAR.CHEVROLET_BOLT_CC_2022_2023, CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL))
|
||||
@pytest.mark.parametrize('pedal', ('brake', 'regen'))
|
||||
def test_route28_accepted_lateral_pedal_cutoff_retains_intent_without_output(candidate, pedal):
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, full=True, candidate=candidate)
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from openpilot.selfdrive.car.card import Car
|
||||
cp = params(candidate, pedal=True, camera=True)
|
||||
card = Car(CI=interfaces[candidate](cp), RI=interfaces[candidate].RadarInterface(cp))
|
||||
cleanup.callback(card.vehicle_startup.close)
|
||||
assert card.CP.to_dict() == sd.CP.to_dict()
|
||||
card.slc_producer_session = 'authority-card'
|
||||
card.aol_card_intent.allowed_latch = True
|
||||
def receive(now):
|
||||
frames = [sd.pm.messages[name] for name in ('aolAxisState', 'onroadEvents', 'selfdriveState')]
|
||||
for service in ('pandaStates', 'aolSafetyWire', 'deviceState'):
|
||||
msg = messaging.new_message(service, len(sd.sm[service]) if service in ('pandaStates', 'aolSafetyWire') else None,
|
||||
valid=sd.sm.valid[service], logMonoTime=sd.sm.logMonoTime[service])
|
||||
setattr(msg, service, sd.sm[service])
|
||||
frames.append(msg.as_reader())
|
||||
card.sm.update_msgs(now / 1e9, frames)
|
||||
return card.aol_disarming_fault(sd.CS_prev, int(card.sm.logMonoTime['onroadEvents']), now)
|
||||
for tick in (0, 1):
|
||||
_, host, command = step(tick)
|
||||
assert host.enabled and command.longActive
|
||||
assert not receive(BASE + tick * 10_000_000)
|
||||
state = fixture.car_state(brake=pedal == 'brake')
|
||||
state.regenBraking = pedal == 'regen'
|
||||
axis, host, command = step(2, state_stamp=BASE + 20_000_000, offset_ns=5_042_623, observed_state=state,
|
||||
intent_message=intent(BASE + 20_000_000, 3))
|
||||
assert command.latActive and not command.longActive and not host.enabled
|
||||
assert axis.sourceCarStateMonoTime == BASE + 20_000_000
|
||||
assert not receive(BASE + 25_042_623)
|
||||
# Exact source/publication gap shape from route 28. The last native FULL
|
||||
# receipt may still be in flight after the host has already withdrawn LONG.
|
||||
sd.car_state_samples = ()
|
||||
with mock.patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', return_value=BASE + 52_866_472):
|
||||
sd.publish_axis_proof(sd.CS_prev)
|
||||
paused = sd.pm.messages['aolAxisState'].aolAxisState
|
||||
assert str(paused.faultReason) == 'availabilityPause'
|
||||
assert not paused.lateralActive and not paused.longitudinalActive and paused.sourceCarStateMonoTime == 0
|
||||
assert not sd.aol_authority_lost and EventName.controlsMismatch not in sd.events.names
|
||||
from openpilot.starpilot.car.gm.aol import gm_lateral_pedal_source_zero
|
||||
from openpilot.starpilot.aol.runtime import current_native
|
||||
native = current_native(sd.sm, sd.CP, now_ns=BASE + 52_866_472, axis_session_id=sd.aol_session_id)
|
||||
for field, value in (('accFaulted', True), ('steerFaultTemporary', True), ('steerFaultPermanent', True),
|
||||
('gearShifter', 'unknown'), ('canValid', False)):
|
||||
invalid = sd.CS_prev.as_builder()
|
||||
setattr(invalid, field, value)
|
||||
assert not gm_lateral_pedal_source_zero(card.aol_transport_feedback, card.CP, invalid, paused,
|
||||
native, BASE + 52_866_472, producer='authority-card')
|
||||
assert not receive(BASE + 52_866_472)
|
||||
axis, host, command = step(6, observed_state=state, native_request_long=False, panda_controls_allowed=False)
|
||||
assert command.latActive and not command.longActive and not host.enabled and axis.lateralActive
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fault', ('native', 'can', 'cancel', 'new_pedal', 'intent_expired', 'model'))
|
||||
def test_held_pedal_never_hides_real_faults_or_admits_a_new_edge(fault):
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, full=True)
|
||||
step(0)
|
||||
state = fixture.car_state(brake=True)
|
||||
step(1, observed_state=state)
|
||||
sd.car_state_samples = ()
|
||||
cs = sd.CS_prev
|
||||
if fault == 'native':
|
||||
sd.sm.valid['aolSafetyWire'] = False
|
||||
elif fault == 'can':
|
||||
cs = cs.as_builder()
|
||||
cs.canValid = False
|
||||
elif fault == 'cancel':
|
||||
sd.events.add(EventName.buttonCancel)
|
||||
elif fault == 'new_pedal':
|
||||
cs = cs.as_builder()
|
||||
elif fault == 'model':
|
||||
sd.sm.valid['modelV2'] = False
|
||||
now = BASE + (220_000_001 if fault == 'intent_expired' else 42_866_472)
|
||||
with mock.patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', return_value=now):
|
||||
sd.publish_axis_proof(cs)
|
||||
axis = sd.pm.messages['aolAxisState'].aolAxisState
|
||||
assert not axis.lateralActive and not axis.longitudinalActive
|
||||
assert sd.aol_authority_lost and EventName.controlsMismatch in sd.events.names
|
||||
@@ -162,6 +162,7 @@ class AvailabilityPause:
|
||||
self.baseline_ns = 0
|
||||
return False
|
||||
if source_expired:
|
||||
# Retain intent under its original 200ms lease, never a renewed command.
|
||||
latest = companion.latest
|
||||
raw = latest[2] if latest is not None and latest[1] else None
|
||||
verified = companion.verified_sm_projection
|
||||
@@ -178,7 +179,7 @@ class AvailabilityPause:
|
||||
not 0 < raw.carStateLogMonoTime <= now_ns or raw.observedMonoTime > now_ns or
|
||||
now_ns - raw.carStateLogMonoTime > SAFETY_MAX_AGE_NS or
|
||||
(int(sm.logMonoTime['aolIntentWire']) != raw.carStateLogMonoTime and not projection) or
|
||||
not sm.seen['aolIntentWire'] or not sm.valid['aolIntentWire'] or not sm.alive['aolIntentWire']):
|
||||
not sm.seen['aolIntentWire'] or not sm.valid['aolIntentWire']):
|
||||
self.active = False
|
||||
return False
|
||||
inventory = (healthy.pandaSerial, tuple(p.hardwareSerial for p in healthy.pandaInventory))
|
||||
@@ -898,8 +899,8 @@ class TransportPauseFeedback:
|
||||
toyota_pedal and event.name == log.OnroadEvent.EventName.pedalPressed) and
|
||||
event.userDisable and not event.softDisable and not event.immediateDisable)]
|
||||
if cp.brand == 'gm':
|
||||
from openpilot.starpilot.car.gm.aol import sdgm_lateral_pedal_source_zero
|
||||
if sdgm_lateral_pedal_source_zero(self, cp, cs, axis, native, now_ns,
|
||||
from openpilot.starpilot.car.gm.aol import gm_lateral_pedal_source_zero
|
||||
if gm_lateral_pedal_source_zero(self, cp, cs, axis, native, now_ns,
|
||||
producer=intent_producer_session):
|
||||
events = [event for event in events if not (event.name == log.OnroadEvent.EventName.pedalPressed and
|
||||
event.userDisable and not event.softDisable and not event.immediateDisable)]
|
||||
|
||||
@@ -96,34 +96,35 @@ def steering_source_gap_intent_only(owner, cs, companion, now_ns: int) -> bool:
|
||||
return owner.ordinary_source_availability(cs, now_ns)
|
||||
|
||||
|
||||
def held_sdgm_lateral_pedal(owner, cs) -> bool:
|
||||
def held_gm_lateral_pedal(owner, cs) -> bool:
|
||||
"""An already accepted LAT-only pedal state remains intent, never output."""
|
||||
from openpilot.starpilot.nostalgia import physical_cancel
|
||||
anchor = getattr(owner, 'aol_ordinary_source_anchor', None)
|
||||
if not sdgm_alpha_lateral_owner(owner.CP) or anchor is None:
|
||||
if not qualified_gm(owner.CP) or not aol_enabled(owner.CP) or anchor is None:
|
||||
return False
|
||||
proof, native, accepted, _, accepted_state = anchor
|
||||
settings = getattr(owner, 'aol_settings', None)
|
||||
return bool(cs is accepted_state and not getattr(owner, 'car_state_samples', ()) and
|
||||
proof[8] and not proof[9] and not owner.enabled and not owner.active and
|
||||
native is not None and native.requestedLateral and native.lateralAllowed and
|
||||
not native.requestedLongitudinal and not native.longitudinalAllowed and
|
||||
accepted is not None and accepted.allowedLatch and accepted.lateralArmed and
|
||||
accepted.settingsQualified and not accepted.pauseLateral and not accepted.pauseLongitudinal and
|
||||
settings is not None and settings.enabled and
|
||||
not getattr(owner, 'aol_dm_lateral_inhibit', False) and
|
||||
sdgm_alpha_pedal_withdrawal(owner.CP, cs) and not cs.gasPressed and
|
||||
cs.cruiseState.available and (cs.brakePressed or cs.regenBraking) and
|
||||
not cs.steeringDisengage and not cs.gasPressed and
|
||||
not cs.seatbeltUnlatched and not physical_cancel(cs, owner.CP) and
|
||||
(not cs.brakePressed or cs.standstill or cs.vEgo >= settings.pause_brake_mps))
|
||||
|
||||
|
||||
|
||||
def sdgm_lateral_pedal_source_zero(feedback, cp, cs, axis, native, now_ns, *, producer):
|
||||
def gm_lateral_pedal_source_zero(feedback, cp, cs, axis, native, now_ns, *, producer):
|
||||
"""Fresh physical braking cannot turn an accepted LAT-only source wait fatal."""
|
||||
from openpilot.starpilot.aol.runtime import SAFETY_MAX_AGE_NS
|
||||
from openpilot.starpilot.nostalgia import physical_cancel
|
||||
from opendbc.car.structs import car
|
||||
prior = feedback.signature
|
||||
return bool(sdgm_alpha_lateral_owner(cp) and feedback.lateral_only_baseline and
|
||||
return bool(qualified_gm(cp) and aol_enabled(cp) and feedback.ever_lateral_active and
|
||||
not feedback.failed and not feedback.pending_event and prior is not None and
|
||||
prior[0] == feedback.session and prior[7] == feedback.session and
|
||||
prior[14:16] == (producer, feedback.availability_epoch) and
|
||||
@@ -134,10 +135,15 @@ def sdgm_lateral_pedal_source_zero(feedback, cp, cs, axis, native, now_ns, *, pr
|
||||
str(axis.faultReason) == 'availabilityPause' and axis.sourceCarStateMonoTime == 0 and
|
||||
axis.desiredLateral and axis.nativeAcknowledged and not axis.desiredLongitudinal and
|
||||
not axis.lateralActive and not axis.longitudinalActive and
|
||||
sdgm_alpha_pedal_withdrawal(cp, cs) and not cs.gasPressed and not cs.seatbeltUnlatched and
|
||||
cs.canValid and not cs.canTimeout and cs.cruiseState.available and
|
||||
not cs.steerFaultTemporary and not cs.steerFaultPermanent and not cs.accFaulted and
|
||||
cs.gearShifter in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low,
|
||||
car.CarState.GearShifter.sport, car.CarState.GearShifter.brake,
|
||||
car.CarState.GearShifter.manumatic) and
|
||||
(cs.brakePressed or cs.regenBraking) and not cs.doorOpen and not cs.steeringDisengage and
|
||||
not cs.gasPressed and not cs.seatbeltUnlatched and
|
||||
not physical_cancel(cs, cp) and native is not None and
|
||||
native.requestedLateral and native.lateralAllowed and
|
||||
not native.requestedLongitudinal and not native.longitudinalAllowed)
|
||||
native.requestedLateral and native.lateralAllowed)
|
||||
|
||||
|
||||
def cold_gm_main_intent_current(feedback, sm, cp, cs, axis, native, now_ns, *, producer):
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "0871a6b9131b55f6b8f2838589b548cd3ab52825acb1a6c367da7512c000aa76", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "a0affde1033797b56d8d350d2d6b221f689cc4b52aa9844cc4f8478ba00ff442", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user