This commit is contained in:
firestar5683
2026-07-01 13:27:44 -05:00
parent 032b2a1fde
commit 7da4359dd0
5 changed files with 245 additions and 77 deletions
+123 -33
View File
@@ -1,33 +1,91 @@
#!/usr/bin/env python3
import dataclasses
import json
import requests
import os
import threading
import time
from pathlib import Path
from types import SimpleNamespace
from cereal import messaging
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.time_helpers import system_time_valid
from openpilot.system.hardware import HARDWARE
from openpilot.system.version import get_build_metadata
from openpilot.starpilot.assets.theme_manager import ThemeManager
from openpilot.starpilot.common.starpilot_backups import backup_starpilot
from openpilot.starpilot.common.connect_server import sync_konik_dongle_id
from openpilot.starpilot.common.maps_catalog import normalize_schedule_value, sanitize_selected_locations_csv
from openpilot.starpilot.common.theme_asset_names import find_matching_theme_asset_file
from openpilot.starpilot.common.starpilot_utilities import get_starpilot_api_info, is_FrogsGoMoo, is_url_pingable, run_cmd
from openpilot.starpilot.common.starpilot_variables import (
ERROR_LOGS_PATH, STARPILOT_API, FROGS_GO_MOO_PATH, HD_LOGS_PATH, KONIK_LOGS_PATH, MAPS_PATH, THEME_SAVE_PATH,
StarPilotVariables, get_starpilot_toggles
)
STARPILOT_API = os.getenv("STARPILOT_API", "https://frogpilot.com/api")
BOOT_LOGO_STATE_PATH = Path("/cache/starpilot_boot_logo_state_v1")
def _starpilot_data_root():
if HARDWARE.get_device_type() == "pc":
from openpilot.system.hardware.hw import Paths
return Path(Paths.comma_home()) / "starpilot" / "data"
return Path("/data")
def _starpilot_persist_root():
if HARDWARE.get_device_type() == "pc":
from openpilot.system.hardware.hw import Paths
return Path(Paths.persist_root())
return Path("/persist")
def _theme_save_path():
return _starpilot_data_root() / "themes"
def _maps_path():
return _starpilot_data_root() / "media/0/osm/offline"
def _run_cmd(*args, **kwargs):
from openpilot.starpilot.common.starpilot_utilities import run_cmd
return run_cmd(*args, **kwargs)
def _boot_logo_cache_key(selected_logo, source_logo):
try:
source_stat = source_logo.stat()
source_path = source_logo.resolve()
except OSError:
return None
selected = selected_logo.decode("utf-8", "ignore") if isinstance(selected_logo, (bytes, bytearray)) else str(selected_logo or "")
return "\n".join([
selected.strip().lower(),
str(source_path),
str(source_stat.st_mtime_ns),
str(source_stat.st_size),
"",
])
def _boot_logo_cache_matches(cache_key):
if cache_key is None:
return False
try:
return BOOT_LOGO_STATE_PATH.read_text() == cache_key
except OSError:
return False
def _write_boot_logo_cache(cache_key):
if cache_key is None:
return
try:
BOOT_LOGO_STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
BOOT_LOGO_STATE_PATH.write_text(cache_key)
except OSError:
pass
def seed_desktop_theme_assets():
from openpilot.starpilot.assets.theme_manager import ThemeManager
params = Params()
params_memory = Params(memory=True)
params_defaults = Params(return_defaults=True)
@@ -59,6 +117,11 @@ def seed_desktop_theme_assets():
def starpilot_boot_functions(build_metadata, params):
from openpilot.starpilot.assets.theme_manager import ThemeManager
from openpilot.starpilot.common.connect_server import sync_konik_dongle_id
from openpilot.starpilot.common.maps_catalog import sanitize_selected_locations_csv
from openpilot.starpilot.common.starpilot_variables import StarPilotVariables
params_memory = Params(memory=True)
maps_selected_raw = params.get("MapsSelected")
@@ -70,12 +133,14 @@ def starpilot_boot_functions(build_metadata, params):
params.put("BuildMetadata", json.dumps(dataclasses.asdict(build_metadata)))
StarPilotVariables()
ThemeManager(params, params_memory, boot_run=True).update_active_theme(time_validated=system_time_valid(), starpilot_toggles=get_starpilot_toggles(), boot_run=True)
starpilot_toggles = StarPilotVariables().starpilot_toggles
ThemeManager(params, params_memory, boot_run=True).update_active_theme(time_validated=system_time_valid(), starpilot_toggles=starpilot_toggles, boot_run=True)
sync_konik_dongle_id(params)
def boot_thread():
from openpilot.starpilot.common.starpilot_backups import backup_starpilot
while not system_time_valid():
print("Waiting for system time to become valid...")
time.sleep(1)
@@ -86,12 +151,13 @@ def starpilot_boot_functions(build_metadata, params):
def install_starpilot(build_metadata, params):
data_root = _starpilot_data_root()
paths = [
ERROR_LOGS_PATH,
HD_LOGS_PATH,
KONIK_LOGS_PATH,
MAPS_PATH,
THEME_SAVE_PATH
data_root / "error_logs",
data_root / "media/0/realdata_HD",
data_root / "media/0/realdata_konik",
data_root / "media/0/osm/offline",
_theme_save_path()
]
for path in paths:
path.mkdir(parents=True, exist_ok=True)
@@ -100,15 +166,27 @@ def install_starpilot(build_metadata, params):
update_boot_logo(starpilot=True, selected_logo=params.get("BootLogo"))
if is_FrogsGoMoo():
mount_options = run_cmd(["findmnt", "-n", "-o", "OPTIONS", "/persist"], "Successfully retrieved mount options", "Failed to retrieve mount options")
run_cmd(["sudo", "mount", "-o", "remount,rw", "/persist"], "Successfully remounted /persist as read-write", "Failed to remount /persist")
run_cmd(["sudo", "python3", FROGS_GO_MOO_PATH], "Successfully ran frogsgomoo.py", "Failed to run frogsgomoo.py")
run_cmd(["sudo", "mount", "-o", f"remount,{mount_options}", "/persist"], "Successfully restored /persist mount options", "Failed to restore /persist mount options")
frogs_go_moo_path = _starpilot_persist_root() / "frogsgomoo.py"
if frogs_go_moo_path.is_file():
mount_options = _run_cmd(["findmnt", "-n", "-o", "OPTIONS", "/persist"], "Successfully retrieved mount options", "Failed to retrieve mount options")
_run_cmd(["sudo", "mount", "-o", "remount,rw", "/persist"], "Successfully remounted /persist as read-write", "Failed to remount /persist")
_run_cmd(["sudo", "python3", frogs_go_moo_path], "Successfully ran frogsgomoo.py", "Failed to run frogsgomoo.py")
_run_cmd(["sudo", "mount", "-o", f"remount,{mount_options}", "/persist"], "Successfully restored /persist mount options", "Failed to restore /persist mount options")
def register_device(build_metadata, params):
def register_thread():
import requests
def is_url_pingable(url):
if not url:
return False
try:
response = requests.head(url, timeout=10, allow_redirects=True, headers={"User-Agent": "starpilot-ping-test/1.0 (https://github.com/FrogAi/StarPilot)"})
return response.ok
except Exception:
return False
dongle_id = params.get("DongleId")
if isinstance(dongle_id, bytes):
dongle_id = dongle_id.decode("utf-8", errors="ignore")
@@ -163,7 +241,9 @@ def update_boot_logo(starpilot=False, stock=False, selected_logo=None):
selected = selected_logo.decode("utf-8", "ignore") if isinstance(selected_logo, (bytes, bytearray)) else str(selected_logo)
selected = selected.strip()
if selected.lower() not in {"", "stock", "default"}:
matched_logo = find_matching_theme_asset_file(THEME_SAVE_PATH / "bootlogos", selected)
from openpilot.starpilot.common.theme_asset_names import find_matching_theme_asset_file
matched_logo = find_matching_theme_asset_file(_theme_save_path() / "bootlogos", selected)
if matched_logo is not None:
target_logo = matched_logo
elif stock:
@@ -176,6 +256,10 @@ def update_boot_logo(starpilot=False, stock=False, selected_logo=None):
print(f"Error: Target logo file not found at {target_logo}")
return
cache_key = _boot_logo_cache_key(selected_logo, target_logo)
if boot_logo_location.is_file() and _boot_logo_cache_matches(cache_key):
return
source_logo = target_logo
staged_logo = Path("/tmp/starpilot_boot_logo.jpg")
try:
@@ -196,13 +280,18 @@ def update_boot_logo(starpilot=False, stock=False, selected_logo=None):
current_logo = boot_logo_location.read_bytes() if boot_logo_location.is_file() else b""
desired_logo = source_logo.read_bytes()
if current_logo != desired_logo:
mount_options = run_cmd(["findmnt", "-n", "-o", "OPTIONS", "/"], "Successfully retrieved mount options", "Failed to retrieve mount options")
run_cmd(["sudo", "mount", "-o", "remount,rw", "/"], "Successfully remounted / as read-write", "Failed to remount /")
run_cmd(["sudo", "cp", source_logo, boot_logo_location], "Successfully replaced boot logo", "Failed to replace boot logo")
run_cmd(["sudo", "mount", "-o", f"remount,{mount_options}", "/"], "Successfully restored / mount options", "Failed to restore / mount options")
mount_options = _run_cmd(["findmnt", "-n", "-o", "OPTIONS", "/"], "Successfully retrieved mount options", "Failed to retrieve mount options")
_run_cmd(["sudo", "mount", "-o", "remount,rw", "/"], "Successfully remounted / as read-write", "Failed to remount /")
if _run_cmd(["sudo", "cp", source_logo, boot_logo_location], "Successfully replaced boot logo", "Failed to replace boot logo") is None:
return
_run_cmd(["sudo", "mount", "-o", f"remount,{mount_options}", "/"], "Successfully restored / mount options", "Failed to restore / mount options")
_write_boot_logo_cache(cache_key)
def update_maps(now, params, params_memory, manual_update=False):
from cereal import messaging
from openpilot.starpilot.common.maps_catalog import normalize_schedule_value, sanitize_selected_locations_csv
maps_selected_raw = params.get("MapsSelected")
maps_selected = sanitize_selected_locations_csv(maps_selected_raw)
if not maps_selected:
@@ -217,7 +306,8 @@ def update_maps(now, params, params_memory, manual_update=False):
is_sunday = now.weekday() == 6
schedule = normalize_schedule_value(params.get("PreferredSchedule"))
maps_downloaded = MAPS_PATH.exists() and any(path.is_file() for path in MAPS_PATH.rglob("*"))
maps_path = _maps_path()
maps_downloaded = maps_path.exists() and any(path.is_file() for path in maps_path.rglob("*"))
if maps_downloaded and (schedule == 0 or (schedule == 1 and not is_sunday) or (schedule == 2 and not is_first)) and not manual_update:
return
@@ -265,7 +355,7 @@ def update_maps(now, params, params_memory, manual_update=False):
def update_openpilot(thread_manager, params):
def update_available():
run_cmd(["pkill", "-SIGUSR1", "-f", "system.updated.updated"], "Checking for updates...", "Failed to check for update...", report=False)
_run_cmd(["pkill", "-SIGUSR1", "-f", "system.updated.updated"], "Checking for updates...", "Failed to check for update...", report=False)
while params.get("UpdaterState") != "checking...":
time.sleep(1)
@@ -279,7 +369,7 @@ def update_openpilot(thread_manager, params):
while params.get_bool("IsOnroad") or thread_manager.is_thread_alive("lock_doors"):
time.sleep(60)
run_cmd(["pkill", "-SIGHUP", "-f", "system.updated.updated"], "Update available, downloading...", "Failed to download update...", report=False)
_run_cmd(["pkill", "-SIGHUP", "-f", "system.updated.updated"], "Update available, downloading...", "Failed to download update...", report=False)
while not params.get_bool("UpdateAvailable"):
time.sleep(60)
+6 -1
View File
@@ -316,7 +316,12 @@ def default_ev_tuning_enabled(CP):
ev_vehicle |= getattr(CP, "transmissionType", None) == car.CarParams.TransmissionType.direct
return bool(ev_vehicle)
def get_starpilot_toggles(sm=messaging.SubMaster(["starpilotPlan"])):
def get_starpilot_toggles(sm=None):
if sm is None:
if not hasattr(get_starpilot_toggles, "_sm"):
get_starpilot_toggles._sm = messaging.SubMaster(["starpilotPlan"])
sm = get_starpilot_toggles._sm
toggles_text = sm["starpilotPlan"].starpilotToggles
if toggles_text:
get_starpilot_toggles._last_toggles_text = toggles_text
+49 -16
View File
@@ -1,29 +1,57 @@
#!/usr/bin/env python3
import time
import json
import jwt
import random
import string
from typing import cast
from pathlib import Path
from datetime import datetime, timedelta, UTC
from openpilot.common.api import api_get, get_key_pair
from openpilot.common.params import Params
from openpilot.common.spinner import Spinner
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
OFFROAD_UNREGISTERED_ALERT = "Offroad_UnregisteredHardware"
KEYS = {"id_rsa": "RS256",
"id_ecdsa": "ES256"}
def api_get(*args, **kwargs):
from openpilot.common.api import api_get as api_get_impl
return api_get_impl(*args, **kwargs)
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
for key, algorithm in KEYS.items():
private_path = Path(Paths.persist_root()) / "comma" / key
public_path = Path(Paths.persist_root()) / "comma" / f"{key}.pub"
if private_path.is_file() and public_path.is_file():
return algorithm, private_path.read_text(), public_path.read_text()
return None, None, None
def _has_key_pair() -> bool:
for key in KEYS:
private_path = Path(Paths.persist_root()) / "comma" / key
public_path = Path(Paths.persist_root()) / "comma" / f"{key}.pub"
if private_path.is_file() and public_path.is_file():
return True
return False
def is_registered_device() -> bool:
dongle = Params().get("DongleId")
return dongle not in (None, UNREGISTERED_DONGLE_ID)
def _set_registration_alert(params: Params, dongle_id: str | None) -> None:
show_alert = (dongle_id == UNREGISTERED_DONGLE_ID) and not PC
if show_alert:
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
set_offroad_alert(OFFROAD_UNREGISTERED_ALERT, True)
else:
params.remove(OFFROAD_UNREGISTERED_ALERT)
def register(show_spinner=False, register_konik=False) -> str | None:
"""
All devices built since March 2024 come with all
@@ -42,13 +70,18 @@ def register(show_spinner=False, register_konik=False) -> str | None:
with open(Paths.persist_root()+"/comma/dongle_id") as f:
dongle_id = f.read().strip()
# Create registration token, in the future, this key will make JWTs directly
jwt_algo, private_key, public_key = get_key_pair()
if not public_key and not register_konik:
if not _has_key_pair() and not register_konik:
dongle_id = UNREGISTERED_DONGLE_ID
cloudlog.warning("missing public key")
elif dongle_id is None or register_konik:
import json
import jwt
from datetime import datetime, timedelta, UTC
from openpilot.common.spinner import Spinner
# Create registration token, in the future, this key will make JWTs directly
jwt_algo, private_key, public_key = get_key_pair()
if show_spinner:
spinner = Spinner()
spinner.update("registering device")
@@ -73,14 +106,14 @@ def register(show_spinner=False, register_konik=False) -> str | None:
while True:
try:
register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)},
cast(str, private_key), algorithm=jwt_algo)
private_key, algorithm=jwt_algo)
cloudlog.info("getting pilotauth")
resp = api_get("v2/pilotauth/", method='POST', timeout=15,
imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token)
if resp.status_code in (402, 403):
cloudlog.info(f"Unable to register device, got {resp.status_code}")
dongle_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=16))
dongle_id = UNREGISTERED_DONGLE_ID
else:
dongleauth = json.loads(resp.text)
dongle_id = dongleauth["dongle_id"]
@@ -99,7 +132,7 @@ def register(show_spinner=False, register_konik=False) -> str | None:
if not register_konik and dongle_id != params.get("KonikDongleId"):
params.put("DongleId", dongle_id)
params.put("StockDongleId", dongle_id)
set_offroad_alert("Offroad_UnregisteredHardware", (dongle_id == UNREGISTERED_DONGLE_ID) and not PC)
_set_registration_alert(params, dongle_id)
return dongle_id
+15 -12
View File
@@ -1,18 +1,11 @@
import errno
import fcntl
import os
import sys
import pathlib
import shutil
import signal
import subprocess
import tempfile
import threading
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
def unblock_stdout() -> None:
import errno
import fcntl
import signal
import sys
# get a non-blocking stdout
child_pid, child_pty = os.forkpty()
if child_pid != 0: # parent
@@ -51,7 +44,17 @@ def write_onroad_params(started, params):
def save_bootlog():
import threading
def fn():
import pathlib
import shutil
import subprocess
import tempfile
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
tmpdir = tempfile.mkdtemp()
env = os.environ.copy()
env['PARAMS_COPY_PATH'] = tmpdir
+52 -15
View File
@@ -2,7 +2,6 @@
import datetime
import json
import os
import shutil
from pathlib import Path
import signal
import sys
@@ -24,26 +23,18 @@ def _append_boot_timing_line(line: str) -> None:
from cereal import car, log
import cereal.messaging as messaging
from openpilot.common.utils import atomic_write
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
from openpilot.system.manager.process import ensure_running
from openpilot.system.manager.process_config import managed_processes
from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID
from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.common.swaglog import cloudlog
from openpilot.system.version import get_build_metadata, terms_version, training_version
from openpilot.system.hardware.hw import Paths
_MANAGER_CORE_IMPORT_DONE = time.monotonic()
from openpilot.starpilot.common.starpilot_functions import starpilot_boot_functions, install_starpilot, uninstall_starpilot
from openpilot.starpilot.common.starpilot_variables import (
LEGACY_STARPILOT_PARAM_RENAMES,
LEGACY_STARPILOT_STATS_KEY_RENAMES,
get_starpilot_toggles,
)
_MANAGER_IMPORT_DONE = time.monotonic()
_manager_import_timing_line = (
@@ -67,9 +58,23 @@ STARPILOT_PC_ROOT_MIGRATION_FLAG = Path("/data") / "starpilot_pc_root_v1"
STARPILOT_PARAMS_CACHE_MIGRATION_FLAG = Path("/data") / "starpilot_params_cache_v1"
STARPILOT_LEGACY_CACHE_MARKER_KEYS = ("RemapCancelToDistance",)
STARPILOT_REMOVED_PARAM_KEYS = ("HumanFollowing",)
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
POWER_WATCHDOG_PATH = "/var/tmp/power_watchdog"
LEGACY_CARMODEL_MIGRATIONS = {
"CHEVROLET_BOLT_CC_2019_2021": "CHEVROLET_BOLT_CC_2018_2021",
}
LEGACY_STARPILOT_PARAM_RENAMES = {
"FrogPilotApiToken": "StarPilotApiToken",
"FrogPilotCarParams": "StarPilotCarParams",
"FrogPilotCarParamsPersistent": "StarPilotCarParamsPersistent",
"FrogPilotDongleId": "StarPilotDongleId",
"FrogPilotStats": "StarPilotStats",
}
LEGACY_STARPILOT_STATS_KEY_RENAMES = {
"FrogPilotDrives": "StarPilotDrives",
"FrogPilotMeters": "StarPilotMeters",
"FrogPilotSeconds": "StarPilotSeconds",
}
STARPILOT_STATS_DROP_KEYS = {"CurrentMonthsKilometers", "ResetStats"}
STARPILOT_STATS_MAX_KEYS = {"LongestDistanceWithoutOverride", "MaxAcceleration"}
@@ -114,6 +119,30 @@ def _capture_manager_exception() -> None:
cloudlog.exception("failed to capture manager exception")
def _write_power_watchdog(timestamp: float) -> None:
import tempfile
tmp_file_name = None
try:
with tempfile.NamedTemporaryFile("w", dir=os.path.dirname(POWER_WATCHDOG_PATH), delete=False) as f:
tmp_file_name = f.name
f.write(str(timestamp))
os.replace(tmp_file_name, POWER_WATCHDOG_PATH)
tmp_file_name = None
finally:
if tmp_file_name is not None:
try:
os.unlink(tmp_file_name)
except FileNotFoundError:
pass
def _get_starpilot_toggles(sm=None):
from openpilot.starpilot.common.starpilot_variables import get_starpilot_toggles
return get_starpilot_toggles(sm)
def _to_text(value):
if value is None:
return None
@@ -285,6 +314,8 @@ def _copy_param_store_without_overwrite(source: Path, destination: Path) -> int:
if not source.is_dir():
return 0
import shutil
destination.mkdir(parents=True, exist_ok=True)
copied_entries = 0
for path in source.iterdir():
@@ -402,6 +433,8 @@ def _merge_tree_without_overwrite(source: Path, destination: Path) -> int:
if not source.exists():
return moved_entries
import shutil
if not destination.exists():
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(source), str(destination))
@@ -881,6 +914,8 @@ def manager_init() -> None:
last_timing = _log_boot_timing("manager_init", "version_params", manager_init_start, last_timing)
# set dongle id
from openpilot.system.athena.registration import register
reg_res = register(show_spinner=True)
if reg_res:
dongle_id = reg_res
@@ -958,7 +993,7 @@ def manager_thread() -> None:
last_timing = _log_boot_timing("manager_thread", "messaging", manager_thread_start, last_timing)
write_onroad_params(False, params)
initial_toggles = get_starpilot_toggles()
initial_toggles = _get_starpilot_toggles()
last_timing = _log_boot_timing("manager_thread", "initial_toggles", manager_thread_start, last_timing)
ensure_running(managed_processes.values(), False, params=params, CP=sm['carParams'], not_run=ignore, starpilot_toggles=initial_toggles)
last_timing = _log_boot_timing("manager_thread", "initial_ensure_running", manager_thread_start, last_timing)
@@ -974,7 +1009,7 @@ def manager_thread() -> None:
params_memory = Params(memory=True)
starpilot_toggles = get_starpilot_toggles()
starpilot_toggles = _get_starpilot_toggles()
last_timing = _log_boot_timing("manager_thread", "loop_toggles", manager_thread_start, last_timing)
_log_boot_timing("manager_thread", "loop_ready", manager_thread_start, last_timing)
@@ -1024,8 +1059,7 @@ def manager_thread() -> None:
# kick AGNOS power monitoring watchdog
try:
if sm.all_checks(['deviceState']):
with atomic_write("/var/tmp/power_watchdog", "w", overwrite=True) as f:
f.write(str(time.monotonic()))
_write_power_watchdog(time.monotonic())
except Exception:
pass
@@ -1048,7 +1082,7 @@ def manager_thread() -> None:
break
# StarPilot variables
starpilot_toggles = get_starpilot_toggles(sm)
starpilot_toggles = _get_starpilot_toggles(sm)
def main() -> None:
@@ -1087,6 +1121,9 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("got CTRL-C, exiting")
except Exception:
from openpilot.common.swaglog import add_file_handler
from openpilot.common.text_window import TextWindow
add_file_handler(cloudlog)
cloudlog.exception("Manager failed to start")