This commit is contained in:
firestar5683
2026-03-22 15:09:31 -05:00
parent a35382505e
commit 0e57f230d7
54 changed files with 178 additions and 45 deletions
Binary file not shown.
+1
View File
@@ -38,6 +38,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DoUninstall", {CLEAR_ON_MANAGER_START, BOOL}},
{"DriverTooDistracted", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}},
{"AlphaLongitudinalEnabled", {PERSISTENT, BOOL}},
{"ExperimentalLongitudinalEnabled", {PERSISTENT, BOOL}},
{"ExperimentalMode", {PERSISTENT, BOOL}},
{"ExperimentalModeConfirmed", {PERSISTENT, BOOL}},
{"FirmwareQueryDone", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
Binary file not shown.
+5 -3
View File
@@ -37,13 +37,13 @@ class FrogPilotCard:
def handle_button_event(self, key, sm, frogpilot_toggles):
if sm["carControl"].longActive and getattr(frogpilot_toggles, f"experimental_mode_via_{key}"):
self.handle_experimental_mode(sm, frogpilot_toggles)
elif sm["carControl"].longActive and getattr(frogpilot_toggles, f"force_coast_via_{key}"):
elif getattr(frogpilot_toggles, f"force_coast_via_{key}"):
self.force_coast = not self.force_coast
elif getattr(frogpilot_toggles, f"pause_lateral_via_{key}"):
self.pause_lateral = not self.pause_lateral
elif sm["carControl"].longActive and getattr(frogpilot_toggles, f"pause_longitudinal_via_{key}"):
elif getattr(frogpilot_toggles, f"pause_longitudinal_via_{key}"):
self.pause_longitudinal = not self.pause_longitudinal
elif getattr(frogpilot_toggles, f"traffic_mode_via_{key}"):
elif sm["carControl"].longActive and getattr(frogpilot_toggles, f"traffic_mode_via_{key}"):
self.traffic_mode_enabled = not self.traffic_mode_enabled
def handle_experimental_mode(self, sm, frogpilot_toggles):
@@ -83,6 +83,8 @@ class FrogPilotCard:
if sm.updated["frogpilotPlan"] or any(be.type == ButtonType.decelCruise for be in carState.buttonEvents):
self.decel_pressed = any(be.type == ButtonType.decelCruise for be in carState.buttonEvents)
frogpilotCarState.distancePressed |= self.params_memory.get_bool("OnroadDistanceButtonPressed")
if frogpilotCarState.distancePressed:
self.gap_counter += 1
elif not self.distancePressed_previously:
@@ -1,4 +1,6 @@
#!/usr/bin/env python3
import json
import numpy as np
from openpilot.common.realtime import DT_MDL
@@ -21,7 +23,11 @@ class CurveSpeedController:
self.training_timer = 0
self.curvature_data = self.frogpilot_planner.params.get("CurvatureData")
curvature_data_raw = self.frogpilot_planner.params.get("CurvatureData", encoding="utf-8")
try:
self.curvature_data = json.loads(curvature_data_raw or "{}")
except (TypeError, ValueError):
self.curvature_data = {}
self.calculate_weights()
self.update_lateral_acceleration()
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# PFEIFER - SLC - Modified by FrogAi for FrogPilot
import calendar
import json
import numpy as np
import requests
@@ -36,6 +37,7 @@ OFFSET_MAP_METRIC = [
class SpeedLimitController:
def __init__(self, FrogPilotVCruise):
self.frogpilot_planner = FrogPilotVCruise.frogpilot_planner
self.frogpilot_toggles = None
self.calling_mapbox = False
self.override_slc = False
@@ -53,14 +55,18 @@ class SpeedLimitController:
self.previous_source = "None"
self.source = "None"
self.mapbox_requests = self.frogpilot_planner.params.get("MapBoxRequests")
mapbox_requests_raw = self.frogpilot_planner.params.get("MapBoxRequests", encoding="utf-8")
try:
self.mapbox_requests = json.loads(mapbox_requests_raw or "{}")
except (TypeError, ValueError):
self.mapbox_requests = {}
self.mapbox_requests.setdefault("total_requests", 0)
self.mapbox_requests.setdefault("max_requests", FREE_MAPBOX_REQUESTS - (28 * 100))
self.mapbox_host = "https://api.mapbox.com"
self.mapbox_token = self.frogpilot_planner.params.get("MapboxSecretKey")
self.mapbox_token = self.frogpilot_planner.params.get("MapboxSecretKey", encoding="utf-8")
self.previous_target = self.frogpilot_planner.params.get("PreviousSpeedLimit")
self.previous_target = self.frogpilot_planner.params.get_float("PreviousSpeedLimit")
self.executor = ThreadPoolExecutor(max_workers=1)
@@ -70,10 +76,12 @@ class SpeedLimitController:
@property
def experimental_mode(self):
return self.target == 0 and self.frogpilot_toggles.slc_fallback_experimental_mode
return self.target == 0 and bool(getattr(self.frogpilot_toggles, "slc_fallback_experimental_mode", False))
@property
def offset(self):
if self.frogpilot_toggles is None:
return 0
offset_map = OFFSET_MAP_METRIC if self.frogpilot_toggles.is_metric else OFFSET_MAP_IMPERIAL
return next((getattr(self.frogpilot_toggles, offset) for low, high, offset in offset_map if low < self.target < high), 0)
+1 -1
View File
@@ -56,7 +56,7 @@ class WeatherChecker:
self.last_updated = None
self.requesting = False
self.user_api_key = self.frogpilot_planner.params.get("WeatherToken")
self.user_api_key = self.frogpilot_planner.params.get("WeatherToken", encoding="utf-8")
if self.user_api_key:
self.check_interval = 60
+9 -6
View File
@@ -20,6 +20,13 @@ CAMERA_CANCEL_DELAY_FRAMES = 10
MIN_STEER_MSG_INTERVAL_MS = 15
def get_stock_cc_active_for_cancel(CP, CS):
stock_cc_active = CS.out.cruiseState.enabled or CS.pcm_acc_status != AccState.OFF
if CP.carFingerprint == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL:
return CS.out.cruiseState.enabled
return stock_cc_active
class CarController(CarControllerBase):
def __init__(self, dbc_names, CP):
super().__init__(dbc_names, CP)
@@ -529,16 +536,12 @@ class CarController(CarControllerBase):
can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN)
# - CC_LONG: cancel stock CC if OP is not active
stock_cc_active = CS.out.cruiseState.enabled or CS.pcm_acc_status != AccState.OFF
# Pedal-long cancel should only run while stock cruise is actually enabled.
# Using pcm_acc_status here causes cancel spam even after disable, which can
# intermittently knock CruiseMain off.
stock_cc_active_pedal = CS.out.cruiseState.enabled
stock_cc_active = get_stock_cc_active_for_cancel(self.CP, CS)
pedal_cancel = bool(self.CP.flags & GMFlags.PEDAL_LONG.value)
if self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC:
pedal_cancel = pedal_cancel and CC.longActive
if ((pedal_cancel and stock_cc_active_pedal) or
if ((pedal_cancel and stock_cc_active) or
(((self.CP.flags & GMFlags.CC_LONG.value) and not CC.enabled) and stock_cc_active)):
if self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC:
malibu_cancel_requested = True
+8
View File
@@ -319,9 +319,16 @@ class CarState(CarStateBase):
self.cruise_buttons in (CruiseButtons.CANCEL, CruiseButtons.MAIN) or
prev_cruise_buttons in (CruiseButtons.CANCEL, CruiseButtons.MAIN)
)
suppress_bolt_cancel_lkas = bolt_cancel_personality and (
self.cruise_buttons == CruiseButtons.CANCEL or
prev_cruise_buttons == CruiseButtons.CANCEL
)
distance_events = [] if suppress_malibu_side_buttons else create_button_events(
self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}
)
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}
)
# 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:
@@ -329,6 +336,7 @@ class CarState(CarStateBase):
*cruise_events,
*cancel_gap_events,
*distance_events,
*lkas_events,
]
if ret.vEgo < self.CP.minSteerSpeed:
+16 -15
View File
@@ -116,23 +116,24 @@ def create_adas_keepalive(bus):
def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop, include_always_one3=False):
# Legacy callsites may still pass include_always_one3; this DBC encodes ACC type explicitly instead.
_ = include_always_one3
values = {
"GasRegenCmdActive": enabled,
"RollingCounter": idx,
"GasRegenCmd": throttle,
"GasRegenFullStopActive": at_full_stop,
"GasRegenAccType": 1,
}
# Keep GM camera-long GasRegen bytes aligned with StarPilot's legacy layout.
# The regenerated DBC shape does not pack to the same wire format on Global A.
dat = bytearray(8)
dat[0] = ((idx & 0x3) << 6) | int(enabled)
dat[1] = 0x42 | (0x20 if at_full_stop else 0x00)
dat = packer.make_can_msg("ASCMGasRegenCmd", bus, values)[1]
values["GasRegenChecksum"] = ((1 - int(enabled)) << 24) | \
(((0xff - dat[1]) & 0xff) << 16) | \
(((0xff - dat[2]) & 0xff) << 8) | \
((0x100 - dat[3] - idx) & 0xff)
cmd = int(throttle) << 3
dat[2] = (cmd >> 8) & 0xFF
dat[3] = cmd & 0xFF
if include_always_one3:
dat[2] |= 0x80
return packer.make_can_msg("ASCMGasRegenCmd", bus, values)
dat[4] = 0x00 if enabled else 0x01
dat[5] = (0xFF - dat[1]) & 0xFF
dat[6] = (0xFF - dat[2]) & 0xFF
dat[7] = (0x100 - dat[3] - idx) & 0xFF
return CanData(0x2CB, bytes(dat), bus)
def create_ecm_cruise_control_command(packer, bus, enabled, target_speed_kph):
+4
View File
@@ -527,6 +527,10 @@ class CarInterface(CarInterfaceBase):
else:
ret.longitudinalTuning.kfDEPRECATED = 0.25
if is_bolt_2022_2023_pedal:
# Gen2 Bolt pedal-long should follow the no-ACC panda path like StarPilot.
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_ACC.value
if candidate in (CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, CAR.CHEVROLET_MALIBU_HYBRID_CC):
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_BOLT_2022_PEDAL.value
@@ -10,6 +10,7 @@ from opendbc.car import DT_CTRL, CanData, structs
from opendbc.car.car_helpers import interfaces
from opendbc.car.fingerprints import FW_VERSIONS
from opendbc.car.fw_versions import FW_QUERY_CONFIGS
from opendbc.car.gm.values import CAR as GM_CAR, CanBus as GMCanBus, GMSafetyFlags
from opendbc.car.interfaces import CarInterfaceBase, get_interface_attr
from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.values import PLATFORMS
@@ -57,6 +58,33 @@ def get_fuzzy_car_interface(car_name: str, draw: DrawType) -> CarInterfaceBase:
class TestCarInterfaces:
def test_gm_bolt_gen2_pedal_safety_flags(self):
CarInterface = interfaces[GM_CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL]
pedal_fingerprint = {bus: {} for bus in range(8)}
pedal_fingerprint[GMCanBus.POWERTRAIN][0x201] = 6
pedal_params = CarInterface.get_params(
GM_CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
pedal_fingerprint,
[],
alpha_long=False,
is_release=False,
docs=False,
)
assert pedal_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_ACC.value
assert pedal_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_BOLT_2022_PEDAL.value
acc_fingerprint = {bus: {} for bus in range(8)}
acc_params = CarInterface.get_params(
GM_CAR.CHEVROLET_BOLT_ACC_2022_2023,
acc_fingerprint,
[],
alpha_long=False,
is_release=False,
docs=False,
)
assert not (acc_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_ACC.value)
# FIXME: Due to the lists used in carParams, Phase.target is very slow and will cause
# many generated examples to overrun when max_examples > ~20, don't use it
@pytest.mark.parametrize("car_name", sorted(PLATFORMS))
+31 -6
View File
@@ -51,6 +51,7 @@ typedef enum {
GM_CAM
} GmHardware;
static GmHardware gm_hw = GM_ASCM;
static bool gm_cam_long = false;
static bool gm_pcm_cruise = false;
static bool gm_sdgm = false;
static bool gm_ascm_int = false;
@@ -178,10 +179,6 @@ static void gm_try_send_3d1_spoof(uint32_t now_us) {
return;
}
if (!gm_3d1_scheduler_ready(now_us)) {
return;
}
if ((int32_t)(now_us - gm_3d1_next_tx_us) < 0) {
return;
}
@@ -509,6 +506,33 @@ static bool gm_tx_hook(const CANPacket_t *msg) {
return tx;
}
static bool gm_fwd_hook(int bus_num, int addr) {
bool block_msg = false;
if ((gm_hw == GM_CAM) || gm_sdgm) {
if (bus_num == 0) {
bool is_pscm_msg = addr == 0x184U;
// Keep the camera side sourced only from OP's spoofed cruise status on non-ACC paths.
bool is_ecm_cruise_status_msg = (addr == 0x3D1U) && !gm_has_acc;
block_msg = is_pscm_msg || is_ecm_cruise_status_msg;
} else if (bus_num == 2) {
bool is_lkas_msg = addr == 0x180U;
bool is_acc_status_msg = addr == 0x370U;
bool is_acc_actuation_msg = (addr == 0x315U) || (addr == 0x2CBU);
block_msg = is_lkas_msg;
if (gm_cam_long || gm_pedal_long) {
block_msg |= is_acc_status_msg;
}
if (gm_cam_long) {
block_msg |= is_acc_actuation_msg;
}
}
}
return block_msg;
}
static safety_config gm_init(uint16_t param) {
const uint16_t GM_PARAM_HW_CAM = 1;
const uint16_t GM_PARAM_HW_CAM_LONG = 2;
@@ -663,7 +687,7 @@ static safety_config gm_init(uint16_t param) {
gm_prndl2_state.phase_locked = false;
gm_zero_u8(gm_prndl2_state.spoof_data, 8U);
bool gm_cam_long = GET_FLAG(param, GM_PARAM_HW_CAM_LONG);
gm_cam_long = GET_FLAG(param, GM_PARAM_HW_CAM_LONG) && !gm_cc_long;
gm_pcm_cruise = (gm_hw == GM_CAM || gm_sdgm) && !gm_cam_long && !gm_force_ascm && !gm_pedal_long;
gm_steer_limits = GET_FLAG(param, GM_PARAM_BOLT_2017) ? &GM_BOLT_2017_STEERING_LIMITS : &GM_STEERING_LIMITS;
@@ -707,7 +731,7 @@ static safety_config gm_init(uint16_t param) {
}
// ASCM does not forward any messages
if (gm_hw == GM_ASCM || gm_cc_long) {
if (gm_hw == GM_ASCM) {
ret.disable_forwarding = true;
}
return ret;
@@ -717,4 +741,5 @@ const safety_hooks gm_hooks = {
.init = gm_init,
.rx = gm_rx_hook,
.tx = gm_tx_hook,
.fwd = gm_fwd_hook,
};
+2 -7
View File
@@ -246,6 +246,7 @@ def interceptor_msg(gas, addr):
class TestGmInterceptorSafety(common.GasInterceptorSafetyTest, TestGmCameraSafety, TestGmEVSafetyBase):
INTERCEPTOR_THRESHOLD = 550
FWD_BLACKLISTED_ADDRS = {2: [0x180, 0x370], 0: [0x184, 0x3D1]}
def setUp(self):
self.packer = CANPackerPanda("gm_global_a_powertrain_generated")
@@ -292,9 +293,6 @@ class TestGmInterceptorSafety(common.GasInterceptorSafetyTest, TestGmCameraSafet
self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL)))
self.assertTrue(self.safety.get_controls_allowed())
def test_fwd_hook(self):
pass
def test_disable_control_allowed_from_cruise(self):
pass
@@ -314,7 +312,7 @@ class TestGmInterceptorSafety(common.GasInterceptorSafetyTest, TestGmCameraSafet
class TestGmCcLongitudinalSafety(TestGmCameraSafety):
TX_MSGS = [[0x180, 0], [0x370, 0], [0x1E1, 0], [0x3D1, 0], [0xBD, 0], [0x1F5, 0], [0x184, 2], [0x1E1, 2]]
FWD_BLACKLISTED_ADDRS = {2: [0x180], 0: [0x184]} # block LKAS message and PSCMStatus
FWD_BLACKLISTED_ADDRS = {2: [0x180], 0: [0x184, 0x3D1]} # block LKAS, PSCMStatus, and stock cruise status
BUTTONS_BUS = 0 # tx only
def setUp(self):
@@ -328,9 +326,6 @@ class TestGmCcLongitudinalSafety(TestGmCameraSafety):
values = {"CruiseActive": enable}
return self.packer.make_can_msg_panda("ECMCruiseControl", 0, values)
def test_fwd_hook(self):
pass
def test_buttons(self):
self.safety.set_controls_allowed(0)
for btn in range(8):
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,2 +1,2 @@
extern const uint8_t gitversion[19];
const uint8_t gitversion[19] = "DEV-9ec91aa7-DEBUG";
const uint8_t gitversion[19] = "DEV-60a7fb11-DEBUG";
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
DEV-9ec91aa7-DEBUG
DEV-60a7fb11-DEBUG
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+18
View File
@@ -250,6 +250,21 @@ def migrate_param_type_canonicalization(params: Params) -> None:
cloudlog.exception(f"Failed to write migration flag: {STARPILOT_PARAM_CANONICALIZATION_MIGRATION_FLAG}")
def migrate_legacy_experimental_longitudinal(params: Params, params_cache: Params) -> None:
legacy_value = params.get("ExperimentalLongitudinalEnabled")
if legacy_value is None:
return
if params.get("AlphaLongitudinalEnabled") is None:
alpha_long_enabled = params.get_bool("ExperimentalLongitudinalEnabled")
params.put_bool("AlphaLongitudinalEnabled", alpha_long_enabled)
params_cache.put_bool("AlphaLongitudinalEnabled", alpha_long_enabled)
cloudlog.warning("Migrated legacy ExperimentalLongitudinalEnabled to AlphaLongitudinalEnabled")
params.remove("ExperimentalLongitudinalEnabled")
params_cache.remove("ExperimentalLongitudinalEnabled")
def manager_init() -> None:
save_bootlog()
@@ -272,6 +287,9 @@ def manager_init() -> None:
cache_params_path = os.path.join(Paths.comma_home(), "cache", "params")
params_cache = Params(cache_params_path, return_defaults=True)
# Preserve StarPilot's legacy longitudinal toggle when switching branches.
migrate_legacy_experimental_longitudinal(params, params_cache)
# Canonicalize legacy string encodings (e.g. INT params stored as "26.000000")
# before bulk reads below to avoid repeated cast warnings and UI-side churn.
migrate_param_type_canonicalization(params)
+34
View File
@@ -51,6 +51,40 @@ class TestManager:
assert params.get("OpenpilotEnabledToggle")
assert params.get("RouteCount") == 0
def test_migrate_legacy_experimental_longitudinal(self):
class FakeParams:
def __init__(self, values):
self.values = dict(values)
def get(self, key):
return self.values.get(key)
def get_bool(self, key):
value = self.values.get(key)
if value is None:
return False
if isinstance(value, bytes):
value = value.decode("utf-8", errors="ignore")
if isinstance(value, str):
return value.strip().lower() in ("1", "true", "yes", "on")
return bool(value)
def put_bool(self, key, value):
self.values[key] = b"1" if value else b"0"
def remove(self, key):
self.values.pop(key, None)
params = FakeParams({"ExperimentalLongitudinalEnabled": b"1"})
params_cache = FakeParams({})
manager.migrate_legacy_experimental_longitudinal(params, params_cache)
assert params.get_bool("AlphaLongitudinalEnabled")
assert params_cache.get_bool("AlphaLongitudinalEnabled")
assert params.get("ExperimentalLongitudinalEnabled") is None
assert params_cache.get("ExperimentalLongitudinalEnabled") is None
@pytest.mark.skip("this test is flaky the way it's currently written, should be moved to test_onroad")
def test_clean_exit(self, subtests):
"""