diff --git a/opendbc_repo/opendbc/car/car_helpers.py b/opendbc_repo/opendbc/car/car_helpers.py index 91041e1c..69929f43 100644 --- a/opendbc_repo/opendbc/car/car_helpers.py +++ b/opendbc_repo/opendbc/car/car_helpers.py @@ -52,10 +52,16 @@ LEGACY_FORCED_CANDIDATE_MAP = { "CHEVROLET_BOLT_CC_2019_2021": "CHEVROLET_BOLT_CC_2018_2021", } +GM_CANDIDATE_PREFIXES = ("CHEVROLET_", "GMC_", "CADILLAC_", "BUICK_", "HOLDEN_") +GM_CORE_FINGERPRINT_MSGS = frozenset((190, 201, 209, 211, 241)) + def _normalize_forced_candidate(candidate: str | None) -> str | None: if candidate is None: return None + if isinstance(candidate, (bytes, bytearray)): + candidate = candidate.decode("utf-8", errors="ignore") + candidate = str(candidate) return LEGACY_FORCED_CANDIDATE_MAP.get(candidate, candidate) @@ -102,6 +108,25 @@ def _normalize_gm_bolt_candidate(candidate: str | None, fingerprints: dict[int, return candidate +def _is_gm_candidate(candidate: str | None) -> bool: + return isinstance(candidate, str) and candidate.startswith(GM_CANDIDATE_PREFIXES) + + +def _get_gm_stored_candidate_fallback(fingerprints: dict[int, dict], stored_candidate: str | None, + cached_candidate: str | None) -> str | None: + pt = fingerprints.get(0, {}) + # Several GM variants intentionally share FPv1 signatures. If live matching + # can't resolve them, reuse the last known GM platform instead of dropping to MOCK. + if len(GM_CORE_FINGERPRINT_MSGS.intersection(pt.keys())) < 4: + return None + + for candidate in (_normalize_forced_candidate(stored_candidate), _normalize_forced_candidate(cached_candidate)): + if _is_gm_candidate(candidate) and candidate != MOCK.MOCK and candidate in interfaces: + return candidate + + return None + + def can_fingerprint(can_recv: CanRecvCallable) -> tuple[str | None, dict[int, dict]]: finger = gen_empty_fingerprint() candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1 @@ -217,6 +242,19 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip candidate = _normalize_gm_bolt_candidate(candidate, fingerprints) candidate = _normalize_forced_candidate(candidate) fingerprinted_candidate = candidate + stored_candidate = _normalize_forced_candidate(params.get("CarModel")) + cached_candidate = _normalize_forced_candidate(getattr(cached_params, "carFingerprint", None)) + + if candidate is None: + gm_fallback_candidate = _get_gm_stored_candidate_fallback(fingerprints, stored_candidate, cached_candidate) + if gm_fallback_candidate is not None: + carlog.error({ + "event": "using stored GM candidate after ambiguous live fingerprint", + "candidate": gm_fallback_candidate, + "stored_candidate": stored_candidate, + "cached_candidate": cached_candidate, + }) + candidate = gm_fallback_candidate if candidate is None or starpilot_toggles.force_fingerprint: if starpilot_toggles.force_fingerprint: diff --git a/opendbc_repo/opendbc/car/tests/test_can_fingerprint.py b/opendbc_repo/opendbc/car/tests/test_can_fingerprint.py index 006e6bee..1a129443 100644 --- a/opendbc_repo/opendbc/car/tests/test_can_fingerprint.py +++ b/opendbc_repo/opendbc/car/tests/test_can_fingerprint.py @@ -1,6 +1,6 @@ import pytest from opendbc.car.can_definitions import CanData -from opendbc.car.car_helpers import FRAME_FINGERPRINT, can_fingerprint +from opendbc.car.car_helpers import FRAME_FINGERPRINT, _get_gm_stored_candidate_fallback, can_fingerprint from opendbc.car.fingerprints import _FINGERPRINTS as FINGERPRINTS @@ -53,3 +53,24 @@ class TestCanFingerprint: car_fingerprint, _ = can_fingerprint(can_recv) assert car_fingerprint == car_model assert frames == expected_frames + 2 # TODO: fix extra frames + + def test_gm_stored_candidate_fallback_prefers_persisted_model(self): + fingerprints = {0: {190: 6, 201: 8, 209: 7, 211: 2, 241: 6}} + + candidate = _get_gm_stored_candidate_fallback(fingerprints, "CHEVROLET_VOLT_CC", None) + + assert candidate == "CHEVROLET_VOLT_CC" + + def test_gm_stored_candidate_fallback_uses_cached_model(self): + fingerprints = {0: {190: 6, 201: 8, 209: 7, 211: 2, 241: 6}} + + candidate = _get_gm_stored_candidate_fallback(fingerprints, "MOCK", "CHEVROLET_VOLT_CC") + + assert candidate == "CHEVROLET_VOLT_CC" + + def test_gm_stored_candidate_fallback_ignores_non_gm_fingerprint(self): + fingerprints = {0: {1: 1, 2: 2, 3: 3, 4: 4}} + + candidate = _get_gm_stored_candidate_fallback(fingerprints, "CHEVROLET_VOLT_CC", "CHEVROLET_VOLT_CC") + + assert candidate is None diff --git a/panda/board/obj/body_h7.bin.signed b/panda/board/obj/body_h7.bin.signed index d33c3825..10582dce 100644 Binary files a/panda/board/obj/body_h7.bin.signed and b/panda/board/obj/body_h7.bin.signed differ diff --git a/panda/board/obj/body_h7/bootstub.elf b/panda/board/obj/body_h7/bootstub.elf index 0d3c2667..2b1a10e0 100755 Binary files a/panda/board/obj/body_h7/bootstub.elf and b/panda/board/obj/body_h7/bootstub.elf differ diff --git a/panda/board/obj/body_h7/main.bin b/panda/board/obj/body_h7/main.bin index b005fb48..8ef1b2ba 100755 Binary files a/panda/board/obj/body_h7/main.bin and b/panda/board/obj/body_h7/main.bin differ diff --git a/panda/board/obj/body_h7/main.elf b/panda/board/obj/body_h7/main.elf index ac2b40f4..4e170ba4 100755 Binary files a/panda/board/obj/body_h7/main.elf and b/panda/board/obj/body_h7/main.elf differ diff --git a/panda/board/obj/bootstub.body_h7.bin b/panda/board/obj/bootstub.body_h7.bin index 7eccda5e..07c1b483 100755 Binary files a/panda/board/obj/bootstub.body_h7.bin and b/panda/board/obj/bootstub.body_h7.bin differ diff --git a/panda/board/obj/bootstub.panda.bin b/panda/board/obj/bootstub.panda.bin index 88ef9ff2..b3d0fa52 100755 Binary files a/panda/board/obj/bootstub.panda.bin and b/panda/board/obj/bootstub.panda.bin differ diff --git a/panda/board/obj/bootstub.panda_h7.bin b/panda/board/obj/bootstub.panda_h7.bin index 61d7943f..6156569c 100755 Binary files a/panda/board/obj/bootstub.panda_h7.bin and b/panda/board/obj/bootstub.panda_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_h7_remote.bin b/panda/board/obj/bootstub.panda_h7_remote.bin index 61d7943f..6156569c 100755 Binary files a/panda/board/obj/bootstub.panda_h7_remote.bin and b/panda/board/obj/bootstub.panda_h7_remote.bin differ diff --git a/panda/board/obj/bootstub.panda_jungle_h7.bin b/panda/board/obj/bootstub.panda_jungle_h7.bin index 893b8cee..1bc24366 100755 Binary files a/panda/board/obj/bootstub.panda_jungle_h7.bin and b/panda/board/obj/bootstub.panda_jungle_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_remote.bin b/panda/board/obj/bootstub.panda_remote.bin index 88ef9ff2..b3d0fa52 100755 Binary files a/panda/board/obj/bootstub.panda_remote.bin and b/panda/board/obj/bootstub.panda_remote.bin differ diff --git a/panda/board/obj/gitversion.h b/panda/board/obj/gitversion.h index d39c6e92..e0f294fd 100644 --- a/panda/board/obj/gitversion.h +++ b/panda/board/obj/gitversion.h @@ -1,2 +1,2 @@ extern const uint8_t gitversion[19]; -const uint8_t gitversion[19] = "DEV-3d8af236-DEBUG"; +const uint8_t gitversion[19] = "DEV-74113197-DEBUG"; diff --git a/panda/board/obj/panda.bin.signed b/panda/board/obj/panda.bin.signed index bb860ca9..8ebb1746 100644 Binary files a/panda/board/obj/panda.bin.signed and b/panda/board/obj/panda.bin.signed differ diff --git a/panda/board/obj/panda/bootstub.elf b/panda/board/obj/panda/bootstub.elf index 6f20f172..5681ed04 100755 Binary files a/panda/board/obj/panda/bootstub.elf and b/panda/board/obj/panda/bootstub.elf differ diff --git a/panda/board/obj/panda/main.bin b/panda/board/obj/panda/main.bin index 5bf7fdac..48f824db 100755 Binary files a/panda/board/obj/panda/main.bin and b/panda/board/obj/panda/main.bin differ diff --git a/panda/board/obj/panda/main.elf b/panda/board/obj/panda/main.elf index b3edac62..823ce9c0 100755 Binary files a/panda/board/obj/panda/main.elf and b/panda/board/obj/panda/main.elf differ diff --git a/panda/board/obj/panda_h7.bin.signed b/panda/board/obj/panda_h7.bin.signed index a7b746e9..02706bf4 100644 Binary files a/panda/board/obj/panda_h7.bin.signed and b/panda/board/obj/panda_h7.bin.signed differ diff --git a/panda/board/obj/panda_h7/bootstub.elf b/panda/board/obj/panda_h7/bootstub.elf index be7df209..17080177 100755 Binary files a/panda/board/obj/panda_h7/bootstub.elf and b/panda/board/obj/panda_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_h7/main.bin b/panda/board/obj/panda_h7/main.bin index cacad09e..b6187e7e 100755 Binary files a/panda/board/obj/panda_h7/main.bin and b/panda/board/obj/panda_h7/main.bin differ diff --git a/panda/board/obj/panda_h7/main.elf b/panda/board/obj/panda_h7/main.elf index 976411f2..d47ef1c6 100755 Binary files a/panda/board/obj/panda_h7/main.elf and b/panda/board/obj/panda_h7/main.elf differ diff --git a/panda/board/obj/panda_h7_remote.bin.signed b/panda/board/obj/panda_h7_remote.bin.signed index 7a63af4a..16e014e7 100644 Binary files a/panda/board/obj/panda_h7_remote.bin.signed and b/panda/board/obj/panda_h7_remote.bin.signed differ diff --git a/panda/board/obj/panda_h7_remote/bootstub.elf b/panda/board/obj/panda_h7_remote/bootstub.elf index 6b4b3cc7..c9405942 100755 Binary files a/panda/board/obj/panda_h7_remote/bootstub.elf and b/panda/board/obj/panda_h7_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_h7_remote/main.bin b/panda/board/obj/panda_h7_remote/main.bin index 0aecfe70..97f3b2f2 100755 Binary files a/panda/board/obj/panda_h7_remote/main.bin and b/panda/board/obj/panda_h7_remote/main.bin differ diff --git a/panda/board/obj/panda_h7_remote/main.elf b/panda/board/obj/panda_h7_remote/main.elf index ef3e89e3..332d91b9 100755 Binary files a/panda/board/obj/panda_h7_remote/main.elf and b/panda/board/obj/panda_h7_remote/main.elf differ diff --git a/panda/board/obj/panda_jungle_h7.bin.signed b/panda/board/obj/panda_jungle_h7.bin.signed index 5cc2f66c..bce3a46f 100644 Binary files a/panda/board/obj/panda_jungle_h7.bin.signed and b/panda/board/obj/panda_jungle_h7.bin.signed differ diff --git a/panda/board/obj/panda_jungle_h7/bootstub.elf b/panda/board/obj/panda_jungle_h7/bootstub.elf index d5bfb275..3277884a 100755 Binary files a/panda/board/obj/panda_jungle_h7/bootstub.elf and b/panda/board/obj/panda_jungle_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_jungle_h7/main.bin b/panda/board/obj/panda_jungle_h7/main.bin index 6838926c..01c12b85 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.bin and b/panda/board/obj/panda_jungle_h7/main.bin differ diff --git a/panda/board/obj/panda_jungle_h7/main.elf b/panda/board/obj/panda_jungle_h7/main.elf index 1d583d2a..77185b38 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.elf and b/panda/board/obj/panda_jungle_h7/main.elf differ diff --git a/panda/board/obj/panda_remote.bin.signed b/panda/board/obj/panda_remote.bin.signed index f45bc170..ddfd2094 100644 Binary files a/panda/board/obj/panda_remote.bin.signed and b/panda/board/obj/panda_remote.bin.signed differ diff --git a/panda/board/obj/panda_remote/bootstub.elf b/panda/board/obj/panda_remote/bootstub.elf index 020b076d..31b86413 100755 Binary files a/panda/board/obj/panda_remote/bootstub.elf and b/panda/board/obj/panda_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_remote/main.bin b/panda/board/obj/panda_remote/main.bin index cb6a37fe..7b8458e2 100755 Binary files a/panda/board/obj/panda_remote/main.bin and b/panda/board/obj/panda_remote/main.bin differ diff --git a/panda/board/obj/panda_remote/main.elf b/panda/board/obj/panda_remote/main.elf index f5863202..8d7f3af7 100755 Binary files a/panda/board/obj/panda_remote/main.elf and b/panda/board/obj/panda_remote/main.elf differ diff --git a/panda/board/obj/version b/panda/board/obj/version index 2be98359..274fb8a0 100644 --- a/panda/board/obj/version +++ b/panda/board/obj/version @@ -1 +1 @@ -DEV-3d8af236-DEBUG \ No newline at end of file +DEV-74113197-DEBUG \ No newline at end of file diff --git a/selfdrive/ui/ui b/selfdrive/ui/ui index a8e14026..da4f7e9e 100755 Binary files a/selfdrive/ui/ui and b/selfdrive/ui/ui differ diff --git a/starpilot/ui/qt/offroad/starpilot_settings.cc b/starpilot/ui/qt/offroad/starpilot_settings.cc index 0f86056f..6d6f76dc 100644 --- a/starpilot/ui/qt/offroad/starpilot_settings.cc +++ b/starpilot/ui/qt/offroad/starpilot_settings.cc @@ -172,7 +172,15 @@ StarPilotSettingsWindow::StarPilotSettingsWindow(SettingsWindow *parent) : QFram shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object(); QString className = this->metaObject()->className(); - if (!shownDescriptions.value(className).toBool(false)) { + QString legacyClassName = className; + legacyClassName.replace("StarPilot", "FrogPilot"); + + bool alreadyShown = shownDescriptions.value(className).toBool(false); + bool legacyShown = legacyClassName != className && shownDescriptions.value(legacyClassName).toBool(false); + if (legacyShown && !alreadyShown) { + shownDescriptions.insert(className, true); + params.putNonBlocking("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString()); + } else if (!alreadyShown) { forceOpenDescriptions = true; } diff --git a/system/manager/manager.py b/system/manager/manager.py index 06a2865f..4ff71f49 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -136,6 +136,15 @@ def _load_first_available_param_value(params: Params, params_cache: Params, sour return None +def _has_persisted_param_file(params: Params, key: str | bytes) -> bool: + try: + path = params.get_param_path(key) + except Exception: + return False + + return bool(path) and os.path.isfile(path) + + def migrate_starpilot_param_renames(params: Params, params_cache: Params) -> None: if STARPILOT_PARAM_RENAME_MIGRATION_FLAG.exists(): return @@ -320,6 +329,7 @@ def migrate_starpilot_default_parity(params: Params, params_cache: Params) -> No if STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG.exists(): return + seeded_keys: list[str] = [] desired_bool_values = { "AdvancedLateralTune": True, "ForceAutoTuneOff": True, @@ -330,27 +340,34 @@ def migrate_starpilot_default_parity(params: Params, params_cache: Params) -> No } for key, value in desired_bool_values.items(): + if _has_persisted_param_file(params, key) or _has_persisted_param_file(params_cache, key): + continue params.put_bool(key, value) params_cache.put_bool(key, value) + seeded_keys.append(key) - params.put_float("CEModelStopTime", 7.0) - params_cache.put_float("CEModelStopTime", 7.0) + if not _has_persisted_param_file(params, "CEModelStopTime") and not _has_persisted_param_file(params_cache, "CEModelStopTime"): + params.put_float("CEModelStopTime", 7.0) + params_cache.put_float("CEModelStopTime", 7.0) + seeded_keys.append("CEModelStopTime") # Rebase default regression fix: # EVTuning must default to enabled on EV/direct-drive platforms to preserve - # StarPilot acceleration profile behavior. + # StarPilot acceleration profile behavior, but existing user overrides win. carparams_blob = params.get("CarParamsPersistent") or params.get("CarParams") if carparams_blob is not None: try: with car.CarParams.from_bytes(carparams_blob) as cp: is_ev_platform = cp.transmissionType == car.CarParams.TransmissionType.direct - if is_ev_platform and not params.get_bool("TruckTuning"): + if is_ev_platform and not params.get_bool("TruckTuning") and not _has_persisted_param_file(params, "EVTuning") and not _has_persisted_param_file(params_cache, "EVTuning"): params.put_bool("EVTuning", True) params_cache.put_bool("EVTuning", True) + seeded_keys.append("EVTuning") except Exception: cloudlog.exception("Failed EVTuning EV default parity migration") - cloudlog.warning("Applied one-time StarPilot default parity migration for lateral/longitudinal toggles") + if seeded_keys: + cloudlog.warning(f"Applied one-time StarPilot default parity migration for {seeded_keys}") try: STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG.parent.mkdir(parents=True, exist_ok=True) @@ -524,6 +541,7 @@ def manager_init() -> None: # 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) + migrate_starpilot_default_parity(params, params_cache) # set unset params to their default value for k in params.all_keys(): @@ -558,7 +576,6 @@ def manager_init() -> None: # Branch migration: rename legacy Bolt fingerprint persisted in CarParams. migrate_legacy_bolt_fingerprint(params) - migrate_starpilot_default_parity(params, params_cache) # set dongle id reg_res = register(show_spinner=True) diff --git a/system/manager/test/test_manager.py b/system/manager/test/test_manager.py index 5eca7a9f..78796a4c 100644 --- a/system/manager/test/test_manager.py +++ b/system/manager/test/test_manager.py @@ -2,6 +2,8 @@ import os import pytest import signal import time +from pathlib import Path +import json from cereal import car from openpilot.common.params import Params @@ -16,6 +18,59 @@ MAX_STARTUP_TIME = 3 BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond'] +class FileBackedFakeParams: + def __init__(self, root: Path, values: dict[str, object] | None = None): + self.root = root + self.root.mkdir(parents=True, exist_ok=True) + for key, value in (values or {}).items(): + self.put(key, value) + + def get_param_path(self, key): + return str(self.root / (key.decode() if isinstance(key, bytes) else str(key))) + + def get(self, key): + path = Path(self.get_param_path(key)) + if not path.is_file(): + return None + + raw = path.read_bytes() + try: + return raw.decode("utf-8") + except UnicodeDecodeError: + return raw + + def get_bool(self, key): + value = self.get(key) + if value is None: + return False + if isinstance(value, bytes): + value = value.decode("utf-8", errors="ignore") + return str(value).strip().lower() in ("1", "true", "yes", "on") + + def put(self, key, value): + path = Path(self.get_param_path(key)) + path.parent.mkdir(parents=True, exist_ok=True) + + if isinstance(value, bytes): + raw = value + elif isinstance(value, bool): + raw = b"1" if value else b"0" + elif isinstance(value, float): + raw = str(float(value)).encode("utf-8") + elif isinstance(value, (dict, list)): + raw = json.dumps(value, separators=(",", ":")).encode("utf-8") + else: + raw = str(value).encode("utf-8") + + path.write_bytes(raw) + + def put_bool(self, key, value): + self.put(key, bool(value)) + + def put_float(self, key, value): + self.put(key, float(value)) + + class TestManager: def setup_method(self): HARDWARE.set_power_save(False) @@ -85,6 +140,30 @@ class TestManager: assert params.get("ExperimentalLongitudinalEnabled") is None assert params_cache.get("ExperimentalLongitudinalEnabled") is None + def test_migrate_starpilot_default_parity_preserves_existing_values(self, tmp_path, monkeypatch): + monkeypatch.setattr(manager, "STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG", tmp_path / "starpilot_defaults_parity_v1") + + params = FileBackedFakeParams(tmp_path / "params", { + "AdvancedLateralTune": False, + "ForceAutoTuneOff": False, + "HumanAcceleration": True, + "CEModelStopTime": 3.5, + }) + params_cache = FileBackedFakeParams(tmp_path / "cache", { + "NNFF": True, + }) + + manager.migrate_starpilot_default_parity(params, params_cache) + + assert not params.get_bool("AdvancedLateralTune") + assert not params.get_bool("ForceAutoTuneOff") + assert params.get_bool("HumanAcceleration") + assert params.get("CEModelStopTime") == "3.5" + assert params_cache.get_bool("NNFF") + + assert Path(params.get_param_path("HumanFollowing")).is_file() + assert not params.get_bool("HumanFollowing") + @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): """