diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 7d410f7df3..e46738fedb 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -24,6 +24,9 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * comma Prime support * Personal Mapbox/Amap/Google Maps token support * Instructions on how to set up your iOS Siri Shortcuts: https://routinehub.co/shortcut/17677/ +* NEW❗: Forced Offroad mode + * Force sunnypilot in the offroad state even when the car is on + * When Forced Offroad mode is on, allows changing offroad-only settings even when the car is turned on * NEW❗: Ford CAN-FD longitudinal * NEW❗: Parse speed limit sign recognition from camera for certain supported platforms * UPDATED: Hyundai CAN-FD Radar-based SCC diff --git a/common/params.cc b/common/params.cc index 9cd213bd7b..6ef443f1b9 100644 --- a/common/params.cc +++ b/common/params.cc @@ -256,6 +256,7 @@ std::unordered_map keys = { {"EnhancedScc", PERSISTENT | BACKUP}, {"FeatureStatus", PERSISTENT | BACKUP}, {"FleetManagerPin", PERSISTENT}, + {"ForceOffroad", CLEAR_ON_MANAGER_START}, {"GmapKey", PERSISTENT | BACKUP}, {"HandsOnWheelMonitoring", PERSISTENT | BACKUP}, {"HideVEgoUi", PERSISTENT | BACKUP}, @@ -325,6 +326,7 @@ std::unordered_map keys = { {"VisionCurveLaneless", PERSISTENT | BACKUP}, {"VwAccType", PERSISTENT | BACKUP}, {"VwCCOnly", PERSISTENT | BACKUP}, + {"Offroad_ForceStatus", CLEAR_ON_MANAGER_START}, {"Offroad_SupersededUpdate", PERSISTENT}, {"SunnylinkCache_Users", PERSISTENT}, diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index fcbf58999e..eb301b1f00 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -229,7 +229,7 @@ void can_recv_thread(std::vector pandas) { } } -std::optional send_panda_states(PubMaster *pm, const std::vector &pandas, bool spoofing_started) { +std::optional send_panda_states(PubMaster *pm, const std::vector &pandas, bool spoofing_started, Params ¶ms) { bool ignition_local = false; const uint32_t pandas_cnt = pandas.size(); @@ -277,7 +277,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector health.ignition_line_pkt = 0; } - ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)); + ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)) && !params.getBool("ForceOffroad"); pandaStates.push_back(health); } @@ -432,7 +432,7 @@ void panda_state_thread(std::vector pandas, bool spoofing_started) { send_peripheral_state(&pm, peripheral_panda); } - auto ignition_opt = send_panda_states(&pm, pandas, spoofing_started); + auto ignition_opt = send_panda_states(&pm, pandas, spoofing_started, params); if (!ignition_opt) { LOGE("Failed to get ignition_opt"); diff --git a/selfdrive/controls/lib/alerts_offroad.json b/selfdrive/controls/lib/alerts_offroad.json index 0293a655fa..4bb7fd1736 100644 --- a/selfdrive/controls/lib/alerts_offroad.json +++ b/selfdrive/controls/lib/alerts_offroad.json @@ -52,5 +52,9 @@ "Offroad_OSMUpdateRequired": { "text": "OpenStreetMap database is out of date. New maps must be downloaded if you wish to continue using OpenStreetMap data for Enhanced Speed Control and road name display.\n\n%1", "severity": 0 + }, + "Offroad_ForceStatus": { + "text": "sunnypilot is now in Forced Offroad mode. sunnypilot won't start until Forced Offroad mode is disabled. Go to \"Settings\" -> \"Device\" -> \"Unforce Offroad\" to exit Force Offroad mode.", + "severity": 1 } } diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 10d0c13883..57b9914239 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -311,6 +311,12 @@ def thermald_thread(end_event, hw_queue) -> None: # ensure device is fully booted startup_conditions["device_booted"] = startup_conditions.get("device_booted", False) or HARDWARE.booted() + # user-forced status + force_offroad = params.get_bool("ForceOffroad") + startup_conditions["not_force_offroad"] = not force_offroad + onroad_conditions["not_force_offroad"] = not force_offroad + set_offroad_alert("Offroad_ForceStatus", force_offroad) + # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger extra_text = f"{offroad_comp_temp:.1f}C" diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 72d9b8b5aa..63831f39a6 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -442,13 +442,24 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible); } + offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad")); + offroad_btn->setObjectName("offroad_btn"); + QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanel::forceoffroad); + + QVBoxLayout *buttons_layout = new QVBoxLayout(); + buttons_layout->setSpacing(24); + buttons_layout->addLayout(power_layout); + buttons_layout->addWidget(offroad_btn); + setStyleSheet(R"( #reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; } #reboot_btn:pressed { background-color: #4a4a4a; } #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } #poweroff_btn:pressed { background-color: #FF2424; } )"); - addItem(power_layout); + addItem(buttons_layout); + + updateLabels(); } void DevicePanel::onPinFileChanged(const QString &file_path) { @@ -523,9 +534,51 @@ void DevicePanel::poweroff() { } } +void DevicePanel::forceoffroad() { + if (!uiState()->engaged()) { + if (params.getBool("ForceOffroad")) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) { + // Check engaged again in case it changed while the dialog was open + if (!uiState()->engaged()) { + params.remove("ForceOffroad"); + } + } + } else { + if (ConfirmationDialog::confirm(tr("Are you sure you want to force offroad?"), tr("Force"), this)) { + // Check engaged again in case it changed while the dialog was open + if (!uiState()->engaged()) { + params.putBool("ForceOffroad", true); + } + } + } + } else { + ConfirmationDialog::alert(tr("Disengage to Force Offroad"), this); + } + + updateLabels(); +} + void DevicePanel::showEvent(QShowEvent *event) { pair_device->setVisible(uiState()->primeType() == PrimeType::UNPAIRED); ListWidget::showEvent(event); + updateLabels(); +} + +void DevicePanel::updateLabels() { + if (!isVisible()) { + return; + } + + bool force_offroad_param = params.getBool("ForceOffroad"); + QString offroad_btn_style = force_offroad_param ? "#393939" : "#E22C2C"; + QString offroad_btn_pressed_style = force_offroad_param ? "#4a4a4a" : "#FF2424"; + QString btn_common_style = QString("QPushButton { height: 120px; border-radius: 15px; background-color: %1; }" + "QPushButton:pressed { background-color: %2; }") + .arg(offroad_btn_style, + offroad_btn_pressed_style); + + offroad_btn->setText(force_offroad_param ? tr("Unforce Offroad") : tr("Force Offroad")); + offroad_btn->setStyleSheet(btn_common_style + offroad_btn_style + offroad_btn_pressed_style); } void SettingsWindow::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index b1ee32f39d..0e550850e8 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -63,6 +63,9 @@ private slots: void updateCalibDescription(); void onPinFileChanged(const QString &file_path); void refreshPin(); + void forceoffroad(); + + void updateLabels(); private: Params params; @@ -72,6 +75,8 @@ private: QString pin_title = tr("Fleet Manager PIN:") + " "; QString pin = "OFF"; QFileSystemWatcher *fs_watch; + + QPushButton *offroad_btn; }; class TogglesPanel : public ListWidget { diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 11ac3ee650..b7b37ec9de 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -165,6 +165,7 @@ static void update_sockets(UIState *s) { static void update_state(UIState *s) { SubMaster &sm = *(s->sm); UIScene &scene = s->scene; + auto params = Params(); if (sm.updated("liveCalibration")) { auto live_calib = sm["liveCalibration"].getLiveCalibration(); @@ -214,7 +215,7 @@ static void update_state(UIState *s) { float scale = (cam_state.getSensor() == cereal::FrameData::ImageSensor::AR0231) ? 6.0f : 1.0f; scene.light_sensor = std::max(100.0f - scale * cam_state.getExposureValPercent(), 0.0f); } - scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; + scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); scene.world_objects_visible = scene.world_objects_visible || (scene.started &&