STM32 firmware: - safety.h/c: EstopSource enum, safety_remote_estop/clear/get/active() CDC 'E'=ESTOP_REMOTE, 'F'=ESTOP_CELLULAR_TIMEOUT, 'Z'=clear latch - usbd_cdc_if: cdc_estop_request/cdc_estop_clear_request volatile flags - status: status_update() +remote_estop param; both LEDs fast-blink 200ms - main.c: immediate motor cutoff highest-priority; arming gated by !safety_remote_estop_active(); motor estop auto-clear gated; telemetry 'es' field 0-4; status_update() updated to 5 args Safety: IMMEDIATE motor cutoff, latched until explicit Z + DISARMED, cannot re-arm via MQTT alone (requires RC arm hold). IWDG-safe. Jetson bridge: - remote_estop_node.py: paho-mqtt + pyserial, cellular watchdog 5s - estop_params.yaml, remote_estop.launch.py - setup.py / package.xml: register node + paho-mqtt dep - docker-compose.yml: remote-estop service - test_remote_estop.py: kill/clear/watchdog/latency unit tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
308 lines
9.5 KiB
YAML
308 lines
9.5 KiB
YAML
version: "3.8"
|
|
|
|
# Jetson Orin Nano Super — ROS2 Humble SLAM stack
|
|
# Run with: docker compose up -d
|
|
# Requires: NVIDIA Container Toolkit (JetPack 6) on host
|
|
|
|
services:
|
|
|
|
# ── Core ROS2 + SLAM node ─────────────────────────────────────────────────
|
|
saltybot-ros2:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-ros2
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
privileged: false
|
|
network_mode: host
|
|
environment:
|
|
- NVIDIA_VISIBLE_DEVICES=all
|
|
- NVIDIA_DRIVER_CAPABILITIES=all
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
- ROS_DOMAIN_ID=42
|
|
- DISPLAY=${DISPLAY:-:0}
|
|
volumes:
|
|
- /tmp/.X11-unix:/tmp/.X11-unix:rw
|
|
- ./ros2_ws/src:/ros2_ws/src:rw
|
|
- /mnt/nvme/saltybot/maps:/maps
|
|
- /mnt/nvme/saltybot:/data:rw
|
|
- ./config:/config:ro
|
|
devices:
|
|
- /dev/rplidar:/dev/rplidar
|
|
- /dev/stm32-bridge:/dev/stm32-bridge
|
|
- /dev/bus/usb:/dev/bus/usb
|
|
- /dev/i2c-7:/dev/i2c-7
|
|
- /dev/video0:/dev/video0
|
|
- /dev/video2:/dev/video2
|
|
- /dev/video4:/dev/video4
|
|
- /dev/video6:/dev/video6
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
ros2 launch saltybot_bringup slam_rtabmap.launch.py
|
|
"
|
|
|
|
# ── RPLIDAR driver node ────────────────────────────────────────────────────
|
|
rplidar:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-rplidar
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
network_mode: host
|
|
environment:
|
|
- ROS_DOMAIN_ID=42
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
devices:
|
|
- /dev/rplidar:/dev/rplidar
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
ros2 launch rplidar_ros rplidar_a1_launch.py
|
|
serial_port:=/dev/rplidar
|
|
frame_id:=laser
|
|
"
|
|
|
|
# ── RealSense D435i driver node ────────────────────────────────────────────
|
|
realsense:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-realsense
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
network_mode: host
|
|
environment:
|
|
- ROS_DOMAIN_ID=42
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
devices:
|
|
- /dev/bus/usb:/dev/bus/usb
|
|
volumes:
|
|
- /dev:/dev
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
ros2 launch realsense2_camera rs_launch.py
|
|
enable_color:=true
|
|
enable_depth:=true
|
|
enable_gyro:=true
|
|
enable_accel:=true
|
|
unite_imu_method:=linear_interpolation
|
|
depth_module.profile:=640x480x30
|
|
rgb_camera.profile:=640x480x30
|
|
"
|
|
|
|
# ── STM32 bridge node (bidirectional serial<->ROS2) ────────────────────────
|
|
stm32-bridge:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-stm32-bridge
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
network_mode: host
|
|
environment:
|
|
- ROS_DOMAIN_ID=42
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
devices:
|
|
- /dev/stm32-bridge:/dev/stm32-bridge
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
ros2 launch saltybot_bridge bridge.launch.py
|
|
mode:=bidirectional
|
|
serial_port:=/dev/stm32-bridge
|
|
"
|
|
|
|
# ── 4x IMX219 CSI cameras ──────────────────────────────────────────────────
|
|
csi-cameras:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-csi-cameras
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
network_mode: host
|
|
privileged: true
|
|
environment:
|
|
- ROS_DOMAIN_ID=42
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
- NVIDIA_VISIBLE_DEVICES=all
|
|
- NVIDIA_DRIVER_CAPABILITIES=all
|
|
devices:
|
|
- /dev/video0:/dev/video0
|
|
- /dev/video2:/dev/video2
|
|
- /dev/video4:/dev/video4
|
|
- /dev/video6:/dev/video6
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
ros2 launch saltybot_cameras csi_cameras.launch.py
|
|
width:=640
|
|
height:=480
|
|
fps:=30
|
|
"
|
|
|
|
# ── Surround vision — 360 bird's-eye view + Nav2 camera obstacle layer ─────
|
|
saltybot-surround:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-surround
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
network_mode: host
|
|
depends_on:
|
|
- csi-cameras
|
|
environment:
|
|
- NVIDIA_VISIBLE_DEVICES=all
|
|
- NVIDIA_DRIVER_CAPABILITIES=all
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
- ROS_DOMAIN_ID=42
|
|
volumes:
|
|
- ./ros2_ws/src:/ros2_ws/src:rw
|
|
- ./config:/config:ro
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
source /ros2_ws/install/local_setup.bash 2>/dev/null || true &&
|
|
ros2 launch saltybot_surround surround_vision.launch.py
|
|
start_cameras:=false
|
|
camera_height:=0.30
|
|
publish_rate:=5.0
|
|
"
|
|
|
|
# ── rosbridge WebSocket server — ws://jetson:9090 ──────────────────────────
|
|
rosbridge:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-rosbridge
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
network_mode: host
|
|
depends_on:
|
|
- saltybot-ros2
|
|
- stm32-bridge
|
|
- csi-cameras
|
|
environment:
|
|
- ROS_DOMAIN_ID=42
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
volumes:
|
|
- ./ros2_ws/src:/ros2_ws/src:rw
|
|
- ./config:/config:ro
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
source /ros2_ws/install/local_setup.bash 2>/dev/null || true &&
|
|
ros2 launch saltybot_bringup rosbridge.launch.py
|
|
"
|
|
|
|
|
|
# -- Remote e-stop bridge (MQTT over 4G -> STM32 CDC) ----------------------
|
|
# Subscribes to saltybot/estop MQTT topic. {"kill":true} -> 'E\r\n' to STM32.
|
|
# Cellular watchdog: 5s MQTT drop in AUTO mode -> 'F\r\n' (ESTOP_CELLULAR_TIMEOUT).
|
|
remote-estop:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-remote-estop
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
network_mode: host
|
|
depends_on:
|
|
- stm32-bridge
|
|
environment:
|
|
- ROS_DOMAIN_ID=42
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
devices:
|
|
- /dev/stm32-bridge:/dev/stm32-bridge
|
|
volumes:
|
|
- ./ros2_ws/src:/ros2_ws/src:rw
|
|
- ./config:/config:ro
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
source /ros2_ws/install/local_setup.bash 2>/dev/null || true &&
|
|
pip install paho-mqtt --quiet 2>/dev/null || true &&
|
|
ros2 launch saltybot_bridge remote_estop.launch.py
|
|
"
|
|
|
|
# ── Nav2 autonomous navigation stack ──────────────────────────────────────
|
|
saltybot-nav2:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-nav2
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
network_mode: host
|
|
depends_on:
|
|
- saltybot-ros2
|
|
environment:
|
|
- NVIDIA_VISIBLE_DEVICES=all
|
|
- NVIDIA_DRIVER_CAPABILITIES=all
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
- ROS_DOMAIN_ID=42
|
|
volumes:
|
|
- ./ros2_ws/src:/ros2_ws/src:rw
|
|
- ./config:/config:ro
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
source /ros2_ws/install/local_setup.bash 2>/dev/null || true &&
|
|
ros2 launch saltybot_bringup nav2.launch.py
|
|
"
|
|
|
|
# ── Outdoor navigation — OSM routing + GPS waypoints + geofence ───────────
|
|
# Start only for outdoor missions. Usage:
|
|
# ros2 param set /osm_router goal_lat 37.7749
|
|
# ros2 param set /osm_router goal_lon -122.4194
|
|
# ros2 service call /outdoor/plan_route std_srvs/srv/Trigger '{}'
|
|
# ros2 service call /outdoor/start_navigation std_srvs/srv/Trigger '{}'
|
|
saltybot-outdoor:
|
|
image: saltybot/ros2-humble:jetson-orin
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: saltybot-outdoor
|
|
restart: unless-stopped
|
|
runtime: nvidia
|
|
network_mode: host
|
|
depends_on:
|
|
- saltybot-nav2
|
|
environment:
|
|
- NVIDIA_VISIBLE_DEVICES=all
|
|
- NVIDIA_DRIVER_CAPABILITIES=all
|
|
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
- ROS_DOMAIN_ID=42
|
|
volumes:
|
|
- ./ros2_ws/src:/ros2_ws/src:rw
|
|
- ./config:/config:ro
|
|
- /mnt/nvme/saltybot/osm_cache:/data/osm_cache:rw
|
|
devices:
|
|
- /dev/sim7600:/dev/sim7600
|
|
command: >
|
|
bash -c "
|
|
source /opt/ros/humble/setup.bash &&
|
|
source /ros2_ws/install/local_setup.bash 2>/dev/null || true &&
|
|
ros2 launch saltybot_outdoor outdoor_nav.launch.py
|
|
use_rtk:=false
|
|
fence_active:=true
|
|
"
|
|
|
|
volumes:
|
|
saltybot-maps:
|
|
driver: local
|