Files
StarPilot/selfdrive/ui/lib/fingerprint_catalog.py
T
2026-06-06 14:53:43 -04:00

191 lines
5.6 KiB
Python

from __future__ import annotations
import re
from collections import Counter
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
FINGERPRINT_MAKE_TO_VALUES_DIR = {
"acura": "honda",
"audi": "volkswagen",
"buick": "gm",
"cadillac": "gm",
"chevrolet": "gm",
"chrysler": "chrysler",
"cupra": "volkswagen",
"dodge": "chrysler",
"ford": "ford",
"genesis": "hyundai",
"gmc": "gm",
"holden": "gm",
"honda": "honda",
"hyundai": "hyundai",
"jeep": "chrysler",
"kia": "hyundai",
"lexus": "toyota",
"lincoln": "ford",
"man": "volkswagen",
"mazda": "mazda",
"nissan": "nissan",
"peugeot": "psa",
"ram": "chrysler",
"rivian": "rivian",
"seat": "volkswagen",
"\u0161koda": "volkswagen",
"subaru": "subaru",
"tesla": "tesla",
"toyota": "toyota",
"volkswagen": "volkswagen",
}
_FINGERPRINT_CARDOCS_RE = re.compile(r'\w*CarDocs\(\s*"([^"]+)"')
_FINGERPRINT_PLATFORM_RE = re.compile(r'(\w+)\s*=\s*\w+\s*\(\s*\[([\s\S]*?)\]\s*,')
_FINGERPRINT_PLATFORM_NAME_RE = re.compile(r'^[A-Z0-9_]+$')
_FINGERPRINT_VALID_NAME_RE = re.compile(r'^[A-Za-z0-9 \u0160.()\-]+$')
@dataclass(frozen=True)
class FingerprintModelOption:
value: str
label: str
short_label: str
option_label: str
button_label: str
def format_fingerprint_value(value: str) -> str:
formatted_tokens: list[str] = []
for token in value.split("_"):
if not token:
continue
if token.isupper() and (len(token) <= 4 or any(char.isdigit() for char in token)):
formatted_tokens.append(token)
else:
formatted_tokens.append(token.title())
return " ".join(formatted_tokens)
def shorten_model_label(make: str, label: str) -> str:
prefix = f"{make} "
if label.lower().startswith(prefix.lower()):
return label[len(prefix):]
return label
def compact_model_label(label: str) -> str:
compact = re.sub(r'\s+\([^)]*\)', "", label)
compact = re.sub(r'\s+-\s+.*$', "", compact)
compact = re.sub(r'\s+(?:ASCM Harness|Camera Harness|SDGM Harness(?:.*)?|ACC w Pedal|Instrument Cluster)$', "", compact)
compact = re.sub(r'\s{2,}', " ", compact).strip()
return compact or label
def _get_openpilot_root() -> Path:
for parent in Path(__file__).resolve().parents:
if (parent / "opendbc" / "car").is_dir() or (parent / "selfdrive" / "car").is_dir():
return parent
return Path(__file__).resolve().parents[5]
def _extract_fingerprint_models_for_make(make_key: str) -> list[tuple[str, str, str]]:
source_make = FINGERPRINT_MAKE_TO_VALUES_DIR.get(make_key, make_key)
root = _get_openpilot_root()
values_candidates = (
root / "opendbc" / "car" / source_make / "values.py",
root / "selfdrive" / "car" / source_make / "values.py",
)
values_path = next((path for path in values_candidates if path.is_file()), None)
if values_path is None:
return []
try:
content = values_path.read_text(encoding="utf-8", errors="replace")
except Exception:
return []
content = re.sub(r'#[^\n]*', "", content)
content = re.sub(r'footnotes=\[[^\]]*\],\s*', "", content)
models: list[tuple[str, str, str]] = []
seen: set[tuple[str, str]] = set()
for platform_match in _FINGERPRINT_PLATFORM_RE.finditer(content):
platform_name = platform_match.group(1)
if not _FINGERPRINT_PLATFORM_NAME_RE.match(platform_name):
continue
platform_section = platform_match.group(2)
for name_match in _FINGERPRINT_CARDOCS_RE.finditer(platform_section):
car_name = name_match.group(1).strip()
if " " not in car_name or not _FINGERPRINT_VALID_NAME_RE.match(car_name):
continue
make_label = car_name.split(" ", 1)[0]
if make_label.lower() != make_key:
continue
dedupe_key = (car_name, platform_name)
if dedupe_key in seen:
continue
seen.add(dedupe_key)
models.append((platform_name, car_name, make_label))
models.sort(key=lambda entry: entry[1].lower())
return models
@lru_cache(maxsize=1)
def get_fingerprint_catalog() -> tuple[
tuple[str, ...],
dict[str, tuple[FingerprintModelOption, ...]],
dict[str, FingerprintModelOption],
dict[str, str],
]:
makes: list[str] = []
models_by_make: dict[str, tuple[FingerprintModelOption, ...]] = {}
model_by_value: dict[str, FingerprintModelOption] = {}
make_by_model: dict[str, str] = {}
for make_key in sorted(FINGERPRINT_MAKE_TO_VALUES_DIR):
entries = _extract_fingerprint_models_for_make(make_key)
if not entries:
continue
make_label = entries[0][2]
short_counts = Counter(shorten_model_label(make_label, label) for _, label, _ in entries)
options: list[FingerprintModelOption] = []
used_option_labels: set[str] = set()
for value, label, _ in entries:
short_label = shorten_model_label(make_label, label)
compact_label = compact_model_label(short_label)
# Prefer the shorter label in mici, but fall back when it would collide.
option_label = short_label if short_counts[short_label] == 1 else label
if option_label in used_option_labels:
option_label = f"{option_label} ({value})"
used_option_labels.add(option_label)
button_label = compact_label if len(compact_label) < len(short_label) else short_label
option = FingerprintModelOption(
value=value,
label=label,
short_label=short_label,
option_label=option_label,
button_label=button_label,
)
options.append(option)
model_by_value.setdefault(value, option)
make_by_model.setdefault(value, make_label)
makes.append(make_label)
models_by_make[make_label] = tuple(options)
makes.sort(key=str.lower)
return tuple(makes), models_by_make, model_by_value, make_by_model