mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-10 20:02:13 +08:00
187 lines
6.9 KiB
Python
187 lines
6.9 KiB
Python
import time
|
|
import pyray as rl
|
|
from openpilot.common.params import Params
|
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
|
from openpilot.system.ui.widgets import Widget
|
|
|
|
METRIC_HEIGHT = 126
|
|
METRIC_WIDTH = 275
|
|
SIDEBAR_WIDTH = 300
|
|
FONT_SIZE = 32
|
|
|
|
|
|
def _fmt(v, decimals=2):
|
|
return f"{v:.{decimals}f}" if isinstance(v, float) else str(v)
|
|
|
|
|
|
class DeveloperSidebar(Widget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._params = Params()
|
|
self._font_bold = gui_app.font(FontWeight.SEMI_BOLD)
|
|
self._font_normal = gui_app.font(FontWeight.NORMAL)
|
|
|
|
# Engagement tracking
|
|
self._lat_time = 0.0
|
|
self._long_time = 0.0
|
|
self._total_time = 0.0
|
|
self._last_update_time: float = time.monotonic()
|
|
|
|
# Peak tracking
|
|
self._max_steer_angle = 0.0
|
|
self._max_torque = 0.0
|
|
|
|
def _update_state(self):
|
|
sm = ui_state.sm
|
|
|
|
now = time.monotonic()
|
|
dt = now - self._last_update_time
|
|
self._last_update_time = now
|
|
|
|
# Engagement time tracking (only when onroad and not standstill)
|
|
if ui_state.started and sm.valid.get("carControl", False) and sm.valid.get("carState", False):
|
|
cc = sm["carControl"]
|
|
cs = sm["carState"]
|
|
if not cs.standstill and cs.gearShifter != 3: # not reverse
|
|
self._total_time += dt
|
|
if cc.latActive:
|
|
self._lat_time += dt
|
|
if cc.longActive:
|
|
self._long_time += dt
|
|
|
|
# Peak tracking
|
|
if sm.valid.get("carControl", False):
|
|
torque = abs(sm["carControl"].actuators.torque)
|
|
if torque > self._max_torque:
|
|
self._max_torque = torque
|
|
if sm.valid.get("carState", False):
|
|
angle = abs(sm["carState"].steeringAngleDeg)
|
|
if angle > self._max_steer_angle:
|
|
self._max_steer_angle = angle
|
|
|
|
def reset_engagement(self):
|
|
self._lat_time = 0.0
|
|
self._long_time = 0.0
|
|
self._total_time = 0.0
|
|
self._max_steer_angle = 0.0
|
|
self._max_torque = 0.0
|
|
|
|
def _get_metric_value(self, metric_id: int) -> tuple[str, str]:
|
|
"""Return (label, value_string) for a metric ID (1-16)."""
|
|
sm = ui_state.sm
|
|
is_metric = ui_state.is_metric
|
|
conv = 1.0 if is_metric else 3.281 # m/s^2 to ft/s^2
|
|
|
|
if metric_id == 1: # ACCEL
|
|
a = sm["carState"].aEgo * conv if sm.valid.get("carState", False) else 0
|
|
return ("ACCEL", f"{a:.2f}")
|
|
elif metric_id == 2: # MAX ACCEL
|
|
return ("MAX ACCEL", "-")
|
|
elif metric_id == 3: # STEER DELAY
|
|
d = sm["liveDelay"].lateralDelay if sm.valid.get("liveDelay", False) else 0
|
|
return ("STEER DELAY", f"{d:.5f}")
|
|
elif metric_id == 4: # FRICTION
|
|
f = sm["liveTorqueParameters"].frictionCoefficientFiltered if sm.valid.get("liveTorqueParameters", False) else 0
|
|
return ("FRICTION", f"{f:.5f}")
|
|
elif metric_id == 5: # LAT ACCEL
|
|
l = sm["liveTorqueParameters"].latAccelFactorFiltered if sm.valid.get("liveTorqueParameters", False) else 0
|
|
return ("LAT ACCEL", f"{l:.5f}")
|
|
elif metric_id == 6: # STEER RATIO
|
|
r = sm["liveParameters"].steerRatio if sm.valid.get("liveParameters", False) else 0
|
|
return ("STEER RATIO", f"{r:.5f}")
|
|
elif metric_id == 7: # STEER STIFF
|
|
s = sm["liveParameters"].stiffnessFactor if sm.valid.get("liveParameters", False) else 0
|
|
return ("STEER STIFF", f"{s:.5f}")
|
|
elif metric_id == 8: # LATERAL %
|
|
pct = self._lat_time * 100 / max(self._total_time, 1)
|
|
return ("LATERAL %", f"{pct:.2f}%")
|
|
elif metric_id == 9: # LONG %
|
|
pct = self._long_time * 100 / max(self._total_time, 1)
|
|
return ("LONG %", f"{pct:.2f}%")
|
|
elif metric_id == 10: # STEER ANGLE
|
|
if sm.valid.get("carState", False):
|
|
angle = sm["carState"].steeringAngleDeg
|
|
if abs(self._max_torque) >= 50:
|
|
return ("STEER ANGLE", f"{angle:.0f} - ({self._max_steer_angle:.0f})")
|
|
return ("STEER ANGLE", f"{angle:.0f}")
|
|
return ("STEER ANGLE", "-")
|
|
elif metric_id == 11: # TORQUE %
|
|
if sm.valid.get("carControl", False):
|
|
torque = sm["carControl"].actuators.torque
|
|
if abs(torque) >= 50:
|
|
return ("TORQUE %", f"{torque:.0f} - ({self._max_torque:.0f})")
|
|
return ("TORQUE %", f"{torque:.0f}")
|
|
return ("TORQUE %", "-")
|
|
elif metric_id == 12: # ACT ACCEL
|
|
a = sm["carControl"].actuators.accel * conv if sm.valid.get("carControl", False) else 0
|
|
return ("ACT ACCEL", f"{a:.2f}")
|
|
elif metric_id == 13: # DANGER %
|
|
if sm.valid.get("frogpilotPlan", False):
|
|
d = sm["frogpilotPlan"].dangerFactor * 100
|
|
return ("DANGER %", f"{d:.2f}%")
|
|
return ("DANGER %", "-")
|
|
elif metric_id == 14: # ACCEL JERK
|
|
j = sm["frogpilotPlan"].accelerationJerk if sm.valid.get("frogpilotPlan", False) else 0
|
|
return ("ACCEL JERK", f"{j}")
|
|
elif metric_id == 15: # DANGER JERK
|
|
j = sm["frogpilotPlan"].dangerJerk if sm.valid.get("frogpilotPlan", False) else 0
|
|
return ("DANGER JERK", f"{j}")
|
|
elif metric_id == 16: # SPEED JERK
|
|
j = sm["frogpilotPlan"].speedJerk if sm.valid.get("frogpilotPlan", False) else 0
|
|
return ("SPEED JERK", f"{j}")
|
|
return ("", "")
|
|
|
|
def _render(self, rect: rl.Rectangle):
|
|
t = ui_state.frogpilot_toggles
|
|
if not t.get("developer_sidebar", False):
|
|
return
|
|
|
|
# Read slot assignments (1-7 map to metric IDs 1-16, 0 = empty)
|
|
slots = []
|
|
for i in range(1, 8):
|
|
mid = t.get(f"developer_sidebar_metric{i}", 0)
|
|
if mid and 1 <= mid <= 16:
|
|
slots.append(mid)
|
|
|
|
if not slots:
|
|
return
|
|
|
|
count = len(slots)
|
|
spacing = (rect.height - count * METRIC_HEIGHT) / max(count + 1, 1)
|
|
y = rect.y + spacing
|
|
custom_color = t.get("sidebar_color1", None)
|
|
|
|
for mid in slots:
|
|
label, value = self._get_metric_value(mid)
|
|
if not label:
|
|
continue
|
|
self._draw_metric(rect.x + 12, y, METRIC_WIDTH, METRIC_HEIGHT, label, value, custom_color)
|
|
y += METRIC_HEIGHT + spacing
|
|
|
|
def _draw_metric(self, x, y, w, h, label, value, custom_color=None):
|
|
metric_rect = rl.Rectangle(x, y, w, h)
|
|
|
|
# Accent bar on right side (like QT developer sidebar)
|
|
accent_color = rl.Color(0x17, 0x86, 0x44, 255) # Green default
|
|
if custom_color:
|
|
s = custom_color.lstrip('#')
|
|
if len(s) >= 6:
|
|
accent_color = rl.Color(int(s[-6:-4], 16), int(s[-4:-2], 16), int(s[-2:], 16), 255)
|
|
|
|
edge_rect = rl.Rectangle(x + w - 100, y + 4, 100, h - 8)
|
|
rl.begin_scissor_mode(int(x + w - 18), int(y), 18, int(h))
|
|
rl.draw_rectangle_rounded(edge_rect, 0.3, 10, accent_color)
|
|
rl.end_scissor_mode()
|
|
|
|
# Border
|
|
rl.draw_rectangle_rounded_lines_ex(metric_rect, 0.3, 10, 2, rl.Color(255, 255, 255, 85))
|
|
|
|
# Text
|
|
text = f"{label}\n{value}"
|
|
ts = measure_text_cached(self._font_bold, text, FONT_SIZE)
|
|
text_x = x + 22 + (w - 22 - ts.x) / 2
|
|
text_y = y + (h - ts.y) / 2
|
|
rl.draw_text_ex(self._font_bold, text, rl.Vector2(text_x, text_y), FONT_SIZE, 0, rl.WHITE)
|