lateral tuning migration and dashcam mode
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
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,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";
|
||||
|
||||
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 @@
|
||||
DEV-3d8af236-DEBUG
|
||||
DEV-74113197-DEBUG
|
||||
Binary file not shown.
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user