This commit is contained in:
firestar5683
2026-06-27 22:58:38 -05:00
parent 0df2444e95
commit a8ec45ec12
5 changed files with 184 additions and 18 deletions
+31 -7
View File
@@ -27,13 +27,14 @@ env_var_truthy() {
usage() {
cat <<'EOF'
Usage:
./onroad [jobs] (--c3 | --c4 | --raybig | --all | --replay-only) [-nav] [--mici-widget-demo] [--prefix name] <route-or-replay-args...>
./onroad [jobs] (--c3 | --c4 | --raybig | --all | --replay-only) [-nav] [--cem] [--prefix name] <route-or-replay-args...>
Examples:
./onroad --c3 <route>
./onroad --c4 <route> --start 30
./onroad --c4 -nav <route>
./onroad --c4 --mici-widget-demo --demo
./onroad --c4 --cem --demo
./onroad --raybig --cem --demo
./onroad --all <route>
./onroad --replay-only --demo --no-vipc --no-loop
@@ -42,7 +43,7 @@ Notes:
- A private comma connect route still requires tools/lib/auth.py before replay can download it.
- Use multiple UI flags together if you want more than one desktop UI at once.
- -nav injects a fake navigation demo stream and blocks replay from publishing navInstruction/navRoute.
- --mici-widget-demo makes the small C4 sidebar cycle through widget states for visual review.
- --cem publishes fake CEM statuses for desktop visual review in the raylib UIs.
EOF
}
@@ -58,9 +59,10 @@ UI_TARGETS=()
LEGACY_UI_SELECTION=""
REPLAY_ONLY=0
NAV_DEMO=0
MICI_WIDGET_DEMO=0
CEM_DEMO=0
REPLAY_PID=""
NAV_PID=""
CEM_PID=""
UI_PIDS=()
parse_args() {
@@ -94,8 +96,8 @@ parse_args() {
NAV_DEMO=1
shift
;;
--mici-widget-demo|--widget-demo)
MICI_WIDGET_DEMO=1
--cem|--mici-widget-demo|--widget-demo)
CEM_DEMO=1
shift
;;
--ui)
@@ -212,6 +214,9 @@ cleanup() {
if [[ -n "${NAV_PID}" ]]; then
kill "${NAV_PID}" >/dev/null 2>&1 || true
fi
if [[ -n "${CEM_PID}" ]]; then
kill "${CEM_PID}" >/dev/null 2>&1 || true
fi
for pid in "${UI_PIDS[@]-}"; do
if [[ -n "${pid}" ]]; then
@@ -224,6 +229,9 @@ cleanup() {
if [[ -n "${NAV_PID}" ]]; then
wait "${NAV_PID}" >/dev/null 2>&1 || true
fi
if [[ -n "${CEM_PID}" ]]; then
wait "${CEM_PID}" >/dev/null 2>&1 || true
fi
if [[ -n "${OPENPILOT_PREFIX:-}" && "${OPENPILOT_PREFIX}" == desktop-onroad-* ]]; then
echo "Cleaning up temporary prefix environment (${OPENPILOT_PREFIX})..."
@@ -322,7 +330,7 @@ prepare_env() {
export SP_RAYBIG_FAKE_WIFI=0
export SP_ALLOW_DESKTOP_FAKE_WIFI=0
export SP_ONROAD_NAV_DEMO="${NAV_DEMO}"
export SP_MICI_WIDGET_DEMO="${MICI_WIDGET_DEMO}"
export SP_CEM_DEMO="${CEM_DEMO}"
if [[ "$(uname -s)" == "Darwin" ]] || env_var_truthy "${ZMQ:-0}"; then
export OPENPILOT_ZMQ_NAMESPACE="${PREFIX_ARG:-${OPENPILOT_ZMQ_NAMESPACE:-desktop-onroad-$$}}"
@@ -403,6 +411,18 @@ launch_nav_demo() {
fi
}
launch_cem_demo() {
echo "Starting fake CEM demo publisher..."
"${ROOT_DIR}/.venv/bin/python3" "${ROOT_DIR}/tools/replay/fake_cem_demo.py" &
CEM_PID=$!
sleep 0.5
if ! kill -0 "${CEM_PID}" >/dev/null 2>&1; then
wait "${CEM_PID}"
return 1
fi
}
launch_c3_ui() {
local os_ext="linux"
if [[ "$(uname -s)" == "Darwin" ]]; then
@@ -496,6 +516,10 @@ if [[ "${NAV_DEMO}" == "1" ]]; then
launch_nav_demo
fi
if [[ "${CEM_DEMO}" == "1" ]]; then
launch_cem_demo
fi
if [[ ${#UI_TARGETS[@]} -eq 0 ]]; then
echo "Replay is running without UI windows. Press Ctrl-C to stop."
wait "${REPLAY_PID}"
+23 -10
View File
@@ -84,7 +84,7 @@ class MiciSidebarWidgets(Widget):
self._confidence_ball = confidence_ball
self._font_bold = gui_app.font(FontWeight.BOLD)
self._font_semi_bold = gui_app.font(FontWeight.SEMI_BOLD)
self._demo = _env_truthy("SP_MICI_WIDGET_DEMO")
self._demo = _env_truthy("SP_CEM_DEMO") or _env_truthy("SP_MICI_WIDGET_DEMO")
@property
def demo_active(self) -> bool:
@@ -195,12 +195,11 @@ class MiciSidebarWidgets(Widget):
def _cem_reason(self) -> tuple[str, rl.Color]:
if self._demo:
reason = DEMO_REASONS[int(rl.get_time() / DEMO_HOLD_SECONDS) % len(DEMO_REASONS)]
if reason == "chill":
return reason, WHITE
if reason == "stop":
return reason, TRAFFIC_RED
return reason, CEM_BLUE
status = ui_state.params_memory.get_int("CEStatus", default=CEStatus["OFF"])
status_reason = self._ce_status_reason(status)
if status_reason is not None:
return status_reason
return self._fallback_demo_reason()
conditional_experimental = ui_state.params.get_bool("ConditionalExperimental")
conditional_chill = ui_state.params.get_bool("ConditionalChill") and not conditional_experimental
@@ -217,6 +216,22 @@ class MiciSidebarWidgets(Widget):
return "chill", WHITE
status = ui_state.params_memory.get_int("CEStatus", default=CEStatus["OFF"]) if conditional_experimental else CEStatus["OFF"]
status_reason = self._ce_status_reason(status)
if status_reason is not None:
return status_reason
if self._model_stop_active():
return "stop", TRAFFIC_RED
return "chill", WHITE
def _fallback_demo_reason(self) -> tuple[str, rl.Color]:
reason = DEMO_REASONS[int(rl.get_time() / DEMO_HOLD_SECONDS) % len(DEMO_REASONS)]
if reason == "chill":
return reason, WHITE
if reason == "stop":
return reason, TRAFFIC_RED
return reason, CEM_BLUE
def _ce_status_reason(self, status: int) -> tuple[str, rl.Color] | None:
if status == CEStatus["CURVATURE"]:
return "curve", CEM_BLUE
if status == CEStatus["LEAD"]:
@@ -231,9 +246,7 @@ class MiciSidebarWidgets(Widget):
return "chill", WHITE
if status == CEStatus["USER_OVERRIDDEN"]:
return "chill", WHITE
if self._model_stop_active():
return "stop", TRAFFIC_RED
return "chill", WHITE
return None
def _draw_chill_icon(self, rect: rl.Rectangle) -> None:
cx = rect.x + rect.width / 2
+7 -1
View File
@@ -87,7 +87,13 @@ class HudRenderer(Widget):
car_state = sm['carState']
v_cruise_cluster = car_state.vCruiseCluster
v_cruise = controls_state.deprecated.vCruise if v_cruise_cluster == 0.0 else v_cruise_cluster
if v_cruise_cluster == 0.0:
try:
v_cruise = controls_state.vCruiseDEPRECATED
except AttributeError:
v_cruise = controls_state.deprecated.vCruise
else:
v_cruise = v_cruise_cluster
offset = ui_state.starpilot_toggles.get("set_speed_offset", 0.0)
self.set_speed = v_cruise + offset if (0 < v_cruise < SET_SPEED_NA) else v_cruise
self.is_cruise_set = 0 < v_cruise < SET_SPEED_NA
@@ -1,9 +1,11 @@
from dataclasses import dataclass
from enum import Enum
import math
import os
import pyray as rl
from openpilot.common.constants import CV
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.starpilot.common.experimental_state import CEStatus
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.selfdrive.ui.onroad.starpilot.starpilot_border import _csc_state, _intensity, _glow_color
@@ -57,6 +59,7 @@ COLOR_STOP_SIGN = rl.Color(196, 30, 58, 255)
COLOR_STOP_SIGN_OUTLINE = rl.Color(255, 255, 255, 255)
COLOR_STOP_LINE_GLOW = rl.Color(255, 30, 60, 255)
COLOR_STOP_LINE_CORE = rl.Color(255, 200, 200, 255)
COLOR_CEM_SPEED = rl.Color(112, 192, 216, 255)
# Set to True to force test-cycle mode (flip back to False before pushing)
TEST_CYCLE = False
@@ -70,6 +73,9 @@ def _speed_conversion() -> float:
def _speed_unit() -> str:
return "km/h" if ui_state.is_metric else "mph"
def _env_truthy(name: str) -> bool:
return os.getenv(name, "").lower() in {"1", "true", "yes", "on"}
def _to_display_speed(val: float) -> tuple[int, str]:
return int(round(val * _speed_conversion())), _speed_unit()
@@ -267,6 +273,7 @@ _TEST_STATES = [
(IndicatorType.FORCE_STOP, "", COLOR_FORCE_STOP, 12.0, "", ""),
]
_TEST_CYCLE_SEC = 6.0
CEM_DEMO = _env_truthy("SP_CEM_DEMO") or _env_truthy("SP_MICI_WIDGET_DEMO")
def _test_cycle_active() -> bool:
return rl.get_time() > 3.0
@@ -299,6 +306,63 @@ def _test_cycle_data() -> AetherGaugeData:
indicator_extra=extra, reduction_text=reduction, is_numeric=True)
def _cem_demo_active() -> bool:
return CEM_DEMO and ui_state.conditional_status in {
CEStatus["CURVATURE"],
CEStatus["LEAD"],
CEStatus["STOP_LIGHT"],
CEStatus["SPEED"],
CEStatus["SPEED_LIMIT"],
}
def _cem_demo_data() -> AetherGaugeData:
status = ui_state.conditional_status
if status == CEStatus["CURVATURE"]:
v_ego = _get_val("carState", "vEgo", 18.0)
target_speed = max(8.0, v_ego * 0.72)
v_cruise = max(target_speed + 4.0, _get_val("starpilotPlan", "vCruise", target_speed + 4.0))
return _build_curve_gauge_data(0.005, target_speed, v_cruise)
if status == CEStatus["LEAD"]:
if _sm_valid("radarState") and ui_state.sm["radarState"].leadOne.status:
return _lead_data()
is_stopped = int(rl.get_time() / 1.0) % 2 == 0
return AetherGaugeData(
text="STOPPED" if is_stopped else "SLOW",
color=COLOR_LEAD_STOPPED if is_stopped else COLOR_LEAD_SLOWER,
indicator_type=IndicatorType.LEAD,
indicator_value=12.0 if is_stopped else 24.0,
indicator_extra="stopped" if is_stopped else "slower",
)
if status == CEStatus["STOP_LIGHT"]:
phase = rl.get_time() % 2.0
distance = max(6.0, 35.0 - phase * 12.0)
v_ego = max(0.1, _get_val("carState", "vEgo", 10.0))
return AetherGaugeData(
text=_time_to_stop(distance, v_ego),
unit="s",
color=COLOR_FORCE_STOP,
indicator_type=IndicatorType.STOP_LIGHT,
indicator_value=distance,
indicator_extra="red",
is_numeric=True,
)
v_ego = _get_val("carState", "vEgo", 20.0)
display_speed, unit = _to_display_speed(max(8.0, v_ego * 0.85))
return AetherGaugeData(
text=str(display_speed),
unit=unit,
color=COLOR_CEM_SPEED,
indicator_type=IndicatorType.ROAD_CURVE,
indicator_value=0.0,
is_numeric=True,
)
# --- Main widget ---
class AetherGauge:
@@ -312,6 +376,8 @@ class AetherGauge:
]
if TEST_CYCLE:
self._sources.insert(0, (_test_cycle_active, _test_cycle_data))
if CEM_DEMO:
self._sources.insert(0, (_cem_demo_active, _cem_demo_data))
self._chevron_accum = 0.0
self._lead_chevron_accum = 0.0
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
import argparse
import signal
import time
from itertools import cycle
from openpilot.common.params import Params
from openpilot.starpilot.common.experimental_state import CEStatus, CE_STATUS_PARAM
SCENARIOS = [
("curve", CEStatus["CURVATURE"]),
("lead", CEStatus["LEAD"]),
("stop light", CEStatus["STOP_LIGHT"]),
("speed", CEStatus["SPEED"]),
]
running = True
def _handle_signal(_signum, _frame) -> None:
global running
running = False
def main() -> None:
parser = argparse.ArgumentParser(description="Publish fake CEM statuses for desktop onroad replay.")
parser.add_argument("--hold-seconds", type=float, default=2.0, help="How long each CEM status stays active.")
parser.add_argument("--publish-interval", type=float, default=0.25, help="Seconds between CEStatus writes.")
args = parser.parse_args()
signal.signal(signal.SIGINT, _handle_signal)
signal.signal(signal.SIGTERM, _handle_signal)
params_memory = Params(memory=True)
scenario_cycle = cycle(SCENARIOS)
label, status = next(scenario_cycle)
next_switch = time.monotonic() + args.hold_seconds
print(f"showing CEM: {label}", flush=True)
try:
while running:
now = time.monotonic()
if now >= next_switch:
label, status = next(scenario_cycle)
next_switch = now + args.hold_seconds
print(f"showing CEM: {label}", flush=True)
params_memory.put_int(CE_STATUS_PARAM, status)
time.sleep(args.publish_interval)
finally:
params_memory.put_int(CE_STATUS_PARAM, CEStatus["OFF"])
if __name__ == "__main__":
main()