Compare commits

...

26 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
Jason Wen 7e03277962 ui: bigger cluster set speed fonts when ICBM is active (#1369) 2025-10-13 03:06:39 -04:00
Jason Wen bd9bb74d03 custom cereal: fix formatting (#1367) 2025-10-13 01:40:22 -04:00
Kirito3481 7d54b58b8d ui: Update Korean translations (#1359)
Update ko-kr translations

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-13 00:19:24 -04:00
Jason Wen 68d059fd5d ui: ensure Cruise panel widget is reset when hideEvent (#1366) 2025-10-12 23:48:58 -04:00
Nayan 728108f97f ui: Optimize Param Read for Onroad UI (#1365)
* Move params to uiscene

* more

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-12 23:36:58 -04:00
Kumar cb6fa622ee Visuals: Move custom chevron info to sunnypilot/qt (#1066)
* refactor: move to sp ui

* pr suggestion

* no need to check pcm just oplong

* no color, big front, long check

* Fix Rainbow Mode & Y Positioning

* Move param to uiscene

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: nayan <nayan8teen@gmail.com>
2025-10-12 23:11:23 -04:00
Nayan 5b29fd0f2c UI: Onroad Screen Timeout Fixes (#1364)
* Screen Timeout Fixes

* rename

* auto type

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-12 12:12:52 -04:00
Nayan 6f42bbab18 Reapply "UI: Make Always Offroad more accessible" (#1327) (#1361)
* UI: Make Always Offroad more accessible

* conditional - based on offroad

* no need to delete

* account for always offroad

* fix offroad transition

* do this inside updateState on every invoke

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-12 01:59:03 -04:00
Nayan b89393a5a2 UI: Blinker Size & State Fix (#1363)
Blinker Size & State Fix

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-12 00:34:15 -04:00
Nayan 1e5758e712 ui: Better E2E Alert UI Positioning (#1355)
Better E2E Alert UI Positioning
2025-10-12 00:21:38 -04:00
James Vecellio-Grant 974a7a3f7d ui: improve toggle states in Developer panel (#1352)
ui: quickboot toggle race condition

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-11 02:47:45 -04:00
Jason Wen fe6edda23a Revert "UI: Make Always Offroad more accessible (#1327)" (#1358)
Reverts #1327
2025-10-11 01:52:33 -04:00
Jason Wen 17e25f78b4 ui: dynamic ICBM status (#1357)
* ui: dynamic ICBM status

* straight up pls
2025-10-10 17:00:44 -04:00
Nayan 9b92cdd2cc ui: fix standstill timer reset when offroad (#1351)
Standstill timer fix
2025-10-09 23:57:09 -04:00
40 changed files with 1059 additions and 364 deletions
+11 -5
View File
@@ -150,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;
@@ -252,6 +253,11 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
greenLightAlert @0 :Bool;
leadDepartAlert @1 :Bool;
}
enum AccelerationPersonality {
sport @0;
normal @1;
eco @2;
}
}
struct OnroadEventSP @0xda96579883444c35 {
@@ -406,11 +412,11 @@ 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 {
}
+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"}},
@@ -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"}},
@@ -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:
+19 -1
View File
@@ -92,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);
@@ -120,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);
}
}
@@ -140,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) {
@@ -186,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();
};
-167
View File
@@ -34,7 +34,6 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
}
}
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
painter.restore();
}
@@ -175,172 +174,6 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float
}
void ModelRenderer::drawLeadStatus(QPainter &painter, int height, int width) {
auto *s = uiState();
auto &sm = *(s->sm);
if (!sm.alive("radarState")) return;
const auto &radar_state = sm["radarState"].getRadarState();
const auto &lead_one = radar_state.getLeadOne();
const auto &lead_two = radar_state.getLeadTwo();
// Check if we have any active leads
bool has_lead_one = lead_one.getStatus();
bool has_lead_two = lead_two.getStatus();
if (!has_lead_one && !has_lead_two) {
// Fade out status display
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
if (lead_status_alpha <= 0.0f) return;
} else {
// Fade in status display
lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
}
// Draw status for each lead vehicle under its chevron
if (true) {
drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1");
}
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2");
}
}
void ModelRenderer::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();
auto &sm = *(s->sm);
float v_ego = sm["carState"].getCarState().getVEgo();
int chevron_data = std::atoi(Params().get("ChevronInfo").c_str());
// Calculate chevron size (same logic as drawLead)
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
QFont content_font = painter.font();
content_font.setPixelSize(35);
content_font.setBold(true);
painter.setFont(content_font);
QFontMetrics fm(content_font);
bool is_metric = s->scene.is_metric;
QStringList text_lines;
const int chevron_types = 3;
const int chevron_all = chevron_types + 1; // All metrics (value 4)
QStringList chevron_text[chevron_types];
int position;
float val;
// Distance display (chevron_data == 1 or all)
if (chevron_data == 1 || chevron_data == chevron_all) {
position = 0;
val = std::max(0.0f, d_rel);
QString distance_unit = is_metric ? "m" : "ft";
if (!is_metric) {
val *= 3.28084f; // Convert meters to feet
}
chevron_text[position].append(QString::number(val, 'f', 0) + " " + distance_unit);
}
// Absolute velocity display (chevron_data == 2 or all)
if (chevron_data == 2 || chevron_data == chevron_all) {
position = (chevron_data == 2) ? 0 : 1;
val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast<float>(MS_TO_KPH) : static_cast<float>(MS_TO_MPH)));
chevron_text[position].append(QString::number(val, 'f', 0) + " " + (is_metric ? "km/h" : "mph"));
}
// Time-to-contact display (chevron_data == 3 or all)
if (chevron_data == 3 || chevron_data == chevron_all) {
position = (chevron_data == 3) ? 0 : 2;
val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
chevron_text[position].append(ttc_str);
}
// Collect all non-empty text lines
for (int i = 0; i < chevron_types; ++i) {
if (!chevron_text[i].isEmpty()) {
text_lines.append(chevron_text[i]);
}
}
// If no text to display, return early
if (text_lines.isEmpty()) {
return;
}
// Text box dimensions
float str_w = 150; // Width of text area
float str_h = 45; // Height per line
// Position text below chevron, centered horizontally
float text_x = chevron_pos.x() - str_w / 2;
float text_y = chevron_pos.y() + sz + 15;
// Clamp to screen bounds
text_x = std::clamp(text_x, 10.0f, (float)width - str_w - 10);
// Shadow offset
QPoint shadow_offset(2, 2);
// Draw each line of text with shadow
for (int i = 0; i < text_lines.size(); ++i) {
if (!text_lines[i].isEmpty()) {
QRect textRect(text_x, text_y + (i * str_h), str_w, str_h);
// Draw shadow
painter.setPen(QColor(0x0, 0x0, 0x0, (int)(200 * lead_status_alpha)));
painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y()),
Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
// Determine text color based on content and danger level
QColor text_color;
// Check if this is a distance line (contains 'm' or 'ft')
if (text_lines[i].contains("m") || text_lines[i].contains("ft")) {
if (d_rel < 20.0f) {
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - danger
} else if (d_rel < 40.0f) {
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
} else {
text_color = QColor(80, 255, 120, (int)(255 * lead_status_alpha)); // Green - safe
}
}
// Enhanced color coding for time-to-contact
else if (text_lines[i].contains("s") && !text_lines[i].contains("---")) {
float ttc_val = text_lines[i].left(text_lines[i].length() - 1).toFloat();
if (ttc_val < 3.0f) {
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - urgent
} else if (ttc_val < 6.0f) {
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
} else {
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White - safe
}
}
else {
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White for other lines
}
// Draw main text
painter.setPen(text_color);
painter.drawText(textRect, Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
}
}
// Reset pen
painter.setPen(Qt::NoPen);
}
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &vd, const QRect &surface_rect) {
const float speedBuff = 10.;
-10
View File
@@ -34,12 +34,6 @@ protected:
bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out);
void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off,
QPolygonF *pvd, int max_idx, bool allow_invert = true);
void drawLeadStatus(QPainter &painter, int height, int width);
void drawLeadStatusAtPosition(QPainter &painter,
const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos,
int height, int width,
const QString &label);
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect);
void update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line);
virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead);
@@ -65,8 +59,4 @@ protected:
Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero();
QRectF clip_region;
float lead_status_alpha = 0.0f;
QPointF lead_status_pos;
QString lead_status_text;
QColor lead_status_color;
};
+10
View File
@@ -92,6 +92,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
// ignore events when device is awakened by resetInteractiveTimeout
ignore = !device()->isAwake();
device()->resetInteractiveTimeout();
#ifdef SUNNYPILOT
auto *s_sp = uiStateSP();
bool onroadScreenControl = s_sp->scene.onroadScreenOffControl;
bool started = s_sp->scene.started;
bool timerExpired = (s_sp->scene.onroadScreenOffTimer == 0);
ignore |= (onroadScreenControl and started and timerExpired);
s_sp->reset_onroad_sleep_timer();
#endif
break;
}
default:
+4
View File
@@ -7,6 +7,10 @@
#include "selfdrive/ui/qt/offroad/onboarding.h"
#include "selfdrive/ui/qt/offroad/settings.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#endif
class MainWindow : public QWidget {
Q_OBJECT
@@ -56,14 +56,13 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare
addItem(errorLogBtn);
QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles);
is_release = params.getBool("IsReleaseBranch");
is_tested = params.getBool("IsTestedBranch");
is_development = params.getBool("IsDevelopmentBranch");
}
void DeveloperPanelSP::updateToggles(bool offroad) {
bool disable_updates = params.getBool("DisableUpdates");
bool is_release = params.getBool("IsReleaseBranch");
bool is_tested = params.getBool("IsTestedBranch");
bool is_development = params.getBool("IsDevelopmentBranch");
prebuiltToggle->setVisible(!is_release && !is_tested && !is_development);
prebuiltToggle->setEnabled(disable_updates);
@@ -22,9 +22,6 @@ private:
ParamControlSP *prebuiltToggle;
Params params;
ParamControlSP *showAdvancedControls;
bool is_development;
bool is_release;
bool is_tested;
private slots:
void updateToggles(bool offroad);
@@ -88,7 +88,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
connect(toggleDeviceBootMode, &ButtonParamControlSP::buttonClicked, this, [=](int index) {
params.put("DeviceBootMode", QString::number(index).toStdString());
updateState();
updateState(offroad);
});
addItem(device_grid_layout);
@@ -122,7 +122,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
addItem(power_group_layout);
std::vector always_enabled_btns = {
always_enabled_btns = {
rebootBtn,
poweroffBtn,
offroadBtn,
@@ -130,17 +130,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
buttons["onroadUploadsBtn"],
};
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool _offroad) {
for (auto btn : findChildren<PushButtonSP*>()) {
bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end();
if (!always_enabled) {
btn->setEnabled(offroad);
}
}
offroad = _offroad;
updateState();
});
QObject::connect(uiState(), &UIState::offroadTransition, this, &DevicePanelSP::updateState);
}
void DevicePanelSP::setOffroadMode() {
@@ -164,7 +154,7 @@ void DevicePanelSP::setOffroadMode() {
ConfirmationDialog::alert(tr("Disengage to Enter Always Offroad Mode"), this);
}
updateState();
updateState(offroad);
}
void DevicePanelSP::resetSettings() {
@@ -181,12 +171,16 @@ void DevicePanelSP::resetSettings() {
}
void DevicePanelSP::showEvent(QShowEvent *event) {
updateState();
updateState(offroad);
}
void DevicePanelSP::updateState() {
if (!isVisible()) {
return;
void DevicePanelSP::updateState(bool _offroad) {
for (auto btn : findChildren<PushButtonSP*>()) {
bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end();
if (!always_enabled) {
btn->setEnabled(_offroad);
}
}
bool offroad_mode_param = params.getBool("OffroadMode");
@@ -204,4 +198,6 @@ void DevicePanelSP::updateState() {
} else {
AddWidgetAt(0, offroadBtn);
}
offroad = _offroad;
}
@@ -23,7 +23,7 @@ public:
explicit DevicePanelSP(SettingsWindowSP *parent = 0);
void showEvent(QShowEvent *event) override;
void setOffroadMode();
void updateState();
void updateState(bool _offroad);
void resetSettings();
private:
@@ -34,6 +34,8 @@ private:
QVBoxLayout *power_group_layout;
bool offroad;
std::vector<PushButtonSP*> always_enabled_btns = {};
const QString alwaysOffroadStyle = R"(
PushButtonSP {
border-radius: 20px;
@@ -158,3 +158,7 @@ void SpeedLimitSettings::refresh() {
void SpeedLimitSettings::showEvent(QShowEvent *event) {
refresh();
}
void SpeedLimitSettings::hideEvent(QHideEvent *event) {
setCurrentWidget(subPanelFrame);
}
@@ -21,6 +21,7 @@ public:
SpeedLimitSettings(QWidget *parent = nullptr);
void refresh();
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
signals:
void backPress();
@@ -75,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);
@@ -86,6 +115,10 @@ void LongitudinalPanel::showEvent(QShowEvent *event) {
refresh(offroad);
}
void LongitudinalPanel::hideEvent(QHideEvent *event) {
main_layout->setCurrentWidget(cruisePanelScreen);
}
void LongitudinalPanel::refresh(bool _offroad) {
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
@@ -131,6 +164,14 @@ void LongitudinalPanel::refresh(bool _offroad) {
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);
@@ -142,6 +183,13 @@ void LongitudinalPanel::refresh(bool _offroad) {
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;
}
@@ -18,6 +18,7 @@ class LongitudinalPanel : public QWidget {
public:
explicit LongitudinalPanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void refresh(bool _offroad);
private:
@@ -36,4 +37,8 @@ private:
ParamControl *intelligentCruiseButtonManagement = nullptr;
SpeedLimitSettings *speedLimitScreen;
PushButtonSP *speedLimitSettings;
ParamControlSP *vibePersonalityControl;
ParamControlSP *vibeAccelPersonalityControl;
ParamControlSP *vibeFollowPersonalityControl;
};
@@ -21,7 +21,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
paramsRefresh(param_name, param_value);
});
is_sunnylink_enabled = Params().getBool("SunnylinkEnabled");
is_sunnylink_enabled = params.getBool("SunnylinkEnabled");
connect(uiStateSP(), &UIStateSP::sunnylinkRolesChanged, this, &SunnylinkPanel::updatePanel);
connect(uiStateSP(), &UIStateSP::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updatePanel);
connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) {
@@ -272,7 +272,7 @@ void SunnylinkPanel::updatePanel() {
const auto sunnylinkDongleId = getSunnylinkDongleId().value_or(tr("N/A"));
sunnylinkEnabledBtn->setEnabled(!is_onroad);
is_sunnylink_enabled = Params().getBool("SunnylinkEnabled");
is_sunnylink_enabled = params.getBool("SunnylinkEnabled");
bool is_sub = uiStateSP()->isSunnylinkSponsor() && is_sunnylink_enabled;
auto max_current_sponsor_rule = uiStateSP()->sunnylinkSponsorRole();
auto role_name = max_current_sponsor_rule.getSponsorTierString();
@@ -140,6 +140,40 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
vlayout->addWidget(sunnypilotScroller);
main_layout->addWidget(sunnypilotScreen);
QObject::connect(uiState(), &UIState::offroadTransition, this, &VisualsPanel::refreshLongitudinalStatus);
refreshLongitudinalStatus();
}
void VisualsPanel::refreshLongitudinalStatus() {
auto cp_bytes = params.get("CarParamsPersistent");
if (!cp_bytes.empty()) {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
has_longitudinal_control = hasLongitudinalControl(CP);
} else {
has_longitudinal_control = false;
}
if (chevron_info_settings) {
QString chevronEnabledDescription = tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control).");
QString chevronNoLongDescription = tr("This feature requires openpilot longitudinal control to be available.");
if (has_longitudinal_control) {
chevron_info_settings->setDescription(chevronEnabledDescription);
} else {
// Reset to "Off" when longitudinal not available
params.put("ChevronInfo", "0");
chevron_info_settings->setDescription(chevronNoLongDescription);
}
// Enable only when longitudinal is available
chevron_info_settings->setEnabled(has_longitudinal_control);
chevron_info_settings->refresh();
}
}
void VisualsPanel::paramsRefresh() {
@@ -19,6 +19,7 @@ public:
explicit VisualsPanel(QWidget *parent = nullptr);
void paramsRefresh();
void refreshLongitudinalStatus();
protected:
QStackedLayout* main_layout = nullptr;
@@ -29,4 +30,6 @@ protected:
ParamWatcher * param_watcher;
ButtonParamControlSP *chevron_info_settings;
ButtonParamControlSP *dev_ui_settings;
bool has_longitudinal_control = false;
};
@@ -18,4 +18,10 @@ void AnnotatedCameraWidgetSP::updateState(const UIState &s) {
void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) {
AnnotatedCameraWidget::showEvent(event);
ui_update_params_sp(uiState());
uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::RESUME);
}
void AnnotatedCameraWidgetSP::hideEvent(QHideEvent *event) {
AnnotatedCameraWidget::hideEvent(event);
uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::PAUSE);
}
@@ -18,4 +18,5 @@ public:
protected:
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent* event) override;
};
+40 -9
View File
@@ -107,6 +107,7 @@ void HudRendererSP::updateState(const UIState &s) {
standstillTimer = s.scene.standstill_timer;
isStandstill = car_state.getStandstill();
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();
@@ -126,6 +127,9 @@ void HudRendererSP::updateState(const UIState &s) {
leftBlindspot = car_state.getLeftBlindspot();
rightBlindspot = car_state.getRightBlindspot();
showTurnSignals = s.scene.turn_signals;
carControlEnabled = car_control.getEnabled();
speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv;
}
void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
@@ -250,6 +254,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
// No Alerts displayed
else {
e2eAlertFrame = 0;
if (not isStandstill) standstillElapsedTime = 0.0;
}
// Blinker
@@ -683,10 +688,23 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
}
}
// Draw "MAX" text
p.setFont(InterFont(40, QFont::DemiBold));
// Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text
if (carControlEnabled) {
if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) {
icbm_active_counter = 3 * UI_FREQ;
} else if (icbm_active_counter > 0) {
icbm_active_counter--;
}
} else {
icbm_active_counter = 0;
}
int max_str_size = (icbm_active_counter != 0) ? 60 : 40;
int max_str_y = (icbm_active_counter != 0) ? 15 : 27;
QString max_str = (icbm_active_counter != 0) ? QString::number(std::nearbyint(speedCluster)) : tr("MAX");
p.setFont(InterFont(max_str_size, QFont::DemiBold));
p.setPen(max_color);
p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX"));
p.drawText(set_speed_rect.adjusted(0, max_str_y, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str);
// Draw set speed
QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "–";
@@ -698,9 +716,8 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) {
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() + 40;
int y = surface_rect.center().y() + 20;
x += devUiInfo > 0 ? 0 : 50;
y += devUiInfo > 0 ? 0 : 80;
QRect alertRect(x - size, y - size, size * 2, size * 2);
// Alert Circle
@@ -761,19 +778,33 @@ 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 or rightBlinkerOn) ? 1 : 0;
if (!leftBlinkerOn && !rightBlinkerOn) {
blinkerFrameCounter = 0;
lastBlinkerStatus = 0;
return;
}
if (blinkerStatus != lastBlinkerStatus) {
blinkerFrameCounter = 0;
lastBlinkerStatus = blinkerStatus;
}
++blinkerFrameCounter;
const int circleRadius = 44;
const int arrowLength = 44;
const int x_gap = 180;
const int BLINKER_COOLDOWN_FRAMES = UI_FREQ / 10;
if (blinkerFrameCounter < BLINKER_COOLDOWN_FRAMES) {
return;
}
const int circleRadius = 60;
const int arrowLength = 60;
const int x_gap = 160;
const int y_offset = 272;
const int centerX = surface_rect.center().x();
const bool hazard = leftBlinkerOn && rightBlinkerOn;
const QPen bgBorder(Qt::white, 5);
const QPen arrowPen(Qt::NoPen);
+5
View File
@@ -114,5 +114,10 @@ private:
bool leftBlindspot;
bool rightBlindspot;
int blinkerFrameCounter;
int lastBlinkerStatus;
bool showTurnSignals;
bool carControlEnabled;
float speedCluster = 0;
int icbm_active_counter = 0;
};
+137 -2
View File
@@ -24,7 +24,7 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con
void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {
auto *s = uiState();
auto &sm = *(s->sm);
bool blindspot = Params().getBool("BlindSpot");
bool blindspot = s->scene.blindspot_ui;
if (blindspot) {
bool left_blindspot = sm["carState"].getCarState().getLeftBlindspot();
@@ -49,7 +49,7 @@ void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Rea
}
}
bool rainbow = Params().getBool("RainbowMode");
bool rainbow = s->scene.rainbow_mode;
//float v_ego = sm["carState"].getCarState().getVEgo();
if (rainbow) {
@@ -87,4 +87,139 @@ void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Rea
// Normal path rendering
ModelRenderer::drawPath(painter, model, surface_rect.height());
}
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
}
void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) {
auto *s = uiState();
auto &sm = *(s->sm);
bool longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
if (!longitudinal_control) {
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
return;
}
if (!sm.alive("radarState")) {
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
return;
}
const auto &radar_state = sm["radarState"].getRadarState();
const auto &lead_one = radar_state.getLeadOne();
const auto &lead_two = radar_state.getLeadTwo();
bool has_lead_one = lead_one.getStatus();
bool has_lead_two = lead_two.getStatus();
if (!has_lead_one && !has_lead_two) {
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
if (lead_status_alpha <= 0.0f) return;
} else {
lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
}
if (has_lead_one) {
drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1");
}
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2");
}
}
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();
auto &sm = *(s->sm);
float v_ego = sm["carState"].getCarState().getVEgo();
int chevron_data = s->scene.chevron_info;
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
QFont content_font = painter.font();
content_font.setPixelSize(50);
content_font.setBold(true);
painter.setFont(content_font);
bool is_metric = s->scene.is_metric;
QStringList text_lines;
const int chevron_all = 4;
QStringList chevron_text[3];
// Distance display
if (chevron_data == 1 || chevron_data == chevron_all) {
int pos = 0;
float val = std::max(0.0f, d_rel);
QString unit = is_metric ? "m" : "ft";
if (!is_metric) val *= 3.28084f;
chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit);
}
// Speed display
if (chevron_data == 2 || chevron_data == chevron_all) {
int pos = (chevron_data == 2) ? 0 : 1;
float multiplier = is_metric ? static_cast<float>(MS_TO_KPH) : static_cast<float>(MS_TO_MPH);
float val = std::max(0.0f, (v_rel + v_ego) * multiplier);
QString unit = is_metric ? "km/h" : "mph";
chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit);
}
// Time to contact
if (chevron_data == 3 || chevron_data == chevron_all) {
int pos = (chevron_data == 3) ? 0 : 2;
float val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
QString ttc = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
chevron_text[pos].append(ttc);
}
for (int i = 0; i < 3; ++i) {
if (!chevron_text[i].isEmpty()) text_lines.append(chevron_text[i]);
}
if (text_lines.isEmpty()) return;
QFontMetrics fm(content_font);
float text_width = 120.0f;
for (const QString &line : text_lines) {
text_width = std::max(text_width, fm.horizontalAdvance(line) + 20.0f);
}
text_width = std::min(text_width, 250.0f);
float line_height = 50.0f;
float total_height = text_lines.size() * line_height;
float margin = 20.0f;
float text_y = chevron_pos.y() + sz + 15;
if (text_y + total_height > height - margin) {
float y_max = chevron_pos.y() > (height - margin) ? (height - margin) : chevron_pos.y();
text_y = y_max - 15 - total_height;
text_y = std::max(margin, text_y);
}
float text_x = chevron_pos.x() - text_width / 2;
text_x = std::clamp(text_x, margin, (float)width - text_width - margin);
QPoint shadow_offset(2, 2);
QColor text_color = QColor(255, 255, 255, (int)(255 * lead_status_alpha));
for (int i = 0; i < text_lines.size(); ++i) {
float y = text_y + (i * line_height);
if (y + line_height > height - margin) break;
QRect rect(text_x, y, text_width, line_height);
// Draw shadow
painter.setPen(QColor(0, 0, 0, (int)(200 * lead_status_alpha)));
painter.drawText(rect.translated(shadow_offset), Qt::AlignCenter, text_lines[i]);
painter.setPen(text_color);
painter.drawText(rect, Qt::AlignCenter, text_lines[i]);
}
painter.setPen(Qt::NoPen);
}
+11
View File
@@ -17,6 +17,17 @@ 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 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;
// Lead status animation
float lead_status_alpha = 0.0f;
};
@@ -25,10 +25,8 @@ void OnroadWindowSP::updateState(const UIStateSP &s) {
void OnroadWindowSP::mousePressEvent(QMouseEvent *e) {
OnroadWindow::mousePressEvent(e);
uiStateSP()->reset_onroad_sleep_timer();
}
void OnroadWindowSP::offroadTransition(bool offroad) {
OnroadWindow::offroadTransition(offroad);
uiStateSP()->reset_onroad_sleep_timer();
}
+10 -5
View File
@@ -71,17 +71,22 @@ void ui_update_params_sp(UIStateSP *s) {
s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str());
s->scene.onroadScreenOffControl = params.getBool("OnroadScreenOffControl");
s->scene.onroadScreenOffTimerParam = std::atoi(params.get("OnroadScreenOffTimer").c_str());
s->reset_onroad_sleep_timer();
s->scene.turn_signals = params.getBool("ShowTurnSignals");
s->scene.chevron_info = std::atoi(params.get("ChevronInfo").c_str());
s->scene.blindspot_ui = params.getBool("BlindSpot");
s->scene.rainbow_mode = params.getBool("RainbowMode");
}
void UIStateSP::reset_onroad_sleep_timer() {
if (scene.onroadScreenOffTimerParam >= 0 and scene.onroadScreenOffControl) {
scene.onroadScreenOffTimer = scene.onroadScreenOffTimerParam * UI_FREQ;
} else {
void UIStateSP::reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus) {
// Toggling from active state to inactive
if (toggleTimerStatus == OnroadTimerStatusToggle::PAUSE and scene.onroadScreenOffTimer != -1) {
scene.onroadScreenOffTimer = -1;
}
// Toggling from a previously inactive state or resetting an active timer
else if ((scene.onroadScreenOffTimerParam >= 0 and scene.onroadScreenOffControl and scene.onroadScreenOffTimer != -1) or toggleTimerStatus == OnroadTimerStatusToggle::RESUME) {
scene.onroadScreenOffTimer = scene.onroadScreenOffTimerParam * UI_FREQ;
}
}
DeviceSP::DeviceSP(QObject *parent) : Device(parent) {
+7 -1
View File
@@ -15,6 +15,12 @@
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
enum OnroadTimerStatusToggle {
NONE,
PAUSE,
RESUME
};
class UIStateSP : public UIState {
Q_OBJECT
@@ -61,7 +67,7 @@ public:
return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor";
});
}
void reset_onroad_sleep_timer();
void reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus = OnroadTimerStatusToggle::NONE);
signals:
void sunnylinkRoleChanged(bool subscriber);
+3
View File
@@ -18,4 +18,7 @@ typedef struct UISceneSP : UIScene {
bool trueVEgoUI;
bool hideVEgoUI;
bool turn_signals = false;
int chevron_info;
bool blindspot_ui;
bool rainbow_mode;
} UISceneSP;
+135 -130
View File
@@ -144,11 +144,11 @@ Please use caution when using this feature. Only use the blinker when traffic an
</message>
<message>
<source>Global Brightness</source>
<translation type="unfinished"></translation>
<translation>전역 밝기</translation>
</message>
<message>
<source>Overrides the brightness of the device. This applies to both onroad and offroad screens. </source>
<translation type="unfinished"></translation>
<translation>기기의 밝기를 강제로 설정합니다. 이 설정은 주행 중 화면과 비주행 화면(설정 등) 모두에 적용됩니다. </translation>
</message>
</context>
<context>
@@ -256,11 +256,11 @@ This only toggles the visibility of the controls; it does not toggle the actual
</message>
<message>
<source>Enable Copyparty service</source>
<translation type="unfinished"></translation>
<translation>Copyparty 서비스 사용</translation>
</message>
<message>
<source>Copyparty is a very capable file server, you can use it to download your routes, view your logs and even make some edits on some files from your browser. Requires you to connect to your comma locally via it&apos;s IP.</source>
<translation type="unfinished"></translation>
<translation>Copyparty는 매우 유능한 파일 서버로, 이를 통해 주행 기록을 다운로드하고, 로그 파일을 확인하며, 심지어 브라우저에서 일부 파일을 편집할 수도 있습니다. 이 기능을 사용하려면 comma 기기의 로컬 IP를 통해 접속해야 합니다.</translation>
</message>
</context>
<context>
@@ -538,32 +538,32 @@ Steering lag calibration is complete.</source>
</message>
<message>
<source>Onroad Uploads</source>
<translation type="unfinished"></translation>
<translation>주행 중 업로드</translation>
</message>
<message>
<source>Enable Always Offroad</source>
<translation type="unfinished"></translation>
<translation>항상 오프로드 사용</translation>
</message>
</context>
<context>
<name>DisplayPanel</name>
<message>
<source>Onroad Screen: Reduced Brightness</source>
<translation type="unfinished"></translation>
<translation>주행 중 화면: 밝기 감소</translation>
</message>
<message>
<source>Turn off device screen or reduce brightness after driving starts. It automatically brightens again when screen is touched or a visible alert is displayed.</source>
<translation type="unfinished"></translation>
<translation>운전이 시작된 후 기기 화면을 끄거나 밝기를 줄입니다. 화면을 터치하거나 시각적 알림이 표시되면 자동으로 다시 밝아집니다.</translation>
</message>
<message>
<source>Interactivity Timeout</source>
<translation type="unfinished">상호작용 타임아웃</translation>
<translation>상호작용 타임아웃</translation>
</message>
<message>
<source>Apply a custom timeout for settings UI.
This is the time after which settings UI closes automatically if user is not interacting with the screen.</source>
<translation type="unfinished">설정 화면에 사용자 지정 타임아웃을 적용하세요.
사용자가 화면과 상호작용하지 않으면 설정 화면이 자동으로 닫히는 시간입니다.</translation>
<translation>설정 UI에 사용자 지정 타임아웃을 적용하세요.
이는 사용자가 화면과 상호 작용하지 않으면 설정 UI가 자동으로 닫히는 시간입니다.</translation>
</message>
</context>
<context>
@@ -630,55 +630,55 @@ This is the time after which settings UI closes automatically if user is not int
<name>ExternalStorageControl</name>
<message>
<source>External Storage</source>
<translation type="unfinished"></translation>
<translation>외부 저장소</translation>
</message>
<message>
<source>Extend your comma device&apos;s storage by inserting a USB drive into the aux port.</source>
<translation type="unfinished"></translation>
<translation>USB 드라이브를 AUX 포트에 삽입하여 comma 기기의 저장 공간을 확장합니다.</translation>
</message>
<message>
<source>CHECK</source>
<translation type="unfinished">확인</translation>
<translation>확인</translation>
</message>
<message>
<source>MOUNT</source>
<translation type="unfinished"></translation>
<translation>마운트</translation>
</message>
<message>
<source>UNMOUNT</source>
<translation type="unfinished"></translation>
<translation>마운트 해제</translation>
</message>
<message>
<source>FORMAT</source>
<translation type="unfinished"></translation>
<translation>포맷</translation>
</message>
<message>
<source>Are you sure you want to format this drive? This will erase all data.</source>
<translation type="unfinished"></translation>
<translation>이 드라이브를 포맷하시겠습니까? 이 작업은 모든 데이터를 지웁니다.</translation>
</message>
<message>
<source>Format</source>
<translation type="unfinished"></translation>
<translation>포맷</translation>
</message>
<message>
<source>formatting</source>
<translation type="unfinished"></translation>
<translation>포맷 중</translation>
</message>
<message>
<source>insert drive</source>
<translation type="unfinished"></translation>
<translation>드라이브 삽입</translation>
</message>
<message>
<source>needs format</source>
<translation type="unfinished"></translation>
<translation>포맷 필요</translation>
</message>
<message>
<source>mounting</source>
<translation type="unfinished"></translation>
<translation>마운트 중</translation>
</message>
<message>
<source>unmounting</source>
<translation type="unfinished"></translation>
<translation>마운트 해제 중</translation>
</message>
</context>
<context>
@@ -737,57 +737,58 @@ Firehose 모드를 사용하면 훈련 데이터 업로드를 극대화하여 op
<name>HudRendererSP</name>
<message>
<source>km/h</source>
<translation type="unfinished">km/h</translation>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation type="unfinished">mph</translation>
<translation>mph</translation>
</message>
<message>
<source>GREEN
LIGHT</source>
<translation type="unfinished"></translation>
<translation>녹색 신호</translation>
</message>
<message>
<source>LEAD VEHICLE
DEPARTING</source>
<translation type="unfinished"></translation>
<translation>전방 차량이
출발하였습니다</translation>
</message>
<message>
<source>SPEED</source>
<translation type="unfinished"></translation>
<translation>SPEED</translation>
</message>
<message>
<source>LIMIT</source>
<translation type="unfinished"></translation>
<translation>LIMIT</translation>
</message>
<message>
<source>Near</source>
<translation type="unfinished"></translation>
<translation>근처</translation>
</message>
<message>
<source>km</source>
<translation type="unfinished"></translation>
<translation>km</translation>
</message>
<message>
<source>m</source>
<translation type="unfinished">분</translation>
<translation>m</translation>
</message>
<message>
<source>mi</source>
<translation type="unfinished"></translation>
<translation>mi</translation>
</message>
<message>
<source>ft</source>
<translation type="unfinished"></translation>
<translation>ft</translation>
</message>
<message>
<source>AHEAD</source>
<translation type="unfinished"></translation>
<translation>전방</translation>
</message>
<message>
<source>MAX</source>
<translation type="unfinished">최대</translation>
<translation>최대</translation>
</message>
</context>
<context>
@@ -905,15 +906,15 @@ DEPARTING</source>
</message>
<message>
<source>Enforce Torque Lateral Control</source>
<translation type="unfinished"></translation>
<translation>토크 조향 제어 강제 적용</translation>
</message>
<message>
<source>Enable this to enforce sunnypilot to steer with Torque lateral control.</source>
<translation type="unfinished"></translation>
<translation>sunnypilot이 토크 조향 제어를 통해 조향하도록 강제하려면 이 기능을 활성화하세요.</translation>
</message>
<message>
<source>Customize Params</source>
<translation type="unfinished"></translation>
<translation>매개변수 사용자 지정</translation>
</message>
</context>
<context>
@@ -940,31 +941,31 @@ DEPARTING</source>
</message>
<message>
<source>Intelligent Cruise Button Management (ICBM) (Alpha)</source>
<translation type="unfinished"></translation>
<translation>지능형 크루즈 버튼 관리 (ICBM) (알파)</translation>
</message>
<message>
<source>When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control.</source>
<translation type="unfinished"></translation>
<translation>활성화하면, sunnypilot은 제한적인 가감속 제어를 위해 버튼 조작을 재현하여 내장 크루즈 컨트롤 버튼을 관리하려고 시도합니다.</translation>
</message>
<message>
<source>Smart Cruise Control - Vision</source>
<translation type="unfinished"></translation>
<translation>스마트 크루즈 컨트롤 - 비전</translation>
</message>
<message>
<source>Use vision path predictions to estimate the appropriate speed to drive through turns ahead.</source>
<translation type="unfinished"></translation>
<translation>전방 커브 구간을 통과하기 위한 적절한 속도를 예측하기 위해 시각 경로 예측 기능을 사용합니다.</translation>
</message>
<message>
<source>Smart Cruise Control - Map</source>
<translation type="unfinished"></translation>
<translation>스마트 크루즈 컨트롤 - 지도</translation>
</message>
<message>
<source>Use map data to estimate the appropriate speed to drive through turns ahead.</source>
<translation type="unfinished"></translation>
<translation>전방 커브 구간을 통과하기 위한 적절한 속도를 예측하기 위해 지도 데이터를 사용합니다.</translation>
</message>
<message>
<source>Speed Limit</source>
<translation type="unfinished"></translation>
<translation>속도 제한</translation>
</message>
</context>
<context>
@@ -1195,47 +1196,47 @@ The default software delay value is 0.2</source>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
<translation>모델 목록 새로고침</translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished">새로 고침</translation>
<translation>새로고침</translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
<translation>최신 모델 불러오는 중</translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
<translation>이 기능을 켜면 차량이 스스로 조향 응답 속도를 학습하고 맞춥니다. 끄면 고정된 조향 응답 속도를 사용합니다. 이 기능을 켜두는 것이 기본 openpilot 경험을 제공합니다.</translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
<translation>실시간 조향 지연 시간:</translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
<translation>액츄에이터 지연 시간:</translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
<translation>소프트웨어 지연 시간:</translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
<translation>전체 지연 시간:</translation>
</message>
<message>
<source>Use Lane Turn Desires</source>
<translation type="unfinished"></translation>
<translation>차로 회전 의도 사용</translation>
</message>
<message>
<source>Adjust Lane Turn Speed</source>
<translation type="unfinished"></translation>
<translation>차로 회전 속도 조정</translation>
</message>
<message>
<source>Set the maximum speed for lane turn desires. Default is 19 %1.</source>
<translation type="unfinished"></translation>
<translation>차로 회전 의도의 최대 속도를 설정합니다. 기본값은 19 %1 입니다.</translation>
</message>
</context>
<context>
@@ -1400,7 +1401,7 @@ The default software delay value is 0.2</source>
</message>
<message>
<source>&lt;b&gt;Unsupported branch detected&lt;/b&gt; - The current version of &lt;b&gt;&lt;u&gt;%1&lt;/u&gt;&lt;/b&gt; branch is no longer supported on the comma three. Please go to &lt;b&gt;[Device &gt; Software]&lt;/b&gt; and install a supported branch with &lt;b&gt;&lt;u&gt;-tici&lt;/u&gt;&lt;/b&gt; in the branch name for the comma three.</source>
<translation type="unfinished"></translation>
<translation>&lt;b&gt;지원되지 않는 브랜치 감지&lt;/b&gt; - 현재 사용 중인 &lt;b&gt;&lt;u&gt;%1&lt;/u&gt;&lt;/b&gt; 브랜치는 comma three 기기에서 더 이상 지원되지 않습니다. &lt;b&gt;[기기 &gt; 소프트웨어]&lt;/b&gt;로 이동하여 브랜치 이름에 &lt;b&gt;&lt;u&gt;-tici&lt;/u&gt;&lt;/b&gt; 가 포함된 comma three용 브랜치로 설치하세요.</translation>
</message>
</context>
<context>
@@ -1779,56 +1780,59 @@ Warning: You are on a metered connection!</source>
</message>
<message>
<source>None</source>
<translation type="unfinished"></translation>
<translation>없음</translation>
</message>
<message>
<source>Fixed</source>
<translation type="unfinished"></translation>
<translation>고정</translation>
</message>
<message>
<source>Percent</source>
<translation type="unfinished"></translation>
<translation>비율</translation>
</message>
<message>
<source>Car
Only</source>
<translation type="unfinished"></translation>
<translation>차량만</translation>
</message>
<message>
<source>Map
Only</source>
<translation type="unfinished"></translation>
<translation>지도만</translation>
</message>
<message>
<source>Car
First</source>
<translation type="unfinished"></translation>
<translation>차량
우선</translation>
</message>
<message>
<source>Map
First</source>
<translation type="unfinished"></translation>
<translation>지도
우선</translation>
</message>
<message>
<source>Combined
Data</source>
<translation type="unfinished"></translation>
<translation>결합
데이터</translation>
</message>
<message>
<source>Off</source>
<translation type="unfinished"></translation>
<translation>끄기</translation>
</message>
<message>
<source>Information</source>
<translation type="unfinished"></translation>
<translation>정보</translation>
</message>
<message>
<source>Warning</source>
<translation type="unfinished"></translation>
<translation>경고</translation>
</message>
<message>
<source>Assist</source>
<translation type="unfinished"></translation>
<translation>보조</translation>
</message>
</context>
<context>
@@ -1926,7 +1930,7 @@ Data</source>
</message>
<message>
<source>Display</source>
<translation type="unfinished"></translation>
<translation>화면</translation>
</message>
</context>
<context>
@@ -2168,78 +2172,78 @@ Data</source>
<name>SpeedLimitPolicy</name>
<message>
<source>Back</source>
<translation type="unfinished">뒤로</translation>
<translation>뒤로</translation>
</message>
<message>
<source>Speed Limit Source</source>
<translation type="unfinished"></translation>
<translation>속도 제한 출처</translation>
</message>
<message>
<source>⦿ Car Only: Use Speed Limit data only from Car</source>
<translation type="unfinished"></translation>
<translation>⦿ 차량만: 차량에서 제공되는 속도 제한 데이터만 사용</translation>
</message>
<message>
<source>⦿ Map Only: Use Speed Limit data only from OpenStreetMaps</source>
<translation type="unfinished"></translation>
<translation>⦿ 지도만: OpenStreetMaps에서 제공되는 속도 제한 데이터만 사용</translation>
</message>
<message>
<source>⦿ Car First: Use Speed Limit data from Car if available, else use from OpenStreetMaps</source>
<translation type="unfinished"></translation>
<translation>⦿ 차량 우선: 사용 가능한 경우 차량의 속도 제한 데이터를 사용하고, 그렇지 않은 경우 OpenStreetMaps의 데이터 사용</translation>
</message>
<message>
<source>⦿ Map First: Use Speed Limit data from OpenStreetMaps if available, else use from Car</source>
<translation type="unfinished"></translation>
<translation>⦿ 지도 우선: 사용 가능한 경우 OpenStreetMaps의 속도 제한 데이터를 사용하고, 그렇지 않은 경우 차량의 데이터 사용</translation>
</message>
<message>
<source>⦿ Combined: Use combined Speed Limit data from Car &amp; OpenStreetMaps</source>
<translation type="unfinished"></translation>
<translation>⦿ 결합: 차량 및 OpenStreetMaps의 속도 제한 결합 데이터 사용</translation>
</message>
</context>
<context>
<name>SpeedLimitSettings</name>
<message>
<source>Back</source>
<translation type="unfinished">뒤로</translation>
<translation>뒤로</translation>
</message>
<message>
<source>Speed Limit</source>
<translation type="unfinished"></translation>
<translation>속도 제한</translation>
</message>
<message>
<source>Customize Source</source>
<translation type="unfinished"></translation>
<translation>출처 사용자 지정</translation>
</message>
<message>
<source>Speed Limit Offset</source>
<translation type="unfinished"></translation>
<translation>속도 제한 오프셋</translation>
</message>
<message>
<source>⦿ None: No Offset</source>
<translation type="unfinished"></translation>
<translation>⦿ 없음: 오프셋 없음</translation>
</message>
<message>
<source>⦿ Fixed: Adds a fixed offset [Speed Limit + Offset]</source>
<translation type="unfinished"></translation>
<translation>⦿ 고정: 고정 오프셋을 추가합니다. [제한 속도 + 오프셋]</translation>
</message>
<message>
<source>⦿ Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)]</source>
<translation type="unfinished"></translation>
<translation>⦿ 비율: 백분율 오프셋을 추가합니다. [제한 속도 + (오프셋 % 제한 속도)]</translation>
</message>
<message>
<source>⦿ Off: Disables the Speed Limit functions.</source>
<translation type="unfinished"></translation>
<translation>⦿ 끄기: 속도 제한 기능을 비활성화합니다.</translation>
</message>
<message>
<source>⦿ Information: Displays the current road&apos;s speed limit.</source>
<translation type="unfinished"></translation>
<translation>⦿ 정보: 현재 도로의 제한 속도를 표시합니다.</translation>
</message>
<message>
<source>⦿ Warning: Provides a warning when exceeding the current road&apos;s speed limit.</source>
<translation type="unfinished"></translation>
<translation>⦿ 경고: 현재 도로의 제한 속도를 초과할 경우 경고합니다.</translation>
</message>
<message>
<source>⦿ Assist: Adjusts the vehicle&apos;s cruise speed based on the current road&apos;s speed limit when operating the +/- buttons.</source>
<translation type="unfinished"></translation>
<translation>⦿ 보조: +/- 버튼을 조작할 때 현재 도로의 제한 속도를 기준으로 차량의 크루즈 속도를 조정합니다.</translation>
</message>
</context>
<context>
@@ -2412,27 +2416,27 @@ Data</source>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
<translation>sunnylink 업로더를 활성화하여 sunnypilot이 귀하의 주행 데이터를 sunnypilot 서버로 업로드하도록 허용합니다. (가장 높은 등급에서만 해당하며, 사용자에게는 어떠한 이점도 제공하지 않습니다. 저희는 그냥 데이터 용량을 테스트하는 중입니다.)</translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
<translation>[사용하지 마세요] sunnylink 업로더 사용</translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
<translation>🚀 sunnylink 🚀</translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
<translation>안전한 백업, 복원 및 원격 설정을 위해</translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
<translation>기본적인 백업/복원은 후원이 필요하지 않습니다</translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
<translation>후원 버튼을 눌러 더 자세한 정보를 확인하세요</translation>
</message>
</context>
<context>
@@ -2628,54 +2632,54 @@ Data</source>
<name>TorqueLateralControlCustomParams</name>
<message>
<source>Manual Real-Time Tuning</source>
<translation type="unfinished"></translation>
<translation>실시간 수동 튜닝</translation>
</message>
<message>
<source>Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values.</source>
<translation type="unfinished"></translation>
<translation>토크 조향 제어기가 셀프 튜닝에서 학습된 값 대신 고정된 값을 사용하도록 강제합니다. 이 토글을 활성화하면 셀프 튜닝 값이 무시됩니다.</translation>
</message>
<message>
<source>Lateral Acceleration Factor</source>
<translation type="unfinished"></translation>
<translation>조향 가속 계수</translation>
</message>
<message>
<source>Friction</source>
<translation type="unfinished"></translation>
<translation>마찰</translation>
</message>
<message>
<source>Real-time and Offline</source>
<translation type="unfinished"></translation>
<translation>실시간 및 오프라인</translation>
</message>
<message>
<source>Offline Only</source>
<translation type="unfinished"></translation>
<translation>오프라인만</translation>
</message>
</context>
<context>
<name>TorqueLateralControlSettings</name>
<message>
<source>Self-Tune</source>
<translation type="unfinished"></translation>
<translation>셀프 튜닝</translation>
</message>
<message>
<source>Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default.</source>
<translation type="unfinished"></translation>
<translation>기본적으로 토크 조향 제어를 사용하지 않는 플랫폼에 대해 토크 조향 제어의 셀프 튜닝 기능을 활성화합니다.</translation>
</message>
<message>
<source>Less Restrict Settings for Self-Tune (Beta)</source>
<translation type="unfinished"></translation>
<translation>셀프 튜닝 제한 완화 설정 (베타)</translation>
</message>
<message>
<source>Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values.</source>
<translation type="unfinished"></translation>
<translation>셀프 튜닝 사용 시 덜 엄격한 설정을 사용합니다. 이는 학습 값에 대해 torqued가 더 관대하도록 허용합니다.</translation>
</message>
<message>
<source>Enable Custom Tuning</source>
<translation type="unfinished"></translation>
<translation>사용자 지정 튜닝 사용</translation>
</message>
<message>
<source>Enables custom tuning for Torque lateral control. Modifying Lateral Acceleration Factor and Friction below will override the offline values indicated in the YAML files within &quot;opendbc/car/torque_data&quot;. The values will also be used live when &quot;Manual Real-Time Tuning&quot; toggle is enabled.</source>
<translation type="unfinished"></translation>
<translation>토크 조향 제어에 대한 사용자 지정 튜닝을 활성화합니다. 아래에서 조향 가속 계수 및 마찰을 수정하면 &quot;opendbc/car/torque_data&quot; 내의 YAML 파일에 명시된 오프라인 값이 무시됩니다. 이 값들은 &quot;실시간 수동 튜닝&quot; 토글이 활성화되면 실시간으로도 사용됩니다.</translation>
</message>
</context>
<context>
@@ -2690,7 +2694,7 @@ Data</source>
</message>
<message>
<source>Favorites</source>
<translation type="unfinished"></translation>
<translation>즐겨찾기</translation>
</message>
</context>
<context>
@@ -2737,88 +2741,89 @@ Data</source>
</message>
<message>
<source>Enable Tesla Rainbow Mode</source>
<translation type="unfinished"></translation>
<translation>테슬라 무지개 모드 사용</translation>
</message>
<message>
<source>A beautiful rainbow effect on the path the model wants to take.</source>
<translation type="unfinished"></translation>
<translation>모델이 가고자 하는 경로에 아름다운 무지개 효과를 추가합니다.</translation>
</message>
<message>
<source>It</source>
<translation type="unfinished"></translation>
<translation>이는 운전에</translation>
</message>
<message>
<source>does not</source>
<translation type="unfinished"></translation>
<translation>어떠한</translation>
</message>
<message>
<source>affect driving in any way.</source>
<translation type="unfinished"></translation>
<translation>영향도 미치지 않습니다.</translation>
</message>
<message>
<source>Enable Standstill Timer</source>
<translation type="unfinished"></translation>
<translation>정차 타이머 사용</translation>
</message>
<message>
<source>Show a timer on the HUD when the car is at a standstill.</source>
<translation type="unfinished"></translation>
<translation>차량이 정차 상태일 때 HUD에 타이머를 표시합니다.</translation>
</message>
<message>
<source>Display Road Name</source>
<translation type="unfinished"></translation>
<translation>도로 이름 표시</translation>
</message>
<message>
<source>Displays the name of the road the car is traveling on. The OpenStreetMap database of the location must be downloaded from the OSM panel to fetch the road name.</source>
<translation type="unfinished"></translation>
<translation>차량이 주행 중인 도로의 이름을 표시합니다. 도로 이름을 가져오려면 OSM 패널에서 해당 위치의 OpenStreetMap 데이터베이스를 다운로드해야 합니다.</translation>
</message>
<message>
<source>Green Traffic Light Alert (Beta)</source>
<translation type="unfinished"></translation>
<translation>녹색 신호 감지 알림 (베타)</translation>
</message>
<message>
<source>A chime and on-screen alert will play when the traffic light you are waiting for turns green and you have no vehicle in front of you.</source>
<translation type="unfinished"></translation>
<translation>앞 신호등이 초록 신호로 바뀌고 전방에 차량이 없을 때, 알림음과 화면 경고가 표시됩니다.</translation>
</message>
<message>
<source>Note: This chime is only designed as a notification. It is the driver&apos;s responsibility to observe their environment and make decisions accordingly.</source>
<translation type="unfinished"></translation>
<translation>참고: 이 알림음은 단순한 알림 목적으로만 설계되었습니다. 주변 환경을 살피고 그에 따라 결정하는 것은 운전자의 책임입니다.</translation>
</message>
<message>
<source>Lead Departure Alert (Beta)</source>
<translation type="unfinished"></translation>
<translation>전방 차량 출발 알림 (베타)</translation>
</message>
<message>
<source>A chime and on-screen alert will play when you are stopped, and the vehicle in front of you start moving.</source>
<translation type="unfinished"></translation>
<translation>당신이 정차 중이고 전방 차량이 움직이기 시작할 때, 알림음과 화면 경고가 표시됩니다.</translation>
</message>
<message>
<source>Speedometer: Always Display True Speed</source>
<translation type="unfinished"></translation>
<translation>속도계: 항상 실제 속도 표시</translation>
</message>
<message>
<source>Always display the true vehicle current speed from wheel speed sensors.</source>
<translation type="unfinished"></translation>
<translation>항상 휠 스피드 센서에서 얻은 실제 차량 속도를 표시합니다.</translation>
</message>
<message>
<source>Speedometer: Hide from Onroad Screen</source>
<translation type="unfinished"></translation>
<translation>속도계: 주행 화면에서 숨기기</translation>
</message>
<message>
<source>Right</source>
<translation type="unfinished"></translation>
<translation>오른쪽</translation>
</message>
<message>
<source>Right &amp;&amp;
Bottom</source>
<translation type="unfinished"></translation>
<translation>오른쪽 &amp;&amp;
아래</translation>
</message>
<message>
<source>Developer UI</source>
<translation type="unfinished"></translation>
<translation>개발자 UI</translation>
</message>
<message>
<source>Display real-time parameters and metrics from various sources.</source>
<translation type="unfinished"></translation>
<translation>다양한 출처의 실시간 매개변수 및 측정 지표를 표시합니다.</translation>
</message>
</context>
<context>
+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;
@@ -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')
@@ -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()