mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-19 16:22:08 +08:00
cleanup errybody errywhere
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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())
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user