StarPilot

This commit is contained in:
firestar5683
2026-03-12 01:49:47 -05:00
parent 0e9ef526f7
commit d0e1db6766
2171 changed files with 590688 additions and 247754 deletions
+32 -10
View File
@@ -29,13 +29,22 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None:
if AGNOS:
HARDWARE.set_power_save(False)
os.sched_setaffinity(0, range(8)) # ensure we can use the isolcpus cores
# Extremely conservative on-device compile to avoid lowmemorykiller SIGKILLs.
attempts = [1]
else:
attempts = [nproc, max(1, nproc // 2), 1]
# building with all cores can result in using too
# much memory, so retry with less parallelism
# Preserve order while de-duplicating.
attempts = list(dict.fromkeys(max(1, int(n)) for n in attempts))
# building with all cores can result in using too much memory,
# so retry with less parallelism.
compile_output: list[bytes] = []
for n in (nproc, nproc/2, 1):
compile_output.clear()
scons: subprocess.Popen = subprocess.Popen(["scons", f"-j{int(n)}", "--cache-populate", *extra_args], cwd=BASEDIR, env=env, stderr=subprocess.PIPE)
last_returncode = 0
def run_scons(n: int, cache_args: list[str]) -> int:
nonlocal compile_output, spinner, env
scons: subprocess.Popen = subprocess.Popen(["scons", f"-j{int(n)}", *cache_args, *extra_args], cwd=BASEDIR, env=env, stderr=subprocess.PIPE)
assert scons.stderr is not None
# Read progress from stderr and update spinner
@@ -56,14 +65,27 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None:
except Exception:
pass
if scons.returncode == 0:
if scons.returncode != 0 and scons.stderr is not None:
compile_output += scons.stderr.read().split(b'\n')
return scons.returncode
for n in attempts:
compile_output.clear()
last_returncode = run_scons(n, ["--cache-populate"])
if last_returncode == 0:
break
if scons.returncode != 0:
# Read remaining output
if scons.stderr is not None:
compile_output += scons.stderr.read().split(b'\n')
if last_returncode != 0:
# OOM-ish builds often fail with "Error -9" when clang gets SIGKILL.
# Do one final conservative retry before surfacing a hard failure.
output_blob = b"\n".join(compile_output)
if AGNOS and b"Error -9" in output_blob:
cloudlog.warning("scons likely OOM-killed (Error -9), retrying with -j1 --cache-disable")
print("Build retry: detected Error -9, retrying with -j1 --cache-disable")
compile_output.clear()
last_returncode = run_scons(1, ["--cache-disable"])
if last_returncode != 0:
# Build failed log errors
error_s = b"\n".join(compile_output).decode('utf8', 'replace')
add_file_handler(cloudlog)
+239 -3
View File
@@ -1,16 +1,18 @@
#!/usr/bin/env python3
import datetime
import json
import os
from pathlib import Path
import signal
import sys
import time
import traceback
from cereal import log
from cereal import car, log
import cereal.messaging as messaging
import openpilot.system.sentry as sentry
from openpilot.common.utils import atomic_write
from openpilot.common.params import Params, ParamKeyFlag
from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType
from openpilot.common.text_window import TextWindow
from openpilot.system.hardware import HARDWARE
from openpilot.system.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog
@@ -25,6 +27,229 @@ from openpilot.frogpilot.common.frogpilot_functions import frogpilot_boot_functi
from openpilot.frogpilot.common.frogpilot_variables import get_frogpilot_toggles
LEGACY_BOLT_FP_MIGRATION_FLAG = Path("/data") / "legacy_bolt_fp_migration_v1"
STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG = Path("/data") / "starpilot_defaults_parity_v1"
STARPILOT_PARAM_CANONICALIZATION_MIGRATION_FLAG = Path("/data") / "starpilot_param_canonicalization_v1"
LEGACY_CARMODEL_MIGRATIONS = {
"CHEVROLET_BOLT_CC_2019_2021": "CHEVROLET_BOLT_CC_2018_2021",
}
def _to_text(value):
if value is None:
return None
if isinstance(value, bytes):
return value.decode("utf-8", errors="ignore")
return str(value)
def migrate_legacy_bolt_fingerprint(params: Params) -> None:
old_fp, new_fp = next(iter(LEGACY_CARMODEL_MIGRATIONS.items()))
carparams_keys = ("CarParams", "CarParamsCache", "CarParamsPersistent", "CarParamsPrevRoute")
keys_to_clear = (
"CarParams",
"CarParamsCache",
"CarParamsPersistent",
"CarParamsPrevRoute",
"FrogPilotCarParams",
"FrogPilotCarParamsPersistent",
)
car_model = _to_text(params.get("CarModel"))
legacy_detected = car_model == old_fp
if not legacy_detected:
old_fp_bytes = old_fp.encode()
for key in carparams_keys:
raw = params.get(key)
if raw is None:
continue
raw_bytes = raw if isinstance(raw, bytes) else str(raw).encode()
# Fast path for payloads that still embed the legacy fingerprint string.
if old_fp_bytes in raw_bytes:
legacy_detected = True
break
# Fallback decode for payloads that don't expose the raw string directly.
try:
with car.CarParams.from_bytes(raw_bytes) as cp:
if cp.carFingerprint == old_fp:
legacy_detected = True
break
except Exception:
continue
if not legacy_detected:
return
cleared_keys: list[str] = []
for key in keys_to_clear:
if params.get(key) is None:
continue
params.remove(key)
cleared_keys.append(key)
if car_model == old_fp:
params.put("CarModel", new_fp)
car_model_name = _to_text(params.get("CarModelName")) or ""
if "2019-21" in car_model_name:
params.put("CarModelName", car_model_name.replace("2019-21", "2018-21"))
cloudlog.warning(
f"Detected legacy Bolt fingerprint {old_fp}; cleared={cleared_keys}, remapped CarModel to {new_fp}"
)
try:
LEGACY_BOLT_FP_MIGRATION_FLAG.parent.mkdir(parents=True, exist_ok=True)
LEGACY_BOLT_FP_MIGRATION_FLAG.write_text(f"{datetime.datetime.now(datetime.UTC).isoformat()}\n")
except Exception:
cloudlog.exception(f"Failed to write migration flag: {LEGACY_BOLT_FP_MIGRATION_FLAG}")
def migrate_starpilot_default_parity(params: Params, params_cache: Params) -> None:
if STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG.exists():
return
desired_bool_values = {
"AdvancedLateralTune": True,
"ForceAutoTuneOff": True,
"HumanAcceleration": False,
"HumanFollowing": False,
"NNFF": False,
"NNFFLite": False,
}
for key, value in desired_bool_values.items():
params.put_bool(key, value)
params_cache.put_bool(key, value)
params.put_float("CEModelStopTime", 7.0)
params_cache.put_float("CEModelStopTime", 7.0)
cloudlog.warning("Applied one-time StarPilot default parity migration for lateral/longitudinal toggles")
try:
STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG.parent.mkdir(parents=True, exist_ok=True)
STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG.write_text(f"{datetime.datetime.now(datetime.UTC).isoformat()}\n")
except Exception:
cloudlog.exception(f"Failed to write migration flag: {STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG}")
def _read_raw_param_bytes(params: Params, key: str | bytes):
try:
path = params.get_param_path(key)
except Exception:
return None
if not path or not os.path.isfile(path):
return None
try:
with open(path, "rb") as f:
return f.read()
except Exception:
return None
def _parse_legacy_time(raw_text: str):
text = raw_text.strip()
if not text:
return None
try:
return datetime.datetime.fromisoformat(text)
except ValueError:
pass
for fmt in ("%B %d, %Y - %I:%M%p", "%B %d, %Y - %I:%M %p"):
try:
return datetime.datetime.strptime(text, fmt)
except ValueError:
continue
return None
def migrate_param_type_canonicalization(params: Params) -> None:
if STARPILOT_PARAM_CANONICALIZATION_MIGRATION_FLAG.exists():
return
normalized_keys: list[str] = []
for raw_key in params.all_keys():
key = raw_key.decode() if isinstance(raw_key, bytes) else str(raw_key)
raw_value = _read_raw_param_bytes(params, raw_key)
if not raw_value:
continue
try:
text_value = raw_value.decode("utf-8", errors="strict").strip()
except UnicodeDecodeError:
continue
if not text_value:
continue
try:
expected_type = params.get_type(raw_key)
except Exception:
continue
try:
if expected_type == ParamKeyType.INT:
parsed = float(text_value)
# Canonicalize decimal/exponent forms into integer storage.
canonical = str(int(parsed))
if canonical != text_value:
params.put_int(raw_key, int(parsed))
normalized_keys.append(key)
elif expected_type == ParamKeyType.FLOAT:
parsed = float(text_value)
canonical = str(parsed)
if canonical != text_value:
params.put_float(raw_key, parsed)
normalized_keys.append(key)
elif expected_type == ParamKeyType.BOOL:
lowered = text_value.lower()
if lowered in ("1", "true", "yes", "on"):
if text_value != "1":
params.put_bool(raw_key, True)
normalized_keys.append(key)
elif lowered in ("0", "false", "no", "off"):
if text_value != "0":
params.put_bool(raw_key, False)
normalized_keys.append(key)
elif expected_type == ParamKeyType.TIME:
dt = _parse_legacy_time(text_value)
if dt is not None:
if dt.tzinfo is not None:
dt = dt.astimezone(datetime.UTC).replace(tzinfo=None)
if text_value != dt.isoformat():
params.put(raw_key, dt)
normalized_keys.append(key)
elif expected_type == ParamKeyType.JSON:
parsed = json.loads(text_value)
canonical = json.dumps(parsed, separators=(",", ":"))
if canonical != text_value:
params.put(raw_key, parsed)
normalized_keys.append(key)
except Exception:
continue
if normalized_keys:
cloudlog.warning(f"Canonicalized legacy param values for {len(normalized_keys)} keys")
try:
STARPILOT_PARAM_CANONICALIZATION_MIGRATION_FLAG.parent.mkdir(parents=True, exist_ok=True)
STARPILOT_PARAM_CANONICALIZATION_MIGRATION_FLAG.write_text(f"{datetime.datetime.now(datetime.UTC).isoformat()}\n")
except Exception:
cloudlog.exception(f"Failed to write migration flag: {STARPILOT_PARAM_CANONICALIZATION_MIGRATION_FLAG}")
def manager_init() -> None:
save_bootlog()
@@ -42,7 +267,14 @@ def manager_init() -> None:
params.put_bool("RecordFront", True)
# FrogPilot variables
params_cache = Params("/cache/params", return_defaults=True)
cache_params_path = "/cache/params"
if HARDWARE.get_device_type() == "pc":
cache_params_path = os.path.join(Paths.comma_home(), "cache", "params")
params_cache = Params(cache_params_path, return_defaults=True)
# 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)
# set unset params to their default value
for k in params.all_keys():
@@ -75,6 +307,10 @@ def manager_init() -> None:
params.put_bool("IsReleaseBranch", build_metadata.release_channel)
params.put("HardwareSerial", serial)
# 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)
if reg_res:
+19 -6
View File
@@ -22,8 +22,11 @@ from openpilot.common.watchdog import WATCHDOG_FN
ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None
def launcher(proc: str, name: str) -> None:
def launcher(proc: str, name: str, nice: int | None = None) -> None:
try:
if nice is not None:
os.nice(nice)
# import the process
mod = importlib.import_module(proc)
@@ -48,7 +51,10 @@ def launcher(proc: str, name: str) -> None:
raise
def nativelauncher(pargs: list[str], cwd: str, name: str) -> None:
def nativelauncher(pargs: list[str], cwd: str, name: str, nice: int | None = None) -> None:
if nice is not None:
os.nice(nice)
os.environ['MANAGER_DAEMON'] = name
# exec the process
@@ -168,7 +174,7 @@ class ManagerProcess(ABC):
class NativeProcess(ManagerProcess):
def __init__(self, name, cwd, cmdline, should_run, enabled=True, sigkill=False, watchdog_max_dt=None):
def __init__(self, name, cwd, cmdline, should_run, enabled=True, sigkill=False, watchdog_max_dt=None, nice=None):
self.name = name
self.cwd = cwd
self.cmdline = cmdline
@@ -176,6 +182,7 @@ class NativeProcess(ManagerProcess):
self.enabled = enabled
self.sigkill = sigkill
self.watchdog_max_dt = watchdog_max_dt
self.nice = nice
self.launcher = nativelauncher
def prepare(self) -> None:
@@ -191,20 +198,21 @@ class NativeProcess(ManagerProcess):
cwd = os.path.join(BASEDIR, self.cwd)
cloudlog.info(f"starting process {self.name}")
self.proc = Process(name=self.name, target=self.launcher, args=(self.cmdline, cwd, self.name))
self.proc = Process(name=self.name, target=self.launcher, args=(self.cmdline, cwd, self.name, self.nice))
self.proc.start()
self.watchdog_seen = False
self.shutting_down = False
class PythonProcess(ManagerProcess):
def __init__(self, name, module, should_run, enabled=True, sigkill=False, watchdog_max_dt=None):
def __init__(self, name, module, should_run, enabled=True, sigkill=False, watchdog_max_dt=None, nice=None):
self.name = name
self.module = module
self.should_run = should_run
self.enabled = enabled
self.sigkill = sigkill
self.watchdog_max_dt = watchdog_max_dt
self.nice = nice
self.launcher = launcher
def prepare(self) -> None:
@@ -225,7 +233,7 @@ class PythonProcess(ManagerProcess):
name = self.name if "modeld" not in self.name else "MainProcess"
cloudlog.info(f"starting python {self.module}")
self.proc = Process(name=name, target=self.launcher, args=(self.module, self.name))
self.proc = Process(name=name, target=self.launcher, args=(self.module, self.name, self.nice))
self.proc.start()
self.watchdog_seen = False
self.shutting_down = False
@@ -284,6 +292,11 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None
running = []
for p in procs:
# Reap crashed processes so they can be cleanly restarted below.
if p.proc is not None and p.proc.exitcode is not None and not p.shutting_down:
cloudlog.error(f"Process {p.name} crashed with exitcode {p.proc.exitcode}, restarting")
p.stop(retry=False)
if p.enabled and p.name not in not_run and p.should_run(started, params, CP, frogpilot_toggles):
running.append(p)
else:
+11 -7
View File
@@ -68,7 +68,7 @@ def allow_logging(started: bool, params: Params, CP: car.CarParams, frogpilot_to
return not frogpilot_toggles.no_logging
def allow_uploads(started: bool, params: Params, CP: car.CarParams, frogpilot_toggles: SimpleNamespace) -> bool:
return not frogpilot_toggles.no_uploads or frogpilot_toggles.no_onroad_uploads
return params.get_bool("AlwaysAllowUploads") or not frogpilot_toggles.no_uploads or frogpilot_toggles.no_onroad_uploads
def run_speed_limit_filler(started: bool, params: Params, CP: car.CarParams, frogpilot_toggles: SimpleNamespace) -> bool:
return frogpilot_toggles.speed_limit_filler
@@ -88,8 +88,8 @@ procs = [
PythonProcess("micd", "system.micd", iscar),
PythonProcess("timed", "system.timed", always_run, enabled=not PC),
PythonProcess("modeld", "selfdrive.modeld.modeld", only_onroad),
PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)),
PythonProcess("modeld", "frogpilot.tinygrad_modeld.tinygrad_modeld", only_onroad),
PythonProcess("dmonitoringmodeld", "frogpilot.tinygrad_modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)),
PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC),
PythonProcess("soundd", "selfdrive.ui.soundd", driverview),
@@ -127,14 +127,18 @@ procs = [
]
# FrogPilot variables
if HARDWARE.get_device_type() == "mici":
procs.append(PythonProcess("ui", "selfdrive.ui.ui", always_run))
elif TICI:
procs.append(NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=5)),
device_type = HARDWARE.get_device_type()
if device_type in ("tici", "tizi"):
procs.append(NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=5))
else:
# C4 (mici) runs the Python raylib UI path; keep watchdog parity with C3/C3X.
procs.append(PythonProcess("ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=5))
procs += [
PythonProcess("device_syncd", "frogpilot.system.device_syncd", always_run),
PythonProcess("frogpilot_process", "frogpilot.frogpilot_process", always_run),
NativeProcess("mapd", "frogpilot/navigation", ["./mapd"], always_run),
PythonProcess("the_pond", "frogpilot.system.the_pond.the_pond", always_run, nice=19),
PythonProcess("galaxy", "frogpilot.system.galaxy.galaxy", always_run, nice=19),
PythonProcess("speed_limit_filler", "frogpilot.system.speed_limit_filler", run_speed_limit_filler),
]