This commit is contained in:
firestar5683
2026-03-29 16:24:26 -05:00
parent f5d0eee724
commit b7d448e064
9 changed files with 484 additions and 6 deletions

6
onroad Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec "${ROOT_DIR}/scripts/host_tool_runner.sh" onroad "$@"

View File

@@ -27,6 +27,7 @@ Commands:
c3 Launch the desktop Qt UI from the isolated host cache.
c4 Launch the small raylib UI from the isolated host cache.
raybig Launch the large raylib UI from the isolated host cache.
onroad Launch replay plus desktop UI(s) from the isolated host cache.
replay Build and run replay from the isolated host cache.
cabana Build and run cabana from the isolated host cache.
plotjuggler Run PlotJuggler helper from the isolated host cache.
@@ -43,6 +44,7 @@ Notes:
- For c3/c4/raybig, pass the jobs count first to preserve existing shorthand:
./dev c3 8
./dev raybig 12
- `./onroad --c3 f08912a233c1584f/2022-08-11--18-02-41/1` launches replay plus the selected desktop UI.
- `./dev sync` refreshes all host buckets. Use `./dev sync cabana` to sync one.
EOF
}
@@ -61,7 +63,7 @@ resolve_host_bucket() {
local name="${1:-shared}"
case "${name}" in
shared|default|ui|c3|c4|raybig|replay|shell)
shared|default|ui|c3|c4|raybig|onroad|replay|shell)
echo "shared"
;;
cabana)
@@ -212,6 +214,17 @@ purge_host_python_artifacts() {
"${WORK_DIR}/msgq_repo/msgq/visionipc/visionipc_pyx.cpp"
}
purge_host_desktop_ui_artifacts() {
rm -f \
"${WORK_DIR}/selfdrive/ui/libqt_widgets.a" \
"${WORK_DIR}/selfdrive/ui/libqt_util.a" \
"${WORK_DIR}/selfdrive/ui/assets.o" \
"${WORK_DIR}/selfdrive/ui/main.o" \
"${WORK_DIR}/selfdrive/ui/moc_ui.o" \
"${WORK_DIR}/selfdrive/ui/ui.o" \
"${WORK_DIR}/selfdrive/ui/ui"
}
ensure_host_python_tools() {
ensure_venv
@@ -237,6 +250,22 @@ ensure_host_python_tools() {
)
}
sync_host_generated_headers() {
if ! command -v capnpc >/dev/null 2>&1; then
return
fi
(
cd "${WORK_DIR}"
capnpc --src-prefix=cereal \
cereal/log.capnp \
cereal/car.capnp \
cereal/legacy.capnp \
cereal/custom.capnp \
-o c++:cereal/gen/cpp/
)
}
sync_worktree() {
ensure_venv
@@ -264,6 +293,8 @@ sync_worktree() {
"selfdrive/ui/ui"
"selfdrive/ui/ui.macos"
"selfdrive/ui/ui.larch64"
"selfdrive/ui/libqt_widgets.a"
"selfdrive/ui/libqt_util.a"
"cereal/libcereal.a"
"cereal/libsocketmaster.a"
"cereal/messaging/bridge"
@@ -294,7 +325,9 @@ sync_worktree() {
done
rsync "${rsync_args[@]}" "${ROOT_DIR}/" "${WORK_DIR}/"
purge_host_desktop_ui_artifacts
rm -f "${WORK_DIR}/third_party/libjson11.a" "${WORK_DIR}/third_party/libkaitai.a"
sync_host_generated_headers
ensure_host_python_tools
rm -rf "${WORK_DIR}/.venv"
ln -s "${HOST_VENV}" "${WORK_DIR}/.venv"
@@ -396,6 +429,18 @@ launch_raybig() {
run_in_worktree "${WORK_DIR}/scripts/launch_ui_raybig_desktop.sh" "${jobs}" "$@"
}
launch_onroad() {
local jobs
jobs="$(default_jobs)"
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
jobs="$1"
shift || true
fi
sync_worktree
run_in_worktree "${WORK_DIR}/scripts/launch_onroad_desktop.sh" "${jobs}" "$@"
}
launch_replay() {
local jobs
jobs="$(default_jobs)"
@@ -473,7 +518,7 @@ main() {
help|-h|--help)
usage
;;
c3|c4|raybig|replay|shell)
c3|c4|raybig|onroad|replay|shell)
set_host_bucket "shared"
acquire_host_lock "${command} $*"
;;
@@ -517,6 +562,9 @@ main() {
raybig)
launch_raybig "$@"
;;
onroad)
launch_onroad "$@"
;;
replay)
launch_replay "$@"
;;

386
scripts/launch_onroad_desktop.sh Executable file
View File

@@ -0,0 +1,386 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${ROOT_DIR}"
if [[ ! -f .venv/bin/activate ]]; then
echo "Missing .venv. Run tools/install_python_dependencies.sh first."
exit 1
fi
default_jobs() {
if command -v nproc >/dev/null 2>&1; then
nproc
elif command -v sysctl >/dev/null 2>&1; then
sysctl -n hw.ncpu
else
echo 8
fi
}
env_var_truthy() {
[[ "${1:-}" =~ ^(1|true|yes|on)$ ]]
}
usage() {
cat <<'EOF'
Usage:
./onroad [jobs] (--c3 | --c4 | --raybig | --all | --replay-only) [--prefix name] <route-or-replay-args...>
Examples:
./onroad --c3 f08912a233c1584f/2022-08-11--18-02-41/1
./onroad --c4 f08912a233c1584f/2022-08-11--18-02-41/1 --start 30
./onroad --all f08912a233c1584f/2022-08-11--18-02-41/1
./onroad --replay-only --demo --no-vipc --no-loop
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.
- Use multiple UI flags together if you want more than one desktop UI at once.
EOF
}
jobs="$(default_jobs)"
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
jobs="$1"
shift || true
fi
PREFIX_ARG=""
REPLAY_ARGS=()
UI_TARGETS=()
LEGACY_UI_SELECTION=""
REPLAY_ONLY=0
REPLAY_PID=""
UI_PIDS=()
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--c3)
UI_TARGETS+=(c3)
shift
;;
--c4)
UI_TARGETS+=(c4)
shift
;;
--raybig)
UI_TARGETS+=(raybig)
shift
;;
--all)
UI_TARGETS+=(c3 c4 raybig)
shift
;;
--replay-only)
REPLAY_ONLY=1
shift
;;
--ui)
if [[ $# -lt 2 ]]; then
echo "Missing value for --ui" >&2
exit 1
fi
LEGACY_UI_SELECTION="$2"
shift 2
;;
--ui=*)
LEGACY_UI_SELECTION="${1#*=}"
shift
;;
-p|--prefix)
if [[ $# -lt 2 ]]; then
echo "Missing value for $1" >&2
exit 1
fi
PREFIX_ARG="$2"
shift 2
;;
--prefix=*)
PREFIX_ARG="${1#*=}"
shift
;;
--)
shift
REPLAY_ARGS+=("$@")
break
;;
*)
REPLAY_ARGS+=("$1")
shift
;;
esac
done
}
expand_ui_targets() {
local selection="${1// /}"
case "${selection,,}" in
all|"")
UI_TARGETS=(c3 c4 raybig)
return
;;
none)
UI_TARGETS=()
return
;;
esac
local raw_targets=()
IFS=',' read -r -a raw_targets <<< "${selection}"
local normalized=()
local raw=""
for raw in "${raw_targets[@]}"; do
case "${raw,,}" in
c3|c4|raybig)
normalized+=("${raw,,}")
;;
*)
echo "Unknown UI target in --ui: ${raw}" >&2
echo "Valid values: all, none, c3, c4, raybig" >&2
exit 1
;;
esac
done
local ordered_targets=(c3 c4 raybig)
local target=""
for target in "${ordered_targets[@]}"; do
local candidate=""
for candidate in "${normalized[@]}"; do
if [[ "${candidate}" == "${target}" ]]; then
UI_TARGETS+=("${target}")
break
fi
done
done
}
dedupe_ui_targets() {
local ordered_targets=(c3 c4 raybig)
local deduped=()
local target=""
for target in "${ordered_targets[@]}"; do
local candidate=""
for candidate in "${UI_TARGETS[@]-}"; do
if [[ "${candidate}" == "${target}" ]]; then
deduped+=("${target}")
break
fi
done
done
UI_TARGETS=("${deduped[@]-}")
}
cleanup() {
local exit_code=$?
trap - EXIT INT TERM
local pid=""
for pid in "${UI_PIDS[@]-}"; do
if [[ -n "${pid}" ]]; then
kill "${pid}" >/dev/null 2>&1 || true
fi
done
if [[ -n "${REPLAY_PID}" ]]; then
kill "${REPLAY_PID}" >/dev/null 2>&1 || true
fi
for pid in "${UI_PIDS[@]-}"; do
if [[ -n "${pid}" ]]; then
wait "${pid}" >/dev/null 2>&1 || true
fi
done
if [[ -n "${REPLAY_PID}" ]]; then
wait "${REPLAY_PID}" >/dev/null 2>&1 || true
fi
exit "${exit_code}"
}
prepare_env() {
source .venv/bin/activate
if [[ -d /opt/homebrew/bin ]]; then
export PATH="/opt/homebrew/bin:${PATH}"
fi
export PATH="${ROOT_DIR}/.venv/bin:${PATH}"
export PYTHONPATH="${ROOT_DIR}:${ROOT_DIR}/starpilot/third_party"
local repo_dir=""
for repo_dir in "${ROOT_DIR}"/*_repo; do
[[ -d "${repo_dir}" ]] && export PYTHONPATH="${PYTHONPATH}:${repo_dir}"
done
[[ -d "${ROOT_DIR}/third_party/acados" ]] && export PYTHONPATH="${PYTHONPATH}:${ROOT_DIR}/third_party/acados"
export BASEDIR="${ROOT_DIR}"
export NOBOARD=1
export SIMULATION=1
export SKIP_FW_QUERY=1
export USE_WEBCAM=1
export SP_C3_FAKE_WIFI=0
export SP_C4_FAKE_WIFI=0
export SP_RAYBIG_FAKE_WIFI=0
export SP_ALLOW_DESKTOP_FAKE_WIFI=0
if [[ "$(uname -s)" == "Darwin" ]] || env_var_truthy "${ZMQ:-0}"; then
if [[ -n "${PREFIX_ARG}" || -n "${OPENPILOT_PREFIX:-}" ]]; then
echo "Ignoring OPENPILOT_PREFIX because the ZMQ backend does not support prefixes." >&2
fi
unset OPENPILOT_PREFIX
else
export OPENPILOT_PREFIX="${PREFIX_ARG:-${OPENPILOT_PREFIX:-desktop-onroad-$$}}"
fi
}
seed_params() {
"${ROOT_DIR}/.venv/bin/python3" - <<'PY'
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)
PY
}
build_replay() {
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${ROOT_DIR}/.venv/bin/scons" --extras -j"${jobs}" tools/replay/replay
}
prepare_c3_runtime() {
SP_C3_COMPILE_ONLY=1 "${ROOT_DIR}/scripts/launch_ui_desktop.sh" "${jobs}"
}
prepare_python_ui_runtime() {
SP_KEEP_DESKTOP_RUNTIME_ARTIFACTS=1 SP_C4_COMPILE_ONLY=1 "${ROOT_DIR}/scripts/launch_ui_c4_desktop.sh" "${jobs}"
}
launch_replay() {
local replay_cmd=(
"${ROOT_DIR}/tools/replay/replay"
--headless
--dcam
--ecam
)
replay_cmd+=("${REPLAY_ARGS[@]}")
"${replay_cmd[@]}" &
REPLAY_PID=$!
sleep 1
if ! kill -0 "${REPLAY_PID}" >/dev/null 2>&1; then
wait "${REPLAY_PID}"
return 1
fi
}
launch_c3_ui() {
local os_ext="linux"
if [[ "$(uname -s)" == "Darwin" ]]; then
os_ext="macos"
fi
local host_ui="${ROOT_DIR}/selfdrive/ui/ui.${os_ext}"
if [[ ! -x "${host_ui}" ]]; then
echo "Missing ${host_ui}. C3 build did not produce the desktop binary." >&2
return 1
fi
"${host_ui}" &
UI_PIDS+=("$!")
}
launch_python_ui() {
local big="$1"
(
export BIG="${big}"
exec "${ROOT_DIR}/.venv/bin/python3" "${ROOT_DIR}/selfdrive/ui/ui.py"
) &
UI_PIDS+=("$!")
}
parse_args "$@"
if [[ -n "${LEGACY_UI_SELECTION}" ]]; then
expand_ui_targets "${LEGACY_UI_SELECTION}"
fi
if [[ "${REPLAY_ONLY}" == "1" ]]; then
UI_TARGETS=()
else
dedupe_ui_targets
fi
if [[ ${#REPLAY_ARGS[@]} -eq 0 ]]; then
usage >&2
exit 1
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
prepare_env
trap cleanup EXIT INT TERM
echo "Using OPENPILOT_PREFIX=${OPENPILOT_PREFIX:-<default>}"
echo "Preparing replay and desktop UI runtime..."
build_replay
case " ${UI_TARGETS[*]-} " in
*" c3 "*)
prepare_c3_runtime
;;
esac
case " ${UI_TARGETS[*]-} " in
*" c4 "*|*" raybig "*)
prepare_python_ui_runtime
;;
esac
seed_params
echo "Starting replay: ${REPLAY_ARGS[*]}"
launch_replay
if [[ ${#UI_TARGETS[@]} -eq 0 ]]; then
echo "Replay is running without UI windows. Press Ctrl-C to stop."
wait "${REPLAY_PID}"
exit 0
fi
echo "Launching UIs: ${UI_TARGETS[*]}"
local_target=""
for local_target in "${UI_TARGETS[@]}"; do
case "${local_target}" in
c3)
launch_c3_ui
;;
c4)
launch_python_ui 0
;;
raybig)
launch_python_ui 1
;;
esac
done
wait "${UI_PIDS[@]}"

View File

@@ -110,8 +110,10 @@ cleanup() {
kill "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
wait "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
fi
restore_runtime_artifacts
restore_new_tracked_changes
if [[ ! "${SP_KEEP_DESKTOP_RUNTIME_ARTIFACTS:-0}" =~ ^(1|true|yes|on)$ ]]; then
restore_runtime_artifacts
restore_new_tracked_changes
fi
rm -f "${PRE_TRACKED_DIRTY}" "${POST_TRACKED_DIRTY}"
}

View File

@@ -182,6 +182,7 @@ prepare_host_artifacts() {
purge_objects "${ROOT_DIR}/selfdrive/ui"
purge_objects "${ROOT_DIR}/starpilot/ui"
rm -f "${ROOT_DIR}/selfdrive/ui/libqt_widgets.a" "${ROOT_DIR}/selfdrive/ui/libqt_util.a"
rm -f "${ROOT_DIR}/selfdrive/ui/ui"
}
@@ -201,6 +202,8 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
# Keep desktop host builds on Apple toolchain even if shell exports Homebrew llvm.
export CC="/usr/bin/clang"
export CXX="/usr/bin/clang++"
export AR="/usr/bin/ar"
export RANLIB="/usr/bin/ranlib"
fi
unset CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH CPPFLAGS CFLAGS CXXFLAGS LDFLAGS

View File

@@ -110,8 +110,10 @@ cleanup() {
kill "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
wait "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
fi
restore_runtime_artifacts
restore_new_tracked_changes
if [[ ! "${SP_KEEP_DESKTOP_RUNTIME_ARTIFACTS:-0}" =~ ^(1|true|yes|on)$ ]]; then
restore_runtime_artifacts
restore_new_tracked_changes
fi
rm -f "${PRE_TRACKED_DIRTY}" "${POST_TRACKED_DIRTY}"
}

View File

@@ -73,6 +73,7 @@ All three entrypoints do the same thing. `./dev` is the shortest general-purpose
### Available host commands
- `./dev replay [args...]`
- `./onroad [jobs] (--c3 | --c4 | --raybig | --all | --replay-only) <route-or-replay-args...>`
- `./dev cabana [args...]`
- `./dev plotjuggler [args...]`
- `./dev juggle [args...]`
@@ -91,6 +92,9 @@ Examples:
```bash
./dev replay
./onroad --c3 f08912a233c1584f/2022-08-11--18-02-41/1
./onroad --c4 f08912a233c1584f/2022-08-11--18-02-41/1 --start 30
./onroad --all f08912a233c1584f/2022-08-11--18-02-41/1
./dev plotjuggler --help
./dev cabana
./c3 8
@@ -159,6 +163,12 @@ Use `./dev ...` when:
- you want host-native `.so` files separated from the main repo
- you do not want AI tools or git status confused by temporary build churn
Use `./onroad ...` when:
- you want a recorded route to drive the desktop UI(s) on PC
- you need replay and UI to share the same isolated host runtime and messaging prefix
- you want the default side-by-side desktop UI launch without running separate replay/UI commands
Use `./c3`, `./c4`, or `./raybig` when:
- you want the desktop UI variants

View File

@@ -77,6 +77,7 @@ Options:
--no-vipc do not output video
--all do output all messages including uiDebug, userBookmark.
this may causes issues when used along with UI
--headless run replay without the ncurses console UI
Arguments:
route the drive to replay. find your drives at
@@ -91,6 +92,12 @@ tools/replay/replay <route-name>
cd selfdrive/ui && ./ui.py
```
For the StarPilot host workflow, the combined desktop launcher is:
```bash
./onroad --c3 <route-name>
```
## Work with plotjuggler
If you want to use replay with plotjuggler, you can stream messages by running:

View File

@@ -5,6 +5,7 @@
#include <string>
#include <vector>
#include "common/util.h"
#include "common/prefix.h"
#include "tools/replay/consoleui.h"
#include "tools/replay/replay.h"
@@ -31,6 +32,7 @@ Options:
--no-hw-decoder Disable HW video decoding
--no-vipc Do not output video
--all Output all messages including bookmarkButton, uiDebug, userBookmark
--headless Run replay without the ncurses console UI
-h, --help Show this help message
)";
@@ -42,6 +44,7 @@ struct ReplayConfig {
std::string prefix;
uint32_t flags = REPLAY_FLAG_NONE;
bool auto_source = false;
bool headless = false;
int start_seconds = 0;
int cache_segments = -1;
float playback_speed = -1;
@@ -66,6 +69,7 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) {
{"no-hw-decoder", no_argument, nullptr, 0},
{"no-vipc", no_argument, nullptr, 0},
{"all", no_argument, nullptr, 0},
{"headless", no_argument, nullptr, 0},
{"help", no_argument, nullptr, 'h'},
{nullptr, 0, nullptr, 0}, // Terminating entry
};
@@ -100,6 +104,7 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) {
std::string name = cli_options[option_index].name;
if (name == "demo") config.route = DEMO_ROUTE;
else if (name == "auto") config.auto_source = true;
else if (name == "headless") config.headless = true;
else config.flags |= flag_map.at(name);
break;
}
@@ -149,6 +154,15 @@ int main(int argc, char *argv[]) {
return 1;
}
if (config.headless) {
replay.start(config.start_seconds);
ExitHandler do_exit;
while (!do_exit) {
util::sleep_for(100);
}
return 0;
}
ConsoleUI console_ui(&replay);
replay.start(config.start_seconds);
return console_ui.exec();