Files
sunnypilot/system/manager/manager.py
T
DevTekVE 1bd2a2f007 add back the dirinvg model even if dupped. it's not responisibility of this mr
add back the dirinvg model even if dupped. it's not responisibility of this mr

Refactor Sunnylink's operation and default state handling

This update refines the operation and handling of the Sunnylink feature, focusing on its management within the launch script and the logic for its default state.

Starting with the default state, the 'SunnylinkEnabled' parameter is no longer hard-coded to 0. Instead, it now depends on the presence of a release_channel or release_sp_channel. Furthermore, Sunnylink is set to be active in all non-release builds by default.

Regarding the launch script, obsolete invocations of Sunnylink are avoided. This procedure actively checks for Sunnylink registration status before initiating the related processes, effectively conserving system resources. Also, this modifies the launching order of the mapd installer and manager.

As for the functionality of Sunnylink, crucial improvements are brought into effect:
- The status display post Sunnylink registration is made more accurate and informative, with the inclusion of a progress color.
- The handling of Sunnylink capability is overhauled, ensuring that if Sunnylink feature is disabled, a clear warning is logged and the daemon process shuts down correctly.
- System exit conditions now accommodate the Sunnylink registration status to ensure clean termination.
- The start of Sunnylink Uploader is now strictly dependent on the validation of Sunnylink registration.
- This optimization is also incorporated into the 'manage_athenad' method which now gracefully handles any potential null values for Sunnylink Dongle ID.

This comprehensive update aims to enhance the transparency, functionality, and efficient operation of the Sunnylink feature.
2024-06-20 08:16:59 +02:00

324 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
import datetime
import os
import signal
import sys
import traceback
from cereal import custom
import cereal.messaging as messaging
import openpilot.system.sentry as sentry
from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID
from openpilot.common.params import Params, ParamKeyType
from openpilot.common.text_window import TextWindow
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog
from openpilot.system.manager.mapd_installer import VERSION
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, is_registered_device
from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.system.hardware.hw import Paths
from openpilot.system.version import get_build_metadata, terms_version, terms_version_sp, training_version
def manager_init() -> None:
save_bootlog()
build_metadata = get_build_metadata()
params = 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 build_metadata.release_channel:
params.clear_all(ParamKeyType.DEVELOPMENT_ONLY)
default_params: list[tuple[str, str | bytes]] = [
("CompletedTrainingVersion", "0"),
("DisengageOnAccelerator", "0"),
("GsmMetered", "1"),
("HasAcceptedTerms", "0"),
("LanguageSetting", "main_en"),
("OpenpilotEnabledToggle", "1"),
("LongitudinalPersonality", str(custom.LongitudinalPersonalitySP.standard)),
("AccMadsCombo", "1"),
("AutoLaneChangeTimer", "0"),
("AutoLaneChangeBsmDelay", "1"),
("BelowSpeedPause", "0"),
("BrakeLights", "0"),
("BrightnessControl", "0"),
("CustomTorqueLateral", "0"),
("CameraControl", "2"),
("CameraControlToggle", "0"),
("CameraOffset", "4"),
("CarModel", ""),
("CarModelText", ""),
("ChevronInfo", "1"),
("MadsCruiseMain", "1"),
("CustomBootScreen", "0"),
("CustomOffsets", "0"),
("DevUIInfo", "0"),
("DisableOnroadUploads", "0"),
("DisengageLateralOnBrake", "0"),
("DrivingModelGeneration", "0"),
("DynamicLaneProfile", "1"),
("EnableMads", "1"),
("EnhancedScc", "0"),
("FeatureStatus", "1"),
("HandsOnWheelMonitoring", "0"),
("HasAcceptedTermsSP", "0"),
("HideVEgoUi", "0"),
("LastSpeedLimitSignTap", "0"),
("LkasToggle", "0"),
("MadsIconToggle", "1"),
("MapdVersion", f"{VERSION}"),
("MaxTimeOffroad", "9"),
("NNFF", "0"),
("OnroadScreenOff", "-2"),
("OnroadScreenOffBrightness", "50"),
("OnroadScreenOffEvent", "1"),
("OnroadSettings", "1"),
("PathOffset", "0"),
("PauseLateralSpeed", "0"),
("ReverseAccChange", "0"),
("ScreenRecorder", "1"),
("ShowDebugUI", "1"),
("SpeedLimitControlPolicy", "3"),
("SpeedLimitEngageType", "0"),
("SpeedLimitValueOffset", "0"),
("SpeedLimitOffsetType", "0"),
("SpeedLimitWarningType", "0"),
("SpeedLimitWarningValueOffset", "0"),
("SpeedLimitWarningOffsetType", "0"),
("StandStillTimer", "0"),
("StockLongToyota", "0"),
("TorqueDeadzoneDeg", "0"),
("TorqueFriction", "1"),
("TorqueMaxLatAccel", "250"),
("TrueVEgoUi", "0"),
("TurnSpeedControl", "0"),
("TurnVisionControl", "0"),
("VisionCurveLaneless", "0"),
("VwAccType", "0"),
("OsmDbUpdatesCheck", "0"),
("OsmDownloadedDate", "0"),
("OSMDownloadProgress", "{}"),
("SidebarTemperatureOptions", "0"),
("SunnylinkEnabled", "0" if (build_metadata.release_channel or build_metadata.release_sp_channel) else "1"),
("SunnylinkDongleId", f"{UNREGISTERED_SUNNYLINK_DONGLE_ID}"),
("CustomDrivingModel", "0"),
("DrivingModelGeneration", "4"),
("LastSunnylinkPingTime", "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:
params.put(k, v)
# parameters set by Environment Variables
if os.getenv("HANDSMONITORING") is not None:
params.put_bool("HandsOnWheelMonitoring", bool(int(os.getenv("HANDSMONITORING", "0"))))
# 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", build_metadata.openpilot.version)
params.put("TermsVersion", terms_version)
params.put("TermsVersionSunnypilot", terms_version_sp)
params.put("TrainingVersion", training_version)
params.put("GitCommit", build_metadata.openpilot.git_commit)
params.put("GitCommitDate", build_metadata.openpilot.git_commit_date)
params.put("GitBranch", build_metadata.channel)
params.put("GitRemote", build_metadata.openpilot.git_origin)
params.put_bool("IsTestedBranch", build_metadata.tested_channel)
params.put_bool("IsReleaseBranch", build_metadata.release_channel)
params.put_bool("IsReleaseSPBranch", build_metadata.release_sp_channel)
# 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}")
if params.get("HardwareSerial") is None:
try:
serial = HARDWARE.get_serial()
params.put("HardwareSerial", serial)
except Exception:
cloudlog.exception("Error getting serial for device")
os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog
os.environ['GIT_ORIGIN'] = build_metadata.openpilot.git_normalized_origin # Needed for swaglog
os.environ['GIT_BRANCH'] = build_metadata.channel # Needed for swaglog
os.environ['GIT_COMMIT'] = build_metadata.openpilot.git_commit # Needed for swaglog
if not build_metadata.openpilot.is_dirty:
os.environ['CLEAN'] = '1'
# init logging
sentry.init(sentry.SentryProject.SELFDRIVE)
cloudlog.bind_global(dongle_id=dongle_id,
version=build_metadata.openpilot.version,
origin=build_metadata.openpilot.git_normalized_origin,
branch=build_metadata.channel,
commit=build_metadata.openpilot.git_commit,
dirty=build_metadata.openpilot.is_dirty,
device=HARDWARE.get_device_type())
if os.path.isfile(os.path.join(sentry.CRASHES_DIR, 'error.txt')):
os.remove(os.path.join(sentry.CRASHES_DIR, 'error.txt'))
models_dir = Paths.model_root()
if not os.path.exists(models_dir):
os.makedirs(models_dir)
# 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 manager_thread() -> None:
cloudlog.bind(daemon="manager")
cloudlog.info("manager start")
cloudlog.info({"environ": os.environ})
params = Params()
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]
if params.get("DriverCameraHardwareMissing") and not is_registered_device():
ignore += ["dmonitoringd", "dmonitoringmodeld"]
sm = messaging.SubMaster(['deviceState', 'carParams'], poll='deviceState')
pm = messaging.PubMaster(['managerState'])
write_onroad_params(False, params)
ensure_running(managed_processes.values(), False, params=params, 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 pandad's safety setter thread
if started != started_prev:
write_onroad_params(started, params)
started_prev = started
ensure_running(managed_processes.values(), started, params=params, CP=sm['carParams'], not_run=ignore)
running = ' '.join("{}{}\u001b[0m".format("\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
def main() -> None:
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)