This commit is contained in:
firestar5683
2026-03-10 15:56:04 -05:00
parent 6f65751a4e
commit 831a719d3e
11 changed files with 110 additions and 20 deletions
+1
View File
@@ -478,6 +478,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"RoadName", CLEAR_ON_MANAGER_START},
{"RoadNameUI", PERSISTENT},
{"RedPanda", PERSISTENT},
{"RemapCancelToDistance", PERSISTENT},
{"RemoteStartBootsComma", PERSISTENT},
{"RotatingWheel", PERSISTENT},
{"ScreenBrightness", PERSISTENT},
+4
View File
@@ -239,6 +239,7 @@ frogpilot_default_params: list[tuple[str, str | bytes, int, str]] = [
("FullMap", "0", 2, "0"),
("GasRegenCmd", "1", 2, "0"),
("GMPedalLongitudinal", "1", 2, "1"),
("RemapCancelToDistance", "0", 2, "0"),
("RedPanda", "0", 3, "0"),
("RemoteStartBootsComma", "0", 3, "0"),
("GithubSshKeys", "", 0, ""),
@@ -828,6 +829,9 @@ class FrogPilotVariables:
toggle.red_panda = toggle.car_make == "gm" and (params.get_bool("RedPanda") if tuning_level >= level["RedPanda"] else default.get_bool("RedPanda"))
toggle.remote_start_boots_comma = toggle.car_make == "gm" and (params.get_bool("RemoteStartBootsComma") if tuning_level >= level["RemoteStartBootsComma"] else default.get_bool("RemoteStartBootsComma"))
toggle.remap_cancel_to_distance = toggle.car_make == "gm" and toggle.openpilot_longitudinal and (
params.get_bool("RemapCancelToDistance") if tuning_level >= level["RemapCancelToDistance"] else default.get_bool("RemapCancelToDistance")
)
toggle.force_fingerprint = (params.get_bool("ForceFingerprint") if tuning_level >= level["ForceFingerprint"] else default.get_bool("ForceFingerprint")) and toggle.car_model is not None
+3 -2
View File
@@ -171,6 +171,7 @@ FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(FrogPilotSettingsWindow *parent)
std::vector<std::tuple<QString, QString, QString, QString>> vehicleToggles {
{"GMToggles", tr("General Motors Settings"), tr("<b>FrogPilot features for General Motors vehicles.</b>"), ""},
{"GMPedalLongitudinal", tr("Use Pedal for Longitudinal Control"), tr("<b>Use the pedal interceptor for longitudinal control</b> instead of camera ACC/Redneck when available."), ""},
{"RemapCancelToDistance", tr("Remap cancel to distance"), tr("<b>Treat CANCEL as distance-button input</b> on supported pedal-long GM platforms. Toggle requires a reboot to take effect."), ""},
{"LongPitch", tr("Smooth Pedal Response on Hills"), tr("<b>Smoothen acceleration and braking</b> when driving downhill/uphill."), ""},
{"RedPanda", tr("Red Panda"), tr("<b>Enable Red Panda behavior</b> for GM (alternate safety config and bus numbering). Requires a reboot to take effect."), ""},
{"RemoteStartBootsComma", tr("Remote Start Boots Comma"), tr("<b>Use GM C9 SystemPowerMode</b> for ignition detection. Toggle requires a panda firmware update and a reboot to take effect."), ""},
@@ -287,7 +288,7 @@ FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(FrogPilotSettingsWindow *parent)
static_cast<FrogPilotParamValueControl*>(toggles["LockDoorsTimer"])->setWarning("<b>Warning:</b> openpilot can't detect if keys are still inside the car, so ensure you have a spare key to prevent accidental lockouts!");
QSet<QString> rebootKeys = {"NewLongAPI", "TacoTuneHacks"};
QSet<QString> rebootKeys = {"NewLongAPI", "RemapCancelToDistance", "TacoTuneHacks"};
for (const QString &key : rebootKeys) {
QObject::connect(static_cast<ToggleControl*>(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) {
if (started) {
@@ -430,7 +431,7 @@ void FrogPilotVehiclesPanel::updateToggles() {
setVisible &= isHKGCanFd;
}
else if (key == "GMPedalLongitudinal") {
else if (key == "GMPedalLongitudinal" || key == "RemapCancelToDistance") {
setVisible &= hasPedal;
}
+2 -2
View File
@@ -36,9 +36,9 @@ private:
std::map<QString, AbstractControl*> toggles;
QSet<QString> gmKeys = {"GMPedalLongitudinal", "LongPitch", "RedPanda", "RemoteStartBootsComma", "VoltSNG"};
QSet<QString> gmKeys = {"GMPedalLongitudinal", "LongPitch", "RedPanda", "RemapCancelToDistance", "RemoteStartBootsComma", "VoltSNG"};
QSet<QString> hkgKeys = {"NewLongAPI", "TacoTuneHacks"};
QSet<QString> longitudinalKeys = {"FrogsGoMoosTweak", "LongPitch", "NewLongAPI", "SNGHack", "VoltSNG"};
QSet<QString> longitudinalKeys = {"FrogsGoMoosTweak", "LongPitch", "NewLongAPI", "RemapCancelToDistance", "SNGHack", "VoltSNG"};
QSet<QString> toyotaKeys = {"ClusterOffset", "FrogsGoMoosTweak", "LockDoorsTimer", "SNGHack", "ToyotaDoors"};
QSet<QString> vehicleInfoKeys = {"BlindSpotSupport", "HardwareDetected", "OpenpilotLongitudinal", "PedalSupport", "RadarSupport", "SASCMSupport", "SDSUSupport", "SNGSupport"};
+6 -2
View File
@@ -263,9 +263,13 @@ static void gm_rx_hook(const CANPacket_t *to_push) {
// ACC steering wheel buttons (GM_CAM is tied to the PCM)
if ((addr == 0x1E1) && (!gm_pcm_cruise || gm_cc_long)) {
int button = (GET_BYTE(to_push, 5) & 0x70U) >> 4;
bool remap_cancel_to_distance = (alternative_experience & ALT_EXP_GM_REMAP_CANCEL_TO_DISTANCE) != 0;
// Malibu Hybrid pedal-long observed format uses byte3 bit0 set (typically 0x01/0x41) on ASCMSteeringButton.
// Don't treat that physical CANCEL frame as OP disengage.
bool malibu_cancel_passthrough = gm_bolt_2022_pedal && gm_pedal_long && ((GET_BYTE(to_push, 3) & 0x1U) != 0U);
bool malibu_cancel_passthrough = remap_cancel_to_distance && gm_bolt_2022_pedal && gm_pedal_long && ((GET_BYTE(to_push, 3) & 0x1U) != 0U);
// Gen1 Bolt pedal-long behavior: let wheel CANCEL act as an in-drive aux button (personality cycling),
// so safety should not force a controls drop on this RX cancel edge.
bool bolt_cancel_passthrough = remap_cancel_to_distance && gm_pedal_long && !gm_has_acc && !gm_bolt_2022_pedal;
// enter controls on falling edge of set or rising edge of resume (avoids fault)
bool set = (button != GM_BTN_SET) && (cruise_button_prev == GM_BTN_SET);
@@ -275,7 +279,7 @@ static void gm_rx_hook(const CANPacket_t *to_push) {
}
// exit controls on cancel press
if ((button == GM_BTN_CANCEL) && !malibu_cancel_passthrough) {
if ((button == GM_BTN_CANCEL) && !(malibu_cancel_passthrough || bolt_cancel_passthrough)) {
controls_allowed = false;
}
+3
View File
@@ -275,3 +275,6 @@ const uint32_t RELAY_TRNS_TIMEOUT = 1U;
// Always on Lateral
#define ALT_EXP_ALWAYS_ON_LATERAL 32
// GM pedal-long option: interpret wheel CANCEL as an auxiliary distance-style button.
#define ALT_EXP_GM_REMAP_CANCEL_TO_DISTANCE 64
+1
View File
@@ -112,6 +112,7 @@ class ALTERNATIVE_EXPERIENCE:
RAISE_LONGITUDINAL_LIMITS_TO_ISO_MAX = 8
ALLOW_AEB = 16
ALWAYS_ON_LATERAL = 32
GM_REMAP_CANCEL_TO_DISTANCE = 64
class Panda:
+8 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
import unittest
from panda import Panda
from panda import ALTERNATIVE_EXPERIENCE, Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
@@ -344,6 +344,13 @@ class TestGmInterceptorSafety(common.GasInterceptorSafetyTest, TestGmCameraSafet
self._rx(_acc_pcm_msg(False))
self.assertEqual(enable, self.safety.get_controls_allowed())
def test_cancel_button_passthrough(self):
# Gen1 Bolt-style pedal-long path: wheel cancel shouldn't force controls off.
self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.GM_REMAP_CANCEL_TO_DISTANCE)
self.safety.set_controls_allowed(1)
self._rx(self._button_msg(Buttons.CANCEL))
self.assertTrue(self.safety.get_controls_allowed())
def test_buttons(self):
self._rx(self._interceptor_user_gas(0))
# Only CANCEL button is allowed while cruise is enabled
+3 -2
View File
@@ -56,8 +56,9 @@ class Car:
# set alternative experiences from parameters
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
self.CP.alternativeExperience = 0
if not self.disengage_on_accelerator:
if self.disengage_on_accelerator:
self.CP.alternativeExperience &= ~ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
else:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
+64 -10
View File
@@ -2,7 +2,7 @@
from cereal import car, custom
from math import fabs, exp
import numpy as np
from panda import Panda
from panda import ALTERNATIVE_EXPERIENCE, Panda
from openpilot.common.conversions import Conversions as CV
from openpilot.common.numpy_fast import interp
@@ -48,6 +48,13 @@ BOLT_PEDAL_LONG_CARS = {
CAR.CHEVROLET_MALIBU_HYBRID_CC,
}
# Cancel-to-personality mapping target: gen1 Bolt pedal-long paths only.
BOLT_GEN1_CANCEL_PERSONALITY_CARS = {
CAR.CHEVROLET_BOLT_CC_2017,
CAR.CHEVROLET_BOLT_CC_2019_2021,
}
CANCEL_REMAP_DISTANCE_CARS = BOLT_GEN1_CANCEL_PERSONALITY_CARS | {CAR.CHEVROLET_MALIBU_HYBRID_CC}
NON_LINEAR_TORQUE_PARAMS = {
CAR.CHEVROLET_BOLT_ACC_2022_2023: {
"left": [2.6531724862969748, 1.1, 0.1919764879840985, 0.0],
@@ -602,29 +609,76 @@ class CarInterface(CarInterfaceBase):
if use_panda_paddle_sched:
gm_safety_cfg.safetyParam |= Panda.FLAG_GM_PANDA_PADDLE_SCHED
remap_cancel_to_distance = (
getattr(frogpilot_toggles, "remap_cancel_to_distance", False) and
ret.openpilotLongitudinalControl and
bool(ret.flags & GMFlags.PEDAL_LONG.value) and
candidate in CANCEL_REMAP_DISTANCE_CARS
)
if remap_cancel_to_distance:
ret.alternativeExperience |= ALTERNATIVE_EXPERIENCE.GM_REMAP_CANCEL_TO_DISTANCE
return ret
# returns a car.CarState
def _update(self, c, frogpilot_toggles):
ret, fp_ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback, frogpilot_toggles)
remap_cancel_to_distance = bool(self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.GM_REMAP_CANCEL_TO_DISTANCE)
malibu_cancel_passthrough = (
remap_cancel_to_distance and
self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC and
self.CP.openpilotLongitudinalControl
)
bolt_cancel_personality = (
remap_cancel_to_distance and
self.CP.carFingerprint in BOLT_GEN1_CANCEL_PERSONALITY_CARS and
self.CP.openpilotLongitudinalControl and
bool(self.CP.flags & GMFlags.PEDAL_LONG.value)
)
# Don't add event if transitioning from INIT, unless it's to an actual button
cruise_button_map = BUTTONS_DICT
if self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC and self.CP.openpilotLongitudinalControl:
# Malibu Hybrid only: keep all wheel-button functionality, but don't map CANCEL
# to ButtonType.cancel so OP long isn't disengaged by the physical cancel button.
if malibu_cancel_passthrough:
# Keep pedal-long cancel presses from creating a buttonCancel disengage event.
cruise_button_map = {k: v for k, v in BUTTONS_DICT.items() if k != CruiseButtons.CANCEL}
elif bolt_cancel_personality:
# Keep pedal-long cancel presses from creating a buttonCancel disengage event.
cruise_button_map = {k: v for k, v in BUTTONS_DICT.items() if k != CruiseButtons.CANCEL}
cruise_events = create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, cruise_button_map,
unpressed_btn=CruiseButtons.UNPRESS)
cancel_gap_events = []
if bolt_cancel_personality:
# Gen1 Bolt pedal-long: treat CANCEL as a distance-style button for personality cycling.
cancel_gap_events = create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons,
{CruiseButtons.CANCEL: ButtonType.gapAdjustCruise},
unpressed_btn=CruiseButtons.UNPRESS)
# Malibu pedal-long cancel can alias wheel-button bits on 0x1E1; ignore those side effects.
suppress_malibu_side_buttons = malibu_cancel_passthrough and (
self.CS.cruise_buttons in (CruiseButtons.CANCEL, CruiseButtons.MAIN) or
self.CS.prev_cruise_buttons in (CruiseButtons.CANCEL, CruiseButtons.MAIN)
)
distance_events = [] if suppress_malibu_side_buttons else create_button_events(
self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}
)
lkas_events = [] if suppress_malibu_side_buttons else create_button_events(
self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}
)
if self.CS.cruise_buttons != CruiseButtons.UNPRESS or self.CS.prev_cruise_buttons != CruiseButtons.INIT:
ret.buttonEvents = [
*create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, cruise_button_map,
unpressed_btn=CruiseButtons.UNPRESS),
*create_button_events(self.CS.distance_button, self.CS.prev_distance_button,
{1: ButtonType.gapAdjustCruise}),
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled,
{1: FrogPilotButtonType.lkas}),
*cruise_events,
*cancel_gap_events,
*distance_events,
*lkas_events,
]
if bolt_cancel_personality and self.CS.cruise_buttons == CruiseButtons.CANCEL:
# Feed long-press logic (traffic mode, etc.) as if distance button is being held.
fp_ret.distancePressed = True
# The ECM allows enabling on falling edge of set, but only rising edge of resume
events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low,
GearShifter.eco, GearShifter.manumatic],
+15 -1
View File
@@ -9,6 +9,7 @@ from typing import SupportsFloat
import cereal.messaging as messaging
from cereal import car, custom, log
from panda import ALTERNATIVE_EXPERIENCE
from msgq.visionipc import VisionIpcClient, VisionStreamType
@@ -20,7 +21,7 @@ from openpilot.common.realtime import config_realtime_process, Priority, Ratekee
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.car.car_helpers import get_car_interface, get_startup_event
from openpilot.selfdrive.car.gm.values import CC_ONLY_CAR, GMFlags
from openpilot.selfdrive.car.gm.values import CAR, CC_ONLY_CAR, GMFlags
from openpilot.selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert
from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, clip_curvature
from openpilot.selfdrive.controls.lib.events import Events, ET
@@ -64,6 +65,10 @@ CSID_MAP = {"1": EventName.roadCameraError, "2": EventName.wideRoadCameraError,
ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys())
ACTIVE_STATES = (State.enabled, State.softDisabling, State.overriding)
ENABLED_STATES = (State.preEnabled, *ACTIVE_STATES)
BOLT_GEN1_CANCEL_PERSONALITY_CARS = {
CAR.CHEVROLET_BOLT_CC_2017,
CAR.CHEVROLET_BOLT_CC_2019_2021,
}
class Controls:
@@ -773,10 +778,19 @@ class Controls:
# decrement personality on distance button press
if self.CP.openpilotLongitudinalControl:
distance_pressed = params_memory.get_bool("OnroadDistanceButtonPressed")
bolt_cancel_personality = (
self.CP.carName == "gm" and
bool(self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.GM_REMAP_CANCEL_TO_DISTANCE) and
bool(self.CP.flags & GMFlags.PEDAL_LONG.value) and
self.CP.carFingerprint in BOLT_GEN1_CANCEL_PERSONALITY_CARS
)
if self.frogpilot_toggles.personality_profile_via_distance:
distance_pressed |= any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents)
distance_pressed &= not (self.sm['frogpilotCarState'].distanceLongPressed or self.sm['frogpilotCarState'].distanceVeryLongPressed)
elif bolt_cancel_personality:
distance_pressed |= any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents)
distance_pressed &= not (self.sm['frogpilotCarState'].distanceLongPressed or self.sm['frogpilotCarState'].distanceVeryLongPressed)
if self.frogpilot_toggles.personality_profile_via_distance_long:
distance_pressed |= self.sm['frogpilotCarState'].distanceLongPressed
if self.frogpilot_toggles.personality_profile_via_distance_very_long: