mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
131 lines
4.5 KiB
Python
131 lines
4.5 KiB
Python
"""Read-only Jetlink status for model feedback and Android Auto, polled off the drawing thread."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import json
|
|
import math
|
|
import os
|
|
import threading
|
|
import time
|
|
|
|
STALE_NS = 2_500_000_000
|
|
# The host's processing time for the last big-model frame (excludes USB); jetlink's runtime record does not carry it
|
|
LATENCY_RECORD = "/dev/shm/jetlink/starpilot_latency.json"
|
|
LATENCY_FRESH_S = 3.
|
|
RUNNING = {'ready': 'ready', 'joining': 'connecting', 'retrying': 'connecting'}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class JetlinkStatus:
|
|
device: str = ''
|
|
state: str = 'off' # off, disconnected, connected, connecting, ready, active, unavailable
|
|
latency_ms: float | None = None
|
|
port: str | None = None # jetlink's port_state: 'connected' (host on the link), 'device' (plugged in), else None
|
|
|
|
|
|
def _write_latency(model, path) -> None:
|
|
timings = getattr(getattr(model, "client", None), "last_timings", None) # the big model's client while it drives
|
|
total_us = timings[2] if isinstance(timings, (tuple, list)) and len(timings) == 3 else 0
|
|
record = {"at": time.monotonic(), "server_ms": total_us / 1000 if type(total_us) in (int, float) and total_us > 0 else None}
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
tmp = f"{path}.tmp"
|
|
with open(tmp, "w") as f:
|
|
json.dump(record, f)
|
|
os.replace(tmp, path)
|
|
|
|
|
|
def start_latency_writer(model, path=LATENCY_RECORD, period=1.):
|
|
"""modeld, before it goes realtime, so the thread keeps normal priority and the frame loop does no file IO."""
|
|
def run():
|
|
while True:
|
|
try:
|
|
_write_latency(model, path)
|
|
except Exception:
|
|
pass
|
|
time.sleep(period)
|
|
thread = threading.Thread(target=run, name="jetlink-latency", daemon=True)
|
|
thread.start()
|
|
return thread
|
|
|
|
|
|
def read_latency(path=LATENCY_RECORD) -> float | None:
|
|
try:
|
|
with open(path) as f:
|
|
record = json.load(f)
|
|
ms, at = record.get("server_ms"), record.get("at")
|
|
fresh = type(at) in (int, float) and 0 <= time.monotonic() - at < LATENCY_FRESH_S
|
|
return float(ms) if fresh and type(ms) in (int, float) and math.isfinite(ms) and ms > 0 else None
|
|
except (OSError, ValueError, AttributeError):
|
|
return None
|
|
|
|
|
|
def read_status() -> JetlinkStatus:
|
|
from openpilot.starpilot.models.jetlink_adapter import hooks, link_on
|
|
if not link_on():
|
|
return JetlinkStatus() # one file read: nothing of jetlink's loads
|
|
status = hooks().status()
|
|
if status is None:
|
|
return JetlinkStatus(state='unavailable')
|
|
if not status.enabled:
|
|
return JetlinkStatus() # a Chestnut is fitted
|
|
device = 'iPhone' if status.mode == 'ios' else 'Computer'
|
|
port = status.port_state if status.port_state in ('connected', 'device') else None
|
|
if not status.present:
|
|
return JetlinkStatus(device, 'disconnected', port=port)
|
|
if status.active:
|
|
return JetlinkStatus(device, 'active', read_latency(), port)
|
|
if status.reason:
|
|
return JetlinkStatus(device, 'unavailable', port=port)
|
|
return JetlinkStatus(device, RUNNING.get(status.running, 'ready' if status.ready else 'connected'), port=port)
|
|
|
|
|
|
class JetlinkStatusFeed:
|
|
"""Polls once a second only while enabled; snapshot() never does file IO."""
|
|
def __init__(self, read=read_status, period=1.0):
|
|
self.read, self.period = read, period
|
|
self._lock = threading.Lock()
|
|
self._stop = threading.Event()
|
|
self._thread = None
|
|
self._enabled = False
|
|
self._status = JetlinkStatus()
|
|
self._read_at = 0
|
|
|
|
def set_enabled(self, enabled):
|
|
with self._lock:
|
|
self._enabled = enabled
|
|
if not enabled:
|
|
self._status = JetlinkStatus()
|
|
if enabled and self._thread is None:
|
|
self._thread = threading.Thread(target=self._run, name='jetlink-status', daemon=True)
|
|
self._thread.start()
|
|
|
|
def poll(self):
|
|
with self._lock:
|
|
if not self._enabled:
|
|
return
|
|
try:
|
|
status = self.read()
|
|
except Exception:
|
|
status = JetlinkStatus(state='unavailable')
|
|
with self._lock:
|
|
if self._enabled:
|
|
self._status = status
|
|
self._read_at = time.monotonic_ns()
|
|
|
|
def snapshot(self):
|
|
with self._lock:
|
|
status, read_at = self._status, self._read_at
|
|
if status.state != 'off' and time.monotonic_ns() - read_at > STALE_NS:
|
|
return JetlinkStatus(status.device, 'unavailable')
|
|
return status
|
|
|
|
def _run(self):
|
|
while not self._stop.is_set():
|
|
self.poll()
|
|
self._stop.wait(self.period)
|
|
|
|
def close(self):
|
|
self._stop.set()
|
|
if self._thread is not None:
|
|
self._thread.join(timeout=2)
|