mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
153 lines
4.5 KiB
Python
153 lines
4.5 KiB
Python
"""Immutable Home presentation data and explicit input actions."""
|
|
|
|
from dataclasses import dataclass
|
|
from enum import StrEnum
|
|
from collections.abc import Callable
|
|
|
|
from openpilot.starpilot.ui.presentation import Profile
|
|
|
|
|
|
class HomeMode(StrEnum):
|
|
CHILL = "chill"
|
|
EXPERIMENTAL = "experimental"
|
|
CONDITIONAL_EXPERIMENTAL = "conditional_experimental"
|
|
CONDITIONAL_CHILL = "conditional_chill"
|
|
|
|
|
|
class HomeActionKind(StrEnum):
|
|
OPEN_SETTINGS = "open_settings"
|
|
OPEN_TOGGLES = "open_toggles"
|
|
OPEN_PAIRING = "open_pairing"
|
|
SET_EXPERIMENTAL = "set_experimental"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DriveSummary:
|
|
drives: int = 0
|
|
distance: float = 0.0
|
|
hours: float = 0.0
|
|
unit: str = "miles"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DailyDistance:
|
|
label: str
|
|
distance: float = 0.0
|
|
is_today: bool = False
|
|
is_future: bool = False
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PersonalRecord:
|
|
title: str
|
|
value: str
|
|
detail: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DriveStatsData:
|
|
all_time: DriveSummary
|
|
past_week: DriveSummary
|
|
this_week: DriveSummary
|
|
daily_distance: tuple[DailyDistance, ...]
|
|
records: tuple[PersonalRecord, ...]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class HomeState:
|
|
version: str
|
|
commit: str
|
|
commit_date: str
|
|
model_label: str
|
|
description: str
|
|
mode: HomeMode
|
|
experimental_enabled: bool
|
|
experimental_available: bool
|
|
stats: DriveStatsData | None
|
|
paired: bool = True
|
|
network: str = "none"
|
|
network_strength: int = 0
|
|
temperature_c: float | None = None
|
|
vehicle_online: bool = True
|
|
connection: str = "OFFLINE"
|
|
bluetooth: str = ""
|
|
gpu_present: bool = False
|
|
gpu_active: bool = False
|
|
recording_audio: bool = False
|
|
branch: str = ""
|
|
gpu_state: str = "disconnected"
|
|
metric: bool = False
|
|
temperature_high: bool = False
|
|
jetlink: str = "off"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class HomeAction:
|
|
kind: HomeActionKind
|
|
value: bool | None = None
|
|
|
|
|
|
class HomeInput:
|
|
"""Dispatch touch gestures without writing settings or accessing services."""
|
|
|
|
def __init__(self, profile: Profile, emit: Callable[[HomeAction], None]):
|
|
self.profile = profile
|
|
self.emit = emit
|
|
self._press: tuple[float, float, float] | None = None
|
|
self._long_press_handled = False
|
|
self._hold_started: float | None = None
|
|
self._inside = False
|
|
|
|
def press(self, x: float, y: float, timestamp: float) -> None:
|
|
width, height = self.profile.size
|
|
self._press = (x, y, timestamp) if 0 <= x < width and 0 <= y < height else None
|
|
self._long_press_handled = False
|
|
self._inside = self._press is not None
|
|
self._hold_started = timestamp if self._inside else None
|
|
|
|
def move(self, x: float, y: float, timestamp: float) -> None:
|
|
width, height = self.profile.size
|
|
inside = 0 <= x < width and 0 <= y < height
|
|
if self._press is not None and self.profile == Profile.COMPACT:
|
|
if not inside:
|
|
self._hold_started = None
|
|
self._long_press_handled = False
|
|
elif not self._inside:
|
|
self._hold_started = timestamp
|
|
self._inside = inside
|
|
|
|
def tick(self, timestamp: float, state: HomeState) -> None:
|
|
if self.profile == Profile.COMPACT and self._hold_started is not None and not self._long_press_handled:
|
|
if timestamp - self._hold_started > 0.5:
|
|
self._long_press_handled = True
|
|
if state.experimental_available:
|
|
self.emit(HomeAction(HomeActionKind.SET_EXPERIMENTAL, not state.experimental_enabled))
|
|
|
|
def release(self, x: float, y: float, timestamp: float, state: HomeState) -> None:
|
|
if self._press is None:
|
|
return
|
|
self.move(x, y, timestamp)
|
|
self.tick(timestamp, state)
|
|
px, py, _ = self._press
|
|
self._press = None
|
|
self._hold_started = None
|
|
if self._long_press_handled:
|
|
return
|
|
if self.profile == Profile.COMPACT:
|
|
if 0 <= x < 536 and 0 <= y < 240:
|
|
self.emit(HomeAction(HomeActionKind.OPEN_SETTINGS))
|
|
elif px < 300 and 50 <= x <= 250 and 35 <= y <= 152:
|
|
self.emit(HomeAction(HomeActionKind.OPEN_SETTINGS))
|
|
elif px < 300 and state.recording_audio and 170 <= x <= 245 and 245 <= y <= 285:
|
|
self.emit(HomeAction(HomeActionKind.OPEN_TOGGLES))
|
|
elif 1370 <= px <= 2120 and 145 <= py <= 270 and 1370 <= x <= 2120 and 145 <= y <= 270:
|
|
self.emit(HomeAction(HomeActionKind.OPEN_TOGGLES))
|
|
elif not state.paired and 1370 <= px <= 2120 and 300 <= py <= 430 and 1370 <= x <= 2120 and 300 <= y <= 430:
|
|
self.emit(HomeAction(HomeActionKind.OPEN_PAIRING))
|
|
|
|
def cancel(self) -> None:
|
|
self._press = None
|
|
self._long_press_handled = False
|
|
self._hold_started = None
|
|
self._inside = False
|