Files
StarPilot/openpilot/selfdrive/ui/ui_state.py
T
2026-10-26 20:38:11 -04:00

698 lines
30 KiB
Python

import numpy as np
import os
import time
import threading
import capnp
from collections.abc import Callable
from enum import Enum
from dataclasses import replace
from openpilot.cereal import messaging, log
from openpilot.cereal.services import SERVICE_LIST
from opendbc.car.structs import car
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.starpilot.schema_cache import get_cache, prewarm_cache_contracts
from openpilot.starpilot.feature_runtime import enabled as feature_enabled
from openpilot.starpilot.ui.display_preferences import AUTO, DisplayPreferences, read_preferences
from openpilot.common.realtime import drop_realtime
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.ui.lib.prime_state import PrimeState
from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS
from openpilot.system.ui.lib.application import gui_app
from openpilot.common.hardware import HARDWARE, PC
from openpilot.common.hardware.usb import cable_connected, get_usb_state, is_chestnut_usb_id
from openpilot.starpilot.models.manager import ModelManager
from openpilot.starpilot.models.jetlink_status import JetlinkStatusFeed
from openpilot.tools.replay.display_clock import BOOT_TIME_SERVICES, DisplayClockReader
BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50
PARAM_UPDATE_TIME = 1 / 5.0
class UIStatus(Enum):
DISENGAGED = "disengaged"
ENGAGED = "engaged"
OVERRIDE = "override"
class ChestnutState(Enum):
DISCONNECTED = "disconnected"
UNCOMPILED = "uncompiled"
READY = "ready"
LOADING = "loading"
ACTIVE = "active"
FAILED = "failed"
class ProjectionParams:
"""Only the read interface needed by the projection display."""
def __init__(self, source: Params):
self._source = source
def get(self, *args, **kwargs):
return self._source.get(*args, **kwargs)
def get_bool(self, *args, **kwargs):
return self._source.get_bool(*args, **kwargs)
def get_int(self, *args, **kwargs):
return self._source.get_int(*args, **kwargs)
def get_float(self, *args, **kwargs):
return self._source.get_float(*args, **kwargs)
def get_param_path(self, *args, **kwargs):
return self._source.get_param_path(*args, **kwargs)
class ProjectionPrimeState:
"""Saved pairing label only; projection never starts the API or alert owner."""
def __init__(self, params: ProjectionParams | Params):
self.params = params
def is_paired(self) -> bool:
try:
return int(self.params.get("PrimeType") or -1) >= 0
except (TypeError, ValueError):
return False
def start(self) -> None:
pass
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):
prewarm_cache_contracts()
self.replay_clock = DisplayClockReader() if DisplayClockReader.enabled() else None
self.replay_sample = None
self._replay_state_epoch = None
self.replay_needs_reprime = False
self._replay_reprime_services: set[str] = set()
self.projection_read_only = os.environ.get("STARPILOT_PROJECTION_READ_ONLY") == "1"
params = Params()
self.params = ProjectionParams(params) if self.projection_read_only else params
self.sm = messaging.SubMaster(
[
"modelV2",
"controlsState",
"lateralDelay",
"lateralTorqueParameters",
"onroadEvents",
"extrinsicsCalibration",
"radarState",
"deviceState",
"pandaStates",
"carParams",
"driverMonitoringState",
"carState",
"starpilotCarState",
"driverStateV2",
"narrowRoadCameraState",
"wideRoadCameraState",
"managerState",
"selfdriveState",
"starpilotSelfdriveState",
"starpilotLateralState",
"starpilotNavigation",
"longitudinalPlan",
"gpsLocationExternal",
"gpsLocation",
"navInstruction",
"navRoute",
"carOutput",
"carControl",
"aolAxisState",
"slcState",
"vehicleParameters",
"testJoystick",
"rawAudioData",
*(["customReserved6", "cabinCameraState", "narrowRoadEncodeIdx", "wideRoadEncodeIdx", "cabinEncodeIdx"]
if self.replay_clock is not None else []),
],
ignore_alive=["starpilotSelfdriveState", "starpilotLateralState", "aolAxisState", "starpilotCarState"],
ignore_valid=["starpilotSelfdriveState", "starpilotLateralState", "aolAxisState", "starpilotCarState"],
ignore_avg_freq=["starpilotSelfdriveState", "starpilotLateralState", "aolAxisState", "starpilotCarState"],
)
self.prime_state = ProjectionPrimeState(self.params) if self.replay_clock is not None or isinstance(self.params, ProjectionParams) else PrimeState()
# UI Status tracking
self.status: UIStatus = UIStatus.DISENGAGED
self.started_frame: int = 0
self.started_time: float = 0.0
self._engaged_prev: bool = False
self._started_prev: bool = False
# Core state variables
self.is_metric: bool = self.params.get_bool("IsMetric")
self.initial_cruise_speed: int = self.params.get("InitialCruiseSpeedKph", return_default=True)
self.is_release = self.params.get_bool("IsReleaseBranch")
self.always_on_dm: bool = self.params.get_bool("AlwaysOnDM")
self.experimental_mode: bool = self.params.get_bool("ExperimentalMode")
self.navigation_enabled = self.params.get_bool("NavigationEnabled")
self.nav_destination_set = self.params.get("NavDestination") is not None
self.jetlink_status_feed = None if self.replay_clock is not None else JetlinkStatusFeed()
if self.jetlink_status_feed is not None:
self.jetlink_status_feed.set_enabled(True)
self.chestnut_present: bool = False
self._gpu_artifacts = None if self.replay_clock is not None else ModelManager()
self._gpu_artifacts_at: float | None = None
self.chestnut_compiled: bool = False
self.chestnut_checking: bool = False
self.chestnut_active: bool | None = None
self.chestnut_output_seen: bool = False
self.chestnut_alert: bool = False
self.chestnut_loading: bool = False
self.usb_connected: bool = False
self.usb_connected_ts: float | None = None
self.usb_disconnected_ts: float | None = None
self.usb_unknown: bool = False
self.chestnut_state = ChestnutState.DISCONNECTED
self.started: bool = False
self.ignition: bool = False
self.recording_audio: bool = False
self.panda_type: log.PandaState.PandaType = log.PandaState.PandaType.unknown
self.personality: log.LongitudinalPersonality = log.LongitudinalPersonality.standard
self.has_longitudinal_control: bool = False
self.aol_runtime: bool = False
self.is_body: bool | None = False
self.CP: car.CarParams | None = None
self.light_sensor: float = -1.0
self._params_thread: threading.Thread | None = None
self._projection_params_at: float | None = None
# Callbacks
self._offroad_transition_callbacks: list[Callable[[], None]] = []
self._engaged_transition_callbacks: list[Callable[[], None]] = []
self._on_body_changed_callbacks: list[Callable[[], None]] = []
def add_offroad_transition_callback(self, callback: Callable[[], None]):
self._offroad_transition_callbacks.append(callback)
def add_engaged_transition_callback(self, callback: Callable[[], None]):
self._engaged_transition_callbacks.append(callback)
def add_on_body_changed_callbacks(self, callback: Callable[[], None]):
self._on_body_changed_callbacks.append(callback)
@property
def engaged(self) -> bool:
return self.started and self.sm["selfdriveState"].enabled
def is_onroad(self) -> bool:
return self.started
def is_offroad(self) -> bool:
return not self.started
def update(self) -> None:
if getattr(self, "replay_clock", None) is not None:
before = self.replay_clock.sample()
cache_epoch = getattr(self, "_replay_state_epoch", self.replay_sample.epoch if self.replay_sample is not None else None)
if before.epoch is not None and before.epoch != cache_epoch:
self._reset_replay_state()
self._replay_state_epoch = before.epoch
self.replay_needs_reprime = False
self._replay_reprime_services = set()
self.sm.update(0)
sample = self.replay_clock.sample()
if sample.epoch is not None and sample.epoch != before.epoch:
# A seek straddled this receive. Keep only its eligible new primer, and
# withhold this mixed frame until the next coherent clock sample.
self._replay_reprime_services = {name for name in self.sm.sock if self.sm.updated[name]}
self._reset_replay_state(retain_sample=sample)
self.replay_needs_reprime = not sample.valid
self._replay_state_epoch = sample.epoch
sample = replace(sample, now_ns=None, boot_ns=None, valid=False)
if sample.valid and getattr(self, "replay_needs_reprime", False):
# A later receive can restore the primer without another decoder seek.
for name in self._replay_reprime_services.copy():
target = sample.boot_ns if name in BOOT_TIME_SERVICES else sample.now_ns
stamp = self.sm.logMonoTime[name]
if self.sm.seen[name] and self.sm.valid[name] and type(target) is int and type(stamp) is int and 0 < stamp <= target:
self._replay_reprime_services.remove(name)
self.replay_needs_reprime = bool(self._replay_reprime_services)
self.replay_sample = sample
self.sm.replay_sample = sample
self._update_replay_alive(sample)
self._update_state()
self._update_status()
now = time.monotonic()
if self._projection_params_at is None or now - self._projection_params_at >= 1.0:
self.update_params()
self._projection_params_at = now
self._update_chestnut_state()
return
if self.projection_read_only:
self.sm.update(0)
self._update_state()
self._update_status()
now = time.monotonic()
if self._projection_params_at is None or now < self._projection_params_at or now - self._projection_params_at >= 1.0:
self.update_params()
self._projection_params_at = now
return
self.prime_state.start() # start thread after manager forks ui
if self._params_thread is None:
self._params_thread = threading.Thread(target=self._params_refresh_worker, daemon=True)
self._params_thread.start()
self.sm.update(0)
self._update_state()
self._update_status()
self._update_chestnut_state()
device.update()
def display_time_ns(self) -> int:
if getattr(self, "replay_clock", None) is not None:
sample = self.replay_sample
return sample.now_ns if sample is not None and sample.valid and sample.now_ns is not None else 0
return time.monotonic_ns()
def _update_replay_alive(self, sample) -> None:
# Receipt timestamps stay on the host clock. Logical freshness follows the
# recorded clock, including a paused primer and Panda's boot-time domain.
for service in self.sm.static_freq_services:
target = sample.boot_ns if service in BOOT_TIME_SERVICES else sample.now_ns
stamp = self.sm.logMonoTime[service]
spec = SERVICE_LIST[service]
max_age_ns = 10e9 / spec.frequency
if spec.decimation:
# Qlogs sample high-rate messages sparsely. Keep replay transport alive
# through the display window; current_message still enforces its own
# stricter age limit for actions, independently of this UI-only flag.
max_age_ns = max(max_age_ns, min(1e9, 2e9 * spec.decimation / spec.frequency))
self.sm.alive[service] = bool(sample.valid and self.sm.seen[service] and
type(target) is int and type(stamp) is int and
0 < stamp <= target and target - stamp < max_age_ns)
def _reset_replay_state(self, retain_sample=None) -> None:
for service in self.sm.sock:
target = None if retain_sample is None else retain_sample.boot_ns if service in BOOT_TIME_SERVICES else retain_sample.now_ns
stamp = self.sm.logMonoTime[service]
if (retain_sample is not None and retain_sample.valid and self.sm.updated[service] and
type(target) is int and type(stamp) is int and 0 < stamp <= target):
continue
# Do not drain sockets: the new epoch's paused primer may already be queued.
for field in ("seen", "updated", "alive", "valid"):
getattr(self.sm, field)[service] = False
for field in ("recv_frame", "recv_time", "logMonoTime"):
getattr(self.sm, field)[service] = 0
try:
empty = messaging.new_message(service)
except capnp.lib.capnp.KjException:
empty = messaging.new_message(service, 0)
self.sm.data[service] = getattr(empty.as_reader(), service)
self.started = self._started_prev = self._engaged_prev = False
self.started_frame = max(0, self.sm.frame - 1)
self.CP = None
self.has_longitudinal_control = False
self.aol_runtime = False
self.ignition = False
self.light_sensor = -1.0
self._projection_params_at = None
self.chestnut_output_seen = False
self.chestnut_present = False
self.status = UIStatus.DISENGAGED
def _params_refresh_worker(self):
drop_realtime()
while True:
self.update_params()
time.sleep(PARAM_UPDATE_TIME)
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 not self.sm.alive["pandaStates"]:
self.panda_type = log.PandaState.PandaType.unknown
# Handle wide road camera state updates
if self.sm.updated["wideRoadCameraState"]:
cam_state = self.sm["wideRoadCameraState"]
self.light_sensor = max(100.0 - cam_state.exposureValPercent, 0.0)
elif not self.sm.alive["wideRoadCameraState"] or not self.sm.valid["wideRoadCameraState"]:
self.light_sensor = -1
# hardwared owns the pipeline transition; raw ignition can arrive separately.
self.started = self.sm["deviceState"].started
# Update body state
if self.CP is not None and self.is_body != self.CP.notCar:
self.is_body = self.CP.notCar
for callback in self._on_body_changed_callbacks:
callback()
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:
for callback in self._engaged_transition_callbacks:
gui_app.measure_frame_phase("transition", callback)
self._engaged_prev = self.engaged
# Handle onroad/offroad transition
if self.started != self._started_prev or self.sm.frame == 1:
if self.started:
replay = getattr(self, "replay_clock", None) is not None
if not replay:
self.status = UIStatus.DISENGAGED
self.chestnut_output_seen = False
# A paused replay publishes one atomic primer; its same-frame receipts
# are eligible. Live transitions retain their stricter receive boundary.
self.started_frame = max(0, self.sm.frame - 1) if replay else self.sm.frame
self.started_time = time.monotonic()
self.chestnut_present = self.sm["deviceState"].chestnutPresent or (self.chestnut_compiled and self.usb_connected)
for callback in self._offroad_transition_callbacks:
gui_app.measure_frame_phase("transition", callback)
self._started_prev = self.started
if (self.started and self.sm.updated["selfdriveState"] and self.sm.valid["selfdriveState"] and
self.sm.alive["selfdriveState"] and self.sm.recv_frame["selfdriveState"] > self.started_frame):
stamp = self.sm.logMonoTime["selfdriveState"]
if 0 < stamp <= UIState.display_time_ns(self) <= stamp + 250_000_000:
self.personality = log.LongitudinalPersonality.schema.enumerants[self.sm["selfdriveState"].personality]
def _update_chestnut_state(self) -> None:
detected = self.sm["deviceState"].chestnutPresent
feed = getattr(self, "jetlink_status_feed", None)
jetlink = feed.snapshot() if feed is not None and getattr(self, "replay_clock", None) is None and not detected else None
if jetlink is not None and jetlink.state != "off" and not self.started:
self.chestnut_present = jetlink.state not in ("disconnected", "unavailable")
self.chestnut_state = (ChestnutState.DISCONNECTED if jetlink.state == "disconnected" else
ChestnutState.FAILED if jetlink.state == "unavailable" else
ChestnutState.READY if jetlink.state in ("ready", "active") else ChestnutState.LOADING)
return
if not self.started:
self.chestnut_present = detected
self.chestnut_state = (ChestnutState.FAILED if self.chestnut_alert else
ChestnutState.DISCONNECTED if not detected else
ChestnutState.LOADING if self.chestnut_checking else
ChestnutState.READY if self.chestnut_compiled else ChestnutState.UNCOMPILED)
return
model_seen = self.sm.recv_frame["modelV2"] > self.started_frame
model_stamp = self.sm.logMonoTime["modelV2"]
model_fresh = (model_seen and self.sm.valid["modelV2"] and self.sm.alive["modelV2"] and
0 < model_stamp <= UIState.display_time_ns(self) <= model_stamp + 250_000_000)
if jetlink is not None and jetlink.state != "off":
self.chestnut_present = jetlink.state not in ("disconnected", "unavailable")
if jetlink.state == "active" and model_fresh and self.sm["modelV2"].big:
self.chestnut_output_seen = True
self.chestnut_state = ChestnutState.ACTIVE
elif jetlink.state == "disconnected":
self.chestnut_state = ChestnutState.DISCONNECTED
elif jetlink.state == "unavailable" or self.chestnut_output_seen:
self.chestnut_state = ChestnutState.FAILED
else:
self.chestnut_state = ChestnutState.LOADING
return
if getattr(self, "replay_clock", None) is not None:
self.chestnut_present = detected
if model_fresh and self.sm["modelV2"].big:
self.chestnut_output_seen = True
if not self.chestnut_present:
self.chestnut_state = ChestnutState.DISCONNECTED
elif detected and model_fresh and self.sm["modelV2"].big:
self.chestnut_state = ChestnutState.ACTIVE
elif model_fresh and not self.sm["modelV2"].big:
self.chestnut_state = ChestnutState.FAILED
elif not self.chestnut_output_seen and self.chestnut_loading:
self.chestnut_state = ChestnutState.LOADING
elif not detected:
self.chestnut_state = ChestnutState.FAILED
elif not self.chestnut_output_seen and (self.chestnut_loading or not model_seen or self.chestnut_active is not False):
self.chestnut_state = ChestnutState.LOADING
else:
self.chestnut_state = ChestnutState.FAILED
def update_params(self) -> None:
# For slower operations
# Update longitudinal control state
CP_bytes = get_cache(self.params, "CarParamsPersistent")
if (getattr(self, "replay_clock", None) is not None and
self.sm.seen["carParams"] and self.sm.valid["carParams"]):
self.CP = self.sm["carParams"]
self.has_longitudinal_control = self.CP.openpilotLongitudinalControl
elif CP_bytes is not None:
self.CP = messaging.log_from_bytes(CP_bytes, car.CarParams)
# The saved Alpha Long choice can survive an unavailable configuration.
self.has_longitudinal_control = self.CP.openpilotLongitudinalControl
else:
self.CP = None
self.has_longitudinal_control = False
self.aol_runtime = bool(self.CP is not None and feature_enabled(self.params, self.CP, "aol", os.environ))
self.recording_audio = self.params.get_bool("RecordAudio") and self.started
self.is_metric = self.params.get_bool("IsMetric")
self.initial_cruise_speed = self.params.get("InitialCruiseSpeedKph", return_default=True)
self.always_on_dm = self.params.get_bool("AlwaysOnDM")
self.experimental_mode = self.params.get_bool("ExperimentalMode")
self.navigation_enabled = self.params.get_bool("NavigationEnabled")
self.nav_destination_set = self.params.get("NavDestination") is not None
raw_chestnut = self.params.get("ChestnutActive")
self.chestnut_active = raw_chestnut if type(raw_chestnut) is bool else (
True if raw_chestnut in (b"1", "1") else False if raw_chestnut in (b"0", "0") else None)
self.chestnut_loading = self.params.get_bool("ChestnutLoading")
self.chestnut_alert = any(self.params.get(k) is not None for k, a in OFFROAD_ALERTS.items()
if k.startswith("Offroad_Chestnut") and a["severity"] >= 0)
if getattr(self, "replay_clock", None) is not None:
self.chestnut_compiled = self.chestnut_active is True or self.chestnut_output_seen
return
now = time.monotonic()
if not self.started and (self._gpu_artifacts_at is None or now < self._gpu_artifacts_at or now - self._gpu_artifacts_at >= 1.):
self.chestnut_compiled, self.chestnut_checking = self._gpu_artifacts.selected_gpu_status()
self._gpu_artifacts_at = now
if cable_connected():
self.usb_disconnected_ts = None
if not self.usb_connected:
self.usb_connected = True
self.usb_connected_ts = now
self.usb_unknown = False
elif self.usb_connected_ts is not None and now - self.usb_connected_ts > 10.:
self.usb_unknown = not any(is_chestnut_usb_id(d["vendorId"], d["productId"], True) for d in get_usb_state())
self.usb_connected_ts = None
elif self.usb_connected:
if self.usb_disconnected_ts is None:
self.usb_disconnected_ts = now
elif now - self.usb_disconnected_ts > PARAM_UPDATE_TIME:
self.usb_connected = False
self.usb_connected_ts = None
self.usb_unknown = False
class Device:
def __init__(self):
self._ignition = False
self._interaction_time: float = -1
self._override_interactive_timeout: int | None = None
self._interactive_timeout_callbacks: list[Callable] = []
self._prev_timed_out = False
self._awake: bool = True
self._offroad_brightness: int = BACKLIGHT_OFFROAD
self._last_brightness: int = 0
self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / gui_app.target_fps)
self._brightness_thread: threading.Thread | None = None
self._brightness_event = threading.Event()
self._brightness_target: int = 0
self._display_preferences = DisplayPreferences(driving_timeout=10 if gui_app.big_ui() else 5)
self._display_refresh_time: float | None = None
self._display_started = False
self._screen_off = False
self._screen_off_started = False
self._wake_touch_active = False
def invalidate_display_preferences(self) -> None:
self._display_refresh_time = None
def _refresh_display_preferences(self) -> None:
now = time.monotonic()
if self._display_refresh_time is not None and 0 <= now - self._display_refresh_time < 1.0:
return
previous = self._display_preferences
self._display_preferences = read_preferences(ui_state.params, large=gui_app.big_ui())
self._display_refresh_time = now
if previous != self._display_preferences and self._interaction_time > 0:
self._reset_interactive_timeout()
@property
def awake(self) -> bool:
return self._awake
def toggle_screen_off(self) -> bool:
if ui_state.projection_read_only or not ui_state.started:
return False
self._screen_off = self._awake
self._screen_off_started = ui_state.started
self._reset_interactive_timeout()
self._set_awake(not self._screen_off)
return True
def set_override_interactive_timeout(self, timeout: int | None) -> None:
# Override the interactive timeout duration temporarily
self._override_interactive_timeout = timeout
self._reset_interactive_timeout()
@property
def interactive_timeout(self) -> int:
if self._override_interactive_timeout is not None:
return self._override_interactive_timeout
if self._display_preferences.enabled:
return self._display_preferences.driving_timeout if ui_state.started else self._display_preferences.parked_timeout
ignition_timeout = 10 if gui_app.big_ui() else 5
return ignition_timeout if ui_state.started else 30
def _reset_interactive_timeout(self) -> None:
self._interaction_time = time.monotonic() + self.interactive_timeout
def add_interactive_timeout_callback(self, callback: Callable):
self._interactive_timeout_callbacks.append(callback)
def update(self):
if ui_state.projection_read_only:
return
self._start_brightness_thread() # start thread after manager forks ui
started_changed = self._display_started != bool(ui_state.started)
if started_changed:
self.invalidate_display_preferences()
self._refresh_display_preferences()
if started_changed:
self._reset_interactive_timeout()
self._display_started = bool(ui_state.started)
# do initial reset
if self._interaction_time <= 0:
self._reset_interactive_timeout()
self._update_wakefulness()
self._update_brightness()
def _start_brightness_thread(self):
if self._brightness_thread is None or not self._brightness_thread.is_alive():
self._brightness_thread = threading.Thread(target=self._brightness_worker, daemon=True)
self._brightness_thread.start()
def _brightness_worker(self):
drop_realtime()
while True:
self._brightness_event.wait()
self._brightness_event.clear()
HARDWARE.set_screen_brightness(self._brightness_target)
def set_offroad_brightness(self, brightness: int | None):
if brightness is None:
brightness = BACKLIGHT_OFFROAD
self._offroad_brightness = min(max(brightness, 0), 100)
def _update_brightness(self):
clipped_brightness = self._offroad_brightness
if ui_state.started and ui_state.light_sensor >= 0:
clipped_brightness = ui_state.light_sensor
# CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm
if clipped_brightness <= 8:
clipped_brightness = clipped_brightness / 903.3
else:
clipped_brightness = ((clipped_brightness + 16.0) / 116.0) ** 3.0
clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [30, 100]))
if self._display_preferences.enabled:
saved = (self._display_preferences.driving_brightness if ui_state.started else
self._display_preferences.parked_brightness)
if saved != AUTO:
clipped_brightness = saved
brightness = round(self._brightness_filter.update(clipped_brightness))
if not self._awake:
brightness = 0
if brightness != self._last_brightness:
self._brightness_target = brightness
self._brightness_event.set()
self._last_brightness = brightness
def _update_wakefulness(self):
# Handle interactive timeout
ignition_just_turned_off = not ui_state.ignition and self._ignition
ignition_changed = self._ignition != ui_state.ignition
self._ignition = ui_state.ignition
touched = any(ev.left_down for ev in gui_app.mouse_events)
if not self._awake and touched:
self._wake_touch_active = True
if (ignition_just_turned_off or (self._display_preferences.enabled and ignition_changed) or
touched):
self._reset_interactive_timeout()
road_changed = ui_state.started != self._screen_off_started
self._screen_off_started = ui_state.started
critical = (ui_state.sm.valid.get('selfdriveState', False) and ui_state.sm.alive.get('selfdriveState', False) and
ui_state.sm['selfdriveState'].alertStatus == log.SelfdriveState.AlertStatus.critical)
if not ui_state.started or road_changed or touched or critical:
self._screen_off = False
if critical:
self._reset_interactive_timeout()
gpu_loading = (ui_state.started and self._display_preferences.keep_screen_on_gpu_loading and ui_state.chestnut_loading)
if gpu_loading:
self._reset_interactive_timeout()
interaction_timeout = time.monotonic() > self._interaction_time
if interaction_timeout and not self._prev_timed_out:
for callback in self._interactive_timeout_callbacks:
callback()
self._prev_timed_out = interaction_timeout
auto_screen_off = ui_state.started and self._display_preferences.turn_off_screen and interaction_timeout and not critical and not gpu_loading
self._set_awake(not self._screen_off and not auto_screen_off and (ui_state.started or not interaction_timeout or PC))
# The entire wake gesture belongs to the display, including its release.
# Widgets were not rendered while asleep, so their touch state can be stale.
if self._wake_touch_active:
if any(ev.left_released for ev in gui_app.mouse_events):
self._wake_touch_active = False
gui_app.mouse_events.clear()
def _set_awake(self, on: bool):
if on != self._awake:
self._awake = on
cloudlog.debug(f"setting display power {int(on)}")
HARDWARE.set_display_power(on)
gui_app.set_should_render(on)
# Global instance
ui_state = UIState()
device = Device()