cleanup errybody errywhere

This commit is contained in:
firestar5683
2026-07-13 23:59:13 -05:00
parent c9cf8cfdf6
commit a0ae06022c
12 changed files with 623 additions and 71 deletions
+146 -53
View File
@@ -27,9 +27,10 @@ env_var_truthy() {
usage() {
cat <<'EOF'
Usage:
./onroad [jobs] (--c3 | --c4 | --raybig | --all | --replay-only) [-nav] [-alert] [--cem] [--prefix name] <route-or-replay-args...>
./onroad [jobs] [--c3 | --c4 | --raybig | --all | --replay-only] [--galaxy] [-nav] [-alert] [--cem] [--prefix name] <route-or-replay-args...>
Examples:
./onroad <route>
./onroad --c3 <route>
./onroad --c4 <route> --start 30
./onroad --c4 -nav <route>
@@ -42,7 +43,9 @@ Examples:
Notes:
- This is host/dev only. It uses the isolated host worktree and does not touch the device path.
- A private comma connect route still requires tools/lib/auth.py before replay can download it.
- If no UI flag is provided, the route's logged device type selects the UI: mici/c4 -> c4, tici/tizi -> raybig unless UseOldUI was enabled.
- Use multiple UI flags together if you want more than one desktop UI at once.
- --galaxy starts a local Galaxy web session with the same preview params and prints the localhost URL. It blocks replay's logged customReserved9 stream so Galaxy can own the live Testing Grounds publisher.
- -nav injects a fake navigation demo stream and blocks replay from publishing navInstruction/navRoute.
- --cem publishes fake CEM statuses for desktop visual review in the raylib UIs.
- -alert blocks replay from publishing selfdriveState and fires a fake critical full-screen red alert (alertSize=full, alertStatus=critical) 20 seconds after the demo publisher starts (10s for replay route + UI to come up, plus 10s for the user to open Settings). Default alert text mimics a real controlsMismatch event; run tools/replay/fake_alert_demo.py directly to override --text1/--text2/--delay.
@@ -58,15 +61,21 @@ fi
PREFIX_ARG=""
REPLAY_ARGS=()
UI_TARGETS=()
UI_SELECTION_EXPLICIT=0
LEGACY_UI_SELECTION=""
REPLAY_ONLY=0
NAV_DEMO=0
CEM_DEMO=0
ALERT_DEMO=0
GALAXY=0
REPLAY_PID=""
NAV_PID=""
CEM_PID=""
ALERT_PID=""
GALAXY_PID=""
GALAXY_PORT=""
GALAXY_URL=""
ONROAD_TEMP_PREFIX=""
UI_PIDS=()
parse_args() {
@@ -78,18 +87,22 @@ parse_args() {
;;
--c3)
UI_TARGETS+=(c3)
UI_SELECTION_EXPLICIT=1
shift
;;
--c4)
UI_TARGETS+=(c4)
UI_SELECTION_EXPLICIT=1
shift
;;
--raybig)
UI_TARGETS+=(raybig)
UI_SELECTION_EXPLICIT=1
shift
;;
--all)
UI_TARGETS+=(c3 c4 raybig)
UI_SELECTION_EXPLICIT=1
shift
;;
--replay-only)
@@ -104,6 +117,10 @@ parse_args() {
CEM_DEMO=1
shift
;;
--galaxy)
GALAXY=1
shift
;;
-alert|--alert|--alert-demo)
ALERT_DEMO=1
shift
@@ -114,10 +131,12 @@ parse_args() {
exit 1
fi
LEGACY_UI_SELECTION="$2"
UI_SELECTION_EXPLICIT=1
shift 2
;;
--ui=*)
LEGACY_UI_SELECTION="${1#*=}"
UI_SELECTION_EXPLICIT=1
shift
;;
-p|--prefix)
@@ -228,6 +247,9 @@ cleanup() {
if [[ -n "${ALERT_PID}" ]]; then
kill "${ALERT_PID}" >/dev/null 2>&1 || true
fi
if [[ -n "${GALAXY_PID}" ]]; then
kill "${GALAXY_PID}" >/dev/null 2>&1 || true
fi
for pid in "${UI_PIDS[@]-}"; do
if [[ -n "${pid}" ]]; then
@@ -246,12 +268,15 @@ cleanup() {
if [[ -n "${ALERT_PID}" ]]; then
wait "${ALERT_PID}" >/dev/null 2>&1 || true
fi
if [[ -n "${GALAXY_PID}" ]]; then
wait "${GALAXY_PID}" >/dev/null 2>&1 || true
fi
if [[ -n "${OPENPILOT_PREFIX:-}" && "${OPENPILOT_PREFIX}" == desktop-onroad-* ]]; then
echo "Cleaning up temporary prefix environment (${OPENPILOT_PREFIX})..."
rm -rf "/dev/shm/msgq_${OPENPILOT_PREFIX}"
rm -rf "/tmp/comma_download_cache${OPENPILOT_PREFIX}"
rm -rf "${HOME}/.comma${OPENPILOT_PREFIX}"
if [[ -n "${ONROAD_TEMP_PREFIX:-}" && "${ONROAD_TEMP_PREFIX}" == desktop-onroad-* ]]; then
echo "Cleaning up temporary prefix environment (${ONROAD_TEMP_PREFIX})..."
rm -rf "/dev/shm/msgq_${ONROAD_TEMP_PREFIX}"
rm -rf "/tmp/comma_download_cache${ONROAD_TEMP_PREFIX}"
rm -rf "${HOME}/.comma${ONROAD_TEMP_PREFIX}"
fi
exit "${exit_code}"
@@ -295,8 +320,8 @@ append_blocked_service_names() {
printf '%s' "${joined}"
}
ensure_nav_demo_replay_blocklist() {
local nav_services="navInstruction,navRoute"
ensure_replay_blocklist() {
local services="$1"
local idx=0
for ((idx=0; idx<${#REPLAY_ARGS[@]}; idx++)); do
@@ -306,41 +331,29 @@ ensure_nav_demo_replay_blocklist() {
echo "Missing value for ${REPLAY_ARGS[$idx]}" >&2
exit 1
fi
REPLAY_ARGS[$((idx + 1))]="$(append_blocked_service_names "${REPLAY_ARGS[$((idx + 1))]}" "${nav_services}")"
REPLAY_ARGS[$((idx + 1))]="$(append_blocked_service_names "${REPLAY_ARGS[$((idx + 1))]}" "${services}")"
return
;;
--block=*)
REPLAY_ARGS[$idx]="--block=$(append_blocked_service_names "${REPLAY_ARGS[$idx]#*=}" "${nav_services}")"
REPLAY_ARGS[$idx]="--block=$(append_blocked_service_names "${REPLAY_ARGS[$idx]#*=}" "${services}")"
return
;;
esac
done
REPLAY_ARGS=(-b "${nav_services}" "${REPLAY_ARGS[@]}")
REPLAY_ARGS=(-b "${services}" "${REPLAY_ARGS[@]}")
}
ensure_nav_demo_replay_blocklist() {
ensure_replay_blocklist "navInstruction,navRoute"
}
ensure_alert_demo_replay_blocklist() {
local alert_services="selfdriveState"
local idx=0
ensure_replay_blocklist "selfdriveState"
}
for ((idx=0; idx<${#REPLAY_ARGS[@]}; idx++)); do
case "${REPLAY_ARGS[$idx]}" in
-b|--block)
if (( idx + 1 >= ${#REPLAY_ARGS[@]} )); then
echo "Missing value for ${REPLAY_ARGS[$idx]}" >&2
exit 1
fi
REPLAY_ARGS[$((idx + 1))]="$(append_blocked_service_names "${REPLAY_ARGS[$((idx + 1))]}" "${alert_services}")"
return
;;
--block=*)
REPLAY_ARGS[$idx]="--block=$(append_blocked_service_names "${REPLAY_ARGS[$idx]#*=}" "${alert_services}")"
return
;;
esac
done
REPLAY_ARGS=(-b "${alert_services}" "${REPLAY_ARGS[@]}")
ensure_galaxy_replay_blocklist() {
ensure_replay_blocklist "customReserved9"
}
prepare_env() {
@@ -371,32 +384,29 @@ prepare_env() {
export SP_CEM_DEMO="${CEM_DEMO}"
export SP_ONROAD_ALERT_DEMO="${ALERT_DEMO}"
local generated_prefix="${PREFIX_ARG:-${OPENPILOT_PREFIX:-desktop-onroad-$$}}"
ONROAD_TEMP_PREFIX="${generated_prefix}"
if [[ "$(uname -s)" == "Darwin" ]] || env_var_truthy "${ZMQ:-0}"; then
export OPENPILOT_ZMQ_NAMESPACE="${PREFIX_ARG:-${OPENPILOT_ZMQ_NAMESPACE:-desktop-onroad-$$}}"
export OPENPILOT_ZMQ_NAMESPACE="${PREFIX_ARG:-${OPENPILOT_ZMQ_NAMESPACE:-${generated_prefix}}}"
unset OPENPILOT_PREFIX
export PARAMS_ROOT="${PARAMS_ROOT:-${HOME}/.comma${generated_prefix}/params}"
else
export OPENPILOT_PREFIX="${PREFIX_ARG:-${OPENPILOT_PREFIX:-desktop-onroad-$$}}"
export OPENPILOT_PREFIX="${generated_prefix}"
mkdir -p "/dev/shm/msgq_${OPENPILOT_PREFIX}"
fi
}
seed_params() {
"${ROOT_DIR}/.venv/bin/python3" - <<'PY'
import os
"${ROOT_DIR}/.venv/bin/python3" "${ROOT_DIR}/tools/replay/onroad_config.py" seed "${REPLAY_ARGS[@]}"
}
from openpilot.common.params import Params
from openpilot.system.version import terms_version, training_version
params = Params()
params.put("HasAcceptedTerms", terms_version)
params.put("CompletedTrainingVersion", training_version)
params.put_bool("OpenpilotEnabledToggle", True)
params.put_bool("IsDriverViewEnabled", False)
params.put_bool("ForceOnroad", False)
params.put_bool("ForceOffroad", False)
if os.getenv("SP_ONROAD_NAV_DEMO", "").lower() in {"1", "true", "yes", "on"}:
params.put_bool("NavigationUI", True)
PY
auto_select_ui_targets() {
local selection=""
selection="$("${ROOT_DIR}/.venv/bin/python3" "${ROOT_DIR}/tools/replay/onroad_config.py" select-ui "${REPLAY_ARGS[@]}")"
IFS=' ' read -r -a UI_TARGETS <<< "${selection}"
dedupe_ui_targets
echo "Auto-selected UI: ${UI_TARGETS[*]}"
}
seed_starpilot_theme() {
@@ -474,6 +484,73 @@ launch_alert_demo() {
fi
}
pick_free_local_port() {
"${ROOT_DIR}/.venv/bin/python3" - <<'PY'
import socket
# The desktop ZMQ transport hashes replay service names into ports 8023-65535.
# Keep Galaxy below that range so the HTTP server cannot steal a replay service port.
for port in range(4600, 8023):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.bind(("127.0.0.1", port))
except OSError:
continue
print(port)
raise SystemExit(0)
raise SystemExit("Unable to find a free local Galaxy port below the ZMQ replay range.")
PY
}
wait_for_galaxy() {
local url="${GALAXY_URL}/api/galaxy/status"
local idx=0
for ((idx=0; idx<50; idx++)); do
if ! kill -0 "${GALAXY_PID}" >/dev/null 2>&1; then
wait "${GALAXY_PID}"
return 1
fi
if "${ROOT_DIR}/.venv/bin/python3" - "${url}" <<'PY' >/dev/null 2>&1; then
import sys
import urllib.request
with urllib.request.urlopen(sys.argv[1], timeout=0.5) as response:
raise SystemExit(0 if response.status == 200 else 1)
PY
return 0
fi
sleep 0.1
done
echo "Timed out waiting for local Galaxy session at ${GALAXY_URL}" >&2
return 1
}
launch_galaxy() {
GALAXY_PORT="$(pick_free_local_port)"
GALAXY_URL="http://127.0.0.1:${GALAXY_PORT}"
local galaxy_dir="${HOME}/.comma${ONROAD_TEMP_PREFIX}/starpilot/data/galaxy"
echo "Starting local Galaxy session..."
(
export SP_GALAXY_DIR="${galaxy_dir}"
export SP_GALAXY_HOST="127.0.0.1"
export SP_GALAXY_PORT="${GALAXY_PORT}"
export SP_GALAXY_DEBUG=0
export SP_GALAXY_RELOAD=0
exec "${ROOT_DIR}/.venv/bin/python3" -m openpilot.starpilot.system.the_galaxy.the_galaxy
) &
GALAXY_PID=$!
wait_for_galaxy
echo "Access Galaxy with ${GALAXY_URL}"
}
launch_c3_ui() {
local os_ext="linux"
if [[ "$(uname -s)" == "Darwin" ]]; then
@@ -528,13 +605,12 @@ if [[ "${ALERT_DEMO}" == "1" ]]; then
ensure_alert_demo_replay_blocklist
fi
if [[ ${#REPLAY_ARGS[@]} -eq 0 ]]; then
usage >&2
exit 1
if [[ "${GALAXY}" == "1" ]]; then
ensure_galaxy_replay_blocklist
fi
if [[ "${REPLAY_ONLY}" != "1" && ${#UI_TARGETS[@]} -eq 0 ]]; then
echo "Select at least one UI with --c3, --c4, --raybig, or use --replay-only." >&2
if [[ ${#REPLAY_ARGS[@]} -eq 0 ]]; then
usage >&2
exit 1
fi
@@ -545,6 +621,19 @@ echo "Using OPENPILOT_PREFIX=${OPENPILOT_PREFIX:-<default>}"
if [[ -n "${OPENPILOT_ZMQ_NAMESPACE:-}" ]]; then
echo "Using OPENPILOT_ZMQ_NAMESPACE=${OPENPILOT_ZMQ_NAMESPACE}"
fi
if [[ -n "${PARAMS_ROOT:-}" ]]; then
echo "Using PARAMS_ROOT=${PARAMS_ROOT}"
fi
if [[ "${REPLAY_ONLY}" != "1" && "${UI_SELECTION_EXPLICIT}" == "0" && ${#UI_TARGETS[@]} -eq 0 ]]; then
auto_select_ui_targets
fi
if [[ "${REPLAY_ONLY}" != "1" && ${#UI_TARGETS[@]} -eq 0 ]]; then
echo "Select at least one UI with --c3, --c4, --raybig, or use --replay-only." >&2
exit 1
fi
echo "Preparing replay and desktop UI runtime..."
build_replay
@@ -564,6 +653,10 @@ esac
seed_params
seed_starpilot_theme
if [[ "${GALAXY}" == "1" ]]; then
launch_galaxy
fi
echo "Starting replay: ${REPLAY_ARGS[*]}"
launch_replay
+6 -1
View File
@@ -142,7 +142,12 @@ def main():
assert vipc_client.is_connected()
cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}")
model = ModelState(vipc_client.width, vipc_client.height)
# connect() finishes before stream is populated. The first buffer always has known dimensions.
first_buf = vipc_client.recv()
while first_buf is None:
first_buf = vipc_client.recv()
model = ModelState(first_buf.width, first_buf.height)
cloudlog.warning("models loaded, dmonitoringmodeld starting")
sm = SubMaster(["liveCalibration"])
+13 -10
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
from cereal import log
from openpilot.common.params import Params
from openpilot.common.params import Params, UnknownKeyName
SAFE_MODE_PARAM = "SafeMode"
SAFE_MODE_BACKUP_PARAM = "SafeModeBackup"
@@ -249,19 +249,22 @@ def _safe_value(params: Params, key: str):
def _apply_value(params_raw: Params, key: str, value) -> bool:
current = params_raw.get(key)
if value is None:
if current is None:
try:
current = params_raw.get(key)
if value is None:
if current is None:
return False
params_raw.remove(key)
return True
if current == value:
return False
params_raw.remove(key)
params_raw.put(key, value)
return True
if current == value:
except UnknownKeyName:
return False
params_raw.put(key, value)
return True
def _mark_toggle_update(params_memory: Params | None) -> None:
if params_memory is None:
+11
View File
@@ -0,0 +1,11 @@
from openpilot.common.params import UnknownKeyName
from openpilot.starpilot.common.safe_mode import _apply_value
class RemovedParamStore:
def get(self, key):
raise UnknownKeyName(key)
def test_apply_value_ignores_removed_param():
assert not _apply_value(RemovedParamStore(), "RemovedParam", "stale value")
+4 -1
View File
@@ -107,7 +107,10 @@ def terminate_child(proc: subprocess.Popen[str]) -> None:
proc.wait(timeout=2)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=2)
try:
proc.wait(timeout=2)
except subprocess.TimeoutExpired:
cloudlog.error(f"mapd_wrapper child did not exit after kill: pid={proc.pid}")
def run_mapd_once() -> int:
+29 -1
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env python3
import json
import subprocess
from pathlib import Path
from openpilot.starpilot.navigation.mapd_wrapper import CorruptTileMonitor, quarantine_offline_tile
from openpilot.starpilot.navigation.mapd_wrapper import CorruptTileMonitor, quarantine_offline_tile, terminate_child
def _loading_line(filename: str) -> str:
@@ -57,3 +58,30 @@ def test_quarantine_offline_tile_renames_backing_archive(tmp_path, monkeypatch):
assert not archive.exists()
assert Path(quarantined).exists()
assert Path(quarantined).name.startswith(f"{archive.name}.corrupt.")
def test_terminate_child_tolerates_wedged_process():
class WedgedProcess:
pid = 123
def __init__(self):
self.terminated = False
self.killed = False
def poll(self):
return None
def terminate(self):
self.terminated = True
def kill(self):
self.killed = True
def wait(self, timeout):
raise subprocess.TimeoutExpired("mapd", timeout)
proc = WedgedProcess()
terminate_child(proc)
assert proc.terminated
assert proc.killed
+8 -3
View File
@@ -536,6 +536,8 @@ MAPS_CANCEL_DOWNLOAD_PARAM = "CancelDownloadMaps"
def _get_galaxy_dir():
if override := os.getenv("SP_GALAXY_DIR"):
return Path(override)
return Path(Paths.comma_home()) / "starpilot" / "data" / "galaxy" if PC else Path("/data/galaxy")
@@ -7237,14 +7239,17 @@ def main():
threading.Thread(target=_testing_ground_custom_reserved_worker, daemon=True).start()
# Desktop-only debug mode. On-device must stay on 8082 to match Galaxy FRP routing.
debug = not _is_comma_device_runtime()
port = 8083 if debug else 8082
on_device = _is_comma_device_runtime()
debug = False if on_device else os.getenv("SP_GALAXY_DEBUG", "1").lower() in {"1", "true", "yes", "on"}
port = 8082 if on_device else int(os.getenv("SP_GALAXY_PORT", "8083"))
host = "0.0.0.0" if on_device else os.getenv("SP_GALAXY_HOST", "0.0.0.0")
use_reloader = False if on_device else os.getenv("SP_GALAXY_RELOAD", "0" if not debug else "1").lower() in {"1", "true", "yes", "on"}
if debug:
print("\"The Galaxy\" is not running on a comma device, enabling debug mode")
app.secret_key = secrets.token_hex(32)
app.run(host="0.0.0.0", port=port, debug=debug)
app.run(host=host, port=port, debug=debug, use_reloader=use_reloader)
if __name__ == "__main__":
main()
+5 -1
View File
@@ -8,6 +8,10 @@ from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import get_file_handler
def decode_record(data: bytes) -> str:
return data.decode("utf-8", errors="replace")
def main() -> NoReturn:
log_handler = get_file_handler()
log_handler.setFormatter(SwagLogFileFormatter(None))
@@ -25,7 +29,7 @@ def main() -> NoReturn:
while True:
dat = b''.join(sock.recv_multipart())
level = dat[0]
record = dat[1:].decode("utf-8")
record = decode_record(dat[1:])
if level >= log_level:
log_handler.emit(record)
+7 -1
View File
@@ -125,7 +125,13 @@ class Mic:
def micd_thread(self):
# sounddevice must be imported after forking processes
import sounddevice as sd
while True:
try:
import sounddevice as sd
break
except Exception:
cloudlog.exception("micd: sounddevice/PortAudio unavailable, retrying")
time.sleep(5)
while True:
stream = None
+5
View File
@@ -0,0 +1,5 @@
from openpilot.system.logmessaged import decode_record
def test_decode_record_replaces_malformed_utf8():
assert decode_record(b'before\xffafter') == "before\ufffdafter"
+296
View File
@@ -0,0 +1,296 @@
#!/usr/bin/env python3
from __future__ import annotations
import os
import re
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Sequence
from openpilot.common.params import Params, UnknownKeyName
from openpilot.system.version import terms_version, training_version
from openpilot.tools.lib.logreader import LogReader, ReadMode, parse_direct, parse_indirect
from openpilot.tools.lib.route import SegmentRange
DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19"
_VALUE_OPTIONS = {
"-a", "--allow",
"-b", "--block",
"-c", "--cache",
"-s", "--start",
"-x", "--playback",
"-d", "--data_dir",
"-p", "--prefix",
}
_NO_VALUE_OPTIONS = {
"--dcam",
"--ecam",
"--no-loop",
"--no-cache",
"--qcam",
"--no-hw-decoder",
"--no-vipc",
"--all",
"--headless",
}
_SEGMENT_SLICE_RE = re.compile(r"(?P<start>-?[0-9]+)?:?(?P<end>-?[0-9]+)?:?(?P<step>-?[0-9]+)?$")
@dataclass(frozen=True)
class ReplayArgs:
route: str | None
data_dir: str | None
auto_source: bool = False
def _truthy_env(name: str) -> bool:
return os.getenv(name, "").lower() in {"1", "true", "yes", "on"}
def parse_replay_args(args: Sequence[str]) -> ReplayArgs:
route: str | None = None
data_dir: str | None = None
auto_source = False
idx = 0
while idx < len(args):
arg = args[idx]
if arg == "--":
idx += 1
continue
if arg == "--demo":
route = DEMO_ROUTE
idx += 1
continue
if arg == "--auto":
auto_source = True
idx += 1
continue
if arg.startswith("--data_dir="):
data_dir = arg.split("=", 1)[1]
idx += 1
continue
if arg in ("-d", "--data_dir"):
if idx + 1 < len(args):
data_dir = args[idx + 1]
idx += 2
continue
if any(arg.startswith(f"{option}=") for option in _VALUE_OPTIONS if option.startswith("--")):
idx += 1
continue
if arg in _VALUE_OPTIONS:
idx += 2
continue
if arg in _NO_VALUE_OPTIONS:
idx += 1
continue
if arg.startswith("-"):
# Unknown replay option. Treat it as a flag so we do not mistake its value
# for a route; replay itself will validate it later.
idx += 1
continue
if route is None:
route = arg
idx += 1
return ReplayArgs(route=route, data_dir=data_dir, auto_source=auto_source)
def _first_selected_segment(sr: SegmentRange) -> int:
if not sr.slice:
return 0
if ":" not in sr.slice:
segment = int(sr.slice)
return sr.seg_idxs[0] if segment < 0 else segment
match = _SEGMENT_SLICE_RE.fullmatch(sr.slice)
if match is None:
return sr.seg_idxs[0]
start_raw = match.group("start")
start = int(start_raw) if start_raw else 0
return sr.seg_idxs[0] if start < 0 else start
def first_segment_identifier(route: str) -> str:
parsed = parse_indirect(route)
direct = parse_direct(parsed)
if direct is not None:
return direct
sr = SegmentRange(parsed)
selector = sr.selector or "a"
segment = _first_selected_segment(sr)
return f"{sr.dongle_id}/{sr.log_id}/{segment}/{selector}"
def _local_route_identifiers(route: str, data_dir: str) -> list[str]:
direct = parse_direct(route)
if direct is not None:
return [direct]
try:
parsed = parse_indirect(route)
sr = SegmentRange(parsed)
segment = _first_selected_segment(sr)
except Exception:
return []
data_root = Path(data_dir)
route_name = sr.route_name.replace("/", "|")
route_name_slash = route_name.replace("|", "/")
segment_names = (f"{route_name}--{segment}", f"{route_name_slash}/{segment}")
filenames = ("rlog.zst", "rlog.bz2", "qlog.zst", "qlog.bz2")
identifiers: list[str] = []
for segment_name in segment_names:
for filename in filenames:
candidate = data_root / segment_name / filename
if candidate.exists():
identifiers.append(str(candidate))
for filename in filenames:
explorer_candidate = data_root / f"{route_name}--{segment}--{filename}"
if explorer_candidate.exists():
identifiers.append(str(explorer_candidate))
return identifiers
def replay_log_identifiers(replay_args: ReplayArgs) -> list[str]:
if replay_args.route is None or replay_args.auto_source:
return []
if replay_args.data_dir:
identifiers = _local_route_identifiers(replay_args.route, replay_args.data_dir)
if identifiers:
return identifiers
try:
return [first_segment_identifier(replay_args.route)]
except Exception as exc:
print(f"Unable to resolve route metadata for onroad preview: {exc}", file=sys.stderr)
return []
def load_init_data(replay_args: ReplayArgs) -> Any | None:
for identifier in replay_log_identifiers(replay_args):
try:
init_data = LogReader(identifier, default_mode=ReadMode.AUTO).first("initData")
except Exception as exc:
print(f"Unable to read route initData from {identifier}: {exc}", file=sys.stderr)
continue
if init_data is not None:
return init_data
return None
def logged_params(init_data: Any | None) -> dict[str, bytes]:
if init_data is None:
return {}
try:
entries = init_data.params.entries
except Exception:
return {}
params: dict[str, bytes] = {}
for entry in entries:
params[str(entry.key)] = bytes(entry.value)
return params
def select_ui_target(init_data: Any | None) -> str:
if init_data is None:
return "raybig"
device_type = str(getattr(init_data, "deviceType", "")).lower()
if device_type in {"mici", "c4"}:
return "c4"
if device_type in {"tici", "tizi"} and logged_params(init_data).get("UseOldUI") == b"1":
return "c3"
return "raybig"
def seed_logged_params(init_data: Any | None, params: Params) -> int:
seeded = 0
for key, raw_value in logged_params(init_data).items():
if raw_value == b"":
continue
try:
value = params.cpp2python(key, raw_value)
if value is None:
continue
params.put(key, value)
seeded += 1
except (UnknownKeyName, TypeError, ValueError):
continue
return seeded
def seed_desktop_overrides(params: Params) -> None:
params.put("HasAcceptedTerms", terms_version)
params.put("CompletedTrainingVersion", training_version)
params.put_bool("OpenpilotEnabledToggle", True)
params.put_bool("IsDriverViewEnabled", False)
params.put_bool("ForceOnroad", False)
params.put_bool("ForceOffroad", False)
if _truthy_env("SP_ONROAD_NAV_DEMO"):
params.put_bool("NavigationUI", True)
def seed_onroad_params(init_data: Any | None, params: Params | None = None) -> int:
params = params or Params()
seeded = seed_logged_params(init_data, params)
seed_desktop_overrides(params)
return seeded
def _cmd_select_ui(args: Sequence[str]) -> int:
init_data = load_init_data(parse_replay_args(args))
print(select_ui_target(init_data))
return 0
def _cmd_seed(args: Sequence[str]) -> int:
init_data = load_init_data(parse_replay_args(args))
seeded = seed_onroad_params(init_data)
if init_data is not None:
print(f"Seeded {seeded} logged route params for onroad preview.", file=sys.stderr)
else:
print("No route initData found; using desktop onroad defaults.", file=sys.stderr)
return 0
def main(argv: Sequence[str] | None = None) -> int:
argv = list(sys.argv[1:] if argv is None else argv)
if not argv or argv[0] in {"-h", "--help"}:
print("Usage: onroad_config.py (select-ui|seed) <replay-args...>", file=sys.stderr)
return 2
command = argv[0]
args = argv[1:]
if args[:1] == ["--"]:
args = args[1:]
if command == "select-ui":
return _cmd_select_ui(args)
if command == "seed":
return _cmd_seed(args)
print(f"Unknown command: {command}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())
+93
View File
@@ -0,0 +1,93 @@
from __future__ import annotations
from types import SimpleNamespace
from openpilot.tools.replay import onroad_config
TEST_ROUTE = "344c5c15b34f2d8a/2024-01-03--09-37-12"
class FakeParams:
def __init__(self):
self.values = {}
def cpp2python(self, key, value):
if key == "ShowSLCOffset":
return value == b"1"
if key == "UnknownOldParam":
raise onroad_config.UnknownKeyName(key)
return value.decode("utf-8")
def put(self, key, value):
self.values[key] = value
def put_bool(self, key, value):
self.values[key] = bool(value)
def _init_data(device_type="tici", params=None):
params = params or {}
entries = [SimpleNamespace(key=key, value=value) for key, value in params.items()]
return SimpleNamespace(deviceType=device_type, params=SimpleNamespace(entries=entries))
def test_parse_replay_args_handles_demo_data_dir_and_flags():
replay_args = onroad_config.parse_replay_args([
"--start", "30",
"--data_dir=/tmp/routes",
"--demo",
"--no-loop",
])
assert replay_args.route == onroad_config.DEMO_ROUTE
assert replay_args.data_dir == "/tmp/routes"
assert not replay_args.auto_source
def test_first_segment_identifier_limits_full_route_to_segment_zero():
assert onroad_config.first_segment_identifier(TEST_ROUTE) == f"{TEST_ROUTE}/0/a"
assert onroad_config.first_segment_identifier(f"{TEST_ROUTE}/5:6/r") == f"{TEST_ROUTE}/5/r"
def test_replay_log_identifiers_prefers_local_data_dir(tmp_path):
segment_dir = tmp_path / f"{TEST_ROUTE.replace('/', '|')}--5"
segment_dir.mkdir()
qlog = segment_dir / "qlog.zst"
qlog.touch()
identifiers = onroad_config.replay_log_identifiers(
onroad_config.ReplayArgs(route=f"{TEST_ROUTE}/5", data_dir=str(tmp_path))
)
assert identifiers == [str(qlog)]
def test_select_ui_uses_c4_for_mici_routes():
assert onroad_config.select_ui_target(_init_data("mici")) == "c4"
def test_select_ui_uses_old_qt_only_when_big_route_logged_use_old_ui():
assert onroad_config.select_ui_target(_init_data("tici", {"UseOldUI": b"1"})) == "c3"
assert onroad_config.select_ui_target(_init_data("tici", {"TryRaylibUI": b"0"})) == "raybig"
assert onroad_config.select_ui_target(_init_data("tizi")) == "raybig"
def test_seed_onroad_params_uses_logged_disabled_bool_and_desktop_overrides(monkeypatch):
monkeypatch.setenv("SP_ONROAD_NAV_DEMO", "1")
params = FakeParams()
init_data = _init_data("tici", {
"ShowSLCOffset": b"0",
"LanguageSetting": b"main_en",
"AccessToken": b"",
"UnknownOldParam": b"1",
})
seeded = onroad_config.seed_onroad_params(init_data, params)
assert seeded == 2
assert params.values["ShowSLCOffset"] is False
assert params.values["LanguageSetting"] == "main_en"
assert "AccessToken" not in params.values
assert params.values["OpenpilotEnabledToggle"] is True
assert params.values["NavigationUI"] is True