[TIZI/TICI] ui: Visuals panel (#1496)

* commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* sunnypilot: remove Qt

* cabana: revert to stock Qt

* commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9

* commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e

* commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e

* sum more

* bump opendbc

* not yet

* should've been symlink'ed

* raylib says wut

* quiet mode back

* more fixes

* no more

* too extra red diff on the side

* need to bring this back

* too extra

* let's update docs here

* Revert "let's update docs here"

This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851.

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* multi-button

* Lint

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* Panels. With Icons. And Scroller.

* patience, grasshopper

* more patience, grasshopper

* sp raylib preview

* fix callback

* fix ui preview

* add ui previews

* introducing ui_state_sp for py

* param to control stock vs sp ui

* better

* add ui_update callback

* better padding

* init

* revert padding to 20

* new line, who dis

* this

* support for next line multi-button

* use inline=false

* uhh

* disabled colors

* hide em all

* lambdas

* NOT inline

* final touches

* hide HIDE

* ruff.. RUFF.. WHY RUFF

* listitem -> listitemsp

* Revert "add ui_update callback"

This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958.

* add show_description method

* remove padding from line separator.
like, WHY? 😩😩

* scroller -> scroller_tici

* scroller -> scroller_tici

* remove line separator padding

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* uhhh. nope

* optimizations

* I THINK this is not needed, i don't see it used on the visuals panel...

* unhide for now... Why hidden tho?

* refresh controls

* missing

* blindspot

* standstill timer

* road name toggle

* more descriptions

* more descriptions

* update desc

* param turn signals

* sort

* fix

* always show desc if not available

* should be bool

* rocket fuel

* steering arc

* lint

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: DevTekVE <devtekve@gmail.com>
This commit is contained in:
Nayan
2026-02-09 00:17:34 -05:00
committed by GitHub
parent 254f55ac15
commit 981494a354
10 changed files with 175 additions and 43 deletions

View File

@@ -249,7 +249,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OsmStateTitle", {PERSISTENT, STRING}},
{"OsmWayTest", {PERSISTENT, STRING}},
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
{"RoadNameToggle", {PERSISTENT, STRING}},
{"RoadNameToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
// Speed Limit
{"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}},

View File

@@ -5,9 +5,18 @@ 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.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, multiple_button_item_sp
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
CHEVRON_INFO_DESCRIPTION = {
"enabled": tr_noop("Display useful metrics below the chevron that tracks the lead car " +
"only applicable to cars with sunnypilot longitudinal control."),
"disabled": tr_noop("This feature requires sunnypilot longitudinal control to be available.")
}
class VisualsLayout(Widget):
def __init__(self):
@@ -18,13 +27,128 @@ class VisualsLayout(Widget):
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
self._toggle_defs = {
"BlindSpot": (
lambda: tr("Show Blind Spot Warnings"),
tr("Enabling this will display warnings when a vehicle is detected in your " +
"blind spot as long as your car has BSM supported."),
None,
),
"TorqueBar": (
lambda: tr("Steering Arc"),
tr("Display steering arc on the driving screen when lateral control is enabled."),
None,
),
"RainbowMode": (
lambda: tr("Enable Tesla Rainbow Mode"),
tr("A beautiful rainbow effect on the path the model wants to take. " +
"It does not affect driving in any way."),
None,
),
"StandstillTimer": (
lambda: tr("Enable Standstill Timer"),
tr("Show a timer on the HUD when the car is at a standstill."),
None,
),
"RoadNameToggle": (
lambda: tr("Display Road Name"),
tr("Displays the name of the road the car is traveling on." +
"<br>The OpenStreetMap database of the location must be downloaded from " +
"the OSM panel to fetch the road name."),
None,
),
"GreenLightAlert": (
lambda: tr("Green Traffic Light Alert (Beta)"),
tr("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." +
"<br>Note: This chime is only designed as a notification. " +
"It is the driver's responsibility to observe their environment and make decisions accordingly."),
None,
),
"LeadDepartAlert": (
lambda: tr("Lead Departure Alert (Beta)"),
tr("A chime and on-screen alert will play when you are stopped, and the vehicle in front of you start moving." +
"<br>Note: This chime is only designed as a notification. " +
"It is the driver's responsibility to observe their environment and make decisions accordingly."),
None,
),
"TrueVEgoUI": (
lambda: tr("Speedometer: Always Display True Speed"),
tr("For applicable vehicles, always display the true vehicle current speed from wheel speed sensors."),
None,
),
"HideVEgoUI": (
lambda: tr("Speedometer: Hide from Onroad Screen"),
tr("When enabled, the speedometer on the onroad screen is not displayed."),
None,
),
"ShowTurnSignals": (
lambda: tr("Display Turn Signals"),
tr("When enabled, visual turn indicators are drawn on the HUD."),
None,
),
"RocketFuel": (
lambda: tr("Real-time Acceleration Bar"),
tr("Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration. " +
"This displays what the car is currently doing, not what the planner is requesting."),
None,
),
}
self._toggles = {}
for param, (title, desc, callback) in self._toggle_defs.items():
toggle = toggle_item_sp(
title=title,
description=desc,
param=param,
initial_state=ui_state.params.get_bool(param),
callback=callback,
)
self._toggles[param] = toggle
self._chevron_info = multiple_button_item_sp(
title=lambda: tr("Display Metrics Below Chevron"),
description="",
buttons=[lambda: tr("Off"), lambda: tr("Distance"), lambda: tr("Speed"), lambda: tr("Time"), lambda: tr("All")],
param="ChevronInfo",
inline=False
)
self._dev_ui_info = multiple_button_item_sp(
title=lambda: tr("Developer UI"),
description=lambda: tr("Display real-time parameters and metrics from various sources."),
buttons=[lambda: tr("Off"), lambda: tr("Bottom"), lambda: tr("Right"), lambda: tr("Right & Bottom")],
param="DevUIInfo",
button_width=350,
inline=False
)
items = list(self._toggles.values()) + [
self._chevron_info,
self._dev_ui_info,
]
return items
def _update_state(self):
super()._update_state()
for param in self._toggle_defs:
self._toggles[param].action_item.set_state(self._params.get_bool(param))
self._dev_ui_info.action_item.set_selected_button(ui_state.params.get("DevUIInfo", return_default=True))
if ui_state.has_longitudinal_control:
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["enabled"]))
self._chevron_info.action_item.set_selected_button(ui_state.params.get("ChevronInfo", return_default=True))
self._chevron_info.action_item.set_enabled(True)
else:
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["disabled"]))
self._chevron_info.action_item.set_enabled(False)
ui_state.params.put("ChevronInfo", 0)
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
if not ui_state.has_longitudinal_control:
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["disabled"]))
self._chevron_info.show_description(True)

View File

@@ -31,6 +31,9 @@ class BlindSpotIndicators:
return self._blind_spot_left_alpha_filter.x > 0.01 or self._blind_spot_right_alpha_filter.x > 0.01
def render(self, rect: rl.Rectangle) -> None:
if not ui_state.blindspot:
return
BLIND_SPOT_MARGIN_X = 20 # Distance from edge of screen
BLIND_SPOT_Y_OFFSET = 100 # Distance from top of screen

View File

@@ -23,7 +23,6 @@ class CircularAlertsRenderer:
self._e2e_alert_frame = 0
self._green_light_alert = False
self._lead_depart_alert = False
self._standstill_timer = False
self._standstill_elapsed_time = 0.0
self._is_standstill = False
self._alert_text = ""
@@ -36,7 +35,6 @@ class CircularAlertsRenderer:
car_state = sm['carState']
self._green_light_alert = lp_sp.e2eAlerts.greenLightAlert
self._lead_depart_alert = lp_sp.e2eAlerts.leadDepartAlert
self._standstill_timer = ui_state.standstill_timer
self._is_standstill = car_state.standstill
if not ui_state.started:
@@ -61,7 +59,7 @@ class CircularAlertsRenderer:
self._alert_text = "LEAD VEHICLE\nDEPARTING"
self._alert_img = self._lead_depart_alert_img
elif self._standstill_timer and self._is_standstill:
elif ui_state.standstill_timer and self._is_standstill:
self._alert_img = None
self._standstill_elapsed_time += 1.0 / gui_app.target_fps
minute = int(self._standstill_elapsed_time / 60)
@@ -75,7 +73,7 @@ class CircularAlertsRenderer:
self._standstill_elapsed_time = 0.0
def render(self, rect: rl.Rectangle) -> None:
if not self._allow_e2e_alerts or (self._e2e_alert_display_timer <= 0 and not (self._standstill_timer and self._is_standstill)):
if not self._allow_e2e_alerts or (self._e2e_alert_display_timer <= 0 and not (ui_state.standstill_timer and self._is_standstill)):
return
e2e_alert_size = 250
@@ -91,7 +89,7 @@ class CircularAlertsRenderer:
is_pulsing = (self._e2e_alert_frame % gui_app.target_fps) < (gui_app.target_fps / 2.5)
# Standstill Timer (STOPPED) should be static white
if self._e2e_alert_display_timer == 0 and self._standstill_timer and self._is_standstill:
if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill:
frame_color = rl.Color(255, 255, 255, 75)
else:
frame_color = rl.Color(255, 255, 255, 75) if is_pulsing else rl.Color(0, 255, 0, 75)
@@ -121,7 +119,7 @@ class CircularAlertsRenderer:
# Draw lines upwards from bottom
current_y = bottom_y - (len(lines) * text_size * FONT_SCALE)
if self._e2e_alert_display_timer == 0 and self._standstill_timer and self._is_standstill:
if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill:
# Standstill Timer Text
alert_alt_text = "STOPPED"
top_text_size = 80

View File

@@ -59,6 +59,4 @@ class HudRendererSP(HudRenderer):
self.smart_cruise_control_renderer.render(rect)
self.turn_signal_controller.render(rect)
self.circular_alerts_renderer.render(rect)
if ui_state.rocket_fuel:
self.rocket_fuel.render(rect, ui_state.sm)
self.rocket_fuel.render(rect, ui_state.sm)

View File

@@ -31,7 +31,7 @@ class RoadNameRenderer(Widget):
self.road_name = lmd.roadName
def _render(self, rect: rl.Rectangle):
if not self.road_name:
if not self.road_name or not ui_state.road_name_toggle:
return
text = self.road_name

View File

@@ -6,12 +6,17 @@ See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.ui_state import ui_state
class RocketFuel:
def __init__(self):
self.vc_accel = 0.0
def render(self, rect: rl.Rectangle, sm) -> None:
if not ui_state.rocket_fuel:
return
vc_accel0 = sm['carState'].aEgo
# Smooth the acceleration

View File

@@ -137,6 +137,9 @@ class TurnSignalController:
self._right_signal.deactivate()
def render(self, rect: rl.Rectangle):
if not ui_state.turn_signals:
return
x = rect.x + rect.width / 2
left_x = x - self._config.left_x - self._config.size

View File

@@ -120,22 +120,23 @@ class UIStateSP:
CP_SP_bytes = self.params.get("CarParamsSPPersistent")
if CP_SP_bytes is not None:
self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP)
self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled")
self.developer_ui = self.params.get("DevUIInfo")
self.rocket_fuel = self.params.get_bool("RocketFuel")
self.rainbow_path = self.params.get_bool("RainbowMode")
self.chevron_metrics = self.params.get("ChevronInfo")
self.torque_bar = self.params.get_bool("TorqueBar")
self.active_bundle = self.params.get("ModelManager_ActiveBundle")
self.blindspot = self.params.get_bool("BlindSpot")
self.chevron_metrics = self.params.get("ChevronInfo")
self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True)
self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True)
self.standstill_timer = self.params.get_bool("StandstillTimer")
self.true_v_ego_ui = self.params.get_bool("TrueVEgoUI")
self.developer_ui = self.params.get("DevUIInfo")
self.hide_v_ego_ui = self.params.get_bool("HideVEgoUI")
# Onroad Screen Brightness
self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True)))
self.onroad_brightness_timer_param = self.params.get("OnroadScreenOffTimer", return_default=True)
self.rainbow_path = self.params.get_bool("RainbowMode")
self.road_name_toggle = self.params.get_bool("RoadNameToggle")
self.rocket_fuel = self.params.get_bool("RocketFuel")
self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True)
self.standstill_timer = self.params.get_bool("StandstillTimer")
self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled")
self.torque_bar = self.params.get_bool("TorqueBar")
self.true_v_ego_ui = self.params.get_bool("TrueVEgoUI")
self.turn_signals = self.params.get_bool("ShowTurnSignals")
class DeviceSP:

View File

@@ -90,8 +90,8 @@
"description": ""
},
"BlindSpot": {
"title": "Blind Spot Detection",
"description": ""
"title": "[TIZI/TICI only] Blind Spot Detection",
"description": "Enabling this will display warnings when a vehicle is detected in your blind spot as long as your car has BSM supported."
},
"BlinkerMinLateralControlSpeed": {
"title": "Blinker Min Lateral Control Speed",
@@ -339,8 +339,8 @@
"description": ""
},
"GreenLightAlert": {
"title": "Green Light Alert",
"description": ""
"title": "Green Traffic Light Alert (Beta)",
"description": "A chime and on-screen alert (TIZI/TICI only) will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. <br>Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly."
},
"GsmApn": {
"title": "GSM APN",
@@ -367,8 +367,8 @@
"description": ""
},
"HideVEgoUI": {
"title": "Hide vEgo UI",
"description": ""
"title": "[TIZI/TICI only] Speedometer: Hide from Onroad Screen",
"description": "When enabled, the speedometer on the onroad screen is not displayed."
},
"HyundaiLongitudinalTuning": {
"title": "Hyundai Longitudinal Tuning",
@@ -585,8 +585,8 @@
"description": ""
},
"LeadDepartAlert": {
"title": "Lead Depart Alert",
"description": ""
"title": "Lead Departure Alert (Beta)",
"description": "A chime and on-screen alert (TIZI/TICI only) will play when you are stopped, and the vehicle in front of you start moving. <br>Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly."
},
"LiveDelay": {
"title": "Live Delay",
@@ -1079,12 +1079,12 @@
"description": ""
},
"RoadNameToggle": {
"title": "Display Road Name",
"description": ""
"title": "[TIZI/TICI only] Display Road Name",
"description": "Displays the name of the road the car is traveling on. <br>The OpenStreetMap database of the location must be downloaded to fetch the road name."
},
"RocketFuel": {
"title": "Display Rocket Fuel Bar",
"description": "Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration."
"title": "[TIZI/TICI only] Real-time Acceleration Bar",
"description": "Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration. This displays what the car is currently doing, not what the planner is requesting."
},
"RouteCount": {
"title": "Route Count",
@@ -1103,8 +1103,8 @@
"description": ""
},
"ShowTurnSignals": {
"title": "Show Turn Signals",
"description": ""
"title": "[TIZI/TICI only] Display Turn Signals",
"description": "When enabled, visual turn indicators are drawn on the HUD."
},
"SmartCruiseControlMap": {
"title": "Smart Cruise Control - Map",
@@ -1196,8 +1196,8 @@
"description": ""
},
"StandstillTimer": {
"title": "Standstill Timer",
"description": ""
"title": "[TIZI/TICI only] Standstill Timer",
"description": "Show a timer on the HUD when the car is at a standstill."
},
"SubaruStopAndGo": {
"title": "Subaru Stop and Go",
@@ -1240,8 +1240,8 @@
"description": ""
},
"TorqueBar": {
"title": "Steering Arc",
"description": "[TIZI/TICI only] Display steering arc on the driving screen when lateral control is enabled."
"title": "[TIZI/TICI only] Steering Arc",
"description": "Display steering arc on the driving screen when lateral control is enabled."
},
"TorqueParamsOverrideEnabled": {
"title": "Manual Real-Time Tuning",
@@ -1271,8 +1271,8 @@
"description": ""
},
"TrueVEgoUI": {
"title": "True vEgo UI",
"description": ""
"title": "[TIZI/TICI only] Speedometer: Always Display True Speed",
"description": "For applicable vehicles, always display the true vehicle current speed from wheel speed sensors."
},
"UbloxAvailable": {
"title": "Ublox Available",