Separate Volt CC steering minimum from cruise engagement

This commit is contained in:
firestar5683
2026-10-29 19:19:20 -05:00
parent ad7bfa4899
commit 4dd24beabc
5 changed files with 83 additions and 8 deletions
+9 -1
View File
@@ -19,6 +19,8 @@ class CarEvents:
self.CP = CP
self.event_rules = event_rules(CP)
from opendbc.car.gm.values import is_volt_cc_profile
self.volt_cc_profile = CP.brand == "gm" and is_volt_cc_profile(CP)
self.steering_unpressed = 0
self.low_speed_alert = False
self.no_steer_warning = False
@@ -74,6 +76,12 @@ class CarEvents:
events.add(EventName.manualRestart)
elif self.CP.brand == 'gm':
if self.volt_cc_profile:
# Conventional LONG requires the car's cruise acknowledgment. MAIN alone
# still arms independent steering and cannot create a LONG engagement.
if (CS.canValid and not CS.canTimeout and CS.cruiseState.available and CS.cruiseState.enabled and
(CS.buttonEnable or not CS_prev.cruiseState.enabled)):
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
@@ -152,7 +160,7 @@ class CarEvents:
events.add(EventName.invalidLkasSetting)
if CS.lowSpeedAlert:
events.add(EventName.belowSteerSpeed)
if CS.buttonEnable:
if CS.buttonEnable and not self.volt_cc_profile:
events.add(EventName.buttonEnable)
# Handle cancel button presses
@@ -26,8 +26,8 @@ def test_cold_park_main_waits_without_losing_intent(index, main_action):
_exercise_parked_main(index, main_action)
def _exercise_parked_main(index, main_action, *, below_engage=False):
cp = list(cases())[index]
def _exercise_parked_main(index, main_action, *, below_engage=False, cp=None):
cp = list(cases())[index] if cp is None else cp
case = fixture.IoniqAvailabilityTests(methodName='runTest')
with OpenpilotPrefix(), ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
@@ -44,6 +44,9 @@ def _exercise_parked_main(index, main_action, *, below_engage=False):
cleanup.callback(card.vehicle_startup.close)
card.ci_initialized = True
card.car_params_published = True
if getattr(card, 'volt_cc_selected', False):
card.volt_cc_boot_offset_ns = 0
card.volt_cc_source_floor_ns = 9_000_000_000
card.sm.frame = 1
assert card.CP.to_dict() == sd.CP.to_dict() == controls.CP.to_dict()
original_pm = card.pm
@@ -159,6 +162,11 @@ def _exercise_parked_main(index, main_action, *, below_engage=False):
def receive_card(now):
nonlocal last_control_stamp
rows = list(wire_batch(now))
if getattr(card, 'volt_cc_selected', False):
device = messaging.new_message('deviceState', valid=True, logMonoTime=now)
device.deviceState.started = True
device.deviceState.startedMonoTime = 9_000_000_000
rows.append(device.as_reader())
rows += [sd.pm.messages[k] for k in ('aolAxisState', 'selfdriveState', 'onroadEvents') if k in sd.pm.messages]
published = controls.pm.messages['carControl']
if published.logMonoTime != last_control_stamp:
@@ -178,10 +186,19 @@ def _exercise_parked_main(index, main_action, *, below_engage=False):
def physical(tick, stamp, driving):
speed = max(4.0, cp.minSteerSpeed + 2.0)
if below_engage:
speed = cp.minSteerSpeed + 0.05
assert speed < cp.minEnableSpeed
else:
speed = max(speed, cp.minEnableSpeed + 2.0)
packets = parsed_packets(cp, packer, stamp, tick, speed if driving else 0.0, True)
from opendbc.car.gm.values import CAR, is_volt_cc_f1_profile
if is_volt_cc_f1_profile(cp):
packets = [f for f in packets if f[0] != 0xBE]
packets.append(packer.make_can_msg('EBCMBrakePedalPosition', 0, {}))
if cp.carFingerprint == CAR.CHEVROLET_VOLT_CC and tick in (320, 321):
button = packer.make_can_msg('ASCMSteeringButton', 0,
{'ACCButtons': 3 if tick == 320 else 1, 'RollingCounter': tick % 4})
packets = [f for f in packets if f[0] != button[0]] + [button]
packets = [f for f in packets if f[0] not in (0x1F5, 0x12A, 0x184, 0x17D)]
if cp.pcmCruise and cp.networkLocation == structs.CarParams.NetworkLocation.fwdCamera:
address = packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {})[0]
@@ -210,7 +227,7 @@ def _exercise_parked_main(index, main_action, *, below_engage=False):
assert safety.safety_config_valid() and safety.aol_get_permission_mask() == 0
tx = 0
active = 0
for tick in range(1, 381):
for tick in range(1, 581 if below_engage and cp.carFingerprint == "CHEVROLET_VOLT_CC" else 381):
now = 10_020_000_000 + (tick - 1) * 10_000_000
driving = tick > 300
safety.set_timer(now // 1000)
@@ -0,0 +1,50 @@
from dataclasses import replace
import pytest
from opendbc.car import structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gm.tests.test_volt_cc_f1 import params as f1_params
from opendbc.car.gm.tests.test_volt_cc_gas import fixture as be_fixture
from openpilot.cereal import log
from openpilot.selfdrive.car.car_events import CarEvents
from openpilot.starpilot.aol.tests.test_gm_cc_lateral_minimum import setup
from openpilot.starpilot.aol.tests.test_gm_parked_startup import _exercise_parked_main
from openpilot.starpilot.car.gm.aol import cc_independent_lateral_below_engage
def params(f1):
cp = f1_params() if f1 else be_fixture()[0]
cp.alternativeExperience = 32
return cp
@pytest.mark.parametrize('f1', [False, True])
def test_volt_cc_steering_minimum_keeps_cruise_minimum(f1):
cp = params(f1)
_, intent = setup()
assert cp.minSteerSpeed == pytest.approx(7 * CV.MPH_TO_MS)
assert cp.minEnableSpeed == pytest.approx(24 * CV.MPH_TO_MS)
assert cc_independent_lateral_below_engage(cp, intent, enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, replace(intent, allowedLatch=False), enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, intent, enabled=True, active=True)
@pytest.mark.parametrize('f1', [False, True])
@pytest.mark.parametrize('active,previous,button,valid,expected', [
(False, False, True, True, False), (True, False, False, True, True),
(True, True, True, True, True), (True, True, False, True, False),
(True, False, False, False, False)])
def test_volt_cc_long_waits_for_real_cruise_ack(f1, active, previous, button, valid, expected):
cp = params(f1)
cs = structs.CarState(canValid=valid, buttonEnable=button, vEgo=12, gearShifter='drive')
cs.cruiseState.available = True
cs.cruiseState.enabled = active
prior = structs.CarState()
prior.cruiseState.enabled = previous
events = CarEvents(cp).update(cs, prior, structs.CarControl())
assert (log.OnroadEvent.EventName.buttonEnable in events.names) == expected
@pytest.mark.parametrize('f1', [False, True])
def test_volt_cc_main_already_on_arms_steering_at_seven_mph(f1):
_exercise_parked_main(0, 0, below_engage=True, cp=params(f1))
+3 -3
View File
@@ -29,10 +29,10 @@ def native_accepts_cp(cp, model: int, param: int) -> bool:
def cc_independent_lateral_below_engage(cp, intent, *, enabled, active):
"""An armed independent Bolt LAT request uses its steering speed gate."""
from opendbc.car.gm.values import is_bolt_cc_profile
"""An armed conventional-cruise LAT request uses its steering speed gate."""
from opendbc.car.gm.values import is_bolt_cc_profile, is_volt_cc_profile
return bool(not enabled and not active and qualified_gm(cp) and aol_enabled(cp) and
is_bolt_cc_profile(cp) and intent is not None and intent.settingsQualified and
(is_bolt_cc_profile(cp) or is_volt_cc_profile(cp)) and intent is not None and intent.settingsQualified and
intent.allowedLatch and intent.lateralArmed and not intent.pauseLateral)
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "df360061636f084000aa4afe04c561fc519e8cebcb1c84a483a8eb716452db98", "schema_version": 1}
{"protected_inventory_sha256": "505172af4fab7bd15c27b90d3c52205e2a64db67875aa1447008cc0ed932c5dc", "schema_version": 1}