feat: 启动命令不用环境变量
This commit is contained in:
81
scripts/dev/README.md
Normal file
81
scripts/dev/README.md
Normal file
@@ -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"
|
||||||
|
```
|
||||||
78
scripts/dev/load-env.sh
Normal file
78
scripts/dev/load-env.sh
Normal file
@@ -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}"
|
||||||
49
scripts/dev/robot-remote.env
Normal file
49
scripts/dev/robot-remote.env
Normal file
@@ -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"
|
||||||
25
scripts/dev/start-b-side-omnid.sh
Normal file
25
scripts/dev/start-b-side-omnid.sh
Normal file
@@ -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
|
||||||
18
scripts/dev/start-backend.sh
Normal file
18
scripts/dev/start-backend.sh
Normal file
@@ -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}"
|
||||||
21
scripts/dev/start-dev-tmux.sh
Normal file
21
scripts/dev/start-dev-tmux.sh
Normal file
@@ -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}"
|
||||||
9
scripts/dev/start-frontend.sh
Normal file
9
scripts/dev/start-frontend.sh
Normal file
@@ -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}"
|
||||||
24
scripts/dev/start-ros-receiver.sh
Normal file
24
scripts/dev/start-ros-receiver.sh
Normal file
@@ -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}"
|
||||||
Reference in New Issue
Block a user