diff --git a/openpilot/starpilot/aol/source_availability.py b/openpilot/starpilot/aol/source_availability.py index 38a7d1e8de..51253ec2ac 100644 --- a/openpilot/starpilot/aol/source_availability.py +++ b/openpilot/starpilot/aol/source_availability.py @@ -46,11 +46,17 @@ def stock_lateral_role(self, CS, native, intent=None, *, allow_independent_cance settings = getattr(self, 'aol_settings', None) previous = getattr(self, 'aol_axis_decision', None) - gas_role = bool(self.CP.brand == 'hyundai' and len(self.CP.safetyConfigs) == 1 and - settings is not None and settings.enabled and CS.gasPressed and not CS.brakePressed and not CS.regenBraking and - previous is not None and previous.lateral_active and previous.native_acknowledged and not previous.longitudinal_active and - CS.gearShifter == car.CarState.GearShifter.drive and isfinite(CS.vEgoRaw) and isfinite(CS.vEgo) and - stock_canfd_qualified(self.CP, marked_only=True)) + from opendbc.car.gm.aol import qualified_gm + + gas_role = bool((self.CP.brand == 'hyundai' and len(self.CP.safetyConfigs) == 1 and + stock_canfd_qualified(self.CP, marked_only=True) or self.CP.brand == 'gm' and qualified_gm(self.CP) and self.CP.pcmCruise and + not self.CP.openpilotLongitudinalControl) and settings is not None and settings.enabled and + CS.gasPressed and not CS.brakePressed and not CS.regenBraking and + previous is not None and (previous.lateral_active or self.CP.brand == 'gm' and + getattr(self, 'aol_fault_reason', 'none') == 'availabilityPause' and previous.desired_lateral) and + previous.native_acknowledged and not previous.longitudinal_active and + (CS.gearShifter == car.CarState.GearShifter.drive or self.CP.brand == 'gm' and + CS.gearShifter == car.CarState.GearShifter.low) and isfinite(CS.vEgoRaw) and isfinite(CS.vEgo)) rules = self.events.event_rules stock_user = stock_lateral_user_event(self.CP) users = [name for name in self.events.names if ET.USER_DISABLE in rules.get(name, {})] diff --git a/openpilot/starpilot/aol/tests/test_gm_stock_gas_source_gap.py b/openpilot/starpilot/aol/tests/test_gm_stock_gas_source_gap.py new file mode 100644 index 0000000000..c3eff9c29a --- /dev/null +++ b/openpilot/starpilot/aol/tests/test_gm_stock_gas_source_gap.py @@ -0,0 +1,111 @@ +"""Stock LONG-off source gaps preserve GM LAT intent, never expired output.""" +from contextlib import ExitStack +from unittest.mock import patch + +import pytest +from opendbc.car.gm.tests.test_bolt_factory_acc import factory_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 messaging +from openpilot.common.params import Params +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.test_input_deadline_continuity import intent +from openpilot.starpilot.aol.tests import test_gm as gm_fixture +from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture + +BASE = 10_000_000_000 + + +def params(name): + if name == 'ascm': + return ordinary_params(CAR.CHEVROLET_VOLT_ASCM, alpha=False, sascm=True, radar=True) + if name == 'sdgm': + return sdgm_params(alpha=False, brake_c9=True) + return factory_params(alpha=False) + + +@pytest.mark.parametrize('name', ['ascm', 'sdgm', 'bolt_acc']) +@pytest.mark.parametrize('fault', ['none', 'native', 'rx', 'can', 'eps', 'cancel', 'new_gas', 'expired', 'model']) +def test_accepted_stock_gas_gap_reaches_card_without_takeover_or_output(name, fault): + with OpenpilotPrefix(), ExitStack() as cleanup: + saved = Params() + saved.put_bool('AlwaysOnLateral', True, block=True) + card = gm_fixture.TestGmAol.card(params(name), saved) + cleanup.callback(card.vehicle_startup.close) + card.slc_producer_session = 'authority-card' + card.aol_card_intent.allowed_latch = True + case = fixture.IoniqAvailabilityTests(methodName='runTest') + cleanup.callback(case.doCleanups) + sd, controls, step = case._authority_callers(False, cp=card.CP.as_reader().as_builder(), genuine_events=True) + sd.aol_settings = read_settings(sd.params) + def receive(now): + frames = [sd.pm.messages[n] for n 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): + state = fixture.car_state() + state.gearShifter = fixture.car.CarState.GearShifter.low + state.vEgo = state.vEgoRaw = 12.5 + state.cruiseState.enabled = False + axis, host, command = step(tick, observed_state=state, gas=fault != 'new_gas', steering=True, + native_request_long=False, panda_controls_allowed=False) + assert axis.lateralActive and command.latActive and not command.longActive and not host.enabled + assert not receive(BASE + tick * 10_000_000) + original_deadline = axis.validUntilMonoTime + cs = sd.CS_prev + sd.car_state_samples = () + if fault in ('can', 'eps', 'new_gas'): + cs = cs.as_builder() + if fault == 'can': + cs.canValid = False + elif fault == 'eps': + cs.steerFaultPermanent = True + else: + cs.gasPressed = True + elif fault == 'native': + sd.sm.valid['aolSafetyWire'] = False + elif fault == 'rx': + bad = messaging.new_message('pandaStates', len(sd.sm['pandaStates']), valid=True, logMonoTime=BASE + 10_000_000) + for slot, previous in zip(bad.pandaStates, sd.sm['pandaStates'], strict=True): + slot.from_dict(previous.to_dict()) + slot.safetyRxChecksInvalid = True + sd.sm.update_msgs((BASE + 10_000_000) / 1e9, [bad.as_reader()]) + elif fault == 'cancel': + sd.events.add(EventName.buttonCancel) + elif fault == 'model': + sd.sm.valid['modelV2'] = False + now = BASE + (220_000_001 if fault == 'expired' else 41_372_648) + with patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', return_value=now): + sd.publish_axis_proof(cs) + paused = sd.pm.messages['aolAxisState'].aolAxisState + assert not paused.lateralActive and not paused.longitudinalActive and paused.sourceCarStateMonoTime == 0 + # Original Controls decision cannot command after its own original deadline. + with patch('openpilot.selfdrive.controls.controlsd.time.monotonic_ns', return_value=max(now, original_deadline + 1)): + expired, _ = controls.state_control() + assert not expired.latActive and not expired.longActive + if fault != 'none': + assert sd.aol_authority_lost and EventName.controlsMismatch in sd.events.names + return + assert str(paused.faultReason) == 'availabilityPause' and paused.desiredLateral and paused.nativeAcknowledged + assert not sd.aol_authority_lost and EventName.controlsMismatch not in sd.events.names + assert not receive(now) + assert card.aol_transport_feedback.retention_verified and not card.aol_transport_feedback.failed + for tick in (6, 10): + held, host, command = step(tick, timeout=True, state_stamp=BASE + 10_000_000, + intent_message=intent(BASE + 10_000_000, 2), observed_state=state, gas=True, steering=True, + native_request_long=False, panda_controls_allowed=False) + assert str(held.faultReason) == 'availabilityPause' and held.sourceCarStateMonoTime == 0 + assert not held.lateralActive and not held.longitudinalActive and not sd.aol_authority_lost + assert not command.latActive and not command.longActive and not host.enabled + assert EventName.controlsMismatch not in sd.events.names and not receive(BASE + tick * 10_000_000) + axis, host, command = step(11, gas=True, steering=True, observed_state=state, native_request_long=False, + panda_controls_allowed=False) + assert axis.lateralActive and command.latActive and not command.longActive and not host.enabled diff --git a/openpilot/starpilot/aol/transport_pause.py b/openpilot/starpilot/aol/transport_pause.py index 0adc07652b..ecbe9f226b 100644 --- a/openpilot/starpilot/aol/transport_pause.py +++ b/openpilot/starpilot/aol/transport_pause.py @@ -122,6 +122,9 @@ def stock_lateral_off(cp, cs, native, *, allow_independent_cancel=False, allow_i from openpilot.starpilot.car.toyota.aol import qualified as toyota_qualified + from opendbc.car.gm.aol import qualified_gm + + gm_stock_gas = bool(cp.brand == 'gm' and qualified_gm(cp) and cp.pcmCruise and not cp.openpilotLongitudinalControl) independent_angle = angle_qualified(cp, marked_only=True) independent_brake = independent_angle or (allow_independent_brake and ( toyota_qualified(cp, marked_only=True) and not cs.cruiseState.enabled or @@ -132,7 +135,7 @@ def stock_lateral_off(cp, cs, native, *, allow_independent_cancel=False, allow_i not cs.accFaulted and not cs.doorOpen and not cs.seatbeltUnlatched and (not cs.brakePressed or independent_brake and allow_independent_brake) and not cs.regenBraking and (not cs.gasPressed or independent_angle or - allow_independent_gas and (toyota_qualified(cp, marked_only=True) or + allow_independent_gas and (gm_stock_gas or toyota_qualified(cp, marked_only=True) or cp.brand == 'hyundai' and stock_canfd_qualified(cp, marked_only=True))) and (not physical_cancel(cs, cp) or independent_angle and allow_independent_cancel) and native is not None and native.requestedLateral and native.lateralAllowed and not native.requestedLongitudinal) @@ -870,11 +873,15 @@ class TransportPauseFeedback: toyota_pedal = bool(toyota_qualified(cp, marked_only=True) and cs.brakePressed != cs.gasPressed and not cs.regenBraking and self.lateral_only_baseline and not native.requestedLongitudinal and not native.longitudinalAllowed) + from opendbc.car.gm.aol import qualified_gm + gm_stock_gas = bool(cp.brand == 'gm' and qualified_gm(cp) and cp.pcmCruise and not cp.openpilotLongitudinalControl and + cs.gasPressed and not cs.brakePressed and not cs.regenBraking and self.lateral_only_baseline and + not native.requestedLongitudinal and not native.longitudinalAllowed) stock_user = stock_lateral_user_event(cp) stock_lateral = bool(reason == 'availabilityPause' and axis.sourceCarStateMonoTime == 0 and not axis.lateralActive and not axis.longitudinalActive and not axis.desiredLongitudinal and stock_lateral_off(cp, cs, native, allow_independent_brake=toyota_pedal, - allow_independent_gas=toyota_pedal and cs.gasPressed) and previous is not None and + allow_independent_gas=gm_stock_gas or toyota_pedal and cs.gasPressed) and previous is not None and previous[0] == self.session and previous[7] == self.session and previous[14] == intent_producer_session and previous[15] == self.availability_epoch and (previous[11] and not previous[12] and previous[13] and not previous[10] and diff --git a/prebuilt.json b/prebuilt.json index a28e35e6a9..07c82a04e4 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "373b7db8d438a42c2c6f34ebe80638bdedd8412e86073f52d1dc7f8fcd9c3dc8", "schema_version": 1} +{"protected_inventory_sha256": "9557b6d99d89bebb534bfae00e1d9ac6c5998d838daf45c3065969329226a663", "schema_version": 1}