mochi mochi mochi

This commit is contained in:
firestar5683
2026-06-06 17:49:41 -05:00
parent e47fbd1cb9
commit 26e7b25147
12 changed files with 192 additions and 15 deletions
+1
View File
@@ -160,6 +160,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"AggressiveJerkSpeed", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
{"AggressiveJerkSpeedDecrease", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
{"AlertVolumeControl", {PERSISTENT, BOOL, "0", "0", 2}},
{"AllowImpossibleAcceleration", {PERSISTENT, BOOL, "0", "0", 3}},
{"AlwaysOnLateral", {PERSISTENT, BOOL, "1", "0", 0}},
{"AlwaysOnLateralLKAS", {PERSISTENT, BOOL, "1", "0", 2}},
{"ApiCache_DriveStats", {PERSISTENT, JSON, "{}", "{}"}},
+2
View File
@@ -285,6 +285,8 @@ struct CarState {
setCruise @9;
resumeCruise @10;
gapAdjustCruise @11;
accelHardCruise @12;
decelHardCruise @13;
}
}
+29 -4
View File
@@ -33,6 +33,8 @@ AUTO_HOLD_REGEN_RELEASE_COOLDOWN_S = 1.0
BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise,
CruiseButtons.MAIN: ButtonType.mainCruise, CruiseButtons.CANCEL: ButtonType.cancel}
HARD_BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelHardCruise, CruiseButtons.DECEL_SET: ButtonType.decelHardCruise}
NORMAL_CRUISE_BUTTONS = (CruiseButtons.RES_ACCEL, CruiseButtons.DECEL_SET)
GearShifter = structs.CarState.GearShifter
BOLT_GEN1_CANCEL_PERSONALITY_CARS = {
@@ -64,6 +66,8 @@ class CarState(CarStateBase):
self.prev_distance_button = 0
self.distance_button = 0
self.hard_cruise_buttons = CruiseButtons.INIT
self.force_reset_cruise_buttons = False
self.single_pedal_mode = False
self.auto_hold_armed = False
@@ -92,8 +96,8 @@ class CarState(CarStateBase):
if not self.CP.pcmCruise:
for b in buttonEvents:
# The ECM allows enabling on falling edge of set, but only rising edge of resume
if (b.type == ButtonType.accelCruise and b.pressed) or \
(b.type == ButtonType.decelCruise and not b.pressed):
if (b.type in (ButtonType.accelCruise, ButtonType.accelHardCruise) and b.pressed) or \
(b.type in (ButtonType.decelCruise, ButtonType.decelHardCruise) and not b.pressed):
return True
return False
@@ -119,9 +123,11 @@ class CarState(CarStateBase):
sdgm_non_volt = self.CP.carFingerprint in SDGM_CAR and self.CP.carFingerprint not in kaofui_state_cars
prev_cruise_buttons = self.cruise_buttons
prev_hard_cruise_buttons = self.hard_cruise_buttons
prev_distance_button = self.distance_button
if not sdgm_non_volt:
self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"]
self.hard_cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtonsHard"]
self.distance_button = pt_cp.vl["ASCMSteeringButton"]["DistanceButton"]
self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"]
self.steering_button_checksum = pt_cp.vl["ASCMSteeringButton"]["SteeringButtonChecksum"]
@@ -131,9 +137,20 @@ class CarState(CarStateBase):
self.steering_button_prefix = (int(acc_always_one) & 1) | ((int(acc_hidden_bit) & 1) << 6)
else:
self.cruise_buttons = cam_cp.vl["ASCMSteeringButton"]["ACCButtons"]
self.hard_cruise_buttons = cam_cp.vl["ASCMSteeringButton"]["ACCButtonsHard"]
self.distance_button = cam_cp.vl["ASCMSteeringButton"]["DistanceButton"]
self.buttons_counter = cam_cp.vl["ASCMSteeringButton"]["RollingCounter"]
self.steering_button_ts_nanos = cam_cp.ts_nanos["ASCMSteeringButton"]["ACCButtons"]
# A GM hard press keeps the normal cruise button signal active too. Suppress
# the normal button until the wheel reports a different normal state.
if self.hard_cruise_buttons != CruiseButtons.INIT and self.cruise_buttons in NORMAL_CRUISE_BUTTONS:
self.force_reset_cruise_buttons = True
if self.force_reset_cruise_buttons and self.cruise_buttons in NORMAL_CRUISE_BUTTONS:
self.cruise_buttons = CruiseButtons.UNPRESS
elif self.force_reset_cruise_buttons and self.cruise_buttons not in NORMAL_CRUISE_BUTTONS:
self.force_reset_cruise_buttons = False
self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"])
self.moving_backward = (pt_cp.vl["EBCMWheelSpdRear"]["RLWheelDir"] == 2) or (pt_cp.vl["EBCMWheelSpdRear"]["RRWheelDir"] == 2)
@@ -178,7 +195,10 @@ class CarState(CarStateBase):
ret.brakePressed = ret.brake >= VOLT_EBCM_BRAKE_PRESSED_THRESHOLD
elif self.CP.carFingerprint in {CAR.CHEVROLET_MALIBU_CC} or (self.CP.carFingerprint == CAR.CHEVROLET_BLAZER and not no_accel_pos):
ret.brakePressed = ret.brake >= 8
elif (self.CP.flags & GMFlags.FORCE_BRAKE_C9.value) or ((self.CP.networkLocation == NetworkLocation.fwdCamera) and (self.CP.carFingerprint != CAR.CHEVROLET_BLAZER)):
elif (self.CP.flags & GMFlags.FORCE_BRAKE_C9.value) or (
self.CP.networkLocation == NetworkLocation.fwdCamera and
self.CP.carFingerprint not in (SDGM_CAR | ASCM_INT | {CAR.CHEVROLET_BLAZER})
):
ret.brakePressed = pt_cp.vl["ECMEngineStatus"]["BrakePressed"] != 0
else:
# Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe
@@ -374,13 +394,18 @@ class CarState(CarStateBase):
lkas_events = [] if (suppress_malibu_side_buttons or suppress_bolt_cancel_lkas) else create_button_events(
self.lkas_enabled, self.lkas_previously_enabled, {1: ButtonType.lkas}
)
hard_cruise_events = create_button_events(
self.hard_cruise_buttons, prev_hard_cruise_buttons, HARD_BUTTONS_DICT, unpressed_btn=CruiseButtons.INIT
)
# Don't add events if transitioning from INIT, unless it's to an actual button.
if self.cruise_buttons != CruiseButtons.UNPRESS or prev_cruise_buttons != CruiseButtons.INIT:
if (self.cruise_buttons != CruiseButtons.UNPRESS or prev_cruise_buttons != CruiseButtons.INIT or
self.hard_cruise_buttons != CruiseButtons.INIT or prev_hard_cruise_buttons != CruiseButtons.INIT):
ret.buttonEvents = [
*cruise_events,
*distance_events,
*lkas_events,
*hard_cruise_events,
]
if ret.vEgo < self.CP.minSteerSpeed:
@@ -146,6 +146,7 @@ BO_ 481 ASCMSteeringButton: 7 K124_ASCM
SG_ LKAButton : 23|1@0+ (1,0) [0|0] "" NEO
SG_ ACCAlwaysOne : 24|1@0+ (1,0) [0|1] "" XXX
SG_ ACCButtons : 46|3@0+ (1,0) [0|0] "" NEO
SG_ ACCButtonsHard : 26|2@0+ (1,0) [0|3] "" XXX
SG_ DriveModeButton : 39|1@0+ (1,0) [0|1] "" XXX
SG_ RollingCounter : 33|2@0+ (1,0) [0|3] "" NEO
SG_ SteeringButtonChecksum : 43|12@0+ (1,0) [0|255] "" NEO
@@ -166,6 +166,7 @@ BO_ 481 ASCMSteeringButton: 7 K124_ASCM
SG_ LKAButton : 23|1@0+ (1,0) [0|0] "" NEO
SG_ ACCAlwaysOne : 24|1@0+ (1,0) [0|1] "" XXX
SG_ ACCButtons : 46|3@0+ (1,0) [0|0] "" NEO
SG_ ACCButtonsHard : 26|2@0+ (1,0) [0|3] "" XXX
SG_ DriveModeButton : 39|1@0+ (1,0) [0|1] "" XXX
SG_ RollingCounter : 33|2@0+ (1,0) [0|3] "" NEO
SG_ SteeringButtonChecksum : 43|12@0+ (1,0) [0|255] "" NEO
+2 -2
View File
@@ -283,9 +283,9 @@ class Car:
CS.vCruise = float(self.v_cruise_helper.v_cruise_kph)
CS.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph)
if any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in filtered_CS.buttonEvents):
if any(be.type in (ButtonType.accelCruise, ButtonType.accelHardCruise, ButtonType.resumeCruise) for be in filtered_CS.buttonEvents):
self.resume_prev_button = True
elif any(be.type in (ButtonType.decelCruise, ButtonType.setCruise) for be in filtered_CS.buttonEvents):
elif any(be.type in (ButtonType.decelCruise, ButtonType.decelHardCruise, ButtonType.setCruise) for be in filtered_CS.buttonEvents):
self.resume_prev_button = False
FPCS = self.starpilot_card.update(CS, FPCS, self.sm, self.starpilot_toggles)
+16 -5
View File
@@ -24,11 +24,17 @@ CRUISE_LONG_PRESS = 50
CRUISE_NEAREST_FUNC = {
ButtonType.accelCruise: math.ceil,
ButtonType.decelCruise: math.floor,
ButtonType.accelHardCruise: math.ceil,
ButtonType.decelHardCruise: math.floor,
}
CRUISE_INTERVAL_SIGN = {
ButtonType.accelCruise: +1,
ButtonType.decelCruise: -1,
ButtonType.accelHardCruise: +1,
ButtonType.decelHardCruise: -1,
}
HARD_CRUISE_BUTTONS = (ButtonType.accelHardCruise, ButtonType.decelHardCruise)
ACCEL_CRUISE_BUTTONS = (ButtonType.accelCruise, ButtonType.accelHardCruise)
class VCruiseHelper:
@@ -37,7 +43,12 @@ class VCruiseHelper:
self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET
self.v_cruise_kph_last = 0
self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0}
self.button_timers = {
ButtonType.decelCruise: 0,
ButtonType.accelCruise: 0,
ButtonType.decelHardCruise: 0,
ButtonType.accelHardCruise: 0,
}
self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers}
self.gm_cc_only = self.CP.carFingerprint in CC_ONLY_CAR and self.CP.flags & GMFlags.CC_LONG.value
@@ -121,7 +132,7 @@ class VCruiseHelper:
# Don't adjust speed when pressing resume to exit standstill
cruise_standstill = self.button_change_states[button_type]["standstill"] or CS.cruiseState.standstill
if button_type == ButtonType.accelCruise and cruise_standstill:
if button_type in ACCEL_CRUISE_BUTTONS and cruise_standstill:
return
# Don't adjust speed if we've enabled since the button was depressed (some ports enable on rising edge)
@@ -129,7 +140,7 @@ class VCruiseHelper:
return
short_interval, long_interval = self._get_cruise_delta_intervals(starpilot_toggles)
v_cruise_delta_interval = long_interval if long_press else short_interval
v_cruise_delta_interval = long_interval if long_press or button_type in HARD_CRUISE_BUTTONS else short_interval
v_cruise_delta = v_cruise_delta * v_cruise_delta_interval
if v_cruise_delta_interval % 5 == 0 and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
@@ -137,7 +148,7 @@ class VCruiseHelper:
self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
# If set is pressed while overriding, clip cruise speed to minimum of vEgo
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.decelHardCruise, ButtonType.setCruise):
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
@@ -161,7 +172,7 @@ class VCruiseHelper:
return
engage_floor_kph = max(V_CRUISE_MIN, 7.0 * CV.MPH_TO_KPH)
resume_pressed = any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents)
resume_pressed = any(b.type in (ButtonType.accelCruise, ButtonType.accelHardCruise, ButtonType.resumeCruise) for b in CS.buttonEvents)
remembered_resume = resume_prev_button and (self.gm_cc_only or self.redneck_non_pcm)
if self.v_cruise_initialized and (resume_pressed or remembered_resume):
+57
View File
@@ -1,5 +1,6 @@
import pytest
import itertools
import math
import numpy as np
from parameterized import parameterized_class
@@ -101,6 +102,62 @@ class TestVCruiseHelper:
)
assert pressed == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
def test_hard_press_uses_long_press_interval(self):
self.enable(52 * CV.MPH_TO_MS, False)
initial_v_cruise_kph = self.v_cruise_helper.v_cruise_kph
pressed_cs = car.CarState(cruiseState={"available": True})
pressed_cs.buttonEvents = [ButtonEvent(type=ButtonType.accelHardCruise, pressed=True)]
self.v_cruise_helper.update_v_cruise(
pressed_cs,
enabled=True,
is_metric=False,
speed_limit_changed=False,
starpilot_toggles=self.starpilot_toggles,
)
released_cs = car.CarState(cruiseState={"available": True})
released_cs.buttonEvents = [ButtonEvent(type=ButtonType.accelHardCruise, pressed=False)]
self.v_cruise_helper.update_v_cruise(
released_cs,
enabled=True,
is_metric=False,
speed_limit_changed=False,
starpilot_toggles=self.starpilot_toggles,
)
hard_interval = self.starpilot_toggles.cruise_increase_long * IMPERIAL_INCREMENT
expected_kph = math.ceil(initial_v_cruise_kph / hard_interval) * hard_interval
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(expected_kph)
def test_hard_decel_press_uses_long_press_interval(self):
self.enable(52 * CV.MPH_TO_MS, False)
initial_v_cruise_kph = self.v_cruise_helper.v_cruise_kph
pressed_cs = car.CarState(cruiseState={"available": True})
pressed_cs.buttonEvents = [ButtonEvent(type=ButtonType.decelHardCruise, pressed=True)]
self.v_cruise_helper.update_v_cruise(
pressed_cs,
enabled=True,
is_metric=False,
speed_limit_changed=False,
starpilot_toggles=self.starpilot_toggles,
)
released_cs = car.CarState(cruiseState={"available": True})
released_cs.buttonEvents = [ButtonEvent(type=ButtonType.decelHardCruise, pressed=False)]
self.v_cruise_helper.update_v_cruise(
released_cs,
enabled=True,
is_metric=False,
speed_limit_changed=False,
starpilot_toggles=self.starpilot_toggles,
)
hard_interval = self.starpilot_toggles.cruise_increase_long * IMPERIAL_INCREMENT
expected_kph = math.floor(initial_v_cruise_kph / hard_interval) * hard_interval
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(expected_kph)
def test_rising_edge_enable(self):
"""
Some car interfaces may enable on rising edge of a button,
+6 -2
View File
@@ -22,11 +22,15 @@ class ExcessiveActuationCheck:
self._excessive_counter = 0
self._engaged_counter = 0
def update(self, sm: messaging.SubMaster, CS: car.CarState, calibrated_pose: Pose) -> ExcessiveActuationType | None:
def update(self, sm: messaging.SubMaster, CS: car.CarState, calibrated_pose: Pose,
allow_impossible_acceleration: bool = False) -> ExcessiveActuationType | None:
# CS.aEgo can be noisy to bumps in the road, transitioning from standstill, losing traction, etc.
# longitudinal
accel_calibrated = calibrated_pose.acceleration.x
excessive_long_actuation = sm['carControl'].longActive and (accel_calibrated > ACCEL_MAX * 2 or accel_calibrated < ACCEL_MIN * 2)
excessive_long_actuation = (
not allow_impossible_acceleration and sm['carControl'].longActive and
(accel_calibrated > ACCEL_MAX * 2 or accel_calibrated < ACCEL_MIN * 2)
)
# lateral
yaw_rate = calibrated_pose.angular_velocity.yaw
+34 -2
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
import json
import os
import time
import threading
@@ -71,6 +72,9 @@ class SelfdriveD:
self.pose_calibrator = PoseCalibrator()
self.calibrated_pose: Pose | None = None
self.excessive_actuation_check = ExcessiveActuationCheck()
self.allow_impossible_acceleration = self.params.get_bool("AllowImpossibleAcceleration")
if self.allow_impossible_acceleration:
self.clear_longitudinal_excessive_actuation_alert()
self.excessive_actuation = self.params.get("Offroad_ExcessiveActuation") is not None
# Setup sockets
@@ -172,6 +176,32 @@ class SelfdriveD:
self.FPCP = messaging.log_from_bytes(self.params.get("StarPilotCarParams", block=True), custom.StarPilotCarParams)
def clear_longitudinal_excessive_actuation_alert(self):
alert = self.params.get("Offroad_ExcessiveActuation")
if not alert:
return
if isinstance(alert, bytes):
try:
alert = json.loads(alert.decode("utf-8", errors="replace"))
except json.JSONDecodeError:
return
elif isinstance(alert, str):
try:
alert = json.loads(alert)
except json.JSONDecodeError:
return
if not isinstance(alert, dict):
return
extra = alert.get("extra", "")
if isinstance(extra, bytes):
extra = extra.decode("utf-8", errors="replace")
if str(extra).strip().lower() == "longitudinal":
self.params.remove("Offroad_ExcessiveActuation")
def update_events(self, CS):
"""Compute onroadEvents from carState"""
@@ -221,7 +251,7 @@ class SelfdriveD:
return
# Block resume if cruise never previously enabled
resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in CS.buttonEvents)
resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.accelHardCruise, ButtonType.resumeCruise) for be in CS.buttonEvents)
if not self.CP.pcmCruise and CS.vCruise > 250 and resume_pressed:
self.events.add(EventName.resumeBlocked)
@@ -332,7 +362,9 @@ class SelfdriveD:
self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_pose)
if self.calibrated_pose is not None:
excessive_actuation = self.excessive_actuation_check.update(self.sm, CS, self.calibrated_pose)
excessive_actuation = self.excessive_actuation_check.update(
self.sm, CS, self.calibrated_pose, self.allow_impossible_acceleration
)
if not self.excessive_actuation and excessive_actuation is not None:
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text=str(excessive_actuation))
self.excessive_actuation = True
@@ -3219,6 +3219,13 @@
"description": "Only enable this if the wide camera is broken or for development!\n\nDisabling the wide camera may degrade driving performance and cause instability.\n\nRequires a reboot to take effect.",
"data_type": "bool",
"ui_type": "toggle"
},
{
"key": "AllowImpossibleAcceleration",
"label": "Allow Impossible Acceleration",
"description": "WARNING: This suppresses openpilot's excessive longitudinal actuation diagnostic when measured acceleration looks impossible for the requested gas/brake command.\n\nLeave this OFF unless you are intentionally testing edge cases like shifting to neutral and understand that it can hide a real actuation problem.",
"data_type": "bool",
"ui_type": "toggle"
}
]
}
+36
View File
@@ -2255,6 +2255,30 @@ def _serialize_param_write_value(raw_value):
return raw_value.decode("utf-8", errors="replace")
return str(raw_value or "")
def _offroad_excessive_actuation_type():
alert = _safe_params_get_live_raw("Offroad_ExcessiveActuation")
if not alert:
return ""
if isinstance(alert, bytes):
try:
alert = json.loads(alert.decode("utf-8", errors="replace"))
except json.JSONDecodeError:
return ""
elif isinstance(alert, str):
try:
alert = json.loads(alert)
except json.JSONDecodeError:
return ""
if not isinstance(alert, dict):
return ""
extra = alert.get("extra", "")
if isinstance(extra, bytes):
extra = extra.decode("utf-8", errors="replace")
return str(extra).strip().lower()
def _apply_cellular_metered_setting(metered_enabled):
"""Apply GsmMetered changes to active NetworkManager GSM profiles."""
if not shutil.which("nmcli"):
@@ -3742,6 +3766,18 @@ def setup(app):
if key == "AutomaticUpdates" and params.get_bool("IsOnroad"):
return jsonify({"error": "Cannot change Automatic Updates while driving."}), 403
if key == "AllowImpossibleAcceleration":
enabled = str_val.strip() in ("1", "true", "True")
params.put_bool(key, enabled)
if enabled and _offroad_excessive_actuation_type() == "longitudinal":
params.remove("Offroad_ExcessiveActuation")
update_starpilot_toggles()
return jsonify({
"message": f"Parameter '{key}' updated successfully.",
"updated": {key: enabled},
}), 200
if key in {"EVTuning", "TruckTuning"}:
enabled = str_val.strip() in ("1", "true", "True")
params.put_bool(key, enabled)