Files
onepilot/frogpilot/common/frogpilot_functions.py
T
firestar5683 d0e1db6766 StarPilot
2026-03-22 03:15:05 -05:00

308 lines
11 KiB
Python

#!/usr/bin/env python3
import dataclasses
import json
import requests
import threading
import time
from pathlib import Path
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.athena.registration import register
from openpilot.system.hardware import HARDWARE
from openpilot.system.version import get_build_metadata
from openpilot.frogpilot.assets.theme_manager import ThemeManager
from openpilot.frogpilot.common.frogpilot_backups import backup_frogpilot
from openpilot.frogpilot.common.frogpilot_utilities import get_frogpilot_api_info, is_FrogsGoMoo, is_url_pingable, run_cmd, use_konik_server
from openpilot.frogpilot.common.frogpilot_variables import (
ERROR_LOGS_PATH, FROGPILOT_API, FROGS_GO_MOO_PATH, HD_LOGS_PATH, KONIK_LOGS_PATH, MAPS_PATH, THEME_SAVE_PATH,
FrogPilotVariables, get_frogpilot_toggles
)
def capture_report(discord_user, report, params, frogpilot_toggles):
if not is_url_pingable(FROGPILOT_API):
return
api_token, build_metadata, device_type, dongle_id = get_frogpilot_api_info()
error_file_path = ERROR_LOGS_PATH / "error.txt"
error_content = "No error log found."
if error_file_path.exists():
error_content = error_file_path.read_text()[:1000]
payload = {
"api_token": api_token,
"build_metadata": build_metadata,
"device": device_type,
"discord_user": discord_user,
"error_content": error_content,
"frogpilot_dongle_id": dongle_id,
"frogpilot_toggles": frogpilot_toggles,
"report": report,
}
try:
response = requests.post(f"{FROGPILOT_API}/discord/report", json=payload, headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"}, timeout=30)
response.raise_for_status()
print("Successfully sent error report!")
except requests.exceptions.RequestException as exception:
print(f"Error sending report: {exception}")
def frogpilot_boot_functions(build_metadata, params):
params_memory = Params(memory=True)
maps_selected = params.get("MapsSelected")
if maps_selected:
try:
data = json.loads(maps_selected)
if isinstance(data, dict):
new_items = []
for nation in data.get("nations", []):
new_items.append(f"nation.{nation}")
for state in data.get("states", []):
new_items.append(f"us_state.{state}")
new_items.sort()
params.put("MapsSelected", ",".join(new_items))
except (json.JSONDecodeError, TypeError, ValueError):
pass
params.put("BuildMetadata", json.dumps(dataclasses.asdict(build_metadata)))
FrogPilotVariables()
ThemeManager(params, params_memory, boot_run=True).update_active_theme(time_validated=system_time_valid(), frogpilot_toggles=get_frogpilot_toggles(), boot_run=True)
if use_konik_server():
if params.get("KonikDongleId") is not None:
params.put("DongleId", params.get("KonikDongleId"))
else:
params.put("KonikDongleId", register(show_spinner=True, register_konik=True))
params.put("DongleId", params.get("KonikDongleId"))
elif params.get("DongleId") == params.get("KonikDongleId"):
params.put("DongleId", params.get("StockDongleId"))
def boot_thread():
while not system_time_valid():
print("Waiting for system time to become valid...")
time.sleep(1)
backup_frogpilot(build_metadata, params)
threading.Thread(target=boot_thread, daemon=True).start()
def install_frogpilot(build_metadata, params):
paths = [
ERROR_LOGS_PATH,
HD_LOGS_PATH,
KONIK_LOGS_PATH,
THEME_SAVE_PATH
]
for path in paths:
path.mkdir(parents=True, exist_ok=True)
register_device(build_metadata, params)
update_boot_logo(frogpilot=True, selected_logo=params.get("BootLogo"))
if build_metadata.channel == "FrogPilot-Development" and 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")
def register_device(build_metadata, params):
def register_thread():
dongle_id = params.get("DongleId")
if isinstance(dongle_id, bytes):
dongle_id = dongle_id.decode("utf-8", errors="ignore")
frogpilot_dongle_id = params.get("FrogPilotDongleId")
if isinstance(frogpilot_dongle_id, bytes):
frogpilot_dongle_id = frogpilot_dongle_id.decode("utf-8", errors="ignore")
# Keep a stable local identifier even if the remote registration endpoint
# is unavailable or slow to respond.
if dongle_id and not frogpilot_dongle_id:
params.put("FrogPilotDongleId", dongle_id)
while not is_url_pingable(FROGPILOT_API):
time.sleep(60)
payload = {
"api_token": params.get("FrogPilotApiToken"),
"build_metadata": dataclasses.asdict(build_metadata),
"device": HARDWARE.get_device_type(),
"dongle_id": dongle_id,
"frogpilot_dongle_id": params.get("FrogPilotDongleId"),
}
try:
response = requests.post(f"{FROGPILOT_API}/register", json=payload, headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"}, timeout=10)
response.raise_for_status()
data = response.json()
params.put("FrogPilotApiToken", data.get("api_token", ""))
params.put("FrogPilotDongleId", data.get("frogpilot_dongle_id"))
except Exception:
pass
threading.Thread(target=register_thread, daemon=True).start()
def uninstall_frogpilot():
update_boot_logo(stock=True)
HARDWARE.uninstall()
def update_boot_logo(frogpilot=False, stock=False, selected_logo=None):
if HARDWARE.get_device_type() == "pc":
return
boot_logo_location = Path("/usr/comma/bg.jpg")
if frogpilot:
target_logo = Path(BASEDIR) / "frogpilot/assets/other_images/frogpilot_boot_logo.jpg"
if selected_logo:
selected = selected_logo.decode("utf-8", "ignore") if isinstance(selected_logo, (bytes, bytearray)) else str(selected_logo)
selected = selected.strip().lower().replace(" ", "_")
if selected and selected not in {"stock", "default"}:
candidates = list((THEME_SAVE_PATH / "bootlogos").glob(f"{selected}.*"))
if candidates:
target_logo = candidates[0]
elif stock:
target_logo = Path(BASEDIR) / "frogpilot/assets/other_images/stock_bg.jpg"
else:
print(f'Error: Must specify either "frogpilot=True" or "stock=True"')
return
if not target_logo.is_file():
print(f"Error: Target logo file not found at {target_logo}")
return
source_logo = target_logo
staged_logo = Path("/tmp/frogpilot_boot_logo.jpg")
try:
from PIL import Image
with Image.open(target_logo) as img:
# weston.service always writes a JPEG copy of /usr/comma/bg.jpg; make sure
# the source image is already RGB JPEG to avoid startup failure on RGBA assets.
if img.format != "JPEG" or img.mode != "RGB":
img.convert("RGB").save(staged_logo, format="JPEG", quality=95)
source_logo = staged_logo
except Exception as error:
print(f"Error normalizing boot logo {target_logo}: {error}")
if target_logo.suffix.lower() not in {".jpg", ".jpeg"}:
print("Skipping boot logo update to keep weston startup stable.")
return
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")
def update_maps(now, params, params_memory, manual_update=False):
maps_selected = params.get("MapsSelected")
if not maps_selected:
return
day = now.day
is_first = day == 1
is_sunday = now.weekday() == 6
schedule = params.get("PreferredSchedule")
maps_downloaded = MAPS_PATH.exists()
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
suffix = "th" if 11 <= day <= 13 else {1: "st", 2: "nd", 3: "rd"}.get(day % 10, "th")
todays_date = now.strftime(f"%B {day}{suffix}, %Y")
if maps_downloaded and params.get("LastMapsUpdate") == todays_date and not manual_update:
return
pm = messaging.PubMaster(["mapdIn"])
sm = messaging.SubMaster(["mapdExtendedOut"])
time.sleep(1)
msg = messaging.new_message("mapdIn")
msg.mapdIn.type = 0
msg.mapdIn.str = maps_selected
pm.send("mapdIn", msg)
started = False
while True:
sm.update(1000)
if params_memory.get_bool("CancelDownloadMaps"):
msg = messaging.new_message("mapdIn")
msg.mapdIn.type = 27
pm.send("mapdIn", msg)
params_memory.remove("CancelDownloadMaps")
params_memory.remove("DownloadMaps")
return
if sm.updated["mapdExtendedOut"]:
progress = sm["mapdExtendedOut"].downloadProgress
if progress.active:
started = True
if not progress.active and started:
break
params.put("LastMapsUpdate", todays_date)
params_memory.remove("DownloadMaps")
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)
while params.get("UpdaterState") != "checking...":
time.sleep(1)
while params.get("UpdaterState") == "checking...":
time.sleep(1)
if not params.get_bool("UpdaterFetchAvailable"):
return False
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)
while not params.get_bool("UpdateAvailable"):
time.sleep(60)
return True
if params.get("UpdaterState") != "idle":
return
while params.get_bool("IsOnroad") or thread_manager.is_thread_alive("lock_doors"):
time.sleep(60)
if not update_available():
return
while True:
if not update_available():
break
HARDWARE.reboot()