diff --git a/docker/navigation/Dockerfile b/docker/navigation/Dockerfile
index 69378ea7c7..a109bf3b61 100644
--- a/docker/navigation/Dockerfile
+++ b/docker/navigation/Dockerfile
@@ -1,153 +1,297 @@
-# Base image with ROS Jazzy desktop full
-FROM osrf/ros:jazzy-desktop-full
+# =============================================================================
+# OPTIMIZED DOCKERFILE - Multi-stage build for reduced image size
+# =============================================================================
+#
+# Key optimizations:
+# 1. Use ros:jazzy-ros-base instead of desktop-full (~2.5 GB saved)
+# 2. Multi-stage build to discard build artifacts
+# 3. No Python ML dependencies (~14 GB saved)
+# 4. COPY dimos source for editable pip install (volume-mounted at runtime overlays it)
+# 5. Clean up build directories after compile (~800 MB saved)
+# 6. Minimal apt packages with --no-install-recommends
+# 7. DDS configuration for optimized ROS 2 communication
+#
+# Supported ROS distributions: jazzy, humble
+# Build with: docker build --build-arg ROS_DISTRO=humble ...
+#
+# =============================================================================
-# Set environment variables
+# Build argument for ROS distribution (default: humble)
+ARG ROS_DISTRO=humble
+
+# -----------------------------------------------------------------------------
+# STAGE 1: Build Stage - compile all C++ dependencies
+# -----------------------------------------------------------------------------
+FROM osrf/ros:${ROS_DISTRO}-desktop-full AS builder
+
+ARG ROS_DISTRO
ENV DEBIAN_FRONTEND=noninteractive
-ENV ROS_DISTRO=jazzy
+ENV ROS_DISTRO=${ROS_DISTRO}
ENV WORKSPACE=/ros2_ws
-ENV DIMOS_PATH=/workspace/dimos
-# Install system dependencies
-RUN apt-get update && apt-get install -y \
- # ROS packages
- ros-jazzy-pcl-ros \
- # Development tools
+# Install build dependencies only
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ # Build tools
git \
- git-lfs \
cmake \
build-essential \
python3-colcon-common-extensions \
- # PCL and system libraries
+ # Libraries needed for building
libpcl-dev \
libgoogle-glog-dev \
libgflags-dev \
libatlas-base-dev \
libeigen3-dev \
libsuitesparse-dev \
- # X11 and GUI support for RVIZ
- x11-apps \
- xorg \
- openbox \
- # Networking tools
- iputils-ping \
- net-tools \
- iproute2 \
- ethtool \
- # USB and serial tools (for hardware support)
- usbutils \
- udev \
- # Time synchronization (for multi-computer setup)
- chrony \
- # Editor (optional but useful)
- nano \
- vim \
- # Python tools
- python3-pip \
- python3-setuptools \
- python3-venv \
- # Additional dependencies for dimos
- ffmpeg \
- portaudio19-dev \
- libsndfile1 \
- # For OpenCV
- libgl1 \
- libglib2.0-0 \
- # For Open3D
- libgomp1 \
- # For TurboJPEG
- libturbojpeg0-dev \
- # Clean up
+ # ROS packages needed for build
+ ros-${ROS_DISTRO}-pcl-ros \
+ ros-${ROS_DISTRO}-cv-bridge \
&& rm -rf /var/lib/apt/lists/*
-# Create workspace directory
+# Create workspace
RUN mkdir -p ${WORKSPACE}/src
-# Copy the autonomy stack repository (should be cloned by build.sh)
+# Copy autonomy stack source
COPY docker/navigation/ros-navigation-autonomy-stack ${WORKSPACE}/src/ros-navigation-autonomy-stack
-# Set working directory
-WORKDIR ${WORKSPACE}
+# Compatibility fix: In Humble, cv_bridge uses .h extension, but Jazzy uses .hpp
+# Create a symlink so code written for Jazzy works on Humble
+RUN if [ "${ROS_DISTRO}" = "humble" ]; then \
+ CV_BRIDGE_DIR=$(find /opt/ros/humble/include -name "cv_bridge.h" -printf "%h\n" 2>/dev/null | head -1) && \
+ if [ -n "$CV_BRIDGE_DIR" ]; then \
+ ln -sf "$CV_BRIDGE_DIR/cv_bridge.h" "$CV_BRIDGE_DIR/cv_bridge.hpp"; \
+ echo "Created cv_bridge.hpp symlink in $CV_BRIDGE_DIR"; \
+ else \
+ echo "Warning: cv_bridge.h not found, skipping symlink creation"; \
+ fi; \
+ fi
-# Set up ROS environment
-RUN echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> ~/.bashrc
-
-# Build all hardware dependencies
-RUN \
- # Build Livox-SDK2 for Mid-360 lidar
- cd ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/utilities/livox_ros_driver2/Livox-SDK2 && \
+# Build Livox-SDK2
+RUN cd ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/utilities/livox_ros_driver2/Livox-SDK2 && \
mkdir -p build && cd build && \
cmake .. && make -j$(nproc) && make install && ldconfig && \
- # Install Sophus
- cd ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/dependency/Sophus && \
+ rm -rf ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/utilities/livox_ros_driver2/Livox-SDK2/build
+
+# Build Sophus
+RUN cd ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/dependency/Sophus && \
mkdir -p build && cd build && \
cmake .. -DBUILD_TESTS=OFF && make -j$(nproc) && make install && \
- # Install Ceres Solver
- cd ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/dependency/ceres-solver && \
+ rm -rf ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/dependency/Sophus/build
+
+# Build Ceres Solver
+RUN cd ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/dependency/ceres-solver && \
mkdir -p build && cd build && \
cmake .. && make -j$(nproc) && make install && \
- # Install GTSAM
- cd ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/dependency/gtsam && \
+ rm -rf ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/dependency/ceres-solver/build
+
+# Build GTSAM
+RUN cd ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/dependency/gtsam && \
mkdir -p build && cd build && \
cmake .. -DGTSAM_USE_SYSTEM_EIGEN=ON -DGTSAM_BUILD_WITH_MARCH_NATIVE=OFF && \
- make -j$(nproc) && make install && ldconfig
+ make -j$(nproc) && make install && ldconfig && \
+ rm -rf ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/dependency/gtsam/build
-# Build the autonomy stack
+# Build ROS workspace (no --symlink-install for multi-stage build compatibility)
RUN /bin/bash -c "source /opt/ros/${ROS_DISTRO}/setup.bash && \
cd ${WORKSPACE} && \
- colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release"
+ colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release"
-# Source the workspace setup
-RUN echo "source ${WORKSPACE}/install/setup.bash" >> ~/.bashrc
+# -----------------------------------------------------------------------------
+# STAGE 2: Runtime Stage - minimal image for running
+# -----------------------------------------------------------------------------
+ARG ROS_DISTRO
+FROM osrf/ros:${ROS_DISTRO}-desktop-full AS runtime
-# Create directory for Unity environment models
-RUN mkdir -p ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/base_autonomy/vehicle_simulator/mesh/unity
+ARG ROS_DISTRO
+ENV DEBIAN_FRONTEND=noninteractive
+ENV ROS_DISTRO=${ROS_DISTRO}
+ENV WORKSPACE=/ros2_ws
+ENV DIMOS_PATH=/workspace/dimos
-# Copy the dimos repository
-RUN mkdir -p ${DIMOS_PATH}
-COPY . ${DIMOS_PATH}/
+# DDS Configuration - Use FastDDS (default ROS 2 middleware)
+ENV RMW_IMPLEMENTATION=rmw_fastrtps_cpp
+ENV FASTRTPS_DEFAULT_PROFILES_FILE=/ros2_ws/config/fastdds.xml
-# Create a virtual environment in /opt (not in /workspace/dimos)
-# This ensures the venv won't be overwritten when we mount the host dimos directory
-# The container will always use its own dependencies, independent of the host
-RUN python3 -m venv /opt/dimos-venv
+# Install runtime dependencies only (no build tools)
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ # ROS packages
+ ros-${ROS_DISTRO}-pcl-ros \
+ ros-${ROS_DISTRO}-cv-bridge \
+ ros-${ROS_DISTRO}-foxglove-bridge \
+ ros-${ROS_DISTRO}-rviz2 \
+ ros-${ROS_DISTRO}-rqt* \
+ # DDS middleware (FastDDS is default, just ensure it's installed)
+ ros-${ROS_DISTRO}-rmw-fastrtps-cpp \
+ # Runtime libraries
+ libpcl-dev \
+ libgoogle-glog-dev \
+ libgflags-dev \
+ libatlas-base-dev \
+ libeigen3-dev \
+ libsuitesparse-dev \
+ # X11 for GUI (minimal)
+ libx11-6 \
+ libxext6 \
+ libxrender1 \
+ libgl1 \
+ libglib2.0-0 \
+ # Networking tools
+ iputils-ping \
+ net-tools \
+ iproute2 \
+ # Serial/USB for hardware
+ usbutils \
+ # Python (minimal)
+ python3-pip \
+ python3-venv \
+ # Joystick support
+ joystick \
+ # Time sync for multi-computer setups
+ chrony \
+ && rm -rf /var/lib/apt/lists/*
-# Activate Python virtual environment in interactive shells
-RUN echo "source /opt/dimos-venv/bin/activate" >> ~/.bashrc
+# Copy installed libraries from builder
+COPY --from=builder /usr/local/lib /usr/local/lib
+COPY --from=builder /usr/local/include /usr/local/include
-# Install Python dependencies for dimos
-WORKDIR ${DIMOS_PATH}
-RUN /bin/bash -c "source /opt/dimos-venv/bin/activate && \
- pip install --upgrade pip setuptools wheel && \
- pip install -e .[cpu,dev] 'mmengine>=0.10.3' 'mmcv>=2.1.0'"
+RUN ldconfig
+
+# Copy built ROS workspace from builder
+COPY --from=builder ${WORKSPACE}/install ${WORKSPACE}/install
+
+# Copy only config/rviz files from src (not the large dependency folders)
+# These are needed if running without volume mount
+COPY --from=builder ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/base_autonomy/vehicle_simulator/rviz ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/base_autonomy/vehicle_simulator/rviz
+COPY --from=builder ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/route_planner/far_planner/rviz ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/route_planner/far_planner/rviz
+COPY --from=builder ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/exploration_planner/tare_planner/rviz ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/exploration_planner/tare_planner/rviz
+COPY --from=builder ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/arise_slam_mid360/config ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/slam/arise_slam_mid360/config
+COPY --from=builder ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/utilities/livox_ros_driver2/config ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/utilities/livox_ros_driver2/config
+
+# Copy simulation shell scripts (real robot mode uses volume mount)
+COPY --from=builder ${WORKSPACE}/src/ros-navigation-autonomy-stack/system_simulation*.sh ${WORKSPACE}/src/ros-navigation-autonomy-stack/
+
+# Create directories
+RUN mkdir -p ${DIMOS_PATH} \
+ ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/base_autonomy/vehicle_simulator/mesh/unity \
+ ${WORKSPACE}/bagfiles \
+ ${WORKSPACE}/logs \
+ ${WORKSPACE}/config
+
+# Create FastDDS configuration file
+RUN cat > ${WORKSPACE}/config/fastdds.xml <<'EOF'
+
+
+
+
+
+ ros2_navigation_participant
+
+
+ SIMPLE
+
+ 10
+ 0
+
+
+ 3
+ 0
+
+
+
+
+ 10485760
+ 10485760
+ true
+
+
+
+
+
+
+ udp_transport
+ UDPv4
+ 10485760
+ 10485760
+ 65500
+
+
+
+ shm_transport
+ SHM
+ 10485760
+ 1048576
+
+
+
+EOF
+
+# Install portaudio for unitree-webrtc-connect (pyaudio dependency)
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ portaudio19-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+# Create Python venv and install dependencies
+RUN python3 -m venv /opt/dimos-venv && \
+ /opt/dimos-venv/bin/pip install --no-cache-dir \
+ pyyaml
+
+# Copy dimos source and install as editable package
+# The volume mount at runtime will overlay /workspace/dimos, but the editable
+# install creates a link that will use the volume-mounted files
+COPY pyproject.toml setup.py /workspace/dimos/
+COPY dimos /workspace/dimos/dimos
+RUN /opt/dimos-venv/bin/pip install --no-cache-dir -e "/workspace/dimos[unitree]"
+
+# Set up shell environment
+RUN echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> ~/.bashrc && \
+ echo "source ${WORKSPACE}/install/setup.bash" >> ~/.bashrc && \
+ echo "source /opt/dimos-venv/bin/activate" >> ~/.bashrc && \
+ echo "export RMW_IMPLEMENTATION=rmw_fastrtps_cpp" >> ~/.bashrc && \
+ echo "export FASTRTPS_DEFAULT_PROFILES_FILE=/ros2_ws/config/fastdds.xml" >> ~/.bashrc
# Copy helper scripts
COPY docker/navigation/run_both.sh /usr/local/bin/run_both.sh
COPY docker/navigation/ros_launch_wrapper.py /usr/local/bin/ros_launch_wrapper.py
-RUN chmod +x /usr/local/bin/run_both.sh /usr/local/bin/ros_launch_wrapper.py
+COPY docker/navigation/foxglove_utility/twist_relay.py /usr/local/bin/twist_relay.py
+COPY docker/navigation/foxglove_utility/goal_autonomy_relay.py /usr/local/bin/goal_autonomy_relay.py
+RUN chmod +x /usr/local/bin/run_both.sh /usr/local/bin/ros_launch_wrapper.py /usr/local/bin/twist_relay.py /usr/local/bin/goal_autonomy_relay.py
-# Set up udev rules for USB devices (motor controller)
-RUN echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", MODE="0666", GROUP="dialout"' > /etc/udev/rules.d/99-motor-controller.rules && \
- usermod -a -G dialout root || true
+# Set up udev rules for motor controller
+RUN mkdir -p /etc/udev/rules.d && \
+ echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", MODE="0666", GROUP="dialout"' \
+ > /etc/udev/rules.d/99-motor-controller.rules
# Set up entrypoint script
RUN echo '#!/bin/bash\n\
set -e\n\
\n\
-git config --global --add safe.directory /workspace/dimos\n\
+# Mark git directories as safe\n\
+git config --global --add safe.directory /workspace/dimos 2>/dev/null || true\n\
+git config --global --add safe.directory /ros2_ws/src/ros-navigation-autonomy-stack 2>/dev/null || true\n\
\n\
# Source ROS setup\n\
source /opt/ros/${ROS_DISTRO}/setup.bash\n\
source ${WORKSPACE}/install/setup.bash\n\
\n\
-# Activate Python virtual environment for dimos\n\
+# Activate Python virtual environment\n\
source /opt/dimos-venv/bin/activate\n\
\n\
+# DDS Configuration (FastDDS)\n\
+export RMW_IMPLEMENTATION=rmw_fastrtps_cpp\n\
+export FASTRTPS_DEFAULT_PROFILES_FILE=/ros2_ws/config/fastdds.xml\n\
+\n\
+# Use custom DDS config if provided via mount\n\
+if [ -f "/ros2_ws/config/custom_fastdds.xml" ]; then\n\
+ export FASTRTPS_DEFAULT_PROFILES_FILE=/ros2_ws/config/custom_fastdds.xml\n\
+ echo "Using custom FastDDS configuration"\n\
+fi\n\
+\n\
# Export ROBOT_CONFIG_PATH for autonomy stack\n\
export ROBOT_CONFIG_PATH="${ROBOT_CONFIG_PATH:-mechanum_drive}"\n\
\n\
# Hardware-specific configurations\n\
if [ "${HARDWARE_MODE}" = "true" ]; then\n\
- # Set network buffer sizes for WiFi data transmission (if needed)\n\
+ # Set network buffer sizes for WiFi data transmission\n\
if [ "${ENABLE_WIFI_BUFFER}" = "true" ]; then\n\
sysctl -w net.core.rmem_max=67108864 net.core.rmem_default=67108864 2>/dev/null || true\n\
sysctl -w net.core.wmem_max=67108864 net.core.wmem_default=67108864 2>/dev/null || true\n\
@@ -157,9 +301,6 @@ if [ "${HARDWARE_MODE}" = "true" ]; then\n\
if [ -n "${LIDAR_INTERFACE}" ] && [ -n "${LIDAR_COMPUTER_IP}" ]; then\n\
ip addr add ${LIDAR_COMPUTER_IP}/24 dev ${LIDAR_INTERFACE} 2>/dev/null || true\n\
ip link set ${LIDAR_INTERFACE} up 2>/dev/null || true\n\
- if [ -n "${LIDAR_GATEWAY}" ]; then\n\
- ip route add default via ${LIDAR_GATEWAY} dev ${LIDAR_INTERFACE} 2>/dev/null || true\n\
- fi\n\
fi\n\
\n\
# Generate MID360_config.json if LIDAR_COMPUTER_IP and LIDAR_IP are set\n\
@@ -207,6 +348,9 @@ if [ "${HARDWARE_MODE}" = "true" ]; then\n\
]\n\
}\n\
EOF\n\
+ # Also copy to installed location where the driver actually reads from\n\
+ cp ${WORKSPACE}/src/ros-navigation-autonomy-stack/src/utilities/livox_ros_driver2/config/MID360_config.json \\\n\
+ ${WORKSPACE}/install/livox_ros_driver2/share/livox_ros_driver2/config/MID360_config.json 2>/dev/null || true\n\
echo "Generated MID360_config.json with LIDAR_COMPUTER_IP=${LIDAR_COMPUTER_IP} and LIDAR_IP=${LIDAR_IP}"\n\
fi\n\
\n\
@@ -214,13 +358,15 @@ EOF\n\
if [ -n "${ROBOT_IP}" ]; then\n\
echo "Robot IP configured on local network: ${ROBOT_IP}"\n\
fi\n\
- \n\
fi\n\
\n\
# Execute the command\n\
exec "$@"' > /ros_entrypoint.sh && \
chmod +x /ros_entrypoint.sh
+# Working directory
+WORKDIR ${DIMOS_PATH}
+
# Set the entrypoint
ENTRYPOINT ["/ros_entrypoint.sh"]
diff --git a/docker/navigation/Overwatch.json b/docker/navigation/Overwatch.json
new file mode 100644
index 0000000000..8b2bc7bc55
--- /dev/null
+++ b/docker/navigation/Overwatch.json
@@ -0,0 +1,486 @@
+{
+ "_watermark": {
+ "creator": "DIMOS Navigation",
+ "author": "bona",
+ "organization": "iServe Robotics",
+ "version": "1.0",
+ "timestamp": "2026-01-17",
+ "signature": "dimos-nav-overwatch-layout"
+ },
+ "configById": {
+ "3D!main": {
+ "cameraState": {
+ "perspective": true,
+ "distance": 11.376001845526945,
+ "phi": 60.000000000004256,
+ "thetaOffset": -154.65065502183666,
+ "targetOffset": [
+ -1.3664627921863377,
+ -0.4507491962036497,
+ 2.2548288625522925e-16
+ ],
+ "target": [
+ 0,
+ 0,
+ 0
+ ],
+ "targetOrientation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fovy": 45,
+ "near": 0.01,
+ "far": 5000
+ },
+ "followMode": "follow-none",
+ "followTf": "map",
+ "scene": {
+ "backgroundColor": "#000000",
+ "enableStats": false,
+ "syncCamera": false
+ },
+ "transforms": {
+ "frame:vehicle": {
+ "visible": true
+ },
+ "frame:map": {
+ "visible": true
+ }
+ },
+ "topics": {
+ "/registered_scan": {
+ "visible": true,
+ "colorField": "z",
+ "colorMode": "colormap",
+ "flatColor": "#ffffff",
+ "pointSize": 2,
+ "decayTime": 1
+ },
+ "/overall_map": {
+ "visible": true,
+ "colorField": "intensity",
+ "colorMode": "flat",
+ "flatColor": "#ffffff",
+ "pointSize": 2,
+ "decayTime": 0
+ },
+ "/free_paths": {
+ "visible": true,
+ "colorField": "intensity",
+ "colorMode": "colormap",
+ "colorMap": "turbo",
+ "pointSize": 2
+ },
+ "/path": {
+ "visible": true,
+ "type": "line",
+ "lineWidth": 0.05,
+ "color": "#19ff00"
+ },
+ "/way_point": {
+ "visible": true,
+ "color": "#cc29cc"
+ },
+ "/navigation_boundary": {
+ "visible": true,
+ "color": "#00ff00",
+ "lineWidth": 0.2
+ },
+ "/goal_pose": {
+ "visible": true,
+ "color": "#ff1900",
+ "type": "arrow"
+ },
+ "/terrain_map": {
+ "visible": false,
+ "colorField": "intensity",
+ "colorMode": "colormap",
+ "colorMap": "rainbow",
+ "pointSize": 4
+ },
+ "/terrain_map_ext": {
+ "visible": false,
+ "colorField": "intensity",
+ "colorMode": "colormap",
+ "colorMap": "rainbow",
+ "pointSize": 4
+ },
+ "/sensor_scan": {
+ "visible": false,
+ "colorField": "intensity",
+ "colorMode": "flat",
+ "flatColor": "#ffffff",
+ "pointSize": 2
+ },
+ "/added_obstacles": {
+ "visible": false,
+ "colorField": "intensity",
+ "colorMode": "flat",
+ "flatColor": "#ff1900",
+ "pointSize": 3
+ },
+ "/explored_areas": {
+ "visible": false,
+ "colorField": "intensity",
+ "colorMode": "flat",
+ "flatColor": "#00aaff",
+ "pointSize": 2
+ },
+ "/trajectory": {
+ "visible": false,
+ "colorField": "intensity",
+ "colorMode": "colormap",
+ "colorMap": "rainbow",
+ "pointSize": 7
+ },
+ "/viz_graph_topic": {
+ "visible": true,
+ "namespaces": {
+ "angle_direct": { "visible": false },
+ "boundary_edge": { "visible": false },
+ "boundary_vertex": { "visible": false },
+ "freespace_vertex": { "visible": false },
+ "freespace_vgraph": { "visible": true },
+ "frontier_vertex": { "visible": false },
+ "global_vertex": { "visible": false },
+ "global_vgraph": { "visible": false },
+ "localrange_vertex": { "visible": false },
+ "odom_edge": { "visible": false },
+ "polygon_edge": { "visible": true },
+ "to_goal_edge": { "visible": false },
+ "trajectory_edge": { "visible": false },
+ "trajectory_vertex": { "visible": false },
+ "updating_vertex": { "visible": false },
+ "vertex_angle": { "visible": false },
+ "vertices_matches": { "visible": false },
+ "visibility_edge": { "visible": false }
+ }
+ }
+ },
+ "layers": {
+ "grid": {
+ "layerId": "foxglove.Grid",
+ "visible": true,
+ "frameLocked": true,
+ "label": "Grid",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "color": "#248f24",
+ "size": 100,
+ "divisions": 100,
+ "lineWidth": 1,
+ "frameId": "map"
+ }
+ },
+ "publish": {
+ "type": "pose",
+ "poseTopic": "/goal_pose",
+ "pointTopic": "/way_point",
+ "poseEstimateTopic": "/initialpose",
+ "poseEstimateXDeviation": 0.5,
+ "poseEstimateYDeviation": 0.5,
+ "poseEstimateThetaDeviation": 0.26179939,
+ "frameId": "map"
+ },
+ "imageMode": {},
+ "fixedFrame": "map"
+ },
+ "Image!camera": {
+ "cameraTopic": "/camera/image",
+ "enabledMarkerTopics": [],
+ "synchronize": false,
+ "transformMarkers": true,
+ "smooth": false,
+ "flipHorizontal": false,
+ "flipVertical": false,
+ "minValue": 0,
+ "maxValue": 1,
+ "rotation": 0,
+ "cameraState": {
+ "distance": 20,
+ "perspective": true,
+ "phi": 60,
+ "target": [
+ 0,
+ 0,
+ 0
+ ],
+ "targetOffset": [
+ 0,
+ 0,
+ 0
+ ],
+ "targetOrientation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "thetaOffset": 45,
+ "fovy": 45,
+ "near": 0.5,
+ "far": 5000
+ },
+ "followMode": "follow-pose",
+ "scene": {},
+ "transforms": {},
+ "topics": {},
+ "layers": {},
+ "publish": {
+ "type": "point",
+ "poseTopic": "/move_base_simple/goal",
+ "pointTopic": "/clicked_point",
+ "poseEstimateTopic": "/initialpose",
+ "poseEstimateXDeviation": 0.5,
+ "poseEstimateYDeviation": 0.5,
+ "poseEstimateThetaDeviation": 0.26179939
+ },
+ "imageMode": {}
+ },
+ "Image!semantic": {
+ "cameraTopic": "/camera/semantic_image",
+ "enabledMarkerTopics": [],
+ "synchronize": false,
+ "transformMarkers": true,
+ "smooth": false,
+ "flipHorizontal": false,
+ "flipVertical": false,
+ "minValue": 0,
+ "maxValue": 1,
+ "rotation": 0,
+ "cameraState": {
+ "distance": 20,
+ "perspective": true,
+ "phi": 60,
+ "target": [
+ 0,
+ 0,
+ 0
+ ],
+ "targetOffset": [
+ 0,
+ 0,
+ 0
+ ],
+ "targetOrientation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "thetaOffset": 45,
+ "fovy": 45,
+ "near": 0.5,
+ "far": 5000
+ },
+ "followMode": "follow-pose",
+ "scene": {},
+ "transforms": {},
+ "topics": {},
+ "layers": {},
+ "publish": {
+ "type": "point",
+ "poseTopic": "/move_base_simple/goal",
+ "pointTopic": "/clicked_point",
+ "poseEstimateTopic": "/initialpose",
+ "poseEstimateXDeviation": 0.5,
+ "poseEstimateYDeviation": 0.5,
+ "poseEstimateThetaDeviation": 0.26179939
+ },
+ "imageMode": {}
+ },
+ "Teleop!teleop": {
+ "topic": "/foxglove_teleop",
+ "publishRate": 10,
+ "upButton": {
+ "field": "linear.x",
+ "value": 0.5
+ },
+ "downButton": {
+ "field": "linear.x",
+ "value": -0.5
+ },
+ "leftButton": {
+ "field": "angular.z",
+ "value": 0.5
+ },
+ "rightButton": {
+ "field": "angular.z",
+ "value": -0.5
+ }
+ },
+ "RawMessages!odom": {
+ "diffEnabled": false,
+ "diffMethod": "custom",
+ "diffTopicPath": "",
+ "showFullMessageForDiff": false,
+ "topicPath": "/state_estimation.pose.pose"
+ },
+ "RawMessages!cmdvel": {
+ "diffEnabled": false,
+ "diffMethod": "custom",
+ "diffTopicPath": "",
+ "showFullMessageForDiff": false,
+ "topicPath": "/cmd_vel.twist",
+ "fontSize": 12
+ },
+ "Plot!speed": {
+ "paths": [
+ {
+ "value": "/cmd_vel.twist.linear.x",
+ "enabled": true,
+ "timestampMethod": "receiveTime",
+ "label": "Linear X"
+ },
+ {
+ "value": "/cmd_vel.twist.linear.y",
+ "enabled": true,
+ "timestampMethod": "receiveTime",
+ "label": "Linear Y"
+ },
+ {
+ "value": "/cmd_vel.twist.angular.z",
+ "enabled": true,
+ "timestampMethod": "receiveTime",
+ "label": "Angular Z"
+ }
+ ],
+ "showXAxisLabels": true,
+ "showYAxisLabels": true,
+ "showLegend": true,
+ "legendDisplay": "floating",
+ "showPlotValuesInLegend": true,
+ "isSynced": true,
+ "xAxisVal": "timestamp",
+ "sidebarDimension": 240,
+ "minYValue": -2,
+ "maxYValue": 2,
+ "followingViewWidth": 30
+ },
+ "Indicator!goalreached": {
+ "path": "/goal_reached.data",
+ "style": "background",
+ "fallbackColor": "#a0a0a0",
+ "fallbackLabel": "No Data",
+ "rules": [
+ {
+ "operator": "=",
+ "rawValue": "true",
+ "color": "#68e24a",
+ "label": "Goal Reached"
+ },
+ {
+ "operator": "=",
+ "rawValue": "false",
+ "color": "#f5f5f5",
+ "label": "Navigating"
+ }
+ ],
+ "fontSize": 36
+ },
+ "Indicator!stop": {
+ "path": "/stop.data",
+ "style": "background",
+ "fallbackColor": "#a0a0a0",
+ "fallbackLabel": "No Data",
+ "rules": [
+ {
+ "operator": "=",
+ "rawValue": "0",
+ "color": "#68e24a",
+ "label": "OK"
+ },
+ {
+ "operator": "=",
+ "rawValue": "1",
+ "color": "#f5ba42",
+ "label": "Speed Stop"
+ },
+ {
+ "operator": "=",
+ "rawValue": "2",
+ "color": "#eb4034",
+ "label": "Full Stop"
+ }
+ ],
+ "fontSize": 36
+ },
+ "Indicator!autonomy": {
+ "path": "/joy.axes[2]",
+ "style": "background",
+ "fallbackColor": "#a0a0a0",
+ "fallbackLabel": "No Joystick",
+ "rules": [
+ {
+ "operator": "<",
+ "rawValue": "-0.1",
+ "color": "#68e24a",
+ "label": "Autonomy ON"
+ },
+ {
+ "operator": ">=",
+ "rawValue": "-0.1",
+ "color": "#eb4034",
+ "label": "Autonomy OFF"
+ }
+ ],
+ "fontSize": 36
+ }
+ },
+ "globalVariables": {},
+ "userNodes": {},
+ "playbackConfig": {
+ "speed": 1
+ },
+ "layout": {
+ "first": {
+ "first": "3D!main",
+ "second": {
+ "first": "Image!camera",
+ "second": "Image!semantic",
+ "direction": "column",
+ "splitPercentage": 50
+ },
+ "direction": "row",
+ "splitPercentage": 70
+ },
+ "second": {
+ "first": {
+ "first": "Teleop!teleop",
+ "second": {
+ "first": "Indicator!autonomy",
+ "second": {
+ "first": "Indicator!goalreached",
+ "second": "Indicator!stop",
+ "direction": "row",
+ "splitPercentage": 50
+ },
+ "direction": "row",
+ "splitPercentage": 33
+ },
+ "direction": "row",
+ "splitPercentage": 40
+ },
+ "second": {
+ "first": "RawMessages!cmdvel",
+ "second": "Plot!speed",
+ "direction": "row",
+ "splitPercentage": 30
+ },
+ "direction": "column",
+ "splitPercentage": 40
+ },
+ "direction": "column",
+ "splitPercentage": 75
+ }
+}
\ No newline at end of file
diff --git a/docker/navigation/README.md b/docker/navigation/README.md
index 1505786914..66fc238dbe 100644
--- a/docker/navigation/README.md
+++ b/docker/navigation/README.md
@@ -16,28 +16,39 @@ This is an optimistic overview. Use the commands below for an in depth version.
```bash
cd docker/navigation
-./build.sh
+./build.sh --humble # Build with ROS 2 Humble (default)
+# or
+./build.sh --jazzy # Build with ROS 2 Jazzy
```
This will:
-- Clone the ros-navigation-autonomy-stack repository (jazzy branch)
+- Clone the ros-navigation-autonomy-stack repository (matching branch: humble or jazzy)
- Build a Docker image with both ROS and DimOS dependencies
- Set up the environment for both systems
-Note that the build will take over 10 minutes and build an image over 30GiB.
+The resulting image will be named `dimos_autonomy_stack:humble` or `dimos_autonomy_stack:jazzy` depending on the option used.
+
+Note that the build will take a while and produce an image of approximately 24 GB.
**Run the simulator to test it's working:**
+Use the same ROS distribution flag as your build:
+
```bash
-./start.sh --simulation
+./start.sh --simulation --humble # If built with --humble
+# or
+./start.sh --simulation --jazzy # If built with --jazzy
```
-## Manual build
+
+Manual build
-Go to the docker dir and clone the ROS navigation stack.
+Go to the docker dir and clone the ROS navigation stack (choose the branch matching your ROS distribution).
```bash
cd docker/navigation
+git clone -b humble git@github.com:dimensionalOS/ros-navigation-autonomy-stack.git
+# or
git clone -b jazzy git@github.com:dimensionalOS/ros-navigation-autonomy-stack.git
```
@@ -50,13 +61,17 @@ tar -xf ../../data/.lfs/office_building_1.tar.gz
mv office_building_1 unity_models
```
-Then, go back to the root and build the docker image:
+Then, go back to the root (from docker/navigation) and build the docker image:
```bash
-cd ../..
-docker compose -f docker/navigation/docker-compose.yml build
+cd ../.. # Back to dimos root
+ROS_DISTRO=humble docker compose -f docker/navigation/docker-compose.yml build
+# or
+ROS_DISTRO=jazzy docker compose -f docker/navigation/docker-compose.yml build
```
+
+
## On Real Hardware
### Configure the WiFi
@@ -77,36 +92,71 @@ cp .env.hardware .env
Key configuration parameters:
```bash
+# Robot Configuration
+ROBOT_CONFIG_PATH=unitree/unitree_go2 # Robot type (mechanum_drive, unitree/unitree_go2, unitree/unitree_g1)
+
# Lidar Configuration
LIDAR_INTERFACE=eth0 # Your ethernet interface (find with: ip link show)
LIDAR_COMPUTER_IP=192.168.1.5 # Computer IP on the lidar subnet
LIDAR_GATEWAY=192.168.1.1 # Gateway IP address for the lidar subnet
-LIDAR_IP=192.168.1.116 # Full IP address of your Mid-360 lidar
+LIDAR_IP=192.168.1.1xx # xx = last two digits from lidar QR code serial number
ROBOT_IP= # IP addres of robot on local network (if using WebRTC connection)
-# Motor Controller
-MOTOR_SERIAL_DEVICE=/dev/ttyACM0 # Serial device (check with: ls /dev/ttyACM*)
+# Special Configuration for Unitree G1 EDU
+For the Unitree G1 EDU, use these specific values:
+LIDAR_COMPUTER_IP=192.168.123.5
+LIDAR_GATEWAY=192.168.123.1
+LIDAR_IP=192.168.123.120
+ROBOT_IP=192.168.12.1 # For WebRTC local AP mode (optional, need additional wifi dongle)
```
-### Start the Container
+### Start the Navigation Stack
+
+#### Start with Route Planner automatically
+
+Use --humble or --jazzy matching your build:
+
+```bash
+./start.sh --hardware --humble --route-planner # Run route planner automatically
+./start.sh --hardware --humble --route-planner --rviz # Route planner + RViz2 visualization
+./start.sh --hardware --humble --dev # Development mode (mount src for config editing)
+```
-Start the container and leave it open.
+[Foxglove Studio](https://foxglove.dev/download) is the default visualization tool. It's ideal for remote operation - SSH with port forwarding to the robot's mini PC and run commands there:
```bash
-./start.sh --hardware
+ssh -L 8765:localhost:8765 user@robot-ip
+```
+
+Then on your local machine:
+1. Open Foxglove and connect to `ws://localhost:8765`
+2. Load the layout from `docker/navigation/Overwatch.json` (Layout menu → Import)
+3. Click in the 3D panel to drop a target pose (similar to RViz). The "Autonomy ON" indicator should be green, and "Goal Reached" will show when the robot arrives.
+
+
+Start manually
+
+Start the container and leave it open. Use the same ROS distribution flag as your build:
+
+```bash
+./start.sh --hardware --humble # If built with --humble
+# or
+./start.sh --hardware --jazzy # If built with --jazzy
```
It doesn't do anything by default. You have to run commands on it by `exec`-ing:
+To enter the container from another terminal:
+
```bash
docker exec -it dimos_hardware_container bash
```
-### In the container
+##### In the container
In the container to run the full navigation stack you must run both the dimensional python runfile with connection module and the navigation stack.
-#### Dimensional Python + Connection Module
+###### Dimensional Python + Connection Module
For the Unitree G1
```bash
@@ -114,7 +164,7 @@ dimos run unitree-g1
ROBOT_IP=XX.X.X.XXX dimos run unitree-g1 # If ROBOT_IP env variable is not set in .env
```
-#### Navigation Stack
+###### Navigation Stack
```bash
cd /ros2_ws/src/ros-navigation-autonomy-stack
@@ -122,3 +172,5 @@ cd /ros2_ws/src/ros-navigation-autonomy-stack
```
Now you can place goal points/poses in RVIZ by clicking the "Goalpoint" button. The robot will navigate to the point, running both local and global planners for dynamic obstacle avoidance.
+
+
diff --git a/docker/navigation/build.sh b/docker/navigation/build.sh
index da0aa2de8c..ea1729fb63 100755
--- a/docker/navigation/build.sh
+++ b/docker/navigation/build.sh
@@ -4,20 +4,79 @@ set -e
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
+RED='\033[0;31m'
NC='\033[0m'
+# Default ROS distribution
+ROS_DISTRO="humble"
+
+# Parse command line arguments
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --humble)
+ ROS_DISTRO="humble"
+ shift
+ ;;
+ --jazzy)
+ ROS_DISTRO="jazzy"
+ shift
+ ;;
+ --help|-h)
+ echo "Usage: $0 [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " --humble Build with ROS 2 Humble (default)"
+ echo " --jazzy Build with ROS 2 Jazzy"
+ echo " --help, -h Show this help message"
+ echo ""
+ echo "Examples:"
+ echo " $0 # Build with ROS Humble (default)"
+ echo " $0 --jazzy # Build with ROS Jazzy"
+ echo " $0 --humble # Build with ROS Humble"
+ exit 0
+ ;;
+ *)
+ echo -e "${RED}Unknown option: $1${NC}"
+ echo "Run '$0 --help' for usage information"
+ exit 1
+ ;;
+ esac
+done
+
+export ROS_DISTRO
+
echo -e "${GREEN}================================================${NC}"
echo -e "${GREEN}Building DimOS + ROS Autonomy Stack Docker Image${NC}"
+echo -e "${GREEN}ROS Distribution: ${ROS_DISTRO}${NC}"
echo -e "${GREEN}================================================${NC}"
echo ""
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR"
+# Clone or checkout ros-navigation-autonomy-stack with dev branch
if [ ! -d "ros-navigation-autonomy-stack" ]; then
- echo -e "${YELLOW}Cloning ros-navigation-autonomy-stack repository...${NC}"
- git clone -b jazzy git@github.com:dimensionalOS/ros-navigation-autonomy-stack.git
+ echo -e "${YELLOW}Cloning ros-navigation-autonomy-stack repository (dev branch)...${NC}"
+ git clone -b dev git@github.com:dimensionalOS/ros-navigation-autonomy-stack.git
echo -e "${GREEN}Repository cloned successfully!${NC}"
+else
+ # Directory exists, ensure we're on the dev branch
+ cd ros-navigation-autonomy-stack
+ CURRENT_BRANCH=$(git branch --show-current)
+ if [ "$CURRENT_BRANCH" != "dev" ]; then
+ echo -e "${YELLOW}Switching from ${CURRENT_BRANCH} to dev branch...${NC}"
+ # Stash any local changes (e.g., auto-generated config files)
+ if git stash --quiet 2>/dev/null; then
+ echo -e "${YELLOW}Stashed local changes${NC}"
+ fi
+ git fetch origin dev
+ git checkout dev
+ git pull origin dev
+ echo -e "${GREEN}Switched to dev branch${NC}"
+ else
+ echo -e "${GREEN}Already on dev branch${NC}"
+ fi
+ cd ..
fi
if [ ! -d "unity_models" ]; then
@@ -29,7 +88,7 @@ fi
echo ""
echo -e "${YELLOW}Building Docker image with docker compose...${NC}"
echo "This will take a while as it needs to:"
-echo " - Download base ROS Jazzy image"
+echo " - Download base ROS ${ROS_DISTRO^} image"
echo " - Install ROS packages and dependencies"
echo " - Build the autonomy stack"
echo " - Build Livox-SDK2 for Mid-360 lidar"
@@ -42,18 +101,19 @@ cd ../..
docker compose -f docker/navigation/docker-compose.yml build
echo ""
-echo -e "${GREEN}================================${NC}"
+echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN}Docker image built successfully!${NC}"
-echo -e "${GREEN}================================${NC}"
+echo -e "${GREEN}Image: dimos_autonomy_stack:${ROS_DISTRO}${NC}"
+echo -e "${GREEN}============================================${NC}"
echo ""
echo "To run in SIMULATION mode:"
-echo -e "${YELLOW} ./start.sh${NC}"
+echo -e "${YELLOW} ./start.sh --${ROS_DISTRO}${NC}"
echo ""
echo "To run in HARDWARE mode:"
echo " 1. Configure your hardware settings in .env file"
echo " (copy from .env.hardware if needed)"
echo " 2. Run the hardware container:"
-echo -e "${YELLOW} ./start.sh --hardware${NC}"
+echo -e "${YELLOW} ./start.sh --hardware --${ROS_DISTRO}${NC}"
echo ""
echo "The script runs in foreground. Press Ctrl+C to stop."
echo ""
diff --git a/docker/navigation/docker-compose.yml b/docker/navigation/docker-compose.yml
index f26b7fbabd..d2abf73296 100644
--- a/docker/navigation/docker-compose.yml
+++ b/docker/navigation/docker-compose.yml
@@ -4,10 +4,16 @@ services:
build:
context: ../..
dockerfile: docker/navigation/Dockerfile
- image: dimos_autonomy_stack:jazzy
+ network: host
+ args:
+ ROS_DISTRO: ${ROS_DISTRO:-humble}
+ image: dimos_autonomy_stack:${ROS_DISTRO:-humble}
container_name: dimos_simulation_container
profiles: ["", "simulation"] # Active by default (empty profile) AND with --profile simulation
+ # Shared memory size for ROS 2 FastDDS
+ shm_size: '8gb'
+
# Enable interactive terminal
stdin_open: true
tty: true
@@ -15,6 +21,10 @@ services:
# Network configuration - required for ROS communication
network_mode: host
+ # Allow `ip link set ...` (needed by DimOS LCM autoconf) without requiring sudo
+ cap_add:
+ - NET_ADMIN
+
# Use nvidia runtime for GPU acceleration (falls back to runc if not available)
runtime: ${DOCKER_RUNTIME:-nvidia}
@@ -28,6 +38,9 @@ services:
- ROBOT_CONFIG_PATH=${ROBOT_CONFIG_PATH:-mechanum_drive}
- ROBOT_IP=${ROBOT_IP:-}
- HARDWARE_MODE=false
+ # DDS Configuration (FastDDS)
+ - RMW_IMPLEMENTATION=rmw_fastrtps_cpp
+ - FASTRTPS_DEFAULT_PROFILES_FILE=/ros2_ws/config/fastdds.xml
# Volume mounts
volumes:
@@ -38,9 +51,6 @@ services:
# Mount Unity environment models (if available)
- ./unity_models:/ros2_ws/src/ros-navigation-autonomy-stack/src/base_autonomy/vehicle_simulator/mesh/unity:rw
- # Mount the autonomy stack source for development
- - ./ros-navigation-autonomy-stack:/ros2_ws/src/ros-navigation-autonomy-stack:rw
-
# Mount entire dimos directory for live development
- ../..:/workspace/dimos:rw
@@ -66,10 +76,20 @@ services:
build:
context: ../..
dockerfile: docker/navigation/Dockerfile
- image: dimos_autonomy_stack:jazzy
+ network: host
+ args:
+ ROS_DISTRO: ${ROS_DISTRO:-humble}
+ image: dimos_autonomy_stack:${ROS_DISTRO:-humble}
container_name: dimos_hardware_container
profiles: ["hardware"]
+ # Shared memory size for ROS 2 FastDDS
+ shm_size: '8gb'
+
+ # Load environment from .env file
+ env_file:
+ - .env
+
# Enable interactive terminal
stdin_open: true
tty: true
@@ -83,9 +103,14 @@ services:
# Override runtime for GPU support
runtime: ${DOCKER_RUNTIME:-runc}
+ # Add host groups for device access (input for joystick, dialout for serial)
+ group_add:
+ - ${INPUT_GID:-995}
+ - ${DIALOUT_GID:-20}
+
# Hardware environment variables
environment:
- - DISPLAY=${DISPLAY}
+ - DISPLAY=${DISPLAY:-:0}
- QT_X11_NO_MITSHM=1
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=all
@@ -93,6 +118,9 @@ services:
- ROBOT_CONFIG_PATH=${ROBOT_CONFIG_PATH:-mechanum_drive}
- ROBOT_IP=${ROBOT_IP:-}
- HARDWARE_MODE=true
+ # DDS Configuration (FastDDS)
+ - RMW_IMPLEMENTATION=rmw_fastrtps_cpp
+ - FASTRTPS_DEFAULT_PROFILES_FILE=/ros2_ws/config/fastdds.xml
# Mid-360 Lidar configuration
- LIDAR_INTERFACE=${LIDAR_INTERFACE:-}
- LIDAR_COMPUTER_IP=${LIDAR_COMPUTER_IP:-192.168.1.5}
@@ -102,6 +130,13 @@ services:
- MOTOR_SERIAL_DEVICE=${MOTOR_SERIAL_DEVICE:-/dev/ttyACM0}
# Network optimization
- ENABLE_WIFI_BUFFER=true
+ # Route planner option
+ - USE_ROUTE_PLANNER=${USE_ROUTE_PLANNER:-false}
+ # RViz option
+ - USE_RVIZ=${USE_RVIZ:-false}
+ # Unitree robot configuration
+ - UNITREE_IP=${UNITREE_IP:-192.168.12.1}
+ - UNITREE_CONN=${UNITREE_CONN:-LocalAP}
# Volume mounts
volumes:
@@ -110,8 +145,6 @@ services:
- ${HOME}/.Xauthority:/root/.Xauthority:rw
# Mount Unity environment models (optional for hardware)
- ./unity_models:/ros2_ws/src/ros-navigation-autonomy-stack/src/base_autonomy/vehicle_simulator/mesh/unity:rw
- # Mount the autonomy stack source
- - ./ros-navigation-autonomy-stack:/ros2_ws/src/ros-navigation-autonomy-stack:rw
# Mount entire dimos directory
- ../..:/workspace/dimos:rw
# Mount bagfiles directory
@@ -127,8 +160,8 @@ services:
# Device access for hardware
devices:
- # Joystick controllers
- - /dev/input:/dev/input
+ # Joystick controller (specific device to avoid permission issues)
+ - /dev/input/js0:/dev/input/js0
# GPU access
- /dev/dri:/dev/dri
# Motor controller serial ports
@@ -142,8 +175,46 @@ services:
# Working directory
working_dir: /workspace/dimos
- # Command - for hardware, we run bash as the user will launch specific scripts
- command: bash
+ # Command - launch the real robot system with foxglove_bridge
+ command:
+ - bash
+ - -c
+ - |
+ echo "Checking joystick..."
+ ls -la /dev/input/js0 2>/dev/null || echo "Warning: No joystick found at /dev/input/js0"
+ cd /ros2_ws
+ source install/setup.bash
+ source /opt/dimos-venv/bin/activate
+ if [ "$USE_ROUTE_PLANNER" = "true" ]; then
+ echo "Starting real robot system WITH route planner..."
+ ros2 launch vehicle_simulator system_real_robot_with_route_planner.launch.py &
+ else
+ echo "Starting real robot system (base autonomy)..."
+ ros2 launch vehicle_simulator system_real_robot.launch.py &
+ fi
+ sleep 2
+ if [ "$USE_RVIZ" = "true" ]; then
+ echo "Starting RViz2..."
+ if [ "$USE_ROUTE_PLANNER" = "true" ]; then
+ ros2 run rviz2 rviz2 -d /ros2_ws/src/ros-navigation-autonomy-stack/src/route_planner/far_planner/rviz/default.rviz &
+ else
+ ros2 run rviz2 rviz2 -d /ros2_ws/src/ros-navigation-autonomy-stack/src/base_autonomy/vehicle_simulator/rviz/vehicle_simulator.rviz &
+ fi
+ fi
+ # Launch Unitree control if ROBOT_CONFIG_PATH contains "unitree"
+ if [[ "$ROBOT_CONFIG_PATH" == *"unitree"* ]]; then
+ echo "Starting Unitree WebRTC control (IP: $UNITREE_IP, Method: $UNITREE_CONN)..."
+ ros2 launch unitree_webrtc_ros unitree_control.launch.py robot_ip:=$UNITREE_IP connection_method:=$UNITREE_CONN &
+ fi
+ # Start twist relay for Foxglove Teleop (converts Twist -> TwistStamped)
+ echo "Starting Twist relay for Foxglove Teleop..."
+ python3 /usr/local/bin/twist_relay.py &
+ # Start goal autonomy relay (publishes Joy to enable autonomy when goal_pose received)
+ echo "Starting Goal Autonomy relay for Foxglove..."
+ python3 /usr/local/bin/goal_autonomy_relay.py &
+ echo "Starting Foxglove Bridge on port 8765..."
+ echo "Connect via Foxglove Studio: ws://$(hostname -I | awk '{print $1}'):8765"
+ ros2 launch foxglove_bridge foxglove_bridge_launch.xml port:=8765
# Capabilities for hardware operations
cap_add:
diff --git a/docker/navigation/foxglove_utility/goal_autonomy_relay.py b/docker/navigation/foxglove_utility/goal_autonomy_relay.py
new file mode 100755
index 0000000000..6b8d69b52b
--- /dev/null
+++ b/docker/navigation/foxglove_utility/goal_autonomy_relay.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+"""
+Relay node that publishes Joy message to enable autonomy mode when goal_pose is received.
+Mimics the behavior of the goalpoint_rviz_plugin for Foxglove compatibility.
+"""
+
+import rclpy
+from rclpy.node import Node
+from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy, DurabilityPolicy
+from geometry_msgs.msg import PoseStamped, PointStamped
+from sensor_msgs.msg import Joy
+
+
+class GoalAutonomyRelay(Node):
+ def __init__(self):
+ super().__init__('goal_autonomy_relay')
+
+ # QoS for goal topics (match foxglove_bridge)
+ goal_qos = QoSProfile(
+ reliability=ReliabilityPolicy.RELIABLE,
+ history=HistoryPolicy.KEEP_LAST,
+ durability=DurabilityPolicy.VOLATILE,
+ depth=5
+ )
+
+ # Subscribe to goal_pose (PoseStamped from Foxglove)
+ self.pose_sub = self.create_subscription(
+ PoseStamped,
+ '/goal_pose',
+ self.goal_pose_callback,
+ goal_qos
+ )
+
+ # Subscribe to way_point (PointStamped from Foxglove)
+ self.point_sub = self.create_subscription(
+ PointStamped,
+ '/way_point',
+ self.way_point_callback,
+ goal_qos
+ )
+
+ # Publisher for Joy message to enable autonomy
+ self.joy_pub = self.create_publisher(Joy, '/joy', 5)
+
+ self.get_logger().info('Goal autonomy relay started - will publish Joy to enable autonomy when goals are received')
+
+ def publish_autonomy_joy(self):
+ """Publish Joy message that enables autonomy mode (mimics goalpoint_rviz_plugin)"""
+ joy = Joy()
+ joy.header.stamp = self.get_clock().now().to_msg()
+ joy.header.frame_id = 'goal_autonomy_relay'
+
+ # axes[2] = -1.0 enables autonomy mode in pathFollower
+ # axes[4] = 1.0 sets forward speed
+ # axes[5] = 1.0 enables obstacle checking
+ joy.axes = [0.0, 0.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0]
+
+ # buttons[7] = 1 (same as RViz plugin)
+ joy.buttons = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
+
+ self.joy_pub.publish(joy)
+ self.get_logger().info('Published Joy message to enable autonomy mode')
+
+ def goal_pose_callback(self, msg: PoseStamped):
+ self.get_logger().info(f'Received goal_pose at ({msg.pose.position.x:.2f}, {msg.pose.position.y:.2f})')
+ self.publish_autonomy_joy()
+
+ def way_point_callback(self, msg: PointStamped):
+ self.get_logger().info(f'Received way_point at ({msg.point.x:.2f}, {msg.point.y:.2f})')
+ self.publish_autonomy_joy()
+
+
+def main(args=None):
+ rclpy.init(args=args)
+ node = GoalAutonomyRelay()
+ try:
+ rclpy.spin(node)
+ except KeyboardInterrupt:
+ pass
+ finally:
+ node.destroy_node()
+ rclpy.shutdown()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/docker/navigation/foxglove_utility/twist_relay.py b/docker/navigation/foxglove_utility/twist_relay.py
new file mode 100644
index 0000000000..3a13de724d
--- /dev/null
+++ b/docker/navigation/foxglove_utility/twist_relay.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+"""
+Simple relay node that converts geometry_msgs/Twist to geometry_msgs/TwistStamped.
+Used for Foxglove Teleop panel which only publishes Twist.
+"""
+
+import rclpy
+from rclpy.node import Node
+from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy
+from geometry_msgs.msg import Twist, TwistStamped
+
+
+class TwistRelay(Node):
+ def __init__(self):
+ super().__init__('twist_relay')
+
+ # Declare parameters
+ self.declare_parameter('input_topic', '/foxglove_teleop')
+ self.declare_parameter('output_topic', '/cmd_vel')
+ self.declare_parameter('frame_id', 'vehicle')
+
+ input_topic = self.get_parameter('input_topic').value
+ output_topic = self.get_parameter('output_topic').value
+ self.frame_id = self.get_parameter('frame_id').value
+
+ # QoS for real-time control
+ qos = QoSProfile(
+ reliability=ReliabilityPolicy.BEST_EFFORT,
+ history=HistoryPolicy.KEEP_LAST,
+ depth=1
+ )
+
+ # Subscribe to Twist (from Foxglove Teleop)
+ self.subscription = self.create_subscription(
+ Twist,
+ input_topic,
+ self.twist_callback,
+ qos
+ )
+
+ # Publish TwistStamped
+ self.publisher = self.create_publisher(
+ TwistStamped,
+ output_topic,
+ qos
+ )
+
+ self.get_logger().info(f'Twist relay: {input_topic} (Twist) -> {output_topic} (TwistStamped)')
+
+ def twist_callback(self, msg: Twist):
+ stamped = TwistStamped()
+ stamped.header.stamp = self.get_clock().now().to_msg()
+ stamped.header.frame_id = self.frame_id
+ stamped.twist = msg
+ self.publisher.publish(stamped)
+
+
+def main(args=None):
+ rclpy.init(args=args)
+ node = TwistRelay()
+ try:
+ rclpy.spin(node)
+ except KeyboardInterrupt:
+ pass
+ finally:
+ node.destroy_node()
+ rclpy.shutdown()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/docker/navigation/run_both.sh b/docker/navigation/run_both.sh
index 24c480eaea..e59a15ef5e 100755
--- a/docker/navigation/run_both.sh
+++ b/docker/navigation/run_both.sh
@@ -87,11 +87,20 @@ cleanup() {
# Set up trap to call cleanup on exit
trap cleanup EXIT INT TERM
+# Source ROS environment
+echo "Sourcing ROS environment..."
+source /opt/ros/${ROS_DISTRO:-humble}/setup.bash
+source /ros2_ws/install/setup.bash
+
# Start ROS route planner in background (in new process group)
echo "Starting ROS route planner..."
cd /ros2_ws/src/ros-navigation-autonomy-stack
-setsid bash -c './system_simulation_with_route_planner.sh' &
+# Run simulation directly instead of using the script (which has wrong install path)
+./src/base_autonomy/vehicle_simulator/mesh/unity/environment/Model.x86_64 &
+sleep 3
+setsid bash -c 'ros2 launch vehicle_simulator system_simulation_with_route_planner.launch.py' &
ROS_PID=$!
+ros2 run rviz2 rviz2 -d src/route_planner/far_planner/rviz/default.rviz &
# Wait a bit for ROS to initialize
echo "Waiting for ROS to initialize..."
@@ -111,6 +120,17 @@ else
source /opt/dimos-venv/bin/activate
echo "Python path: $(which python)"
echo "Python version: $(python --version)"
+
+ # Install dimos package if not already installed
+ if ! python -c "import dimos" 2>/dev/null; then
+ echo "Installing dimos package..."
+ if [ -f "/workspace/dimos/setup.py" ] || [ -f "/workspace/dimos/pyproject.toml" ]; then
+ # Install Unitree extra (includes agents stack + unitree deps used by demo)
+ pip install -e "/workspace/dimos[unitree]" --quiet
+ else
+ echo "WARNING: dimos package not found at /workspace/dimos"
+ fi
+ fi
else
echo "WARNING: Virtual environment not found at /opt/dimos-venv, using system Python"
fi
diff --git a/docker/navigation/start.sh b/docker/navigation/start.sh
index 4347006957..9b27a1a3ce 100755
--- a/docker/navigation/start.sh
+++ b/docker/navigation/start.sh
@@ -9,6 +9,10 @@ NC='\033[0m'
# Parse command line arguments
MODE="simulation"
+USE_ROUTE_PLANNER="false"
+USE_RVIZ="false"
+DEV_MODE="false"
+ROS_DISTRO="humble"
while [[ $# -gt 0 ]]; do
case $1 in
--hardware)
@@ -19,17 +23,47 @@ while [[ $# -gt 0 ]]; do
MODE="simulation"
shift
;;
+ --route-planner)
+ USE_ROUTE_PLANNER="true"
+ shift
+ ;;
+ --rviz)
+ USE_RVIZ="true"
+ shift
+ ;;
+ --dev)
+ DEV_MODE="true"
+ shift
+ ;;
+ --humble)
+ ROS_DISTRO="humble"
+ shift
+ ;;
+ --jazzy)
+ ROS_DISTRO="jazzy"
+ shift
+ ;;
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
- echo " --simulation Start simulation container (default)"
- echo " --hardware Start hardware container for real robot"
- echo " --help, -h Show this help message"
+ echo " --simulation Start simulation container (default)"
+ echo " --hardware Start hardware container for real robot"
+ echo " --route-planner Enable FAR route planner (for hardware mode)"
+ echo " --rviz Launch RViz2 visualization"
+ echo " --dev Development mode (mount src for config editing)"
+ echo " --humble Use ROS 2 Humble image (default)"
+ echo " --jazzy Use ROS 2 Jazzy image"
+ echo " --help, -h Show this help message"
echo ""
echo "Examples:"
- echo " $0 # Start simulation container"
- echo " $0 --hardware # Start hardware container"
+ echo " $0 # Start simulation (Humble)"
+ echo " $0 --jazzy # Start simulation (Jazzy)"
+ echo " $0 --hardware # Start hardware (base autonomy, Humble)"
+ echo " $0 --hardware --jazzy # Start hardware (Jazzy)"
+ echo " $0 --hardware --route-planner # Hardware with route planner"
+ echo " $0 --hardware --route-planner --rviz # Hardware with route planner + RViz"
+ echo " $0 --hardware --dev # Hardware with src mounted for development"
echo ""
echo "Press Ctrl+C to stop the container"
exit 0
@@ -42,12 +76,15 @@ while [[ $# -gt 0 ]]; do
esac
done
+export ROS_DISTRO
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR"
echo -e "${GREEN}================================================${NC}"
echo -e "${GREEN}Starting DimOS Docker Container${NC}"
echo -e "${GREEN}Mode: ${MODE}${NC}"
+echo -e "${GREEN}ROS Distribution: ${ROS_DISTRO}${NC}"
echo -e "${GREEN}================================================${NC}"
echo ""
@@ -74,7 +111,23 @@ if [ "$MODE" = "hardware" ]; then
set -a
source .env
set +a
+ fi
+ # Auto-detect group IDs for device permissions
+ echo -e "${GREEN}Detecting device group IDs...${NC}"
+ export INPUT_GID=$(getent group input | cut -d: -f3 || echo "995")
+ export DIALOUT_GID=$(getent group dialout | cut -d: -f3 || echo "20")
+ # Warn if fallback values are being used
+ if ! getent group input > /dev/null 2>&1; then
+ echo -e "${YELLOW}Warning: input group not found, using fallback GID ${INPUT_GID}${NC}"
+ fi
+ if ! getent group dialout > /dev/null 2>&1; then
+ echo -e "${YELLOW}Warning: dialout group not found, using fallback GID ${DIALOUT_GID}${NC}"
+ fi
+ echo -e " input group GID: ${INPUT_GID}"
+ echo -e " dialout group GID: ${DIALOUT_GID}"
+
+ if [ -f ".env" ]; then
# Check for required environment variables
if [ -z "$LIDAR_IP" ] || [ "$LIDAR_IP" = "192.168.1.116" ]; then
echo -e "${YELLOW}Warning: LIDAR_IP still using default value in .env${NC}"
@@ -165,22 +218,34 @@ if [ "$MODE" = "hardware" ]; then
fi
-# Check if unified image exists
-if ! docker images | grep -q "dimos_autonomy_stack.*jazzy"; then
- echo -e "${YELLOW}Docker image not found. Building...${NC}"
- ./build.sh
+# Check if the correct ROS distro image exists
+if ! docker images | grep -q "dimos_autonomy_stack.*${ROS_DISTRO}"; then
+ echo -e "${YELLOW}Docker image for ROS ${ROS_DISTRO} not found. Building...${NC}"
+ ./build.sh --${ROS_DISTRO}
fi
# Check for X11 display
if [ -z "$DISPLAY" ]; then
echo -e "${YELLOW}Warning: DISPLAY not set. GUI applications may not work.${NC}"
export DISPLAY=:0
+else
+ echo -e "${GREEN}Using DISPLAY: $DISPLAY${NC}"
fi
+export DISPLAY
# Allow X11 connections from Docker
echo -e "${GREEN}Configuring X11 access...${NC}"
xhost +local:docker 2>/dev/null || true
+# Setup X11 auth for remote/SSH connections
+XAUTH=/tmp/.docker.xauth
+touch $XAUTH 2>/dev/null || true
+if [ -n "$DISPLAY" ]; then
+ xauth nlist $DISPLAY 2>/dev/null | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge - 2>/dev/null || true
+ chmod 644 $XAUTH 2>/dev/null || true
+ echo -e "${GREEN}X11 auth configured for display: $DISPLAY${NC}"
+fi
+
cleanup() {
xhost -local:docker 2>/dev/null || true
}
@@ -207,15 +272,37 @@ else
CONTAINER_NAME="dimos_simulation_container"
fi
+# Export settings for docker-compose
+export USE_ROUTE_PLANNER
+export USE_RVIZ
+
# Print helpful info before starting
echo ""
if [ "$MODE" = "hardware" ]; then
- echo "Hardware mode - Interactive shell"
+ if [ "$USE_ROUTE_PLANNER" = "true" ]; then
+ echo "Hardware mode - Auto-starting ROS real robot system WITH route planner"
+ echo ""
+ echo "The container will automatically run:"
+ echo " - ROS navigation stack (system_real_robot_with_route_planner.launch)"
+ echo " - FAR Planner for goal-based navigation"
+ echo " - Foxglove Bridge"
+ else
+ echo "Hardware mode - Auto-starting ROS real robot system (base autonomy)"
+ echo ""
+ echo "The container will automatically run:"
+ echo " - ROS navigation stack (system_real_robot.launch)"
+ echo " - Foxglove Bridge"
+ fi
+ if [ "$USE_RVIZ" = "true" ]; then
+ echo " - RViz2 visualization"
+ fi
+ if [ "$DEV_MODE" = "true" ]; then
+ echo ""
+ echo -e " ${YELLOW}Development mode: src folder mounted for config editing${NC}"
+ fi
echo ""
- echo -e "${GREEN}=================================================${NC}"
- echo -e "${GREEN}The container is running. Exec in to run scripts:${NC}"
+ echo "To enter the container from another terminal:"
echo -e " ${YELLOW}docker exec -it ${CONTAINER_NAME} bash${NC}"
- echo -e "${GREEN}=================================================${NC}"
else
echo "Simulation mode - Auto-starting ROS simulation and DimOS"
echo ""
@@ -227,8 +314,17 @@ else
echo " docker exec -it ${CONTAINER_NAME} bash"
fi
+# Note: DISPLAY is now passed directly via environment variable
+# No need to write RUNTIME_DISPLAY to .env for local host running
+
+# Build compose command with optional dev mode
+COMPOSE_CMD="docker compose -f docker-compose.yml"
+if [ "$DEV_MODE" = "true" ]; then
+ COMPOSE_CMD="$COMPOSE_CMD -f docker-compose.dev.yml"
+fi
+
if [ "$MODE" = "hardware" ]; then
- docker compose -f docker-compose.yml --profile hardware up
+ $COMPOSE_CMD --profile hardware up
else
- docker compose -f docker-compose.yml up
+ $COMPOSE_CMD up
fi