#!/usr/bin/env bash set -euo pipefail LOAD_ENV_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEFAULT_OMNISOCKETGO_ROOT="$(cd "${LOAD_ENV_SCRIPT_DIR}/../.." && pwd)" die() { echo "$*" >&2 return 1 2>/dev/null || exit 1 } normalize_loaded_env_vars() { local var_name local value for var_name in $(compgen -A variable); do case "${var_name}" in BACKEND_*|BLITZ_*|B_SIDE_*|CONTROL_*|FRONTEND_*|OMNI_*|PYTHON3_BIN|PYTHON_VENV_PATH|ROBOT_*|ROS_DISTRO|VITE_*) value="${!var_name}" if [[ "${value}" == *$'\r' ]]; then printf -v "${var_name}" '%s' "${value%$'\r'}" export "${var_name}" fi ;; esac done } is_omnisocketgo_root() { local dir="$1" [[ -f "${dir}/Makefile" && -f "${dir}/cmd/b_side_omnid.c" && -d "${dir}/ros-control-py" ]] } is_robot_command_center_root() { local dir="$1" [[ -f "${dir}/backend/config/asgi.py" && -f "${dir}/frontend/package.json" ]] } require_robot_command_center_root() { if ! is_robot_command_center_root "${ROBOT_COMMAND_CENTER_ROOT}"; then die "ROBOT_COMMAND_CENTER_ROOT must point to the robot-command-center repo root. Current value: ${ROBOT_COMMAND_CENTER_ROOT}. Set it in ${LOAD_ENV_SCRIPT_DIR}/robot-remote.env.local if needed." fi } export OMNISOCKETGO_ROOT="${OMNISOCKETGO_ROOT:-${DEFAULT_OMNISOCKETGO_ROOT}}" omni_camera_device_was_set=0 omni_camera_profile_was_set=0 omni_camera_brightness_was_set=0 omni_camera_custom_ctrl_was_set=0 omni_camera_verify_was_set=0 if [[ "${OMNI_CAMERA_DEVICE+x}" == "x" ]]; then omni_camera_device_was_set=1 preserved_omni_camera_device="${OMNI_CAMERA_DEVICE}" fi if [[ "${OMNI_CAMERA_PROFILE+x}" == "x" ]]; then omni_camera_profile_was_set=1 preserved_omni_camera_profile="${OMNI_CAMERA_PROFILE}" fi if [[ "${OMNI_CAMERA_BRIGHTNESS+x}" == "x" ]]; then omni_camera_brightness_was_set=1 preserved_omni_camera_brightness="${OMNI_CAMERA_BRIGHTNESS}" fi if [[ "${OMNI_CAMERA_CUSTOM_CTRL+x}" == "x" ]]; then omni_camera_custom_ctrl_was_set=1 preserved_omni_camera_custom_ctrl="${OMNI_CAMERA_CUSTOM_CTRL}" fi if [[ "${OMNI_CAMERA_VERIFY+x}" == "x" ]]; then omni_camera_verify_was_set=1 preserved_omni_camera_verify="${OMNI_CAMERA_VERIFY}" fi ENV_FILES=( "${LOAD_ENV_SCRIPT_DIR}/robot-remote.env" "${LOAD_ENV_SCRIPT_DIR}/robot-remote.env.local" ) for env_file in "${ENV_FILES[@]}"; do if [[ -f "${env_file}" ]]; then set -a # shellcheck disable=SC1090 source "${env_file}" set +a fi done normalize_loaded_env_vars if [[ "${omni_camera_device_was_set}" == "1" ]]; then export OMNI_CAMERA_DEVICE="${preserved_omni_camera_device}" fi if [[ "${omni_camera_profile_was_set}" == "1" ]]; then export OMNI_CAMERA_PROFILE="${preserved_omni_camera_profile}" fi if [[ "${omni_camera_brightness_was_set}" == "1" ]]; then export OMNI_CAMERA_BRIGHTNESS="${preserved_omni_camera_brightness}" fi if [[ "${omni_camera_custom_ctrl_was_set}" == "1" ]]; then export OMNI_CAMERA_CUSTOM_CTRL="${preserved_omni_camera_custom_ctrl}" fi if [[ "${omni_camera_verify_was_set}" == "1" ]]; then export OMNI_CAMERA_VERIFY="${preserved_omni_camera_verify}" fi export OMNISOCKETGO_ROOT="${OMNISOCKETGO_ROOT:-${DEFAULT_OMNISOCKETGO_ROOT}}" export ROBOT_COMMAND_CENTER_ROOT="${ROBOT_COMMAND_CENTER_ROOT:-$(dirname "${OMNISOCKETGO_ROOT}")/robot-command-center}" if ! is_omnisocketgo_root "${OMNISOCKETGO_ROOT}"; then die "OMNISOCKETGO_ROOT must point to the OmniSocketGo repo root. Current value: ${OMNISOCKETGO_ROOT}" fi export BACKEND_DIR="${BACKEND_DIR:-${ROBOT_COMMAND_CENTER_ROOT}/backend}" export FRONTEND_DIR="${FRONTEND_DIR:-${ROBOT_COMMAND_CENTER_ROOT}/frontend}" export ROS_CONTROL_PY_DIR="${ROS_CONTROL_PY_DIR:-${OMNISOCKETGO_ROOT}/ros-control-py}" export PYTHON3_BIN="${PYTHON3_BIN:-python3}" export PYTHON_VENV_PATH="${PYTHON_VENV_PATH:-${OMNISOCKETGO_ROOT}/.venv}" export BACKEND_HOST="${BACKEND_HOST:-0.0.0.0}" export BACKEND_PORT="${BACKEND_PORT:-8001}" export FRONTEND_HOST="${FRONTEND_HOST:-0.0.0.0}" export FRONTEND_PORT="${FRONTEND_PORT:-5173}" export OMNISOCKET_TELEMETRY_PEER_ID="${OMNISOCKET_TELEMETRY_PEER_ID:-peer-a-telemetry}" export OMNISOCKET_TELEMETRY_INTERVAL_MS="${OMNISOCKET_TELEMETRY_INTERVAL_MS:-1000}" export OMNISOCKET_TELEMETRY_STALE_AFTER_MS="${OMNISOCKET_TELEMETRY_STALE_AFTER_MS:-3000}" export OMNI_NETWORK_SUMMARY_LOG_ENABLED="${OMNI_NETWORK_SUMMARY_LOG_ENABLED:-1}" export OMNI_NETWORK_SUMMARY_LOG_PATH="${OMNI_NETWORK_SUMMARY_LOG_PATH:-${OMNISOCKETGO_ROOT}/logs/a-network-summary.jsonl}" export OMNI_NETWORK_SUMMARY_LOG_INTERVAL_MS="${OMNI_NETWORK_SUMMARY_LOG_INTERVAL_MS:-2000}" export OMNI_NETWORK_SUMMARY_LOG_REQUEST_TIMEOUT_SEC="${OMNI_NETWORK_SUMMARY_LOG_REQUEST_TIMEOUT_SEC:-3}" export CONTROL_SIDE_OMNISOCKET_SERVER_ADDR="${CONTROL_SIDE_OMNISOCKET_SERVER_ADDR:-}" export CONTROL_SIDE_OMNISOCKET_RELAY_VIA="${CONTROL_SIDE_OMNISOCKET_RELAY_VIA:-}" export ROBOT_SIDE_OMNISOCKET_SERVER_ADDR="${ROBOT_SIDE_OMNISOCKET_SERVER_ADDR:-}" export ROBOT_SIDE_OMNISOCKET_RELAY_VIA="${ROBOT_SIDE_OMNISOCKET_RELAY_VIA:-}" export ROS_DISTRO="${ROS_DISTRO:-jazzy}" export ROBOT_RECEIVER_TRANSPORT="${ROBOT_RECEIVER_TRANSPORT:-unix_dgram}" export ROBOT_RECEIVER_SERVER_ADDR="${ROBOT_RECEIVER_SERVER_ADDR:-${ROBOT_SIDE_OMNISOCKET_SERVER_ADDR:-}}" export ROBOT_RECEIVER_RELAY_VIA="${ROBOT_RECEIVER_RELAY_VIA:-${ROBOT_SIDE_OMNISOCKET_RELAY_VIA:-}}" export ROBOT_RECEIVER_PEER_ID="${ROBOT_RECEIVER_PEER_ID:-ros-bridge-ctrl}" export ROBOT_RECEIVER_EXPECTED_SENDER="${ROBOT_RECEIVER_EXPECTED_SENDER:-}" export ROBOT_RECEIVER_LOCAL_SOCKET_PATH="${ROBOT_RECEIVER_LOCAL_SOCKET_PATH:-/tmp/omnisocket-b-side-cmd.sock}" export ROBOT_RECEIVER_OUTPUT_TOPIC="${ROBOT_RECEIVER_OUTPUT_TOPIC:-/hric/robot/cmd_vel}" export ROBOT_RECEIVER_FRAME_ID="${ROBOT_RECEIVER_FRAME_ID:-pelvis}" export ROBOT_RECEIVER_WATCHDOG_TIMEOUT="${ROBOT_RECEIVER_WATCHDOG_TIMEOUT:-0.5}" export ROBOT_RECEIVER_PUBLISH_RATE_HZ="${ROBOT_RECEIVER_PUBLISH_RATE_HZ:-100.0}" export OMNI_CAMERA_DEVICE="${OMNI_CAMERA_DEVICE:-/dev/video0}" export OMNI_CAMERA_PROFILE="${OMNI_CAMERA_PROFILE:-night}" export OMNI_CAMERA_BRIGHTNESS="${OMNI_CAMERA_BRIGHTNESS:-}" export OMNI_CAMERA_CUSTOM_CTRL="${OMNI_CAMERA_CUSTOM_CTRL:-}" export OMNI_CAMERA_VERIFY="${OMNI_CAMERA_VERIFY:-0}" export OMNI_GPSD_HOST="${OMNI_GPSD_HOST:-127.0.0.1}" export OMNI_VIDEO_SERVER_ADDR="${OMNI_VIDEO_SERVER_ADDR:-${ROBOT_SIDE_OMNISOCKET_SERVER_ADDR:-}}" export OMNI_VIDEO_RELAY_VIA="${OMNI_VIDEO_RELAY_VIA:-${ROBOT_SIDE_OMNISOCKET_RELAY_VIA:-}}" export OMNI_CONTROL_SERVER_ADDR="${OMNI_CONTROL_SERVER_ADDR:-${ROBOT_SIDE_OMNISOCKET_SERVER_ADDR:-}}" export OMNI_CONTROL_RELAY_VIA="${OMNI_CONTROL_RELAY_VIA:-${ROBOT_SIDE_OMNISOCKET_RELAY_VIA:-}}" export OMNI_CONTROL_UNIX_SOCKET_PATH="${OMNI_CONTROL_UNIX_SOCKET_PATH:-${ROBOT_RECEIVER_LOCAL_SOCKET_PATH}}" export B_SIDE_OMNID_USE_SUDO="${B_SIDE_OMNID_USE_SUDO:-1}" export BLITZ_RUNTIME_DIR="${BLITZ_RUNTIME_DIR:-${OMNISOCKETGO_ROOT}/logs/runtime}" export BLITZ_RUN_ROOT="${BLITZ_RUN_ROOT:-${OMNISOCKETGO_ROOT}/logs}" export BLITZ_RUN_CONTEXT_FILE="${BLITZ_RUN_CONTEXT_FILE:-${BLITZ_RUNTIME_DIR}/run-context.env}" export BLITZ_RUN_ID_FILE="${BLITZ_RUN_ID_FILE:-${BLITZ_RUNTIME_DIR}/run-id}" export BLITZ_CURRENT_RUN_LINK="${BLITZ_CURRENT_RUN_LINK:-${BLITZ_RUN_ROOT}/current}" export BLITZ_5G_INTERFACE="${BLITZ_5G_INTERFACE:-}" export BLITZ_5G_MODEM_SUBNET="${BLITZ_5G_MODEM_SUBNET:-192.168.224.0/22}" export BLITZ_5G_GATEWAY="${BLITZ_5G_GATEWAY:-192.168.225.1}" export BLITZ_5G_ROUTE_TARGETS="${BLITZ_5G_ROUTE_TARGETS:-106.55.173.235}" export BLITZ_5G_INFO_JSON="${BLITZ_5G_INFO_JSON:-${OMNISOCKETGO_ROOT}/scripts/boot/modem_network_info.json}" export BLITZ_TIME_SERVER_IP="${BLITZ_TIME_SERVER_IP:-}" export BLITZ_KCP_STATS_INTERVAL_MS="${BLITZ_KCP_STATS_INTERVAL_MS:-1000}" export BLITZ_CONTROL_LATENCY_LOG_ENABLED="${BLITZ_CONTROL_LATENCY_LOG_ENABLED:-1}" export BLITZ_CONTROL_LATENCY_LOG_SAMPLE_MOD="${BLITZ_CONTROL_LATENCY_LOG_SAMPLE_MOD:-100}" export BLITZ_5G_LINK_LOG_INTERVAL_SEC="${BLITZ_5G_LINK_LOG_INTERVAL_SEC:-5}" export BLITZ_JSONL_FLUSH_INTERVAL_MS="${BLITZ_JSONL_FLUSH_INTERVAL_MS:-1000}" export BLITZ_JSONL_FLUSH_BYTES="${BLITZ_JSONL_FLUSH_BYTES:-262144}" export BLITZ_JSONL_ROTATE_BYTES="${BLITZ_JSONL_ROTATE_BYTES:-134217728}" export BLITZ_JSONL_ROTATE_FILES="${BLITZ_JSONL_ROTATE_FILES:-8}" blitz_dev_utc_compact_timestamp() { date -u '+%Y%m%dT%H%M%SZ' } blitz_dev_git_commit() { git -C "${OMNISOCKETGO_ROOT}" rev-parse HEAD 2>/dev/null || true } blitz_dev_git_dirty_flag() { if git -C "${OMNISOCKETGO_ROOT}" diff --quiet --ignore-submodules=dirty >/dev/null 2>&1; then printf '0\n' return 0 fi printf '1\n' } blitz_dev_prepare_dirs() { mkdir -p "${BLITZ_RUNTIME_DIR}" "${BLITZ_RUN_ROOT}/runs" "${BLITZ_RUN_ROOT}/incidents" } blitz_dev_write_run_info() { local run_dir="$1" local run_id="$2" local boot_id="$3" local tmp_info="${run_dir}/run-info.json.tmp.$$" local started_at local commit_hash local dirty_flag started_at="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" commit_hash="$(blitz_dev_git_commit)" dirty_flag="$(blitz_dev_git_dirty_flag)" python3 - "${tmp_info}" "${run_id}" "${run_dir}" "${boot_id}" "${started_at}" "${commit_hash}" "${dirty_flag}" "${HOSTNAME:-$(hostname)}" <<'PY' import json import os import sys path, run_id, run_dir, boot_id, started_at, commit_hash, dirty_flag, hostname = sys.argv[1:9] payload = { "run_id": run_id, "run_dir": run_dir, "boot_id": boot_id, "started_at": started_at, "hostname": hostname, "git_commit": commit_hash, "git_dirty": dirty_flag == "1", "env": { key: os.environ.get(key, "") for key in sorted(os.environ) if key.startswith(("BLITZ_", "OMNI_", "ROBOT_RECEIVER_")) }, } with open(path, "w", encoding="utf-8") as handle: json.dump(payload, handle, ensure_ascii=False, indent=2, sort_keys=True) PY mv -f "${tmp_info}" "${run_dir}/run-info.json" } blitz_dev_init_run_context() { local run_id="${1:-$(blitz_dev_utc_compact_timestamp)}" local boot_id="dev-$(blitz_dev_utc_compact_timestamp)" local run_dir="${BLITZ_RUN_ROOT}/runs/${run_id}" local tmp_context="${BLITZ_RUN_CONTEXT_FILE}.tmp.$$" blitz_dev_prepare_dirs mkdir -p "${run_dir}" export BLITZ_RUN_ID="${run_id}" export BLITZ_RUN_DIR="${run_dir}" export BLITZ_BOOT_ID="${boot_id}" printf '%s\n' "${run_id}" > "${BLITZ_RUN_ID_FILE}" cat > "${tmp_context}" <