Compare commits

..

12 Commits

Author SHA1 Message Date
rav4kumar 6b10358865 sunny said to split it, now he is going to say make separate pr... coming up 2025-10-13 13:40:48 -07:00
rav4kumar 79e84fe081 clean up 2025-10-13 13:16:35 -07:00
rav4kumar c1416c5c06 clean up 2025-10-13 12:55:55 -07:00
rav4kumar 2a4317aa56 vibe pytest cleanup 2025-10-13 12:55:55 -07:00
rav4kumar 555a0d1393 clean ul; 2025-10-13 12:55:55 -07:00
rav4kumar f1f9894777 missing params 2025-10-13 12:55:55 -07:00
rav4kumar 0ff2dde674 vibe: Linear Interpolation 2025-10-13 12:55:55 -07:00
rav4kumar 1ddd0ea633 return type 2025-10-13 12:55:55 -07:00
rav4kumar ba0f963357 good time to remove 2025-10-13 12:55:55 -07:00
rav4kumar 833a13e076 so picky 2025-10-13 12:55:55 -07:00
rav4kumar 652ede7264 vibe pytest 2025-10-13 12:55:55 -07:00
rav4kumar 1cd9c8cd3c vibe 2025-10-13 12:55:53 -07:00
51 changed files with 1039 additions and 850 deletions
+11 -47
View File
@@ -69,48 +69,6 @@ struct LeadData {
struct SelfdriveStateSP @0x81c2f05a394cf4af {
mads @0 :ModularAssistiveDrivingSystem;
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
enum AudibleAlert {
none @0;
engage @1;
disengage @2;
refuse @3;
warningSoft @4;
warningImmediate @5;
prompt @6;
promptRepeat @7;
promptDistracted @8;
# unused, these are reserved for upstream events so we don't collide
reserved9 @9;
reserved10 @10;
reserved11 @11;
reserved12 @12;
reserved13 @13;
reserved14 @14;
reserved15 @15;
reserved16 @16;
reserved17 @17;
reserved18 @18;
reserved19 @19;
reserved20 @20;
reserved21 @21;
reserved22 @22;
reserved23 @23;
reserved24 @24;
reserved25 @25;
reserved26 @26;
reserved27 @27;
reserved28 @28;
reserved29 @29;
reserved30 @30;
promptSingleLow @31;
promptSingleHigh @32;
}
}
struct ModelManagerSP @0xaedffd8f31e7b55d {
@@ -192,6 +150,7 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
aTarget @5 :Float32;
events @6 :List(OnroadEventSP.Event);
e2eAlerts @7 :E2eAlerts;
accelPersonality @8 :AccelerationPersonality;
struct DynamicExperimentalControl {
state @0 :DynamicExperimentalControlState;
@@ -294,6 +253,11 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
greenLightAlert @0 :Bool;
leadDepartAlert @1 :Bool;
}
enum AccelerationPersonality {
sport @0;
normal @1;
eco @2;
}
}
struct OnroadEventSP @0xda96579883444c35 {
@@ -446,12 +410,12 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
struct ModelDataV2SP @0xa1680744031fdb2d {
laneTurnDirection @0 :TurnDirection;
}
enum TurnDirection {
none @0;
turnLeft @1;
turnRight @2;
}
enum TurnDirection {
none @0;
turnLeft @1;
turnRight @2;
}
struct CustomReserved10 @0xcb9fd56c7057593a {
+6 -4
View File
@@ -130,6 +130,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"Version", {PERSISTENT, STRING}},
// --- sunnypilot params --- //
{"AccelPersonality", {PERSISTENT | BACKUP, INT, std::to_string(static_cast<int>(cereal::LongitudinalPlanSP::AccelerationPersonality::NORMAL))}},
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
{"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}},
{"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}},
@@ -160,9 +161,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}},
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "100"}},
{"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}},
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}},
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "0"}},
{"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}},
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
@@ -171,6 +172,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
{"VibePersonalityEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"VibeAccelPersonalityEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"VibeFollowPersonalityEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
// MADS params
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
@@ -205,8 +209,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
// sunnypilot car specific params
{"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}},
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dbfa5858c0a672411ffdc691efdecb06d01ae458cc1df409bcf3fdeaa4756f72
size 34638
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:db9671bb03e01f119bba1eb6cc0507e0f039ac4e5b7f9f839a87071c52e86e56
size 44416
+1 -3
View File
@@ -99,6 +99,7 @@ class Controls(ControlsExt, ModelStateBase):
self.LaC.extension.update_model_v2(self.sm['modelV2'])
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
self.LaC.extension.update_lateral_lag(self.lat_delay)
long_plan = self.sm['longitudinalPlan']
@@ -233,9 +234,6 @@ class Controls(ControlsExt, ModelStateBase):
while not evt.is_set():
self.get_params_sp()
if self.CP.lateralTuning.which() == 'torque':
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
time.sleep(0.1)
def run(self):
+5 -6
View File
@@ -6,7 +6,6 @@ from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTur
LaneChangeState = log.LaneChangeState
LaneChangeDirection = log.LaneChangeDirection
TurnDirection = custom.ModelDataV2SP.TurnDirection
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
LANE_CHANGE_TIME_MAX = 10.
@@ -33,9 +32,9 @@ DESIRES = {
}
TURN_DESIRES = {
TurnDirection.none: log.Desire.none,
TurnDirection.turnLeft: log.Desire.turnLeft,
TurnDirection.turnRight: log.Desire.turnRight,
custom.TurnDirection.none: log.Desire.none,
custom.TurnDirection.turnLeft: log.Desire.turnLeft,
custom.TurnDirection.turnRight: log.Desire.turnRight,
}
@@ -50,7 +49,7 @@ class DesireHelper:
self.desire = log.Desire.none
self.alc = AutoLaneChangeController(self)
self.lane_turn_controller = LaneTurnController(self)
self.lane_turn_direction = TurnDirection.none
self.lane_turn_direction = custom.TurnDirection.none
@staticmethod
def get_lane_change_direction(CS):
@@ -127,7 +126,7 @@ class DesireHelper:
self.prev_one_blinker = one_blinker
if self.lane_turn_direction != TurnDirection.none:
if self.lane_turn_direction != custom.TurnDirection.none:
self.desire = TURN_DESIRES[self.lane_turn_direction]
else:
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
@@ -10,6 +10,8 @@ from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.modeld.constants import index_function
from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU
from openpilot.sunnypilot.selfdrive.controls.lib.vibe_personality.vibe_personality import VibePersonalityController
if __name__ == '__main__': # generating code
from openpilot.third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
else:
@@ -228,6 +230,7 @@ class LongitudinalMpc:
self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
self.reset()
self.source = SOURCES[2]
self.vibe_controller = VibePersonalityController()
def reset(self):
# self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
@@ -328,10 +331,18 @@ class LongitudinalMpc:
return lead_xv
def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard):
t_follow = get_T_FOLLOW(personality)
v_ego = self.x0[1]
# Get following distance
t_follow_vibe = self.vibe_controller.get_follow_distance_multiplier(v_ego)
t_follow = t_follow_vibe if t_follow_vibe is not None else get_T_FOLLOW(personality)
self.status = radarstate.leadOne.status or radarstate.leadTwo.status
# Get acceleration limits
accel_limits = self.vibe_controller.get_accel_limits(v_ego)
a_cruise_min = accel_limits[0] if accel_limits is not None else CRUISE_MIN_ACCEL
lead_xv_0 = self.process_lead(radarstate.leadOne)
lead_xv_1 = self.process_lead(radarstate.leadTwo)
@@ -350,7 +361,7 @@ class LongitudinalMpc:
# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
# when the leads are no factor.
v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05)
v_lower = v_ego + (T_IDXS * a_cruise_min * 1.05)
# TODO does this make sense when max_a is negative?
v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05)
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
@@ -405,7 +416,7 @@ class LongitudinalMpc:
if any((lead_0_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0):
self.source = 'lead0'
if any((lead_1_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0) and \
(lead_1_obstacle[0] - lead_0_obstacle[0]):
(lead_1_obstacle[0] - lead_0_obstacle[0]):
self.source = 'lead1'
def run(self):
@@ -124,7 +124,11 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
if mode == 'acc':
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
accel_limits = self.vibe_controller.get_accel_limits(v_ego)
if accel_limits is not None:
accel_clip = [ACCEL_MIN, accel_limits[1]]
else:
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP)
else:
+3 -10
View File
@@ -24,7 +24,6 @@ from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroa
from openpilot.system.version import get_build_metadata
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
from openpilot.sunnypilot import get_sanitize_int_param
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
@@ -44,7 +43,6 @@ LaneChangeDirection = log.LaneChangeDirection
EventName = log.OnroadEvent.EventName
ButtonType = car.CarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel
TurnDirection = custom.ModelDataV2SP.TurnDirection
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
@@ -132,12 +130,7 @@ class SelfdriveD(CruiseHelper):
self.logged_comm_issue = None
self.not_running_prev = None
self.experimental_mode = False
self.personality = get_sanitize_int_param(
"LongitudinalPersonality",
min(log.LongitudinalPersonality.schema.enumerants.values()),
max(log.LongitudinalPersonality.schema.enumerants.values()),
self.params
)
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
self.recalibrating_seen = False
self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None)
@@ -306,9 +299,9 @@ class SelfdriveD(CruiseHelper):
# Handle lane turn
lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection
if lane_turn_direction == TurnDirection.turnLeft:
if lane_turn_direction == custom.TurnDirection.turnLeft:
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft)
elif lane_turn_direction == TurnDirection.turnRight:
elif lane_turn_direction == custom.TurnDirection.turnRight:
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight)
for i, pandaState in enumerate(self.sm['pandaStates']):
+26 -1
View File
@@ -33,6 +33,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"../assets/icons/experimental_white.svg",
false,
},
{
"DynamicExperimentalControl",
tr("Enable Dynamic Experimental Control"),
tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."),
"../assets/offroad/icon_blank.png",
false,
},
{
"DisengageOnAccelerator",
tr("Disengage on Accelerator Pedal"),
@@ -85,7 +92,15 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"your steering wheel distance button."),
"../assets/icons/speed_limit.png",
longi_button_texts);
// accel controller
std::vector<QString> accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco")};
accel_personality_setting = new ButtonParamControlSP("AccelPersonality", tr("Acceleration Personality"),
tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. "
"In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these "
"acceleration personality within Onroad Settings on the driving screen."),
"",
accel_personality_texts);
accel_personality_setting->showDescription();
// set up uiState update for personality setting
QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState);
@@ -113,6 +128,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
// insert longitudinal personality after NDOG toggle
if (param == "DisengageOnAccelerator") {
addItem(long_personality_setting);
addItem(accel_personality_setting);
}
}
@@ -133,6 +149,13 @@ void TogglesPanel::updateState(const UIState &s) {
}
uiState()->scene.personality = personality;
}
if (sm.updated("longitudinalPlanSP")) {
auto accel_personality = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getAccelPersonality();
if (accel_personality != s.scene.accel_personality && s.scene.started && isVisible()) {
accel_personality_setting->setCheckedButton(static_cast<int>(accel_personality));
}
uiState()->scene.accel_personality = accel_personality;
}
}
void TogglesPanel::expandToggleDescription(const QString &param) {
@@ -179,10 +202,12 @@ void TogglesPanel::updateToggles() {
experimental_mode_toggle->setEnabled(true);
experimental_mode_toggle->setDescription(e2e_description);
long_personality_setting->setEnabled(true);
accel_personality_setting->setEnabled(true);
} else {
// no long for now
experimental_mode_toggle->setEnabled(false);
long_personality_setting->setEnabled(false);
accel_personality_setting->setEnabled(true);
params.remove("ExperimentalMode");
const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.");
+1
View File
@@ -88,6 +88,7 @@ protected:
Params params;
std::map<std::string, ParamControl*> toggles;
ButtonParamControl *long_personality_setting;
ButtonParamControl *accel_personality_setting;
virtual void updateToggles();
};
+2 -1
View File
@@ -22,7 +22,7 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
update_model(model, lead_one);
drawLaneLines(painter);
drawPath(painter, model, surface_rect.height());
drawPath(painter, model, surface_rect);
if (longitudinal_control && sm.alive("radarState")) {
update_leads(radar_state, model.getPosition());
@@ -173,6 +173,7 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float
(1 - t) * start.alphaF() + t * end.alphaF());
}
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &vd, const QRect &surface_rect) {
const float speedBuff = 10.;
+4
View File
@@ -39,6 +39,9 @@ protected:
virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead);
void drawLaneLines(QPainter &painter);
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height);
virtual void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {;
drawPath(painter, model, surface_rect.height());
}
void updatePathGradient(QLinearGradient &bg);
QColor blendColors(const QColor &start, const QColor &end, float t);
@@ -55,4 +58,5 @@ protected:
QPointF lead_vertices[2] = {};
Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero();
QRectF clip_region;
};
+1 -10
View File
@@ -4,7 +4,7 @@ import time
import wave
from cereal import car, messaging, custom
from cereal import car, messaging
from openpilot.common.basedir import BASEDIR
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.realtime import Ratekeeper
@@ -26,15 +26,8 @@ AMBIENT_DB = 30 # DB where MIN_VOLUME is applied
DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert
sound_list_sp: dict[int, tuple[str, int | None, float]] = {
# AudibleAlertSP, file name, play count (none for infinite)
AudibleAlertSP.promptSingleLow: ("prompt_single_low.wav", 1, MAX_VOLUME),
AudibleAlertSP.promptSingleHigh: ("prompt_single_high.wav", 1, MAX_VOLUME),
}
sound_list: dict[int, tuple[str, int | None, float]] = {
# AudibleAlert, file name, play count (none for infinite)
AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME),
@@ -47,8 +40,6 @@ sound_list: dict[int, tuple[str, int | None, float]] = {
AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME),
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
**sound_list_sp,
}
def check_selfdrive_timeout_alert(sm):
@@ -29,7 +29,7 @@ OnroadScreenBrightnessControl::OnroadScreenBrightnessControl(const QString &para
"Onroad Brightness",
"",
"",
{0, 90}, 10, true);
{0, 100}, 10, true);
connect(onroadScreenOffTimer, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
connect(onroadScreenBrightness, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
@@ -13,9 +13,9 @@ enum class SpeedLimitOffsetType {
};
inline const QString SpeedLimitOffsetTypeTexts[]{
QT_TRANSLATE_NOOP("SpeedLimitSettings", "None"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Fixed"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Percent"),
QObject::tr("None"),
QObject::tr("Fixed"),
QObject::tr("Percent"),
};
enum class SpeedLimitSourcePolicy {
@@ -27,11 +27,11 @@ enum class SpeedLimitSourcePolicy {
};
inline const QString SpeedLimitSourcePolicyTexts[]{
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nOnly"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nOnly"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nFirst"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nFirst"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Combined\nData")
QObject::tr("Car\nOnly"),
QObject::tr("Map\nOnly"),
QObject::tr("Car\nFirst"),
QObject::tr("Map\nFirst"),
QObject::tr("Combined\nData")
};
enum class SpeedLimitMode {
@@ -42,8 +42,8 @@ enum class SpeedLimitMode {
};
inline const QString SpeedLimitModeTexts[]{
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Off"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Information"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Warning"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Assist"),
QObject::tr("Off"),
QObject::tr("Information"),
QObject::tr("Warning"),
QObject::tr("Assist"),
};
@@ -23,11 +23,11 @@ SpeedLimitPolicy::SpeedLimitPolicy(QWidget *parent) : QWidget(parent) {
ListWidgetSP *list = new ListWidgetSP(this);
std::vector<QString> speed_limit_policy_texts{
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_ONLY)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_ONLY)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_FIRST)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_FIRST)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::COMBINED)].toStdString().c_str())
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_ONLY)],
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_ONLY)],
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_FIRST)],
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_FIRST)],
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::COMBINED)]
};
speed_limit_policy = new ButtonParamControlSP(
"SpeedLimitPolicy",
@@ -7,8 +7,6 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) {
subPanelFrame = new QFrame();
QVBoxLayout *subPanelLayout = new QVBoxLayout(subPanelFrame);
@@ -27,10 +25,10 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent)
speedLimitPolicyScreen = new SpeedLimitPolicy(this);
std::vector<QString> speed_limit_mode_texts{
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::OFF)].toStdString().c_str()),
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::INFORMATION)].toStdString().c_str()),
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::WARNING)].toStdString().c_str()),
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::ASSIST)].toStdString().c_str())
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::OFF)],
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::INFORMATION)],
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::WARNING)],
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::ASSIST)],
};
speed_limit_mode_settings = new ButtonParamControlSP(
"SpeedLimitMode",
@@ -66,9 +64,9 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent)
QVBoxLayout *offsetLayout = new QVBoxLayout(offsetFrame);
std::vector<QString> speed_limit_offset_texts{
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::NONE)].toStdString().c_str()),
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::FIXED)].toStdString().c_str()),
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::PERCENT)].toStdString().c_str())
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::NONE)],
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::FIXED)],
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::PERCENT)]
};
speed_limit_offset_settings = new ButtonParamControlSP(
"SpeedLimitOffsetType",
@@ -111,7 +109,7 @@ void SpeedLimitSettings::refresh() {
QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset"));
bool has_longitudinal_control;
bool has_icbm;
bool intelligent_cruise_button_management_available;
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
@@ -123,16 +121,10 @@ void SpeedLimitSettings::refresh() {
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
has_longitudinal_control = hasLongitudinalControl(CP);
has_icbm = hasIntelligentCruiseButtonManagement(CP_SP);
if (!has_longitudinal_control && !has_icbm) {
if (speed_limit_mode_param == SpeedLimitMode::ASSIST) {
params.put("SpeedLimitMode", std::to_string(static_cast<int>(SpeedLimitMode::WARNING)));
}
}
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
} else {
has_longitudinal_control = false;
has_icbm = false;
intelligent_cruise_button_management_available = false;
}
speed_limit_mode_settings->setDescription(modeDescription(speed_limit_mode_param));
@@ -152,14 +144,13 @@ void SpeedLimitSettings::refresh() {
speed_limit_offset->showDescription();
}
if (has_longitudinal_control || has_icbm) {
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues()));
} else {
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(
{SpeedLimitMode::OFF, SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING}));
{SpeedLimitMode::OFF,SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING}));
}
speed_limit_mode_settings->refresh();
speed_limit_mode_settings->showDescription();
speed_limit_offset->showDescription();
}
@@ -35,7 +35,6 @@ private:
SpeedLimitPolicy *speedLimitPolicyScreen;
ButtonParamControlSP *speed_limit_offset_settings;
OptionControlSP *speed_limit_offset;
bool icbm_available = false;
static QString offsetDescription(SpeedLimitOffsetType type = SpeedLimitOffsetType::NONE) {
QString none_str = tr("⦿ None: No Offset");
@@ -7,8 +7,6 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
setStyleSheet(R"(
#back_btn {
@@ -42,20 +40,9 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
"",
this
);
QObject::connect(intelligentCruiseButtonManagement, &ParamControlSP::toggleFlipped, this, [=](bool) {
refresh(offroad);
});
intelligentCruiseButtonManagement->setConfirmation(true, false);
list->addItem(intelligentCruiseButtonManagement);
dynamicExperimentalControl = new ParamControlSP(
"DynamicExperimentalControl",
tr("Dynamic Experimental Control (DEC)"),
tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."),
"",
this
);
list->addItem(dynamicExperimentalControl);
SmartCruiseControlVision = new ParamControl(
"SmartCruiseControlVision",
tr("Smart Cruise Control - Vision"),
@@ -88,6 +75,35 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
main_layout->setCurrentWidget(cruisePanelScreen);
});
// Vibe Personality Controller
vibePersonalityControl = new ParamControlSP("VibePersonalityEnabled",
tr("Vibe Personality Controller"),
tr("Advanced driving personality system with separate controls for acceleration behavior (Eco/Normal/Sport) and following distance/braking (Relaxed/Standard/Aggressive). "
"Customize your driving experience with independent acceleration and distance personalities."),
"../assets/offroad/icon_shell.png");
list->addItem(vibePersonalityControl);
connect(vibePersonalityControl, &ParamControlSP::toggleFlipped, [=]() {
refresh(offroad);
});
// Vibe Acceleration Personality
vibeAccelPersonalityControl = new ParamControlSP("VibeAccelPersonalityEnabled",
tr("Acceleration Personality"),
tr("Controls acceleration behavior: Eco (efficient), Normal (balanced), Sport (responsive). "
"Adjust how aggressively the vehicle accelerates while maintaining smooth operation."),
"../assets/offroad/icon_shell.png");
list->addItem(vibeAccelPersonalityControl);
// Vibe Following Distance Personality
vibeFollowPersonalityControl = new ParamControlSP("VibeFollowPersonalityEnabled",
tr("Following Distance Personality"),
tr("Controls following distance and braking behavior: Relaxed (longer distance, gentler braking), Standard (balanced), Aggressive (shorter distance, firmer braking). "
"Fine-tune your comfort level in traffic situations."),
"../assets/offroad/icon_shell.png");
list->addItem(vibeFollowPersonalityControl);
main_layout->addWidget(cruisePanelScreen);
main_layout->addWidget(speedLimitScreen);
main_layout->setCurrentWidget(cruisePanelScreen);
@@ -116,41 +132,11 @@ void LongitudinalPanel::refresh(bool _offroad) {
has_longitudinal_control = hasLongitudinalControl(CP);
is_pcm_cruise = CP.getPcmCruise();
has_icbm = hasIntelligentCruiseButtonManagement(CP_SP);
if (CP_SP.getIntelligentCruiseButtonManagementAvailable() && !has_longitudinal_control) {
intelligentCruiseButtonManagement->setEnabled(offroad);
} else {
params.remove("IntelligentCruiseButtonManagement");
intelligentCruiseButtonManagement->setEnabled(false);
}
if (has_longitudinal_control || has_icbm) {
// enable Custom ACC Increments when long is available and is not PCM cruise
customAccIncrement->setEnabled(((has_longitudinal_control && !is_pcm_cruise) || has_icbm) && offroad);
dynamicExperimentalControl->setEnabled(has_longitudinal_control);
SmartCruiseControlVision->setEnabled(true);
SmartCruiseControlMap->setEnabled(true);
} else {
params.remove("CustomAccIncrementsEnabled");
params.remove("DynamicExperimentalControl");
params.remove("SmartCruiseControlVision");
params.remove("SmartCruiseControlMap");
customAccIncrement->setEnabled(false);
dynamicExperimentalControl->setEnabled(false);
SmartCruiseControlVision->setEnabled(false);
SmartCruiseControlMap->setEnabled(false);
}
intelligentCruiseButtonManagement->refresh();
customAccIncrement->refresh();
dynamicExperimentalControl->refresh();
SmartCruiseControlVision->refresh();
SmartCruiseControlMap->refresh();
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
} else {
has_longitudinal_control = false;
is_pcm_cruise = false;
has_icbm = false;
intelligent_cruise_button_management_available = false;
}
QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease.");
@@ -162,19 +148,48 @@ void LongitudinalPanel::refresh(bool _offroad) {
customAccIncrement->setDescription(onroadOnlyDescription);
customAccIncrement->showDescription();
} else {
if (has_longitudinal_control || has_icbm) {
if (has_longitudinal_control && is_pcm_cruise) {
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
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();
params.remove("IntelligentCruiseButtonManagement");
intelligentCruiseButtonManagement->toggleFlipped(false);
}
}
bool vibePersonalityEnabled = params.getBool("VibePersonalityEnabled");
if (vibePersonalityEnabled) {
vibeAccelPersonalityControl->setVisible(true);
vibeFollowPersonalityControl->setVisible(true);
} else {
vibeAccelPersonalityControl->setVisible(false);
vibeFollowPersonalityControl->setVisible(false);
}
bool icbm_allowed = intelligent_cruise_button_management_available && !has_longitudinal_control;
intelligentCruiseButtonManagement->setEnabled(icbm_allowed && offroad);
// enable toggle when long is available and is not PCM cruise
bool cai_allowed = (has_longitudinal_control && !is_pcm_cruise) || icbm_allowed;
customAccIncrement->setEnabled(cai_allowed && !offroad);
customAccIncrement->refresh();
SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed);
SmartCruiseControlMap->setEnabled(has_longitudinal_control || icbm_allowed);
// Vibe Personality controls - always enabled for toggling
vibePersonalityControl->setEnabled(true);
vibeAccelPersonalityControl->setEnabled(true);
vibeFollowPersonalityControl->setEnabled(true);
vibePersonalityControl->refresh();
vibeAccelPersonalityControl->refresh();
vibeFollowPersonalityControl->refresh();
offroad = _offroad;
}
@@ -25,7 +25,7 @@ private:
Params params;
bool has_longitudinal_control = false;
bool is_pcm_cruise = false;
bool has_icbm = false;
bool intelligent_cruise_button_management_available = false;;
bool offroad = false;
QStackedLayout *main_layout = nullptr;
@@ -35,7 +35,10 @@ private:
ParamControl *SmartCruiseControlVision;
ParamControl *SmartCruiseControlMap;
ParamControl *intelligentCruiseButtonManagement = nullptr;
ParamControl *dynamicExperimentalControl = nullptr;
SpeedLimitSettings *speedLimitScreen;
PushButtonSP *speedLimitSettings;
ParamControlSP *vibePersonalityControl;
ParamControlSP *vibeAccelPersonalityControl;
ParamControlSP *vibeFollowPersonalityControl;
};
@@ -8,52 +8,7 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h"
SubaruSettings::SubaruSettings(QWidget *parent) : BrandSettingsInterface(parent) {
stopAndGoToggle = new ParamControl("SubaruStopAndGo", tr("Stop and Go (Beta)"), "", "");
stopAndGoToggle->setConfirmation(true, false);
list->addItem(stopAndGoToggle);
stopAndGoManualParkingBrakeToggle = new ParamControl(
"SubaruStopAndGoManualParkingBrake",
tr("Stop and Go for Manual Parking Brake (Beta)"),
"",
""
);
stopAndGoManualParkingBrakeToggle->setConfirmation(true, false);
list->addItem(stopAndGoManualParkingBrakeToggle);
}
void SubaruSettings::updateSettings() {
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>();
is_subaru = CP.getBrand() == "subaru";
if (is_subaru) {
if (!(CP.getFlags() & (SUBARU_FLAG_GLOBAL_GEN2 | SUBARU_FLAG_HYBRID))) {
has_stop_and_go = true;
}
}
} else {
is_subaru = false;
has_stop_and_go = false;
}
bool stop_and_go_disabled = !offroad || !has_stop_and_go;
QString stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc);
QString stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc);
if (stop_and_go_disabled) {
stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc, stopAndGoDisabledMsg());
stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc, stopAndGoDisabledMsg());
}
stopAndGoToggle->setEnabled(has_stop_and_go);
stopAndGoToggle->setDescription(stop_and_go_desc);
stopAndGoToggle->showDescription();
stopAndGoManualParkingBrakeToggle->setEnabled(has_stop_and_go);
stopAndGoManualParkingBrakeToggle->setDescription(stop_and_go_manual_parking_brake_desc);
stopAndGoManualParkingBrakeToggle->showDescription();
}
@@ -14,9 +14,6 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
const int SUBARU_FLAG_GLOBAL_GEN2 = 4;
const int SUBARU_FLAG_HYBRID = 32;
class SubaruSettings : public BrandSettingsInterface {
Q_OBJECT
@@ -26,32 +23,4 @@ public:
private:
bool offroad = false;
bool is_subaru;
bool has_stop_and_go;
ParamControl* stopAndGoToggle;
ParamControl* stopAndGoManualParkingBrakeToggle;
QString stopAndGoDesc = tr("Experimental feature to enable auto-resume during stop-and-go for certain supported Subaru platforms.");
QString stopAndGoManualParkingBrakeDesc = tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!");
QString stopAndGoDisabledMsg() const {
if (is_subaru && !has_stop_and_go) {
return tr("This feature is currently not available on this platform.");
}
if (!is_subaru) {
return tr("Start the car to check car compatibility.");
}
if (!offroad) {
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle.");
}
return QString();
}
static QString stopAndGoDescriptionBuilder(const QString &base_description, const QString &custom_description = "") {
return "<b>" + custom_description + "</b><br><br>" + base_description;
}
};
+65 -90
View File
@@ -12,65 +12,43 @@
HudRendererSP::HudRendererSP() {
plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {90, 90});
minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {90, 90});
plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {105, 105});
minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {105, 105});
int size = e2e_alert_size * 2 - 40;
green_light_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {size, size});
lead_depart_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {size, size});
int small_max = e2e_alert_small * 2 - 40;
int large_max = e2e_alert_large * 2 - 40;
green_light_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {small_max, small_max});
green_light_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {large_max, large_max});
lead_depart_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {small_max, small_max});
lead_depart_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {large_max, large_max});
}
void HudRendererSP::updateState(const UIState &s) {
HudRenderer::updateState(s);
float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH;
devUiInfo = s.scene.dev_ui_info;
roadName = s.scene.road_name;
showTurnSignals = s.scene.turn_signals;
speedLimitMode = static_cast<SpeedLimitMode>(s.scene.speed_limit_mode);
speedUnit = is_metric ? tr("km/h") : tr("mph");
standstillTimer = s.scene.standstill_timer;
const SubMaster &sm = *(s.sm);
const auto cs = sm["controlsState"].getControlsState();
const auto car_state = sm["carState"].getCarState();
const auto car_control = sm["carControl"].getCarControl();
const auto radar_state = sm["radarState"].getRadarState();
const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1;
const char *gps_source = is_gps_location_external ? "gpsLocationExternal" : "gpsLocation";
const auto gpsLocation = is_gps_location_external ? sm[gps_source].getGpsLocationExternal() : sm[gps_source].getGpsLocation();
const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation();
const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters();
const auto car_params = sm["carParams"].getCarParams();
const auto car_params_sp = sm["carParamsSP"].getCarParamsSP();
const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP();
const auto lmd = sm["liveMapDataSP"].getLiveMapDataSP();
if (sm.updated("carParams")) {
steerControlType = car_params.getSteerControlType();
}
if (sm.updated("carParamsSP")) {
pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed();
}
if (sm.updated("longitudinalPlanSP")) {
speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv;
speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv;
speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv;
speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid();
speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid();
speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv;
speedLimitSource = lp_sp.getSpeedLimit().getResolver().getSource();
speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState();
speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive();
smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled();
smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive();
smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled();
smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive();
}
greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert();
leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert();
float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH;
speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv;
speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv;
speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv;
speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid();
speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid();
speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv;
speedLimitMode = static_cast<SpeedLimitMode>(s.scene.speed_limit_mode);
speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState();
speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive();
roadName = s.scene.road_name;
if (sm.updated("liveMapDataSP")) {
roadNameStr = QString::fromStdString(lmd.getRoadName());
speedLimitAheadValid = lmd.getSpeedLimitAheadValid();
@@ -86,7 +64,7 @@ void HudRendererSP::updateState(const UIState &s) {
static int reverse_delay = 0;
bool reverse_allowed = false;
if (car_state.getGearShifter() != cereal::CarState::GearShifter::REVERSE) {
if (int(car_state.getGearShifter()) != 4) {
reverse_delay = 0;
reverse_allowed = false;
} else {
@@ -98,47 +76,46 @@ void HudRendererSP::updateState(const UIState &s) {
reversing = reverse_allowed;
if (sm.updated("liveParameters")) {
roll = sm["liveParameters"].getLiveParameters().getRoll();
}
if (sm.updated("deviceState")) {
memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
}
if (sm.updated(gps_source)) {
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not.
altitude = gpsLocation.getAltitude();
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
bearingDeg = gpsLocation.getBearingDeg();
}
if (sm.updated("liveTorqueParameters")) {
torquedUseParams = ltp.getUseParams();
latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
liveValid = ltp.getLiveValid();
}
latActive = car_control.getLatActive();
actuators = car_control.getActuators();
longOverride = car_control.getCruiseControl().getOverride();
carControlEnabled = car_control.getEnabled();
steerOverride = car_state.getSteeringPressed();
devUiInfo = s.scene.dev_ui_info;
speedUnit = is_metric ? tr("km/h") : tr("mph");
lead_d_rel = radar_state.getLeadOne().getDRel();
lead_v_rel = radar_state.getLeadOne().getVRel();
lead_status = radar_state.getLeadOne().getStatus();
steerControlType = car_params.getSteerControlType();
actuators = car_control.getActuators();
torqueLateral = steerControlType == cereal::CarParams::SteerControlType::TORQUE;
angleSteers = car_state.getSteeringAngleDeg();
desiredCurvature = cs.getDesiredCurvature();
curvature = cs.getCurvature();
roll = sm["liveParameters"].getLiveParameters().getRoll();
memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not.
altitude = gpsLocation.getAltitude();
vEgo = car_state.getVEgo();
aEgo = car_state.getAEgo();
steeringTorqueEps = car_state.getSteeringTorqueEps();
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
bearingDeg = gpsLocation.getBearingDeg();
torquedUseParams = ltp.getUseParams();
latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
liveValid = ltp.getLiveValid();
standstillTimer = s.scene.standstill_timer;
isStandstill = car_state.getStandstill();
if (!s.scene.started) standstillElapsedTime = 0.0;
if (not s.scene.started) standstillElapsedTime = 0.0;
longOverride = car_control.getCruiseControl().getOverride();
smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled();
smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive();
smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled();
smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive();
greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert();
leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert();
// override stock current speed values
float v_ego = (v_ego_cluster_seen && !s.scene.trueVEgoUI) ? car_state.getVEgoCluster() : car_state.getVEgo();
@@ -149,11 +126,10 @@ void HudRendererSP::updateState(const UIState &s) {
rightBlinkerOn = car_state.getRightBlinker();
leftBlindspot = car_state.getLeftBlindspot();
rightBlindspot = car_state.getRightBlindspot();
showTurnSignals = s.scene.turn_signals;
carControlEnabled = car_control.getEnabled();
speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv;
allow_e2e_alerts = sm["selfdriveState"].getSelfdriveState().getAlertSize() == cereal::SelfdriveState::AlertSize::NONE &&
sm.rcv_frame("driverStateV2") > s.scene.started_frame && !reversing;
}
void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
@@ -246,7 +222,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
drawRoadName(p, surface_rect);
// Green Light & Lead Depart Alerts
if (greenLightAlert || leadDepartAlert) {
if (greenLightAlert or leadDepartAlert) {
e2eAlertDisplayTimer = 3 * UI_FREQ;
// reset onroad sleep timer for e2e alerts
uiStateSP()->reset_onroad_sleep_timer();
@@ -256,11 +232,11 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
e2eAlertFrame++;
if (greenLightAlert) {
alert_text = tr("GREEN\nLIGHT");
alert_img = green_light_alert_img;
alert_img = devUiInfo > 0 ? green_light_alert_small_img : green_light_alert_large_img;
}
else if (leadDepartAlert) {
alert_text = tr("LEAD VEHICLE\nDEPARTING");
alert_img = lead_depart_alert_img;
alert_img = devUiInfo > 0 ? lead_depart_alert_small_img : lead_depart_alert_large_img;
}
drawE2eAlert(p, surface_rect);
}
@@ -278,7 +254,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
// No Alerts displayed
else {
e2eAlertFrame = 0;
if (!isStandstill) standstillElapsedTime = 0.0;
if (not isStandstill) standstillElapsedTime = 0.0;
}
// Blinker
@@ -569,8 +545,7 @@ void HudRendererSP::drawSpeedLimitSigns(QPainter &p, QRect &sign_rect) {
}
void HudRendererSP::drawUpcomingSpeedLimit(QPainter &p) {
bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0 &&
speedLimitSource == cereal::LongitudinalPlanSP::SpeedLimit::Source::MAP;
bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0;
if (!speed_limit_ahead) {
return;
}
@@ -664,7 +639,7 @@ void HudRendererSP::drawRoadName(QPainter &p, const QRect &surface_rect) {
void HudRendererSP::drawSpeedLimitPreActiveArrow(QPainter &p, QRect &sign_rect) {
const int sign_margin = 12;
const int arrow_spacing = sign_margin * 1.4;
const int arrow_spacing = sign_margin * 3;
int arrow_x = sign_rect.right() + arrow_spacing;
int _set_speed = std::nearbyint(set_speed);
@@ -714,7 +689,7 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
}
// Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text
if (!pcmCruiseSpeed && carControlEnabled) {
if (carControlEnabled) {
if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) {
icbm_active_counter = 3 * UI_FREQ;
} else if (icbm_active_counter > 0) {
@@ -739,26 +714,26 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
}
void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) {
if (!allow_e2e_alerts) return;
int x = surface_rect.right() - e2e_alert_size - (devUiInfo > 0 ? 180 : 100) - (UI_BORDER_SIZE * 3);
int size = devUiInfo > 0 ? e2e_alert_small : e2e_alert_large;
int x = surface_rect.center().x() + surface_rect.width() / 4;
int y = surface_rect.center().y() + 20;
QRect alertRect(x - e2e_alert_size, y - e2e_alert_size, e2e_alert_size * 2, e2e_alert_size * 2);
x += devUiInfo > 0 ? 0 : 50;
QRect alertRect(x - size, y - size, size * 2, size * 2);
// Alert Circle
QPoint center = alertRect.center();
QColor frameColor;
if (!alert_alt_text.isEmpty()) frameColor = QColor(255, 255, 255, 75);
if (not alert_alt_text.isEmpty()) frameColor = QColor(255, 255, 255, 75);
else frameColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 75) : QColor(0, 255, 0, 75);
p.setPen(QPen(frameColor, 15));
p.setBrush(QColor(0, 0, 0, 190));
p.drawEllipse(center, e2e_alert_size, e2e_alert_size);
p.drawEllipse(center, size, size);
// Alert Text
QColor txtColor;
QFont font;
int alert_bottom_adjustment;
if (!alert_alt_text.isEmpty()) {
if (not alert_alt_text.isEmpty()) {
font = InterFont(100, QFont::Bold);
alert_bottom_adjustment = 5;
txtColor = QColor(255, 255, 255, 255);
@@ -775,7 +750,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q
textRect.moveBottom(alertRect.bottom() - alertRect.height() / alert_bottom_adjustment);
p.drawText(textRect, Qt::AlignCenter, alert_text);
if (!alert_alt_text.isEmpty()) {
if (not alert_alt_text.isEmpty()) {
// Alert Alternate Text
p.setFont(InterFont(80, QFont::Bold));
p.setPen(QColor(255, 175, 3, 240));
@@ -804,7 +779,7 @@ void HudRendererSP::drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect) {
void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) {
const bool hazard = leftBlinkerOn && rightBlinkerOn;
int blinkerStatus = hazard ? 2 : (leftBlinkerOn || rightBlinkerOn) ? 1 : 0;
int blinkerStatus = hazard ? 2 : (leftBlinkerOn or rightBlinkerOn) ? 1 : 0;
if (!leftBlinkerOn && !rightBlinkerOn) {
blinkerFrameCounter = 0;
+6 -6
View File
@@ -83,7 +83,6 @@ private:
bool speedLimitValid;
bool speedLimitLastValid;
float speedLimitFinalLast;
cereal::LongitudinalPlanSP::SpeedLimit::Source speedLimitSource;
bool speedLimitAheadValid;
float speedLimitAhead;
float speedLimitAheadDistance;
@@ -97,14 +96,16 @@ private:
int speedLimitAssistFrame;
QPixmap plus_arrow_up_img;
QPixmap minus_arrow_down_img;
int e2e_alert_size = 250;
QPixmap green_light_alert_img;
int e2e_alert_small = 250;
int e2e_alert_large = 300;
QPixmap green_light_alert_small_img;
QPixmap green_light_alert_large_img;
bool greenLightAlert;
int e2eAlertFrame;
int e2eAlertDisplayTimer = 0;
bool allow_e2e_alerts;
bool leadDepartAlert;
QPixmap lead_depart_alert_img;
QPixmap lead_depart_alert_small_img;
QPixmap lead_depart_alert_large_img;
QString alert_text;
QPixmap alert_img;
bool hideVEgoUI;
@@ -119,5 +120,4 @@ private:
bool carControlEnabled;
float speedCluster = 0;
int icbm_active_counter = 0;
bool pcmCruiseSpeed = true;
};
+61 -90
View File
@@ -21,73 +21,74 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con
mapLineToPolygon(model.getLaneLines()[2], 0.2, -0.05, &right_blindspot_vertices, max_idx_barrier);
}
void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) {
void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {
auto *s = uiState();
auto &sm = *(s->sm);
bool blindspot = s->scene.blindspot_ui;
if (sm.rcv_frame("liveCalibration") < s->scene.started_frame ||
sm.rcv_frame("modelV2") < s->scene.started_frame) {
return;
if (blindspot) {
bool left_blindspot = sm["carState"].getCarState().getLeftBlindspot();
bool right_blindspot = sm["carState"].getCarState().getRightBlindspot();
//painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.4)); // Red with alpha for blind spot
if (left_blindspot && !left_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(left_blindspot_vertices);
}
if (right_blindspot && !right_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(right_blindspot_vertices);
}
}
clip_region = surface_rect.adjusted(-CLIP_MARGIN, -CLIP_MARGIN, CLIP_MARGIN, CLIP_MARGIN);
experimental_mode = sm["selfdriveState"].getSelfdriveState().getExperimentalMode();
longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
path_offset_z = sm["liveCalibration"].getLiveCalibration().getHeight()[0];
bool rainbow = s->scene.rainbow_mode;
//float v_ego = sm["carState"].getCarState().getVEgo();
painter.save();
if (rainbow) {
// Simple time-based animation
float time_offset = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f;
const auto &model = sm["modelV2"].getModelV2();
const auto &radar_state = sm["radarState"].getRadarState();
const auto &lead_one = radar_state.getLeadOne();
const auto &car_state = sm["carState"].getCarState();
// simple linear gradient from bottom to top
QLinearGradient bg(0, surface_rect.height(), 0, 0);
update_model(model, lead_one);
drawLaneLines(painter);
// evenly spaced colors across the spectrum
// The animation shifts the entire spectrum smoothly
float animation_speed = 40.0f; // speed vroom vroom
float hue_offset = fmod(time_offset * animation_speed, 360.0f);
if (s->scene.rainbow_mode) {
drawRainbowPath(painter, surface_rect);
// 6-8 color stops for smooth transitions more color makes it laggy
const int num_stops = 7;
for (int i = 0; i < num_stops; i++) {
float position = static_cast<float>(i) / (num_stops - 1);
float hue = fmod(hue_offset + position * 360.0f, 360.0f);
float saturation = 0.9f;
float lightness = 0.6f;
// Alpha fades out towards the far end of the path
float alpha = 0.8f * (1.0f - position * 0.3f);
QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha);
bg.setColorAt(position, color);
}
painter.setBrush(bg);
painter.drawPolygon(track_vertices);
} else {
// Normal path rendering
ModelRenderer::drawPath(painter, model, surface_rect.height());
}
if (longitudinal_control && sm.alive("radarState")) {
update_leads(radar_state, model.getPosition());
const auto &lead_two = radar_state.getLeadTwo();
if (lead_one.getStatus()) {
drawLead(painter, lead_one, lead_vertices[0], surface_rect);
}
if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) {
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
}
}
if (s->scene.blindspot_ui) {
const bool left_blindspot = car_state.getLeftBlindspot();
const bool right_blindspot = car_state.getRightBlindspot();
drawBlindspot(painter, surface_rect, left_blindspot, right_blindspot);
}
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
painter.restore();
}
void ModelRendererSP::drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot) {
if (left_blindspot && !left_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(left_blindspot_vertices);
}
if (right_blindspot && !right_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(right_blindspot_vertices);
}
}
void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) {
@@ -120,16 +121,19 @@ void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) {
}
if (has_lead_one) {
drawLeadStatusPosition(painter, lead_one, lead_vertices[0], height, width);
drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1");
}
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
drawLeadStatusPosition(painter, lead_two, lead_vertices[1], height, width);
drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2");
}
}
void ModelRendererSP::drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos, int height, int width) {
void ModelRendererSP::drawLeadStatusAtPosition(QPainter &painter,
const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos,
int height, int width,
const QString &label) {
float d_rel = lead_data.getDRel();
float v_rel = lead_data.getVRel();
auto *s = uiState();
@@ -219,36 +223,3 @@ void ModelRendererSP::drawLeadStatusPosition(QPainter &painter, const cereal::Ra
painter.setPen(Qt::NoPen);
}
void ModelRendererSP::drawRainbowPath(QPainter &painter, const QRect &surface_rect) {
// Simple time-based animation
float time_offset = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f;
// simple linear gradient from bottom to top
QLinearGradient bg(0, surface_rect.height(), 0, 0);
// evenly spaced colors across the spectrum
// The animation shifts the entire spectrum smoothly
float animation_speed = 40.0f; // speed vroom vroom
float hue_offset = fmod(time_offset * animation_speed, 360.0f);
// 6-8 color stops for smooth transitions more color makes it laggy
const int num_stops = 7;
for (int i = 0; i < num_stops; i++) {
float position = static_cast<float>(i) / (num_stops - 1);
float hue = fmod(hue_offset + position * 360.0f, 360.0f);
float saturation = 0.9f;
float lightness = 0.6f;
// Alpha fades out towards the far end of the path
float alpha = 0.8f * (1.0f - position * 0.3f);
QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha);
bg.setColorAt(position, color);
}
painter.setBrush(bg);
painter.drawPolygon(track_vertices);
}
+8 -6
View File
@@ -13,15 +13,17 @@ class ModelRendererSP : public ModelRenderer {
public:
ModelRendererSP() = default;
void draw(QPainter &painter, const QRect &surface_rect);
private:
void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) override;
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &rect) override;
// Lead status display methods
void drawLeadStatus(QPainter &painter, int height, int width);
void drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos, int height, int width);
void drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot);
void drawRainbowPath(QPainter &painter, const QRect &surface_rect);
void drawLeadStatusAtPosition(QPainter &painter,
const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos,
int height, int width,
const QString &label);
QPolygonF left_blindspot_vertices;
QPolygonF right_blindspot_vertices;
-4
View File
@@ -122,7 +122,3 @@ std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::
return std::nullopt;
}
}
bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp) {
return car_params_sp.getIntelligentCruiseButtonManagementAvailable() && Params().getBool("IntelligentCruiseButtonManagement");
}
-1
View File
@@ -23,4 +23,3 @@ std::optional<QString> getParamIgnoringDefault(const std::string &param_name, co
QMap<QString, QVariantMap> loadPlatformList();
QStringList searchFromList(const QString &query, const QStringList &list);
std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::string& _param);
bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp);
+1 -1
View File
@@ -29,7 +29,7 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) {
"wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan",
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
"carControl", "gpsLocationExternal", "gpsLocation", "liveTorqueParameters",
"carStateSP", "liveParameters", "liveMapDataSP", "carParamsSP"
"carStateSP", "liveParameters", "liveMapDataSP"
});
// update timer
+56 -56
View File
@@ -1778,6 +1778,62 @@ Warning: You are on a metered connection!</source>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>None</source>
<translation></translation>
</message>
<message>
<source>Fixed</source>
<translation></translation>
</message>
<message>
<source>Percent</source>
<translation></translation>
</message>
<message>
<source>Car
Only</source>
<translation></translation>
</message>
<message>
<source>Map
Only</source>
<translation></translation>
</message>
<message>
<source>Car
First</source>
<translation>
</translation>
</message>
<message>
<source>Map
First</source>
<translation>
</translation>
</message>
<message>
<source>Combined
Data</source>
<translation>
</translation>
</message>
<message>
<source>Off</source>
<translation></translation>
</message>
<message>
<source>Information</source>
<translation></translation>
</message>
<message>
<source>Warning</source>
<translation></translation>
</message>
<message>
<source>Assist</source>
<translation></translation>
</message>
</context>
<context>
<name>SettingsWindow</name>
@@ -2142,34 +2198,6 @@ Warning: You are on a metered connection!</source>
<source>⦿ Combined: Use combined Speed Limit data from Car &amp; OpenStreetMaps</source>
<translation>⦿ 결합: 차량 OpenStreetMaps의 </translation>
</message>
<message>
<source>Car
Only</source>
<translation></translation>
</message>
<message>
<source>Map
Only</source>
<translation></translation>
</message>
<message>
<source>Car
First</source>
<translation>
</translation>
</message>
<message>
<source>Map
First</source>
<translation>
</translation>
</message>
<message>
<source>Combined
Data</source>
<translation>
</translation>
</message>
</context>
<context>
<name>SpeedLimitSettings</name>
@@ -2217,34 +2245,6 @@ Data</source>
<source>⦿ Assist: Adjusts the vehicle&apos;s cruise speed based on the current road&apos;s speed limit when operating the +/- buttons.</source>
<translation>⦿ : +/- .</translation>
</message>
<message>
<source>None</source>
<translation></translation>
</message>
<message>
<source>Fixed</source>
<translation></translation>
</message>
<message>
<source>Percent</source>
<translation></translation>
</message>
<message>
<source>Off</source>
<translation></translation>
</message>
<message>
<source>Information</source>
<translation></translation>
</message>
<message>
<source>Warning</source>
<translation></translation>
</message>
<message>
<source>Assist</source>
<translation></translation>
</message>
</context>
<context>
<name>SshControl</name>
+1
View File
@@ -60,6 +60,7 @@ typedef struct UIScene {
cereal::PandaState::PandaType pandaType;
cereal::LongitudinalPersonality personality;
cereal::LongitudinalPlanSP::AccelerationPersonality accel_personality;
float light_sensor = -1;
bool started, ignition, is_metric, recording_audio;
-21
View File
@@ -5,7 +5,6 @@ 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 enum import IntEnum
import hashlib
PARAMS_UPDATE_PERIOD = 3 # seconds
@@ -17,23 +16,3 @@ def get_file_hash(path: str) -> str:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
class IntEnumBase(IntEnum):
@classmethod
def min(cls):
return min(cls)
@classmethod
def max(cls):
return max(cls)
def get_sanitize_int_param(key: str, min_val: int, max_val: int, params) -> int:
val: int = params.get(key, return_default=True)
clipped_val = max(min_val, min(max_val, val))
if clipped_val != val:
params.put(key, clipped_val)
return clipped_val
@@ -8,12 +8,8 @@ from abc import abstractmethod, ABC
import cereal.messaging as messaging
from openpilot.common.params import Params
from openpilot.common.constants import CV
from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET
from openpilot.sunnypilot.navd.helpers import coordinate_from_param
MAX_SPEED_LIMIT = V_CRUISE_UNSET * CV.KPH_TO_MS
class BaseMapData(ABC):
def __init__(self):
@@ -50,9 +46,9 @@ class BaseMapData(ABC):
mapd_sp_send.valid = self.sm['liveLocationKalman'].gpsOK
live_map_data = mapd_sp_send.liveMapDataSP
live_map_data.speedLimitValid = bool(MAX_SPEED_LIMIT > speed_limit > 0)
live_map_data.speedLimitValid = bool(speed_limit > 0)
live_map_data.speedLimit = speed_limit
live_map_data.speedLimitAheadValid = bool(MAX_SPEED_LIMIT > next_speed_limit > 0)
live_map_data.speedLimitAheadValid = bool(next_speed_limit > 0)
live_map_data.speedLimitAhead = next_speed_limit
live_map_data.speedLimitAheadDistance = next_speed_limit_distance
live_map_data.roadName = self.get_current_road_name()
-32
View File
@@ -11,7 +11,6 @@ from opendbc.car.interfaces import CarInterfaceBase
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.helpers import get_nn_model_path
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode
import openpilot.system.sentry as sentry
@@ -67,30 +66,6 @@ def _initialize_torque_lateral_control(CI: CarInterfaceBase, CP: structs.CarPara
CI.configure_torque_tune(CP.carFingerprint, CP.lateralTuning)
def _cleanup_unsupported_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params = None) -> None:
if params is None:
params = Params()
if CP.steerControlType == structs.CarParams.SteerControlType.angle:
cloudlog.warning("SteerControlType is angle, cleaning up params")
params.remove("NeuralNetworkLateralControl")
params.remove("EnforceTorqueControl")
if not CP_SP.intelligentCruiseButtonManagementAvailable or CP.openpilotLongitudinalControl:
cloudlog.warning("ICBM not available or openpilot Longitudinal Control enabled, cleaning up params")
params.remove("IntelligentCruiseButtonManagement")
if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed:
cloudlog.warning("openpilot Longitudinal Control and ICBM not available, cleaning up params")
params.remove("DynamicExperimentalControl")
params.remove("CustomAccIncrementsEnabled")
params.remove("SmartCruiseControlVision")
params.remove("SmartCruiseControlMap")
if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist:
params.put("SpeedLimitMode", int(SpeedLimitMode.warning))
def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None:
CP = CI.CP
CP_SP = CI.CP_SP
@@ -99,7 +74,6 @@ def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None:
nnlc_enabled = _initialize_neural_network_lateral_control(CP, CP_SP, params)
_initialize_intelligent_cruise_button_management(CP, CP_SP, params)
_initialize_torque_lateral_control(CI, CP, enforce_torque, nnlc_enabled)
_cleanup_unsupported_params(CP, CP_SP)
def initialize_params(params) -> list[dict[str, Any]]:
@@ -110,10 +84,4 @@ def initialize_params(params) -> list[dict[str, Any]]:
"HyundaiLongitudinalTuning"
])
# subaru
keys.extend([
"SubaruStopAndGo",
"SubaruStopAndGoManualParkingBrake",
])
return [{k: params.get(k, return_default=True)} for k in keys]
@@ -12,159 +12,47 @@ from openpilot.common.realtime import DT_MDL
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
GREEN_LIGHT_X_THRESHOLD = 30
LEAD_DEPART_DIST_THRESHOLD = 1.0
TRIGGER_TIMER_THRESHOLD = 0.3
class E2EStates:
INACTIVE = 0
ARMED = 1
CONSUMED = 2
TRIGGER_THRESHOLD = 30
class E2EAlertsHelper:
def __init__(self):
self._params = Params()
self.frame = -1
self.green_light_state = E2EStates.INACTIVE
self.prev_green_light_state = E2EStates.INACTIVE
self.lead_depart_state = E2EStates.INACTIVE
self.prev_lead_depart_state = E2EStates.INACTIVE
self._frame = -1
self.green_light_alert = False
self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert")
self.lead_depart_alert = False
self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert")
self.green_light_trigger_timer = 0
self.lead_depart_trigger_timer = 0
self.last_lead_distance = -1
self.last_moving_frame = -1
self.allowed = False
self.last_allowed = False
self.has_lead = False
self.lead_depart_arm_timer = 0
self.lead_depart_confirmed_lead = False
self.lead_depart_armed = False
def _read_params(self) -> None:
if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0:
if self._frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0:
self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert")
self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert")
def update_alert_trigger(self, sm: messaging.SubMaster):
self._frame += 1
def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None:
self._read_params()
if not (self.green_light_alert_enabled or self.lead_depart_alert_enabled):
return
CS = sm['carState']
CC = sm['carControl']
model_x = sm['modelV2'].position.x
max_idx = len(model_x) - 1
self.has_lead = sm['radarState'].leadOne.status
lead_dRel = sm['radarState'].leadOne.dRel
has_lead = sm['radarState'].leadOne.status
lead_vRel: float = sm['radarState'].leadOne.vRel
standstill = CS.standstill
moving = not standstill and CS.vEgo > 0.1
if moving:
self.last_moving_frame = self.frame
recent_moving = self.last_moving_frame == -1 or (self.frame - self.last_moving_frame) * DT_MDL < 2.0
self.allowed = not moving and not CS.gasPressed and not CC.enabled and not recent_moving
# Green Light Alert
green_light_trigger = False
if self.green_light_state == E2EStates.ARMED:
if model_x[max_idx] > GREEN_LIGHT_X_THRESHOLD:
self.green_light_trigger_timer += 1
else:
self.green_light_trigger_timer = 0
if self.green_light_trigger_timer * DT_MDL > TRIGGER_TIMER_THRESHOLD:
green_light_trigger = True
elif self.green_light_state != E2EStates.ARMED:
self.green_light_trigger_timer = 0
# Green light alert
self.green_light_alert = (self.green_light_alert_enabled and model_x[max_idx] > TRIGGER_THRESHOLD
and not has_lead and CS.standstill and not CS.gasPressed and not CC.enabled)
# Lead Departure Alert
close_lead_valid = self.has_lead and lead_dRel < 8.0
if self.allowed and not self.last_allowed and close_lead_valid:
self.lead_depart_confirmed_lead = True
elif not self.allowed:
self.lead_depart_confirmed_lead = False
if self.allowed and self.lead_depart_confirmed_lead and close_lead_valid:
self.lead_depart_arm_timer += 1
if self.lead_depart_arm_timer * DT_MDL >= 1.0:
self.lead_depart_armed = True
else:
self.lead_depart_arm_timer = 0
self.lead_depart_armed = False
lead_depart_trigger = False
if self.lead_depart_state == E2EStates.ARMED:
if self.last_lead_distance == -1 or lead_dRel < self.last_lead_distance:
self.last_lead_distance = lead_dRel
if self.last_lead_distance != -1 and (lead_dRel - self.last_lead_distance > LEAD_DEPART_DIST_THRESHOLD):
self.lead_depart_trigger_timer += 1
else:
self.lead_depart_trigger_timer = 0
if self.lead_depart_trigger_timer * DT_MDL > TRIGGER_TIMER_THRESHOLD:
lead_depart_trigger = True
elif self.lead_depart_state != E2EStates.ARMED:
self.last_lead_distance = -1
self.lead_depart_trigger_timer = 0
self.last_allowed = self.allowed
return green_light_trigger, lead_depart_trigger
@staticmethod
def update_state_machine(state: int, enabled: bool, allowed: bool, triggered: bool) -> tuple[int, bool]:
if state != E2EStates.INACTIVE:
if not allowed or not enabled:
state = E2EStates.INACTIVE
else:
if state == E2EStates.ARMED:
if triggered:
state = E2EStates.CONSUMED
elif state == E2EStates.CONSUMED:
pass
elif state == E2EStates.INACTIVE:
if allowed and enabled:
state = E2EStates.ARMED
return state, triggered
def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None:
self._read_params()
green_light_trigger, lead_depart_trigger = self.update_alert_trigger(sm)
self.prev_green_light_state = self.green_light_state
self.prev_lead_depart_state = self.lead_depart_state
self.green_light_state, self.green_light_alert = self.update_state_machine(
self.green_light_state,
self.green_light_alert_enabled,
self.allowed and not self.has_lead,
green_light_trigger
)
self.lead_depart_state, self.lead_depart_alert = self.update_state_machine(
self.lead_depart_state,
self.lead_depart_alert_enabled,
self.allowed and self.lead_depart_armed,
lead_depart_trigger
)
self.lead_depart_alert = (self.lead_depart_alert_enabled and CS.standstill and model_x[max_idx] > 30
and has_lead and lead_vRel > 1 and not CS.gasPressed)
if self.green_light_alert or self.lead_depart_alert:
events_sp.add(custom.OnroadEventSP.EventName.e2eChime)
self.frame += 1
@@ -9,15 +9,13 @@ from cereal import custom
from openpilot.common.constants import CV
from openpilot.common.params import Params
TurnDirection = custom.ModelDataV2SP.TurnDirection
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
class LaneTurnController:
def __init__(self, desire_helper):
self.DH = desire_helper
self.turn_direction = TurnDirection.none
self.turn_direction = custom.TurnDirection.none
self.params = Params()
self.lane_turn_value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS
self.param_read_counter = 0
@@ -35,13 +33,13 @@ class LaneTurnController:
def update_lane_turn(self, blindspot_left: bool, blindspot_right: bool, left_blinker: bool, right_blinker: bool, v_ego: float) -> None:
if left_blinker and not right_blinker and v_ego < self.lane_turn_value and not blindspot_left:
self.turn_direction = TurnDirection.turnLeft
self.turn_direction = custom.TurnDirection.turnLeft
elif right_blinker and not left_blinker and v_ego < self.lane_turn_value and not blindspot_right:
self.turn_direction = TurnDirection.turnRight
self.turn_direction = custom.TurnDirection.turnRight
else:
self.turn_direction = TurnDirection.none
self.turn_direction = custom.TurnDirection.none
def get_turn_direction(self):
if not self.enabled:
return TurnDirection.none
return custom.TurnDirection.none
return self.turn_direction
@@ -17,6 +17,7 @@ from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_resolve
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
from openpilot.sunnypilot.models.helpers import get_active_bundle
from openpilot.sunnypilot.selfdrive.controls.lib.vibe_personality.vibe_personality import VibePersonalityController
DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimentalControlState
LongitudinalPlanSource = custom.LongitudinalPlanSP.LongitudinalPlanSource
@@ -29,6 +30,7 @@ class LongitudinalPlannerSP:
self.scc = SmartCruiseControl()
self.resolver = SpeedLimitResolver()
self.sla = SpeedLimitAssist(CP)
self.vibe_controller = VibePersonalityController()
self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None
self.source = LongitudinalPlanSource.cruise
self.e2e_alerts_helper = E2EAlertsHelper()
@@ -81,6 +83,7 @@ class LongitudinalPlannerSP:
self.events_sp.clear()
self.dec.update(sm)
self.e2e_alerts_helper.update(sm, self.events_sp)
self.vibe_controller.update()
def publish_longitudinal_plan_sp(self, sm: messaging.SubMaster, pm: messaging.PubMaster) -> None:
plan_sp_send = messaging.new_message('longitudinalPlanSP')
@@ -4,11 +4,10 @@ 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 openpilot.sunnypilot import IntEnumBase
from enum import IntEnum
class Policy(IntEnumBase):
class Policy(IntEnum):
car_state_only = 0
map_data_only = 1
car_state_priority = 2
@@ -16,13 +15,13 @@ class Policy(IntEnumBase):
combined = 4
class OffsetType(IntEnumBase):
class OffsetType(IntEnum):
off = 0
fixed = 1
percentage = 2
class Mode(IntEnumBase):
class Mode(IntEnum):
off = 0
information = 1
warning = 2
@@ -27,11 +27,7 @@ ACTIVE_STATES = (SpeedLimitAssistState.active, SpeedLimitAssistState.adapting)
ENABLED_STATES = (SpeedLimitAssistState.preActive, SpeedLimitAssistState.pending, *ACTIVE_STATES)
DISABLED_GUARD_PERIOD = 0.5 # secs.
# secs. Time to wait after activation before considering temp deactivation signal.
PRE_ACTIVE_GUARD_PERIOD = {
True: 15,
False: 5,
}
PRE_ACTIVE_GUARD_PERIOD = 15 # secs. Time to wait after activation before considering temp deactivation signal.
SPEED_LIMIT_CHANGED_HOLD_PERIOD = 1 # secs. Time to wait after speed limit change before switching to preActive.
LIMIT_MIN_ACC = -1.5 # m/s^2 Maximum deceleration allowed for limit controllers to provide.
@@ -113,16 +109,6 @@ class SpeedLimitAssist:
def target_set_speed_confirmed(self) -> bool:
return bool(self.v_cruise_cluster_conv == self.target_set_speed_conv)
@property
def v_cruise_cluster_below_confirm_speed_threshold(self) -> bool:
return bool(self.v_cruise_cluster_conv < CONFIRM_SPEED_THRESHOLD[self.is_metric])
def update_active_event(self, events_sp: EventsSP) -> None:
if self.v_cruise_cluster_below_confirm_speed_threshold:
events_sp.add(EventNameSP.speedLimitChanged)
else:
events_sp.add(EventNameSP.speedLimitActive)
def get_v_target_from_control(self) -> float:
if self._has_speed_limit:
if self.pcm_op_long and self.is_enabled:
@@ -189,7 +175,7 @@ class SpeedLimitAssist:
@property
def apply_confirm_speed_threshold(self) -> bool:
# below CST: always require user confirmation
if self.v_cruise_cluster_below_confirm_speed_threshold:
if self.v_cruise_cluster_conv < CONFIRM_SPEED_THRESHOLD[self.is_metric]:
return True
# at/above CST:
@@ -245,7 +231,7 @@ class SpeedLimitAssist:
self.state = SpeedLimitAssistState.inactive
elif self.speed_limit_changed and self.apply_confirm_speed_threshold:
self.state = SpeedLimitAssistState.preActive
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL)
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)
elif self._has_speed_limit and self.v_offset < LIMIT_SPEED_OFFSET_TH:
self.state = SpeedLimitAssistState.adapting
@@ -255,7 +241,7 @@ class SpeedLimitAssist:
self.state = SpeedLimitAssistState.inactive
elif self.speed_limit_changed and self.apply_confirm_speed_threshold:
self.state = SpeedLimitAssistState.preActive
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL)
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)
elif self.v_offset >= LIMIT_SPEED_OFFSET_TH:
self.state = SpeedLimitAssistState.active
@@ -265,7 +251,7 @@ class SpeedLimitAssist:
self._update_confirmed_state()
elif self.speed_limit_changed:
self.state = SpeedLimitAssistState.preActive
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL)
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)
# PRE_ACTIVE
elif self.state == SpeedLimitAssistState.preActive:
@@ -291,7 +277,7 @@ class SpeedLimitAssist:
self._update_confirmed_state()
elif self._has_speed_limit:
self.state = SpeedLimitAssistState.preActive
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL)
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)
else:
self.state = SpeedLimitAssistState.pending
@@ -317,7 +303,7 @@ class SpeedLimitAssist:
elif self.speed_limit_changed and self.apply_confirm_speed_threshold:
self.state = SpeedLimitAssistState.preActive
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL)
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)
# PRE_ACTIVE
elif self.state == SpeedLimitAssistState.preActive:
@@ -331,7 +317,7 @@ class SpeedLimitAssist:
elif self.state == SpeedLimitAssistState.inactive:
if self.speed_limit_changed:
self.state = SpeedLimitAssistState.preActive
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL)
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)
elif self._update_non_pcm_long_confirmed_state():
self.state = SpeedLimitAssistState.active
@@ -347,7 +333,7 @@ class SpeedLimitAssist:
self.state = SpeedLimitAssistState.active
elif self._has_speed_limit:
self.state = SpeedLimitAssistState.preActive
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL)
self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)
else:
self.state = SpeedLimitAssistState.inactive
@@ -365,15 +351,15 @@ class SpeedLimitAssist:
if self.is_active:
if self._state_prev not in ACTIVE_STATES:
self.update_active_event(events_sp)
events_sp.add(EventNameSP.speedLimitActive)
# only notify if we acquire a valid speed limit
# do not check has_speed_limit here
elif self._speed_limit != self.speed_limit_prev:
if self.speed_limit_prev <= 0:
self.update_active_event(events_sp)
events_sp.add(EventNameSP.speedLimitActive)
elif self.speed_limit_prev > 0 and self._speed_limit > 0:
self.update_active_event(events_sp)
events_sp.add(EventNameSP.speedLimitChanged)
def update(self, long_enabled: bool, long_override: bool, v_ego: float, a_ego: float, v_cruise_cluster: float, speed_limit: float,
speed_limit_final_last: float, has_speed_limit: bool, distance: float, events_sp: EventsSP) -> None:
@@ -12,7 +12,7 @@ from openpilot.common.constants import CV
from openpilot.common.gps import get_gps_location_service
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD, get_sanitize_int_param
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import LIMIT_MAX_MAP_DATA_AGE, LIMIT_ADAPT_ACC
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Policy, OffsetType
@@ -42,12 +42,6 @@ class SpeedLimitResolver:
self.distance_solutions = {} # Store for distance to current speed limit start for different sources
self.policy = self.params.get("SpeedLimitPolicy", return_default=True)
self.policy = get_sanitize_int_param(
"SpeedLimitPolicy",
Policy.min().value,
Policy.max().value,
self.params
)
self._policy_to_sources_map = {
Policy.car_state_only: [SpeedLimitSource.car],
Policy.map_data_only: [SpeedLimitSource.map],
@@ -60,12 +54,7 @@ class SpeedLimitResolver:
self._reset_limit_sources(source)
self.is_metric = self.params.get_bool("IsMetric")
self.offset_type = get_sanitize_int_param(
"SpeedLimitOffsetType",
OffsetType.min().value,
OffsetType.max().value,
self.params
)
self.offset_type = self.params.get("SpeedLimitOffsetType", return_default=True)
self.offset_value = self.params.get("SpeedLimitValueOffset", return_default=True)
self.speed_limit = 0.
@@ -142,7 +131,6 @@ class SpeedLimitResolver:
self.limit_solutions[SpeedLimitSource.map] = speed_limit
self.distance_solutions[SpeedLimitSource.map] = 0.
# FIXME-SP: this is not working as expected
if 0. < next_speed_limit < self.v_ego:
adapt_time = (next_speed_limit - self.v_ego) / LIMIT_ADAPT_ACC
adapt_distance = self.v_ego * adapt_time + 0.5 * LIMIT_ADAPT_ACC * adapt_time ** 2
@@ -39,7 +39,7 @@ class TestSpeedLimitAssist:
self.events_sp = EventsSP()
CI = self._setup_platform(TOYOTA.TOYOTA_RAV4_TSS2)
self.sla = SpeedLimitAssist(CI.CP)
self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL)
self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)
self.pcm_long_max_set_speed = PCM_LONG_REQUIRED_MAX_SET_SPEED[self.sla.is_metric][1] # use 80 MPH for now
self.speed_conv = CV.MS_TO_KPH if self.sla.is_metric else CV.MS_TO_MPH
@@ -114,7 +114,7 @@ class TestSpeedLimitAssist:
self.sla.state = SpeedLimitAssistState.preActive
self.sla.update(True, False, SPEED_LIMITS['city'], 0, SPEED_LIMITS['highway'], SPEED_LIMITS['city'], SPEED_LIMITS['city'], True, 0, self.events_sp)
for _ in range(int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL)):
for _ in range(int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)):
self.sla.update(True, False, SPEED_LIMITS['city'], 0, SPEED_LIMITS['highway'], SPEED_LIMITS['city'], SPEED_LIMITS['city'], True, 0, self.events_sp)
assert self.sla.state == SpeedLimitAssistState.inactive
@@ -1,113 +1,113 @@
import pytest
from cereal import log, custom
from cereal import log
from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController, LANE_CHANGE_SPEED_MIN
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeMode
TurnDirection = custom.ModelDataV2SP.TurnDirection
class TurnDirection:
none = 0
turnLeft = 1
turnRight = 2
@pytest.mark.parametrize("left_blinker,right_blinker,v_ego,blindspot_left,blindspot_right,expected", [
(True, False, 5, False, False, TurnDirection.turnLeft),
(False, True, 6, False, False, TurnDirection.turnRight),
(True, False, 9, False, False, TurnDirection.none),
(True, False, 7, True, False, TurnDirection.none),
(False, True, 6, False, True, TurnDirection.none),
(False, False, 5, False, False, TurnDirection.none),
(True, True, 5, False, False, TurnDirection.none),
(True, False, 5, False, False, TurnDirection.turnLeft),
(False, True, 6, False, False, TurnDirection.turnRight),
(True, False, 9, False, False, TurnDirection.none),
(True, False, 7, True, False, TurnDirection.none),
(False, True, 6, False, True, TurnDirection.none),
(False, False, 5, False, False, TurnDirection.none),
(True, True, 5, False, False, TurnDirection.none),
])
def test_lane_turn_desire_conditions(left_blinker, right_blinker, v_ego, blindspot_left, blindspot_right, expected):
dh = DesireHelper()
controller = LaneTurnController(dh)
controller.enabled = True
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
controller.turn_direction = TurnDirection.none
controller.update_lane_turn(blindspot_left, blindspot_right, left_blinker, right_blinker, v_ego)
assert controller.get_turn_direction() == expected
dh = DesireHelper()
controller = LaneTurnController(dh)
controller.enabled = True
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
controller.turn_direction = TurnDirection.none
controller.update_lane_turn(blindspot_left, blindspot_right, left_blinker, right_blinker, v_ego)
assert controller.get_turn_direction() == expected
def test_lane_turn_desire_disabled():
dh = DesireHelper()
controller = LaneTurnController(dh)
controller.enabled = False
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
controller.turn_direction = TurnDirection.none
controller.update_lane_turn(False, False, True, False, 7)
assert controller.get_turn_direction() == TurnDirection.none
dh = DesireHelper()
controller = LaneTurnController(dh)
controller.enabled = False
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
controller.turn_direction = TurnDirection.none
controller.update_lane_turn(False, False, True, False, 7)
assert controller.get_turn_direction() == TurnDirection.none
def test_lane_turn_overrides_lane_change():
dh = DesireHelper()
controller = LaneTurnController(dh)
controller.enabled = True
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
controller.turn_direction = TurnDirection.none
# left turn desire
controller.update_lane_turn(False, False, True, False, 5)
assert controller.get_turn_direction() == TurnDirection.turnLeft
# right turn desire
controller.update_lane_turn(False, False, False, True, 6)
assert controller.get_turn_direction() == TurnDirection.turnRight
# no turn
controller.update_lane_turn(False, False, False, False, 7)
assert controller.get_turn_direction() == TurnDirection.none
dh = DesireHelper()
controller = LaneTurnController(dh)
controller.enabled = True
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
controller.turn_direction = TurnDirection.none
# left turn desire
controller.update_lane_turn(False, False, True, False, 5)
assert controller.get_turn_direction() == TurnDirection.turnLeft
# right turn desire
controller.update_lane_turn(False, False, False, True, 6)
assert controller.get_turn_direction() == TurnDirection.turnRight
# no turn
controller.update_lane_turn(False, False, False, False, 7)
assert controller.get_turn_direction() == TurnDirection.none
@pytest.mark.parametrize("v_ego,expected", [
(8.93, TurnDirection.turnLeft), # just below threshold
(8.96, TurnDirection.none), # above threshold
(8.95, TurnDirection.none), # just above threshold
(8.93, TurnDirection.turnLeft), # just below threshold
(8.96, TurnDirection.none), # above threshold
(8.95, TurnDirection.none), # just above threshold
])
def test_lane_turn_desire_speed_boundary(v_ego, expected):
dh = DesireHelper()
controller = LaneTurnController(dh)
controller.enabled = True
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
controller.turn_direction = TurnDirection.none
controller.update_lane_turn(False, True, True, False, v_ego)
assert controller.get_turn_direction() == expected
dh = DesireHelper()
controller = LaneTurnController(dh)
controller.enabled = True
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
controller.turn_direction = TurnDirection.none
controller.update_lane_turn(False, True, True, False, v_ego)
assert controller.get_turn_direction() == expected
class DummyCarState:
def __init__(self, vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False,
steeringPressed=False, steeringTorque=0, brakePressed=False):
self.vEgo = vEgo
self.leftBlinker = leftBlinker
self.rightBlinker = rightBlinker
self.leftBlindspot = leftBlindspot
self.rightBlindspot = rightBlindspot
self.steeringPressed = steeringPressed
self.steeringTorque = steeringTorque
self.brakePressed = brakePressed
def __init__(self, vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False,
steeringPressed=False, steeringTorque=0, brakePressed=False):
self.vEgo = vEgo
self.leftBlinker = leftBlinker
self.rightBlinker = rightBlinker
self.leftBlindspot = leftBlindspot
self.rightBlindspot = rightBlindspot
self.steeringPressed = steeringPressed
self.steeringTorque = steeringTorque
self.brakePressed = brakePressed
@pytest.fixture
def set_lane_turn_params():
params = Params()
params.put("LaneTurnDesire", True)
params.put("LaneTurnValue", 20.0)
params = Params()
params.put("LaneTurnDesire", True)
params.put("LaneTurnValue", 20.0)
@pytest.mark.parametrize("carstate, lateral_active, lane_change_prob, expected_desire", [
# Lane turn desire overrides lane change desire
(DummyCarState(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False), True, 1.0,
log.Desire.turnLeft),
(DummyCarState(vEgo=7, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False), True, 1.0,
log.Desire.turnRight),
# Lane change desire only (no turn desires)
(DummyCarState(vEgo=9, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False,
steeringPressed=True, steeringTorque=1), True, 1.0, log.Desire.laneChangeLeft),
(DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False,
steeringPressed=True, steeringTorque=-1), True, 1.0, log.Desire.laneChangeRight),
# No desire (inactive)
(DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=False), False, 1.0, log.Desire.none),
(DummyCarState(vEgo=4, leftBlinker=False, rightBlinker=False), True, 1.0, log.Desire.none), # No blinkers? no desire!
# Lane turn desire overrides lane change desire
(DummyCarState(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False), True, 1.0, log.Desire.turnLeft),
(DummyCarState(vEgo=7, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False), True, 1.0, log.Desire.turnRight),
# Lane change desire only (no turn desires)
(DummyCarState(vEgo=9, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False,
steeringPressed=True, steeringTorque=1), True, 1.0, log.Desire.laneChangeLeft),
(DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False,
steeringPressed=True, steeringTorque=-1), True, 1.0, log.Desire.laneChangeRight),
# No desire (inactive)
(DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=False), False, 1.0, log.Desire.none),
(DummyCarState(vEgo=4, leftBlinker=False, rightBlinker=False), True, 1.0, log.Desire.none), # No blinkers? no desire!
])
def test_desire_helper_integration(carstate, lateral_active, lane_change_prob, expected_desire, set_lane_turn_params):
dh = DesireHelper()
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
for _ in range(10):
dh.update(carstate, lateral_active, lane_change_prob)
assert dh.desire == expected_desire # The first four tests were unit tests to test the controller, where this tests the integration in desire helpers
dh = DesireHelper()
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
for _ in range(10):
dh.update(carstate, lateral_active, lane_change_prob)
assert dh.desire == expected_desire # The first four tests were unit tests to test the controller, where this tests the integration in desire helpers
@@ -0,0 +1,74 @@
"""
Copyright (c) 2021-, rav4kumar, 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 log, custom
import numpy as np
from openpilot.common.realtime import DT_MDL
from openpilot.common.params import Params
LongPersonality = log.LongitudinalPersonality
AccelPersonality = custom.LongitudinalPlanSP.AccelerationPersonality
MAX_ACCEL_PROFILES = {
AccelPersonality.eco: [2.0, 1.99, 1.88, 1.10, .500, .292, .15, .10],
AccelPersonality.normal: [2.0, 2.00, 1.94, 1.22, .635, .33, .20, .16],
AccelPersonality.sport: [2.0, 2.00, 2.00, 1.85, .800, .54, .32, .22],
}
MAX_ACCEL_BREAKPOINTS = [0., 4., 6., 9., 16., 25., 30., 55.]
MIN_ACCEL_PROFILES = {
LongPersonality.relaxed: [-.0007, -.0007, -.07, -1.00, -1.00],
LongPersonality.standard: [-.0007, -.0007, -.08, -1.10, -1.10],
LongPersonality.aggressive: [-.0006, -.0007, -.09, -1.20, -1.20],
}
MIN_ACCEL_BREAKPOINTS = [0., 3.0, 11., 22., 50.]
class AccelPersonalityController:
def __init__(self):
self.params = Params()
self.frame = 0
self.accel_personality = AccelPersonality.normal
self.long_personality = LongPersonality.standard
def _update_from_params(self):
if self.frame % int(1. / DT_MDL) != 0:
return
accel_bytes = self.params.get('AccelPersonality')
if accel_bytes:
self.accel_personality = int(accel_bytes)
long_bytes = self.params.get('LongitudinalPersonality')
if long_bytes:
self.long_personality = int(long_bytes)
def is_enabled(self) -> bool:
return self.params.get_bool('VibeAccelPersonalityEnabled') or False
def set_accel_personality(self, personality: int) -> bool:
if personality not in [AccelPersonality.eco, AccelPersonality.normal, AccelPersonality.sport]:
return False
self.accel_personality = personality
self.params.put('AccelPersonality', str(personality))
return True
def cycle_accel_personality(self) -> int:
personalities = [AccelPersonality.eco, AccelPersonality.normal, AccelPersonality.sport]
idx = personalities.index(self.accel_personality)
next_p = personalities[(idx + 1) % len(personalities)]
self.set_accel_personality(next_p)
return int(next_p)
def get_accel_limits(self, v_ego: float) -> tuple[float, float] | None:
if not self.is_enabled():
return None
self._update_from_params()
max_a = np.interp(v_ego, MAX_ACCEL_BREAKPOINTS, MAX_ACCEL_PROFILES[self.accel_personality])
min_a = np.interp(v_ego, MIN_ACCEL_BREAKPOINTS, MIN_ACCEL_PROFILES[self.long_personality])
return float(min_a), float(max_a)
def update(self):
self.frame += 1
@@ -0,0 +1,46 @@
"""
Copyright (c) 2021-, rav4kumar, 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 log
import numpy as np
from openpilot.common.realtime import DT_MDL
from openpilot.common.params import Params
LongPersonality = log.LongitudinalPersonality
FOLLOW_PROFILES = {
LongPersonality.relaxed: [1.55, 1.65, 1.65, 1.80, 1.80],
LongPersonality.standard: [1.35, 1.45, 1.45, 1.55, 1.55],
LongPersonality.aggressive: [1.05, 1.10, 1.10, 1.25, 1.35],
}
FOLLOW_BREAKPOINTS = [0., 6., 18., 20., 36.]
class FollowPersonalityController:
def __init__(self):
self.params = Params()
self.frame = 0
self.long_personality = LongPersonality.standard
def _update_from_params(self):
if self.frame % int(1. / DT_MDL) != 0:
return
long_bytes = self.params.get('LongitudinalPersonality')
if long_bytes:
self.long_personality = int(long_bytes)
def is_enabled(self) -> bool:
return self.params.get_bool('VibeFollowPersonalityEnabled') or False
def get_follow_distance_multiplier(self, v_ego: float) -> float | None:
if not self.is_enabled():
return None
self._update_from_params()
return float(np.interp(v_ego, FOLLOW_BREAKPOINTS, FOLLOW_PROFILES[self.long_personality]))
def update(self):
self.frame += 1
@@ -0,0 +1,314 @@
import pytest
# Import the actual modules
from cereal import log, custom
from openpilot.common.realtime import DT_MDL
# Import the enums we need for testing
LongPersonality = log.LongitudinalPersonality
AccelPersonality = custom.LongitudinalPlanSP.AccelerationPersonality
class MockParams:
"""Simple mock for Params class"""
def __init__(self):
self.data = {}
self.bool_data = {
'VibePersonalityEnabled': True,
'VibeAccelPersonalityEnabled': True,
'VibeFollowPersonalityEnabled': True
}
def get(self, key, encoding=None):
return self.data.get(key)
def get_bool(self, key):
return self.bool_data.get(key, True)
def put(self, key, value):
self.data[key] = value
def put_bool(self, key, value):
self.bool_data[key] = value
def reset_mock(self):
self.call_count = 0
@property
def call_count(self):
return getattr(self, '_call_count', 0)
@call_count.setter
def call_count(self, value):
self._call_count = value
@pytest.fixture
def mock_params():
"""Create mock params instance"""
return MockParams()
@pytest.fixture
def controller(mock_params, monkeypatch):
"""Create controller instance with mocked Params"""
# Patch the Params import in the controller module
monkeypatch.setattr('openpilot.sunnypilot.selfdrive.controls.lib.vibe_personality.vibe_personality.Params',
lambda: mock_params)
from openpilot.sunnypilot.selfdrive.controls.lib.vibe_personality.vibe_personality import VibePersonalityController
return VibePersonalityController()
class TestVibePersonalityController:
def test_initialization(self, controller):
"""Test controller initializes with correct defaults"""
assert controller.frame == 0
assert controller.accel_personality == AccelPersonality.normal
assert controller.long_personality == LongPersonality.standard
assert 'accel_personality' in controller.param_keys
assert 'long_personality' in controller.param_keys
def test_frame_increment(self, controller):
"""Test frame counter increments correctly"""
initial_frame = controller.frame
controller.update()
assert controller.frame == initial_frame + 1
controller.update()
assert controller.frame == initial_frame + 2
def test_parameter_reading_throttled(self, controller, mock_params):
"""Test parameters are only read every DT_MDL frames"""
# Track calls manually
original_get = mock_params.get
call_count = 0
def counting_get(*args, **kwargs):
nonlocal call_count
call_count += 1
return original_get(*args, **kwargs)
mock_params.get = counting_get
# First call should read params (frame 0)
controller._update_from_params()
# Reset counter
call_count = 0
# Advance frame but not to threshold
controller.frame = 5 # Less than int(1/DT_MDL)
controller._update_from_params()
assert call_count == 0 # Should not read params
# Advance to threshold
controller.frame = int(1. / DT_MDL) # Equal to threshold
controller._update_from_params()
assert call_count >= 2 # Should read both personality params
def test_accel_personality_management(self, controller, mock_params):
"""Test acceleration personality setting and cycling"""
# Test setting valid personality
assert controller.set_accel_personality(AccelPersonality.eco)
assert controller.accel_personality == AccelPersonality.eco
assert controller.set_accel_personality(AccelPersonality.sport)
assert controller.accel_personality == AccelPersonality.sport
# Test setting invalid personality
assert not controller.set_accel_personality(999)
assert controller.accel_personality == AccelPersonality.sport # Should remain unchanged
# Test cycling
controller.accel_personality = AccelPersonality.eco
next_personality = controller.cycle_accel_personality()
assert next_personality == AccelPersonality.normal # should cycle to normal
assert controller.accel_personality == AccelPersonality.normal
next_personality = controller.cycle_accel_personality()
assert next_personality == AccelPersonality.sport # should cycle to sport
next_personality = controller.cycle_accel_personality()
assert next_personality == AccelPersonality.eco # should cycle back to eco
def test_long_personality_management(self, controller, mock_params):
"""Test longitudinal personality setting and cycling"""
# Test setting valid personality
assert controller.set_long_personality(LongPersonality.relaxed)
assert controller.long_personality == LongPersonality.relaxed
assert controller.set_long_personality(LongPersonality.aggressive)
assert controller.long_personality == LongPersonality.aggressive
# Test setting invalid personality
assert not controller.set_long_personality(999)
assert controller.long_personality == LongPersonality.aggressive # Should remain unchanged
# Test cycling
controller.long_personality = LongPersonality.standard
next_personality = controller.cycle_long_personality()
assert next_personality == LongPersonality.aggressive # should cycle to aggressive
assert controller.long_personality == LongPersonality.aggressive
next_personality = controller.cycle_long_personality()
assert next_personality == LongPersonality.relaxed # should cycle to relaxed
next_personality = controller.cycle_long_personality()
assert next_personality == LongPersonality.standard # should cycle back to standard
def test_toggle_functions(self, controller, mock_params):
"""Test toggle functionality"""
# Set initial state to False
mock_params.bool_data['VibePersonalityEnabled'] = False
result = controller.toggle_personality()
assert result # Should toggle to True
assert mock_params.bool_data['VibePersonalityEnabled']
# Set initial state to True
mock_params.bool_data['VibeAccelPersonalityEnabled'] = True
result = controller.toggle_accel_personality()
assert not result # Should toggle to False
assert not mock_params.bool_data['VibeAccelPersonalityEnabled']
def test_enable_checks(self, controller, mock_params):
"""Test various enable state checks"""
# All enabled
mock_params.bool_data = {
'VibePersonalityEnabled': True,
'VibeAccelPersonalityEnabled': True,
'VibeFollowPersonalityEnabled': True
}
assert controller.is_enabled()
assert controller.is_accel_enabled()
assert controller.is_follow_enabled()
# Main toggle disabled
mock_params.bool_data['VibePersonalityEnabled'] = False
assert not controller.is_enabled()
assert not controller.is_accel_enabled()
assert not controller.is_follow_enabled()
def test_accel_limits_calculation(self, controller, mock_params):
"""Test acceleration limits calculation"""
# Enable all features through mock_params bool_data
mock_params.bool_data = {
'VibePersonalityEnabled': True,
'VibeAccelPersonalityEnabled': True,
'VibeFollowPersonalityEnabled': True
}
# Test with different speeds and personalities
controller.accel_personality = 1 # normal
controller.long_personality = 1 # standard
limits = controller.get_accel_limits(10.0) # 10 m/s
assert limits is not None
min_a, max_a = limits
assert isinstance(min_a, float)
assert isinstance(max_a, float)
assert min_a < 0 # Should be negative (braking)
assert max_a > 0 # Should be positive (acceleration)
# Test with disabled controller
mock_params.bool_data['VibePersonalityEnabled'] = False
limits = controller.get_accel_limits(10.0)
assert limits is None
def test_follow_distance_multiplier(self, controller, mock_params):
"""Test following distance multiplier calculation"""
# Enable controller
mock_params.bool_data['VibePersonalityEnabled'] = True
mock_params.bool_data['VibeFollowPersonalityEnabled'] = True
# Test with different speeds and personalities
controller.long_personality = LongPersonality.relaxed
multiplier = controller.get_follow_distance_multiplier(15.0) # 15 m/s
assert multiplier is not None
assert isinstance(multiplier, float)
assert multiplier > 0
# Test with different personality - aggressive should have shorter distance
controller.long_personality = LongPersonality.aggressive
aggressive_multiplier = controller.get_follow_distance_multiplier(15.0)
assert aggressive_multiplier is not None
assert aggressive_multiplier < multiplier # Aggressive should have shorter distance
# Test with disabled controller
mock_params.bool_data['VibeFollowPersonalityEnabled'] = False
multiplier = controller.get_follow_distance_multiplier(15.0)
assert multiplier is None
def test_personality_differences(self, controller, mock_params):
"""Test that different personalities actually produce different values"""
# Enable controller
mock_params.bool_data['VibePersonalityEnabled'] = True
mock_params.bool_data['VibeAccelPersonalityEnabled'] = True
mock_params.bool_data['VibeFollowPersonalityEnabled'] = True
# Test acceleration differences - sport should have higher max acceleration than eco
controller.accel_personality = AccelPersonality.eco
eco_limits = controller.get_accel_limits(20.0)
controller.accel_personality = AccelPersonality.sport
sport_limits = controller.get_accel_limits(20.0)
assert sport_limits[1] > eco_limits[1] # Sport should have higher max acceleration
# Test following distance differences - relaxed should have longer distance than aggressive
controller.long_personality = LongPersonality.relaxed
relaxed_dist = controller.get_follow_distance_multiplier(20.0)
controller.long_personality = LongPersonality.aggressive
aggressive_dist = controller.get_follow_distance_multiplier(20.0)
assert relaxed_dist > aggressive_dist # Relaxed should have longer following distance
def test_reset(self, controller):
"""Test reset functionality"""
# Change some values
controller.accel_personality = AccelPersonality.sport
controller.long_personality = LongPersonality.relaxed
controller.frame = 100
# Reset
controller.reset()
# Check defaults are restored
assert controller.accel_personality == AccelPersonality.normal
assert controller.long_personality == LongPersonality.standard
assert controller.frame == 0
def test_edge_cases(self, controller, mock_params):
"""Test edge cases and error handling"""
# Enable all features
mock_params.bool_data = {
'VibePersonalityEnabled': True,
'VibeAccelPersonalityEnabled': True,
'VibeFollowPersonalityEnabled': True
}
# Test with zero speed
limits = controller.get_accel_limits(0.0)
assert limits is not None
multiplier = controller.get_follow_distance_multiplier(0.0)
assert multiplier is not None
# Test with very high speed
limits = controller.get_accel_limits(100.0)
assert limits is not None
multiplier = controller.get_follow_distance_multiplier(100.0)
assert multiplier is not None
# Test interpolation works correctly
low_speed_limits = controller.get_accel_limits(5.0)
high_speed_limits = controller.get_accel_limits(50.0)
assert low_speed_limits[1] > high_speed_limits[1] # Max accel should decrease with speed
@@ -0,0 +1,81 @@
"""
Copyright (c) 2021-, rav4kumar, 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 log, custom
from openpilot.common.params import Params
from openpilot.sunnypilot.selfdrive.controls.lib.vibe_personality.accel_personality import AccelPersonalityController
from openpilot.sunnypilot.selfdrive.controls.lib.vibe_personality.follow_personality import FollowPersonalityController
LongPersonality = log.LongitudinalPersonality
AccelPersonality = custom.LongitudinalPlanSP.AccelerationPersonality
class VibePersonalityController:
def __init__(self):
self.accel = AccelPersonalityController()
self.follow = FollowPersonalityController()
self.params = Params()
@property
def frame(self):
return self.accel.frame
@frame.setter
def frame(self, value):
self.accel.frame = self.follow.frame = value
@property
def accel_personality(self):
return self.accel.accel_personality
@property
def long_personality(self):
return self.accel.long_personality
@long_personality.setter
def long_personality(self, value):
self.accel.long_personality = self.follow.long_personality = value
def set_accel_personality(self, personality: int) -> bool:
return self.accel.set_accel_personality(personality)
def cycle_accel_personality(self) -> int:
return self.accel.cycle_accel_personality()
def set_long_personality(self, personality: int) -> bool:
if personality not in [LongPersonality.relaxed, LongPersonality.standard, LongPersonality.aggressive]:
return False
self.long_personality = personality
self.params.put('LongitudinalPersonality', str(personality))
return True
def cycle_long_personality(self) -> int:
personalities = [LongPersonality.relaxed, LongPersonality.standard, LongPersonality.aggressive]
idx = personalities.index(self.long_personality)
next_p = personalities[(idx + 1) % len(personalities)]
self.set_long_personality(next_p)
return int(next_p)
def is_enabled(self) -> bool:
main_enabled = self.params.get_bool('VibePersonalityEnabled') or False
return main_enabled and (self.accel.is_enabled() or self.follow.is_enabled())
def get_accel_limits(self, v_ego: float) -> tuple[float, float] | None:
main_enabled = self.params.get_bool('VibePersonalityEnabled') or False
if not (main_enabled and self.accel.is_enabled()):
return None
return self.accel.get_accel_limits(v_ego)
def get_follow_distance_multiplier(self, v_ego: float) -> float | None:
main_enabled = self.params.get_bool('VibePersonalityEnabled') or False
if not (main_enabled and self.follow.is_enabled()):
return None
return self.follow.get_follow_distance_multiplier(v_ego)
def update(self):
self.accel.update()
self.follow.update()
+20 -14
View File
@@ -4,13 +4,13 @@ from openpilot.common.constants import CV
from openpilot.sunnypilot.selfdrive.selfdrived.events_base import EventsBase, Priority, ET, Alert, \
NoEntryAlert, ImmediateDisableAlert, EngagementAlert, NormalPermanentAlert, AlertCallbackType, wrong_car_mode_alert
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED, CONFIRM_SPEED_THRESHOLD
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import compare_cluster_target
AlertSize = log.SelfdriveState.AlertSize
AlertStatus = log.SelfdriveState.AlertStatus
VisualAlert = car.CarControl.HUDControl.VisualAlert
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert
EventNameSP = custom.OnroadEventSP.EventName
@@ -33,9 +33,6 @@ def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messag
speed_conv = CV.MS_TO_KPH if metric else CV.MS_TO_MPH
speed_limit_final_last = sm['longitudinalPlanSP'].speedLimit.resolver.speedLimitFinalLast
speed_limit_final_last_conv = round(speed_limit_final_last * speed_conv)
alert_1_str = ""
alert_2_str = ""
alert_size = AlertSize.none
if CP.openpilotLongitudinalControl and CP.pcmCruise:
# PCM long
@@ -43,16 +40,25 @@ def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messag
pcm_long_required_max = cst_low if speed_limit_final_last_conv < CONFIRM_SPEED_THRESHOLD[metric] else cst_high
pcm_long_required_max_set_speed_conv = round(pcm_long_required_max * speed_conv)
speed_unit = "km/h" if metric else "mph"
alert_1_str = "Speed Limit Assist: Activation Required"
alert_2_str = f"Manually change set speed to {pcm_long_required_max_set_speed_conv} {speed_unit} to activate"
alert_size = AlertSize.mid
else:
# Non PCM long
v_cruise_cluster = CS.vCruiseCluster * CV.KPH_TO_MS
req_plus, req_minus = compare_cluster_target(v_cruise_cluster, speed_limit_final_last, metric)
arrow_str = ""
if req_plus:
arrow_str = "RES/+"
elif req_minus:
arrow_str = "SET/-"
alert_2_str = f"Operate the {arrow_str} cruise control button to activate"
return Alert(
alert_1_str,
"Speed Limit Assist: Activation Required",
alert_2_str,
AlertStatus.normal, alert_size,
Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleLow, .1)
AlertStatus.normal, AlertSize.mid,
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1)
class EventsSP(EventsBase):
@@ -196,7 +202,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = {
"Automatically adjusting to the posted speed limit",
"",
AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.),
Priority.LOW, VisualAlert.none, AudibleAlert.none, 5.),
},
EventNameSP.speedLimitChanged: {
@@ -204,7 +210,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = {
"Set speed changed",
"",
AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.),
Priority.LOW, VisualAlert.none, AudibleAlert.none, 5.),
},
EventNameSP.speedLimitPreActive: {
@@ -216,7 +222,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = {
"Automatically adjusting to the last speed limit",
"",
AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.),
Priority.LOW, VisualAlert.none, AudibleAlert.none, 5.),
},
EventNameSP.e2eChime: {
@@ -224,6 +230,6 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = {
"",
"",
AlertStatus.normal, AlertSize.none,
Priority.MID, VisualAlert.none, AudibleAlert.prompt, 3.),
Priority.MID, VisualAlert.none, AudibleAlert.prompt, 0.1),
},
}