Files
carrotpilot/selfdrive/carrot/cluster/cluster_live.py
young a80ce3cdbb carrot hud / cluster - turzx 9.2 in (#369)
* add cluster alpha

* add dep

* add dep

* fix usb timeout

* fix usb timeout

* Improve error handling in USB frame transmission

Refactor error handling and input draining in USB frame methods.

* Refactor JPEG rendering logic in main.py

Refactor JPEG rendering to improve readability and add logging.

* Refactor _send_frame_no_ack method

Refactor _send_frame_no_ack method for better readability and structure.

* Update main.py

* Update main.py

* Update cluster_usb_display.py

* Increase NUM_READERS from 25 to 40

* Add center_clock_text attribute to cluster model

* Add replace import from dataclasses

* Update main.py

* Implement center clock drawing in cluster renderer

Added a new method to draw the center clock on the cluster UI.

* Update cluster_renderer.py

* Simplify input draining condition in USB frame method

Refactor input draining logic to improve readability.

* Update main.py

* Update cluster_renderer.py

* Update main.py

* Update cluster_usb_display.py

* Implement performance profiling in cluster rendering

Added profiling for rendering performance metrics in the cluster renderer.

* Update cluster_renderer.py

* Update cluster_renderer.py

* Update cluster_renderer.py

* Update main.py

* Add CLUSTER_PROFILE_RGBA option to README

Added environment variable for RGBA profile to cluster_run.py command.

* fix replay

* fix replay

* add log

* add log

* add log

* fix

* fix

* fix

* fix

* fix

* fix

* performance

* performance

* performance

* performance

* performance

* performance

* performance

* performance

* performance

* performance

* performance

* process

* process

* process

* process

* remove dummy

* fix ui

* fix ui

* fix ui

* fix usb event monitor

* fix usb event monitor

* fix ui

* fix ui

* fix ui apply font

* fix ui apply font

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* fix radar point

* fix radar point

* fix radar point

* fix radar point

* fix radar point

* fix radar point

* fix radar point

* fix radar point

* fix radar point

* fix radar point

* fix radar point

* fix radar point

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* fix ui

* dark mode

* dark mode

* dark mode

* cleanup

* add bsd

* bsd

* lca

* lca

* lca

* lca

* lca

* lca

* lca

* lca

* move speed limit

* profiler

* perfomance

* perfomance

* perfomance

* perfomance

* perfomance

* perfomance

* perfomance

* perfomance

* perfomance

* fps

* perfomance

* params

* monit

* add git info
2026-05-25 18:33:50 +09:00

187 lines
6.4 KiB
Python

from __future__ import annotations
import sys
import time
from pathlib import Path
from typing import Any
from cluster_config import BLUE, DEFAULT_LANE_WIDTH_M
from cluster_models import ClusterUiState, LaneMarking
from cluster_route_replay import RouteLogParser, frame_to_state
from cluster_utils import clamp
def find_openpilot_root(start: Path) -> Path | None:
for path in (start, *start.parents):
if (path / "cereal").exists() and (path / "selfdrive").exists():
return path
nested = path / "openpilot"
if (nested / "cereal").exists() and (nested / "selfdrive").exists():
return nested
return None
OPENPILOT_ROOT = find_openpilot_root(Path(__file__).resolve().parent)
if OPENPILOT_ROOT is not None:
sys.path.insert(0, str(OPENPILOT_ROOT))
LIVE_SERVICES_BASE = (
"carState",
"modelV2",
"radarState",
"liveTracks",
"longitudinalPlan",
"lateralPlan",
"controlsState",
"cameraOdometry",
"drivingModelData",
"navInstruction",
"navInstructionCarrot",
)
LIVE_CAN_SERVICES = ("can",)
class OpenpilotLiveSource:
def __init__(self, include_can: bool = True, timeout_ms: int = 0) -> None:
try:
import cereal.messaging as messaging
except Exception as exc:
raise RuntimeError(
"Openpilot live input requires cereal.messaging. Run from an openpilot environment "
"or use --input route/random for local checks."
) from exc
self.messaging: Any = messaging
self.services = list(LIVE_SERVICES_BASE + (LIVE_CAN_SERVICES if include_can else ()))
self.sm = messaging.SubMaster(self.services)
self.parser = RouteLogParser()
self.timeout_ms = max(0, int(timeout_ms))
self.last_state: ClusterUiState | None = None
self.start_t = time.monotonic()
self.frames = 0
def update(self) -> ClusterUiState:
self.sm.update(self.timeout_ms)
self._update_current_speed()
for service in self.services:
if not self._service_updated(service):
continue
event_t = self._service_time(service)
self._apply_service_update(service, event_t)
if self._service_alive("carState"):
event_t = self._service_time("carState")
frame = self.parser._frame_from_car_state(self.sm["carState"], event_t)
self.last_state = frame_to_state(frame)
self.frames += 1
return self.last_state
self.last_state = standby_state()
return self.last_state
def status_text(self) -> str:
alive = sum(1 for service in self.services if self._service_alive(service))
updated = sum(1 for service in self.services if self._service_updated(service))
can_status = "can" if "can" in self.services else "no-can"
age = time.monotonic() - self.start_t
fps = self.frames / age if age > 0.1 else 0.0
radar_count = len(self.last_state.radar_points) if self.last_state is not None else 0
detected_count = len(self.last_state.detected_vehicles) if self.last_state is not None else 0
return (
f"live {can_status} alive={alive}/{len(self.services)} upd={updated} state={fps:.1f}Hz "
f"radar={radar_count} detected={detected_count}"
)
def close(self) -> None:
return None
def _apply_service_update(self, service: str, event_t: float) -> None:
data = self.sm[service]
if service == "drivingModelData":
self.parser._update_driving_model(data)
elif service == "modelV2":
self.parser._update_model_v2(data, event_t)
elif service == "lateralPlan":
self.parser._update_lateral_plan(data)
elif service in ("navInstruction", "navInstructionCarrot"):
self.parser._update_nav_instruction(data)
elif service == "longitudinalPlan":
self.parser._update_longitudinal_plan(data)
elif service == "controlsState":
self.parser._update_controls_state(data)
elif service == "cameraOdometry":
self.parser._update_camera_odometry(data, self._service_valid(service))
elif service == "radarState":
self.parser._update_radar_state(data, event_t)
elif service == "liveTracks":
self.parser._update_live_tracks(data, event_t)
elif service == "can":
self.parser._update_can_detections(data, event_t)
def _update_current_speed(self) -> None:
if not self._service_alive("carState"):
return
try:
self.parser.current_speed_kph = clamp(float(self.sm["carState"].vEgo) * 3.6, 0.0, 140.0)
except Exception:
return
def _service_time(self, service: str) -> float:
try:
mono_time = self.sm.logMonoTime.get(service, 0)
except AttributeError:
mono_time = 0
return float(mono_time) / 1_000_000_000.0 if mono_time else time.monotonic()
def _service_alive(self, service: str) -> bool:
try:
return bool(self.sm.alive.get(service, False))
except AttributeError:
return False
def _service_updated(self, service: str) -> bool:
try:
return bool(self.sm.updated.get(service, False))
except AttributeError:
return False
def _service_valid(self, service: str) -> bool:
try:
return bool(self.sm.valid.get(service, True))
except AttributeError:
return True
def standby_state() -> ClusterUiState:
return ClusterUiState(
speed_kph=0.0,
accel_mps2=0.0,
steering=0.0,
speed_limit_kph=None,
cruise_kph=None,
cruise_display_state="off",
left_signal=False,
right_signal=False,
left_blindspot=False,
right_blindspot=False,
lane_change=None,
lane_change_phase="idle",
lane_change_progress=0.0,
highlight_lane=None,
highlight_lane_offset=None,
ego_lane_offset=0.0,
road_view_lane_position=0.0,
camera_lane_center_offset_m=None,
lane_width_m=DEFAULT_LANE_WIDTH_M,
steering_angle_deg=None,
surround_yaw_deg=0.0,
surround_pitch_deg=0.0,
surround_view_active=False,
lanes=(
LaneMarking(-0.5, BLUE, "solid", width=7),
LaneMarking(0.5, BLUE, "solid", width=7),
),
)