From dac714041019fd795e077ca85a3b1cbc5c87d318 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Tue, 15 Sep 2026 14:01:39 -0500 Subject: [PATCH] fingerprint --- .../components/tools/device_settings.js | 29 +++++++-- .../tests/test_fingerprint_catalog.py | 62 +++++++++++++++++++ starpilot/system/the_galaxy/the_galaxy.py | 28 ++++++++- 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/starpilot/system/the_galaxy/assets/components/tools/device_settings.js b/starpilot/system/the_galaxy/assets/components/tools/device_settings.js index 8e76e51867..380e9380e5 100644 --- a/starpilot/system/the_galaxy/assets/components/tools/device_settings.js +++ b/starpilot/system/the_galaxy/assets/components/tools/device_settings.js @@ -335,12 +335,31 @@ function scheduleSyncInputs() { function applySelectOptions(el, options) { el.innerHTML = "" + const labelsByValue = new Map() for (const opt of options || []) { if (opt?.developer_only && !state.values[GALAXY_DEVELOPER_MODE_KEY]) continue const o = document.createElement("option") o.value = String(opt.value) o.textContent = opt.label el.appendChild(o) + if (el.id === "ds-CarModel") { + if (!labelsByValue.has(o.value)) labelsByValue.set(o.value, []) + labelsByValue.get(o.value).push(o.textContent) + } + } + + if (el.id === "ds-CarModel") { + for (const [modelValue, labels] of labelsByValue) { + if (labels.length < 2) continue + const baseNames = labels.map(label => label.split(" (")[0].replace(/\s+\d{4}.*$/, "").trim()) + const baseName = baseNames.every(name => name === baseNames[0]) ? baseNames[0] : modelValue + const placeholder = document.createElement("option") + placeholder.value = modelValue + placeholder.textContent = `${baseName} (variant not identified)` + placeholder.dataset.fingerprintVariantPlaceholder = "1" + const firstVariant = Array.from(el.options).find(option => option.value === modelValue) + el.insertBefore(placeholder, firstVariant) + } } } @@ -354,14 +373,16 @@ function syncSelectValue(el, key) { if (key === "CarModel") { const targetLabel = toSelectValue(state.values.CarModelName) const options = Array.from(el.options) - const matchingIndex = options.findIndex(opt => { - if (opt.value !== targetValue) return false - return !targetLabel || opt.textContent === targetLabel - }) + const matchingIndex = targetLabel ? options.findIndex(opt => opt.value === targetValue && opt.textContent === targetLabel) : -1 if (matchingIndex !== -1) { el.selectedIndex = matchingIndex return } + const unspecifiedIndex = options.findIndex(opt => opt.value === targetValue && opt.dataset.fingerprintVariantPlaceholder === "1") + if (unspecifiedIndex !== -1) { + el.selectedIndex = unspecifiedIndex + return + } } el.value = targetValue diff --git a/starpilot/system/the_galaxy/tests/test_fingerprint_catalog.py b/starpilot/system/the_galaxy/tests/test_fingerprint_catalog.py index e585dc36fb..5439c91454 100644 --- a/starpilot/system/the_galaxy/tests/test_fingerprint_catalog.py +++ b/starpilot/system/the_galaxy/tests/test_fingerprint_catalog.py @@ -1,4 +1,5 @@ from test_dashboard_stats import MODULE_DIR, _install_server_import_stubs +from test_navigation_params import WritableFakeParams, _params_client, the_galaxy as api_server def _load_server_module(): @@ -17,7 +18,68 @@ the_galaxy = _load_server_module() def test_galaxy_lists_tesla_hardware_specific_docs_for_manual_fingerprinting(): tesla_models = the_galaxy._extract_fingerprint_models_for_make("tesla") + assert {"value": "TESLA_MODEL_S_PREAP", "label": "Tesla Model S (Pre-AP) 2012-14"} in tesla_models + assert {"value": "TESLA_MODEL_S_HW1", "label": "Tesla Model S (with HW1) 2014-16"} in tesla_models + assert {"value": "TESLA_MODEL_S_PREAP", "label": "Tesla Model S (with HW1) 2014-16"} not in tesla_models assert {"value": "TESLA_MODEL_3", "label": "Tesla Model 3 (with HW3) 2019-23"} in tesla_models assert {"value": "TESLA_MODEL_3", "label": "Tesla Model 3 (with HW4) 2024-26"} in tesla_models assert {"value": "TESLA_MODEL_Y", "label": "Tesla Model Y (with HW3) 2020-23"} in tesla_models assert {"value": "TESLA_MODEL_X", "label": "Tesla Model X (with HW4) 2024"} in tesla_models + + catalog = the_galaxy._get_fingerprint_catalog() + assert catalog["label_to_model"]["Tesla Model S (with HW1) 2014-16"] == "TESLA_MODEL_S_HW1" + + +def test_galaxy_does_not_assign_a_regional_label_to_ambiguous_ev6_fingerprint(): + kia_models = the_galaxy._extract_fingerprint_models_for_make("kia") + assert {"value": "KIA_EV6", "label": "Kia EV6 (Southeast Asia only) 2022-24"} in kia_models + assert {"value": "KIA_EV6", "label": "Kia EV6 (with HDA II) 2022-24"} in kia_models + + catalog = the_galaxy._get_fingerprint_catalog() + assert catalog["model_to_label"]["KIA_EV6"] is None + + +def test_manual_fingerprint_api_keeps_the_saved_value_and_label_consistent(monkeypatch): + client, params = _params_client(monkeypatch, {}, "pc") + monkeypatch.setattr(api_server, "_get_param_type_info", lambda: ({"CarModel"}, {"CarModel": str})) + monkeypatch.setattr(api_server, "update_starpilot_toggles", lambda: None) + + hw1_label = "Tesla Model S (with HW1) 2014-16" + options = client.get("/api/fingerprints/models?make=Tesla").get_json() + assert {"value": "TESLA_MODEL_S_HW1", "label": hw1_label} in options + response = client.put("/api/params", json={"key": "CarModel", "value": "TESLA_MODEL_S_HW1", "label": hw1_label}) + assert response.status_code == 200 + assert params.values["CarModel"] == "TESLA_MODEL_S_HW1" + assert params.values["CarModelName"] == hw1_label + + response = client.put("/api/params", json={"key": "CarModel", "value": "TESLA_MODEL_S_PREAP", "label": hw1_label}) + assert response.status_code == 400 + assert params.values["CarModel"] == "TESLA_MODEL_S_HW1" + assert params.values["CarModelName"] == hw1_label + + preap_label = "Tesla Model S (Pre-AP) 2012-14" + response = client.put("/api/params", json={"key": "CarModel", "value": "TESLA_MODEL_S_PREAP", "label": preap_label}) + assert response.status_code == 200 + assert params.values["CarModel"] == "TESLA_MODEL_S_PREAP" + assert params.values["CarModelName"] == preap_label + + response = client.put("/api/params", json={"key": "CarModel", "value": "KIA_EV6"}) + assert response.status_code == 200 + assert params.values["CarModel"] == "KIA_EV6" + assert "CarModelName" not in params.values + + hda_label = "Kia EV6 (with HDA II) 2022-24" + response = client.put("/api/params", json={"key": "CarModel", "value": "KIA_EV6", "label": hda_label}) + assert response.status_code == 200 + assert params.values["CarModelName"] == hda_label + + +def test_fingerprint_diagnostic_flags_a_stale_label_value_mismatch(monkeypatch): + monkeypatch.setattr(the_galaxy, "params", WritableFakeParams({ + "CarModel": "TESLA_MODEL_S_PREAP", + "CarModelName": "Tesla Model S (with HW1) 2014-16", + })) + text = the_galaxy._get_fingerprint_snapshot_text() + assert "Mismatch" in text + assert "TESLA_MODEL_S_PREAP" in text + assert "reselect" in text.lower() diff --git a/starpilot/system/the_galaxy/the_galaxy.py b/starpilot/system/the_galaxy/the_galaxy.py index 2ba675f782..2d2cd10fc4 100644 --- a/starpilot/system/the_galaxy/the_galaxy.py +++ b/starpilot/system/the_galaxy/the_galaxy.py @@ -13,6 +13,7 @@ import sysconfig import tarfile import io +import tokenize from io import BytesIO from pathlib import Path @@ -3283,7 +3284,13 @@ def _extract_fingerprint_models_for_make(make_key): except Exception: return [] - content = re.sub(r'#[^\n]*', "", content) + lines = content.splitlines(keepends=True) + for token in tokenize.generate_tokens(io.StringIO(content).readline): + if token.type == tokenize.COMMENT: + line_index, start = token.start[0] - 1, token.start[1] + end = token.end[1] + lines[line_index] = lines[line_index][:start] + " " * (end - start) + lines[line_index][end:] + content = "".join(lines) content = re.sub(r'footnotes=\[[^\]]*\],\s*', "", content) models = [] @@ -3327,6 +3334,7 @@ def _get_fingerprint_catalog(): all_models = [] seen_all = set() model_to_label = {} + labels_by_model = {} model_to_make = {} label_to_model = {} @@ -3340,6 +3348,7 @@ def _get_fingerprint_catalog(): model_label = entry["label"] model_to_label.setdefault(model_value, model_label) + labels_by_model.setdefault(model_value, set()).add(model_label) model_to_make.setdefault(model_value, make_label) label_to_model.setdefault(model_label, model_value) @@ -3356,6 +3365,10 @@ def _get_fingerprint_catalog(): all_models.sort(key=lambda entry: entry["label"].lower()) + for model_value, labels in labels_by_model.items(): + if len(labels) > 1: + model_to_label[model_value] = None + _fingerprint_catalog_cache = { "makes": make_options, "models_by_make": models_by_make, @@ -4286,6 +4299,12 @@ def _get_fingerprint_snapshot_text(): model_value = str(params.get("CarModel", encoding="utf-8") or "").strip() if model_name and model_value: + catalog = _get_fingerprint_catalog() + if model_value in catalog["model_to_make"] and not any( + entry["value"] == model_value and entry["label"] == model_name + for entry in catalog["all_models"] + ): + return f"Mismatch: {model_name} vs {model_value}; reselect your vehicle" return f"{model_name} ({model_value})" if model_name: return model_name @@ -6588,6 +6607,13 @@ def setup(app): return jsonify({"error": "Car model cannot be empty."}), 400 catalog = _get_fingerprint_catalog() + if selected_label_input: + labelled_models = { + entry["value"] for entry in catalog["all_models"] + if entry["label"] == selected_label_input + } + if labelled_models and selected_model not in labelled_models: + return jsonify({"error": "Vehicle label and model do not match; refresh and reselect your vehicle."}), 400 if selected_label_input and any( entry["value"] == selected_model and entry["label"] == selected_label_input for entry in catalog["all_models"]