#!/usr/bin/env python3 import io import json import random import requests import string 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.frogpilot.assets.theme_manager import ThemeManager from openpilot.frogpilot.common.frogpilot_backups import backup_frogpilot from openpilot.frogpilot.common.frogpilot_utilities import is_FrogsGoMoo, run_cmd, use_konik_server from openpilot.frogpilot.common.frogpilot_variables import ( DISCORD_WEBHOOK_URL_REPORT, ERROR_LOGS_PATH, 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, frogpilot_toggles): if not DISCORD_WEBHOOK_URL_REPORT: return 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] message = ( f"**🚨 New Error Report**\n\n" f"**User:** `{discord_user}`\n\n" f"**Report:**\n```{report}```\n\n" f"**Error Log:**\n```{error_content}```\n\n" f"**Toggle Settings:**" ) try: main_response = requests.post( DISCORD_WEBHOOK_URL_REPORT, data={"content": message}, files={"file": ("frogpilot_toggles.json", io.BytesIO(json.dumps(frogpilot_toggles, indent=2).encode("utf-8")), "application/json")}, timeout=10 ) main_response.raise_for_status() mention_response = requests.post( DISCORD_WEBHOOK_URL_REPORT, json={"content": "<@&1198482895342411846>"}, timeout=10 ) mention_response.raise_for_status() except requests.exceptions.RequestException as exception: print(f"Error sending Discord message: {exception}") except Exception as exception: print(f"Unexpected error: {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 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) if params.get("FrogPilotDongleId") is None: params.put("FrogPilotDongleId", "".join(random.choices(string.ascii_lowercase + string.digits, k=16))) update_boot_logo(frogpilot=True) 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 uninstall_frogpilot(): update_boot_logo(stock=True) HARDWARE.uninstall() def update_boot_logo(frogpilot=False, stock=False): boot_logo_location = Path("/usr/comma/bg.jpg") if frogpilot: target_logo = Path(BASEDIR) / "frogpilot/assets/other_images/frogpilot_boot_logo.jpg" 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 if boot_logo_location.read_bytes() != target_logo.read_bytes(): 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", target_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()