Restore flashed Volt LKAS button mapping

This commit is contained in:
firestar5683
2026-10-30 16:53:57 -05:00
parent 5bedae0794
commit 2487154a1e
5 changed files with 133 additions and 7 deletions
+12 -1
View File
@@ -70,6 +70,8 @@ class CarState(CarStateBase):
self.buttons_counter = 0
self.distance_button = 0
self.flashed_volt_profile = is_volt_flashed_profile(CP)
self.flashed_lkas_button = None
self.pedal_sensor_healthy = False
self.pedal_sensor_ts_nanos = 0
self.bolt_pedal_gear_ts_nanos = 0
@@ -535,7 +537,7 @@ class CarState(CarStateBase):
# A missing camera can leave the ECU faulted; that is not engaged cruise.
ret.cruiseState.enabled &= pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.FAULTED
ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
if is_volt_flashed_profile(self.CP):
if self.flashed_volt_profile:
# There is no stock cruise/ACC controller. Preserve the actual EBCM fault.
ret.accFaulted = friction_unavailable
ret.brakePressed |= bool(pt_cp.vl["ECMEngineStatus"]["BrakePressed"])
@@ -684,6 +686,15 @@ class CarState(CarStateBase):
ret.buttonEvents = [event for old, new in edges for event in
create_button_events(new, old, BUTTONS_DICT, unpressed_btn=CruiseButtons.UNPRESS)] + create_button_events(
self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
if self.flashed_volt_profile:
lkas_events = []
for pressed in pt_cp.vl_all["ASCMSteeringButton"]["LKAButton"]:
if self.flashed_lkas_button is not None:
lkas_events.extend(create_button_events(pressed, self.flashed_lkas_button, {1: ButtonType.lkas}))
self.flashed_lkas_button = pressed
if lkas_events:
ret.buttonEvents = [event.to_dict() for event in ret.buttonEvents] + lkas_events
if ret.vEgo < self.CP.minSteerSpeed:
ret.lowSpeedAlert = True
+10 -4
View File
@@ -4,17 +4,17 @@ import math
from opendbc.car.gm.cancel_remap import aol_enabled, enabled as cancel_remap_enabled, supported as cancel_remap_supported
from opendbc.car.structs import CarParams
from opendbc.car.gm.values import CAR, GMFlags, control_flags, camera_acc_pedal_profile, is_volt_ascm_longitudinal
from opendbc.car.gm.values import CAR, GMFlags, control_flags, camera_acc_pedal_profile, is_volt_ascm_longitudinal, is_volt_flashed_profile
from opendbc.car.gm.aol import GM_AOL_ALTERNATIVE_EXPERIENCE, GM_AOL_WORDS, qualified_gm
from openpilot.starpilot.aol.policy import AolVehiclePolicy
from openpilot.starpilot.aol.intent import AOL_TOGGLE, AolCardIntent
from openpilot.starpilot.aol.intent import AOL_TOGGLE, PAUSE_LATERAL, AolCardIntent
def policy_for(cp) -> AolVehiclePolicy:
qualified = qualified_gm(cp)
return AolVehiclePolicy(intent_supported=qualified, settings_supported=qualified,
runtime_supported=qualified, normal_runtime_supported=qualified,
lkas_button_supported=False,
lkas_button_supported=qualified and is_volt_flashed_profile(cp),
full_axis_runtime_required=qualified and aol_enabled(cp),
alternative_experience_addition=GM_AOL_ALTERNATIVE_EXPERIENCE if qualified else 0)
@@ -205,7 +205,7 @@ class GmAolCardIntent(AolCardIntent):
"""Follow physical MAIN; a current fault inhibits intent until healthy again."""
def __init__(self, settings, cp=None):
super().__init__(settings, lkas_button_supported=False)
super().__init__(settings, lkas_button_supported=is_volt_flashed_profile(cp))
self.cp = cp
self.requires_fault_observation = True
self._main_cycle_required = False
@@ -222,6 +222,12 @@ class GmAolCardIntent(AolCardIntent):
semantic.buttonEvents = [event for event in CS.buttonEvents if str(event.type) != 'cancel']
super().update_auxiliary(semantic, media=media, media_eligible=media_eligible, fault_active=fault_active)
@property
def lkas_action(self):
# MAIN still owns automatic arming; a mapped LKAS toggle pauses steering.
action = super().lkas_action
return PAUSE_LATERAL if action == AOL_TOGGLE else action
@property
def main_action(self):
# GM MAIN reports a persistent physical level, not an independent AOL toggle.
@@ -0,0 +1,106 @@
from types import SimpleNamespace
import pytest
from opendbc.can import CANPacker
from opendbc.car import Bus, structs
from opendbc.car.gm.interface import CarInterface
from opendbc.car.gm.tests.test_volt_flashed_obd import params as flashed_params, tick
from opendbc.car.gm.values import DBC
from openpilot.common.params import Params
from openpilot.starpilot.aol.intent import AolSettings
from openpilot.starpilot.aol.tests.test_gm_startup_main import cases
from openpilot.starpilot.aol.vehicle import create_intent, policy_for
from openpilot.starpilot.controllers.wheel_actions import button_reported
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
from openpilot.starpilot.ui.feature_settings_state import row_request
def params():
cp = flashed_params()
cp.alternativeExperience = 32
return cp
def parser():
cp = params()
return CarInterface(cp), CANPacker(DBC[cp.carFingerprint][Bus.pt])
def feed_lkas(ci, packer, levels, index, cruise=1):
now = 1_000_000_000 + index * 10_000_000
_, frames = tick(SimpleNamespace(update=lambda _: None), packer, now, index)
frames = [m for m in frames if m[0] != 0x1E1]
for level in levels:
frames.append(packer.make_can_msg('ASCMSteeringButton', 0, {'ACCButtons': cruise, 'ACCAlwaysOne': 1,
'LKAButton': level, 'RollingCounter': index % 4}))
return ci.update([(now, frames)])
def test_parser_emits_each_lkas_edge_without_cruise_enable():
ci, packer = parser()
for i in range(40):
cs = feed_lkas(ci, packer, [0], i)
assert cs.canValid
cs = feed_lkas(ci, packer, [1, 0, 1, 1, 0], 40)
assert [(str(b.type), b.pressed) for b in cs.buttonEvents] == [('lkas', True), ('lkas', False), ('lkas', True), ('lkas', False)]
assert not cs.buttonEnable and not cs.cruiseState.enabled
assert not feed_lkas(ci, packer, [], 41).buttonEvents
def test_first_held_sample_is_not_a_new_press():
ci, packer = parser()
assert not feed_lkas(ci, packer, [1], 0).buttonEvents
assert not feed_lkas(ci, packer, [1], 1).buttonEvents
assert [(str(b.type), b.pressed) for b in feed_lkas(ci, packer, [0], 2).buttonEvents] == [('lkas', False)]
@pytest.mark.parametrize('mapping', [0, 3, 4, 9])
def test_main_arms_at_startup_and_lkas_only_performs_its_mapping(mapping):
cp = params()
intent = create_intent(cp, AolSettings(True, 0., mapping, 0, (0, 0, 0), (0, 0, 0)), policy_for(cp))
cs = structs.CarState(canValid=True, gearShifter='drive', vEgo=10.)
cs.cruiseState.available = True
intent.update(cs, now_ns=1, fault_active=False)
assert intent.allowed_latch and not intent.pause_lateral and not intent.pause_longitudinal
for pressed, now in [(True, 2), (False, 3)]:
cs.buttonEvents = [structs.CarState.ButtonEvent(type='lkas', pressed=pressed)]
intent.update(cs, now_ns=now, fault_active=False)
assert intent.allowed_latch and intent.pause_lateral == (mapping in (3, 9))
assert intent.pause_longitudinal == (mapping == 4)
cs.buttonEvents = []
intent.update(cs, now_ns=4, fault_active=False)
assert intent.pause_lateral == (mapping in (3, 9))
cs.buttonEvents = [structs.CarState.ButtonEvent(type='lkas', pressed=True)]
intent.update(cs, now_ns=5, fault_active=False)
assert not intent.pause_lateral and not intent.pause_longitudinal and intent.allowed_latch
cs.cruiseState.available = False
cs.buttonEvents = []
intent.update(cs, now_ns=6, fault_active=False)
assert not intent.allowed_latch
def test_lkas_row_is_visible_and_assignment_uses_actual_settings_owner(tmp_path):
cp = params()
saved = Params(str(tmp_path))
owner = FeatureSettingsOwner(saved, lambda _: True, vehicle_fingerprint=lambda: str(cp.carFingerprint), vehicle_params=lambda: cp)
page = owner.snapshot('wheel', parked=True, system_long=True, lateral_context=True, metric=False)
row = next(r for r in page.rows if r.key == 'wheel:LKASButtonControl')
assert row.value == 'Off' and row.available and 'Pause steering' in row.choices and 'Toggle AOL' in row.choices
assert owner.apply(row_request(row, 'Pause steering'))
assert saved.get('LKASButtonControl') == 3
def test_other_gm_profiles_keep_the_existing_lkas_contract():
for cp in cases():
assert not policy_for(cp).lkas_button_supported
assert not button_reported(cp, 'LKASButtonControl')
def test_lkas_does_not_erase_a_simultaneous_set_release():
ci, packer = parser()
for i in range(40):
feed_lkas(ci, packer, [0], i)
feed_lkas(ci, packer, [0], 40, cruise=3)
cs = feed_lkas(ci, packer, [1], 41)
assert cs.buttonEnable
assert [(str(b.type), b.pressed) for b in cs.buttonEvents] == [('decelCruise', False), ('lkas', True)]
@@ -68,7 +68,10 @@ def button_reported(cp, key):
cp.flags & TeslaFlags.AOL_SCREEN_BUTTON)
return bool(key in KEYS[5:8] and cp.carFingerprint == CAR.TESLA_MODEL_S_PREAP)
if key == 'LKASButtonControl':
if brand in ('chrysler', 'gm', 'mazda', 'nissan', 'volkswagen', 'volvo'):
if brand == 'gm':
from opendbc.car.gm.values import is_volt_flashed_profile
return is_volt_flashed_profile(cp)
if brand in ('chrysler', 'mazda', 'nissan', 'volkswagen', 'volvo'):
return False
if brand == 'hyundai':
from opendbc.car.hyundai.values import CAR, HyundaiFlags, is_blended
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "dde91e1ce9c780f132c91438b2fb0559f51132e10dea387a6c1f64a03f2da779", "schema_version": 1}
{"protected_inventory_sha256": "576253242aefccbd2c9bb70dccf54b8ac10c06832c285e686e0105bacce2fd6c", "schema_version": 1}