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