diff --git a/scripts/dev/README.md b/scripts/dev/README.md new file mode 100644 index 0000000..803552d --- /dev/null +++ b/scripts/dev/README.md @@ -0,0 +1,81 @@ +# Dev Startup Scripts + +This directory lives inside the `OmniSocketGo` repo and acts as the main launch entry for the whole local setup. + +Default layout: + +```text +~/Documents/ + OmniSocketGo/ + scripts/dev/ + robot-command-center/ +``` + +The scripts assume: + +- `OmniSocketGo` is the current repo +- `robot-command-center` is a sibling directory next to it + +If your `robot-command-center` is elsewhere, set `ROBOT_COMMAND_CENTER_ROOT` in `robot-remote.env.local`. + +## Files + +- `robot-remote.env`: shared defaults for backend, frontend, ROS, and `b_side_omnid` +- `robot-remote.env.local`: optional local override file loaded after `robot-remote.env` +- `load-env.sh`: loads the shared environment into the current shell +- `start-backend.sh`: starts Django ASGI with `uvicorn` +- `start-frontend.sh`: starts the Vite dev server +- `start-ros-receiver.sh`: starts the ROS2 `udp_teleop_bridge` receiver +- `start-b-side-omnid.sh`: starts `./bin/b_side_omnid` and uses `sudo -E` by default +- `start-dev-tmux.sh`: optional one-command `tmux` launcher for all four processes + +## Usage + +Run these from the `OmniSocketGo` repo root: + +```bash +bash scripts/dev/start-backend.sh +bash scripts/dev/start-frontend.sh +bash scripts/dev/start-ros-receiver.sh +bash scripts/dev/start-b-side-omnid.sh +``` + +If you prefer one command and use `tmux`: + +```bash +bash scripts/dev/start-dev-tmux.sh +``` + +If you only want the shared environment for manual commands: + +```bash +source scripts/dev/load-env.sh +``` + +## Customizing + +Edit `scripts/dev/robot-remote.env` for shared changes such as: + +- `ROBOT_COMMAND_CENTER_ROOT` +- `CONTROL_SIDE_OMNISOCKET_SERVER_ADDR` +- `CONTROL_SIDE_OMNISOCKET_RELAY_VIA` +- `ROBOT_SIDE_OMNISOCKET_SERVER_ADDR` +- `ROBOT_SIDE_OMNISOCKET_RELAY_VIA` +- `VITE_API_BASE_URL` +- `OMNI_CAMERA_DEVICE` +- `OMNI_VIDEO_PEER_ID` +- `OMNI_CONTROL_PEER_ID` + +Role mapping: + +- `start-backend.sh` uses the `CONTROL_SIDE_*` address pair +- `start-b-side-omnid.sh` uses the `ROBOT_SIDE_*` address pair +- `start-ros-receiver.sh` defaults to the robot-side address pair, but with `transport=unix_dgram` it usually does not need the server address + +Put machine-specific overrides into `scripts/dev/robot-remote.env.local`. Example: + +```bash +ROBOT_COMMAND_CENTER_ROOT="$HOME/Documents/robot-command-center" +OMNI_CAMERA_DEVICE="/dev/video30" +B_SIDE_OMNID_USE_SUDO="0" +``` diff --git a/scripts/dev/load-env.sh b/scripts/dev/load-env.sh new file mode 100644 index 0000000..07eeed4 --- /dev/null +++ b/scripts/dev/load-env.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEFAULT_OMNISOCKETGO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +die() { + echo "$*" >&2 + return 1 2>/dev/null || exit 1 +} + +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" ]] +} + +export OMNISOCKETGO_ROOT="${OMNISOCKETGO_ROOT:-${DEFAULT_OMNISOCKETGO_ROOT}}" + +ENV_FILES=( + "${SCRIPT_DIR}/robot-remote.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 + +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 + +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 ${SCRIPT_DIR}/robot-remote.env.local if needed." +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 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_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}" diff --git a/scripts/dev/robot-remote.env b/scripts/dev/robot-remote.env new file mode 100644 index 0000000..87dc195 --- /dev/null +++ b/scripts/dev/robot-remote.env @@ -0,0 +1,49 @@ +# Optional absolute path override for the companion repo. +# By default the scripts assume: +# OmniSocketGo -> current repo +# robot-command-center -> sibling directory next to OmniSocketGo +# Example: +# ROBOT_COMMAND_CENTER_ROOT="$HOME/Documents/robot-command-center" + +CONTROL_SIDE_OMNISOCKET_SERVER_ADDR="81.70.156.140:10909" +CONTROL_SIDE_OMNISOCKET_RELAY_VIA="81.70.156.140:10909" + +ROBOT_SIDE_OMNISOCKET_SERVER_ADDR="81.70.156.140:10909" +ROBOT_SIDE_OMNISOCKET_RELAY_VIA="106.55.173.235:10909" + +CONTROL_WS_ALLOWED_ORIGINS="http://127.0.0.1:5173,http://localhost:5173" +VITE_API_BASE_URL="http://127.0.0.1:8001" + +PYTHON3_BIN="python3" +PYTHON_VENV_PATH="${OMNISOCKETGO_ROOT}/.venv" + +BACKEND_HOST="0.0.0.0" +BACKEND_PORT="8001" + +FRONTEND_HOST="0.0.0.0" +FRONTEND_PORT="5173" + +ROS_DISTRO="jazzy" +ROBOT_RECEIVER_TRANSPORT="unix_dgram" +ROBOT_RECEIVER_SERVER_ADDR="${ROBOT_SIDE_OMNISOCKET_SERVER_ADDR}" +ROBOT_RECEIVER_RELAY_VIA="${ROBOT_SIDE_OMNISOCKET_RELAY_VIA}" +ROBOT_RECEIVER_PEER_ID="ros-bridge-ctrl" +ROBOT_RECEIVER_EXPECTED_SENDER="" +ROBOT_RECEIVER_LOCAL_SOCKET_PATH="/tmp/omnisocket-b-side-cmd.sock" +ROBOT_RECEIVER_OUTPUT_TOPIC="/hric/robot/cmd_vel" +ROBOT_RECEIVER_FRAME_ID="pelvis" +ROBOT_RECEIVER_WATCHDOG_TIMEOUT="0.5" +ROBOT_RECEIVER_PUBLISH_RATE_HZ="100.0" + +OMNI_VIDEO_PEER_ID="peer-b-video" +OMNI_VIDEO_TARGET_PEER="peer-a-video" +OMNI_CAMERA_DEVICE="/dev/video26" +OMNI_VIDEO_SERVER_ADDR="${ROBOT_SIDE_OMNISOCKET_SERVER_ADDR}" +OMNI_VIDEO_RELAY_VIA="${ROBOT_SIDE_OMNISOCKET_RELAY_VIA}" +OMNI_CONTROL_PEER_ID="peer-b-ctrl" +OMNI_CONTROL_EXPECTED_SENDER="peer-a-ctrl" +OMNI_CONTROL_SERVER_ADDR="${ROBOT_SIDE_OMNISOCKET_SERVER_ADDR}" +OMNI_CONTROL_RELAY_VIA="${ROBOT_SIDE_OMNISOCKET_RELAY_VIA}" +OMNI_CONTROL_UNIX_SOCKET_PATH="${ROBOT_RECEIVER_LOCAL_SOCKET_PATH}" + +B_SIDE_OMNID_USE_SUDO="1" diff --git a/scripts/dev/start-b-side-omnid.sh b/scripts/dev/start-b-side-omnid.sh new file mode 100644 index 0000000..2c1dfdf --- /dev/null +++ b/scripts/dev/start-b-side-omnid.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/load-env.sh" + +cd "${OMNISOCKETGO_ROOT}" + +export OMNISOCKET_SERVER_ADDR="${ROBOT_SIDE_OMNISOCKET_SERVER_ADDR}" +export OMNISOCKET_RELAY_VIA="${ROBOT_SIDE_OMNISOCKET_RELAY_VIA}" +export OMNI_VIDEO_SERVER_ADDR="${OMNI_VIDEO_SERVER_ADDR}" +export OMNI_VIDEO_RELAY_VIA="${OMNI_VIDEO_RELAY_VIA}" +export OMNI_CONTROL_SERVER_ADDR="${OMNI_CONTROL_SERVER_ADDR}" +export OMNI_CONTROL_RELAY_VIA="${OMNI_CONTROL_RELAY_VIA}" + +if [[ ! -x "./bin/b_side_omnid" ]]; then + make b_side_omnid +fi + +if [[ "${B_SIDE_OMNID_USE_SUDO}" == "1" && "${EUID}" -ne 0 ]]; then + exec sudo -E ./bin/b_side_omnid +fi + +exec ./bin/b_side_omnid diff --git a/scripts/dev/start-backend.sh b/scripts/dev/start-backend.sh new file mode 100644 index 0000000..2ec10c4 --- /dev/null +++ b/scripts/dev/start-backend.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/load-env.sh" + +if [[ ! -d "${PYTHON_VENV_PATH}" ]]; then + "${PYTHON3_BIN}" -m venv "${PYTHON_VENV_PATH}" +fi + +# shellcheck disable=SC1091 +source "${PYTHON_VENV_PATH}/bin/activate" + +cd "${BACKEND_DIR}" +export OMNISOCKET_SERVER_ADDR="${CONTROL_SIDE_OMNISOCKET_SERVER_ADDR}" +export OMNISOCKET_RELAY_VIA="${CONTROL_SIDE_OMNISOCKET_RELAY_VIA}" +exec python -m uvicorn config.asgi:application --host "${BACKEND_HOST}" --port "${BACKEND_PORT}" diff --git a/scripts/dev/start-dev-tmux.sh b/scripts/dev/start-dev-tmux.sh new file mode 100644 index 0000000..e9c2fe2 --- /dev/null +++ b/scripts/dev/start-dev-tmux.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SESSION_NAME="${1:-robot-remote}" + +if ! command -v tmux >/dev/null 2>&1; then + echo "tmux is required for this launcher" >&2 + exit 1 +fi + +if tmux has-session -t "${SESSION_NAME}" 2>/dev/null; then + exec tmux attach -t "${SESSION_NAME}" +fi + +tmux new-session -d -s "${SESSION_NAME}" -n backend "bash -lc '${SCRIPT_DIR}/start-backend.sh'" +tmux new-window -t "${SESSION_NAME}:" -n frontend "bash -lc '${SCRIPT_DIR}/start-frontend.sh'" +tmux new-window -t "${SESSION_NAME}:" -n ros "bash -lc '${SCRIPT_DIR}/start-ros-receiver.sh'" +tmux new-window -t "${SESSION_NAME}:" -n b-side "bash -lc '${SCRIPT_DIR}/start-b-side-omnid.sh'" + +exec tmux attach -t "${SESSION_NAME}" diff --git a/scripts/dev/start-frontend.sh b/scripts/dev/start-frontend.sh new file mode 100644 index 0000000..2c53dc1 --- /dev/null +++ b/scripts/dev/start-frontend.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/load-env.sh" + +cd "${FRONTEND_DIR}" +exec npm run dev -- --host "${FRONTEND_HOST}" --port "${FRONTEND_PORT}" diff --git a/scripts/dev/start-ros-receiver.sh b/scripts/dev/start-ros-receiver.sh new file mode 100644 index 0000000..449432d --- /dev/null +++ b/scripts/dev/start-ros-receiver.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/load-env.sh" +# shellcheck disable=SC1091 +source "/opt/ros/${ROS_DISTRO}/setup.bash" + +cd "${ROS_CONTROL_PY_DIR}" +# shellcheck disable=SC1091 +source "install/setup.bash" + +exec ros2 launch udp_teleop_bridge robot_udp_receiver.launch.py \ + "transport:=${ROBOT_RECEIVER_TRANSPORT}" \ + "server_addr:=${ROBOT_RECEIVER_SERVER_ADDR}" \ + "relay_via:=${ROBOT_RECEIVER_RELAY_VIA}" \ + "peer_id:=${ROBOT_RECEIVER_PEER_ID}" \ + "expected_sender:=${ROBOT_RECEIVER_EXPECTED_SENDER}" \ + "local_socket_path:=${ROBOT_RECEIVER_LOCAL_SOCKET_PATH}" \ + "output_topic:=${ROBOT_RECEIVER_OUTPUT_TOPIC}" \ + "frame_id:=${ROBOT_RECEIVER_FRAME_ID}" \ + "watchdog_timeout:=${ROBOT_RECEIVER_WATCHDOG_TIMEOUT}" \ + "publish_rate_hz:=${ROBOT_RECEIVER_PUBLISH_RATE_HZ}"