diff --git a/frogpilot/common/frogpilot_functions.py b/frogpilot/common/frogpilot_functions.py index 6fe83ea69..f5192d84a 100644 --- a/frogpilot/common/frogpilot_functions.py +++ b/frogpilot/common/frogpilot_functions.py @@ -12,8 +12,9 @@ from openpilot.common.time_helpers import system_time_valid from openpilot.system.hardware import HARDWARE from openpilot.frogpilot.common.frogpilot_backups import backup_frogpilot -from openpilot.frogpilot.common.frogpilot_utilities import run_cmd +from openpilot.frogpilot.common.frogpilot_utilities import is_FrogsGoMoo, run_cmd from openpilot.frogpilot.common.frogpilot_variables import ( + FROGS_GO_MOO_PATH, FrogPilotVariables ) @@ -33,7 +34,7 @@ def frogpilot_boot_functions(build_metadata, params): threading.Thread(target=boot_thread, daemon=True).start() -def install_frogpilot(params): +def install_frogpilot(build_metadata, params): paths = [ ] for path in paths: @@ -44,6 +45,12 @@ def install_frogpilot(params): 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) diff --git a/frogpilot/common/frogpilot_utilities.py b/frogpilot/common/frogpilot_utilities.py index b19eac31f..c2de09b44 100644 --- a/frogpilot/common/frogpilot_utilities.py +++ b/frogpilot/common/frogpilot_utilities.py @@ -13,7 +13,7 @@ import openpilot.system.sentry as sentry from cereal import messaging -from openpilot.frogpilot.common.frogpilot_variables import EARTH_RADIUS +from openpilot.frogpilot.common.frogpilot_variables import EARTH_RADIUS, FROGS_GO_MOO_PATH class ThreadManager: def __init__(self): @@ -103,6 +103,11 @@ def extract_zip(zip_file, extract_path): print(f"Extraction completed!") +@cache +def is_FrogsGoMoo(): + return FROGS_GO_MOO_PATH.is_file() + + def is_url_pingable(url): if not url: return False diff --git a/frogpilot/common/frogpilot_variables.py b/frogpilot/common/frogpilot_variables.py index 2f60b7e02..ab9f19d64 100644 --- a/frogpilot/common/frogpilot_variables.py +++ b/frogpilot/common/frogpilot_variables.py @@ -62,6 +62,8 @@ BACKUP_PATH = Path("/cache/on_backup") FROGPILOT_BACKUPS = Path("/data/backups") TOGGLE_BACKUPS = Path("/data/toggle_backups") +FROGS_GO_MOO_PATH = Path("/persist/frogsgomoo.py") + MAPD_PATH = Path("/data/media/0/osm/mapd") MAPS_PATH = Path("/data/media/0/osm/offline") @@ -143,6 +145,9 @@ class FrogPilotVariables: self.testing_branch = branch == "FrogPilot-Testing" self.vetting_branch = branch == "FrogPilot-Vetting" + self.frogs_go_moo = FROGS_GO_MOO_PATH.is_file() + toggle.block_user = (self.development_branch or branch == "MAKE-PRS-HERE" or self.vetting_branch) and not self.frogs_go_moo + self.update() def get_value(self, key, cast=bool, condition=True, conversion=None, default=None, min=None, max=None): @@ -273,7 +278,7 @@ class FrogPilotVariables: toggle.always_on_lateral_main = toggle.always_on_lateral and not prohibited_main_aol and not toggle.always_on_lateral_lkas toggle.always_on_lateral_pause_speed = self.get_value("PauseAOLOnBrake", cast=float, condition=toggle.always_on_lateral) - toggle.automatic_updates = self.get_value("AutomaticUpdates", condition=(self.release_branch or self.vetting_branch), default=True) and not BACKUP_PATH.is_file() + toggle.automatic_updates = self.get_value("AutomaticUpdates", condition=(self.release_branch or self.vetting_branch or self.frogs_go_moo), default=True) and not BACKUP_PATH.is_file() car_model = self.params.get("CarModel") toggle.force_fingerprint = self.get_value("ForceFingerprint", condition=car_model != self.default_values["CarModel"]) diff --git a/frogpilot/controls/frogpilot_card.py b/frogpilot/controls/frogpilot_card.py index 07a4913df..1c50ddbc0 100644 --- a/frogpilot/controls/frogpilot_card.py +++ b/frogpilot/controls/frogpilot_card.py @@ -4,6 +4,7 @@ from openpilot.common.params import Params from openpilot.selfdrive.car.cruise import ButtonType from openpilot.selfdrive.selfdrived.events import ET +from openpilot.frogpilot.common.frogpilot_utilities import is_FrogsGoMoo from openpilot.frogpilot.common.frogpilot_variables import NON_DRIVING_GEARS class FrogPilotCard: @@ -18,6 +19,7 @@ class FrogPilotCard: self.decel_pressed = False self.always_on_lateral_set = bool(FPCP.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL) + self.frogs_go_moo = is_FrogsGoMoo() def update(self, carState, frogpilotCarState, sm, frogpilot_toggles): if self.CP.brand == "hyundai": @@ -33,7 +35,7 @@ class FrogPilotCard: self.always_on_lateral_enabled &= carState.gearShifter not in NON_DRIVING_GEARS self.always_on_lateral_enabled &= sm["frogpilotPlan"].lateralCheck self.always_on_lateral_enabled &= sm["liveCalibration"].calPerc >= 1 - self.always_on_lateral_enabled &= (ET.IMMEDIATE_DISABLE not in sm["selfdriveState"].alertType + sm["frogpilotSelfdriveState"].alertType) + self.always_on_lateral_enabled &= (ET.IMMEDIATE_DISABLE not in sm["selfdriveState"].alertType + sm["frogpilotSelfdriveState"].alertType) or self.frogs_go_moo self.always_on_lateral_enabled &= not (carState.brakePressed and carState.vEgo < frogpilot_toggles.always_on_lateral_pause_speed) or carState.standstill if sm.updated["frogpilotPlan"] or any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in carState.buttonEvents): diff --git a/frogpilot/frogpilot_process.py b/frogpilot/frogpilot_process.py index d7c43cead..f8d21b0a7 100644 --- a/frogpilot/frogpilot_process.py +++ b/frogpilot/frogpilot_process.py @@ -107,7 +107,7 @@ def frogpilot_thread(): frogpilot_toggles = update_toggles(frogpilot_variables, started, thread_manager, time_validated, params) run_update_checks |= params_memory.get_bool("ManualUpdateInitiated") - run_update_checks |= now.second == 0 and (now.minute % 60 == 0) + run_update_checks |= now.second == 0 and (now.minute % 60 == 0 or (now.minute % 5 == 0 and frogpilot_variables.frogs_go_moo)) run_update_checks &= time_validated if run_update_checks: diff --git a/frogpilot/ui/qt/widgets/frogpilot_controls.cc b/frogpilot/ui/qt/widgets/frogpilot_controls.cc index ef8c73486..ec2f6de4c 100644 --- a/frogpilot/ui/qt/widgets/frogpilot_controls.cc +++ b/frogpilot/ui/qt/widgets/frogpilot_controls.cc @@ -12,6 +12,11 @@ bool FrogPilotConfirmationDialog::yesorno(const QString &prompt_text, QWidget *p return d.exec(); } +bool isFrogsGoMoo() { + static bool is_FrogsGoMoo = QFile::exists("/persist/frogsgomoo.py"); + return is_FrogsGoMoo; +} + void clearMovie(QSharedPointer &movie, QWidget *parent) { if (!movie) { return; diff --git a/frogpilot/ui/qt/widgets/frogpilot_controls.h b/frogpilot/ui/qt/widgets/frogpilot_controls.h index 2e5cbdaa3..8f720e8c3 100644 --- a/frogpilot/ui/qt/widgets/frogpilot_controls.h +++ b/frogpilot/ui/qt/widgets/frogpilot_controls.h @@ -15,6 +15,8 @@ #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/controls.h" +bool isFrogsGoMoo(); + void loadGif(const QString &gifPath, QSharedPointer &movie, const QSize &size, QWidget *parent); void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer &movie, const QSize &size, QWidget *parent); void openDescriptions(bool forceOpenDescriptions, std::map toggles); diff --git a/opendbc_repo/opendbc/car/car_helpers.py b/opendbc_repo/opendbc/car/car_helpers.py index 3871ada0b..b75886637 100644 --- a/opendbc_repo/opendbc/car/car_helpers.py +++ b/opendbc_repo/opendbc/car/car_helpers.py @@ -162,6 +162,9 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip carlog.error({"event": "car doesn't match any fingerprints", "fingerprints": repr(fingerprints)}) candidate = "MOCK" + if frogpilot_toggles.block_user: + candidate = "MOCK" + CarInterface = interfaces[candidate] CP: CarParams = CarInterface.get_params(candidate, fingerprints, car_fw, alpha_long_allowed, is_release, docs=False, frogpilot_toggles=frogpilot_toggles) CP.carVin = vin diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 7fb045d18..cc544b2cd 100644 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -1040,6 +1040,14 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # FrogPilot variables FROGPILOT_EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { + FrogPilotEventName.blockUser: { + ET.PERMANENT: Alert( + "Don't use the 'Development' branch!", + "Forcing you into 'Dashcam Mode' for your safety...", + AlertStatus.critical, AlertSize.mid, + Priority.HIGHEST, VisualAlert.none, AudibleAlert.warningImmediate, 1.), + }, + FrogPilotEventName.customStartupAlert: { ET.PERMANENT: custom_startup_alert, }, diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index b9bb1e658..b9278465f 100644 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -4,6 +4,7 @@ import time import threading import cereal.messaging as messaging +import openpilot.system.sentry as sentry from cereal import car, custom, log from msgq.visionipc import VisionIpcClient, VisionStreamType @@ -158,6 +159,10 @@ class SelfdriveD: self.FPCP = messaging.log_from_bytes(self.params.get("FrogPilotCarParams", block=True), custom.FrogPilotCarParams) + if self.frogpilot_toggles.block_user: + self.startup_event = FrogPilotEventName.blockUser + sentry.capture_block() + def update_events(self, CS): """Compute onroadEvents from carState""" @@ -174,7 +179,7 @@ class SelfdriveD: # Add startup event if self.startup_event is not None: - if self.startup_event in (FrogPilotEventName.customStartupAlert): + if self.startup_event in (FrogPilotEventName.blockUser, FrogPilotEventName.customStartupAlert): self.frogpilot_events.add(self.startup_event) else: self.events.add(self.startup_event) diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index d7cc33365..640b05e2a 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -32,7 +32,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { // automatic updates toggle ParamControl *automaticUpdatesToggle = new ParamControl("AutomaticUpdates", tr("Automatically Update FrogPilot"), tr("Automatically update FrogPilot when the vehicle is parked with an active internet connection."), ""); - automaticUpdatesToggle->setVisible(params.getBool("IsReleaseBranch")); + automaticUpdatesToggle->setVisible(params.getBool("IsReleaseBranch") || isFrogsGoMoo()); addItem(automaticUpdatesToggle); // download update btn @@ -61,6 +61,15 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { connect(targetBranchBtn, &ButtonControl::clicked, [=]() { auto current = params.get("GitBranch"); QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(","); + if (!isFrogsGoMoo()) { + for (int i = branches.size() - 1; i >= 0; --i) { + if (branches[i].startsWith("FrogPilot-Development", Qt::CaseInsensitive)) { + branches.removeAt(i); + } + } + branches.removeAll("FrogPilot-Vetting"); + branches.removeAll("MAKE-PRS-HERE"); + } for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "nightly-dev", "master"}) { auto i = branches.indexOf(b); if (i >= 0) { @@ -128,7 +137,7 @@ void SoftwarePanel::updateLabels() { FrogPilotUIState &fs = *frogpilotUIState(); FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; - bool parked = frogpilot_scene.parked; + bool parked = frogpilot_scene.parked || isFrogsGoMoo(); // add these back in case the files got removed fs_watch->addParam("LastUpdateTime"); diff --git a/system/manager/manager.py b/system/manager/manager.py index 3a8583f3f..018752561 100644 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -104,7 +104,7 @@ def manager_init() -> None: p.prepare() # FrogPilot variables - install_frogpilot(params) + install_frogpilot(build_metadata, params) frogpilot_boot_functions(build_metadata, params) diff --git a/system/sentry.py b/system/sentry.py index df1c029a1..8303f37fb 100644 --- a/system/sentry.py +++ b/system/sentry.py @@ -29,6 +29,12 @@ def report_tombstone(fn: str, message: str, contents: str) -> None: sentry_sdk.flush() +def capture_block() -> None: + with sentry_sdk.push_scope() as scope: + sentry_sdk.capture_message("Blocked user from using the development branch", level="info") + sentry_sdk.flush() + + def capture_exception(*args, **kwargs) -> None: exc_text = traceback.format_exc()