Files
StarPilot/openpilot/starpilot/ui/onroad_stopped_timer.py
T
Danny 6cfbf1deb8 Add stopped timer to Android Auto and layout previews
Render the stopped timer in projected driving views and preview scenes, with layout migration and runtime coverage. Include the matching prebuilt receipt.
2026-10-11 22:18:22 -07:00

71 lines
2.3 KiB
Python

"""Monotonic, drive-scoped standstill duration for native and Android Auto widgets."""
from dataclasses import dataclass
SHOW_AFTER_DRIVE_NS = 60_000_000_000
SECOND_NS = 1_000_000_000
ENGAGED = (22, 127, 64)
EXPERIMENTAL = (218, 111, 37)
TRAFFIC = (201, 34, 49)
PREVIEW_DURATION_S = 134
def timer_size(profile: str) -> tuple[int, int]:
return (660, 168) if profile == "large" else (440, 112)
@dataclass
class StoppedTimer:
drive_key: tuple[int, int] | None = None
drive_started_ns: int | None = None
standstill_started_ns: int | None = None
last_ns: int | None = None
def reset(self) -> None:
self.drive_key = None
self.drive_started_ns = None
self.standstill_started_ns = None
self.last_ns = None
def step(self, *, now_ns: int, drive_key: tuple[int, int] | None, car_fresh: bool,
standstill: bool, reverse: bool, enabled: bool) -> int | None:
if drive_key is None or (self.last_ns is not None and now_ns < self.last_ns):
self.reset()
if drive_key is None:
return None
self.last_ns = now_ns
if drive_key != self.drive_key:
self.drive_key = drive_key
self.drive_started_ns = now_ns
self.standstill_started_ns = None
if not enabled or not car_fresh or not standstill or reverse:
self.standstill_started_ns = None
return None
if self.standstill_started_ns is None:
self.standstill_started_ns = now_ns
if self.drive_started_ns is None or now_ns - self.drive_started_ns < SHOW_AFTER_DRIVE_NS:
return None
seconds = (now_ns - self.standstill_started_ns) // SECOND_NS
return seconds if seconds > 0 else None
def duration_text(seconds: int) -> tuple[str, str]:
minutes, remainder = divmod(seconds, 60)
return (f"{minutes} minute{'s' if minutes != 1 else ''}",
f"{remainder} second{'s' if remainder != 1 else ''}")
def duration_color(seconds: int) -> tuple[int, int, int]:
if seconds < 60:
return ENGAGED
if seconds < 150:
start, end, amount = ENGAGED, EXPERIMENTAL, (seconds - 60) / 90
elif seconds < 300:
start, end, amount = EXPERIMENTAL, TRAFFIC, (seconds - 150) / 150
else:
return TRAFFIC
return (int(start[0] + amount * (end[0] - start[0])),
int(start[1] + amount * (end[1] - start[1])),
int(start[2] + amount * (end[2] - start[2])))