Compare commits

...

13 Commits

Author SHA1 Message Date
James Vecellio-Grant
16a4c40f77 unittest -> pytest 2025-09-06 15:58:25 -07:00
discountchubbs
e39eb65e47 Merge remote-tracking branch 'origin/master' into hyundai-long-tune-live
# Conflicts:
#	opendbc/sunnypilot/car/interfaces.py
2025-09-06 07:54:44 -07:00
discountchubbs
52c6b69afc hyundai longitudinal: live accel min and accel max 2025-09-06 07:29:59 -07:00
discountchubbs
2f72ff9755 Don't access every update loop unless values change. 2025-07-03 07:07:13 -07:00
discountchubbs
11e0934071 Forgot to add this too.. You kind of need this too 2025-06-29 13:26:51 -07:00
discountchubbs
2bded3b645 stackedWidget 2025-06-29 12:55:55 -07:00
discountchubbs
c30db1dac6 Add to Param Store 2025-06-29 12:45:41 -07:00
discountchubbs
2497452a8a Hyundai Live Tune 2025-06-29 12:36:18 -07:00
discountchubbs
8c6281c721 Merge remote-tracking branch 'origin/master-new' into hyundai-long-tune-live 2025-06-29 12:24:37 -07:00
discountchubbs
1639365450 This fixes onroad transition:
Before this commit, onroad transition would not work appropriately. This commit removes old deprecated params from interfaces.py allowing for onroad transition to correctly apply
2025-05-30 06:26:06 -07:00
Jason Wen
4c0abbc785 move init to opendbc 2025-05-28 23:40:56 -04:00
Jason Wen
81c9c06685 live ui buttons 2025-05-28 23:40:20 -04:00
Jason Wen
c25ecafc7e live init 2025-05-28 23:01:10 -04:00
10 changed files with 274 additions and 24 deletions

View File

@@ -192,6 +192,13 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
// sunnypilot car specific params
{"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}},
// Hyundai Longitudinal Tuning Live Parameters
{"LongTuningCustomToggle", {PERSISTENT | BACKUP, INT, "0"}},
{"LongTuningAccelMax", {PERSISTENT | BACKUP, FLOAT, "2.0"}},
{"LongTuningJerkLimits", {PERSISTENT | BACKUP, FLOAT, "4.0"}},
{"LongTuningMinUpperJerk", {PERSISTENT | BACKUP, FLOAT, ".5"}},
{"LongTuningMinLowerJerk", {PERSISTENT | BACKUP, FLOAT, ".5"}},
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},

View File

@@ -78,6 +78,7 @@ brand_settings_qt_src = [
"sunnypilot/qt/offroad/settings/vehicle/gm_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/honda_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/hyundai_live_tuning.cc",
"sunnypilot/qt/offroad/settings/vehicle/mazda_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/nissan_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/rivian_settings.cc",

View File

@@ -0,0 +1,94 @@
/**
* 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/offroad/settings/vehicle/hyundai_live_tuning.h"
struct ParamConfig {
QString param, title, desc, defaultValue;
float minVal = 0.0f, maxVal, step = 0.01f;
};
// Clipping limits on all tuning params to prevent unsafe values.
static const std::vector<ParamConfig> PARAM_CONFIGS = {
{"LongTuningAccelMax", QObject::tr("Acceleration Max"), QObject::tr("Acceleration limit (m/s^2)"), "2.00", 0.5f, 2.0f, 0.1f},
{"LongTuningMinUpperJerk", QObject::tr("Min Upper Jerk"), QObject::tr("Minimum accel jerk limit (m/s^3)"), "0.50", 0.5f, 3.0f, 0.1f},
{"LongTuningMinLowerJerk", QObject::tr("Min Lower Jerk"), QObject::tr("Minimum braking jerk limit (m/s^3)"), "0.50", 0.5f, 5.0f, 0.1f},
{"LongTuningJerkLimits", QObject::tr("Dynamic Tune Jerk Max"), QObject::tr("Maximum lower jerk limit the tune cannot exceed (m/s^3)"), "4.00", 2.0f, 5.0f, 0.1f},
};
QWidget* createButtonWidget(const QString &text, std::function<void()> callback, bool isPushButton = false) {
QWidget *widget = new QWidget(); QHBoxLayout *layout = new QHBoxLayout(widget);
QAbstractButton *btn = isPushButton ? (QAbstractButton*)new PushButtonSP(text) : (QAbstractButton*)new QPushButton(text);
if (!isPushButton) { btn->setFixedSize(250, 100); btn->setStyleSheet("QPushButton{padding:0;border-radius:20px;font-size:35px;font-weight:500;color:#E4E4E4;background-color:#393939;}QPushButton:pressed{background-color:#4a4a4a;}"); }
QObject::connect(btn, &QAbstractButton::clicked, callback); layout->addWidget(btn, 0, Qt::AlignLeft); layout->addStretch();
return widget;
}
HyundaiLiveTuning::HyundaiLiveTuning(QWidget *parent) : QWidget(parent) {
main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setSpacing(0);
list = new ListWidgetSP(this, false);
list->addItem(createButtonWidget(tr("Back"), [=]() { emit backPress(); }));
list->addItem(createButtonWidget(tr("Reset to defaults"), [=]() { resetToDefaults(); }, true));
for (const auto &config : PARAM_CONFIGS) createFloatControl(config.param, config.title, config.desc, config.minVal, config.maxVal, config.step);
main_layout->addWidget(new ScrollViewSP(list, this));
}
void HyundaiLiveTuning::createFloatControl(const QString &param, const QString &title, const QString &desc, float min_val, float max_val, float step) {
QWidget *widget = new QWidget(); QHBoxLayout *layout = new QHBoxLayout(widget); layout->setSpacing(20);
QVBoxLayout *text_layout = new QVBoxLayout(); QLabel *title_label = new QLabel(title); title_label->setStyleSheet("font-size: 50px; font-weight: 400;");
QLabel *desc_label = new QLabel(desc); desc_label->setStyleSheet("font-size: 40px; color: grey;"); desc_label->setWordWrap(true);
text_layout->addWidget(title_label); text_layout->addWidget(desc_label); layout->addLayout(text_layout, 1);
QPushButton *minus_btn = new QPushButton("-"); QLabel *value_label = new QLabel("0.00"); QPushButton *plus_btn = new QPushButton("+");
QString btn_style = "QPushButton{padding:0;border-radius:8px;font-size:40px;font-weight:600;color:#E4E4E4;background-color:#393939;}QPushButton:pressed{background-color:#4a4a4a;}QPushButton:disabled{color:#33E4E4E4;background-color:#2a2a2a;}";
minus_btn->setFixedSize(120, 80); minus_btn->setStyleSheet(btn_style); plus_btn->setFixedSize(120, 80); plus_btn->setStyleSheet(btn_style);
value_label->setAlignment(Qt::AlignCenter); value_label->setStyleSheet("font-size: 35px; font-weight: 500; min-width: 100px; color: #E4E4E4;");
layout->addWidget(minus_btn); layout->addWidget(value_label); layout->addWidget(plus_btn);
QTimer *plusTimer = new QTimer(widget), *minusTimer = new QTimer(widget);
std::string param_str = param.toStdString(); QString current_value = QString::fromStdString(params.get(param_str));
float current_float = current_value.isEmpty() ? 0.0f : current_value.toFloat();
if (current_float == 0.0f) {
auto it = std::find_if(PARAM_CONFIGS.begin(), PARAM_CONFIGS.end(), [&param](const ParamConfig &c) { return c.param == param; });
if (it != PARAM_CONFIGS.end()) { current_float = it->defaultValue.toFloat(); params.put(param_str, QString::number(current_float, 'f', 2).toStdString()); }
}
value_label->setText(QString::number(current_float, 'f', 2));
auto updateValue = [=](float delta) {
float current = QString::fromStdString(params.get(param_str)).toFloat(), new_value = qMax(min_val, qMin(max_val, current + delta));
params.put(param_str, QString::number(new_value, 'f', 2).toStdString()); value_label->setText(QString::number(new_value, 'f', 2));
minus_btn->setEnabled(new_value > min_val); plus_btn->setEnabled(new_value < max_val);
};
connect(plus_btn, &QPushButton::clicked, [=]() { updateValue(step); });
connect(minus_btn, &QPushButton::clicked, [=]() { updateValue(-step); });
connect(plus_btn, &QPushButton::pressed, [=]() { QTimer::singleShot(300, plus_btn, [=]() { if (plus_btn->isDown()) plusTimer->start(100); });});
connect(plus_btn, &QPushButton::released, plusTimer, &QTimer::stop);
connect(plusTimer, &QTimer::timeout, [=]() { updateValue(step); });
connect(minus_btn, &QPushButton::pressed, [=]() {QTimer::singleShot(300, minus_btn, [=]() { if (minus_btn->isDown()) minusTimer->start(100); });});
connect(minus_btn, &QPushButton::released, minusTimer, &QTimer::stop);
connect(minusTimer, &QTimer::timeout, [=]() { updateValue(-step); });
minus_btn->setEnabled(current_float > min_val); plus_btn->setEnabled(current_float < max_val);
list->addItem(widget); spinner_controls[param.toStdString()] = value_label;
}
void HyundaiLiveTuning::updateToggles() {
for (const auto &pair : spinner_controls) {
QString param_value = QString::fromStdString(params.get(pair.first));
if (!param_value.isEmpty()) pair.second->setText(QString::number(param_value.toFloat(), 'f', 2));
}
}
void HyundaiLiveTuning::resetToDefaults() {
for (const auto &config : PARAM_CONFIGS) params.put(config.param.toStdString(), config.defaultValue.toStdString());
updateToggles();
}
void HyundaiLiveTuning::showEvent(QShowEvent *event){
updateToggles();
}

View File

@@ -0,0 +1,41 @@
/**
* 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 <QWidget>
#include <QTimer>
#include <QVBoxLayout>
#include <map>
#include "common/params.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
#include "selfdrive/ui/qt/widgets/input.h"
class HyundaiLiveTuning : public QWidget {
Q_OBJECT
public:
explicit HyundaiLiveTuning(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
void updateToggles();
signals:
void backPress();
private:
Params params;
QVBoxLayout *main_layout;
ListWidgetSP *list;
std::map<std::string, QLabel*> spinner_controls;
void resetToDefaults();
void createFloatControl(const QString &param, const QString &title, const QString &desc,
float min_val, float max_val, float step = 0.01f);
};

View File

@@ -7,25 +7,64 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h"
HyundaiSettings::HyundaiSettings(QWidget *parent) : BrandSettingsInterface(parent) {
std::vector<QString> tuning_texts{ tr("Off"), tr("Dynamic"), tr("Predictive") };
longitudinalTuningToggle = new ButtonParamControl(
"HyundaiLongitudinalTuning",
tr("Custom Longitudinal Tuning"),
"",
"",
tuning_texts,
500
);
QObject::connect(longitudinalTuningToggle, &ButtonParamControlSP::buttonClicked, this, &HyundaiSettings::updateSettings);
QWidget *stackedWidget = new QWidget();
main_layout = new QStackedLayout(stackedWidget);
settingsScreen = new QWidget();
QVBoxLayout *settingsLayout = new QVBoxLayout(settingsScreen);
settingsLayout->setContentsMargins(0, 0, 0, 0);
settingsLayout->addWidget(list);
longitudinalTuningToggle = new ButtonParamControlSP("HyundaiLongitudinalTuning", tr("Longitudinal Tuning"), "", "", {tr("Off"), tr("Dynamic"), tr("Predictive")}, 500);
connect(longitudinalTuningToggle, &ButtonParamControlSP::buttonClicked, this, &HyundaiSettings::updateSettings);
list->addItem(longitudinalTuningToggle);
longitudinalTuningToggle->showDescription();
customTuningToggle = new ParamControlSP("LongTuningCustomToggle", tr("Custom Tuning Override"), customTuningToggleDescription(), "");
connect(customTuningToggle, &ParamControlSP::toggleFlipped, this, [this](bool state) {
if (state && !ConfirmationDialog::alert(customTuningWarningMsg(), this)) {
customTuningToggle->refresh();
return;
}
updateSettings();
});
list->addItem(customTuningToggle);
customTuningToggle->showDescription();
customTuningButtonWidget = new QWidget();
QHBoxLayout *buttonLayout = new QHBoxLayout(customTuningButtonWidget);
buttonLayout->setContentsMargins(0, 0, 0, 0);
PushButtonSP *customTuningBtn = new PushButtonSP(tr("Customize Tune"));
connect(customTuningBtn, &PushButtonSP::clicked, this, [this]() {
if (!liveTuningWidget) {
liveTuningWidget = new HyundaiLiveTuning(this);
connect(liveTuningWidget, &HyundaiLiveTuning::backPress, this, [this]() {
main_layout->setCurrentWidget(settingsScreen);
emit liveTuningClosed();
});
main_layout->addWidget(liveTuningWidget);
}
main_layout->setCurrentWidget(liveTuningWidget);
emit liveTuningOpened();
});
buttonLayout->addWidget(customTuningBtn, 0, Qt::AlignLeft);
buttonLayout->addStretch();
list->addItem(customTuningButtonWidget);
customTuningButtonWidget->setVisible(false);
main_layout->addWidget(settingsScreen);
main_layout->setCurrentWidget(settingsScreen);
layout()->addWidget(stackedWidget);
}
void HyundaiSettings::updateSettings() {
auto longitudinal_tuning_param = std::atoi(params.get("HyundaiLongitudinalTuning").c_str());
bool custom_tuning_enabled = params.getBool("LongTuningCustomToggle");
uint32_t sp_flags = 0;
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty()) {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
@@ -36,22 +75,50 @@ void HyundaiSettings::updateSettings() {
has_longitudinal_control = false;
}
LongitudinalTuningOption longitudinal_tuning_option;
if (longitudinal_tuning_param == int(LongitudinalTuningOption::PREDICTIVE)) {
longitudinal_tuning_option = LongitudinalTuningOption::PREDICTIVE;
} else if (longitudinal_tuning_param == int(LongitudinalTuningOption::DYNAMIC)) {
longitudinal_tuning_option = LongitudinalTuningOption::DYNAMIC;
} else {
longitudinal_tuning_option = LongitudinalTuningOption::OFF;
if (!cp_sp_bytes.empty()) {
AlignedBuffer aligned_buf_sp;
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
sp_flags = CP_SP.getFlags();
}
bool longitudinal_tuning_disabled = !offroad || !has_longitudinal_control;
LongitudinalTuningOption longitudinal_tuning_option = (longitudinal_tuning_param == 2) ? LongitudinalTuningOption::PREDICTIVE :
(longitudinal_tuning_param == 1) ? LongitudinalTuningOption::DYNAMIC :
LongitudinalTuningOption::OFF;
bool longitudinal_tuning_disabled = !has_longitudinal_control;
QString longitudinal_tuning_description = longitudinalTuningDescription(longitudinal_tuning_option);
if (longitudinal_tuning_disabled) {
longitudinal_tuning_description = toggleDisableMsg(offroad, has_longitudinal_control);
}
longitudinalTuningToggle->setEnabled(!longitudinal_tuning_disabled);
longitudinalTuningToggle->setDescription(longitudinal_tuning_description);
longitudinalTuningToggle->showDescription();
bool tuning_available = !longitudinal_tuning_disabled && longitudinal_tuning_option != LongitudinalTuningOption::OFF;
customTuningToggle->setEnabled(tuning_available);
if (customTuningButtonWidget) {
customTuningButtonWidget->setVisible(custom_tuning_enabled && tuning_available);
}
if (custom_tuning_enabled && tuning_available) {
customTuningToggle->hideDescription();
} else {
QString desc = tuning_available ? customTuningToggleDescription() :
longitudinal_tuning_disabled ? toggleDisableMsg(offroad, has_longitudinal_control) :
customTuningDisabledMsg();
customTuningToggle->setDescription(desc);
customTuningToggle->showDescription();
}
if (!offroad) {
if (sp_flags & (16 | 32)) { // HyundaiFlagsSP.LONG_TUNING_DYNAMIC | HyundaiFlagsSP.LONG_TUNING_PREDICTIVE
longitudinalTuningToggle->setEnableSelectedButtons(!longitudinal_tuning_disabled, {1, 2});
} else {
longitudinalTuningToggle->setEnableSelectedButtons(!longitudinal_tuning_disabled);
}
} else {
longitudinalTuningToggle->setEnabled(!longitudinal_tuning_disabled);
}
}

View File

@@ -8,11 +8,13 @@
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_live_tuning.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/input.h"
enum class LongitudinalTuningOption {
OFF,
@@ -27,9 +29,18 @@ public:
explicit HyundaiSettings(QWidget *parent = nullptr);
void updateSettings() override;
signals:
void liveTuningOpened();
void liveTuningClosed();
private:
bool has_longitudinal_control = false;
ButtonParamControl *longitudinalTuningToggle = nullptr;
ButtonParamControlSP *longitudinalTuningToggle = nullptr;
ParamControlSP *customTuningToggle = nullptr;
HyundaiLiveTuning *liveTuningWidget = nullptr;
QStackedLayout *main_layout = nullptr;
QWidget *settingsScreen = nullptr;
QWidget *customTuningButtonWidget = nullptr;
static QString toggleDisableMsg(bool _offroad, bool _has_longitudinal_control) {
if (!_has_longitudinal_control) {
@@ -37,12 +48,24 @@ private:
}
if (!_offroad) {
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to select an option.");
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to select all available options.");
}
return QString();
}
static QString customTuningWarningMsg() {
return tr("Warning: Deviating from the preset tune can introduce undesirable behavior. Use at your own risk.");
}
static QString customTuningToggleDescription() {
return tr("When enabled, custom values override the selected tuning mode. ") + customTuningWarningMsg();
}
static QString customTuningDisabledMsg() {
return tr("Custom tuning is only available when longitudinal tuning is set to Dynamic or Predictive mode.");
}
static QString longitudinalTuningDescription(LongitudinalTuningOption option = LongitudinalTuningOption::OFF) {
QString off_str = tr("Off: Uses default tuning");
QString dynamic_str = tr("Dynamic: Adjusts acceleration limits based on current speed");

View File

@@ -62,6 +62,8 @@ void VehiclePanel::updateBrandSettings() {
currentBrandSettings->setContentsMargins(0, 0, 0, 0);
brandSettingsContainerLayout->addWidget(currentBrandSettings);
currentBrandSettings->updatePanel(offroad);
QObject::connect(currentBrandSettings, SIGNAL(liveTuningOpened()), platformSelector, SLOT(hide()));
QObject::connect(currentBrandSettings, SIGNAL(liveTuningClosed()), platformSelector, SLOT(show()));
}
}
}

View File

@@ -55,7 +55,12 @@ def initialize_params(params) -> list[dict[str, Any]]:
# hyundai
keys.extend([
"HyundaiLongitudinalTuning"
"HyundaiLongitudinalTuning",
"LongTuningCustomToggle",
"LongTuningAccelMax",
"LongTuningMinUpperJerk",
"LongTuningMinLowerJerk",
"LongTuningJerkLimits",
])
return [{k: params.get(k, return_default=True)} for k in keys]

View File

@@ -20,6 +20,16 @@ class ParamStore:
universal_params: list[str] = []
brand_params: list[str] = []
if CP.brand == "hyundai":
brand_params.extend([
"HyundaiLongitudinalTuning",
"LongTuningCustomToggle",
"LongTuningAccelMax",
"LongTuningMinUpperJerk",
"LongTuningMinLowerJerk",
"LongTuningJerkLimits",
])
self.keys = universal_params + brand_params
self.values = {}
self.cached_params_list: list[capnp.lib.capnp._DynamicStructBuilder] | None = None