diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript
index 379adfced..d18975184 100644
--- a/selfdrive/ui/sunnypilot/SConscript
+++ b/selfdrive/ui/sunnypilot/SConscript
@@ -3,6 +3,7 @@ widgets_src = [
"sunnypilot/qt/widgets/toggle.cc",
"sunnypilot/qt/widgets/controls.cc",
"sunnypilot/qt/widgets/drive_stats.cc",
+ "sunnypilot/qt/widgets/expandable_row.cc",
"sunnypilot/qt/widgets/prime.cc",
"sunnypilot/qt/widgets/scrollview.cc",
"sunnypilot/qt/network/networking.cc",
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.cc
index 97b78399d..2469bfa59 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.cc
@@ -21,22 +21,11 @@ MadsSettings::MadsSettings(QWidget *parent) : QWidget(parent) {
ListWidget *list = new ListWidget(this, false);
// Main cruise
- madsMainCruiseToggle = new ParamControl(
- "MadsMainCruiseAllowed",
- tr("Toggle with Main Cruise"),
- tr("Note: For vehicles without LFA/LKAS button, disabling this will prevent lateral control engagement."),
- "");
+ madsMainCruiseToggle = new ParamControl("MadsMainCruiseAllowed", tr("Toggle with Main Cruise"), "", "");
list->addItem(madsMainCruiseToggle);
// Unified Engagement Mode
- madsUnifiedEngagementModeToggle = new ParamControl(
- "MadsUnifiedEngagementMode",
- tr("Unified Engagement Mode (UEM)"),
- QString("%1
"
- "
%2
")
- .arg(tr("Engage lateral and longitudinal control with cruise control engagement."))
- .arg(tr("Note: Once lateral control is engaged via UEM, it will remain engaged until it is manually disabled via the MADS button or car shut off.")),
- "");
+ madsUnifiedEngagementModeToggle = new ParamControl("MadsUnifiedEngagementMode", tr("Unified Engagement Mode (UEM)"), "", "");
list->addItem(madsUnifiedEngagementModeToggle);
// Steering Mode On Brake
@@ -62,8 +51,51 @@ void MadsSettings::updateToggles(bool _offroad) {
std::clamp(mads_steering_mode_param, static_cast(MadsSteeringMode::REMAIN_ACTIVE), static_cast(MadsSteeringMode::DISENGAGE))
);
- madsSteeringMode->setEnabled(_offroad);
- madsSteeringMode->setDescription(madsSteeringModeDescription(steering_mode));
+ auto cp_bytes = params.get("CarParamsPersistent");
+ if (!cp_bytes.empty()) {
+ AlignedBuffer aligned_buf;
+ capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
+ cereal::CarParams::Reader CP = cmsg.getRoot();
+
+ if (isBrandInList(CP.getBrand(), mads_limited_settings_brands)) {
+ params.remove("MadsMainCruiseAllowed");
+ params.putBool("MadsUnifiedEngagementMode", true);
+ params.put("MadsSteeringMode", std::to_string(static_cast(MadsSteeringMode::DISENGAGE)));
+
+ madsMainCruiseToggle->setEnabled(false);
+ madsMainCruiseToggle->setDescription(madsDescriptionBuilder(DEFAULT_TO_OFF, MADS_MAIN_CRUISE_BASE_DESC));
+ madsMainCruiseToggle->showDescription();
+
+ madsUnifiedEngagementModeToggle->setEnabled(false);
+ madsUnifiedEngagementModeToggle->setDescription(madsDescriptionBuilder(DEFAULT_TO_ON, MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC));
+ madsUnifiedEngagementModeToggle->showDescription();
+
+ madsSteeringModeValues = convertMadsSteeringModeValues({MadsSteeringMode::DISENGAGE});
+ madsSteeringMode->setDescription(madsDescriptionBuilder(STATUS_DISENGAGE_ONLY, madsSteeringModeDescription(steering_mode)));
+ } else {
+ madsMainCruiseToggle->setEnabled(true);
+ madsMainCruiseToggle->setDescription(MADS_MAIN_CRUISE_BASE_DESC);
+
+ madsUnifiedEngagementModeToggle->setEnabled(true);
+ madsUnifiedEngagementModeToggle->setDescription(MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC);
+
+ madsSteeringModeValues = convertMadsSteeringModeValues(getMadsSteeringModeValues());
+ madsSteeringMode->setDescription(madsSteeringModeDescription(steering_mode));
+ }
+ } else {
+ madsMainCruiseToggle->setEnabled(false);
+ madsMainCruiseToggle->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, MADS_MAIN_CRUISE_BASE_DESC));
+ madsMainCruiseToggle->showDescription();
+
+ madsUnifiedEngagementModeToggle->setEnabled(false);
+ madsUnifiedEngagementModeToggle->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC));
+ madsUnifiedEngagementModeToggle->showDescription();
+
+ madsSteeringModeValues = {};
+ madsSteeringMode->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, madsSteeringModeDescription(steering_mode)));
+ }
+
+ madsSteeringMode->setEnableSelectedButtons(_offroad, madsSteeringModeValues);
madsSteeringMode->showDescription();
offroad = _offroad;
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h
index 07ca07834..af624865c 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h
@@ -12,6 +12,8 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+const std::vector mads_limited_settings_brands = {"rivian", "tesla"};
+
enum class MadsSteeringMode {
REMAIN_ACTIVE = 0,
PAUSE = 1,
@@ -46,15 +48,44 @@ private:
ParamControl *madsUnifiedEngagementModeToggle;
ButtonParamControl *madsSteeringMode;
+ std::vector madsSteeringModeValues = {};
+
+ const QString MADS_MAIN_CRUISE_BASE_DESC = tr("Note: For vehicles without LFA/LKAS button, disabling this will prevent lateral control engagement.");
+ const QString MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC = QString("%1
"
+ "%2
")
+ .arg(tr("Engage lateral and longitudinal control with cruise control engagement."))
+ .arg(tr("Note: Once lateral control is engaged via UEM, it will remain engaged until it is manually disabled via the MADS button or car shut off."));
+
+ const QString STATUS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.");
+ const QString DEFAULT_TO_OFF = tr("This feature defaults to OFF, and does not allow selection due to vehicle limitations.");
+ const QString DEFAULT_TO_ON = tr("This feature defaults to ON, and does not allow selection due to vehicle limitations.");
+ const QString STATUS_DISENGAGE_ONLY = tr("This platform only supports Disengage mode due to vehicle limitations.");
+
static const std::vector &madsSteeringModeOptions() {
static const std::vector options = {
- {MadsSteeringMode::REMAIN_ACTIVE, tr("Remain Active"), tr("Remain Active: ALC will remain active when the brake pedal is pressed.")},
- {MadsSteeringMode::PAUSE, tr("Pause"), tr("Pause: ALC will pause when the brake pedal is pressed.")},
- {MadsSteeringMode::DISENGAGE, tr("Disengage"), tr("Disengage: ALC will disengage when the brake pedal is pressed.")},
+ {MadsSteeringMode::REMAIN_ACTIVE, tr("Remain Active"), tr("Remain Active: ALC will remain active when the brake pedal is pressed.")},
+ {MadsSteeringMode::PAUSE, tr("Pause"), tr("Pause: ALC will pause when the brake pedal is pressed.")},
+ {MadsSteeringMode::DISENGAGE, tr("Disengage"), tr("Disengage: ALC will disengage when the brake pedal is pressed.")},
};
return options;
}
+ static std::vector getMadsSteeringModeValues() {
+ std::vector values;
+ for (const auto& option : madsSteeringModeOptions()) {
+ values.push_back(option.mode);
+ }
+ return values;
+ }
+
+ static std::vector convertMadsSteeringModeValues(const std::vector &modes) {
+ std::vector values;
+ for (const auto& mode : modes) {
+ values.push_back(static_cast(mode));
+ }
+ return values;
+ }
+
static std::vector madsSteeringModeTexts() {
std::vector texts;
for (const auto& option : madsSteeringModeOptions()) {
@@ -63,7 +94,7 @@ private:
return texts;
}
- static QString madsSteeringModeDescription(const MadsSteeringMode mode) {
+ static QString madsSteeringModeDescription(const MadsSteeringMode mode = MadsSteeringMode::REMAIN_ACTIVE) {
QString base_desc = tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.");
QString result = base_desc + "
";
@@ -77,4 +108,8 @@ private:
return result;
}
+
+ static QString madsDescriptionBuilder(const QString &custom_description, const QString &base_description) {
+ return "" + custom_description + "
" + base_description;
+ }
};
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc
index 75cd13664..0beae6e24 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc
@@ -122,6 +122,21 @@ void LateralPanel::updateToggles(bool _offroad) {
toggle->setEnabled(_offroad);
}
+ auto cp_bytes = params.get("CarParamsPersistent");
+ if (!cp_bytes.empty()) {
+ AlignedBuffer aligned_buf;
+ capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
+ cereal::CarParams::Reader CP = cmsg.getRoot();
+
+ if (isBrandInList(CP.getBrand(), mads_limited_settings_brands)) {
+ madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY, MADS_BASE_DESC));
+ } else {
+ madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_FULL_COMPATIBILITY, MADS_BASE_DESC));
+ }
+ } else {
+ madsToggle->setDescription(descriptionBuilder(STATUS_MADS_CHECK_COMPATIBILITY, MADS_BASE_DESC));
+ }
+
madsSettingsButton->setEnabled(madsToggle->isToggled());
offroad = _offroad;
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h
index 1b77939ee..cf003dfaf 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h
@@ -30,6 +30,7 @@ public slots:
void updateToggles(bool _offroad);
private:
+ Params params;
QStackedLayout* main_layout = nullptr;
QWidget* sunnypilotScreen = nullptr;
ScrollViewSP *sunnypilotScroller = nullptr;
@@ -42,4 +43,14 @@ private:
PushButtonSP *laneChangeSettingsButton;
LaneChangeSettings *laneChangeWidget = nullptr;
NeuralNetworkLateralControl *nnlcToggle = nullptr;
+
+ const QString MADS_BASE_DESC = tr("Enables independent engagements of Automatic Lane Centering (ALC) and Adaptive Cruise Control (ACC).");
+
+ const QString STATUS_MADS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.");
+ const QString STATUS_MADS_SETTINGS_FULL_COMPATIBILITY = tr("This platform supports all MADS settings.");
+ const QString STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY = tr("This platform supports limited MADS settings.");
+
+ static QString descriptionBuilder(const QString &custom_description, const QString &base_description) {
+ return "" + custom_description + "
" + base_description;
+ }
};
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h
index 2fe511cc4..ff25d3b64 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h
@@ -12,6 +12,10 @@
#include "selfdrive/ui/qt/offroad/settings.h"
+inline bool isBrandInList(const std::string &brand, const std::vector &list) {
+ return std::find(list.begin(), list.end(), brand) != list.end();
+}
+
class SettingsWindowSP : public SettingsWindow {
Q_OBJECT
diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h
index 2a1bc20ac..b5411f3bf 100644
--- a/selfdrive/ui/sunnypilot/qt/widgets/controls.h
+++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h
@@ -324,10 +324,11 @@ public:
}
}
- void setDisabledSelectedButton(std::string val) {
- int value = atoi(val.c_str());
+ void setEnableSelectedButtons(bool enable, const std::vector& enabled_btns = {}) const {
for (int i = 0; i < button_group->buttons().size(); i++) {
- button_group->buttons()[i]->setEnabled(i != value);
+ // Enable the button if its index is in the enabled list
+ bool should_enable = std::find(enabled_btns.begin(), enabled_btns.end(), i) != enabled_btns.end();
+ button_group->buttons()[i]->setEnabled(enable && should_enable);
}
}
@@ -440,15 +441,16 @@ public:
class OptionControlSP : public AbstractControlSP_SELECTOR {
Q_OBJECT
-private:
- bool is_inline_layout;
- QHBoxLayout *optionSelectorLayout = is_inline_layout ? new QHBoxLayout() : hlayout;
-
+protected:
struct MinMaxValue {
int min_value;
int max_value;
};
+private:
+ bool is_inline_layout;
+ QHBoxLayout *optionSelectorLayout = is_inline_layout ? new QHBoxLayout() : hlayout;
+
int getParamValue() {
const auto param_value = QString::fromStdString(params.get(key));
const auto result = valueMap != nullptr ? valueMap->key(param_value) : param_value;
@@ -550,6 +552,10 @@ public:
request_update = _update;
}
+ void setFixedWidth(int width) {
+ label.setFixedWidth(width);
+ }
+
inline void setLabel(const QString &text) { label.setText(text); }
void setEnabled(bool enabled) {
diff --git a/selfdrive/ui/sunnypilot/qt/widgets/expandable_row.cc b/selfdrive/ui/sunnypilot/qt/widgets/expandable_row.cc
new file mode 100644
index 000000000..b918938d1
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/widgets/expandable_row.cc
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+ *
+ * This file is part of sunnypilot and is licensed under the MIT License.
+ * See the LICENSE.md file in the root directory for more details.
+ */
+
+#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
+
+ExpandableToggleRow::ExpandableToggleRow(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
+ : ToggleControlSP(title, desc, icon, false, parent) {
+
+ key = param.toStdString();
+ QObject::connect(this, &ExpandableToggleRow::toggleFlipped, this, &ExpandableToggleRow::toggleClicked);
+
+ collapsibleWidget = new QFrame(this);
+ collapsibleWidget->setContentsMargins(0, 0, 0, 0);
+ collapsibleWidget->setVisible(false);
+ QVBoxLayout *collapsible_layout = new QVBoxLayout();
+ collapsibleWidget->setLayout(collapsible_layout);
+
+ list = new ListWidgetSP(this, false);
+
+ main_layout->addWidget(collapsibleWidget);
+ collapsible_layout->addWidget(list);
+}
+
+void ExpandableToggleRow::toggleClicked(bool state) {
+ params.putBool(key, state);
+ collapsibleWidget->setVisible(state);
+}
diff --git a/selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h b/selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h
new file mode 100644
index 000000000..4bfcb9ef5
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+ *
+ * This file is part of sunnypilot and is licensed under the MIT License.
+ * See the LICENSE.md file in the root directory for more details.
+ */
+
+#pragma once
+
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+
+class ExpandableToggleRow : public ToggleControlSP {
+ Q_OBJECT
+
+public:
+ ExpandableToggleRow(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
+
+ void addItem(QWidget *widget) {
+ list->addItem(widget);
+ }
+
+ ListWidgetSP *innerList() {
+ return list;
+ }
+
+ void refresh() {
+ bool state = params.getBool(key);
+ if (state != toggle.on) {
+ toggle.togglePosition();
+ }
+ collapsibleWidget->setVisible(state);
+ }
+
+ bool isToggled() {
+ return params.getBool(key);
+ }
+
+ void showEvent(QShowEvent *event) override {
+ refresh();
+ }
+
+private:
+ void toggleClicked(bool state);
+
+ std::string key;
+ Params params;
+
+ ListWidgetSP *list;
+ QFrame *collapsibleWidget = nullptr;
+};
diff --git a/sunnypilot/mads/helpers.py b/sunnypilot/mads/helpers.py
index 5f7f9840e..4b1095c63 100644
--- a/sunnypilot/mads/helpers.py
+++ b/sunnypilot/mads/helpers.py
@@ -17,7 +17,14 @@ class MadsSteeringModeOnBrake:
DISENGAGE = 2
-def read_steering_mode_param(params: Params):
+def get_mads_limited_brands(CP: structs.CarParams) -> bool:
+ return CP.brand in ("rivian", "tesla")
+
+
+def read_steering_mode_param(CP: structs.CarParams, params: Params):
+ if get_mads_limited_brands(CP):
+ return MadsSteeringModeOnBrake.DISENGAGE
+
try:
return int(params.get("MadsSteeringMode"))
except (ValueError, TypeError):
@@ -26,7 +33,7 @@ def read_steering_mode_param(params: Params):
def set_alternative_experience(CP: structs.CarParams, params: Params):
enabled = params.get_bool("Mads")
- steering_mode = read_steering_mode_param(params)
+ steering_mode = read_steering_mode_param(CP, params)
if enabled:
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ENABLE_MADS
@@ -46,12 +53,12 @@ def set_car_specific_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, p
CP_SP.flags |= HyundaiFlagsSP.LONGITUDINAL_MAIN_CRUISE_TOGGLEABLE.value
CP_SP.safetyParam |= HyundaiSafetyFlagsSP.LONG_MAIN_CRUISE_TOGGLEABLE
- # MADS is currently not supported in Tesla due to lack of consistent states to engage controls
- # TODO-SP: To enable MADS for Tesla, identify consistent signals for MADS toggling
- if CP.brand == "tesla":
- params.remove("Mads")
-
- # MADS is currently not supported in Rivian due to lack of consistent states to engage controls
- # TODO-SP: To enable MADS for Rivian, identify consistent signals for MADS toggling
- if CP.brand == "rivian":
- params.remove("Mads")
+ # MADS Partial Support
+ # MADS is currently partially supported for these platforms due to lack of consistent states to engage controls
+ # Only MadsSteeringModeOnBrake.DISENGAGE is supported for these platforms
+ # TODO-SP: To enable MADS full support for Rivian/Tesla, identify consistent signals for MADS toggling
+ mads_partial_support = get_mads_limited_brands(CP)
+ if mads_partial_support:
+ params.put("MadsSteeringMode", "2")
+ params.put_bool("MadsUnifiedEngagementMode", True)
+ params.remove("MadsMainCruiseAllowed")
diff --git a/sunnypilot/mads/mads.py b/sunnypilot/mads/mads.py
index 38d38230a..3b0629056 100644
--- a/sunnypilot/mads/mads.py
+++ b/sunnypilot/mads/mads.py
@@ -10,7 +10,7 @@ from cereal import log, custom
from opendbc.car import structs
from opendbc.car.hyundai.values import HyundaiFlags
from opendbc.safety import ALTERNATIVE_EXPERIENCE
-from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param
+from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param, get_mads_limited_brands
from openpilot.sunnypilot.mads.state import StateMachine, GEARS_ALLOW_PAUSED_SILENT
State = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState
@@ -33,6 +33,7 @@ class ModularAssistiveDrivingSystem:
self.active = False
self.available = False
self.allow_always = False
+ self.no_main_cruise = False
self.selfdrive = selfdrive
self.selfdrive.enabled_prev = False
self.state_machine = StateMachine(self)
@@ -44,10 +45,13 @@ class ModularAssistiveDrivingSystem:
if self.CP.flags & (HyundaiFlags.HAS_LDA_BUTTON | HyundaiFlags.CANFD):
self.allow_always = True
+ if get_mads_limited_brands(self.CP):
+ self.no_main_cruise = True
+
# read params on init
self.enabled_toggle = self.params.get_bool("Mads")
self.main_enabled_toggle = self.params.get_bool("MadsMainCruiseAllowed")
- self.steering_mode_on_brake = read_steering_mode_param(self.params)
+ self.steering_mode_on_brake = read_steering_mode_param(self.CP, self.params)
self.unified_engagement_mode = self.params.get_bool("MadsUnifiedEngagementMode")
def read_params(self):
@@ -159,7 +163,7 @@ class ModularAssistiveDrivingSystem:
else:
self.events_sp.add(EventNameSP.lkasEnable)
- if not CS.cruiseState.available:
+ if not CS.cruiseState.available and not self.no_main_cruise:
self.events.remove(EventName.buttonEnable)
if self.selfdrive.CS_prev.cruiseState.available:
self.events_sp.add(EventNameSP.lkasDisable)