From faf5b533219f33cfb4fc51bd6d5dbae1f226bf11 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 28 Sep 2026 14:57:33 -0500 Subject: [PATCH] Ferd Long --- .../lib/longitudinal_vehicle_tunes.py | 3 ++ .../tests/test_longitudinal_planner.py | 15 ++++++++ .../tests/test_personality_profiles_api.py | 34 +++++++++++++++++++ starpilot/system/the_galaxy/the_galaxy.py | 22 +++++++++++- 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longitudinal_vehicle_tunes.py b/selfdrive/controls/lib/longitudinal_vehicle_tunes.py index 45c014e450..4430ee46e5 100644 --- a/selfdrive/controls/lib/longitudinal_vehicle_tunes.py +++ b/selfdrive/controls/lib/longitudinal_vehicle_tunes.py @@ -57,6 +57,7 @@ FORD_LIGHTNING_FAR_FOLLOW_RELEASE_SLEW_RATE = 1.75 FORD_LIGHTNING_STANDSTILL_GUARD_DISTANCE_MARGIN = 5.0 FORD_LIGHTNING_STANDSTILL_GUARD_MAX_LEAD_SPEED = 0.60 FORD_LIGHTNING_GAP_SETTLE_MAX_EXTRA_GAP = 3.0 +FORD_MACH_E_GAP_SETTLE_MAX_EXTRA_GAP = 2.5 TOYOTA_SIENNA_POST_DEPARTURE_RESTOP_MAX_EGO_SPEED = 2.0 TOYOTA_SIENNA_POST_DEPARTURE_RESTOP_MAX_LEAD_SPEED = 0.45 TOYOTA_SIENNA_POST_DEPARTURE_RESTOP_MAX_LEAD_DELTA = 0.35 @@ -329,6 +330,8 @@ def get_standstill_gap_settle_max_extra_gap(CP): return HONDA_CRV_5G_GAP_SETTLE_MAX_EXTRA_GAP if is_ford_f150_lightning(CP): return FORD_LIGHTNING_GAP_SETTLE_MAX_EXTRA_GAP + if getattr(CP, "brand", "") == "ford" and str(getattr(CP, "carFingerprint", "")) == "FORD_MUSTANG_MACH_E_MK1": + return FORD_MACH_E_GAP_SETTLE_MAX_EXTRA_GAP return 1.5 diff --git a/selfdrive/controls/tests/test_longitudinal_planner.py b/selfdrive/controls/tests/test_longitudinal_planner.py index d6942bc880..1650d2c63e 100644 --- a/selfdrive/controls/tests/test_longitudinal_planner.py +++ b/selfdrive/controls/tests/test_longitudinal_planner.py @@ -858,6 +858,21 @@ def test_lightning_stopped_lead_guard_tune_is_vehicle_specific(): assert get_tracked_lead_catchup_bias_gain(civic) is None +def test_mach_e_standstill_gap_settle_covers_observed_gap_without_changing_other_cars(): + mach_e = FordCarInterface.get_non_essential_params(FORD_CAR.FORD_MUSTANG_MACH_E_MK1) + civic = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) + lead = make_lead(status=True, d_rel=7.7, v_lead=0.0, radar=True, model_prob=1.0, y_rel=0.0) + + assert get_standstill_gap_settle_max_extra_gap(mach_e) == pytest.approx(2.5) + assert get_standstill_gap_settle_max_extra_gap(civic) == pytest.approx(1.5) + assert LongitudinalPlanner.is_radar_standstill_gap_settle_candidate( + lead, 0.0, 5.5, max_extra_gap=get_standstill_gap_settle_max_extra_gap(mach_e)) + assert not LongitudinalPlanner.is_radar_standstill_gap_settle_candidate( + lead, 0.0, 5.5, max_extra_gap=get_standstill_gap_settle_max_extra_gap(civic)) + assert not LongitudinalPlanner.is_radar_standstill_gap_settle_candidate( + lead, 0.5, 5.5, max_extra_gap=get_standstill_gap_settle_max_extra_gap(mach_e)) + + def test_lightning_stopped_radar_lead_handoff_is_narrow_and_vehicle_specific(): lightning = FordCarInterface.get_non_essential_params(FORD_CAR.FORD_F_150_LIGHTNING_MK1) civic = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) diff --git a/starpilot/system/the_galaxy/tests/test_personality_profiles_api.py b/starpilot/system/the_galaxy/tests/test_personality_profiles_api.py index cede605820..e0dd929deb 100644 --- a/starpilot/system/the_galaxy/tests/test_personality_profiles_api.py +++ b/starpilot/system/the_galaxy/tests/test_personality_profiles_api.py @@ -1,5 +1,7 @@ import json import sys +from contextlib import contextmanager +from types import SimpleNamespace import numpy as np import pytest @@ -788,6 +790,38 @@ def test_reset_defaults_requires_confirmed_offroad_without_side_effects(monkeypa assert reboots == [] +def test_reset_defaults_preserves_detected_vehicle_for_vehicle_settings(monkeypatch): + client, params = _client(monkeypatch, { + "CarMake": "Ford", "CarModel": "FORD_MUSTANG_MACH_E_MK1", "CarModelName": "Mustang Mach-E", + "FordHandsFreeCluster": True, + }) + monkeypatch.setattr(params, "all_keys", lambda: ["CarMake", "CarModel", "CarModelName", "FordHandsFreeCluster"], raising=False) + monkeypatch.setattr(params, "get_default_value", lambda key: "mock" if key == "CarMake" else False, raising=False) + monkeypatch.setattr(the_galaxy, "_params_raw", params) + monkeypatch.setattr(the_galaxy, "update_starpilot_toggles", lambda: None) + monkeypatch.setattr(the_galaxy.HARDWARE, "reboot", lambda: None) + + assert client.post("/api/toggles/reset_default").status_code == 200 + assert params.values["CarMake"] == "Ford" + assert params.values["CarModel"] == "FORD_MUSTANG_MACH_E_MK1" + assert params.values["CarModelName"] == "Mustang Mach-E" + assert params.values["FordHandsFreeCluster"] is False + + +def test_vehicle_make_recovers_from_persistent_car_params_after_reset(monkeypatch): + @contextmanager + def ford_params(_): + yield SimpleNamespace(brand="ford", carFingerprint="FORD_MUSTANG_MACH_E_MK1") + + client, params = _client(monkeypatch, {"CarMake": "mock", "CarParamsPersistent": b"saved-ford-params"}) + monkeypatch.setattr(the_galaxy, "_safe_params_get_live_raw", lambda key, default=None, block=False: params.values.get(key, default)) + monkeypatch.setattr(the_galaxy.car.CarParams, "from_bytes", ford_params) + + assert the_galaxy._get_effective_car_make("mock") == "Ford" + assert the_galaxy._get_effective_car_make("Toyota") == "Toyota" + assert client.get("/api/params/all").get_json()["CarMake"] == "Ford" + + @pytest.mark.parametrize("device_state", [ {"IsOnroad": True, "IsOffroad": False}, {"IsOnroad": False, "IsOffroad": False}, diff --git a/starpilot/system/the_galaxy/the_galaxy.py b/starpilot/system/the_galaxy/the_galaxy.py index 9201d2f211..7f433c5980 100644 --- a/starpilot/system/the_galaxy/the_galaxy.py +++ b/starpilot/system/the_galaxy/the_galaxy.py @@ -3762,6 +3762,23 @@ def _get_detected_truck_tuning(): return False +def _get_effective_car_make(stored_make): + if isinstance(stored_make, bytes): + stored_make = stored_make.decode("utf-8", errors="replace") + if str(stored_make or "").strip().lower() not in ("", "mock"): + return stored_make + cp_bytes = _safe_params_get_live_raw("CarParamsPersistent") + if cp_bytes: + try: + with car.CarParams.from_bytes(cp_bytes) as cp: + if cp.brand and cp.brand != "mock": + make_key = str(cp.carFingerprint).split("_", 1)[0] + return next((make for make in FINGERPRINT_MAKE_LABELS if make.lower() == make_key.lower()), cp.brand.title()) + except Exception: + pass + return stored_make + + def _get_effective_legacy_custom_accel_curve( ev_tuning: bool, truck_tuning: bool, *, acceleration_profile=None, custom_enabled: bool | None = None, ) -> list[float]: @@ -6773,6 +6790,8 @@ def setup(app): if request_key == "IsRHD" and not params.get_bool("IsRHDOverride"): return ("1" if params.get_bool("IsRhdDetected") else "0"), 200 value = params.get(request_key) or "" + if request_key == "CarMake": + return _get_effective_car_make(value), 200 if request_key in ("Model", "DrivingModel"): if isinstance(value, bytes): value = value.decode("utf-8", errors="replace") @@ -6813,6 +6832,7 @@ def setup(app): except Exception: result[key] = None + result["CarMake"] = _get_effective_car_make(result.get("CarMake")) result["TeslaCANWakeAvailable"] = supports_tesla_can_wake(params) result["HasRadar"] = _get_has_radar() result["VehicleParked"] = _get_vehicle_parked() @@ -10546,7 +10566,7 @@ def setup(app): for raw_key in _params_raw.all_keys(): key = raw_key.decode() if isinstance(raw_key, bytes) else str(raw_key) - if key in EXCLUDED_KEYS: + if key in EXCLUDED_KEYS or key in ("CarMake", "CarModel", "CarModelName"): continue default_value = _params_raw.get_default_value(raw_key)