#!/usr/bin/env python3 import datetime import os import signal import subprocess import sys import threading import traceback from pathlib import Path from typing import List, Tuple, Union from cereal import log import cereal.messaging as messaging import openpilot.selfdrive.sentry as sentry from openpilot.common.params import Params, ParamKeyType from openpilot.common.text_window import TextWindow from openpilot.system.hardware import HARDWARE, PC from openpilot.selfdrive.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog from openpilot.selfdrive.manager.process import ensure_running from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from openpilot.common.swaglog import cloudlog, add_file_handler from openpilot.system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ get_normalized_origin, terms_version, training_version, \ is_tested_branch, is_release_branch, get_commit_date def manager_init() -> None: save_bootlog() params = Params() params_storage = Params("/persist/comma/params") params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION) params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION) if is_release_branch(): params.clear_all(ParamKeyType.DEVELOPMENT_ONLY) ############### Remove this after the April 26th update ############### previous_speed_limit = params.get_float("PreviousSpeedLimit") if previous_speed_limit >= 50: params.put_float("PreviousSpeedLimit", previous_speed_limit / 100) for priority_key in ["SLCPriority", "SLCPriority1", "SLCPriority2", "SLCPriority3"]: priority_value = params.get(priority_key) if isinstance(priority_value, int): params.remove(priority_key) attributes = ["AggressiveFollow", "StandardFollow", "RelaxedFollow", "AggressiveJerk", "StandardJerk", "RelaxedJerk"] values = {attr: params.get_float(attr) for attr in attributes} if any(value > 5 for value in values.values()): for attr, value in values.items(): params.put_float(attr, value / 10) if params.get_bool("SilentMode"): attributes = ["DisengageVolume", "EngageVolume", "PromptVolume", "PromptDistractedVolume", "RefuseVolume", "WarningSoftVolume", "WarningImmediateVolume"] for attr in attributes: params.put_float(attr, 0) params.put_bool("SilentMode", False) ####################################################################### default_params: List[Tuple[str, Union[str, bytes]]] = [ ("CompletedTrainingVersion", "0"), ("DisengageOnAccelerator", "0"), ("GsmMetered", "1"), ("HasAcceptedTerms", "0"), ("LanguageSetting", "main_en"), ("OpenpilotEnabledToggle", "1"), ("UpdaterAvailableBranches", ""), ("LongitudinalPersonality", str(log.LongitudinalPersonality.standard)), # Default FrogPilot parameters ("AccelerationPath", "1"), ("AccelerationProfile", "2"), ("AdjacentPath", "0"), ("AdjacentPathMetrics", "0"), ("AdjustablePersonalities", "1"), ("AggressiveAcceleration", "1"), ("AggressiveFollow", "1.25"), ("AggressiveJerk", "0.5"), ("AlertVolumeControl", "0"), ("AlwaysOnLateral", "1"), ("AlwaysOnLateralMain", "0"), ("BlindSpotPath", "1"), ("CameraView", "1"), ("CarMake", ""), ("CarModel", ""), ("CECurves", "1"), ("CENavigation", "1"), ("CENavigationIntersections", "1"), ("CENavigationLead", "1"), ("CENavigationTurns", "1"), ("CESignal", "1"), ("CESlowerLead", "0"), ("CESpeed", "0"), ("CESpeedLead", "0"), ("CEStopLights", "1"), ("CEStopLightsLead", "0"), ("Compass", "0"), ("ConditionalExperimental", "1"), ("CrosstrekTorque", "1"), ("CurveSensitivity", "100"), ("CustomAlerts", "1"), ("CustomColors", "1"), ("CustomIcons", "1"), ("CustomPersonalities", "1"), ("CustomSignals", "1"), ("CustomSounds", "1"), ("CustomTheme", "1"), ("CustomUI", "1"), ("CydiaTune", "0"), ("DecelerationProfile", "1"), ("DeviceShutdown", "9"), ("DisableMTSCSmoothing", "0"), ("DisableOnroadUploads", "0"), ("DisableOpenpilotLongitudinal", "0"), ("DisableVTSCSmoothing", "0"), ("DisengageVolume", "100"), ("DragonPilotTune", "0"), ("DriverCamera", "0"), ("DriveStats", "1"), ("DynamicPathWidth", "0"), ("EngageVolume", "100"), ("EVTable", "1"), ("ExperimentalModeActivation", "1"), ("ExperimentalModeViaDistance", "0"), ("ExperimentalModeViaLKAS", "0"), ("ExperimentalModeViaScreen", "1"), ("Fahrenheit", "0"), ("FireTheBabysitter", "0"), ("ForceAutoTune", "0"), ("ForceFingerprint", "0"), ("ForceMPHDashboard", "0"), ("FPSCounter", "0"), ("FrogPilotDrives", "0"), ("FrogPilotKilometers", "0"), ("FrogPilotMinutes", "0"), ("FrogsGoMooTune", "1"), ("FullMap", "0"), ("GasRegenCmd", "0"), ("GoatScream", "1"), ("GreenLightAlert", "0"), ("HideAlerts", "0"), ("HideAOLStatusBar", "0"), ("HideCEMStatusBar", "0"), ("HideLeadMarker", "0"), ("HideMapIcon", "0"), ("HideMaxSpeed", "0"), ("HideSpeed", "0"), ("HideSpeedUI", "0"), ("HideUIElements", "0"), ("HigherBitrate", "0"), ("LaneChangeTime", "0"), ("LaneDetection", "1"), ("LaneDetectionWidth", "60"), ("LaneLinesWidth", "4"), ("LateralTune", "1"), ("LeadDepartingAlert", "0"), ("LeadDetectionThreshold", "25"), ("LeadInfo", "0"), ("LockDoors", "0"), ("LongitudinalTune", "1"), ("LongPitch", "1"), ("LoudBlindspotAlert", "0"), ("LowerVolt", "1"), ("MapsSelected", ""), ("MapStyle", "0"), ("MTSCAggressiveness", "100"), ("MTSCCurvatureCheck", "0"), ("MTSCLimit", "0"), ("Model", DEFAULT_MODEL), ("ModelUI", "1"), ("MTSCEnabled", "1"), ("MuteOverheated", "0"), ("NavChill", "0"), ("NNFF", "1"), ("NoLogging", "0"), ("NoUploads", "0"), ("NudgelessLaneChange", "1"), ("NumericalTemp", "0"), ("OfflineMode", "0"), ("Offset1", "5"), ("Offset2", "5"), ("Offset3", "5"), ("Offset4", "10"), ("OneLaneChange", "1"), ("PathEdgeWidth", "20"), ("PathWidth", "61"), ("PauseLateralOnSignal", "0"), ("PedalsOnUI", "1"), ("PersonalitiesViaScreen", "1"), ("PersonalitiesViaWheel", "1"), ("PreferredSchedule", "0"), ("PromptVolume", "100"), ("PromptDistractedVolume", "100"), ("QOLControls", "1"), ("QOLVisuals", "1"), ("RandomEvents", "0"), ("RefuseVolume", "100"), ("RelaxedFollow", "1.75"), ("RelaxedJerk", "1.0"), ("ReverseCruise", "0"), ("ReverseCruiseUI", "1"), ("RoadEdgesWidth", "2"), ("RoadNameUI", "1"), ("RotatingWheel", "1"), ("ScreenBrightness", "101"), ("ScreenBrightnessOnroad", "101"), ("ScreenManagement", "1"), ("ScreenRecorder", "1"), ("ScreenTimeout", "30"), ("ScreenTimeoutOnroad", "30"), ("SearchInput", "0"), ("SetSpeedLimit", "0"), ("SetSpeedOffset", "0"), ("ShowCPU", "0"), ("ShowGPU", "0"), ("ShowIP", "0"), ("ShowMemoryUsage", "0"), ("ShowSLCOffset", "0"), ("ShowSLCOffsetUI", "1"), ("ShowStorageLeft", "0"), ("ShowStorageUsed", "0"), ("Sidebar", "0"), ("SLCConfirmation", "1"), ("SLCConfirmationLower", "1"), ("SLCConfirmationHigher", "1"), ("SLCFallback", "2"), ("SLCOverride", "1"), ("SLCPriority1", "Dashboard"), ("SLCPriority2", "Offline Maps"), ("SLCPriority3", "Navigation"), ("SmoothBraking", "1"), ("SNGHack", "1"), ("SpeedLimitChangedAlert", "1"), ("SpeedLimitController", "1"), ("StandardFollow", "1.45"), ("StandardJerk", "1.0"), ("StandbyMode", "0"), ("SteerRatio", "0"), ("StockTune", "0"), ("StoppingDistance", "0"), ("TurnAggressiveness", "100"), ("TurnDesires", "0"), ("UnlimitedLength", "1"), ("UpdateSchedule", "0"), ("UseLateralJerk", "0"), ("UseSI", "0"), ("UseVienna", "0"), ("VisionTurnControl", "1"), ("WarningSoftVolume", "100"), ("WarningImmediateVolume", "100"), ("WheelIcon", "3"), ("WheelSpeed", "0") ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.utcnow().isoformat().encode('utf8'))) if params.get_bool("RecordFrontLock"): params.put_bool("RecordFront", True) # set unset params for k, v in default_params: if params.get(k) is None: if params_storage.get(k) is None: params.put(k, v) else: params.put(k, params_storage.get(k)) else: params_storage.put(k, params.get(k)) # Create folders needed for msgq try: os.mkdir("/dev/shm") except FileExistsError: pass except PermissionError: print("WARNING: failed to make /dev/shm") # set version params params.put("Version", get_version()) params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) params.put("GitCommit", get_commit()) params.put("GitCommitDate", get_commit_date()) params.put("GitBranch", get_short_branch()) params.put("GitRemote", get_origin()) params.put_bool("IsTestedBranch", is_tested_branch()) params.put_bool("IsReleaseBranch", is_release_branch()) # set dongle id reg_res = register(show_spinner=True) if reg_res: dongle_id = reg_res else: serial = params.get("HardwareSerial") raise Exception(f"Registration failed for device {serial}") os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog os.environ['GIT_ORIGIN'] = get_normalized_origin() # Needed for swaglog os.environ['GIT_BRANCH'] = get_short_branch() # Needed for swaglog os.environ['GIT_COMMIT'] = get_commit() # Needed for swaglog if not is_dirty(): os.environ['CLEAN'] = '1' # init logging sentry.init(sentry.SentryProject.SELFDRIVE) cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), origin=get_normalized_origin(), branch=get_short_branch(), commit=get_commit(), dirty=is_dirty(), device=HARDWARE.get_device_type()) # preimport all processes for p in managed_processes.values(): p.prepare() def manager_cleanup() -> None: # send signals to kill all procs for p in managed_processes.values(): p.stop(block=False) # ensure all are killed for p in managed_processes.values(): p.stop(block=True) cloudlog.info("everything is dead") def update_frogpilot_params(params, params_memory): keys = [] for key in keys: params_memory.put_bool(key, params.get_bool(key)) def manager_thread() -> None: cloudlog.bind(daemon="manager") cloudlog.info("manager start") cloudlog.info({"environ": os.environ}) params = Params() params_memory = Params("/dev/shm/params") params_storage = Params("/persist/comma/params") update_frogpilot_params(params, params_memory) ignore: List[str] = [] if params.get("DongleId", encoding='utf8') in (None, UNREGISTERED_DONGLE_ID): ignore += ["manage_athenad", "uploader"] if os.getenv("NOBOARD") is not None: ignore.append("pandad") ignore += [x for x in os.getenv("BLOCK", "").split(",") if len(x) > 0] sm = messaging.SubMaster(['deviceState', 'carParams'], poll='deviceState') pm = messaging.PubMaster(['managerState']) write_onroad_params(False, params) ensure_running(managed_processes.values(), False, params=params, params_memory=params_memory, CP=sm['carParams'], not_run=ignore) started_prev = False while True: sm.update(1000) started = sm['deviceState'].started if started and not started_prev: params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION) elif not started and started_prev: params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION) # update onroad params, which drives boardd's safety setter thread if started != started_prev: write_onroad_params(started, params) started_prev = started ensure_running(managed_processes.values(), started, params=params, params_memory=params_memory, CP=sm['carParams'], not_run=ignore) running = ' '.join("%s%s\u001b[0m" % ("\u001b[32m" if p.proc.is_alive() else "\u001b[31m", p.name) for p in managed_processes.values() if p.proc) print(running) cloudlog.debug(running) # send managerState msg = messaging.new_message('managerState', valid=True) msg.managerState.processes = [p.get_process_state_msg() for p in managed_processes.values()] pm.send('managerState', msg) # Exit main loop when uninstall/shutdown/reboot is needed shutdown = False for param in ("DoUninstall", "DoShutdown", "DoReboot"): if params.get_bool(param): shutdown = True params.put("LastManagerExitReason", f"{param} {datetime.datetime.now()}") cloudlog.warning(f"Shutting down manager - {param} set") if shutdown: break if params_memory.get_bool("FrogPilotTogglesUpdated"): update_frogpilot_params(params, params_memory) def backup_openpilot(): # Configure the auto backup generator backup_dir_path = '/data/backups' Path(backup_dir_path).mkdir(parents=True, exist_ok=True) # Sort backups by creation time and only keep the 5 newest auto generated ones auto_backups = sorted([f for f in os.listdir(backup_dir_path) if f.endswith("_auto")], key=lambda x: os.path.getmtime(os.path.join(backup_dir_path, x))) for old_backup in auto_backups: subprocess.run(['sudo', 'rm', '-rf', os.path.join(backup_dir_path, old_backup)], check=True) print(f"Deleted oldest backup to maintain limit: {old_backup}") # Generate the backup folder name from the current git commit and branch name branch = get_short_branch() commit = get_commit_date()[12:-16] backup_folder_name = f"{branch}_{commit}_auto" # Check if the backup folder already exists backup_path = os.path.join(backup_dir_path, backup_folder_name) if os.path.exists(backup_path): print(f"Backup folder {backup_folder_name} already exists. Skipping backup.") return # Create the backup directory and copy openpilot to it Path(backup_path).mkdir(parents=True, exist_ok=True) subprocess.run(['sudo', 'cp', '-a', '/data/openpilot/.', backup_path + '/'], check=True) print(f"Successfully backed up openpilot to {backup_folder_name}.") def main() -> None: # Create the long term param storage folder try: # Attempt to remount /persist as read-write subprocess.run(['sudo', 'mount', '-o', 'remount,rw', '/persist'], check=True) print("Successfully remounted /persist as read-write.") except subprocess.CalledProcessError as e: print(f"Failed to remount /persist. Error: {e}") # Backup the current version of openpilot try: backup_thread = threading.Thread(target=backup_openpilot) backup_thread.start() except subprocess.CalledProcessError as e: print(f"Failed to backup openpilot. Error: {e}") manager_init() if os.getenv("PREPAREONLY") is not None: return # SystemExit on sigterm signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit(1)) try: manager_thread() except Exception: traceback.print_exc() sentry.capture_exception() finally: manager_cleanup() params = Params() if params.get_bool("DoUninstall"): cloudlog.warning("uninstalling") HARDWARE.uninstall() elif params.get_bool("DoReboot"): cloudlog.warning("reboot") HARDWARE.reboot() elif params.get_bool("DoShutdown"): cloudlog.warning("shutdown") HARDWARE.shutdown() if __name__ == "__main__": unblock_stdout() try: main() except KeyboardInterrupt: print("got CTRL-C, exiting") except Exception: add_file_handler(cloudlog) cloudlog.exception("Manager failed to start") try: managed_processes['ui'].stop() except Exception: pass # Show last 3 lines of traceback error = traceback.format_exc(-3) error = "Manager failed to start\n\n" + error with TextWindow(error) as t: t.wait_for_exit() raise # manual exit because we are forked sys.exit(0)