Volt One-Pedal

This commit is contained in:
firestar5683
2026-10-05 05:15:59 -05:00
parent ead4c0cf73
commit d8cc751cef
31 changed files with 934 additions and 90 deletions
+2
View File
@@ -16,6 +16,8 @@ GM_BASE_GATEWAY_IDS = frozenset((CAR.GMC_ACADIA, CAR.BUICK_LACROSSE, CAR.CADILLA
CAR.CADILLAC_ESCALADE_ESV, CAR.CADILLAC_ESCALADE_ESV_2019, CAR.CHEVROLET_SUBURBAN))
GM_AOL_WORDS = frozenset((
0, 0x80,
0xD100, 0xD101, 0xD102, 0xD103, 0xD104, 0xD105, 0xD106, 0xD107, 0xD108, 0xD109, 0xD10A,
0xD110, 0xD111, 0xD112, 0xD113, 0xD114, 0xD115, 0xD116, 0xD117, 0xD118, 0xD119, 0xD11A,
0x1001, 0x1401, 0x3001, 0x3401, 0x1003, 0x1403,
0x201, 0x601, 0xA01, 0xE01, 0x203, 0x603, 0xA03, 0xE03,
5, 7, 20, 0xBD, 0x9D, 0x19D, 0x1CD,
+4 -1
View File
@@ -18,7 +18,10 @@ class AutoHoldConfig:
def config_for(cp) -> AutoHoldConfig:
from opendbc.car.gm.values import is_gm_auto_hold, is_volt_sdgm_profile
return AutoHoldConfig(100, .25) if is_gm_auto_hold(cp) and is_volt_sdgm_profile(cp, longitudinal=True) else AutoHoldConfig()
if is_gm_auto_hold(cp) and is_volt_sdgm_profile(cp, longitudinal=True):
solo = 0xD100 <= int(cp.safetyConfigs[0].safetyParam) <= 0xD10A
return AutoHoldConfig(100, .02 if solo else .25)
return AutoHoldConfig()
def stopped_for_hold(cs, config: AutoHoldConfig, sent_hold: bool) -> bool:
+50 -5
View File
@@ -1,5 +1,7 @@
from opendbc.car.gm.one_pedal import VoltOnePedal
from opendbc.car.gm.values import gm_control_word, is_volt_one_pedal
from opendbc.car.gm.longitudinal import volt_sng_release
from opendbc.car.gm.auto_hold import AutoHold, config_for as auto_hold_config_for
from opendbc.car.gm.auto_hold import AutoHold, config_for as auto_hold_config_for, hold_brake as estimate_hold_brake
from opendbc.car.gm.values import is_gm_auto_hold
from opendbc.car.gm.ordinary import demands as ascm_demands
import math
@@ -225,6 +227,8 @@ class CarController(CarControllerBase):
self.pedal_active_last = False
self.volt_sng = False
self.volt_sng_plan_input = None
self.volt_one_pedal = False
self.volt_one_pedal_input = None
self.gm_auto_hold = False
self.gm_auto_hold_input = None
self.gm_auto_hold_state = AutoHold(config=auto_hold_config_for(CP))
@@ -242,6 +246,7 @@ class CarController(CarControllerBase):
self.lka_icon_status_last = (False, False)
self.params = CarControllerParams(self.CP)
self.volt_one_pedal_state = VoltOnePedal(CP, self.params) if is_volt_one_pedal(CP) else None
self.volt_gateway_profile = is_volt_gateway_profile(self.CP)
self.volt_gateway_long = is_volt_gateway_longitudinal(self.CP)
self.volt_ascm_long = is_volt_ascm_longitudinal(self.CP)
@@ -348,16 +353,36 @@ class CarController(CarControllerBase):
def update(self, CC, CS, now_nanos):
# Sample physical hold dwell at parser/controller cadence, before current brake demands.
hold_brake = None
one_pedal_enabled = (self.volt_one_pedal and is_volt_one_pedal(self.CP) and self.volt_one_pedal_input is not None and
self.volt_one_pedal_input.update(now_nanos))
one_pedal_active = False
if is_gm_auto_hold(self.CP):
hold_enabled = self.gm_auto_hold and self.gm_auto_hold_input is not None and self.gm_auto_hold_input.update(now_nanos)
physical_current = (bool(CS.gm_auto_hold_sources) and
all(0 < stamp <= now_nanos and now_nanos - stamp <= limit for stamp, limit in CS.gm_auto_hold_sources) and
not CS.gm_auto_hold_unavailable)
one_pedal_ready = (one_pedal_enabled and physical_current and CS.volt_one_pedal_mode and
0 < CS.volt_one_pedal_mode_ns <= now_nanos and now_nanos - CS.volt_one_pedal_mode_ns <= 300_000_000 and
CS.out.cruiseState.available and CS.gm_auto_hold_forward and
not CS.out.gasPressed and not CS.out.brakePressed and not CS.out.regenBraking)
hold_brake = self.gm_auto_hold_state.update(
CS.out, enabled=hold_enabled, sources_current=all(0 < stamp <= now_nanos and now_nanos - stamp <= limit
CS.out, enabled=hold_enabled or one_pedal_enabled, sources_current=all(0 < stamp <= now_nanos and now_nanos - stamp <= limit
for stamp, limit in CS.gm_auto_hold_sources) and bool(CS.gm_auto_hold_sources) and
not CS.gm_auto_hold_unavailable, long_active=CC.longActive, driver_brake=CS.gm_auto_hold_brake,
controller_brake=self.apply_brake, wheel_ns=CS.gm_auto_hold_wheel_ns, now_ns=now_nanos,
physical_forward=CS.gm_auto_hold_forward, moving=CS.gm_auto_hold_moving, sent_hold=CS.gm_auto_hold_engaged)
if hold_brake is None:
if not hold_enabled:
hold_brake = None
one_pedal_active = (one_pedal_ready and self.gm_auto_hold_state.drive_ns >= 3_000_000_000 and
CS.out.canValid and not CS.out.canTimeout and not CC.longActive and
(CS.volt_one_pedal_moving or CS.volt_one_pedal_stopped))
if hold_brake is None and not (one_pedal_active and CS.volt_one_pedal_stopped and
(CS.out.standstill or CS.out.vEgo < .02)):
CS.gm_auto_hold_engaged = False
if self.volt_one_pedal_state is not None:
self.volt_one_pedal_state.observe(eligible=one_pedal_ready and self.gm_auto_hold_state.drive_ns >= 3_000_000_000 and
not CC.longActive, gas_pressed=CS.out.gasPressed,
speed=CS.out.vEgo)
actuators = CC.actuators
hud_control = CC.hudControl
hud_alert = hud_control.visualAlert
@@ -630,6 +655,25 @@ class CarController(CarControllerBase):
self.ordinary_camera_long and not self.ordinary_camera_removed):
can_sends.append(gmcan.create_acc_2cd_command(CanBus.POWERTRAIN, idx))
if self.volt_one_pedal_state is not None:
one_pedal_brake = self.volt_one_pedal_state.update(
eligible=one_pedal_active, speed=CS.out.vEgo, acceleration=CS.out.aEgo,
steering_angle=CS.out.steeringAngleDeg, orientation=CC.orientationNED)
if one_pedal_active:
one_pedal_stop = CS.volt_one_pedal_stopped and (CS.out.standstill or CS.out.vEgo < .02)
if hold_brake is None and one_pedal_stop and one_pedal_brake > 0:
hold_brake = max(one_pedal_brake, self.gm_auto_hold_state.brake or estimate_hold_brake(
0., one_pedal_brake, self.gm_auto_hold_state.config.minimum_brake))
elif hold_brake is not None:
hold_brake = max(hold_brake, one_pedal_brake)
self.apply_gas = self.params.INACTIVE_REGEN
self.apply_brake = max(self.apply_brake, one_pedal_brake)
one_pedal_braking = one_pedal_active and self.volt_one_pedal_state is not None and self.volt_one_pedal_state.brake > 0
if one_pedal_braking:
at_full_stop = at_full_stop or CS.out.cruiseState.standstill
near_stop = near_stop or CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE
# Resume releases only the gas/regen ACC-state bit; friction remains separately owned.
acc_engaged = CC.enabled and not volt_sng_release(
self.CP, self.volt_sng, CC, CS, now_nanos,
@@ -641,7 +685,8 @@ class CarController(CarControllerBase):
at_full_stop = CS.out.cruiseState.standstill
near_stop = CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE
can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake,
idx, CC.enabled, near_stop, at_full_stop, self.CP, auto_hold=hold_brake is not None))
idx, CC.enabled, near_stop, at_full_stop, self.CP,
auto_hold=hold_brake is not None or one_pedal_braking))
CS.gm_auto_hold_engaged = hold_brake is not None
if self.bolt_euv_long:
@@ -708,7 +753,7 @@ class CarController(CarControllerBase):
if self.cancel_counter > CAMERA_CANCEL_DELAY_FRAMES and (not cc_gateway or
(button_fresh and stock_cruise_fresh and CS.out.cruiseState.available and CS.out.cruiseState.enabled)):
self.last_button_frame = self.frame
cancel_bus = (CanBus.POWERTRAIN if cc_gateway or self.CP.safetyConfigs[0].safetyParam & GMSafetyFlags.SDGM_CANCEL_PT.value
cancel_bus = (CanBus.POWERTRAIN if cc_gateway or gm_control_word(self.CP) & GMSafetyFlags.SDGM_CANCEL_PT.value
else CanBus.CAMERA)
can_sends.append(gmcan.create_buttons(self.packer_pt, cancel_bus, CS.buttons_counter, CruiseButtons.CANCEL))
+28 -9
View File
@@ -1,3 +1,4 @@
from opendbc.car.gm.values import gm_control_word, is_volt_one_pedal
from opendbc.car.gm.values import is_volt_longitudinal, is_gm_auto_hold
from opendbc.car.gm.auto_hold import config_for as auto_hold_config_for, stopped_for_hold
import copy
@@ -66,6 +67,10 @@ class CarState(CarStateBase):
self.gm_auto_hold_wheel_ns = 0
self.gm_auto_hold_unavailable = True
self.gm_auto_hold_engaged = False
self.volt_one_pedal_mode = False
self.volt_one_pedal_mode_ns = 0
self.volt_one_pedal_moving = False
self.volt_one_pedal_stopped = False
self.cc_gateway_cruise_ts_nanos = 0
self.cc_gateway_buttons_ts_nanos = 0
self.camera_stock_status_ts_nanos = 0
@@ -200,8 +205,8 @@ class CarState(CarStateBase):
alternate = is_volt_gateway_alternate_brake(self.CP) or (
is_volt_camera_removed(self.CP) and bool(self.CP.flags & GMFlags.NO_ACCELERATOR_POS_MSG))
c9 = self.CP.networkLocation == NetworkLocation.fwdCamera and not (
self.CP.safetyConfigs[0].safetyParam & (GMSafetyFlags.ASCM_INTERCEPT | GMSafetyFlags.SDGM) and
not self.CP.safetyConfigs[0].safetyParam & GMSafetyFlags.BRAKE_C9)
gm_control_word(self.CP) & (GMSafetyFlags.ASCM_INTERCEPT | GMSafetyFlags.SDGM) and
not gm_control_word(self.CP) & GMSafetyFlags.BRAKE_C9)
brake = (("EBCMBrakePedalPosition", "BrakePedalPosition") if alternate else
("ECMEngineStatus", "BrakePressed") if c9 else ("ECMAcceleratorPos", "BrakePedalPos"))
names = (("AcceleratorPedal2", "CruiseState", 100_000_000),
@@ -219,8 +224,8 @@ class CarState(CarStateBase):
alternate = is_volt_gateway_alternate_brake(self.CP)
alternate_force = alternate or (is_volt_camera_removed(self.CP) and bool(self.CP.flags & GMFlags.NO_ACCELERATOR_POS_MSG))
c9 = self.CP.networkLocation == NetworkLocation.fwdCamera and (
not self.CP.safetyConfigs[0].safetyParam & (GMSafetyFlags.ASCM_INTERCEPT | GMSafetyFlags.SDGM) or
bool(self.CP.safetyConfigs[0].safetyParam & GMSafetyFlags.BRAKE_C9))
not gm_control_word(self.CP) & (GMSafetyFlags.ASCM_INTERCEPT | GMSafetyFlags.SDGM) or
bool(gm_control_word(self.CP) & GMSafetyFlags.BRAKE_C9))
brake_name, brake_signal = (("EBCMBrakePedalPosition", "BrakePedalPosition") if alternate else
("ECMEngineStatus", "BrakePressed") if c9 else ("ECMAcceleratorPos", "BrakePedalPos"))
names = (("ECMEngineStatus", "CruiseMainOn"), ("ECMPRDNL2", "PRNDL2"), (brake_name, brake_signal),
@@ -232,7 +237,7 @@ class CarState(CarStateBase):
pt_cp.vl[name]
self.gm_auto_hold_sources = tuple((pt_cp.ts_nanos[name][signal], 300_000_000) for name, signal in names)
# C9 is pressed authority, never analog force; missing-BE profiles retain minimum hold.
absent_be = c9 and bool(self.CP.safetyConfigs[0].safetyParam & (GMSafetyFlags.ASCM_INTERCEPT | GMSafetyFlags.SDGM))
absent_be = c9 and bool(gm_control_word(self.CP) & (GMSafetyFlags.ASCM_INTERCEPT | GMSafetyFlags.SDGM))
if alternate_force:
self.gm_auto_hold_brake = pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] / 208.
if c9:
@@ -247,6 +252,16 @@ class CarState(CarStateBase):
self.gm_auto_hold_moving = wheels["RLWheelSpd"] >= 12 * .0311 and wheels["RRWheelSpd"] >= 12 * .0311
self.gm_auto_hold_wheel_ns = pt_cp.ts_nanos["EBCMWheelSpdRear"]["RLWheelSpd"]
self.gm_auto_hold_unavailable = bool(pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"])
if is_volt_one_pedal(self.CP):
gear = pt_cp.vl["ECMPRDNL2"]
low = gear["PRNDL2"] == 6 and not gear["ManualMode"]
mode_ns = pt_cp.ts_nanos["EVDriveMode"]["SinglePedalModeActive"]
mode_current = 0 < mode_ns <= pt_cp._last_update_nanos and pt_cp._last_update_nanos - mode_ns <= 300_000_000
self.volt_one_pedal_mode_ns = pt_cp.ts_nanos["ECMPRDNL2"]["PRNDL2"] if low else mode_ns
self.volt_one_pedal_mode = low or (mode_current and bool(pt_cp.vl["EVDriveMode"]["SinglePedalModeActive"]))
rear = pt_cp.vl["EBCMWheelSpdRear"]
self.volt_one_pedal_moving = rear["RLWheelSpd"] > 10 * .0311 and rear["RRWheelSpd"] > 10 * .0311
self.volt_one_pedal_stopped = rear["RLWheelSpd"] <= 10 * .0311 and rear["RRWheelSpd"] <= 10 * .0311
self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"])
# Variables used for avoiding LKAS faults
@@ -283,9 +298,9 @@ class CarState(CarStateBase):
else:
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None))
source_be_brake = (self.CP.safetyConfigs[0].safetyParam &
source_be_brake = (gm_control_word(self.CP) &
(GMSafetyFlags.ASCM_INTERCEPT | GMSafetyFlags.SDGM).value and
not self.CP.safetyConfigs[0].safetyParam & GMSafetyFlags.BRAKE_C9.value)
not gm_control_word(self.CP) & GMSafetyFlags.BRAKE_C9.value)
if is_conventional_cc_pedal_profile(self.CP) and self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_CC:
ret.brakePressed = (pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] / 0xD0 >= .10
if self.CP.flags & GMFlags.NO_ACCELERATOR_POS_MSG else
@@ -451,6 +466,8 @@ class CarState(CarStateBase):
hold_sources_current and not self.gm_auto_hold_unavailable and self.gm_auto_hold_forward and
ret.cruiseState.available and hold_stopped and
not ret.gasPressed and not ret.regenBraking)
if is_volt_one_pedal(self.CP) and int(self.CP.safetyConfigs[0].safetyParam) < 0xD110:
hold_current = hold_current and self.volt_one_pedal_mode and self.volt_one_pedal_stopped and not ret.brakePressed
if not hold_current:
self.gm_auto_hold_engaged = False
ret.brakeHoldActive = bool(hold_current and self.gm_auto_hold_engaged and ret.standstill)
@@ -468,6 +485,8 @@ class CarState(CarStateBase):
pt_messages.append(("ECMAcceleratorPos", 10))
if is_volt_gateway_alternate_brake(CP):
pt_messages.append(("EBCMBrakePedalPosition", 100))
if is_volt_one_pedal(CP):
pt_messages.append(("EVDriveMode", float('nan')))
if CP.carFingerprint in VOLT_BSM_CAR and CP.flags & GMFlags.HAS_BSM.value:
pt_messages.append(("BCMBlindSpotMonitor", float('nan')))
if CP.flags & GMFlags.PEDAL_LONG.value:
@@ -490,7 +509,7 @@ class CarState(CarStateBase):
("BCMDoorBeltStatus", 10), ("BCMGeneralPlatformStatus", 10),
("ASCMSteeringButton", 33),
]
if not CP.safetyConfigs[0].safetyParam & GMSafetyFlags.BRAKE_C9.value:
if not gm_control_word(CP) & GMSafetyFlags.BRAKE_C9.value:
pt_messages.append(("ECMAcceleratorPos", 80))
if (CP.carFingerprint in CC_GATEWAY_STOCK_CAR or CP.carFingerprint == CAR.CHEVROLET_VOLT_CC):
# No camera or ACC status dependency on this gateway conventional-cruise path.
@@ -538,7 +557,7 @@ class CarState(CarStateBase):
("ECMEngineStatus", 100), ("AcceleratorPedal2", 33), ("ECMPRDNL2", 10),
("EBCMWheelSpdRear", 20), ("EBCMRegenPaddle", 40)]
if CP.carFingerprint == CAR.CHEVROLET_VOLT_2019 and CP.safetyConfigs[0].safetyParam & GMSafetyFlags.BRAKE_C9.value:
if CP.carFingerprint == CAR.CHEVROLET_VOLT_2019 and gm_control_word(CP) & GMSafetyFlags.BRAKE_C9.value:
pt_messages = [(name, frequency) for name, frequency in pt_messages if name != "ECMAcceleratorPos"]
if is_conventional_cc_pedal_profile(CP):
+2 -1
View File
@@ -1,3 +1,4 @@
from opendbc.car.gm.values import gm_control_word
"""Exact finalized GM configurations for shared lateral preferences."""
import math
from opendbc.car.structs import CarParams
@@ -35,7 +36,7 @@ def lane_centering_supported(cp) -> bool:
if (len(cp.safetyConfigs) != 1 or cp.safetyConfigs[0].safetyModel != CarParams.SafetyModel.gm or
cp.networkLocation != CarParams.NetworkLocation.fwdCamera or control_flags(cp) != 0):
return False
word = int(cp.safetyConfigs[0].safetyParam)
word = gm_control_word(cp)
if cp.carFingerprint == CAR.CHEVROLET_VOLT_CAMERA:
return ((word == 5 and cp.pcmCruise and not cp.openpilotLongitudinalControl) or
(word in (0x4007, 0x4087) and not cp.pcmCruise and cp.openpilotLongitudinalControl and cp.alphaLongitudinalAvailable))
+80
View File
@@ -0,0 +1,80 @@
"""Volt moving one-pedal braking with vehicle-owned physical admission."""
import math
import numpy as np
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gm.values import CAR
class _VoltPid:
# Original openpilot/common/pid.py arithmetic, with unused derivative/override API removed.
def __init__(self, kp, ki):
self.kp, self.ki = kp, ki
self.reset()
def reset(self):
self.i = 0.0
def update(self, error, *, speed, feedforward):
proportional = float(np.interp(speed, *self.kp)) * error
integral = self.i + float(np.interp(speed, *self.ki)) * 0.04 * error
candidate = proportional + integral + feedforward
upper = self.i if candidate > 0.0 else 0.0
lower = self.i if candidate < -3.5 else -3.5
self.i = float(np.clip(integral, lower, upper))
return float(np.clip(proportional + self.i + feedforward, -3.5, 0.0))
class VoltOnePedal:
def __init__(self, cp, params):
self.params = params
self.stopping_speed = .75 if cp.carFingerprint == CAR.CHEVROLET_VOLT else .25
self.pid = _VoltPid(((0.,), (0.,)),
(cp.longitudinalTuning.kiBP, cp.longitudinalTuning.kiV))
self.decel = 0.0
self.brake = 0
self.lift_frames = 0
self.gas_last = False
def observe(self, *, eligible, gas_pressed, speed):
# Called at controller cadence; preserve a gas-release edge until the next 25 Hz sender.
if eligible and self.gas_last and not gas_pressed and speed < 2.0 * CV.MPH_TO_MS:
self.lift_frames = 8
elif gas_pressed or not eligible:
self.lift_frames = 0
self.gas_last = gas_pressed
def update(self, *, eligible, speed, acceleration, steering_angle, orientation):
# Called only at the existing gas/friction sender cadence, with qualified physical inputs.
finite = all(math.isfinite(value) for value in (speed, acceleration, steering_angle, *orientation))
if not eligible or not finite or speed < 0:
self.pid.reset()
self.decel = min(0.0, acceleration) if math.isfinite(acceleration) else 0.0
self.brake = 0
self.lift_frames = 0
return 0
pitch_accel = 0.0
if len(orientation) == 3 and speed > self.stopping_speed:
pitch_accel = math.sin(orientation[1]) * ACCELERATION_DUE_TO_GRAVITY
pitch_values = [0.4, 1.0] if pitch_accel <= 0 else [0.2, 1.0]
pitch_accel *= float(np.interp(speed, [4.0, 8.0], pitch_values))
target = float(np.interp(speed, [0.5 * CV.MPH_TO_MS, 6.0 * CV.MPH_TO_MS], [-1.0, -1.1]))
measured = min(0.0, acceleration + pitch_accel)
error = (target - measured) * float(np.interp(speed, [1.5, 20.0], [0.4, 0.2]))
raw = float(self.pid.update(error, speed=speed, feedforward=target))
factor = min(float(np.interp(speed, [0.0, 10.0 * CV.MPH_TO_MS], [0.2, 1.0])),
float(np.interp(abs(steering_angle), [20.0, 120.0], [1.0, 0.2])))
lower = min(self.decel, measured) - 0.032 * factor
# Preserve the original asymmetric upper expression; this is not a tuning change.
upper = max(self.decel, measured) + 0.032 + factor
self.decel = max(float(np.clip(raw, lower, upper)), -2.1)
self.brake = int(round(np.clip(np.interp(self.decel, [self.params.ACCEL_MIN, 0.],
[self.params.MAX_BRAKE, 0.]), 0, self.params.MAX_BRAKE)))
if self.lift_frames > 0:
lift = 0 if speed > 2.0 * CV.MPH_TO_MS else int(round(np.interp(
speed, [0.0, self.params.NEAR_STOP_BRAKE_PHASE, 2.0 * CV.MPH_TO_MS], [80.0, 80.0, 20.0])))
self.brake = max(self.brake, lift)
self.lift_frames -= 1
return self.brake
@@ -1,3 +1,4 @@
from opendbc.car.gm.values import gm_control_word, is_volt_one_pedal
"""Finalized GM speed-control choices; existing native envelopes are retained."""
from opendbc.car.gm.values import (is_volt_gateway_profile, is_volt_cc_profile, is_ordinary_cc_profile,
@@ -14,6 +15,8 @@ def disable_long_supported(cp) -> bool:
def prepare_disable_longitudinal(cp, requested: bool) -> None:
if requested and is_volt_one_pedal(cp):
cp.safetyConfigs[0].safetyParam = gm_control_word(cp)
if requested and is_volt_camera_removed(cp, longitudinal=True):
cp.safetyConfigs[0].safetyParam = 0xC150
cp.openpilotLongitudinalControl = False
@@ -11,7 +11,201 @@ from opendbc.car.gm.tests.test_bolt_volt_configurations import controller_messag
from opendbc.car.gm.values import CAR, DBC, GMSafetyFlags, is_volt_gateway_alternate_brake
def one_pedal_profiles(*, release=False):
from opendbc.car.gm.tests.test_ascm_intercept import params as ascm_params
from opendbc.car.gm.tests.test_volt_camera_control import camera_params
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
from opendbc.car.gm.tests.test_volt_camera_removed import removed_params
for be in (True, False):
yield f'gateway-{int(be)}', ordinary_params(CAR.CHEVROLET_VOLT, radar=True, accelerator=be), be
for be in (True, False):
for radar in (False, True):
yield (f'ascm-{int(be)}-{int(radar)}', ascm_params(CAR.CHEVROLET_VOLT_ASCM, sascm=True, alpha=True,
accelerator=be, radar=radar, release=release), be)
yield 'camera', camera_params(release=release), True
for c9 in (False, True):
yield f'sdgm-{int(c9)}', sdgm_params(brake_c9=c9, radar=True, release=release), True
for alternate in (False, True):
yield f'removed-{int(alternate)}', removed_params(alternate=alternate, release=release), not alternate
class TestVoltAlternateBrake(unittest.TestCase):
def test_one_pedal_matches_pinned_original_scalar_histories(self):
import json
from pathlib import Path
from types import SimpleNamespace
from opendbc.car.gm.one_pedal import VoltOnePedal, _VoltPid
reference = json.loads(Path(__file__).with_name('volt_one_pedal_original_oracle.json').read_text())
params = SimpleNamespace(**reference['params'])
for history in reference['histories']:
cp = SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_CAMERA, longitudinalTuning=SimpleNamespace(
kpBP=history['kp'][0], kpV=history['kp'][1], kiBP=history['ki'][0], kiV=history['ki'][1]))
controller = VoltOnePedal(cp, params)
controller.pid = _VoltPid(history['kp'], history['ki'])
controller.lift_frames = history['initial_lift_frames']
for tick, step in enumerate(history['steps']):
with self.subTest(history=history['name'], tick=tick):
result = controller.update(**step['input'])
self.assertEqual(result, step['expected']['brake'])
self.assertAlmostEqual(controller.pid.i, step['expected']['integral'], places=12)
self.assertAlmostEqual(controller.decel, step['expected']['decel'], places=12)
self.assertEqual(controller.lift_frames, step['expected']['lift_frames'])
def test_one_pedal_default_off_preserves_committed_sender_bytes(self):
import json
from pathlib import Path
from types import SimpleNamespace
from opendbc.car import structs
from opendbc.car.gm.interface import CarInterface
from opendbc.car.gm.tests.test_bolt_cc import feed, control
from opendbc.car.gm.values import is_volt_camera_removed
reference = json.loads(Path(__file__).with_name('volt_one_pedal_default_off.json').read_text())
cases = list(one_pedal_profiles())
self.assertEqual(len(cases), len(reference['profiles']))
for (name, cp, be), baseline in zip(cases, reference['profiles'], strict=True):
with self.subTest(profile=name):
self.assertEqual((name, cp.carFingerprint, cp.safetyConfigs[0].safetyParam),
(baseline['name'], baseline['identity'], baseline['word']))
ci = CarInterface(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
for tick, expected in enumerate(baseline['tx']):
now = 1_000_000_000 + tick * 10_000_000
speed = 20. if tick < 40 or tick >= 100 else 0. if tick < 80 else 6.
active, brake, gas = tick < 60 or tick >= 90, 60 <= tick < 65, 65 <= tick < 70
_, packets = feed(SimpleNamespace(update=lambda _: None), packer, now, counter=tick % 4, speed=speed,
brake=brake, gas=gas, camera=cp.networkLocation == structs.CarParams.NetworkLocation.fwdCamera and
not is_volt_camera_removed(cp))
packets = [packet for packet in packets if packet[0] not in (0xBE, 0xF1, 0x1C4)]
packets.append(packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 2 if active else 0,
'AcceleratorPedal2': 30 if gas else 0}))
if be:
packets.append(packer.make_can_msg('ECMAcceleratorPos', 0, {'BrakePedalPos': 20 if brake else 0}))
else:
packets.append(packer.make_can_msg('EBCMBrakePedalPosition', 0, {'BrakePedalPosition': 104 if brake else 0}))
if cp.networkLocation == structs.CarParams.NetworkLocation.fwdCamera and not is_volt_camera_removed(cp):
packets.append(packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {'ACCCruiseState': 3, 'ACCSpeedSetpoint': 60}))
ci.update([(now - 1_000_000, packets)])
self.assertTrue(ci.update([(now, packets)]).canValid)
command = control(enabled=active, long_active=active)
command.latActive = active
command.actuators.accel = -.5 if tick < 40 else 1.
command.actuators.torque = .02
_, messages = ci.apply(command.as_reader(), now)
self.assertEqual([[address, data.hex(), bus] for address, data, bus in messages], expected, (name, tick))
def test_one_pedal_actual_parser_controller_all_tx(self):
from tempfile import TemporaryDirectory
from types import SimpleNamespace
from opendbc.car import structs
from opendbc.car.gm.interface import CarInterface
from opendbc.car.gm.tests.test_bolt_cc import feed, setup, native
from opendbc.car.gm.values import gm_control_word, is_volt_camera_removed
from opendbc.safety.tests.libsafety import libsafety_py
from openpilot.common.params import Params
from openpilot.starpilot.controller_extensions import configure_controller
from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences
release = libsafety_py.libsafety.set_safety_hooks(structs.CarParams.SafetyModel.allOutput, 0) != 0
indexes = {'gateway-1': 0, 'gateway-0': 1, 'ascm-1-0': 2, 'ascm-0-0': 3,
'ascm-1-1': 4, 'ascm-0-1': 5, 'camera': 6, 'sdgm-0': 7,
'sdgm-1': 8, 'removed-0': 9, 'removed-1': 10}
for name, original, be in one_pedal_profiles(release=release):
for paired in (False, True):
with self.subTest(profile=name, paired=paired), TemporaryDirectory() as directory:
cp = original.as_reader().as_builder()
params = Params(directory)
for key, value in [('OpenpilotEnabledToggle', True), ('VoltOnePedalMode', True), ('GMAutoHold', paired)]:
params.put_bool(key, value, block=True)
preferences = VehicleStartupPreferences.read(params, enabled=True)
preferences.prepare(cp)
preferences.finalize(cp)
if release and indexes[name] >= 2:
self.assertEqual(cp.safetyConfigs[0].safetyParam, original.safetyConfigs[0].safetyParam)
self._hold_release_factory(cp, camera=False, alternative=0)
continue
self.assertEqual(cp.safetyConfigs[0].safetyParam, 0xD100 + indexes[name] + (0x10 if paired else 0))
self.assertNotEqual(gm_control_word(cp), cp.safetyConfigs[0].safetyParam)
ci = CarInterface(cp)
preferences.configure_controller(ci)
configure_controller(ci, params)
self.assertTrue(ci.CC.volt_one_pedal)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
setup(cp)
self.assertEqual(libsafety_py.libsafety.set_safety_hooks(int(structs.CarParams.SafetyModel.gm), cp.safetyConfigs[0].safetyParam), 0)
removed = is_volt_camera_removed(cp)
positive = stopped_positive = immediate_release = False
for tick in range(2680 if name == 'gateway-1' and not paired else 940):
now = 1_000_000_000 + tick * 10_000_000
stopped = 360 <= tick < 410 or 440 <= tick < 490 or 550 <= tick < 660
regen = 410 <= tick < 420
mode_off = 490 <= tick < 550 or 610 <= tick < 660 or 730 <= tick < 940
manual = 760 <= tick < 800
fault = 800 <= tick < 820
physical_brake = 820 <= tick < 840
unknown_gear = 840 <= tick < 860
main_off = 860 <= tick < 880
stale_status = 880 <= tick < 940
live_changes = {1280: ('VoltOnePedalMode', False), 1320: ('VoltOnePedalMode', True),
1720: ('OpenpilotEnabledToggle', False), 1760: ('OpenpilotEnabledToggle', True),
2160: ('SafeMode', True), 2200: ('SafeMode', False),
2600: ('DisableOpenpilotLongitudinal', True)}
if tick in live_changes:
key, value = live_changes[tick]
params.put_bool(key, value, block=True)
live_withdrawn = 1305 <= tick < 1320 or 1745 <= tick < 1760 or 2185 <= tick < 2200 or tick >= 2625
gas = 310 <= tick < 320
speed = 0. if stopped else .15 if 420 <= tick < 440 else .5 if tick >= 310 else 2.
_, rx = feed(SimpleNamespace(update=lambda _: None), packer, now, speed=speed, gas=gas, regen=regen,
camera=cp.networkLocation == structs.CarParams.NetworkLocation.fwdCamera and not removed)
rx = [message for message in rx if message[0] not in (0xBE, 0xF1, 0x1C4, 0xC9, 0x1F5, 0x232)]
brake_name, signal = ('ECMAcceleratorPos', 'BrakePedalPos') if be else ('EBCMBrakePedalPosition', 'BrakePedalPosition')
rx += [packer.make_can_msg(brake_name, 0, {signal: 20 if physical_brake else 0}),
packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 4 if stopped else 2, 'AcceleratorPedal2': 30 if gas else 0}),
packer.make_can_msg('ECMEngineStatus', 0, {'CruiseMainOn': int(not main_off), 'BrakePressed': int(physical_brake)}),
packer.make_can_msg('ECMPRDNL2', 0, {'PRNDL2': 8 if unknown_gear else
6 if manual or not mode_off and (tick < 660 or tick >= 940) else 4, 'ManualMode': int(manual)}),
packer.make_can_msg('EBCMFrictionBrakeStatus', 0, {'FrictionBrakeUnavailable': int(fault)})]
if 660 <= tick < 700:
rx.append(packer.make_can_msg('EVDriveMode', 0, {'SinglePedalModeActive': 1}))
if stale_status:
rx = [message for message in rx if message[0] != 0x232]
if cp.networkLocation == structs.CarParams.NetworkLocation.fwdCamera and not removed:
rx.append(packer.make_can_msg('ASCMActiveCruiseControlStatus', 2, {'ACCCruiseState': 3, 'ACCSpeedSetpoint': 60}))
ci.update([(now - 1_000_000, rx)])
out = ci.update([(now, rx)])
if not stale_status:
self.assertTrue(out.canValid, (name, tick))
self.assertEqual(ci.CS.volt_one_pedal_mode, not mode_off, tick)
for message in rx:
native('rx', message, now // 1000)
libsafety_py.libsafety.safety_tick()
_, commands = ci.apply(structs.CarControl(enabled=False, longActive=False).as_reader(), now)
for message in commands:
self.assertTrue(native('tx', message, now // 1000), (name, paired, tick, message))
brakes = [message for message in commands if message[0] == 0x315]
self.assertLessEqual(len(brakes), 1)
if not brakes:
continue
data = brakes[0][1]
self.assertEqual(int.from_bytes(data[2:4], 'big'),
(0x10000 - int.from_bytes(data[:2], 'big') - (data[4] & 3)) & 0xFFFF)
amount = (0x1000 - (((data[0] & 15) << 8) | data[1])) & 0xFFF
if (gas or regen or tick < 300 or mode_off and not paired or fault or physical_brake or
unknown_gear or main_off or 910 <= tick < 940 or live_withdrawn):
self.assertEqual(amount, 0, (name, paired, tick))
if tick in (1260, 1716, 2156, 2596):
self.assertGreater(amount, 0, (name, paired, tick))
if 330 <= tick < 360 and amount:
positive = True
if 390 <= tick < 410 and amount:
stopped_positive = True
self.assertEqual(data[0] >> 4, 0xD)
self.assertFalse(any(message[0] == 0x2CB for message in commands))
if 420 <= tick < 440 and amount:
immediate_release = True
if out.vEgo < ci.CC.params.NEAR_STOP_BRAKE_PHASE:
self.assertEqual(data[0] >> 4, 0xB)
self.assertTrue(positive and stopped_positive and immediate_release, (name, paired))
def test_auto_hold_actual_gateway_parser_controller_native(self):
from itertools import product
for accelerator, alternative in product((True, False), (0, 32)):
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+48 -11
View File
@@ -136,6 +136,43 @@ class GMFlags(IntFlag):
VOLT_CAMERA_NO_ACCEL_POS = NO_ACCELERATOR_POS_MSG
VOLT_ONE_PEDAL_BASE_WORDS = (0x4084, 0xC084, 0x4287, 0x4687, 0x4A87, 0x4E87, 0x4087, 0x5087, 0x5487, 0xC1D1, 0xC1D3)
VOLT_ONE_PEDAL_WORDS = {start + index: word for start in (0xD100, 0xD110)
for index, word in enumerate(VOLT_ONE_PEDAL_BASE_WORDS)}
def gm_control_word(cp: CarParams) -> int:
word = int(cp.safetyConfigs[0].safetyParam)
if word not in VOLT_ONE_PEDAL_WORDS:
return word
index = word - (0xD110 if word >= 0xD110 else 0xD100)
identity = (CAR.CHEVROLET_VOLT if index < 2 else CAR.CHEVROLET_VOLT_ASCM if index < 6 else
CAR.CHEVROLET_VOLT_2019 if index in (7, 8) else CAR.CHEVROLET_VOLT_CAMERA)
network = CarParams.NetworkLocation.gateway if index < 2 else CarParams.NetworkLocation.fwdCamera
if (cp.brand == 'gm' and cp.transmissionType == CarParams.TransmissionType.direct and
cp.carFingerprint == identity and cp.networkLocation == network):
return VOLT_ONE_PEDAL_WORDS[word]
return word
def is_volt_one_pedal(cp: CarParams) -> bool:
try:
return (int(cp.safetyConfigs[0].safetyParam) in VOLT_ONE_PEDAL_WORDS and
cp.transmissionType == CarParams.TransmissionType.direct and is_volt_longitudinal(cp) and is_gm_auto_hold(cp))
except (AttributeError, IndexError, TypeError, ValueError):
return False
def apply_volt_one_pedal(cp: CarParams, enabled: bool, auto_hold_enabled: bool) -> None:
if not is_volt_longitudinal(cp) or cp.transmissionType != CarParams.TransmissionType.direct:
return
cp.safetyConfigs[0].safetyParam = gm_control_word(cp)
apply_gm_auto_hold(cp, enabled or auto_hold_enabled)
word = int(cp.safetyConfigs[0].safetyParam)
if enabled and word in VOLT_ONE_PEDAL_BASE_WORDS:
cp.safetyConfigs[0].safetyParam = (0xD110 if auto_hold_enabled else 0xD100) + VOLT_ONE_PEDAL_BASE_WORDS.index(word)
def control_flags(cp: CarParams) -> int:
"""Exclude only the informational BSM bit from exact control-profile admission."""
return int(cp.flags) & ~int(GMFlags.HAS_BSM)
@@ -175,7 +212,7 @@ def is_volt_gateway_profile(cp: CarParams) -> bool:
not cp.passive and not cp.dashcamOnly and not cp.notCar and not cp.radarUnavailable and
control_flags(cp) == 0 and
len(cp.safetyConfigs) == 1 and cp.safetyConfigs[0].safetyModel == CarParams.SafetyModel.gm and
int(cp.safetyConfigs[0].safetyParam) in (
gm_control_word(cp) in (
int(GMSafetyFlags.EV | GMSafetyFlags.VOLT_GATEWAY_LONG),
int(GMSafetyFlags.EV | GMSafetyFlags.VOLT_GATEWAY_LONG | GMSafetyFlags.VOLT_GATEWAY_ALT_BRAKE),
0x4084, 0xC084))
@@ -195,7 +232,7 @@ def is_lacrosse_gateway_profile(cp: CarParams) -> bool:
cp.transmissionType == CarParams.TransmissionType.automatic and
not cp.passive and not cp.dashcamOnly and not cp.notCar and not cp.radarUnavailable and control_flags(cp) == 0 and
len(cp.safetyConfigs) == 1 and cp.safetyConfigs[0].safetyModel == CarParams.SafetyModel.gm and
cp.safetyConfigs[0].safetyParam in (0, 0x80))
gm_control_word(cp) in (0, 0x80))
except (AttributeError, IndexError, TypeError, ValueError):
return False
@@ -204,7 +241,7 @@ def is_gm_auto_hold(cp: CarParams) -> bool:
return ((is_volt_gateway_longitudinal(cp) or is_volt_ascm_longitudinal(cp) or is_volt_camera_longitudinal(cp) or
is_volt_sdgm_profile(cp, longitudinal=True) or is_volt_camera_removed(cp, longitudinal=True) or
is_lacrosse_gateway_profile(cp) and cp.openpilotLongitudinalControl) and
int(cp.safetyConfigs[0].safetyParam) in (0x4084, 0xC084, 0x4287, 0x4687, 0x4A87, 0x4E87, 0x4087, 0x5087, 0x5487,
gm_control_word(cp) in (0x4084, 0xC084, 0x4287, 0x4687, 0x4A87, 0x4E87, 0x4087, 0x5087, 0x5487,
0xC1D1, 0xC1D3, 0x80))
@@ -214,7 +251,7 @@ def apply_gm_auto_hold(cp: CarParams, enabled: bool) -> None:
return
if (is_volt_gateway_profile(cp) or is_volt_ascm_longitudinal(cp) or is_volt_camera_longitudinal(cp) or
is_volt_sdgm_profile(cp, longitudinal=True) or is_lacrosse_gateway_profile(cp)):
word = int(cp.safetyConfigs[0].safetyParam)
word = gm_control_word(cp)
cp.safetyConfigs[0].safetyParam = (word | int(GMSafetyFlags.VOLT_AUTO_HOLD)) if enabled and cp.openpilotLongitudinalControl else (
word & ~int(GMSafetyFlags.VOLT_AUTO_HOLD))
@@ -227,7 +264,7 @@ def is_volt_ascm_longitudinal(cp: CarParams) -> bool:
optional = int(GMSafetyFlags.ASCM_BRAKE_C9 | GMSafetyFlags.ASCM_RADAR)
if len(cp.safetyConfigs) != 1:
return False
flags = int(cp.safetyConfigs[0].safetyParam)
flags = gm_control_word(cp)
if flags in (0x4287, 0x4687, 0x4A87, 0x4E87):
flags &= ~int(GMSafetyFlags.VOLT_AUTO_HOLD)
return (cp.brand == 'gm' and cp.carFingerprint == CAR.CHEVROLET_VOLT_ASCM and
@@ -253,8 +290,8 @@ def is_volt_camera_removed(cp: CarParams, *, longitudinal=None) -> bool:
(not is_long or cp.alphaLongitudinalAvailable) and
not cp.passive and not cp.dashcamOnly and not cp.notCar and len(cp.safetyConfigs) == 1 and
cp.safetyConfigs[0].safetyModel == CarParams.SafetyModel.gm and
(cp.safetyConfigs[0].safetyParam == (0xC151 if is_long else 0xC150) or
is_long and cp.safetyConfigs[0].safetyParam == (0xC1D3 if cp.flags & GMFlags.NO_ACCELERATOR_POS_MSG else 0xC1D1)))
(gm_control_word(cp) == (0xC151 if is_long else 0xC150) or
is_long and gm_control_word(cp) == (0xC1D3 if cp.flags & GMFlags.NO_ACCELERATOR_POS_MSG else 0xC1D1)))
except (AttributeError, IndexError, TypeError, ValueError):
return False
@@ -266,7 +303,7 @@ def is_volt_camera_stock(cp: CarParams) -> bool:
cp.networkLocation == CarParams.NetworkLocation.fwdCamera and cp.pcmCruise and
not cp.openpilotLongitudinalControl and not cp.passive and not cp.dashcamOnly and
not cp.notCar and control_flags(cp) == 0 and len(cp.safetyConfigs) == 1 and
cp.safetyConfigs[0].safetyModel == CarParams.SafetyModel.gm and cp.safetyConfigs[0].safetyParam == 5)
cp.safetyConfigs[0].safetyModel == CarParams.SafetyModel.gm and gm_control_word(cp) == 5)
except (AttributeError, IndexError, TypeError, ValueError):
return False
@@ -279,7 +316,7 @@ def is_volt_camera_longitudinal(cp: CarParams) -> bool:
cp.openpilotLongitudinalControl and not cp.pcmCruise and not cp.passive and not cp.dashcamOnly and
not cp.notCar and control_flags(cp) == 0 and len(cp.safetyConfigs) == 1 and
cp.safetyConfigs[0].safetyModel == CarParams.SafetyModel.gm and
cp.safetyConfigs[0].safetyParam in (0x4007, 0x4087))
gm_control_word(cp) in (0x4007, 0x4087))
except (AttributeError, IndexError, TypeError, ValueError):
return False
@@ -292,7 +329,7 @@ def is_volt_sdgm_profile(cp: CarParams, *, longitudinal=False) -> bool:
not cp.notCar and control_flags(cp) == 0 and cp.openpilotLongitudinalControl == longitudinal and
cp.pcmCruise != longitudinal and (not longitudinal or cp.alphaLongitudinalAvailable) and
len(cp.safetyConfigs) == 1 and cp.safetyConfigs[0].safetyModel == CarParams.SafetyModel.gm and
cp.safetyConfigs[0].safetyParam in words)
gm_control_word(cp) in words)
except (AttributeError, IndexError, TypeError, ValueError):
return False
@@ -304,7 +341,7 @@ def is_volt_longitudinal(cp: CarParams) -> bool:
def is_volt_gateway_alternate_brake(cp: CarParams) -> bool:
"""The exact gateway Volt profile using EBCM pedal input and PT friction output."""
return is_volt_gateway_profile(cp) and bool(cp.safetyConfigs[0].safetyParam & GMSafetyFlags.VOLT_GATEWAY_ALT_BRAKE)
return is_volt_gateway_profile(cp) and bool(gm_control_word(cp) & GMSafetyFlags.VOLT_GATEWAY_ALT_BRAKE)
class Footnote(Enum):
+3 -1
View File
@@ -3,6 +3,7 @@
#include "opendbc/safety/declarations.h"
#include "opendbc/safety/can_tx.h"
#include "gm_aol.h"
#include "gm_volt_one_pedal.h"
#include "gm_bolt_cc.h"
#include "gm_cc_pedal.h"
#include "gm_volt_auto_hold.h"
@@ -405,6 +406,7 @@ static void gm_rx_hook(const CANPacket_t *msg) {
}
gm_cc_pedal_rx(msg);
gm_one_pedal_observe(msg);
gm_hold_rx(msg);
}
@@ -714,7 +716,7 @@ static bool gm_fwd_hook(int bus_num, int addr) {
}
static safety_config gm_init(uint16_t safety_param) {
uint16_t param = safety_param;
uint16_t param = gm_one_pedal_reset(safety_param);
gm_cc_pedal_silverado = (safety_param == 0xC182U) || (safety_param == 0xC183U) ||
(safety_param == 0xC184U) || (safety_param == 0xC185U);
gm_cc_pedal_ordinary_stock = (safety_param == 0xC186U) || (safety_param == 0xC187U);
@@ -72,6 +72,7 @@ static void gm_aol_initialize(bool profile_supported) {
static bool gm_aol_profile_word(uint16_t word) {
bool supported = false;
switch (word) {
case 0xD100U: case 0xD101U: case 0xD110U: case 0xD111U:
case 0U: case 0x80U:
case 0x201U: case 0x601U: case 0xA01U: case 0xE01U:
case 0xC171U: case 0xC172U: case 5U:
@@ -87,6 +88,11 @@ static bool gm_aol_profile_word(uint16_t word) {
case 0xC130U: case 0xC131U: case 0xC140U: case 0xC141U:
case 0xC150U:
#ifdef ALLOW_DEBUG
case 0xD102U: case 0xD103U: case 0xD104U: case 0xD105U:
case 0xD106U: case 0xD107U: case 0xD108U: case 0xD109U:
case 0xD10AU: case 0xD112U: case 0xD113U: case 0xD114U:
case 0xD115U: case 0xD116U: case 0xD117U: case 0xD118U:
case 0xD119U: case 0xD11AU:
case 0x1003U: case 0x1403U:
case 0x203U: case 0x603U: case 0xA03U: case 0xE03U:
case 0xC170U: case 0xC173U:
@@ -211,6 +211,7 @@ static void gm_bolt_cc_rx(const CANPacket_t *msg) {
}
static void gm_bolt_cc_optional_rx(const CANPacket_t *msg) {
gm_one_pedal_observe(msg);
if (gm_bolt_cc_selected && (msg->addr == 0xBDU) && (msg->bus == 0U) && (GET_LEN(msg) == 7U)) {
gm_bolt_cc_profile_rx(msg);
}
@@ -22,6 +22,7 @@ static bool gm_hold_regen;
static bool gm_hold_stopped;
static bool gm_hold_armed;
static bool gm_hold_moving;
static bool gm_hold_one_pedal_moving;
static uint8_t gm_hold_acc;
static uint32_t gm_hold_drive_us;
static uint32_t gm_hold_regen_release_us;
@@ -29,6 +30,10 @@ static bool gm_hold_regen_released;
static bool gm_hold_counter_seen;
static uint8_t gm_hold_counter;
static void gm_one_pedal_withdraw(void) {
if (!gm_one_pedal_config.paired_auto_hold) { gm_hold_accepted = false; }
}
typedef struct {
bool enabled;
bool alternate;
@@ -53,7 +58,7 @@ static void gm_hold_reset(const GMHoldConfig *config) {
for (uint8_t i = 0U; i < 7U; i++) { gm_hold_seen[i] = false; gm_hold_us[i] = 0U; }
gm_hold_brake_unavailable = true;
gm_hold_main = false; gm_hold_forward = false; gm_hold_gas = false;
gm_hold_brake = false; gm_hold_regen = false; gm_hold_stopped = false; gm_hold_armed = false; gm_hold_moving = false;
gm_hold_brake = false; gm_hold_regen = false; gm_hold_stopped = false; gm_hold_armed = false; gm_hold_moving = false; gm_hold_one_pedal_moving = false;
gm_hold_acc = 0U; gm_hold_drive_us = 0U; gm_hold_regen_release_us = 0U;
gm_hold_regen_released = false; gm_hold_counter_seen = false; gm_hold_counter = 0U;
}
@@ -68,13 +73,20 @@ static bool gm_hold_sources_current(uint32_t now) {
return current;
}
static bool gm_hold_ready(uint32_t now) {
static bool gm_hold_physical_ready(uint32_t now) {
if (gm_hold_regen_released && (safety_get_ts_elapsed(now, gm_hold_regen_release_us) >= 1000000U)) {
gm_hold_regen_released = false;
}
const bool cooldown = gm_hold_regen_released;
return !safety_rx_checks_invalid && !relay_malfunction && gm_hold_sources_current(now) && gm_hold_main && gm_hold_forward && !gm_hold_gas &&
!gm_hold_regen && !gm_hold_brake_unavailable && !cooldown && (gm_hold_drive_us >= 3000000U);
!gm_hold_regen && !gm_hold_brake_unavailable && (gm_hold_drive_us >= 3000000U);
}
static bool gm_hold_ready(uint32_t now) {
return gm_hold_physical_ready(now) && !gm_hold_regen_released;
}
static bool gm_one_pedal_physical_ready(uint32_t now) {
return gm_one_pedal_mode_qualified(now) && !gm_hold_brake && gm_hold_physical_ready(now);
}
static void gm_hold_rx(const CANPacket_t *msg) {
@@ -106,6 +118,7 @@ static void gm_hold_rx(const CANPacket_t *msg) {
gm_hold_stopped = (left <= 10U) && (right <= 10U);
gm_hold_near_stop = (left <= 28U) && (right <= 28U);
if (!gm_hold_near_stop) { gm_hold_accepted = false; }
gm_hold_one_pedal_moving = (left > 10U) && (right > 10U);
gm_hold_moving = (left > 0U) || (right > 0U);
const uint32_t elapsed = safety_get_ts_elapsed(now, gm_hold_us[4]);
// Raw wheel units are .0311 km/h; twelve units exceed .1 m/s.
@@ -123,10 +136,14 @@ static void gm_hold_rx(const CANPacket_t *msg) {
// Unrelated or malformed traffic cannot refresh physical authority.
}
if (source >= 0) { gm_hold_seen[source] = true; gm_hold_us[source] = now; }
if (!gm_hold_ready(now)) { gm_hold_armed = false; gm_hold_accepted = false; }
if (!gm_hold_ready(now)) {
gm_hold_armed = false;
if (!gm_one_pedal_physical_ready(now)) { gm_hold_accepted = false; }
}
else if (gm_hold_moving || gm_hold_brake) { gm_hold_armed = true; }
else { /* Retain a previously armed stopped hold. */ }
if (get_longitudinal_allowed()) { gm_hold_accepted = false; }
if (get_longitudinal_allowed() || (gm_one_pedal_config.enabled && !gm_one_pedal_config.paired_auto_hold &&
(!gm_one_pedal_mode_qualified(now) || gm_hold_brake))) { gm_hold_accepted = false; }
}
}
@@ -142,15 +159,27 @@ static bool gm_hold_brake_tx(const CANPacket_t *msg, int brake) {
(checksum == expected) && ((msg->data[4] & 0xFCU) == 0U) &&
(release || !gm_hold_counter_seen || (counter == ((gm_hold_counter + 1U) & 3U)));
const bool hold_mode = (((mode == 0xAU) || (mode == 0xBU)) && (gm_hold_acc != 4U)) || ((mode == 0xDU) && (gm_hold_acc == 4U));
if (!gm_hold_ready(now)) { gm_hold_armed = false; gm_hold_accepted = false; }
const bool stationary = gm_hold_stopped || (gm_hold_sdgm && gm_hold_accepted && gm_hold_near_stop);
if (!gm_hold_ready(now)) {
gm_hold_armed = false;
if (!gm_one_pedal_physical_ready(now)) { gm_hold_accepted = false; }
}
const int minimum = gm_hold_sdgm ? 100 : 80;
const bool hold = gm_hold_armed && gm_hold_ready(now) && stationary &&
const bool one_pedal_mode = gm_one_pedal_mode_qualified(now);
if (gm_one_pedal_config.enabled && !gm_one_pedal_config.paired_auto_hold && (!one_pedal_mode || gm_hold_brake)) { gm_hold_accepted = false; }
const bool stationary = gm_hold_stopped || (gm_hold_sdgm && gm_hold_accepted && gm_hold_near_stop);
const bool auto_hold_owner = (!gm_one_pedal_config.enabled || gm_one_pedal_config.paired_auto_hold) &&
gm_hold_armed && gm_hold_ready(now);
const bool one_pedal_owner = gm_one_pedal_config.enabled && one_pedal_mode && !gm_hold_brake && gm_hold_physical_ready(now);
const bool hold = auto_hold_owner && stationary &&
(brake >= minimum) && (brake <= 240) && hold_mode;
const bool permitted = packet_valid && (release || hold);
const bool one_pedal_brake = one_pedal_owner && hold_mode && (brake <= 400) &&
((gm_hold_stopped && (brake >= minimum)) || (gm_hold_one_pedal_moving && (brake >= 0)));
const bool permitted = packet_valid && (release || hold || one_pedal_brake);
if (permitted) {
gm_hold_counter_seen = true; gm_hold_counter = counter;
gm_hold_accepted = !release;
if (release || !hold) { gm_hold_accepted = false; }
else if (gm_hold_stopped) { gm_hold_accepted = true; }
else { /* Retain credit only from an accepted stationary hold. */ }
}
return permitted;
}
@@ -0,0 +1,101 @@
#pragma once
#define GM_ONE_PEDAL_MODE_TIMEOUT_US 300000U
typedef struct {
bool enabled;
bool paired_auto_hold;
uint16_t canonical_hold_word;
} GMOnePedalConfig;
static GMOnePedalConfig gm_one_pedal_config = {0};
static bool gm_one_pedal_low;
static bool gm_one_pedal_mode;
static bool gm_one_pedal_mode_seen;
static uint32_t gm_one_pedal_mode_us;
static uint16_t gm_one_pedal_reset(uint16_t word) {
gm_one_pedal_config = (GMOnePedalConfig){0};
gm_one_pedal_low = false;
gm_one_pedal_mode = false;
gm_one_pedal_mode_seen = false;
gm_one_pedal_mode_us = 0U;
switch (word) {
case 0xD100U: case 0xD110U:
gm_one_pedal_config.canonical_hold_word = 0x4084U;
gm_one_pedal_config.paired_auto_hold = word == 0xD110U;
break;
case 0xD101U: case 0xD111U:
gm_one_pedal_config.canonical_hold_word = 0xC084U;
gm_one_pedal_config.paired_auto_hold = word == 0xD111U;
break;
#ifdef ALLOW_DEBUG
case 0xD102U: case 0xD112U:
gm_one_pedal_config.canonical_hold_word = 0x4287U;
gm_one_pedal_config.paired_auto_hold = word == 0xD112U;
break;
case 0xD103U: case 0xD113U:
gm_one_pedal_config.canonical_hold_word = 0x4687U;
gm_one_pedal_config.paired_auto_hold = word == 0xD113U;
break;
case 0xD104U: case 0xD114U:
gm_one_pedal_config.canonical_hold_word = 0x4A87U;
gm_one_pedal_config.paired_auto_hold = word == 0xD114U;
break;
case 0xD105U: case 0xD115U:
gm_one_pedal_config.canonical_hold_word = 0x4E87U;
gm_one_pedal_config.paired_auto_hold = word == 0xD115U;
break;
case 0xD106U: case 0xD116U:
gm_one_pedal_config.canonical_hold_word = 0x4087U;
gm_one_pedal_config.paired_auto_hold = word == 0xD116U;
break;
case 0xD107U: case 0xD117U:
gm_one_pedal_config.canonical_hold_word = 0x5087U;
gm_one_pedal_config.paired_auto_hold = word == 0xD117U;
break;
case 0xD108U: case 0xD118U:
gm_one_pedal_config.canonical_hold_word = 0x5487U;
gm_one_pedal_config.paired_auto_hold = word == 0xD118U;
break;
case 0xD109U: case 0xD119U:
gm_one_pedal_config.canonical_hold_word = 0xC1D1U;
gm_one_pedal_config.paired_auto_hold = word == 0xD119U;
break;
case 0xD10AU: case 0xD11AU:
gm_one_pedal_config.canonical_hold_word = 0xC1D3U;
gm_one_pedal_config.paired_auto_hold = word == 0xD11AU;
break;
#endif
default:
break;
}
gm_one_pedal_config.enabled = gm_one_pedal_config.canonical_hold_word != 0U;
return gm_one_pedal_config.enabled ? gm_one_pedal_config.canonical_hold_word : word;
}
static bool gm_one_pedal_mode_qualified(uint32_t now);
static void gm_one_pedal_withdraw(void);
static void gm_one_pedal_observe(const CANPacket_t *msg) {
if (gm_one_pedal_config.enabled && (msg->bus == 0U)) {
if ((msg->addr == 0x1F5U) && (GET_LEN(msg) == 8U)) {
gm_one_pedal_low = ((msg->data[3] & 0xFU) == 6U) && !GET_BIT(msg, 41U);
} else if ((msg->addr == 0x3C7U) && (GET_LEN(msg) == 4U)) {
gm_one_pedal_mode = GET_BIT(msg, 7U);
gm_one_pedal_mode_seen = true;
gm_one_pedal_mode_us = microsecond_timer_get();
} else {
// Other traffic cannot refresh mode or physical Low.
}
if (!gm_one_pedal_mode_qualified(microsecond_timer_get())) { gm_one_pedal_withdraw(); }
}
}
static bool gm_one_pedal_mode_qualified(uint32_t now) {
// Cadence is unknown: this is a bounded observation, not a periodic RX claim.
if (gm_one_pedal_mode_seen && (safety_get_ts_elapsed(now, gm_one_pedal_mode_us) > GM_ONE_PEDAL_MODE_TIMEOUT_US)) {
gm_one_pedal_mode_seen = false;
}
return gm_one_pedal_config.enabled && (gm_one_pedal_low || (gm_one_pedal_mode_seen && gm_one_pedal_mode));
}
@@ -11,9 +11,10 @@ from opendbc.safety.tests.libsafety import libsafety_py
class TestVoltAutoHold(unittest.TestCase):
def init(self, alternate=False, extra=0, alternative=0, word=None):
def init(self, alternate=False, extra=0, alternative=0, word=None, physical_word=None):
self.safety = libsafety_py.libsafety
self.alternate = alternate or word == 0xC1D3
physical_word = word if physical_word is None else physical_word
self.alternate = alternate or physical_word in (0xC084, 0xC1D3)
self.safety.set_alternative_experience(alternative)
word = int(GMSafetyFlags.EV | GMSafetyFlags.VOLT_GATEWAY_LONG | GMSafetyFlags.VOLT_AUTO_HOLD) if word is None else word
if alternate:
@@ -23,8 +24,9 @@ class TestVoltAutoHold(unittest.TestCase):
self.time = 1
self.counter = 0
self.packer = CANPacker(DBC[CAR.CHEVROLET_VOLT][Bus.pt])
self.c9_brake = word in (0x4687, 0x4E87, 0x4087, 0x5487, 0xC1D1, 0xC1D3)
self.tx_bus = 0 if word in (0x4287, 0x4687, 0x4A87, 0x4E87, 0x4087, 0xC1D1, 0xC1D3) or alternate else 2
physical_word = word if physical_word is None else physical_word
self.c9_brake = physical_word in (0x4687, 0x4E87, 0x4087, 0x5487, 0xC1D1, 0xC1D3)
self.tx_bus = 0 if physical_word in (0x4287, 0x4687, 0x4A87, 0x4E87, 0x4087, 0xC1D1, 0xC1D3) or self.alternate else 2
def tearDown(self):
libsafety_py.libsafety.set_alternative_experience(0)
@@ -43,7 +43,10 @@ class TestGmVoltGatewayMapping(unittest.TestCase):
expected = [EXACT, EXACT | int(GMSafetyFlags.VOLT_GATEWAY_ALT_BRAKE),
EXACT | int(GMSafetyFlags.VOLT_AUTO_HOLD),
EXACT | int(GMSafetyFlags.VOLT_GATEWAY_ALT_BRAKE | GMSafetyFlags.VOLT_AUTO_HOLD)]
expected += [0xD100, 0xD101, 0xD110, 0xD111]
if not release:
expected += [0xD102, 0xD103, 0xD104, 0xD105, 0xD106, 0xD107, 0xD108, 0xD109, 0xD10A,
0xD112, 0xD113, 0xD114, 0xD115, 0xD116, 0xD117, 0xD118, 0xD119, 0xD11A]
expected += [7, 515, 1539, 2563, 3587, 4099, 5123, 16391, 16903, 17927, 18951, 19975,
20487, 21511, 49489, 49520, 49523, 0x4287, 0x4687, 0x4A87, 0x4E87, 0x4087, 0x5087, 0x5487, 0xC1D1, 0xC1D3]
self.assertEqual(accepted, sorted(expected))
@@ -0,0 +1,209 @@
"""Exact Volt inactive OnePedal source, routing and stopped-owner contracts."""
import unittest
from opendbc.car.gm import gmcan
from opendbc.car.structs import CarParams
from opendbc.safety.tests.libsafety import libsafety_py
from opendbc.safety.tests import test_gm_volt_auto_hold as hold_fixture
CANONICAL = (0x4084, 0xC084, 0x4287, 0x4687, 0x4A87, 0x4E87, 0x4087, 0x5087, 0x5487, 0xC1D1, 0xC1D3)
class TestVoltOnePedal(unittest.TestCase):
def fixture(self, index, paired=False, alternative=0):
self.release = libsafety_py.libsafety.set_safety_hooks(CarParams.SafetyModel.allOutput, 0) != 0
f = hold_fixture.TestVoltAutoHold()
f.init(word=0xD100 + index + (0x10 if paired else 0), physical_word=CANONICAL[index], alternative=alternative)
return f
def tearDown(self):
libsafety_py.libsafety.set_alternative_experience(0)
def mode(self, f, active=True, bus=0, length=4):
frame = f.packer.make_can_msg('EVDriveMode', bus, {'SinglePedalModeActive': int(active)})
self.assertEqual(frame[1][0] & 0x80, 0x80 if active else 0)
f.rx(frame[0], frame[1][:length], bus)
def test_all_exact_profiles_moving_and_stopped_bounds(self):
for paired in (False, True):
for index in range(11):
with self.subTest(index=index, paired=paired):
f = self.fixture(index, paired)
denied = self.release and index >= 2
for _ in range(32):
f.observations(gear=6, healthy=not denied)
self.assertEqual(f.tx(400), not denied)
self.assertFalse(f.tx(401))
self.assertFalse(f.safety.safety_tx_hook(f.frame(bus=2 if f.tx_bus == 0 else 0)))
f.observations(speed=0, gear=6, healthy=not denied)
minimum = 100 if index in (7, 8) else 80
self.assertFalse(f.tx(minimum - 1))
self.assertEqual(f.tx(minimum), not denied)
self.assertEqual(f.tx(240), not denied)
self.assertEqual(f.tx(400), not denied)
self.assertFalse(f.tx(401))
self.assertEqual(f.tx(0, 1), not denied)
def test_solo_mode_and_paired_hold_are_distinct(self):
for index in range(11):
if self.release_mode() and index >= 2:
continue
for paired in (False, True):
f = self.fixture(index, paired)
f.dwell()
self.assertEqual(f.tx(), paired)
self.mode(f)
self.assertTrue(f.tx())
f.observations(speed=0, brake=True)
self.assertEqual(f.tx(), paired)
f.observations(speed=0)
self.mode(f, False)
self.assertEqual(f.tx(), paired)
@staticmethod
def release_mode():
return libsafety_py.libsafety.set_safety_hooks(CarParams.SafetyModel.allOutput, 0) != 0
def test_mode_current_observation_low_and_physical_withdrawal(self):
for paired in (False, True):
f = self.fixture(0, paired)
f.dwell()
f.observations(speed=100)
self.mode(f, bus=2)
self.assertFalse(f.tx(400))
self.mode(f, length=3)
self.assertFalse(f.tx(400))
self.mode(f)
self.assertTrue(f.tx(400))
for _ in range(4):
f.observations(speed=100)
self.assertFalse(f.tx(400))
f.time = 0xFFFFFF00 - 100000
f.observations(speed=100)
self.assertFalse(f.tx(400))
f.time = 0
f.observations(speed=100)
self.assertFalse(f.tx(400)) # Expired mode cannot revive at a 32-bit timer wrap.
f.observations(speed=100, gear=6)
self.assertTrue(f.tx(400)) # Fresh Low needs no drive-mode frame.
f.observations(speed=100, gear=6, manual=True)
self.assertFalse(f.tx(400))
for changed in ({'main': False}, {'gas': True}, {'brake': True}, {'regen': True}, {'gear': 2}, {'brake_unavailable': True}):
f = self.fixture(0, paired)
for _ in range(32):
f.observations(gear=6)
self.assertTrue(f.tx(400))
f.observations(gear=6, **changed) if 'gear' not in changed else f.observations(**changed)
self.assertFalse(f.tx(400))
self.assertTrue(f.tx(0, 1))
def test_moving_does_not_seed_sdgm_hold_credit_or_mixed_wheels(self):
for index in (7, 8):
f = self.fixture(index)
if self.release:
self.assertFalse(f.tx())
continue
for _ in range(32):
f.observations(gear=6)
self.assertTrue(f.tx(400))
f.observations(speed=20, gear=4)
self.assertFalse(f.tx()) # No accepted stationary hold; mode withdrawn.
self.mode(f)
self.assertTrue(f.tx(400))
f.observations(speed=0)
self.mode(f)
self.assertTrue(f.tx(100))
f.observations(speed=20)
self.mode(f)
self.assertTrue(f.tx(240))
self.assertTrue(f.tx(400))
self.mode(f, False)
self.mode(f)
self.assertTrue(f.tx(400)) # Mode withdrawal cleared old stop credit before reactivation.
self.assertTrue(f.tx(0, 1))
self.assertTrue(f.tx(400)) # Explicitly moving after released stop credit.
f = self.fixture(0)
for _ in range(32):
f.observations(gear=6)
f.rx(0x34A, [0, 20, 0, 0, 0])
self.assertFalse(f.tx(400))
def test_no_positive_gas_and_unknown_tags_reset(self):
f = self.fixture(0, alternative=32)
for _ in range(32):
f.observations(gear=6)
f.safety.set_aol_test_heartbeat(True)
f.safety.aol_set_host_request(1)
self.assertTrue(f.tx(400))
for gas, enabled, accepted in ((-650, False, True), (-649, False, False), (1, False, False), (-650, True, False)):
frame = gmcan.create_gas_regen_command(f.packer, 0, gas, 0, enabled, False)
self.assertEqual(f.safety.safety_tx_hook(libsafety_py.make_CANPacket(frame[0], frame[2], frame[1])), accepted)
for word in (0xD0FF, 0xD10B, 0xD10F, 0xD11B, 0xD11F, 0xD120):
f.init(word=word)
for _ in range(32):
f.observations(gear=6, healthy=False)
self.assertFalse(f.tx(400))
f.init()
for _ in range(32):
f.observations(gear=6)
self.assertFalse(f.tx(400))
f.observations(speed=0)
self.assertTrue(f.tx(80))
def test_one_pedal_release_is_immediate_but_independent_hold_keeps_cooldown(self):
for paired in (False, True):
for stopped in (False, True):
f = self.fixture(0, paired)
for _ in range(32):
f.observations(gear=6)
speed = 0 if stopped else 100
f.observations(speed=speed, gear=6, regen=True)
self.assertFalse(f.tx(100))
f.observations(speed=speed, gear=6)
self.assertTrue(f.tx(100))
f = self.fixture(0, paired=True)
f.dwell()
f.observations(speed=0, brake=True, regen=True)
f.observations(speed=0, brake=True)
self.assertFalse(f.tx())
for _ in range(10):
f.observations(speed=0, brake=True)
self.assertTrue(f.tx())
def test_independent_paired_hold_keeps_240_and_sdgm_credit(self):
for index in (0, 7, 8):
f = self.fixture(index, paired=True)
if self.release and index >= 2:
self.assertFalse(f.tx())
continue
f.dwell()
self.assertTrue(f.tx(240))
self.assertFalse(f.tx(241))
if index in (7, 8):
f.observations(speed=20)
self.assertTrue(f.tx(240))
self.assertFalse(f.tx(241))
self.assertTrue(f.tx(0, 1))
self.assertFalse(f.tx(240))
f.observations(speed=0, gear=6)
self.assertTrue(f.tx(400))
f.observations(speed=0, gear=6, brake=True)
self.assertTrue(f.tx(240))
self.assertFalse(f.tx(241))
def test_only_independent_auto_hold_seeds_sdgm_mixed_wheel_credit(self):
for index in (7, 8):
for paired in (False, True):
with self.subTest(index=index, paired=paired):
f = self.fixture(index, paired)
if self.release:
self.assertFalse(f.tx(200))
continue
for _ in range(32):
f.observations(gear=6)
f.observations(speed=0, gear=6)
self.assertTrue(f.tx(200))
f.rx(0x34A, [0, 20, 0, 0, 0])
self.assertEqual(f.tx(200), paired)
self.assertTrue(f.tx(0, 1))
self.assertFalse(f.tx(200))
+1
View File
@@ -24,6 +24,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ToyotaAutoHold", {PERSISTENT, BOOL, "0"}},
{"VoltSNG", {PERSISTENT, BOOL, "0"}},
{"GMAutoHold", {PERSISTENT, BOOL, "0"}},
{"VoltOnePedalMode", {PERSISTENT, BOOL, "0"}},
{"HondaBoschARadar", {PERSISTENT, BOOL, "1"}},
{"HondaGasFactorParams", {PERSISTENT, FLOAT, "1.0"}},
{"HondaWindFactorParams", {PERSISTENT, FLOAT, "1.0"}},
@@ -180,7 +180,11 @@ int main() {
forte.safety_mode = HYUNDAI_AOL_PROFILE.mode;
assert(!aol_capable(forte, HYUNDAI_AOL_PROFILE.mode, VEHICLE_REGISTRY));
}
for (const uint16_t word : {0U, 0x80U}) {
for (const uint16_t word : {0U, 0x80U,
0xD100U, 0xD101U, 0xD102U, 0xD103U, 0xD104U, 0xD105U,
0xD106U, 0xD107U, 0xD108U, 0xD109U, 0xD10AU,
0xD110U, 0xD111U, 0xD112U, 0xD113U, 0xD114U, 0xD115U,
0xD116U, 0xD117U, 0xD118U, 0xD119U, 0xD11AU}) {
AolAxisNegotiator negotiator(VEHICLE_REGISTRY);
FakeTransport transport;
auto gateway = status();
@@ -190,20 +194,26 @@ int main() {
queue(transport, gateway, gateway);
const auto first = run(negotiator, transport, axis("gateway"), NOW, GM_AOL_PROFILE.mode);
assert(first.plan.capable && first.plan.request_mask == 0U && first.outcome.compatible);
assert(first.outcome.status && first.outcome.status->safety_param == word);
auto active = gateway;
active.request_mask = 3U;
active.permission_mask = 1U;
queue(transport, gateway, active);
const auto second = run(negotiator, transport, axis("gateway", true, true), NOW, GM_AOL_PROFILE.mode);
assert(second.plan.capable && second.plan.request_mask == 3U && second.outcome.compatible);
assert(second.outcome.status && second.outcome.status->permission_mask == 1U);
assert(second.outcome.status && second.outcome.status->permission_mask == 1U && second.outcome.status->safety_param == word);
}
for (const uint16_t word : {0x40U, 0x81U, 0x82U, 0x84U, 0x180U}) {
for (const uint16_t word : {0x40U, 0x81U, 0x82U, 0x84U, 0x180U, 0xD0FFU, 0xD10BU, 0xD10FU, 0xD11BU, 0xD11FU, 0xD120U}) {
assert(!gm_aol_param(word));
auto unknown = status();
unknown.safety_mode = GM_AOL_PROFILE.mode;
unknown.safety_param = word;
assert(!aol_capable(unknown, GM_AOL_PROFILE.mode, VEHICLE_REGISTRY));
AolAxisNegotiator negotiator(VEHICLE_REGISTRY);
FakeTransport transport;
queue(transport, unknown, unknown);
const auto rejected = run(negotiator, transport, axis(), NOW, GM_AOL_PROFILE.mode);
assert(!rejected.plan.capable && rejected.plan.request_mask == 0U && !rejected.outcome.compatible);
}
for (uint32_t word = 0U; word <= 65535U; word++) {
auto gm = status();
+6 -1
View File
@@ -16,7 +16,7 @@ from opendbc.car.gm.tests.test_bolt_volt_configurations import ordinary_params
from opendbc.car.gm.tests.test_volt_camera_control import camera_params
from opendbc.car.gm.tests.test_volt_camera_removed import removed_params
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
from opendbc.car.gm.values import CAR, DBC, ORDINARY_ASCM_CAR, ORDINARY_SDGM_CAR, ORDINARY_CAMERA_CAR, ORDINARY_CC_CAR
from opendbc.car.gm.values import CAR, DBC, ORDINARY_ASCM_CAR, ORDINARY_SDGM_CAR, ORDINARY_CAMERA_CAR, ORDINARY_CC_CAR, is_volt_one_pedal
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
@@ -106,6 +106,11 @@ def configurations():
VehicleStartupPreferences(gm_auto_hold=True).prepare(selected)
if selected.safetyConfigs[0].safetyParam != cp.safetyConfigs[0].safetyParam:
yield selected
for paired in (False, True):
selected = cp.as_reader().as_builder()
VehicleStartupPreferences(volt_one_pedal=True, gm_auto_hold=paired).prepare(selected)
if is_volt_one_pedal(selected):
yield selected
class TestGmAol(unittest.TestCase):
+6
View File
@@ -4,6 +4,12 @@
inline bool gm_aol_param(uint16_t param) {
switch (param) {
case 0xD100U: case 0xD101U: case 0xD102U: case 0xD103U:
case 0xD104U: case 0xD105U: case 0xD106U: case 0xD107U:
case 0xD108U: case 0xD109U: case 0xD10AU: case 0xD110U:
case 0xD111U: case 0xD112U: case 0xD113U: case 0xD114U:
case 0xD115U: case 0xD116U: case 0xD117U: case 0xD118U:
case 0xD119U: case 0xD11AU:
case 0U: case 0x80U:
case 0x201U: case 0x601U: case 0xA01U: case 0xE01U:
case 0x203U: case 0x603U: case 0xA03U: case 0xE03U:
+8 -5
View File
@@ -1,15 +1,18 @@
from opendbc.car.gm.values import is_gm_auto_hold
from opendbc.car.gm.values import is_gm_auto_hold, is_volt_one_pedal
from openpilot.starpilot.saved_source import read_saved
class AutoHoldPreference:
def __init__(self, cp, params):
self.cp, self.params = cp, params
def __init__(self, cp, params, key="GMAutoHold"):
if key not in ("GMAutoHold", "VoltOnePedalMode"):
raise ValueError("Unsupported GM stop preference")
self.cp, self.params, self.key = cp, params, key
self.supported = is_volt_one_pedal if key == "VoltOnePedalMode" else is_gm_auto_hold
self.read_ns = 0
self.enabled = False
def update(self, now_ns):
if type(now_ns) is not int or now_ns <= 0 or not is_gm_auto_hold(self.cp):
if type(now_ns) is not int or now_ns <= 0 or not self.supported(self.cp):
self.enabled = False
return False
if now_ns < self.read_ns:
@@ -19,7 +22,7 @@ class AutoHoldPreference:
if self.read_ns == 0 or now_ns - self.read_ns >= 250_000_000:
self.read_ns = now_ns
try:
choice = read_saved(self.params, "GMAutoHold", 8)
choice = read_saved(self.params, self.key, 8)
safe, safe_valid = read_saved(self.params, "SafeMode", 8)
longitudinal, long_valid = read_saved(self.params, "DisableOpenpilotLongitudinal", 8)
master = read_saved(self.params, "OpenpilotEnabledToggle", 8)
+4 -1
View File
@@ -110,10 +110,13 @@ class ResumePlanInputs:
def configure_controller(CI, params):
cp = CI.CP
controller = CI.CC
from opendbc.car.gm.values import is_gm_auto_hold
from opendbc.car.gm.values import is_gm_auto_hold, is_volt_one_pedal
if controller is not None and is_gm_auto_hold(cp) and controller.gm_auto_hold:
from openpilot.starpilot.car.gm.auto_hold import AutoHoldPreference
controller.gm_auto_hold_input = AutoHoldPreference(cp, params)
if controller is not None and is_volt_one_pedal(cp) and controller.volt_one_pedal:
from openpilot.starpilot.car.gm.auto_hold import AutoHoldPreference
controller.volt_one_pedal_input = AutoHoldPreference(cp, params, "VoltOnePedalMode")
from opendbc.car.gm.values import is_volt_longitudinal
if controller is not None and is_volt_longitudinal(cp) and controller.volt_sng:
try:
@@ -5,7 +5,7 @@ from openpilot.starpilot.vehicle_selection import read_selection
STARTUP_KEYS = ('AlphaLongitudinalEnabled', 'IsReleaseBranch', 'OpenpilotEnabledToggle', 'SafeMode',
'DisableOpenpilotLongitudinal', 'ToyotaAutoHold', 'VoltSNG', 'GMAutoHold', 'TurnAssist', 'LongPitch',
'DisableOpenpilotLongitudinal', 'ToyotaAutoHold', 'VoltSNG', 'GMAutoHold', 'VoltOnePedalMode', 'TurnAssist', 'LongPitch',
'HondaBoschARadar', 'NAPPedalEnabled', 'NAPRadarEnabled', 'NAPRadarBehindNosecone')
@@ -75,7 +75,10 @@ class TestGMPitchStartupAndSettings(unittest.TestCase):
from opendbc.car.gm.interface import CarInterface
from openpilot.starpilot.vehicle_selection import encode
from openpilot.starpilot.ui.feature_settings_state import FeaturePage, row_change, row_default
settings = (("VoltSNG", "volt_sng", (CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_ASCM,
from openpilot.starpilot.ui.vehicle_bool import confirmation_question
settings = (("VoltOnePedalMode", "volt_one_pedal", (CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_VOLT_CAMERA, CAR.CHEVROLET_VOLT_2019)),
("VoltSNG", "volt_sng", (CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_VOLT_CAMERA, CAR.CHEVROLET_VOLT_2019)),
("GMAutoHold", "gm_auto_hold", (CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_VOLT_CAMERA, CAR.CHEVROLET_VOLT_2019, CAR.BUICK_LACROSSE)))
@@ -95,11 +98,16 @@ class TestGMPitchStartupAndSettings(unittest.TestCase):
self.assertEqual("regen paddle" in row().reason, identity != CAR.BUICK_LACROSSE)
change = row_change(row())
assert change is not None
question = confirmation_question(change)
if key == "GMAutoHold":
self.assertEqual("regen paddle" in question, identity != CAR.BUICK_LACROSSE)
elif key == "VoltOnePedalMode":
self.assertIn("Lifting off the accelerator can apply the brakes", question)
self.assertTrue(owner.apply(replace(change, confirmation=True)))
self.assertEqual(self.saved.get_bool(key), change.value == 'On')
self.assertTrue(owner.apply(row_default(row())))
self.assertFalse(self.saved.get_bool(key))
controller = SimpleNamespace(volt_sng=False, gm_auto_hold=False, long_pitch=True)
controller = SimpleNamespace(volt_sng=False, gm_auto_hold=False, volt_one_pedal=False, long_pitch=True)
VehicleStartupPreferences(**{preference: True}).configure_controller(SimpleNamespace(CP=cp, CC=controller))
self.assertFalse(getattr(controller, preference))
fresh = row()
@@ -27,7 +27,7 @@ class TestVehicleStartupPreferences(unittest.TestCase):
def test_gm_stop_preferences_require_strict_saved_opt_in(self):
from openpilot.common.params import ParamKeyType
for key, name in (("VoltSNG", "volt_sng"), ("GMAutoHold", "gm_auto_hold")):
for key, name in (("VoltSNG", "volt_sng"), ("GMAutoHold", "gm_auto_hold"), ("VoltOnePedalMode", "volt_one_pedal")):
self.raw("SafeMode", None)
self.assertEqual(self.params.get_type(key), ParamKeyType.BOOL)
self.assertIs(self.params.get_default_value(key), False)
@@ -43,29 +43,31 @@ class TestVehicleStartupPreferences(unittest.TestCase):
from opendbc.car.gm.tests.test_bolt_volt_configurations import ordinary_params
from opendbc.car.gm.values import CAR as GM_CAR
from openpilot.starpilot.car.gm.auto_hold import AutoHoldPreference
cp = ordinary_params(GM_CAR.CHEVROLET_VOLT, radar=True)
VehicleStartupPreferences(gm_auto_hold=True).prepare(cp)
now = 1_000_000_000
for key in ("GMAutoHold", "OpenpilotEnabledToggle", "SafeMode", "DisableOpenpilotLongitudinal"):
for raw in (b"0", b"1", b"", b"true", None):
for clean_key, value in (("GMAutoHold", b"1"), ("OpenpilotEnabledToggle", b"1"),
("SafeMode", b"0"), ("DisableOpenpilotLongitudinal", b"0")):
self.raw(clean_key, value)
owner = AutoHoldPreference(cp, self.params)
self.assertTrue(owner.update(now))
self.raw(key, raw)
now += 250_000_000
expected = raw == b"1" if key in ("GMAutoHold", "OpenpilotEnabledToggle") else raw in (None, b"0")
self.assertEqual(owner.update(now), expected, (key, raw))
self.assertFalse(owner.update(now - 1))
cp.passive = True
self.assertFalse(owner.update(now))
cp.passive = False
preferences = VehicleStartupPreferences(gm_auto_hold=True, disable_bolt_long=True)
preferences.prepare(cp)
preferences.finalize(cp)
self.assertFalse(cp.openpilotLongitudinalControl)
self.assertEqual(cp.safetyConfigs[0].safetyParam, 0x4004)
for preference in ("GMAutoHold", "VoltOnePedalMode"):
cp = ordinary_params(GM_CAR.CHEVROLET_VOLT, radar=True)
VehicleStartupPreferences(gm_auto_hold=preference == "GMAutoHold",
volt_one_pedal=preference == "VoltOnePedalMode").prepare(cp)
now = 1_000_000_000
for key in (preference, "OpenpilotEnabledToggle", "SafeMode", "DisableOpenpilotLongitudinal"):
for raw in (b"0", b"1", b"", b"true", None):
for clean_key, value in ((preference, b"1"), ("OpenpilotEnabledToggle", b"1"),
("SafeMode", b"0"), ("DisableOpenpilotLongitudinal", b"0")):
self.raw(clean_key, value)
owner = AutoHoldPreference(cp, self.params, preference)
self.assertTrue(owner.update(now))
self.raw(key, raw)
now += 250_000_000
expected = raw == b"1" if key in (preference, "OpenpilotEnabledToggle") else raw in (None, b"0")
self.assertEqual(owner.update(now), expected, (preference, key, raw))
self.assertFalse(owner.update(now - 1))
cp.passive = True
self.assertFalse(owner.update(now))
cp.passive = False
preferences = VehicleStartupPreferences(gm_auto_hold=True, volt_one_pedal=True, disable_bolt_long=True)
preferences.prepare(cp)
preferences.finalize(cp)
self.assertFalse(cp.openpilotLongitudinalControl)
self.assertEqual(cp.safetyConfigs[0].safetyParam, 0x4004)
def test_suburban_saved_long_pitch_configures_exact_controller(self):
from opendbc.car.gm.carcontroller import CarController
@@ -156,6 +158,54 @@ class TestVehicleStartupPreferences(unittest.TestCase):
else:
self.assertEqual(published.safetyConfigs[0].safetyModel, structs.CarParams.SafetyModel.noOutput)
def test_volt_one_pedal_card_retains_exact_owner_and_withdrawal(self):
from opendbc.car.gm.values import CAR as GM_CAR, is_volt_one_pedal, gm_control_word
profiles = [
(GM_CAR.CHEVROLET_VOLT, {0xBE: 6}, {0x460: 8}, {}, 0xD100),
(GM_CAR.CHEVROLET_VOLT, {}, {0x460: 8}, {}, 0xD101),
(GM_CAR.CHEVROLET_VOLT_ASCM, {0x2FF: 8, 0xBE: 6}, {}, {}, 0xD102),
(GM_CAR.CHEVROLET_VOLT_ASCM, {0x2FF: 8}, {}, {}, 0xD103),
(GM_CAR.CHEVROLET_VOLT_ASCM, {0x2FF: 8, 0xBE: 6}, {0x460: 8}, {}, 0xD104),
(GM_CAR.CHEVROLET_VOLT_ASCM, {0x2FF: 8}, {0x460: 8}, {}, 0xD105),
(GM_CAR.CHEVROLET_VOLT_CAMERA, {}, {}, {0x320: 6}, 0xD106),
(GM_CAR.CHEVROLET_VOLT_2019, {0x2FF: 8, 0xBE: 6}, {}, {}, 0xD107),
(GM_CAR.CHEVROLET_VOLT_2019, {0x2FF: 8}, {}, {}, 0xD108),
]
for alternate in (False, True):
observed = {0x184: 8, 0x34A: 5, 0x1C4: 8, 0xC9: 8, 0x1E1: 7, 0xF1 if alternate else 0xBE: 6}
profiles.append((GM_CAR.CHEVROLET_VOLT_CAMERA, observed, {}, {}, 0xD109 + alternate))
for candidate, pt, radar, camera, solo_word in profiles:
for paired in (False, True):
for state in ('on', 'off', 'live_off', 'master_off', 'safe', 'disable_long', 'release'):
with self.subTest(candidate=candidate, word=hex(solo_word), paired=paired, state=state):
observed = gen_empty_fingerprint()
observed[0], observed[1], observed[2] = dict(pt), dict(radar), dict(camera)
self.params.put_bool('AlphaLongitudinalEnabled', True, block=True)
self.params.put_bool('GMAutoHold', paired, block=True)
self.params.put_bool('SafeMode', state == 'safe', block=True)
self.params.put_bool('DisableOpenpilotLongitudinal', state == 'disable_long', block=True)
self.params.put_bool('IsReleaseBranch', state == 'release', block=True)
host, constructed, published = self.start(candidate, key='VoltOnePedalMode', observed=observed,
capture=lambda ci: int(ci.CS.CP.safetyConfigs[0].safetyParam), enabled=state != 'master_off',
requested=state != 'off', change_saved=state == 'live_off')
admitted = state in ('on', 'live_off') or state == 'release' and candidate == GM_CAR.CHEVROLET_VOLT
self.assertEqual(is_volt_one_pedal(published), admitted)
if admitted:
self.assertEqual(constructed, [solo_word + (0x10 if paired else 0)])
self.assertEqual(published.safetyConfigs[0].safetyParam, constructed[0])
self.assertTrue(host.CI.CC.volt_one_pedal)
self.assertEqual(host.CI.CC.volt_one_pedal_input.update(1_000_000_000), state != 'live_off')
self.assertEqual(host.CI.CC.gm_auto_hold, paired)
else:
self.assertFalse(host.CI.CC.volt_one_pedal)
self.assertIsNone(host.CI.CC.volt_one_pedal_input)
self.assertEqual(gm_control_word(host.CI.CP), host.CI.CP.safetyConfigs[0].safetyParam)
if state == 'master_off':
self.assertEqual(published.safetyConfigs[0].safetyModel, structs.CarParams.SafetyModel.noOutput)
if state == 'disable_long':
self.assertFalse(published.openpilotLongitudinalControl)
self.assertEqual(self.params.get_bool('VoltOnePedalMode'), state not in ('off', 'live_off'))
def test_camera_volt_hold_startup_retains_release_and_disable_longitudinal_owners(self):
from opendbc.car.gm.values import CAR as GM_CAR, is_gm_auto_hold
for candidate in (GM_CAR.CHEVROLET_VOLT_ASCM, GM_CAR.CHEVROLET_VOLT_CAMERA, GM_CAR.CHEVROLET_VOLT_2019):
@@ -65,7 +65,8 @@ from openpilot.starpilot.controllers.toyota_cruise import capability as toyota_c
BOOL_DEFAULTS = {
"GMPedalLongitudinal": False, "ForceStops": False, "AlwaysAllowUploads": False, "TurnAssist": True,
"ReverseCruise": False, "ToyotaAutoHold": False, "VoltSNG": False, "GMAutoHold": False, "LongPitch": True, "DisableOpenpilotLongitudinal": False,
"ReverseCruise": False, "ToyotaAutoHold": False, "VoltSNG": False, "GMAutoHold": False, "VoltOnePedalMode": False,
"LongPitch": True, "DisableOpenpilotLongitudinal": False,
"SpeedLimitController": False, "ShowSpeedLimits": False,
"SLCConfirmation": False, "SLCConfirmationHigher": False, "SLCConfirmationLower": False,
"LaneCentering": False, "LaneCenteringPauseOnSignal": True,
@@ -255,7 +256,7 @@ class FeatureSettingsOwner:
identities = {CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_ASCM, CAR.CHEVROLET_VOLT_CAMERA, CAR.CHEVROLET_VOLT_2019}
if key == "GMAutoHold":
identities.add(CAR.BUICK_LACROSSE)
if key not in ("VoltSNG", "GMAutoHold") or cp is None or cp.brand != "gm" or cp.carFingerprint not in identities:
if key not in ("VoltSNG", "GMAutoHold", "VoltOnePedalMode") or cp is None or cp.brand != "gm" or cp.carFingerprint not in identities:
return None
selection = read_selection(self.params)
if not selection.readable or not selection.valid:
@@ -613,6 +614,12 @@ class FeatureSettingsOwner:
row = self._bool_row("VoltSNG", "Volt Stop-and-Go Assistance", parked and self.authority("parked_preferences"),
"Assists automatic resume from a cruise-controlled stop. Requires StarPilot speed control; applies after the next startup")
rows.append(replace(row, available=row.available and self._readable("VoltSNG"), capability=sng_capability))
one_pedal_capability = self._gm_stop_capability("VoltOnePedalMode")
if one_pedal_capability is not None:
row = self._bool_row("VoltOnePedalMode", "Volt One-Pedal Driving", parked and self.authority("parked_preferences"),
"With cruise main on and cruise disengaged, slows and holds when you lift off the accelerator in Low or single-pedal mode. " +
"Requires StarPilot speed control; applies after the next startup")
rows.append(replace(row, available=row.available and self._readable("VoltOnePedalMode"), capability=one_pedal_capability))
gm_hold_capability = self._gm_stop_capability("GMAutoHold")
if gm_hold_capability is not None:
from opendbc.car.gm.values import CAR
@@ -993,7 +1000,7 @@ class FeatureSettingsOwner:
return self._apply_pedal_setup(request)
if key == "DisableOpenpilotLongitudinal":
return self._apply_bolt_disable(request)
if key in ("VoltSNG", "GMAutoHold"):
if key in ("VoltSNG", "GMAutoHold", "VoltOnePedalMode"):
return self._apply_gm_stop(request)
if key == "LongPitch":
return self._apply_long_pitch(request)
+6 -2
View File
@@ -1,6 +1,6 @@
"""Confirmation text for parked vehicle preferences."""
VEHICLE_BOOL_KEYS = frozenset(("GMPedalLongitudinal", "DisableOpenpilotLongitudinal", "LongPitch", "VoltSNG", "GMAutoHold"))
VEHICLE_BOOL_KEYS = frozenset(("GMPedalLongitudinal", "DisableOpenpilotLongitudinal", "LongPitch", "VoltSNG", "GMAutoHold", "VoltOnePedalMode"))
def confirmation_question(request):
@@ -16,7 +16,11 @@ def confirmation_question(request):
if request.key == "VoltSNG":
return f"{verb} Volt stop-and-go assistance after the next startup?"
if request.key == "GMAutoHold":
return f"{verb} Automatic Brake Hold after the next startup? Press the gas or regen paddle to release a hold."
from opendbc.car.gm.values import CAR
release = "gas" if request.vehicle_fingerprint == CAR.BUICK_LACROSSE else "gas or regen paddle"
return f"{verb} Automatic Brake Hold after the next startup? Press the {release} to release a hold."
if request.key == "VoltOnePedalMode":
return f"{verb} Volt one-pedal driving after the next startup? Lifting off the accelerator can apply the brakes with cruise disengaged and cruise main on."
if request.key == "LongPitch":
return f"{verb} Grade Compensation after the next startup?"
raise ValueError("Unsupported vehicle preference")
+13 -5
View File
@@ -24,6 +24,7 @@ class VehicleStartupPreferences:
tesla_preap_stock: bool = True
volt_sng: bool = False
gm_auto_hold: bool = False
volt_one_pedal: bool = False
@classmethod
def read(cls, params, *, enabled: bool):
@@ -47,6 +48,8 @@ class VehicleStartupPreferences:
readable and safe in (None, b"0"))
gm_auto_hold = bool(enabled and not disable_bolt and read_saved(params, "GMAutoHold", 8) == (b"1", True) and
readable and safe in (None, b"0"))
volt_one_pedal = bool(enabled and not disable_bolt and read_saved(params, "VoltOnePedalMode", 8) == (b"1", True) and
readable and safe in (None, b"0"))
except (OSError, TypeError, ValueError):
return cls(disable_bolt_long=disable_bolt, honda_bosch_a_radar=honda_radar, tesla_preap_stock=preap_stock)
try:
@@ -59,7 +62,7 @@ class VehicleStartupPreferences:
assist = assist_readable and assist_raw in (None, b"1")
except (OSError, TypeError, ValueError):
assist = False
return cls(toyota_auto_hold=toyota, volt_sng=volt_sng, gm_auto_hold=gm_auto_hold,
return cls(toyota_auto_hold=toyota, volt_sng=volt_sng, gm_auto_hold=gm_auto_hold, volt_one_pedal=volt_one_pedal,
turn_assist=bool(enabled and assist and readable and safe in (None, b"0")),
gm_long_pitch=pitch_enabled, disable_bolt_long=disable_bolt, honda_bosch_a_radar=honda_radar, tesla_preap_stock=preap_stock)
@@ -97,35 +100,40 @@ class VehicleStartupPreferences:
cp.dashcamOnly = True
def prepare(self, cp, *, fingerprints=None):
from opendbc.car.gm.values import apply_gm_auto_hold
from opendbc.car.gm.values import apply_gm_auto_hold, apply_volt_one_pedal
from openpilot.starpilot.car.tesla.preap_preferences import prepare_stock
prepare_stock(cp, self.tesla_preap_stock)
self._prepare_honda_radar(cp)
self._prepare_bolt(cp, fingerprints)
prepare_disable_longitudinal(cp, self.disable_bolt_long)
apply_gm_auto_hold(cp, self.gm_auto_hold)
apply_volt_one_pedal(cp, self.volt_one_pedal and not self.disable_bolt_long, self.gm_auto_hold)
if cp.brand == "toyota":
apply_toyota_auto_hold(cp, self.toyota_auto_hold)
return cp
def finalize(self, cp) -> None:
from opendbc.car.gm.values import apply_gm_auto_hold, is_gm_auto_hold
from opendbc.car.gm.values import apply_gm_auto_hold, is_gm_auto_hold, apply_volt_one_pedal, is_volt_one_pedal
admitted_one_pedal = is_volt_one_pedal(cp)
admitted_hold = is_gm_auto_hold(cp)
from openpilot.starpilot.car.tesla.preap_preferences import prepare_stock
prepare_stock(cp, self.tesla_preap_stock)
self._prepare_honda_radar(cp)
self._prepare_bolt(cp)
prepare_disable_longitudinal(cp, self.disable_bolt_long)
apply_gm_auto_hold(cp, self.gm_auto_hold and is_gm_auto_hold(cp))
apply_gm_auto_hold(cp, self.gm_auto_hold and admitted_hold)
apply_volt_one_pedal(cp, self.volt_one_pedal and admitted_one_pedal and not self.disable_bolt_long, self.gm_auto_hold and admitted_hold)
if cp.brand == "toyota":
admitted = bool(cp.flags & ToyotaFlags.AUTO_BRAKE_HOLD)
apply_toyota_auto_hold(cp, self.toyota_auto_hold and admitted)
def configure_controller(self, ci) -> None:
from opendbc.car.gm.values import CAR, is_bolt_euv_longitudinal, is_volt_longitudinal, is_gm_auto_hold
from opendbc.car.gm.values import CAR, is_bolt_euv_longitudinal, is_volt_longitudinal, is_gm_auto_hold, is_volt_one_pedal
cp = ci.CP
if ci.CC is not None and is_volt_longitudinal(cp):
ci.CC.volt_sng = self.volt_sng
if ci.CC is not None and cp.brand == "gm":
ci.CC.gm_auto_hold = self.gm_auto_hold and is_gm_auto_hold(cp)
ci.CC.volt_one_pedal = self.volt_one_pedal and is_volt_one_pedal(cp)
if ci.CC is not None and (is_bolt_euv_longitudinal(cp) or is_volt_longitudinal(cp) or cp.carFingerprint == CAR.CHEVROLET_SUBURBAN):
ci.CC.long_pitch = self.gm_long_pitch