system/ui: add centralized UIState singleton for global state management (#35397)

* add centralized UIState singleton for global state management

* safely import ui_state

* merge master

* merge master
pull/35428/head
Dean Lee 3 months ago committed by GitHub
parent b59841329b
commit d46066225d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 14
      system/ui/lib/application.py
  2. 129
      system/ui/lib/ui_state.py
  3. 5
      system/ui/onroad/alert_renderer.py
  4. 47
      system/ui/onroad/augmented_road_view.py
  5. 8
      system/ui/onroad/driver_state.py
  6. 36
      system/ui/onroad/hud_renderer.py
  7. 4
      system/ui/onroad/model_renderer.py

@ -36,6 +36,16 @@ class FontWeight(IntEnum):
BLACK = 8 BLACK = 8
def _get_ui_state():
"""Safely import and return ui_state, or None if unavailable."""
try:
from openpilot.system.ui.lib.ui_state import ui_state
return ui_state
except ImportError:
return None
class GuiApplication: class GuiApplication:
def __init__(self, width: int, height: int): def __init__(self, width: int, height: int):
self._fonts: dict[FontWeight, rl.Font] = {} self._fonts: dict[FontWeight, rl.Font] = {}
@ -139,6 +149,7 @@ class GuiApplication:
rl.close_window() rl.close_window()
def render(self): def render(self):
ui_state = _get_ui_state()
try: try:
while not (self._window_close_requested or rl.window_should_close()): while not (self._window_close_requested or rl.window_should_close()):
if self._render_texture: if self._render_texture:
@ -148,6 +159,9 @@ class GuiApplication:
rl.begin_drawing() rl.begin_drawing()
rl.clear_background(rl.BLACK) rl.clear_background(rl.BLACK)
if ui_state is not None:
ui_state.update()
yield yield
if self._render_texture: if self._render_texture:

@ -0,0 +1,129 @@
import pyray as rl
from enum import Enum
from cereal import messaging, log
from openpilot.common.params import Params, UnknownKeyName
UI_BORDER_SIZE = 30
class UIStatus(Enum):
DISENGAGED = "disengaged"
ENGAGED = "engaged"
OVERRIDE = "override"
class UIState:
_instance: 'UIState | None' = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialize()
return cls._instance
def _initialize(self):
self.params = Params()
self.sm = messaging.SubMaster(
[
"modelV2",
"controlsState",
"liveCalibration",
"radarState",
"deviceState",
"pandaStates",
"carParams",
"driverMonitoringState",
"carState",
"driverStateV2",
"roadCameraState",
"wideRoadCameraState",
"managerState",
"selfdriveState",
"longitudinalPlan",
]
)
# UI Status tracking
self.status: UIStatus = UIStatus.DISENGAGED
self.started_frame: int = 0
self._engaged_prev: bool = False
self._started_prev: bool = False
# Core state variables
self.is_metric: bool = self.params.get_bool("IsMetric")
self.started: bool = False
self.ignition: bool = False
self.panda_type: log.PandaState.PandaType = log.PandaState.PandaType.unknown
self.personality: log.LongitudinalPersonality = log.LongitudinalPersonality.standard
self.light_sensor: float = -1.0
self._update_params()
@property
def engaged(self) -> bool:
return self.started and self.sm["selfdriveState"].enabled
def update(self) -> None:
self.sm.update(0)
self._update_state()
self._update_status()
def _update_state(self) -> None:
# Handle panda states updates
if self.sm.updated["pandaStates"]:
panda_states = self.sm["pandaStates"]
if len(panda_states) > 0:
# Get panda type from first panda
self.panda_type = panda_states[0].pandaType
# Check ignition status across all pandas
if self.panda_type != log.PandaState.PandaType.unknown:
self.ignition = any(state.ignitionLine or state.ignitionCan for state in panda_states)
elif self.sm.frame - self.sm.recv_frame["pandaStates"] > 5 * rl.get_fps():
self.panda_type = log.PandaState.PandaType.unknown
# Handle wide road camera state updates
if self.sm.updated["wideRoadCameraState"]:
cam_state = self.sm["wideRoadCameraState"]
# Scale factor based on sensor type
scale = 6.0 if cam_state.sensor == 'ar0231' else 1.0
self.light_sensor = max(100.0 - scale * cam_state.exposureValPercent, 0.0)
elif not self.sm.alive["wideRoadCameraState"] or not self.sm.valid["wideRoadCameraState"]:
self.light_sensor = -1
# Update started state
self.started = self.sm["deviceState"].started and self.ignition
def _update_status(self) -> None:
if self.started and self.sm.updated["selfdriveState"]:
ss = self.sm["selfdriveState"]
state = ss.state
if state in (log.SelfdriveState.OpenpilotState.preEnabled, log.SelfdriveState.OpenpilotState.overriding):
self.status = UIStatus.OVERRIDE
else:
self.status = UIStatus.ENGAGED if ss.enabled else UIStatus.DISENGAGED
# Check for engagement state changes
if self.engaged != self._engaged_prev:
self._engaged_prev = self.engaged
# Handle onroad/offroad transition
if self.started != self._started_prev or self.sm.frame == 1:
if self.started:
self.status = UIStatus.DISENGAGED
self.started_frame = self.sm.frame
self._started_prev = self.started
def _update_params(self) -> None:
try:
self.is_metric = self.params.get_bool("IsMetric")
except UnknownKeyName:
self.is_metric = False
# Global instance
ui_state = UIState()

@ -5,6 +5,7 @@ from cereal import messaging, log
from openpilot.system.hardware import TICI from openpilot.system.hardware import TICI
from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.label import gui_text_box from openpilot.system.ui.lib.label import gui_text_box
from openpilot.system.ui.lib.ui_state import ui_state
ALERT_MARGIN = 40 ALERT_MARGIN = 40
@ -61,8 +62,6 @@ ALERT_CRITICAL_REBOOT = Alert(
class AlertRenderer: class AlertRenderer:
def __init__(self): def __init__(self):
# TODO: use ui_state to determine when to start
self.started_frame: int = 0
self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL) self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL)
self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD) self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
self.font_metrics_cache: dict[tuple[str, int, str], rl.Vector2] = {} self.font_metrics_cache: dict[tuple[str, int, str], rl.Vector2] = {}
@ -72,7 +71,7 @@ class AlertRenderer:
ss = sm['selfdriveState'] ss = sm['selfdriveState']
# Check if waiting to start # Check if waiting to start
if sm.recv_frame['selfdriveState'] < self.started_frame: if sm.recv_frame['selfdriveState'] < ui_state.started_frame:
return ALERT_STARTUP_PENDING return ALERT_STARTUP_PENDING
# Handle selfdrive timeout # Handle selfdrive timeout

@ -1,9 +1,9 @@
import numpy as np import numpy as np
import pyray as rl import pyray as rl
from enum import Enum
from cereal import messaging, log from cereal import log
from msgq.visionipc import VisionStreamType from msgq.visionipc import VisionStreamType
from openpilot.system.ui.lib.ui_state import ui_state, UIStatus, UI_BORDER_SIZE
from openpilot.system.ui.onroad.alert_renderer import AlertRenderer from openpilot.system.ui.onroad.alert_renderer import AlertRenderer
from openpilot.system.ui.onroad.driver_state import DriverStateRenderer from openpilot.system.ui.onroad.driver_state import DriverStateRenderer
from openpilot.system.ui.onroad.hud_renderer import HudRenderer from openpilot.system.ui.onroad.hud_renderer import HudRenderer
@ -17,19 +17,18 @@ from openpilot.common.transformations.orientation import rot_from_euler
OpState = log.SelfdriveState.OpenpilotState OpState = log.SelfdriveState.OpenpilotState
CALIBRATED = log.LiveCalibrationData.Status.calibrated CALIBRATED = log.LiveCalibrationData.Status.calibrated
DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"] DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"]
UI_BORDER_SIZE = 30
class BorderStatus(Enum): BORDER_COLORS = {
DISENGAGED = rl.Color(0x17, 0x33, 0x49, 0xc8) # Blue for disengaged state UIStatus.DISENGAGED: rl.Color(0x17, 0x33, 0x49, 0xC8), # Blue for disengaged state
OVERRIDE = rl.Color(0x91, 0x9b, 0x95, 0xf1) # Gray for override state UIStatus.OVERRIDE: rl.Color(0x91, 0x9B, 0x95, 0xF1), # Gray for override state
ENGAGED = rl.Color(0x17, 0x86, 0x44, 0xf1) # Green for engaged state UIStatus.ENGAGED: rl.Color(0x17, 0x86, 0x44, 0xF1), # Green for engaged state
}
class AugmentedRoadView(CameraView): class AugmentedRoadView(CameraView):
def __init__(self, sm: messaging.SubMaster, stream_type: VisionStreamType): def __init__(self, stream_type: VisionStreamType):
super().__init__("camerad", stream_type) super().__init__("camerad", stream_type)
self.sm = sm
self.device_camera: DeviceCameraConfig | None = None self.device_camera: DeviceCameraConfig | None = None
self.view_from_calib = view_frame_from_device_frame.copy() self.view_from_calib = view_frame_from_device_frame.copy()
self.view_from_wide_calib = view_frame_from_device_frame.copy() self.view_from_wide_calib = view_frame_from_device_frame.copy()
@ -72,10 +71,10 @@ class AugmentedRoadView(CameraView):
super().render(rect) super().render(rect)
# Draw all UI overlays # Draw all UI overlays
self.model_renderer.draw(self._content_rect, self.sm) self.model_renderer.draw(self._content_rect, ui_state.sm)
self._hud_renderer.draw(self._content_rect, self.sm) self._hud_renderer.draw(self._content_rect, ui_state.sm)
self.alert_renderer.draw(self._content_rect, self.sm) self.alert_renderer.draw(self._content_rect, ui_state.sm)
self.driver_state_renderer.draw(self._content_rect, self.sm) self.driver_state_renderer.draw(self._content_rect, ui_state.sm)
# Custom UI extension point - add custom overlays here # Custom UI extension point - add custom overlays here
# Use self._content_rect for positioning within camera bounds # Use self._content_rect for positioning within camera bounds
@ -84,18 +83,12 @@ class AugmentedRoadView(CameraView):
rl.end_scissor_mode() rl.end_scissor_mode()
def _draw_border(self, rect: rl.Rectangle): def _draw_border(self, rect: rl.Rectangle):
state = self.sm["selfdriveState"] border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED])
if state.state in (OpState.preEnabled, OpState.overriding): rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, border_color)
status = BorderStatus.OVERRIDE
elif state.enabled:
status = BorderStatus.ENGAGED
else:
status = BorderStatus.DISENGAGED
rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, status.value)
def _update_calibration(self): def _update_calibration(self):
# Update device camera if not already set # Update device camera if not already set
sm = ui_state.sm
if not self.device_camera and sm.seen['roadCameraState'] and sm.seen['deviceState']: if not self.device_camera and sm.seen['roadCameraState'] and sm.seen['deviceState']:
self.device_camera = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] self.device_camera = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
@ -103,7 +96,7 @@ class AugmentedRoadView(CameraView):
if not (sm.updated["liveCalibration"] and sm.valid['liveCalibration']): if not (sm.updated["liveCalibration"] and sm.valid['liveCalibration']):
return return
calib = self.sm['liveCalibration'] calib = sm['liveCalibration']
if len(calib.rpyCalib) != 3 or calib.calStatus != CALIBRATED: if len(calib.rpyCalib) != 3 or calib.calStatus != CALIBRATED:
return return
@ -118,7 +111,7 @@ class AugmentedRoadView(CameraView):
def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray:
# Check if we can use cached matrix # Check if we can use cached matrix
calib_time = self.sm.recv_frame['liveCalibration'] calib_time = ui_state.sm.recv_frame['liveCalibration']
current_dims = (self._content_rect.width, self._content_rect.height) current_dims = (self._content_rect.width, self._content_rect.height)
if (self._last_calib_time == calib_time and if (self._last_calib_time == calib_time and
self._last_rect_dims == current_dims and self._last_rect_dims == current_dims and
@ -178,14 +171,10 @@ class AugmentedRoadView(CameraView):
if __name__ == "__main__": if __name__ == "__main__":
gui_app.init_window("OnRoad Camera View") gui_app.init_window("OnRoad Camera View")
sm = messaging.SubMaster(["modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", road_camera_view = AugmentedRoadView(VisionStreamType.VISION_STREAM_ROAD)
"pandaStates", "carParams", "driverMonitoringState", "carState", "driverStateV2",
"roadCameraState", "wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan"])
road_camera_view = AugmentedRoadView(sm, VisionStreamType.VISION_STREAM_ROAD)
print("***press space to switch camera view***") print("***press space to switch camera view***")
try: try:
for _ in gui_app.render(): for _ in gui_app.render():
sm.update(0)
if rl.is_key_released(rl.KeyboardKey.KEY_SPACE): if rl.is_key_released(rl.KeyboardKey.KEY_SPACE):
is_wide = road_camera_view.stream_type == VisionStreamType.VISION_STREAM_WIDE_ROAD is_wide = road_camera_view.stream_type == VisionStreamType.VISION_STREAM_WIDE_ROAD
road_camera_view.switch_stream(VisionStreamType.VISION_STREAM_ROAD if is_wide else VisionStreamType.VISION_STREAM_WIDE_ROAD) road_camera_view.switch_stream(VisionStreamType.VISION_STREAM_ROAD if is_wide else VisionStreamType.VISION_STREAM_WIDE_ROAD)

@ -2,6 +2,8 @@ import numpy as np
import pyray as rl import pyray as rl
from dataclasses import dataclass from dataclasses import dataclass
from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.ui_state import ui_state, UI_BORDER_SIZE
# Default 3D coordinates for face keypoints as a NumPy array # Default 3D coordinates for face keypoints as a NumPy array
DEFAULT_FACE_KPTS_3D = np.array([ DEFAULT_FACE_KPTS_3D = np.array([
@ -17,7 +19,6 @@ DEFAULT_FACE_KPTS_3D = np.array([
], dtype=np.float32) ], dtype=np.float32)
# UI constants # UI constants
UI_BORDER_SIZE = 30
BTN_SIZE = 192 BTN_SIZE = 192
IMG_SIZE = 144 IMG_SIZE = 144
ARC_LENGTH = 133 ARC_LENGTH = 133
@ -95,8 +96,7 @@ class DriverStateRenderer:
rl.draw_spline_linear(self.face_lines, len(self.face_lines), 5.2, self.white_color) rl.draw_spline_linear(self.face_lines, len(self.face_lines), 5.2, self.white_color)
# Set arc color based on engaged state # Set arc color based on engaged state
engaged = True self.arc_color = self.engaged_color if ui_state.engaged else self.disengaged_color
self.arc_color = self.engaged_color if engaged else self.disengaged_color
self.arc_color.a = int(0.4 * 255 * (1.0 - self.dm_fade_state)) # Fade out when inactive self.arc_color.a = int(0.4 * 255 * (1.0 - self.dm_fade_state)) # Fade out when inactive
# Draw arcs # Draw arcs
@ -107,7 +107,7 @@ class DriverStateRenderer:
def _is_visible(self, sm): def _is_visible(self, sm):
"""Check if the visualization should be rendered.""" """Check if the visualization should be rendered."""
return (sm.seen['driverStateV2'] and return (sm.recv_frame['driverStateV2'] > ui_state.started_frame and
sm.seen['driverMonitoringState'] and sm.seen['driverMonitoringState'] and
sm['selfdriveState'].alertSize == 0) sm['selfdriveState'].alertSize == 0)

@ -1,9 +1,9 @@
import pyray as rl import pyray as rl
from dataclasses import dataclass from dataclasses import dataclass
from cereal.messaging import SubMaster from cereal.messaging import SubMaster
from openpilot.system.ui.lib.ui_state import ui_state, UIStatus
from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.common.conversions import Conversions as CV from openpilot.common.conversions import Conversions as CV
from enum import IntEnum
# Constants # Constants
SET_SPEED_NA = 255 SET_SPEED_NA = 255
@ -53,17 +53,9 @@ FONT_SIZES = FontSizes()
COLORS = Colors() COLORS = Colors()
class HudStatus(IntEnum):
DISENGAGED = 0
OVERRIDE = 1
ENGAGED = 2
class HudRenderer: class HudRenderer:
def __init__(self): def __init__(self):
"""Initialize the HUD renderer.""" """Initialize the HUD renderer."""
self.is_metric: bool = False
self.status: HudStatus = HudStatus.DISENGAGED
self.is_cruise_set: bool = False self.is_cruise_set: bool = False
self.is_cruise_available: bool = False self.is_cruise_available: bool = False
self.set_speed: float = SET_SPEED_NA self.set_speed: float = SET_SPEED_NA
@ -77,10 +69,7 @@ class HudRenderer:
def _update_state(self, sm: SubMaster) -> None: def _update_state(self, sm: SubMaster) -> None:
"""Update HUD state based on car state and controls state.""" """Update HUD state based on car state and controls state."""
self.is_metric = True if sm.recv_frame["carState"] < ui_state.started_frame:
self.status = HudStatus.DISENGAGED
if not sm.valid['carState']:
self.is_cruise_set = False self.is_cruise_set = False
self.set_speed = SET_SPEED_NA self.set_speed = SET_SPEED_NA
self.speed = 0.0 self.speed = 0.0
@ -96,13 +85,13 @@ class HudRenderer:
self.is_cruise_set = 0 < self.set_speed < SET_SPEED_NA self.is_cruise_set = 0 < self.set_speed < SET_SPEED_NA
self.is_cruise_available = self.set_speed != -1 self.is_cruise_available = self.set_speed != -1
if self.is_cruise_set and not self.is_metric: if self.is_cruise_set and not ui_state.is_metric:
self.set_speed *= KM_TO_MILE self.set_speed *= KM_TO_MILE
v_ego_cluster = car_state.vEgoCluster v_ego_cluster = car_state.vEgoCluster
self.v_ego_cluster_seen = self.v_ego_cluster_seen or v_ego_cluster != 0.0 self.v_ego_cluster_seen = self.v_ego_cluster_seen or v_ego_cluster != 0.0
v_ego = v_ego_cluster if self.v_ego_cluster_seen else car_state.vEgo v_ego = v_ego_cluster if self.v_ego_cluster_seen else car_state.vEgo
speed_conversion = CV.MS_TO_KPH if self.is_metric else CV.MS_TO_MPH speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
self.speed = max(0.0, v_ego * speed_conversion) self.speed = max(0.0, v_ego * speed_conversion)
def draw(self, rect: rl.Rectangle, sm: SubMaster) -> None: def draw(self, rect: rl.Rectangle, sm: SubMaster) -> None:
@ -125,7 +114,7 @@ class HudRenderer:
def _draw_set_speed(self, rect: rl.Rectangle) -> None: def _draw_set_speed(self, rect: rl.Rectangle) -> None:
"""Draw the MAX speed indicator box.""" """Draw the MAX speed indicator box."""
set_speed_width = UI_CONFIG.set_speed_width_metric if self.is_metric else UI_CONFIG.set_speed_width_imperial set_speed_width = UI_CONFIG.set_speed_width_metric if ui_state.is_metric else UI_CONFIG.set_speed_width_imperial
x = rect.x + 60 + (UI_CONFIG.set_speed_width_imperial - set_speed_width) // 2 x = rect.x + 60 + (UI_CONFIG.set_speed_width_imperial - set_speed_width) // 2
y = rect.y + 45 y = rect.y + 45
@ -137,11 +126,12 @@ class HudRenderer:
set_speed_color = COLORS.dark_grey set_speed_color = COLORS.dark_grey
if self.is_cruise_set: if self.is_cruise_set:
set_speed_color = COLORS.white set_speed_color = COLORS.white
max_color = { if ui_state.status == UIStatus.ENGAGED:
HudStatus.DISENGAGED: COLORS.disengaged, max_color = COLORS.engaged
HudStatus.OVERRIDE: COLORS.override, elif ui_state.status == UIStatus.DISENGAGED:
HudStatus.ENGAGED: COLORS.engaged, max_color = COLORS.disengaged
}.get(self.status, COLORS.grey) elif ui_state.status == UIStatus.OVERRIDE:
max_color = COLORS.override
max_text = "MAX" max_text = "MAX"
max_text_width = self._measure_text(max_text, self._font_semi_bold, FONT_SIZES.max_speed, 'semi_bold').x max_text_width = self._measure_text(max_text, self._font_semi_bold, FONT_SIZES.max_speed, 'semi_bold').x
@ -172,7 +162,7 @@ class HudRenderer:
speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2) speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2)
rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.white) rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.white)
unit_text = "km/h" if self.is_metric else "mph" unit_text = "km/h" if ui_state.is_metric else "mph"
unit_text_size = self._measure_text(unit_text, self._font_medium, FONT_SIZES.speed_unit, 'medium') unit_text_size = self._measure_text(unit_text, self._font_medium, FONT_SIZES.speed_unit, 'medium')
unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2) unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2)
rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent) rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent)
@ -183,7 +173,7 @@ class HudRenderer:
center_y = int(rect.y + UI_CONFIG.border_size + UI_CONFIG.button_size / 2) center_y = int(rect.y + UI_CONFIG.border_size + UI_CONFIG.button_size / 2)
rl.draw_circle(center_x, center_y, UI_CONFIG.button_size / 2, COLORS.black_translucent) rl.draw_circle(center_x, center_y, UI_CONFIG.button_size / 2, COLORS.black_translucent)
opacity = 0.7 if self.status == HudStatus.DISENGAGED else 1.0 opacity = 0.7 if ui_state.status == UIStatus.DISENGAGED else 1.0
img_pos = rl.Vector2(center_x - self._wheel_texture.width / 2, center_y - self._wheel_texture.height / 2) img_pos = rl.Vector2(center_x - self._wheel_texture.width / 2, center_y - self._wheel_texture.height / 2)
rl.draw_texture_v(self._wheel_texture, img_pos, rl.Color(255, 255, 255, int(255 * opacity))) rl.draw_texture_v(self._wheel_texture, img_pos, rl.Color(255, 255, 255, int(255 * opacity)))

@ -5,6 +5,7 @@ from cereal import messaging, car
from dataclasses import dataclass, field from dataclasses import dataclass, field
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.system.ui.lib.application import DEFAULT_FPS from openpilot.system.ui.lib.application import DEFAULT_FPS
from openpilot.system.ui.lib.ui_state import ui_state
from openpilot.system.ui.lib.shader_polygon import draw_polygon from openpilot.system.ui.lib.shader_polygon import draw_polygon
@ -86,7 +87,8 @@ class ModelRenderer:
def draw(self, rect: rl.Rectangle, sm: messaging.SubMaster): def draw(self, rect: rl.Rectangle, sm: messaging.SubMaster):
# Check if data is up-to-date # Check if data is up-to-date
if not sm.valid['modelV2'] or not sm.valid['liveCalibration']: if (sm.recv_frame["liveCalibration"] < ui_state.started_frame or
sm.recv_frame["modelV2"] < ui_state.started_frame):
return return
# Set up clipping region # Set up clipping region

Loading…
Cancel
Save