diff --git a/common/params_keys.h b/common/params_keys.h index cd2df17634..1b4c6953c0 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -125,6 +125,8 @@ inline static std::unordered_map keys = { {"ApiCache_DriveStats", PERSISTENT}, {"AutoLaneChangeBsmDelay", PERSISTENT}, {"AutoLaneChangeTimer", PERSISTENT}, + {"BlinkerMinLateralControlSpeed", PERSISTENT | BACKUP}, + {"BlinkerPauseLateralControl", PERSISTENT | BACKUP}, {"CarParamsSP", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"CarParamsSPCache", CLEAR_ON_MANAGER_START}, {"CarParamsSPPersistent", PERSISTENT}, diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index 41527fe692..7754e6e5ba 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -79,7 +79,8 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) { } void Networking::setPrimeType(PrimeState::Type type) { - an->setGsmVisible(type == PrimeState::PRIME_TYPE_NONE || type == PrimeState::PRIME_TYPE_LITE); + an->setGsmVisible(type == PrimeState::PRIME_TYPE_NONE || type == PrimeState::PRIME_TYPE_UNKNOWN || \ + type == PrimeState::PRIME_TYPE_PURPLE || type == PrimeState::PRIME_TYPE_LITE); wifi->ipv4_forward = (type == PrimeState::PRIME_TYPE_NONE || type == PrimeState::PRIME_TYPE_LITE); } diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 8c40998a6f..3c2b32de02 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -41,6 +41,7 @@ qt_src = [ ] lateral_panel_qt_src = [ + "sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc", "sunnypilot/qt/offroad/settings/lateral/lane_change_settings.cc", "sunnypilot/qt/offroad/settings/lateral/mads_settings.cc", "sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc new file mode 100644 index 0000000000..b43c6162c6 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc @@ -0,0 +1,27 @@ +/** + * 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/lateral/blinker_pause_lateral_settings.h" + +BlinkerPauseLateralSettings::BlinkerPauseLateralSettings(const QString ¶m, const QString &title, const QString &description, const QString &icon, QWidget *parent) + : ExpandableToggleRow(param, title, description, icon, parent) { + + pauseLateralSpeed = new OptionControlSP("BlinkerMinLateralControlSpeed", "", "", "", {0, 255}, 5); + connect(pauseLateralSpeed, &OptionControlSP::updateLabels, this, &BlinkerPauseLateralSettings::refresh); + addItem(pauseLateralSpeed); + + refresh(); +} + +void BlinkerPauseLateralSettings::refresh() { + const QString option = QString::fromStdString(params.get("BlinkerMinLateralControlSpeed")); + const bool is_metric = params.getBool("IsMetric"); + const QString unit = is_metric ? "km/h" : "mph"; + + pauseLateralSpeed->setLabel(option + " " + unit); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h new file mode 100644 index 0000000000..7f02337b9c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h @@ -0,0 +1,25 @@ +/** + * 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 BlinkerPauseLateralSettings : public ExpandableToggleRow { + Q_OBJECT + +public: + BlinkerPauseLateralSettings(const QString ¶m, const QString &title, const QString &description, const QString &icon, QWidget *parent = nullptr); + void refresh(); + +private: + Params params; + OptionControlSP *pauseLateralSpeed; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc index 0beae6e24f..ae954d7539 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc @@ -64,6 +64,17 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) { list->addItem(vertical_space(0)); list->addItem(horizontal_line()); + // Blinker Pause Lateral Control + blinkerPauseLateralSettings = new BlinkerPauseLateralSettings( + "BlinkerPauseLateralControl", + tr("Pause Lateral Control with Blinker"), + tr("Pause lateral control with blinker when traveling below the desired speed selected."), + "", + this); + list->addItem(blinkerPauseLateralSettings); + + list->addItem(horizontal_line()); + // Neural Network Lateral Control nnlcToggle = new NeuralNetworkLateralControl(); list->addItem(nnlcToggle); @@ -139,5 +150,7 @@ void LateralPanel::updateToggles(bool _offroad) { madsSettingsButton->setEnabled(madsToggle->isToggled()); + blinkerPauseLateralSettings->refresh(); + 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 cf003dfaf3..7b5e73fceb 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h @@ -11,6 +11,7 @@ #include #include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_change_settings.h" @@ -43,6 +44,7 @@ private: PushButtonSP *laneChangeSettingsButton; LaneChangeSettings *laneChangeWidget = nullptr; NeuralNetworkLateralControl *nnlcToggle = nullptr; + BlinkerPauseLateralSettings *blinkerPauseLateralSettings = nullptr; const QString MADS_BASE_DESC = tr("Enables independent engagements of Automatic Lane Centering (ALC) and Adaptive Cruise Control (ACC)."); diff --git a/sunnypilot/selfdrive/controls/controlsd_ext.py b/sunnypilot/selfdrive/controls/controlsd_ext.py index 4978c01332..9a7f0cb5fc 100644 --- a/sunnypilot/selfdrive/controls/controlsd_ext.py +++ b/sunnypilot/selfdrive/controls/controlsd_ext.py @@ -11,12 +11,14 @@ from opendbc.car import structs from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.sunnypilot.selfdrive.controls.lib.param_store import ParamStore +from openpilot.sunnypilot.selfdrive.controls.lib.blinker_pause_lateral import BlinkerPauseLateral class ControlsExt: def __init__(self, CP: structs.CarParams, params: Params): self.CP = CP self.params = params + self.blinker_pause_lateral = BlinkerPauseLateral() self.param_store = ParamStore(self.CP) self.get_params_sp() @@ -29,11 +31,13 @@ class ControlsExt: def get_params_sp(self) -> None: self.param_store.update(self.params) + self.blinker_pause_lateral.get_params() + + def get_lat_active(self, sm: messaging.SubMaster) -> bool: + if self.blinker_pause_lateral.update(sm['carState']): + return False - @staticmethod - def get_lat_active(sm: messaging.SubMaster) -> bool: ss_sp = sm['selfdriveStateSP'] - if ss_sp.mads.available: return bool(ss_sp.mads.active) diff --git a/sunnypilot/selfdrive/controls/lib/blinker_pause_lateral.py b/sunnypilot/selfdrive/controls/lib/blinker_pause_lateral.py new file mode 100644 index 0000000000..fb86280358 --- /dev/null +++ b/sunnypilot/selfdrive/controls/lib/blinker_pause_lateral.py @@ -0,0 +1,34 @@ +""" +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. +""" +from cereal import car + +from openpilot.common.conversions import Conversions as CV +from openpilot.common.params import Params + + +class BlinkerPauseLateral: + def __init__(self): + self.params = Params() + + self.enabled = self.params.get_bool("BlinkerPauseLateralControl") + self.is_metric = self.params.get_bool("IsMetric") + self.min_speed = 0 + + def get_params(self) -> None: + self.enabled = self.params.get_bool("BlinkerPauseLateralControl") + self.is_metric = self.params.get_bool("IsMetric") + self.min_speed = int(self.params.get("BlinkerMinLateralControlSpeed", encoding='utf8')) + + def update(self, CS: car.CarState) -> bool: + if not self.enabled: + return False + + one_blinker = CS.leftBlinker != CS.rightBlinker + speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS + min_speed_ms = self.min_speed * speed_factor + + return bool(one_blinker and CS.vEgo < min_speed_ms) diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit_controller/speed_limit_controller.py b/sunnypilot/selfdrive/controls/lib/speed_limit_controller/speed_limit_controller.py index f2c704f606..0ec7445ebb 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit_controller/speed_limit_controller.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit_controller/speed_limit_controller.py @@ -1,3 +1,4 @@ +from collections.abc import Callable import numpy as np import time @@ -20,10 +21,14 @@ ENABLED_STATES = (SpeedLimitControlState.preActive, SpeedLimitControlState.tempI class SpeedLimitController: + _speed_limit: float + _distance: float + _source: Source + def __init__(self, CP): self._params = Params() self._CP = CP - self._policy = Policy(int(self._params.get("SpeedLimitControlPolicy", encoding='utf8'))) + self._policy = self._read_policy_param() self._resolver = SpeedLimitResolver(self._policy) self._last_params_update = 0.0 self._last_op_enabled_time = 0.0 @@ -48,12 +53,12 @@ class SpeedLimitController: self._gas_pressed = False self._pcm_cruise_op_long = CP.openpilotLongitudinalControl and CP.pcmCruise - self._offset_type = int(self._params.get("SpeedLimitOffsetType", encoding='utf8')) - self._offset_value = float(self._params.get("SpeedLimitValueOffset", encoding='utf8')) - self._warning_type = int(self._params.get("SpeedLimitWarningType", encoding='utf8')) - self._warning_offset_type = int(self._params.get("SpeedLimitWarningOffsetType", encoding='utf8')) - self._warning_offset_value = float(self._params.get("SpeedLimitWarningValueOffset", encoding='utf8')) - self._engage_type = np.clip(int(self._params.get("SpeedLimitEngageType", encoding='utf8')), Engage.auto, Engage.user_confirm) + self._offset_type = OffsetType(self._read_int_param("SpeedLimitOffsetType")) + self._offset_value = self._read_int_param("SpeedLimitValueOffset") + self._warning_type = self._read_int_param("SpeedLimitWarningType") + self._warning_offset_type = OffsetType(self._read_int_param("SpeedLimitWarningOffsetType")) + self._warning_offset_value = self._read_int_param("SpeedLimitWarningValueOffset") + self._engage_type = self._read_engage_type_param() self._brake_pressed = False self._brake_pressed_prev = False self._current_time = 0. @@ -61,7 +66,7 @@ class SpeedLimitController: self._v_cruise_prev_rounded = 0. self._speed_limit_offsetted_rounded = 0. self._speed_limit_warning_offsetted_rounded = 0. - self._ms_to_local = CV.MS_TO_KPH if self._is_metric else CV.MS_TO_MPH + self._speed_factor = CV.MS_TO_KPH if self._is_metric else CV.MS_TO_MPH # Mapping functions to state transitions self.state_transition_strategy = { @@ -114,23 +119,11 @@ class SpeedLimitController: @property def speed_limit_offset(self) -> float: - if self._offset_type == OffsetType.default: - return float(np.interp(self._speed_limit, LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V) * self._speed_limit) - elif self._offset_type == OffsetType.fixed: - return self._offset_value * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS) - elif self._offset_type == OffsetType.percentage: - return self._offset_value * 0.01 * self._speed_limit - return 0. + return self._get_offset(self._offset_type, self._offset_value) @property def speed_limit_warning_offset(self) -> float: - if self._warning_offset_type == OffsetType.default: - return float(np.interp(self._speed_limit, LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V) * self._speed_limit) - elif self._warning_offset_type == OffsetType.fixed: - return self._warning_offset_value * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS) - elif self._warning_offset_type == OffsetType.percentage: - return self._warning_offset_value * 0.01 * self._speed_limit - return 0. + return self._get_offset(self._warning_offset_type, self._warning_offset_value) @property def speed_limit(self) -> float: @@ -144,26 +137,57 @@ class SpeedLimitController: def source(self) -> Source: return self._source + def _get_offset(self, offset_type: OffsetType, offset_value: int) -> float: + if offset_type == OffsetType.default: + return float(np.interp(self._speed_limit, LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V) * self._speed_limit) + elif offset_type == OffsetType.fixed: + return offset_value * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS) + elif offset_type == OffsetType.percentage: + return offset_value * 0.01 * self._speed_limit + else: + raise NotImplementedError("Offset not supported") + def _update_v_cruise_setpoint_prev(self) -> None: self._v_cruise_setpoint_prev = self._v_cruise_setpoint def _update_params(self) -> None: if self._current_time > self._last_params_update + PARAMS_UPDATE_PERIOD: self._is_enabled = self._params.get_bool("SpeedLimitControl") - self._offset_type = int(self._params.get("SpeedLimitOffsetType", encoding='utf8')) - self._offset_value = float(self._params.get("SpeedLimitValueOffset", encoding='utf8')) - self._warning_type = int(self._params.get("SpeedLimitWarningType", encoding='utf8')) - self._warning_offset_type = int(self._params.get("SpeedLimitWarningOffsetType", encoding='utf8')) - self._warning_offset_value = float(self._params.get("SpeedLimitWarningValueOffset", encoding='utf8')) - self._policy = Policy(int(self._params.get("SpeedLimitControlPolicy", encoding='utf8'))) + self._offset_type = OffsetType(self._read_int_param("SpeedLimitOffsetType")) + self._offset_value = self._read_int_param("SpeedLimitValueOffset") + self._warning_type = self._read_int_param("SpeedLimitWarningType") + self._warning_offset_type = OffsetType(self._read_int_param("SpeedLimitWarningOffsetType")) + self._warning_offset_value = self._read_int_param("SpeedLimitWarningValueOffset") + self._policy = self._read_policy_param() self._is_metric = self._params.get_bool("IsMetric") - self._ms_to_local = CV.MS_TO_KPH if self._is_metric else CV.MS_TO_MPH + self._speed_factor = CV.MS_TO_KPH if self._is_metric else CV.MS_TO_MPH self._resolver.change_policy(self._policy) - self._engage_type = Engage.auto if self._pcm_cruise_op_long else \ - np.clip(int(self._params.get("SpeedLimitEngageType", encoding='utf8')), Engage.auto, Engage.user_confirm) - debug(f'Updated Speed limit params. enabled: {self._is_enabled}, with offset: {self._offset_type}') + self._engage_type = self._read_engage_type_param() + self._last_params_update = self._current_time + def _read_policy_param(self) -> Policy: + try: + return Policy(int(self._params.get("SpeedLimitControlPolicy", encoding='utf8'))) + except (ValueError, TypeError): + return Policy.car_state_priority + + def _read_engage_type_param(self) -> Engage: + if self._pcm_cruise_op_long: + return Engage.auto + + return Engage(self._read_int_param("SpeedLimitEngageType", validator=lambda x: np.clip(x, Engage.auto, Engage.user_confirm))) + + def _read_int_param(self, key: str, default: int = 0, validator: Callable[[int], int] = None) -> int: + try: + val = int(self._params.get(key, encoding='utf8')) + + if validator is not None: + return validator(val) + return val + except (ValueError, TypeError): + return default + def _update_calculations(self) -> None: # Update current velocity offset (error) self._v_offset = self.speed_limit_offseted - self._v_ego @@ -184,11 +208,11 @@ class SpeedLimitController: self._op_enabled_prev = self._op_enabled self._brake_pressed_prev = self._brake_pressed - self._v_cruise_rounded = int(round(self._v_cruise_setpoint * self._ms_to_local)) - self._v_cruise_prev_rounded = int(round(self._v_cruise_setpoint_prev * self._ms_to_local)) - self._speed_limit_offsetted_rounded = 0 if self._speed_limit == 0 else int(round((self._speed_limit + self.speed_limit_offset) * self._ms_to_local)) + self._v_cruise_rounded = int(round(self._v_cruise_setpoint * self._speed_factor)) + self._v_cruise_prev_rounded = int(round(self._v_cruise_setpoint_prev * self._speed_factor)) + self._speed_limit_offsetted_rounded = 0 if self._speed_limit == 0 else int(round((self._speed_limit + self.speed_limit_offset) * self._speed_factor)) self._speed_limit_warning_offsetted_rounded = 0 if self._speed_limit == 0 else \ - int(round((self._speed_limit + self.speed_limit_warning_offset) * self._ms_to_local)) + int(round((self._speed_limit + self.speed_limit_warning_offset) * self._speed_factor)) def transition_state_from_inactive(self) -> None: """ Make state transition from inactive state """ @@ -272,27 +296,27 @@ class SpeedLimitController: def get_current_acceleration_as_target(self) -> float: """ When state is inactive or tempInactive, preserve current acceleration """ - return self._a_ego + return float(self._a_ego) def get_adapting_state_target_acceleration(self) -> float: """ In adapting state, calculate target acceleration based on speed limit and current velocity """ if self.distance > 0: - return (self.speed_limit_offseted ** 2 - self._v_ego ** 2) / (2. * self.distance) + return float((self.speed_limit_offseted ** 2 - self._v_ego ** 2) / (2. * self.distance)) - return self._v_offset / ModelConstants.T_IDXS[CONTROL_N] + return float(self._v_offset / ModelConstants.T_IDXS[CONTROL_N]) def get_active_state_target_acceleration(self) -> float: """ In active state, aim to keep speed constant around control time horizon """ - return self._v_offset / ModelConstants.T_IDXS[CONTROL_N] + return float(self._v_offset / ModelConstants.T_IDXS[CONTROL_N]) def _update_events(self, events_sp: EventsSP) -> None: if self._speed_limit > 0 and self._warning_type == 2 and \ - self._speed_limit_warning_offsetted_rounded < int(round(self._v_ego * self._ms_to_local)): + self._speed_limit_warning_offsetted_rounded < int(round(self._v_ego * self._speed_factor)): events_sp.add(EventNameSP.speedLimitPreActive) if not self.is_active: if self._state == SpeedLimitControlState.preActive and self._state_prev != SpeedLimitControlState.preActive and \ - self._v_cruise_rounded != self._speed_limit_offsetted_rounded: + self._v_cruise_rounded != self._speed_limit_offsetted_rounded: events_sp.add(EventNameSP.speedLimitPreActive) else: if self._engage_type == Engage.user_confirm: diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit_controller/speed_limit_resolver.py b/sunnypilot/selfdrive/controls/lib/speed_limit_controller/speed_limit_resolver.py index d04b8ae923..65c1a97c9f 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit_controller/speed_limit_resolver.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit_controller/speed_limit_resolver.py @@ -7,9 +7,12 @@ from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller.helpers class SpeedLimitResolver: + _limit_solutions: dict[Source, float] # Store for speed limit solutions from different sources + _distance_solutions: dict[Source, float] # Store for distance to current speed limit start for different sources + def __init__(self, policy: Policy): - self._limit_solutions = {} # Store for speed limit solutions from different sources - self._distance_solutions = {} # Store for distance to current speed limit start for different sources + self._limit_solutions = {} + self._distance_solutions = {} self._policy = policy self._policy_to_sources_map = { diff --git a/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py b/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py new file mode 100644 index 0000000000..6ba1dcef41 --- /dev/null +++ b/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py @@ -0,0 +1,130 @@ +""" +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. +""" +from cereal import car + +from openpilot.common.conversions import Conversions as CV +from openpilot.sunnypilot.selfdrive.controls.lib.blinker_pause_lateral import BlinkerPauseLateral + + +class TestBlinkerPauseLateral: + + def setup_method(self): + self.blinker_pause_lateral = BlinkerPauseLateral() + self._reset_states() + + def _reset_states(self): + self.blinker_pause_lateral.enabled = True + self.blinker_pause_lateral.is_metric = False + self.blinker_pause_lateral.min_speed = 20 # MPH + + self.CS = car.CarState.new_message() + self.CS.vEgo = 0 + self.CS.leftBlinker = False + self.CS.rightBlinker = False + + def _test_should_blinker_pause_lateral(self, expected_results) -> None: + for left in (True, False): + for right in (True, False): + self.CS.leftBlinker = left + self.CS.rightBlinker = right + + result = self.blinker_pause_lateral.update(self.CS) + assert result == expected_results[(left, right)] + + def test_below_min_speed_blinker(self): + self.CS.vEgo = 4.5 # ~10 MPH + + expected_results = { + (False, False): False, + (True, False): True, + (False, True): True, + (True, True): False + } + self._test_should_blinker_pause_lateral(expected_results) + + def test_above_min_speed_blinker(self): + self.CS.vEgo = 13.4 # ~30 MPH + + expected_results = { + (False, False): False, + (True, False): False, + (False, True): False, + (True, True): False + } + self._test_should_blinker_pause_lateral(expected_results) + + def test_just_below_min_speed(self): + self.CS.vEgo = (20 * CV.MPH_TO_MS) - 0.01 + + expected_results = { + (False, False): False, + (True, False): True, + (False, True): True, + (True, True): False + } + self._test_should_blinker_pause_lateral(expected_results) + + def test_disabled(self): + self.blinker_pause_lateral.enabled = False + self.CS.vEgo = 4.5 # ~10 MPH + + expected_results = { + (False, False): False, + (True, False): False, + (False, True): False, + (True, True): False + } + self._test_should_blinker_pause_lateral(expected_results) + + def test_metric_units_below_min_speed(self): + self.blinker_pause_lateral.is_metric = True + self.CS.vEgo = 5.0 # ~18 km/h + + expected_results = { + (False, False): False, + (True, False): True, + (False, True): True, + (True, True): False + } + self._test_should_blinker_pause_lateral(expected_results) + + def test_metric_units_above_threshold(self): + self.blinker_pause_lateral.is_metric = True + self.CS.vEgo = 6.0 # ~21.6 km/h + + expected_results = { + (False, False): False, + (True, False): False, + (False, True): False, + (True, True): False + } + self._test_should_blinker_pause_lateral(expected_results) + + def test_change_min_speed_threshold(self): + self.blinker_pause_lateral.min_speed = 30 # MPH + + # below min speed + self.CS.vEgo = 11.2 # ~25 MPH + + expected_results = { + (False, False): False, + (True, False): True, + (False, True): True, + (True, True): False + } + self._test_should_blinker_pause_lateral(expected_results) + + # above min speed + self.CS.vEgo = 15.6 # ~35 MPH + + expected_results = { + (False, False): False, + (True, False): False, + (False, True): False, + (True, True): False + } + self._test_should_blinker_pause_lateral(expected_results) diff --git a/system/manager/manager.py b/system/manager/manager.py index 5459a4ff2b..4493b87301 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -46,6 +46,8 @@ def manager_init() -> None: sunnypilot_default_params: list[tuple[str, str | bytes]] = [ ("AutoLaneChangeTimer", "0"), ("AutoLaneChangeBsmDelay", "0"), + ("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h + ("BlinkerPauseLateralControl", "0"), ("DynamicExperimentalControl", "0"), ("HyundaiLongitudinalTuning", "0"), ("Mads", "1"),