diff --git a/common/params.cc b/common/params.cc index 6936f0d86..84d837a9c 100644 --- a/common/params.cc +++ b/common/params.cc @@ -247,6 +247,8 @@ std::unordered_map keys = { {"CustomTheme", PERSISTENT}, {"CydiaTune", PERSISTENT}, {"DecelerationProfile", PERSISTENT}, + {"DeviceManagement", PERSISTENT}, + {"DeviceShutdown", PERSISTENT}, {"DisengageVolume", PERSISTENT}, {"DynamicPathWidth", PERSISTENT}, {"EngageVolume", PERSISTENT}, @@ -256,11 +258,16 @@ std::unordered_map keys = { {"HideAOLStatusBar", PERSISTENT}, {"HideCEMStatusBar", PERSISTENT}, {"HideLeadMarker", PERSISTENT}, + {"IncreaseThermalLimits", PERSISTENT}, {"LaneLinesWidth", PERSISTENT}, {"LateralTune", PERSISTENT}, {"LongitudinalTune", PERSISTENT}, + {"LowVoltageShutdown", PERSISTENT}, {"ManualUpdateInitiated", PERSISTENT}, {"ModelUI", PERSISTENT}, + {"NoLogging", PERSISTENT}, + {"NoUploads", PERSISTENT}, + {"OfflineMode", PERSISTENT}, {"PathEdgeWidth", PERSISTENT}, {"PathWidth", PERSISTENT}, {"PauseAOLOnBrake", PERSISTENT}, diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index edd133e66..db2ea1b21 100644 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -245,7 +245,8 @@ class Controls: # Create events for temperature, disk space, and memory if self.sm['deviceState'].thermalStatus >= ThermalStatus.red: - self.events.add(EventName.overheat) + if not self.increase_thermal_limits or self.sm['deviceState'].thermalStatus == ThermalStatus.danger: + self.events.add(EventName.overheat) if self.sm['deviceState'].freeSpacePercent < 7 and not SIMULATION: # under 7% of space free no enable allowed self.events.add(EventName.outOfSpace) @@ -922,6 +923,9 @@ class Controls: frog_sounds = custom_sounds == 1 self.goat_scream = frog_sounds and self.params.get_bool("GoatScream") + device_management = self.params.get_bool("DeviceManagement") + self.increase_thermal_limits = device_management and self.params.get_bool("IncreaseThermalLimits") + lateral_tune = self.params.get_bool("LateralTune") longitudinal_tune = self.CP.openpilotLongitudinalControl and self.params.get_bool("LongitudinalTune") diff --git a/selfdrive/frogpilot/assets/other_images/icon_wifi_uploading_disabled.svg b/selfdrive/frogpilot/assets/other_images/icon_wifi_uploading_disabled.svg new file mode 100644 index 000000000..156052556 --- /dev/null +++ b/selfdrive/frogpilot/assets/other_images/icon_wifi_uploading_disabled.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/selfdrive/frogpilot/assets/toggle_icons/icon_device.png b/selfdrive/frogpilot/assets/toggle_icons/icon_device.png new file mode 100644 index 000000000..e4f44076f Binary files /dev/null and b/selfdrive/frogpilot/assets/toggle_icons/icon_device.png differ diff --git a/selfdrive/frogpilot/ui/qt/offroad/control_settings.cc b/selfdrive/frogpilot/ui/qt/offroad/control_settings.cc index 3f64fec4d..9beb4ffc7 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/control_settings.cc +++ b/selfdrive/frogpilot/ui/qt/offroad/control_settings.cc @@ -20,6 +20,14 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil {"CustomPersonalities", tr("Custom Driving Personalities"), tr("Customize the driving personality profiles to your driving style."), "../frogpilot/assets/toggle_icons/icon_custom.png"}, + {"DeviceManagement", tr("Device Management"), tr("Tweak your device's behaviors to your personal preferences."), "../frogpilot/assets/toggle_icons/icon_device.png"}, + {"DeviceShutdown", tr("Device Shutdown Timer"), tr("Configure how quickly the device shuts down after going offroad."), ""}, + {"NoLogging", tr("Disable Logging"), tr("Turn off all data tracking to enhance privacy or reduce thermal load."), ""}, + {"NoUploads", tr("Disable Uploads"), tr("Turn off all data uploads to comma's servers."), ""}, + {"IncreaseThermalLimits", tr("Increase Thermal Safety Limit"), tr("Allow the device to run at a temperature above comma's recommended thermal limits."), ""}, + {"LowVoltageShutdown", tr("Low Voltage Shutdown Threshold"), tr("Automatically shut the device down when your battery reaches a specific voltage level to prevent killing your battery."), ""}, + {"OfflineMode", tr("Offline Mode"), tr("Allow the device to be offline indefinitely."), ""}, + {"LateralTune", tr("Lateral Tuning"), tr("Modify openpilot's steering behavior."), "../frogpilot/assets/toggle_icons/icon_lateral_tune.png"}, {"LongitudinalTune", tr("Longitudinal Tuning"), tr("Modify openpilot's acceleration and braking behavior."), "../frogpilot/assets/toggle_icons/icon_longitudinal_tune.png"}, @@ -135,6 +143,24 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil relaxedProfile = new FrogPilotDualParamControl(relaxedFollow, relaxedJerk, this, true); addItem(relaxedProfile); + } else if (param == "DeviceManagement") { + FrogPilotParamManageControl *deviceManagementToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); + QObject::connect(deviceManagementToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { + toggle->setVisible(deviceManagementKeys.find(key.c_str()) != deviceManagementKeys.end()); + } + }); + toggle = deviceManagementToggle; + } else if (param == "DeviceShutdown") { + std::map shutdownLabels; + for (int i = 0; i <= 33; ++i) { + shutdownLabels[i] = i == 0 ? tr("5 mins") : i <= 3 ? QString::number(i * 15) + tr(" mins") : QString::number(i - 3) + (i == 4 ? tr(" hour") : tr(" hours")); + } + toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 33, shutdownLabels, this, false); + } else if (param == "LowVoltageShutdown") { + toggle = new FrogPilotParamValueControl(param, title, desc, icon, 11.8, 12.5, std::map(), this, false, tr(" volts"), 1, 0.01); + } else if (param == "LateralTune") { FrogPilotParamManageControl *lateralTuneToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(lateralTuneToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { @@ -218,6 +244,30 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } }); + QObject::connect(toggles["IncreaseThermalLimits"], &ToggleControl::toggleFlipped, [this]() { + if (params.getBool("IncreaseThermalLimits")) { + FrogPilotConfirmationDialog::toggleAlert( + tr("WARNING: This can cause premature wear or damage by running the device over comma's recommended temperature limits!"), + tr("I understand the risks."), this); + } + }); + + QObject::connect(toggles["NoLogging"], &ToggleControl::toggleFlipped, [this]() { + if (params.getBool("NoLogging")) { + FrogPilotConfirmationDialog::toggleAlert( + tr("WARNING: This will prevent your drives from being recorded and the data will be unobtainable!"), + tr("I understand the risks."), this); + } + }); + + QObject::connect(toggles["NoUploads"], &ToggleControl::toggleFlipped, [this]() { + if (params.getBool("NoUploads")) { + FrogPilotConfirmationDialog::toggleAlert( + tr("WARNING: This will prevent your drives from appearing on comma connect which may impact debugging and support!"), + tr("I understand the risks."), this); + } + }); + std::set rebootKeys = {"AlwaysOnLateral", "AlwaysOnLateralMain"}; for (const QString &key : rebootKeys) { QObject::connect(toggles[key.toStdString().c_str()], &ToggleControl::toggleFlipped, [this]() { diff --git a/selfdrive/frogpilot/ui/qt/offroad/control_settings.h b/selfdrive/frogpilot/ui/qt/offroad/control_settings.h index 348f434c0..bd5d4fe2b 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/control_settings.h +++ b/selfdrive/frogpilot/ui/qt/offroad/control_settings.h @@ -33,7 +33,7 @@ private: std::set aolKeys = {"AlwaysOnLateralMain", "HideAOLStatusBar", "PauseAOLOnBrake"}; std::set conditionalExperimentalKeys = {"CECurves", "CECurvesLead", "CENavigation", "CESignal", "CESlowerLead", "CEStopLights", "HideCEMStatusBar"}; - std::set deviceManagementKeys = {}; + std::set deviceManagementKeys = {"DeviceShutdown", "IncreaseThermalLimits", "LowVoltageShutdown", "NoLogging", "NoUploads", "OfflineMode"}; std::set experimentalModeActivationKeys = {}; std::set laneChangeKeys = {}; std::set lateralTuneKeys = {}; diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index f4f700652..6a8144010 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -42,21 +42,28 @@ def only_offroad(started, params, CP: car.CarParams) -> bool: return not started # FrogPilot functions +def allow_logging(started, params, CP: car.CarParams) -> bool: + allow_logging = not (params.get_bool("DeviceManagement") and params.get_bool("NoLogging")) + return allow_logging and logging(started, params, CP) + +def allow_uploads(started, params, CP: car.CarParams) -> bool: + allow_uploads = not (params.get_bool("DeviceManagement") and params.get_bool("NoUploads")) + return allow_uploads procs = [ DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), NativeProcess("camerad", "system/camerad", ["./camerad"], driverview), - NativeProcess("logcatd", "system/logcatd", ["./logcatd"], only_onroad), - NativeProcess("proclogd", "system/proclogd", ["./proclogd"], only_onroad), - PythonProcess("logmessaged", "system.logmessaged", always_run), + NativeProcess("logcatd", "system/logcatd", ["./logcatd"], allow_logging), + NativeProcess("proclogd", "system/proclogd", ["./proclogd"], allow_logging), + PythonProcess("logmessaged", "system.logmessaged", allow_logging), PythonProcess("micd", "system.micd", iscar), PythonProcess("timed", "system.timed", always_run, enabled=not PC), PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(not PC or WEBCAM)), - NativeProcess("encoderd", "system/loggerd", ["./encoderd"], only_onroad), + NativeProcess("encoderd", "system/loggerd", ["./encoderd"], allow_logging), NativeProcess("stream_encoderd", "system/loggerd", ["./encoderd", "--stream"], notcar), - NativeProcess("loggerd", "system/loggerd", ["./loggerd"], logging), + NativeProcess("loggerd", "system/loggerd", ["./loggerd"], allow_logging), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"], only_onroad), NativeProcess("mapsd", "selfdrive/navd", ["./mapsd"], only_onroad), PythonProcess("navmodeld", "selfdrive.modeld.navmodeld", only_onroad), @@ -82,8 +89,8 @@ procs = [ PythonProcess("thermald", "selfdrive.thermald.thermald", always_run), PythonProcess("tombstoned", "selfdrive.tombstoned", always_run, enabled=not PC), PythonProcess("updated", "selfdrive.updated.updated", only_offroad, enabled=not PC), - PythonProcess("uploader", "system.loggerd.uploader", always_run), - PythonProcess("statsd", "selfdrive.statsd", always_run), + PythonProcess("uploader", "system.loggerd.uploader", allow_uploads), + PythonProcess("statsd", "selfdrive.statsd", allow_logging), # debug procs NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar), diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index d9bc8e2a4..7936f8914 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -37,6 +37,11 @@ class PowerMonitoring: self.car_battery_capacity_uWh = max((CAR_BATTERY_CAPACITY_uWh / 10), int(car_battery_capacity_uWh)) # FrogPilot variables + device_management = self.params.get_bool("DeviceManagement") + device_shutdown_setting = self.params.get_int("DeviceShutdown") if device_management else 33 + # If the toggle is set for < 1 hour, configure by 15 minute increments + self.device_shutdown_time = (device_shutdown_setting - 3) * 3600 if device_shutdown_setting >= 4 else device_shutdown_setting * (60 * 15) + self.low_voltage_shutdown = self.params.get_float("LowVoltageShutdown") if device_management else VBATT_PAUSE_CHARGING # Calculation tick def calculate(self, voltage: int | None, ignition: bool): @@ -116,9 +121,9 @@ class PowerMonitoring: now = time.monotonic() should_shutdown = False offroad_time = (now - offroad_timestamp) - low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and + low_voltage_shutdown = (self.car_voltage_mV < (self.low_voltage_shutdown * 1e3) and offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) - should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S + should_shutdown |= offroad_time > self.device_shutdown_time should_shutdown |= low_voltage_shutdown should_shutdown |= (self.car_battery_capacity_uWh <= 0) should_shutdown &= not ignition diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 9a744f041..da1d09e7c 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -204,6 +204,10 @@ def thermald_thread(end_event, hw_queue) -> None: fan_controller = None + # FrogPilot variables + device_management = params.get_bool("DeviceManagement") + offline_mode = device_management and params.get_bool("OfflineMode") + while not end_event.is_set(): sm.update(PANDA_STATES_TIMEOUT) @@ -296,7 +300,7 @@ def thermald_thread(end_event, hw_queue) -> None: # **** starting logic **** - startup_conditions["up_to_date"] = params.get("Offroad_ConnectivityNeeded") is None or params.get_bool("DisableUpdates") or params.get_bool("SnoozeUpdate") + startup_conditions["up_to_date"] = params.get("Offroad_ConnectivityNeeded") is None or params.get_bool("DisableUpdates") or params.get_bool("SnoozeUpdate") or offline_mode startup_conditions["not_uninstalling"] = not params.get_bool("DoUninstall") startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 5f78e2512..07312eef8 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -158,6 +158,8 @@ void TogglesPanel::showEvent(QShowEvent *event) { void TogglesPanel::updateToggles() { auto disengage_on_accelerator_toggle = toggles["DisengageOnAccelerator"]; disengage_on_accelerator_toggle->setVisible(!params.getBool("AlwaysOnLateral")); + auto driver_camera_toggle = toggles["RecordFront"]; + driver_camera_toggle->setVisible(!(params.getBool("DeviceManagement") && params.getBool("NoLogging") && params.getBool("NoUploads"))); auto experimental_mode_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.cc b/selfdrive/ui/qt/widgets/offroad_alerts.cc index 74ece36d1..f1cde0961 100644 --- a/selfdrive/ui/qt/widgets/offroad_alerts.cc +++ b/selfdrive/ui/qt/widgets/offroad_alerts.cc @@ -32,6 +32,18 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft); QObject::connect(dismiss_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss); + disable_check_btn = new QPushButton(tr("Disable Internet Check")); + disable_check_btn->setVisible(false); + disable_check_btn->setFixedSize(625, 125); + footer_layout->addWidget(disable_check_btn, 1, Qt::AlignBottom | Qt::AlignCenter); + QObject::connect(disable_check_btn, &QPushButton::clicked, [=]() { + params.putBool("SnoozeUpdate", true); + params.putBool("DeviceManagement", true); + params.putBool("OfflineMode", true); + }); + QObject::connect(disable_check_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss); + disable_check_btn->setStyleSheet(R"(color: white; background-color: #4F4F4F;)"); + snooze_btn = new QPushButton(tr("Snooze Update")); snooze_btn->setVisible(false); snooze_btn->setFixedSize(550, 125); @@ -107,6 +119,7 @@ int OffroadAlert::refresh() { label->setVisible(!text.isEmpty()); alertCount += !text.isEmpty(); } + disable_check_btn->setVisible(!alerts["Offroad_ConnectivityNeeded"]->text().isEmpty()); snooze_btn->setVisible(!alerts["Offroad_ConnectivityNeeded"]->text().isEmpty()); return alertCount; } diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.h b/selfdrive/ui/qt/widgets/offroad_alerts.h index ace2e7545..abeafd225 100644 --- a/selfdrive/ui/qt/widgets/offroad_alerts.h +++ b/selfdrive/ui/qt/widgets/offroad_alerts.h @@ -15,6 +15,7 @@ class AbstractAlert : public QFrame { protected: AbstractAlert(bool hasRebootBtn, QWidget *parent = nullptr); + QPushButton *disable_check_btn; QPushButton *snooze_btn; QVBoxLayout *scrollable_layout; Params params; diff --git a/selfdrive/ui/qt/widgets/wifi.cc b/selfdrive/ui/qt/widgets/wifi.cc index 9c5289a22..591c1ce7c 100644 --- a/selfdrive/ui/qt/widgets/wifi.cc +++ b/selfdrive/ui/qt/widgets/wifi.cc @@ -81,6 +81,34 @@ WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) { } stack->addWidget(uploading); + QWidget *notUploading = new QWidget; + QVBoxLayout *not_uploading_layout = new QVBoxLayout(notUploading); + not_uploading_layout->setContentsMargins(64, 56, 64, 56); + not_uploading_layout->setSpacing(36); + { + QHBoxLayout *title_layout = new QHBoxLayout; + { + QLabel *title = new QLabel(tr("Uploading disabled")); + title->setStyleSheet("font-size: 64px; font-weight: 600;"); + title->setWordWrap(true); + title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + title_layout->addWidget(title); + title_layout->addStretch(); + + QLabel *icon = new QLabel; + QPixmap pixmap("../frogpilot/assets/other_images/icon_wifi_uploading_disabled.svg"); + icon->setPixmap(pixmap.scaledToWidth(120, Qt::SmoothTransformation)); + title_layout->addWidget(icon); + } + not_uploading_layout->addLayout(title_layout); + + QLabel *desc = new QLabel(tr("Toggle off the 'Disable Uploading' toggle to enable uploads.")); + desc->setStyleSheet("font-size: 48px; font-weight: 400;"); + desc->setWordWrap(true); + not_uploading_layout->addWidget(desc); + } + stack->addWidget(notUploading); + setStyleSheet(R"( WiFiPromptWidget { background-color: #333333; @@ -99,5 +127,6 @@ void WiFiPromptWidget::updateState(const UIState &s) { auto network_type = sm["deviceState"].getDeviceState().getNetworkType(); auto uploading = network_type == cereal::DeviceState::NetworkType::WIFI || network_type == cereal::DeviceState::NetworkType::ETHERNET; - stack->setCurrentIndex(uploading ? 1 : 0); + bool uploading_disabled = params.getBool("DeviceManagement") && params.getBool("NoUploads"); + stack->setCurrentIndex(uploading_disabled ? 2 : uploading ? 1 : 0); } diff --git a/selfdrive/updated/updated.py b/selfdrive/updated/updated.py index c2010c90e..f17be3f41 100755 --- a/selfdrive/updated/updated.py +++ b/selfdrive/updated/updated.py @@ -237,6 +237,7 @@ class Updater: self._has_internet: bool = False # FrogPilot variables + self.offline_mode = self.params.get_bool("DeviceManagement") and self.params.get_bool("OfflineMode") @property def has_internet(self) -> bool: @@ -326,6 +327,8 @@ class Updater: set_offroad_alert(alert, False) now = datetime.datetime.utcnow() + if self.offline_mode: + last_update = now dt = now - last_update if failed_count > 15 and exception is not None and self.has_internet: if is_tested_branch():