Merge branch 'master-new' of https://github.com/sunnypilot/sunnypilot into master-new

This commit is contained in:
infiniteCable2
2025-06-13 17:56:26 +02:00
12 changed files with 387 additions and 7 deletions
+3
View File
@@ -139,6 +139,9 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"CarParamsSPCache", CLEAR_ON_MANAGER_START},
{"CarParamsSPPersistent", PERSISTENT},
{"CarPlatformBundle", PERSISTENT},
{"CustomAccIncrementsEnabled", PERSISTENT | BACKUP},
{"CustomAccLongPressIncrement", PERSISTENT | BACKUP},
{"CustomAccShortPressIncrement", PERSISTENT | BACKUP},
{"DeviceBootMode", PERSISTENT | BACKUP},
{"EnableGithubRunner", PERSISTENT | BACKUP},
{"MaxTimeOffroad", PERSISTENT | BACKUP},
+1
View File
@@ -307,6 +307,7 @@ class Car:
# sunnypilot
self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl")
self.v_cruise_helper.read_custom_set_speed_params()
time.sleep(0.1)
+4 -2
View File
@@ -3,6 +3,7 @@ import numpy as np
from cereal import car
from openpilot.common.conversions import Conversions as CV
from openpilot.sunnypilot.selfdrive.car.cruise_ext import VCruiseHelperSP
# WARNING: this value was determined based on the model's training distribution,
@@ -28,8 +29,9 @@ CRUISE_INTERVAL_SIGN = {
}
class VCruiseHelper:
class VCruiseHelper(VCruiseHelperSP):
def __init__(self, CP):
VCruiseHelperSP.__init__(self)
self.CP = CP
self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET
@@ -117,7 +119,7 @@ class VCruiseHelper:
if not self.button_change_states[button_type]["enabled"]:
return
v_cruise_delta = v_cruise_delta * (5 if long_press else 1)
long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
else:
+5 -1
View File
@@ -49,6 +49,10 @@ lateral_panel_qt_src = [
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
]
longitudinal_panel_qt_src = [
"sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.cc",
]
network_src = [
"sunnypilot/qt/network/sunnylink/sunnylink_client.cc",
"sunnypilot/qt/network/sunnylink/services/base_device_service.cc",
@@ -83,7 +87,7 @@ brand_settings_qt_src = [
sp_widgets_src = widgets_src + network_src
sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src + osm_panel_qt_src
sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src + longitudinal_panel_qt_src + osm_panel_qt_src
sp_qt_util = qt_util
Export('sp_widgets_src', 'sp_qt_src', "sp_qt_util")
@@ -0,0 +1,47 @@
/**
* 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/longitudinal/custom_acc_increment.h"
CustomAccIncrement::CustomAccIncrement(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: ExpandableToggleRow(param, title, desc, icon, parent) {
auto *accFrame = new QFrame(this);
auto *accFrameLayout = new QGridLayout();
accFrame->setLayout(accFrameLayout);
accFrameLayout->setSpacing(0);
auto *shortPressControl = new AccIncrementOptionControl("CustomAccShortPressIncrement", {1, 10}, 1);
connect(shortPressControl, &OptionControlSP::updateLabels, shortPressControl, &AccIncrementOptionControl::refresh);
auto *longPressControl = new AccIncrementOptionControl("CustomAccLongPressIncrement", {1, 3}, 1, &customLongValues);
connect(longPressControl, &OptionControlSP::updateLabels, longPressControl, &AccIncrementOptionControl::refresh);
shortPressControl->setFixedWidth(280);
longPressControl->setFixedWidth(280);
accFrameLayout->addWidget(shortPressControl, 0, 0, Qt::AlignLeft);
accFrameLayout->addWidget(longPressControl, 0, 1, Qt::AlignRight);
addItem(accFrame);
}
AccIncrementOptionControl::AccIncrementOptionControl(const QString &param, const MinMaxValue &range, const int per_value_change, const QMap<QString, QString> *valMap)
: OptionControlSP(param, "", "", "", range, per_value_change, true, valMap) {
param_name = param.toStdString();
refresh();
}
void AccIncrementOptionControl::refresh() {
std::string val = params.get(param_name);
std::string label = "<span style='font-size: 45px; font-weight: 450; color: #FFFFFF;'>";
label += param_name == "CustomAccShortPressIncrement" ? "Short Press" : "Long Press";
label += " <br><span style='font-size: 40px; font-weight: 450; color:rgb(174, 255, 195);'>" + val;
label += param_name == "CustomAccShortPressIncrement"
? (val == "1" ? " (Default)" : "")
: (val == "5" ? " (Default)" : "");
label += "</span></span>";
setLabel(QString::fromStdString(label));
}
@@ -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 "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/sunnypilot/qt/widgets/expandable_row.h"
class CustomAccIncrement : public ExpandableToggleRow {
Q_OBJECT
public:
CustomAccIncrement(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
private:
QMap<QString, QString> customLongValues = {
{"1", "1"},
{"2", "5"}, // Default
{"3", "10"}
};
};
class AccIncrementOptionControl : public OptionControlSP {
Q_OBJECT
public:
AccIncrementOptionControl(const QString &param, const MinMaxValue &range, int per_value_change, const QMap<QString, QString> *valMap = nullptr);
void refresh();
protected:
std::string param_name;
private:
Params params;
};
@@ -8,4 +8,72 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
main_layout = new QStackedLayout(this);
ListWidget *list = new ListWidget(this, false);
cruisePanelScreen = new QWidget(this);
QVBoxLayout *vlayout = new QVBoxLayout(cruisePanelScreen);
vlayout->setContentsMargins(0, 0, 0, 0);
cruisePanelScroller = new ScrollViewSP(list, this);
vlayout->addWidget(cruisePanelScroller);
customAccIncrement = new CustomAccIncrement("CustomAccIncrementsEnabled", tr("Custom ACC Speed Increments"), "", "", this);
list->addItem(customAccIncrement);
QObject::connect(uiState(), &UIState::offroadTransition, this, &LongitudinalPanel::refresh);
main_layout->addWidget(cruisePanelScreen);
main_layout->setCurrentWidget(cruisePanelScreen);
refresh(offroad);
}
void LongitudinalPanel::showEvent(QShowEvent *event) {
main_layout->setCurrentWidget(cruisePanelScreen);
refresh(offroad);
}
void LongitudinalPanel::refresh(bool _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<cereal::CarParams>();
has_longitudinal_control = hasLongitudinalControl(CP);
is_pcm_cruise = CP.getPcmCruise();
} else {
has_longitudinal_control = false;
is_pcm_cruise = false;
}
QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease.");
QString accNoLongDescription = tr("This feature can only be used with openpilot longitudinal control enabled.");
QString accPcmCruiseDisabledDescription = tr("This feature is not supported on this platform due to vehicle limitations.");
QString onroadOnlyDescription = tr("Start the vehicle to check vehicle compatibility.");
if (offroad) {
customAccIncrement->setDescription(onroadOnlyDescription);
customAccIncrement->showDescription();
} else {
if (has_longitudinal_control) {
if (is_pcm_cruise) {
customAccIncrement->setDescription(accPcmCruiseDisabledDescription);
customAccIncrement->showDescription();
} else {
customAccIncrement->setDescription(accEnabledDescription);
}
} else {
params.remove("CustomAccIncrementsEnabled");
customAccIncrement->toggleFlipped(false);
customAccIncrement->setDescription(accNoLongDescription);
customAccIncrement->showDescription();
}
}
// enable toggle when long is available and is not PCM cruise
customAccIncrement->setEnabled(has_longitudinal_control && !is_pcm_cruise && !offroad);
customAccIncrement->refresh();
offroad = _offroad;
}
@@ -7,11 +7,26 @@
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class LongitudinalPanel : public QWidget {
Q_OBJECT
public:
explicit LongitudinalPanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
void refresh(bool _offroad);
private:
Params params;
bool has_longitudinal_control = false;
bool is_pcm_cruise = false;
bool offroad = false;
QStackedLayout *main_layout = nullptr;
ScrollViewSP *cruisePanelScroller = nullptr;
QWidget *cruisePanelScreen = nullptr;
CustomAccIncrement *customAccIncrement = nullptr;
};
+4 -4
View File
@@ -246,10 +246,10 @@ def setup_settings_steering_alc(click, pm: PubMaster, scroll=None):
click(970, 534)
time.sleep(UI_DELAY)
def setup_settings_driving(click, pm: PubMaster, scroll=None):
def setup_settings_cruise(click, pm: PubMaster, scroll=None):
setup_settings_device(click, pm)
scroll(-1, 278, 962)
click(278, 962)
scroll(-400, 278, 962)
click(278, 324)
time.sleep(UI_DELAY)
def setup_settings_visuals(click, pm: PubMaster, scroll=None):
@@ -312,7 +312,7 @@ CASES.update({
"settings_steering": setup_settings_steering,
"settings_steering_mads": setup_settings_steering_mads,
"settings_steering_alc": setup_settings_steering_alc,
"settings_driving": setup_settings_driving,
"settings_cruise": setup_settings_cruise,
"settings_visuals": setup_settings_visuals,
"settings_trips": setup_settings_trips,
"settings_vehicle": setup_settings_vehicle,
+47
View File
@@ -0,0 +1,47 @@
"""
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.
"""
import numpy as np
from cereal import car
from openpilot.common.params import Params
ButtonType = car.CarState.ButtonEvent.Type
class VCruiseHelperSP:
def __init__(self) -> None:
self.params = Params()
self.custom_acc_enabled = self.params.get_bool("CustomAccIncrementsEnabled")
self.short_increment = self.read_int_param("CustomAccShortPressIncrement", 1)
self.long_increment = self.read_int_param("CustomAccLongPressIncrement", 5)
def read_int_param(self, key: str, default: int = 0) -> int:
try:
return int(self.params.get(key, encoding='utf8'))
except (ValueError, TypeError):
return default
def read_custom_set_speed_params(self) -> None:
self.custom_acc_enabled = self.params.get_bool("CustomAccIncrementsEnabled")
self.short_increment = self.read_int_param("CustomAccShortPressIncrement", 1)
self.long_increment = self.read_int_param("CustomAccLongPressIncrement", 5)
def update_v_cruise_delta(self, long_press: bool, v_cruise_delta: float) -> tuple[bool, float]:
if not self.custom_acc_enabled:
v_cruise_delta = v_cruise_delta * (5 if long_press else 1)
return long_press, v_cruise_delta
# Apply user-specified multipliers to the base increment
short_increment = np.clip(self.short_increment, 1, 10)
long_increment = np.clip(self.long_increment, 1, 10)
actual_increment = long_increment if long_press else short_increment
round_to_nearest = actual_increment in (5, 10)
v_cruise_delta = v_cruise_delta * actual_increment
return round_to_nearest, v_cruise_delta
@@ -0,0 +1,149 @@
import pytest
from parameterized import parameterized_class
from cereal import car
from openpilot.common.conversions import Conversions as CV
from openpilot.common.params import Params
from openpilot.selfdrive.car.cruise import V_CRUISE_INITIAL
from openpilot.selfdrive.car.tests.test_cruise_speed import TestVCruiseHelper
ButtonEvent = car.CarState.ButtonEvent
ButtonType = car.CarState.ButtonEvent.Type
@parameterized_class(('pcm_cruise',), [(False,)])
class TestCustomAccIncrements(TestVCruiseHelper):
def setup_method(self):
TestVCruiseHelper.setup_method(self)
self.params = Params()
self.reset_custom_params()
def reset_custom_params(self) -> None:
"""Reset to default custom ACC parameters"""
self.params.put_bool("CustomAccIncrementsEnabled", False)
self.params.put("CustomAccShortPressIncrement", "1")
self.params.put("CustomAccLongPressIncrement", "5")
self.v_cruise_helper.read_custom_set_speed_params()
def press_button_short(self, button_type: car.CarState.ButtonEvent.Type) -> None:
"""Simulate a short button press (press + release)"""
CS = car.CarState(cruiseState={"available": True})
CS.buttonEvents = [ButtonEvent(type=button_type, pressed=True)]
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
CS.buttonEvents = [ButtonEvent(type=button_type, pressed=False)]
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
def press_button_long(self, button_type: car.CarState.ButtonEvent.Type) -> None:
"""Simulate a long button press (50+ frames)"""
CS = car.CarState(cruiseState={"available": True})
CS.buttonEvents = [ButtonEvent(type=button_type, pressed=True)]
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
# Hold for 50 frames to trigger long press
CS.buttonEvents = []
for _ in range(50):
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
CS.buttonEvents = [ButtonEvent(type=button_type, pressed=False)]
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
def set_custom_increments(self, enabled: bool, short_inc: int, long_inc: int) -> None:
"""Set custom ACC increment parameters"""
self.params.put_bool("CustomAccIncrementsEnabled", enabled)
self.params.put("CustomAccShortPressIncrement", str(short_inc))
self.params.put("CustomAccLongPressIncrement", str(long_inc))
self.v_cruise_helper.read_custom_set_speed_params()
def test_default_behavior_when_disabled(self):
"""Test that default increments are used when custom ACC is disabled"""
self.set_custom_increments(enabled=False, short_inc=5, long_inc=10)
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False, False)
initial_speed = self.v_cruise_helper.v_cruise_kph
# Short press should increment by 1 (default)
self.press_button_short(ButtonType.accelCruise)
assert self.v_cruise_helper.v_cruise_kph == initial_speed + 1
@pytest.mark.parametrize("increment", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
def test_custom_short_press_increments(self, increment):
"""Test custom short press increments (1-10)"""
self.set_custom_increments(enabled=True, short_inc=increment, long_inc=5)
self.enable(50 * CV.KPH_TO_MS, False, False)
initial_speed = self.v_cruise_helper.v_cruise_kph
self.press_button_short(ButtonType.accelCruise)
if increment in (5, 10):
# Should round to nearest increment
expected_speed = ((initial_speed // increment) + 1) * increment
else:
expected_speed = initial_speed + increment
assert self.v_cruise_helper.v_cruise_kph == expected_speed
@pytest.mark.parametrize("increment", (1, 5, 10))
def test_custom_long_press_increments(self, increment):
"""Test custom long press increments (1, 5, 10)"""
self.set_custom_increments(enabled=True, short_inc=1, long_inc=increment)
self.enable(50 * CV.KPH_TO_MS, False, False)
initial_speed = self.v_cruise_helper.v_cruise_kph
self.press_button_long(ButtonType.accelCruise)
if increment in (5, 10):
# Should round to nearest increment
expected_speed = ((initial_speed // increment) + 1) * increment
else:
expected_speed = initial_speed + increment
assert self.v_cruise_helper.v_cruise_kph == expected_speed
@pytest.mark.parametrize("button_type", [ButtonType.accelCruise, ButtonType.decelCruise])
def test_accel_decel_symmetry(self, button_type):
"""Test that acceleration and deceleration work symmetrically"""
self.set_custom_increments(enabled=True, short_inc=3, long_inc=5)
self.enable(50 * CV.KPH_TO_MS, False, False)
initial_speed = self.v_cruise_helper.v_cruise_kph
self.press_button_short(button_type)
expected_change = 3 if button_type == ButtonType.accelCruise else -3
assert self.v_cruise_helper.v_cruise_kph == initial_speed + expected_change
def test_rounding_behavior(self):
"""Test rounding behavior for 5 and 10 increments"""
test_cases = [
(47, 5, 50), # 47 -> 50 (round up to next 5)
(45, 5, 50), # 45 -> 50 (already at 5, increment by 5)
(43, 10, 50), # 43 -> 50 (round up to next 10)
(40, 10, 50), # 40 -> 50 (already at 10, increment by 10)
]
for initial, increment, expected in test_cases:
self.set_custom_increments(enabled=True, short_inc=increment, long_inc=increment)
self.reset_cruise_speed_state()
self.enable(initial * CV.KPH_TO_MS, False, False)
self.press_button_short(ButtonType.accelCruise)
assert self.v_cruise_helper.v_cruise_kph == expected
def test_invalid_values_fallback(self):
"""Test that invalid values fallback to safe defaults"""
# Test invalid short increment
self.set_custom_increments(enabled=True, short_inc=-1, long_inc=5)
self.enable(50 * CV.KPH_TO_MS, False, False)
initial_speed = self.v_cruise_helper.v_cruise_kph
self.press_button_short(ButtonType.accelCruise)
assert self.v_cruise_helper.v_cruise_kph == initial_speed + 1 # Should fallback to 1
# Test invalid long increment
self.reset_cruise_speed_state()
self.set_custom_increments(enabled=True, short_inc=1, long_inc=99)
self.enable(50 * CV.KPH_TO_MS, False, False)
initial_speed = self.v_cruise_helper.v_cruise_kph
self.press_button_long(ButtonType.accelCruise)
assert self.v_cruise_helper.v_cruise_kph == initial_speed + 10 # Should fallback to 10
+3
View File
@@ -49,6 +49,9 @@ def manager_init() -> None:
("BlindSpot", "0"),
("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h
("BlinkerPauseLateralControl", "0"),
("CustomAccIncrementsEnabled", "0"),
("CustomAccLongPressIncrement", "5"),
("CustomAccShortPressIncrement", "1"),
("DeviceBootMode", "0"),
("DynamicExperimentalControl", "0"),
("HyundaiLongitudinalTuning", "0"),