From 438d015b7ae04a7a1befaa9e589c0de1e2b655e8 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 9 Nov 2026 16:28:34 -0600 Subject: [PATCH] Preserve GM stop-and-go authority at zero minimum speed --- openpilot/selfdrive/car/car_events.py | 3 +- .../aol/tests/test_gm_zero_engage_minimum.py | 81 +++++++++++++++++++ prebuilt.json | 2 +- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 openpilot/starpilot/aol/tests/test_gm_zero_engage_minimum.py diff --git a/openpilot/selfdrive/car/car_events.py b/openpilot/selfdrive/car/car_events.py index 35ffe7c631..0605b2bb7f 100644 --- a/openpilot/selfdrive/car/car_events.py +++ b/openpilot/selfdrive/car/car_events.py @@ -85,7 +85,8 @@ class CarEvents: events.add(EventName.buttonEnable) # Enabling at a standstill with brake is allowed # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs - if CS.vEgo < self.CP.minEnableSpeed and not (CS.standstill and CS.brakePressed and + # Zero permits standstill, including a filtered-speed undershoot below zero. + if self.CP.minEnableSpeed > 0. and CS.vEgo < self.CP.minEnableSpeed and not (CS.standstill and CS.brakePressed and self.CP.networkLocation == NetworkLocation.fwdCamera): events.add(EventName.belowEngageSpeed) if CS.cruiseState.standstill: diff --git a/openpilot/starpilot/aol/tests/test_gm_zero_engage_minimum.py b/openpilot/starpilot/aol/tests/test_gm_zero_engage_minimum.py new file mode 100644 index 0000000000..b8cfb760aa --- /dev/null +++ b/openpilot/starpilot/aol/tests/test_gm_zero_engage_minimum.py @@ -0,0 +1,81 @@ +"""A zero cruise engagement floor must retain stop-and-go authority.""" +from contextlib import ExitStack + +import pytest +from opendbc.car import structs +from opendbc.car.gm.tests.test_ordinary_camera import params +from opendbc.car.gm.values import CAR, GMFlags +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.selfdrive.car.car_events import CarEvents, EventName +from openpilot.starpilot.aol.intent import read_settings +from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture + + +def truck_params(): + cp = params(CAR.CHEVROLET_SILVERADO, alpha=True) + cp.flags |= int(GMFlags.HAS_BSM) + assert cp.minEnableSpeed == 0 and cp.openpilotLongitudinalControl and not cp.pcmCruise + return cp + + +def stopped(speed): + cs = fixture.car_state() + cs.vEgo, cs.vEgoRaw, cs.standstill = speed, 0., True + cs.cruiseState.available = cs.cruiseState.enabled = cs.cruiseState.standstill = True + return cs + + +@pytest.mark.parametrize('speed', [-.04, -.00876117, -.0000004, 0., .02]) +def test_zero_minimum_accepts_filtered_standstill_undershoot(speed): + cp = truck_params() + cs = stopped(speed) + events = CarEvents(cp).update(cs, cs, structs.CarControl(enabled=True)) + assert EventName.belowEngageSpeed not in events.names + assert EventName.resumeRequired in events.names + + +@pytest.mark.parametrize('minimum', [1.3888889, 10.72896]) +@pytest.mark.parametrize('speed', [-.04, 0., .5, 1.]) +def test_positive_cruise_minimum_remains_enforced(minimum, speed): + cp = truck_params() + cp.minEnableSpeed = minimum + cs = stopped(speed) + cs.standstill = speed <= 0. + events = CarEvents(cp).update(cs, cs, structs.CarControl(enabled=True)) + assert EventName.belowEngageSpeed in events.names + + +@pytest.mark.parametrize('guard,expected', [('reverse', EventName.reverseGear), ('park', EventName.wrongGear), + ('acc', EventName.accFaulted), ('brake', EventName.preEnableStandstill)]) +def test_zero_minimum_preserves_physical_guards(guard, expected): + cp = truck_params() + cs, previous = stopped(-.008), stopped(0.) + if guard in ('reverse', 'park'): + cs.gearShifter = guard + elif guard == 'acc': + cs.accFaulted = True + else: + cs.brakePressed = True + events = CarEvents(cp).update(cs, previous, structs.CarControl(enabled=True)) + assert expected in events.names + + +def test_combined_authority_survives_repeated_standstill_undershoot(): + with OpenpilotPrefix(), ExitStack() as cleanup: + case = fixture.IoniqAvailabilityTests(methodName='runTest') + cleanup.callback(case.doCleanups) + sd, _, step = case._authority_callers(True, cp=truck_params(), genuine_events=True) + sd.aol_settings = read_settings(sd.params) + for tick, speed in enumerate((.02, 0., -.00876117, -.0001, 0., .002) * 4): + axis, host, control = step(tick, observed_state=stopped(speed)) + assert host.enabled and host.active + assert EventName.belowEngageSpeed not in sd.events.names + assert axis.desiredLongitudinal and axis.longitudinalActive and axis.nativeAcknowledged + assert control.longActive + # The eligibility correction cannot bypass a native denial or driver brake. + _, _, control = step(24, observed_state=stopped(0.), long_allowed=False) + assert not control.longActive + cs = stopped(0.) + cs.brakePressed = True + _, _, control = step(25, observed_state=cs) + assert not control.longActive diff --git a/prebuilt.json b/prebuilt.json index 9117c39c78..39250dab58 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "8f7fd549cacfbb5446c8d17beaab23058f0fe4aa281103879f0c0ea7518a783a", "schema_version": 1} +{"protected_inventory_sha256": "ce5c13c592d47b0af435cf12ab99363accf3127421eaa4bcae7a2e2ae60de4a0", "schema_version": 1}