mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-05 03:54:04 +08:00
Avoid takeover for expired idle GM cruise-main intent
This commit is contained in:
@@ -1313,7 +1313,8 @@ class SelfdriveD:
|
||||
previous = self.aol_axis_decision
|
||||
was_enabled = self.enabled
|
||||
self.aol_axis_decision = AxisDecision()
|
||||
if healthy_expiry and not was_enabled and self.cold_gm_main_readiness(CS, intent, now_ns):
|
||||
if healthy_expiry and not was_enabled and (self.cold_gm_main_readiness(CS, intent, now_ns) or
|
||||
self.cold_gm_main_readiness(CS, intent, now_ns, recovering=True)):
|
||||
self.aol_fault_reason = 'availabilityPause'
|
||||
self.aol_publication_retained_intent = intent
|
||||
return
|
||||
@@ -1790,17 +1791,17 @@ class SelfdriveD:
|
||||
native = _healthy_transport_native(self.sm, self.CP, CS, self.aol_session_id, now_ns)
|
||||
return cold_park_readiness(self.CP, CS, native, self.events.to_msg())
|
||||
|
||||
def cold_gm_main_readiness(self, CS, intent, now_ns):
|
||||
def cold_gm_main_readiness(self, CS, intent, now_ns, *, recovering=False):
|
||||
from openpilot.starpilot.car.gm.aol import cold_main_readiness
|
||||
from openpilot.starpilot.aol.transport_pause import _healthy_transport_native
|
||||
if (self.CP.brand != 'gm' or self.enabled or self.active or getattr(self, 'aol_ever_active', False) or self.aol_authority_lost or
|
||||
not self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE) or
|
||||
not recovering and not self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE) or
|
||||
intent is None or not intent.settingsQualified or not intent.allowedLatch or not intent.lateralArmed or
|
||||
not 0 < intent.carStateLogMonoTime <= intent.observedMonoTime <= now_ns <= intent.validUntilMonoTime or
|
||||
now_ns - intent.carStateLogMonoTime > SAFETY_MAX_AGE_NS):
|
||||
return False
|
||||
native = _healthy_transport_native(self.sm, self.CP, CS, self.aol_session_id, now_ns)
|
||||
return cold_main_readiness(self.CP, CS, native, self.events.to_msg())
|
||||
return cold_main_readiness(self.CP, CS, native, self.events.to_msg(), recovering=recovering)
|
||||
|
||||
def ordinary_native_long_withdrawal(self, CS, native, previous, requested, intent, *, source_current, now_ns):
|
||||
"""Keep the existing ordinary Panda mismatch owner for a LONG-only drop.
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
"""Expired MAIN intent cannot imply previously active steering."""
|
||||
from contextlib import ExitStack
|
||||
from dataclasses import replace
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car.gm.tests.test_bolt_pedal import params
|
||||
from opendbc.car.gm.tests.test_bolt_volt_configurations import ordinary_params
|
||||
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
|
||||
from opendbc.car.gm.values import CAR
|
||||
from openpilot.cereal import log
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
|
||||
from openpilot.starpilot.aol.tests.test_input_deadline_continuity import BASE
|
||||
|
||||
|
||||
def configuration(name):
|
||||
if name == 'ascm':
|
||||
return ordinary_params(CAR.CHEVROLET_VOLT_ASCM, alpha=True, sascm=True, radar=True)
|
||||
if name == 'sdgm':
|
||||
return sdgm_params(alpha=True, brake_c9=True)
|
||||
return params(getattr(CAR, name), pedal=True, camera=True)
|
||||
|
||||
|
||||
CONFIGS = ['ascm', 'sdgm', 'CHEVROLET_BOLT_CC_2017', 'CHEVROLET_BOLT_CC_2018_2021',
|
||||
'CHEVROLET_BOLT_CC_2022_2023', 'CHEVROLET_BOLT_ACC_2022_2023_PEDAL']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name', CONFIGS)
|
||||
@pytest.mark.parametrize('fault', ['none', 'prior_active', 'enabled', 'native_missing', 'native_granted',
|
||||
'can_invalid', 'eps', 'relay', 'authority_lost', 'intent_expired'])
|
||||
def test_initialized_idle_main_expiry_keeps_commands_off_and_real_faults(name, fault):
|
||||
case = fixture.IoniqAvailabilityTests(methodName='runTest')
|
||||
with OpenpilotPrefix(), ExitStack() as cleanup:
|
||||
cleanup.callback(case.doCleanups)
|
||||
sd, controls, step = case._authority_callers(False, cp=configuration(name), genuine_events=True)
|
||||
cs = fixture.car_state(brake=True)
|
||||
cs.vEgo = cs.vEgoRaw = -1.9
|
||||
cs.gearShifter = fixture.car.CarState.GearShifter.low
|
||||
axis, _, command = step(0, observed_state=cs, steering=True, native_request_lat=False,
|
||||
native_request_long=False, allowed=False, long_allowed=False)
|
||||
assert sd.initialized and not getattr(sd, 'aol_ever_active', False)
|
||||
assert not axis.lateralActive and not axis.longitudinalActive
|
||||
assert not command.latActive and not command.longActive
|
||||
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
|
||||
if fault == 'prior_active':
|
||||
sd.aol_ever_active = True
|
||||
elif fault == 'enabled':
|
||||
sd.enabled = sd.active = True
|
||||
elif fault == 'can_invalid':
|
||||
cs.canValid = False
|
||||
elif fault == 'eps':
|
||||
cs.steerFaultPermanent = True
|
||||
elif fault == 'relay':
|
||||
sd.events.add(log.OnroadEvent.EventName.relayMalfunction)
|
||||
elif fault == 'authority_lost':
|
||||
sd.aol_authority_lost = True
|
||||
now = BASE + (201_000_000 if fault == 'intent_expired' else 40_000_000)
|
||||
native = sd.aol_decision_native
|
||||
if fault == 'native_missing':
|
||||
native = None
|
||||
elif fault == 'native_granted':
|
||||
native = replace(native, requestedLateral=True, lateralAllowed=True)
|
||||
with patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', return_value=now), \
|
||||
patch('openpilot.starpilot.aol.transport_pause.current_native', return_value=native):
|
||||
sd._expire_axis_source(cs, now)
|
||||
assert sd.aol_car_state_log_ns == 0 and not sd.conditional_car_state_valid
|
||||
assert not sd.aol_axis_decision.lateral_active and not sd.aol_axis_decision.longitudinal_active
|
||||
assert not sd.enabled and not sd.active
|
||||
mismatch = log.OnroadEvent.EventName.controlsMismatch in sd.events.names
|
||||
assert mismatch == (fault != 'none')
|
||||
assert sd.aol_authority_lost == (fault != 'none')
|
||||
if fault == 'none':
|
||||
assert sd.aol_fault_reason == 'availabilityPause'
|
||||
assert sd.aol_publication_retained_intent.allowedLatch
|
||||
# Only a new original input plus the normal native handshake can engage.
|
||||
recovered, _, command = step(5, observed_state=cs, steering=True, native_request_lat=False,
|
||||
native_request_long=False, allowed=False, long_allowed=False)
|
||||
assert not recovered.lateralActive and not recovered.longitudinalActive
|
||||
assert not command.latActive and not command.longActive
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name', CONFIGS)
|
||||
def test_idle_expiry_during_publication_never_sounds_takeover(name):
|
||||
from openpilot.starpilot.aol.tests.test_input_deadline_continuity import intent
|
||||
case = fixture.IoniqAvailabilityTests(methodName='runTest')
|
||||
with OpenpilotPrefix(), ExitStack() as cleanup:
|
||||
cleanup.callback(case.doCleanups)
|
||||
sd, _, step = case._authority_callers(False, cp=configuration(name), genuine_events=True)
|
||||
cs = fixture.car_state(brake=True)
|
||||
cs.vEgo = cs.vEgoRaw = -1.9
|
||||
cs.gearShifter = fixture.car.CarState.GearShifter.low
|
||||
now = [BASE + 29_000_000]
|
||||
def finish_environment(_cs):
|
||||
now[0] = BASE + 40_000_000
|
||||
with patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', side_effect=lambda: now[0]), \
|
||||
patch.object(sd, 'update_conditional_mode', side_effect=finish_environment):
|
||||
axis, state, command = step(2, observed_state=cs, steering=True, state_stamp=BASE,
|
||||
intent_message=intent(BASE, 3), native_request_lat=False, native_request_long=False,
|
||||
allowed=False, long_allowed=False)
|
||||
assert sd.initialized and not getattr(sd, 'aol_ever_active', False)
|
||||
assert not axis.lateralActive and not axis.longitudinalActive
|
||||
assert not command.latActive and not command.longActive and not state.enabled
|
||||
assert not sd.aol_authority_lost
|
||||
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
|
||||
assert str(state.alertType) != 'controlsMismatch/immediateDisable'
|
||||
@@ -1625,13 +1625,17 @@ class IpcAxisContractTests(unittest.TestCase):
|
||||
self.assertEqual(sd.aol_axis_decision.mode, 'off') # Zero-axis native acknowledgment grants no actuation.
|
||||
sm.seen['aolSafetyWire'] = False
|
||||
sd.step()
|
||||
self.assertIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
|
||||
self.assertTrue(sd.events.contains(ET.IMMEDIATE_DISABLE))
|
||||
self.assertIn(log.OnroadEvent.EventName.commIssue, sd.events.names)
|
||||
self.assertNotIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
|
||||
self.assertFalse(sd.events.contains(ET.IMMEDIATE_DISABLE))
|
||||
self.assertEqual(sd.aol_axis_decision.mode, 'off')
|
||||
sd.initialized = False
|
||||
sm.frame = 601
|
||||
sd.step()
|
||||
self.assertTrue(sd.initialized) # Existing six-second timeout still exposes a genuine missing receipt.
|
||||
self.assertIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
|
||||
self.assertTrue(sd.initialized) # The timeout exposes missing startup transport without inventing active control.
|
||||
self.assertIn(log.OnroadEvent.EventName.commIssue, sd.events.names)
|
||||
self.assertNotIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
|
||||
self.assertEqual(sd.aol_axis_decision.mode, 'off')
|
||||
|
||||
def test_selected_volt_camera_bootstrap_waits_for_native(self):
|
||||
self._assert_sibling_bootstrap(transport=False)
|
||||
@@ -1873,13 +1877,17 @@ class IpcAxisContractTests(unittest.TestCase):
|
||||
self.assertEqual(sd.aol_axis_decision.mode, 'off') # Zero-axis native acknowledgment grants no actuation.
|
||||
sm.seen['aolSafetyWire'] = False
|
||||
sd.step()
|
||||
self.assertIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
|
||||
self.assertTrue(sd.events.contains(ET.IMMEDIATE_DISABLE))
|
||||
self.assertIn(log.OnroadEvent.EventName.commIssue, sd.events.names)
|
||||
self.assertNotIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
|
||||
self.assertFalse(sd.events.contains(ET.IMMEDIATE_DISABLE))
|
||||
self.assertEqual(sd.aol_axis_decision.mode, 'off')
|
||||
sd.initialized = False
|
||||
sm.frame = 601
|
||||
sd.step()
|
||||
self.assertTrue(sd.initialized) # Existing six-second timeout still exposes a genuine missing receipt.
|
||||
self.assertIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
|
||||
self.assertTrue(sd.initialized) # The timeout exposes missing startup transport without inventing active control.
|
||||
self.assertIn(log.OnroadEvent.EventName.commIssue, sd.events.names)
|
||||
self.assertNotIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
|
||||
self.assertEqual(sd.aol_axis_decision.mode, 'off')
|
||||
|
||||
def test_native_receipt_gates_engagement_and_disables_on_loss(self):
|
||||
class SM:
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "fbb3e2ddd78e60eccb4bd06eb8633ffe204a8ad70bd67bec4fa38bb54d22e26b", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "41ec2d371c8336c5f9e6fac218fbc36052d32f7eabc441fbb6977983f5328ce6", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user