From 791087b0e80f8dacbcd62cdf83877325bc4aa86d Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Thu, 27 Nov 2025 19:27:52 +0000 Subject: [PATCH 1/4] feat(turtlebot3): add basic TurtleBot3 + ros2_medkit discovery demo Add containerized demo showing ros2_medkit gateway discovering TurtleBot3 nodes in Gazebo simulation. Includes: - Dockerfile with ROS 2 Jazzy, TurtleBot3, and ros2_medkit - docker-compose.yml with port forwarding for REST API - docker-compose.nvidia.yml for GPU-accelerated Gazebo - ROS 2 package structure (package.xml, CMakeLists.txt) - Launch file for TurtleBot3 empty_world + gateway - One-click run-demo.sh script with X11 setup --- .devcontainer/Dockerfile | 94 +++++++++++++ .devcontainer/devcontainer.json | 58 ++++++++ .devcontainer/setup-env.sh | 27 ++++ demos/turtlebot3_integration/CMakeLists.txt | 16 +++ demos/turtlebot3_integration/Dockerfile | 48 +++++++ demos/turtlebot3_integration/README.md | 128 +++++++++++++++--- .../config/medkit_params.yaml | 7 + .../docker-compose.nvidia.yml | 34 +++++ .../turtlebot3_integration/docker-compose.yml | 21 +++ .../launch/demo.launch.py | 42 ++++++ demos/turtlebot3_integration/package.xml | 19 +++ demos/turtlebot3_integration/run-demo.sh | 51 +++++++ 12 files changed, 527 insertions(+), 18 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100755 .devcontainer/setup-env.sh create mode 100644 demos/turtlebot3_integration/CMakeLists.txt create mode 100644 demos/turtlebot3_integration/Dockerfile create mode 100644 demos/turtlebot3_integration/config/medkit_params.yaml create mode 100644 demos/turtlebot3_integration/docker-compose.nvidia.yml create mode 100644 demos/turtlebot3_integration/docker-compose.yml create mode 100644 demos/turtlebot3_integration/launch/demo.launch.py create mode 100644 demos/turtlebot3_integration/package.xml create mode 100755 demos/turtlebot3_integration/run-demo.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..f92832a --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,94 @@ +FROM ubuntu:24.04 + +# Environment setup +ENV ROS_DISTRO=jazzy +ENV LANG=en_US.UTF-8 +ENV LC_ALL=en_US.UTF-8 +ENV HADOLINT_VERSION=v2.14.0 + +# Args +ARG USERNAME=devuser +ARG USER_UID=1000 +ARG USER_GID=1000 + +# System dependencies +RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \ + locales \ + build-essential \ + cmake \ + python3-pip \ + python3-venv \ + python3-dev \ + python3-setuptools \ + python3-wheel \ + python-is-python3 \ + wget \ + curl \ + vim \ + nano \ + sudo \ + software-properties-common \ + lsb-release \ + gnupg2 \ + yq \ + ca-certificates \ + clang-format \ + graphviz \ + plantuml \ + && locale-gen en_US.UTF-8 \ + && rm -rf /var/lib/apt/lists/* + +# Add ROS 2 repository and install ROS 2 Jazzy +RUN curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \ + | gpg --dearmor -o /usr/share/keyrings/ros-archive-keyring.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) main" \ + > /etc/apt/sources.list.d/ros2.list + +RUN apt-get update && apt-get install -y \ + ros-jazzy-desktop-full \ + python3-colcon-common-extensions \ + python3-rosdep \ + && rm -rf /var/lib/apt/lists/* + +# Install additional Python tools via pip +RUN pip install --break-system-packages \ + vcstool \ + setuptools \ + pytest \ + argcomplete \ + black \ + flake8-blind-except \ + flake8-builtins \ + flake8-class-newline \ + flake8-comprehensions \ + flake8-deprecated \ + flake8-docstrings \ + flake8-import-order \ + flake8-quotes \ + pytest-repeat \ + pytest-rerunfailures \ + sphinx \ + sphinx-rtd-theme \ + sphinx-needs \ + sphinxcontrib-plantuml \ + sphinx-autobuild \ + myst-parser + +# Install hadolint for Dockerfile linting +RUN wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-Linux-x86_64 && \ + chmod +x /usr/local/bin/hadolint + +# Create non-root user +# Remove existing user/group with the same UID/GID if they exist +RUN if id -u ${USER_UID} &>/dev/null; then userdel -r "$(id -un ${USER_UID})" 2>/dev/null || true; fi && \ + if getent group ${USER_GID} &>/dev/null; then groupdel "$(getent group ${USER_GID} | cut -d: -f1)" 2>/dev/null || true; fi && \ + groupadd --gid ${USER_GID} ${USERNAME} && \ + useradd --uid ${USER_UID} --gid ${USER_GID} -m ${USERNAME} && \ + usermod -aG sudo ${USERNAME} && \ + echo "${USERNAME} ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/${USERNAME} && \ + chmod 0440 /etc/sudoers.d/${USERNAME} + +# Switch to user context +USER ${USERNAME} +WORKDIR /home/${USERNAME} +RUN mkdir -p /home/${USERNAME}/workspace diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..da976d6 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,58 @@ +{ + "name": "ROS2 ros2_medkit Development", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + "USERNAME": "${localEnv:USER}" + } + }, + "workspaceFolder": "/home/${localEnv:USER}/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/home/${localEnv:USER}/workspace,type=bind", + "mounts": [ + "source=${localEnv:HOME}/.ssh,target=/home/${localEnv:USER}/.ssh,type=bind,consistency=cached,readonly", + "source=${localEnv:HOME}/.gitconfig,target=/home/${localEnv:USER}/.gitconfig,type=bind,consistency=cached" + ], + "features": { + // Comment this out if you don't want zsh and oh-my-zsh + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": true, + "configureZshAsDefaultShell": true, + "installOhMyZsh": true + }, + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-python.python", + "ms-python.pylint", + "ms-python.vscode-pylance", + "ms-python.black-formatter", + "ms-python.isort", + "redhat.vscode-xml", + "dotjoshjohnson.xml", + "twxs.cmake", + "ms-vscode.cmake-tools", + "xaver.clang-format", + "redhat.vscode-yaml", + "usernamehw.errorlens", + "GitHub.copilot", + "GitHub.copilot-chat", + "eamodio.gitlens", + "christian-kohler.path-intellisense" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "zsh", + "python.defaultInterpreterPath": "/usr/bin/python3", + "cmake.configureOnOpen": false + } + } + }, + "remoteUser": "${localEnv:USER}", + "postCreateCommand": "bash .devcontainer/setup-env.sh" +} diff --git a/.devcontainer/setup-env.sh b/.devcontainer/setup-env.sh new file mode 100755 index 0000000..42f8f8f --- /dev/null +++ b/.devcontainer/setup-env.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +# Add ROS2 setup to shell configs +grep -Fxq "source /opt/ros/jazzy/setup.bash" ~/.bashrc || echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc +grep -Fxq "source /opt/ros/jazzy/setup.zsh" ~/.zshrc || echo "source /opt/ros/jazzy/setup.zsh" >> ~/.zshrc + +# Source the current shell to apply changes immediately +if [ -n "$ZSH_VERSION" ]; then + # Running in zsh + source ~/.zshrc 2>/dev/null || true +elif [ -n "$BASH_VERSION" ]; then + # Running in bash + source ~/.bashrc 2>/dev/null || true +fi + +# Initialize rosdep if not already initialized +if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then + sudo rosdep init +fi +rosdep update + +# Test installations +echo "Testing installations..." +echo "ROS2 Jazzy: $(test -f /opt/ros/jazzy/setup.bash && echo 'Installed' || echo 'Not found')" + +echo "Environment setup complete!" \ No newline at end of file diff --git a/demos/turtlebot3_integration/CMakeLists.txt b/demos/turtlebot3_integration/CMakeLists.txt new file mode 100644 index 0000000..c839a2f --- /dev/null +++ b/demos/turtlebot3_integration/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.8) +project(turtlebot3_medkit_demo) + +find_package(ament_cmake REQUIRED) + +# Install launch files +install(DIRECTORY launch/ + DESTINATION share/${PROJECT_NAME}/launch +) + +# Install config files +install(DIRECTORY config/ + DESTINATION share/${PROJECT_NAME}/config +) + +ament_package() diff --git a/demos/turtlebot3_integration/Dockerfile b/demos/turtlebot3_integration/Dockerfile new file mode 100644 index 0000000..2c235b0 --- /dev/null +++ b/demos/turtlebot3_integration/Dockerfile @@ -0,0 +1,48 @@ +# TurtleBot3 + ros2_medkit Discovery Demo +# Full desktop image with Gazebo GUI support + +FROM osrf/ros:jazzy-desktop + +ENV DEBIAN_FRONTEND=noninteractive +ENV ROS_DISTRO=jazzy +ENV TURTLEBOT3_MODEL=burger +ENV COLCON_WS=/root/demo_ws + +# Install TurtleBot3 and build dependencies +RUN apt-get update && apt-get install -y \ + ros-jazzy-turtlebot3-gazebo \ + ros-jazzy-turtlebot3-msgs \ + ros-jazzy-turtlebot3-description \ + ros-jazzy-ament-clang-format \ + python3-colcon-common-extensions \ + python3-requests \ + nlohmann-json3-dev \ + libcpp-httplib-dev \ + git \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Create workspace and clone ros2_medkit +WORKDIR ${COLCON_WS}/src +RUN git clone https://github.com/selfpatch/ros2_medkit.git + +# Copy demo package +COPY package.xml CMakeLists.txt ${COLCON_WS}/src/turtlebot3_medkit_demo/ +COPY config/ ${COLCON_WS}/src/turtlebot3_medkit_demo/config/ +COPY launch/ ${COLCON_WS}/src/turtlebot3_medkit_demo/launch/ + +# Build ros2_medkit and demo package +WORKDIR ${COLCON_WS} +RUN bash -c "source /opt/ros/jazzy/setup.bash && \ + rosdep update && \ + rosdep install --from-paths src --ignore-src -r -y && \ + colcon build --symlink-install" + +# Setup environment +RUN echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc && \ + echo "source ${COLCON_WS}/install/setup.bash" >> ~/.bashrc && \ + echo "export TURTLEBOT3_MODEL=burger" >> ~/.bashrc + +EXPOSE 8080 + +CMD ["bash"] diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md index 13c0c7d..7c32c87 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -1,40 +1,132 @@ # TurtleBot3 Integration Demo -This demo shows how to integrate ros2_medkit with TurtleBot3 and Nav2 to provide -modern diagnostics for a mobile robot navigation system. +This demo shows how to integrate ros2_medkit with TurtleBot3 to provide +modern diagnostics for a mobile robot system via REST API. ## Status -🚧 **Work in Progress** +🚧 **Work in Progress** - Basic discovery demo ## Overview -This demo will demonstrate: +This demo demonstrates: -- Discovering TurtleBot3 nodes through ros2_medkit REST API -- Organizing navigation components into diagnostic areas -- Reading sensor data and navigation state via HTTP -- Monitoring robot health during autonomous navigation +- Launching TurtleBot3 simulation in Gazebo +- Running ros2_medkit gateway alongside the robot +- Discovering TurtleBot3 nodes through REST API +- Querying robot components and their data via HTTP ## Prerequisites -- ROS 2 Jazzy (Ubuntu 24.04) -- [ros2_medkit](https://github.com/selfpatch/ros2_medkit) -- TurtleBot3 packages -- Nav2 navigation stack +- Docker and docker-compose +- X11 display server (Linux with GUI, or XQuartz on macOS) +- (Optional) NVIDIA GPU + [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) -## Installation +## Quick Start -*Coming soon* +```bash +cd demos/turtlebot3_integration +./run-demo.sh +``` -## Usage +That's it! The script will: +1. Build the Docker image (first run takes ~5-10 min, downloads ~4GB) +2. Setup X11 forwarding for Gazebo GUI +3. Launch TurtleBot3 simulation + ros2_medkit gateway -*Coming soon* +### With NVIDIA GPU + +For better Gazebo performance: + +```bash +./run-demo.sh --nvidia +``` + +Requires [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html). + +## Verifying Discovery + +Once the demo is running, open a new terminal and test the REST API: + +```bash +# Check gateway health +curl http://localhost:8080/health + +# List discovered areas +curl http://localhost:8080/areas + +# List all discovered components (nodes) +curl http://localhost:8080/components +``` ## What You'll See -*Coming soon* +When TurtleBot3 simulation starts, ros2_medkit will discover nodes such as: + +- `turtlebot3_node` - Main robot interface +- `robot_state_publisher` - TF tree publisher +- `gazebo` - Simulation engine +- Various sensor and controller nodes + +These appear as **components** in the ros2_medkit REST API, organized into **areas** based on their ROS 2 namespaces. ## Architecture -*Coming soon* +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Docker Container β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Gazebo Simulation β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ TurtleBot3 β”‚ β”‚ robot_state β”‚ β”‚ LIDAR β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ Node β”‚ β”‚ publisher β”‚ β”‚ sensor β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ ROS 2 Topics β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ ros2_medkit Gateway β”‚ β”‚ +β”‚ β”‚ (REST Server :8080) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + HTTP REST API + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό + curl/browser External tools +``` + +## File Structure + +``` +demos/turtlebot3_integration/ +β”œβ”€β”€ Dockerfile # ROS 2 Jazzy + TurtleBot3 + ros2_medkit +β”œβ”€β”€ docker-compose.yml # Standard (CPU) configuration +β”œβ”€β”€ docker-compose.nvidia.yml # NVIDIA GPU configuration +β”œβ”€β”€ run-demo.sh # One-click demo launcher +β”œβ”€β”€ config/ +β”‚ └── medkit_params.yaml # ros2_medkit gateway config +└── launch/ + └── demo.launch.py # ROS 2 launch file +``` + +## Manual Setup (Alternative) + +If you prefer not to use Docker: + +1. Install ROS 2 Jazzy on Ubuntu 24.04 +2. Install TurtleBot3: `sudo apt install ros-jazzy-turtlebot3-gazebo` +3. Build [ros2_medkit](https://github.com/selfpatch/ros2_medkit) from source +4. Set `export TURTLEBOT3_MODEL=burger` +5. Run: `ros2 launch launch/demo.launch.py` + +## Next Steps + +Future versions of this demo will add: + +- Nav2 navigation stack integration +- Teleopereration with diagnostic monitoring +- Sensor data visualization through the API +- Health monitoring during autonomous navigation diff --git a/demos/turtlebot3_integration/config/medkit_params.yaml b/demos/turtlebot3_integration/config/medkit_params.yaml new file mode 100644 index 0000000..d819c14 --- /dev/null +++ b/demos/turtlebot3_integration/config/medkit_params.yaml @@ -0,0 +1,7 @@ +# ros2_medkit gateway configuration for TurtleBot3 demo +ros2_medkit_gateway: + ros__parameters: + server: + host: "0.0.0.0" + port: 8080 + refresh_interval_ms: 2000 diff --git a/demos/turtlebot3_integration/docker-compose.nvidia.yml b/demos/turtlebot3_integration/docker-compose.nvidia.yml new file mode 100644 index 0000000..70cf042 --- /dev/null +++ b/demos/turtlebot3_integration/docker-compose.nvidia.yml @@ -0,0 +1,34 @@ +# NVIDIA GPU accelerated version +# Requires: nvidia-container-toolkit +# Install: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html + +services: + turtlebot3-demo: + build: + context: . + dockerfile: Dockerfile + container_name: turtlebot3_medkit_demo_nvidia + environment: + - DISPLAY=${DISPLAY} + - TURTLEBOT3_MODEL=burger + - ROS_DOMAIN_ID=30 + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_DRIVER_CAPABILITIES=all + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix:rw + ports: + - "8080:8080" + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + stdin_open: true + tty: true + command: > + bash -c "source /opt/ros/jazzy/setup.bash && + source /root/demo_ws/install/setup.bash && + export TURTLEBOT3_MODEL=burger && + ros2 launch turtlebot3_medkit_demo demo.launch.py" diff --git a/demos/turtlebot3_integration/docker-compose.yml b/demos/turtlebot3_integration/docker-compose.yml new file mode 100644 index 0000000..44bad8d --- /dev/null +++ b/demos/turtlebot3_integration/docker-compose.yml @@ -0,0 +1,21 @@ +services: + turtlebot3-demo: + build: + context: . + dockerfile: Dockerfile + container_name: turtlebot3_medkit_demo + environment: + - DISPLAY=${DISPLAY} + - TURTLEBOT3_MODEL=burger + - ROS_DOMAIN_ID=30 + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix:rw + ports: + - "8080:8080" + stdin_open: true + tty: true + command: > + bash -c "source /opt/ros/jazzy/setup.bash && + source /root/demo_ws/install/setup.bash && + export TURTLEBOT3_MODEL=burger && + ros2 launch turtlebot3_medkit_demo demo.launch.py" diff --git a/demos/turtlebot3_integration/launch/demo.launch.py b/demos/turtlebot3_integration/launch/demo.launch.py new file mode 100644 index 0000000..b1321d2 --- /dev/null +++ b/demos/turtlebot3_integration/launch/demo.launch.py @@ -0,0 +1,42 @@ +"""Launch TurtleBot3 simulation with ros2_medkit gateway for discovery demo.""" + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription, SetEnvironmentVariable +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch_ros.actions import Node + + +def generate_launch_description(): + # Get package directories + turtlebot3_gazebo_dir = get_package_share_directory('turtlebot3_gazebo') + demo_pkg_dir = get_package_share_directory('turtlebot3_medkit_demo') + + # Path to medkit params from installed package + medkit_params_file = os.path.join(demo_pkg_dir, 'config', 'medkit_params.yaml') + + return LaunchDescription([ + # Set TurtleBot3 model (can be overridden by environment variable) + SetEnvironmentVariable( + name='TURTLEBOT3_MODEL', + value=os.environ.get('TURTLEBOT3_MODEL', 'burger') + ), + + # Launch TurtleBot3 Gazebo simulation (empty world) + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(turtlebot3_gazebo_dir, 'launch', 'empty_world.launch.py') + ) + ), + + # Launch ros2_medkit gateway + Node( + package='ros2_medkit_gateway', + executable='gateway_node', + name='ros2_medkit_gateway', + output='screen', + parameters=[medkit_params_file], + ), + ]) diff --git a/demos/turtlebot3_integration/package.xml b/demos/turtlebot3_integration/package.xml new file mode 100644 index 0000000..b208d0f --- /dev/null +++ b/demos/turtlebot3_integration/package.xml @@ -0,0 +1,19 @@ + + + + turtlebot3_medkit_demo + 0.1.0 + TurtleBot3 + ros2_medkit integration demo + Demo Maintainer + Apache-2.0 + + ament_cmake + + ros2launch + turtlebot3_gazebo + ros2_medkit_gateway + + + ament_cmake + + diff --git a/demos/turtlebot3_integration/run-demo.sh b/demos/turtlebot3_integration/run-demo.sh new file mode 100755 index 0000000..429709c --- /dev/null +++ b/demos/turtlebot3_integration/run-demo.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# TurtleBot3 + ros2_medkit Demo Runner + +set -eu + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "πŸ€– TurtleBot3 + ros2_medkit Demo" +echo "==================================" + +# Check for Docker +if ! command -v docker &> /dev/null; then + echo "Error: Docker is not installed" + exit 1 +fi + +# Setup X11 forwarding for GUI (Gazebo) +echo "πŸ–₯️ Setting up X11 forwarding..." +xhost +local:docker 2>/dev/null || { + echo " Warning: xhost failed. GUI may not work." + echo " Install with: sudo apt install x11-xserver-utils" +} + +# Cleanup function +cleanup() { + echo "" + echo "Cleaning up..." + xhost -local:docker 2>/dev/null || true + echo "Done!" +} +trap cleanup EXIT + +# Select compose file +if [[ "$1" == "--nvidia" ]]; then + echo "Using NVIDIA GPU acceleration" + COMPOSE_FILE="docker-compose.nvidia.yml" +else + COMPOSE_FILE="docker-compose.yml" +fi + +# Build and run +echo " Building and starting demo..." +echo " (First run takes ~5-10 min, downloading ~4GB image)" +echo "" + +if docker compose version &> /dev/null; then + docker compose -f "$COMPOSE_FILE" up --build +else + docker-compose -f "$COMPOSE_FILE" up --build +fi From 2edd288c2e6fd3b64bcb857bd788f4e45094ee53 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Tue, 2 Dec 2025 08:16:17 +0000 Subject: [PATCH 2/4] Add TurtleBot3 + Nav2 integration demo with sovd_web_ui Complete demonstration of ros2_medkit integration with TurtleBot3 robot, Nav2 navigation stack, and sovd_web_ui for browser-based control. Key features: - Full TurtleBot3 Gazebo simulation with turtlebot3_world map - Nav2 navigation stack (AMCL, planner, controller, costmaps) - ros2_medkit gateway exposing SOVD-compliant REST API - sovd_web_ui for interactive robot control via browser - Docker Compose orchestration with CPU and NVIDIA GPU profiles - One-command demo launcher with X11 forwarding setup Build system changes: - Clone external dependencies (ros2_medkit, sovd_web_ui) from GitHub instead of using relative paths (temporary until proper releases) - Added Nav2 configuration files (nav2_params.yaml, turtlebot3_world.yaml) - Unified docker-compose.yml with NVIDIA profile (removed separate file) - Enhanced CI with shellcheck, yamllint, and Docker build validation Docker improvements: - Use `context: .` and git clone instead of relative workspace paths - NVIDIA GPU support via Docker Compose profile (--profile nvidia) - sovd_web_ui cloned directly from GitHub in docker-compose - Automatic Web UI startup on port 3000 Script enhancements: - Added `set -eu` for strict error handling in run-demo.sh - Fixed unbound variable issues with proper parameter expansion - Shellcheck compliance with appropriate disable directives - Improved user feedback with emoji icons and clearer messages Documentation: - Updated README with accurate port numbers and instructions - Added Docker Compose profile usage examples - Fixed markdown linting issues (blank lines, code fences, bare URLs) - Clarified prerequisites and NVIDIA GPU requirements CI/CD improvements: - Added shellcheck for .sh and .bash files - Added yamllint with 120-character line limit - Added trailing whitespace check - Added Docker build test for demo image - All checks run on PR and push to main --- .devcontainer/Dockerfile | 2 + .devcontainer/setup-env.sh | 2 + .github/workflows/ci.yml | 31 ++ demos/turtlebot3_integration/Dockerfile | 26 +- demos/turtlebot3_integration/README.md | 218 ++++++++--- .../config/medkit_params.yaml | 10 +- .../config/nav2_params.yaml | 365 ++++++++++++++++++ .../config/turtlebot3_world.yaml | 7 + .../docker-compose.nvidia.yml | 34 -- .../turtlebot3_integration/docker-compose.yml | 49 +++ .../launch/demo.launch.py | 42 +- demos/turtlebot3_integration/package.xml | 11 +- demos/turtlebot3_integration/run-demo.sh | 38 +- 13 files changed, 722 insertions(+), 113 deletions(-) create mode 100644 demos/turtlebot3_integration/config/nav2_params.yaml create mode 100644 demos/turtlebot3_integration/config/turtlebot3_world.yaml delete mode 100644 demos/turtlebot3_integration/docker-compose.nvidia.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f92832a..2cb425e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -35,6 +35,8 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \ clang-format \ graphviz \ plantuml \ + shellcheck \ + yamllint \ && locale-gen en_US.UTF-8 \ && rm -rf /var/lib/apt/lists/* diff --git a/.devcontainer/setup-env.sh b/.devcontainer/setup-env.sh index 42f8f8f..4e4a922 100755 --- a/.devcontainer/setup-env.sh +++ b/.devcontainer/setup-env.sh @@ -8,9 +8,11 @@ grep -Fxq "source /opt/ros/jazzy/setup.zsh" ~/.zshrc || echo "source /opt/ros/ja # Source the current shell to apply changes immediately if [ -n "$ZSH_VERSION" ]; then # Running in zsh + # shellcheck disable=SC1090,SC1091 source ~/.zshrc 2>/dev/null || true elif [ -n "$BASH_VERSION" ]; then # Running in bash + # shellcheck disable=SC1090,SC1091 source ~/.bashrc 2>/dev/null || true fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8120b24..b34731f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,3 +12,34 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + + - name: Check for trailing whitespace + run: | + ! git grep -I --line-number --perl-regexp '\s+$' -- \ + '*.md' '*.py' '*.yaml' '*.yml' || \ + (echo "Found trailing whitespace in the files above" && exit 1) + + - name: Check shell scripts with shellcheck + run: | + sudo apt-get update && sudo apt-get install -y shellcheck + find . -name "*.sh" -o -name "*.bash" -type f | xargs shellcheck + + - name: Validate YAML files + run: | + sudo apt-get update && sudo apt-get install -y yamllint + YAMLLINT_CONFIG="{extends: relaxed, rules: {line-length: {max: 120}}}" + find . -name "*.yaml" -o -name "*.yml" | xargs yamllint -d "$YAMLLINT_CONFIG" + + docker-build: + runs-on: ubuntu-24.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build TurtleBot3 demo image + run: | + cd demos/turtlebot3_integration + docker build -t turtlebot3-medkit-demo:test -f Dockerfile . diff --git a/demos/turtlebot3_integration/Dockerfile b/demos/turtlebot3_integration/Dockerfile index 2c235b0..e0cfdae 100644 --- a/demos/turtlebot3_integration/Dockerfile +++ b/demos/turtlebot3_integration/Dockerfile @@ -1,4 +1,4 @@ -# TurtleBot3 + ros2_medkit Discovery Demo +# TurtleBot3 + ros2_medkit Discovery Demo with Nav2 Navigation # Full desktop image with Gazebo GUI support FROM osrf/ros:jazzy-desktop @@ -6,14 +6,32 @@ FROM osrf/ros:jazzy-desktop ENV DEBIAN_FRONTEND=noninteractive ENV ROS_DISTRO=jazzy ENV TURTLEBOT3_MODEL=burger +ENV GAZEBO_MODEL_PATH=/opt/ros/jazzy/share/turtlebot3_gazebo/models ENV COLCON_WS=/root/demo_ws -# Install TurtleBot3 and build dependencies +# Install TurtleBot3, Nav2, and build dependencies RUN apt-get update && apt-get install -y \ ros-jazzy-turtlebot3-gazebo \ ros-jazzy-turtlebot3-msgs \ ros-jazzy-turtlebot3-description \ + ros-jazzy-turtlebot3-navigation2 \ + ros-jazzy-nav2-bringup \ + ros-jazzy-nav2-bt-navigator \ + ros-jazzy-nav2-controller \ + ros-jazzy-nav2-planner \ + ros-jazzy-nav2-behaviors \ + ros-jazzy-nav2-costmap-2d \ + ros-jazzy-nav2-lifecycle-manager \ + ros-jazzy-nav2-map-server \ + ros-jazzy-nav2-amcl \ + ros-jazzy-ament-lint-auto \ + ros-jazzy-ament-lint-common \ ros-jazzy-ament-clang-format \ + ros-jazzy-ament-cmake-clang-format \ + ros-jazzy-ament-cmake-clang-tidy \ + ros-jazzy-ament-cmake-gtest \ + ros-jazzy-ament-cmake-pytest \ + ros-jazzy-launch-testing-ament-cmake \ python3-colcon-common-extensions \ python3-requests \ nlohmann-json3-dev \ @@ -23,6 +41,7 @@ RUN apt-get update && apt-get install -y \ && rm -rf /var/lib/apt/lists/* # Create workspace and clone ros2_medkit +# TODO: Replace with proper ROS 2 package dependency once ros2_medkit is released WORKDIR ${COLCON_WS}/src RUN git clone https://github.com/selfpatch/ros2_medkit.git @@ -41,7 +60,8 @@ RUN bash -c "source /opt/ros/jazzy/setup.bash && \ # Setup environment RUN echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc && \ echo "source ${COLCON_WS}/install/setup.bash" >> ~/.bashrc && \ - echo "export TURTLEBOT3_MODEL=burger" >> ~/.bashrc + echo "export TURTLEBOT3_MODEL=burger" >> ~/.bashrc && \ + echo "export GAZEBO_MODEL_PATH=/opt/ros/jazzy/share/turtlebot3_gazebo/models" >> ~/.bashrc EXPOSE 8080 diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md index 7c32c87..7fecf1f 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -1,20 +1,22 @@ -# TurtleBot3 Integration Demo +# TurtleBot3 Integration Demo with Nav2 Navigation -This demo shows how to integrate ros2_medkit with TurtleBot3 to provide -modern diagnostics for a mobile robot system via REST API. +This demo shows how to integrate ros2_medkit with TurtleBot3 and Nav2 navigation stack +to provide modern diagnostics and control for a mobile robot system via REST API. ## Status -🚧 **Work in Progress** - Basic discovery demo +βœ… **Demo Ready** - Full navigation demo with Web UI ## Overview This demo demonstrates: -- Launching TurtleBot3 simulation in Gazebo +- Launching TurtleBot3 simulation in Gazebo with turtlebot3_world +- Running Nav2 navigation stack (AMCL, planner, controller) - Running ros2_medkit gateway alongside the robot - Discovering TurtleBot3 nodes through REST API -- Querying robot components and their data via HTTP +- Querying and publishing to ROS2 topics via HTTP +- **NEW:** Controlling the robot via sovd_web_ui ## Prerequisites @@ -24,92 +26,185 @@ This demo demonstrates: ## Quick Start +### 1. Start the ROS2 Backend + ```bash cd demos/turtlebot3_integration ./run-demo.sh ``` That's it! The script will: -1. Build the Docker image (first run takes ~5-10 min, downloads ~4GB) + +1. Build the Docker images (first run takes ~5-10 min, downloads ~4GB) 2. Setup X11 forwarding for Gazebo GUI -3. Launch TurtleBot3 simulation + ros2_medkit gateway +3. Launch TurtleBot3 simulation + Nav2 + ros2_medkit gateway +4. Launch sovd_web_ui at + +### 2. Access the Web UI + +The Web UI is automatically started by docker-compose and available at . + +Connect to the gateway using `http://localhost:8080/api/v1` in the connection dialog. + +**Note:** The first build will take longer as it clones and builds sovd_web_ui from GitHub. ### With NVIDIA GPU -For better Gazebo performance: +For hardware-accelerated Gazebo rendering with NVIDIA GPU: ```bash ./run-demo.sh --nvidia ``` -Requires [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html). +**Requirements:** + +- NVIDIA GPU with recent drivers +- [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) installed + +You can also use Docker Compose directly: + +```bash +docker compose --profile nvidia up --build +``` + +## Controlling the Robot + +### Via Web UI + +1. Connect to the gateway in sovd_web_ui +2. In the "ROS2 Topics" panel on the right, select `/cmd_vel` +3. Enter velocity command JSON: + + ```json + {"linear": {"x": 0.2}, "angular": {"z": 0.0}} + ``` + +4. Click "Send" - the robot will move! + +### Via Command Line + +```bash +# Send velocity command (moves robot forward) +curl -X POST http://localhost:8080/api/v1/topics/publish \ + -H "Content-Type: application/json" \ + -d '{ + "topic": "/cmd_vel", + "type": "geometry_msgs/msg/Twist", + "data": {"linear": {"x": 0.2, "y": 0.0, "z": 0.0}, "angular": {"x": 0.0, "y": 0.0, "z": 0.0}} + }' + +# Stop the robot +curl -X POST http://localhost:8080/api/v1/topics/publish \ + -H "Content-Type: application/json" \ + -d '{ + "topic": "/cmd_vel", + "type": "geometry_msgs/msg/Twist", + "data": {"linear": {"x": 0.0}, "angular": {"z": 0.0}} + }' +``` + +### Via ROS2 CLI (inside container) + +```bash +# Send navigation goal +ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose \ + "{pose: {header: {frame_id: 'map'}, pose: {position: {x: 2.0, y: 0.5, z: 0.0}, orientation: {w: 1.0}}}}" + +# Manual teleop +ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \ + "{linear: {x: 0.2}, angular: {z: 0.0}}" --once +``` -## Verifying Discovery +## REST API Endpoints -Once the demo is running, open a new terminal and test the REST API: +### Discovery ```bash # Check gateway health -curl http://localhost:8080/health +curl http://localhost:8080/api/v1/health # List discovered areas -curl http://localhost:8080/areas +curl http://localhost:8080/api/v1/areas # List all discovered components (nodes) -curl http://localhost:8080/components +curl http://localhost:8080/api/v1/components +``` + +### Topics + +```bash +# List all topics +curl http://localhost:8080/api/v1/topics + +# Get topic details (URL-encode topic name: / -> %2F) +curl http://localhost:8080/api/v1/topics/%2Fcmd_vel + +# Get topic without sample +curl "http://localhost:8080/api/v1/topics/%2Fcmd_vel?sample=false" + +# Publish to topic (see examples above) +curl -X POST http://localhost:8080/api/v1/topics/publish ... ``` ## What You'll See -When TurtleBot3 simulation starts, ros2_medkit will discover nodes such as: +When TurtleBot3 simulation starts with Nav2, ros2_medkit will discover nodes such as: - `turtlebot3_node` - Main robot interface - `robot_state_publisher` - TF tree publisher - `gazebo` - Simulation engine -- Various sensor and controller nodes +- `amcl` - Adaptive Monte Carlo Localization +- `bt_navigator` - Behavior Tree Navigator +- `controller_server` - Path following controller +- `planner_server` - Global path planner +- Various sensor and lifecycle nodes These appear as **components** in the ros2_medkit REST API, organized into **areas** based on their ROS 2 namespaces. ## Architecture -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Docker Container β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Gazebo Simulation β”‚ β”‚ -β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ -β”‚ β”‚ β”‚ TurtleBot3 β”‚ β”‚ robot_state β”‚ β”‚ LIDAR β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ Node β”‚ β”‚ publisher β”‚ β”‚ sensor β”‚ β”‚ β”‚ -β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ -β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ -β”‚ β”‚ ROS 2 Topics β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ ros2_medkit Gateway β”‚ β”‚ -β”‚ β”‚ (REST Server :8080) β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +```text +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Docker Container β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Gazebo Simulation β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ TurtleBot3 β”‚ β”‚ robot_state β”‚ β”‚ LIDAR β”‚ β”‚ Nav2 β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ Node β”‚ β”‚ publisher β”‚ β”‚ sensor β”‚ β”‚ (AMCL, β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ Planner, β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Controllerβ”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ ROS 2 Topics β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ ros2_medkit Gateway β”‚ β”‚ +β”‚ β”‚ (REST Server :8080) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ HTTP REST API β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β–Ό β–Ό - curl/browser External tools + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό β–Ό + sovd_web_ui curl/browser External tools + (localhost:3000) ``` ## File Structure -``` +```text demos/turtlebot3_integration/ -β”œβ”€β”€ Dockerfile # ROS 2 Jazzy + TurtleBot3 + ros2_medkit -β”œβ”€β”€ docker-compose.yml # Standard (CPU) configuration -β”œβ”€β”€ docker-compose.nvidia.yml # NVIDIA GPU configuration -β”œβ”€β”€ run-demo.sh # One-click demo launcher +β”œβ”€β”€ Dockerfile # ROS 2 Jazzy + TurtleBot3 + Nav2 + ros2_medkit +β”œβ”€β”€ docker-compose.yml # Standard (CPU) configuration +β”œβ”€β”€ docker-compose.nvidia.yml # NVIDIA GPU configuration +β”œβ”€β”€ run-demo.sh # One-click demo launcher β”œβ”€β”€ config/ -β”‚ └── medkit_params.yaml # ros2_medkit gateway config +β”‚ β”œβ”€β”€ medkit_params.yaml # ros2_medkit gateway config +β”‚ β”œβ”€β”€ nav2_params.yaml # Nav2 navigation parameters +β”‚ └── turtlebot3_world.yaml # Map configuration └── launch/ - └── demo.launch.py # ROS 2 launch file + └── demo.launch.py # ROS 2 launch file ``` ## Manual Setup (Alternative) @@ -118,15 +213,30 @@ If you prefer not to use Docker: 1. Install ROS 2 Jazzy on Ubuntu 24.04 2. Install TurtleBot3: `sudo apt install ros-jazzy-turtlebot3-gazebo` -3. Build [ros2_medkit](https://github.com/selfpatch/ros2_medkit) from source -4. Set `export TURTLEBOT3_MODEL=burger` -5. Run: `ros2 launch launch/demo.launch.py` +3. Install Nav2: `sudo apt install ros-jazzy-nav2-bringup` +4. Build [ros2_medkit](https://github.com/selfpatch/ros2_medkit) from source +5. Set environment: + + ```bash + export TURTLEBOT3_MODEL=burger + export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:/opt/ros/jazzy/share/turtlebot3_gazebo/models + ``` + +6. Run: `ros2 launch launch/demo.launch.py` + +## Troubleshooting + +### Gazebo window doesn't appear + +- Ensure X11 forwarding is set up: `xhost +local:docker` +- Check DISPLAY environment variable + +### Nav2 doesn't start properly -## Next Steps +- Wait for AMCL to localize (give an initial pose in RViz or via CLI) +- Check lifecycle states with `ros2 lifecycle list` -Future versions of this demo will add: +### Robot doesn't move with /cmd_vel -- Nav2 navigation stack integration -- Teleopereration with diagnostic monitoring -- Sensor data visualization through the API -- Health monitoring during autonomous navigation +- Make sure Nav2's velocity smoother isn't overriding commands +- Check if collision monitor is blocking movement diff --git a/demos/turtlebot3_integration/config/medkit_params.yaml b/demos/turtlebot3_integration/config/medkit_params.yaml index d819c14..bdc7c01 100644 --- a/demos/turtlebot3_integration/config/medkit_params.yaml +++ b/demos/turtlebot3_integration/config/medkit_params.yaml @@ -1,7 +1,11 @@ # ros2_medkit gateway configuration for TurtleBot3 demo ros2_medkit_gateway: ros__parameters: - server: - host: "0.0.0.0" - port: 8080 + server.host: "0.0.0.0" + server.port: 8080 refresh_interval_ms: 2000 + cors.allowed_origins: ["*"] + cors.allowed_methods: ["GET", "PUT", "POST", "OPTIONS"] + cors.allowed_headers: ["Content-Type", "Accept"] + cors.allow_credentials: false + cors.max_age_seconds: 86400 diff --git a/demos/turtlebot3_integration/config/nav2_params.yaml b/demos/turtlebot3_integration/config/nav2_params.yaml new file mode 100644 index 0000000..6ff7bf0 --- /dev/null +++ b/demos/turtlebot3_integration/config/nav2_params.yaml @@ -0,0 +1,365 @@ +# Nav2 parameters for TurtleBot3 + ros2_medkit demo +# Based on default nav2_bringup parameters for TurtleBot3 burger + +amcl: + ros__parameters: + use_sim_time: True + alpha1: 0.2 + alpha2: 0.2 + alpha3: 0.2 + alpha4: 0.2 + alpha5: 0.2 + base_frame_id: "base_footprint" + beam_skip_distance: 0.5 + beam_skip_error_threshold: 0.9 + beam_skip_threshold: 0.3 + do_beamskip: false + global_frame_id: "map" + lambda_short: 0.1 + laser_likelihood_max_dist: 2.0 + laser_max_range: 100.0 + laser_min_range: -1.0 + laser_model_type: "likelihood_field" + max_beams: 60 + max_particles: 2000 + min_particles: 500 + odom_frame_id: "odom" + pf_err: 0.05 + pf_z: 0.99 + recovery_alpha_fast: 0.0 + recovery_alpha_slow: 0.0 + resample_interval: 1 + robot_model_type: "nav2_amcl::DifferentialMotionModel" + save_pose_rate: 0.5 + sigma_hit: 0.2 + tf_broadcast: true + transform_tolerance: 1.0 + update_min_a: 0.2 + update_min_d: 0.25 + z_hit: 0.5 + z_max: 0.05 + z_rand: 0.5 + z_short: 0.05 + scan_topic: scan + set_initial_pose: true + initial_pose: + x: 0.0 + y: 0.0 + z: 0.0 + yaw: 0.0 + +bt_navigator: + ros__parameters: + use_sim_time: True + global_frame: map + robot_base_frame: base_link + odom_topic: /odom + bt_loop_duration: 10 + default_server_timeout: 20 + wait_for_service_timeout: 1000 + action_server_result_timeout: 900.0 + navigators: ["navigate_to_pose", "navigate_through_poses"] + navigate_to_pose: + plugin: "nav2_bt_navigator/NavigateToPoseNavigator" + navigate_through_poses: + plugin: "nav2_bt_navigator/NavigateThroughPosesNavigator" + plugin_lib_names: + - nav2_compute_path_to_pose_action_bt_node + - nav2_compute_path_through_poses_action_bt_node + - nav2_smooth_path_action_bt_node + - nav2_follow_path_action_bt_node + - nav2_spin_action_bt_node + - nav2_wait_action_bt_node + - nav2_assisted_teleop_action_bt_node + - nav2_back_up_action_bt_node + - nav2_drive_on_heading_bt_node + - nav2_clear_costmap_service_bt_node + - nav2_is_stuck_condition_bt_node + - nav2_goal_reached_condition_bt_node + - nav2_goal_updated_condition_bt_node + - nav2_globally_updated_goal_condition_bt_node + - nav2_is_path_valid_condition_bt_node + - nav2_are_error_codes_active_condition_bt_node + - nav2_would_a_controller_recovery_help_condition_bt_node + - nav2_would_a_planner_recovery_help_condition_bt_node + - nav2_would_a_smoother_recovery_help_condition_bt_node + - nav2_initial_pose_received_condition_bt_node + - nav2_reinitialize_global_localization_service_bt_node + - nav2_rate_controller_bt_node + - nav2_distance_controller_bt_node + - nav2_speed_controller_bt_node + - nav2_truncate_path_action_bt_node + - nav2_truncate_path_local_action_bt_node + - nav2_goal_updater_node_bt_node + - nav2_recovery_node_bt_node + - nav2_pipeline_sequence_bt_node + - nav2_round_robin_node_bt_node + - nav2_transform_available_condition_bt_node + - nav2_time_expired_condition_bt_node + - nav2_path_expiring_timer_condition + - nav2_distance_traveled_condition_bt_node + - nav2_single_trigger_bt_node + - nav2_goal_updated_controller_bt_node + - nav2_is_battery_low_condition_bt_node + - nav2_navigate_to_pose_action_bt_node + - nav2_navigate_through_poses_action_bt_node + - nav2_remove_passed_goals_action_bt_node + - nav2_planner_selector_bt_node + - nav2_controller_selector_bt_node + - nav2_goal_checker_selector_bt_node + - nav2_controller_cancel_bt_node + - nav2_path_longer_on_approach_bt_node + - nav2_wait_cancel_bt_node + - nav2_spin_cancel_bt_node + - nav2_back_up_cancel_bt_node + - nav2_assisted_teleop_cancel_bt_node + - nav2_drive_on_heading_cancel_bt_node + - nav2_is_battery_charging_condition_bt_node + +controller_server: + ros__parameters: + use_sim_time: True + controller_frequency: 20.0 + min_x_velocity_threshold: 0.001 + min_y_velocity_threshold: 0.5 + min_theta_velocity_threshold: 0.001 + failure_tolerance: 0.3 + progress_checker_plugins: ["progress_checker"] + goal_checker_plugins: ["general_goal_checker"] + controller_plugins: ["FollowPath"] + odom_topic: "odom" + + progress_checker: + plugin: "nav2_controller::SimpleProgressChecker" + required_movement_radius: 0.5 + movement_time_allowance: 10.0 + + general_goal_checker: + stateful: True + plugin: "nav2_controller::SimpleGoalChecker" + xy_goal_tolerance: 0.25 + yaw_goal_tolerance: 0.25 + + FollowPath: + plugin: "dwb_core::DWBLocalPlanner" + debug_trajectory_details: True + min_vel_x: 0.0 + min_vel_y: 0.0 + max_vel_x: 0.26 + max_vel_y: 0.0 + max_vel_theta: 1.0 + min_speed_xy: 0.0 + max_speed_xy: 0.26 + min_speed_theta: 0.0 + acc_lim_x: 2.5 + acc_lim_y: 0.0 + acc_lim_theta: 3.2 + decel_lim_x: -2.5 + decel_lim_y: 0.0 + decel_lim_theta: -3.2 + vx_samples: 20 + vy_samples: 5 + vtheta_samples: 20 + sim_time: 1.7 + linear_granularity: 0.05 + angular_granularity: 0.025 + transform_tolerance: 0.2 + xy_goal_tolerance: 0.25 + trans_stopped_velocity: 0.25 + short_circuit_trajectory_evaluation: True + stateful: True + critics: ["RotateToGoal", "Oscillation", "BaseObstacle", "GoalAlign", "PathAlign", "PathDist", "GoalDist"] + BaseObstacle.scale: 0.02 + PathAlign.scale: 32.0 + PathAlign.forward_point_distance: 0.1 + GoalAlign.scale: 24.0 + GoalAlign.forward_point_distance: 0.1 + PathDist.scale: 32.0 + GoalDist.scale: 24.0 + RotateToGoal.scale: 32.0 + RotateToGoal.slowing_factor: 5.0 + RotateToGoal.lookahead_time: -1.0 + +local_costmap: + local_costmap: + ros__parameters: + update_frequency: 5.0 + publish_frequency: 2.0 + global_frame: odom + robot_base_frame: base_link + use_sim_time: True + rolling_window: true + width: 3 + height: 3 + resolution: 0.05 + robot_radius: 0.22 + plugins: ["voxel_layer", "inflation_layer"] + inflation_layer: + plugin: "nav2_costmap_2d::InflationLayer" + cost_scaling_factor: 3.0 + inflation_radius: 0.55 + voxel_layer: + plugin: "nav2_costmap_2d::VoxelLayer" + enabled: True + publish_voxel_map: True + origin_z: 0.0 + z_resolution: 0.05 + z_voxels: 16 + max_obstacle_height: 2.0 + mark_threshold: 0 + observation_sources: scan + scan: + topic: /scan + max_obstacle_height: 2.0 + clearing: True + marking: True + data_type: "LaserScan" + raytrace_max_range: 3.0 + raytrace_min_range: 0.0 + obstacle_max_range: 2.5 + obstacle_min_range: 0.0 + static_layer: + plugin: "nav2_costmap_2d::StaticLayer" + map_subscribe_transient_local: True + always_send_full_costmap: True + +global_costmap: + global_costmap: + ros__parameters: + update_frequency: 1.0 + publish_frequency: 1.0 + global_frame: map + robot_base_frame: base_link + use_sim_time: True + robot_radius: 0.22 + resolution: 0.05 + track_unknown_space: true + plugins: ["static_layer", "obstacle_layer", "inflation_layer"] + obstacle_layer: + plugin: "nav2_costmap_2d::ObstacleLayer" + enabled: True + observation_sources: scan + scan: + topic: /scan + max_obstacle_height: 2.0 + clearing: True + marking: True + data_type: "LaserScan" + raytrace_max_range: 3.0 + raytrace_min_range: 0.0 + obstacle_max_range: 2.5 + obstacle_min_range: 0.0 + static_layer: + plugin: "nav2_costmap_2d::StaticLayer" + map_subscribe_transient_local: True + inflation_layer: + plugin: "nav2_costmap_2d::InflationLayer" + cost_scaling_factor: 3.0 + inflation_radius: 0.55 + always_send_full_costmap: True + +planner_server: + ros__parameters: + expected_planner_frequency: 20.0 + use_sim_time: True + planner_plugins: ["GridBased"] + GridBased: + plugin: "nav2_navfn_planner::NavfnPlanner" + tolerance: 0.5 + use_astar: false + allow_unknown: true + +smoother_server: + ros__parameters: + use_sim_time: True + smoother_plugins: ["simple_smoother"] + simple_smoother: + plugin: "nav2_smoother::SimpleSmoother" + tolerance: 1.0e-10 + max_its: 1000 + do_refinement: True + +behavior_server: + ros__parameters: + local_costmap_topic: local_costmap/costmap_raw + global_costmap_topic: global_costmap/costmap_raw + local_footprint_topic: local_costmap/published_footprint + global_footprint_topic: global_costmap/published_footprint + cycle_frequency: 10.0 + behavior_plugins: ["spin", "backup", "drive_on_heading", "assisted_teleop", "wait"] + spin: + plugin: "nav2_behaviors/Spin" + backup: + plugin: "nav2_behaviors/BackUp" + drive_on_heading: + plugin: "nav2_behaviors/DriveOnHeading" + wait: + plugin: "nav2_behaviors/Wait" + assisted_teleop: + plugin: "nav2_behaviors/AssistedTeleop" + local_frame: odom + global_frame: map + robot_base_frame: base_link + transform_tolerance: 0.1 + use_sim_time: True + simulate_ahead_time: 2.0 + max_rotational_vel: 1.0 + min_rotational_vel: 0.4 + rotational_acc_lim: 3.2 + +waypoint_follower: + ros__parameters: + use_sim_time: True + loop_rate: 20 + stop_on_failure: false + action_server_result_timeout: 900.0 + waypoint_task_executor_plugin: "wait_at_waypoint" + wait_at_waypoint: + plugin: "nav2_waypoint_follower::WaitAtWaypoint" + enabled: True + waypoint_pause_duration: 200 + +velocity_smoother: + ros__parameters: + use_sim_time: True + smoothing_frequency: 20.0 + scale_velocities: False + feedback: "OPEN_LOOP" + max_velocity: [0.26, 0.0, 1.0] + min_velocity: [-0.26, 0.0, -1.0] + max_accel: [2.5, 0.0, 3.2] + max_decel: [-2.5, 0.0, -3.2] + odom_topic: "odom" + odom_duration: 0.1 + deadband_velocity: [0.0, 0.0, 0.0] + velocity_timeout: 1.0 + +collision_monitor: + ros__parameters: + use_sim_time: True + base_frame_id: "base_link" + odom_frame_id: "odom" + cmd_vel_in_topic: "cmd_vel_smoothed" + cmd_vel_out_topic: "cmd_vel" + state_topic: "collision_monitor_state" + transform_tolerance: 0.2 + source_timeout: 1.0 + base_shift_correction: True + stop_pub_timeout: 2.0 + polygons: ["FootprintApproach"] + FootprintApproach: + type: "polygon" + action_type: "approach" + footprint_topic: "/local_costmap/published_footprint" + time_before_collision: 1.2 + simulation_time_step: 0.1 + min_points: 6 + visualize: False + enabled: True + observation_sources: ["scan"] + scan: + type: "scan" + topic: "scan" + min_height: 0.15 + max_height: 2.0 + enabled: True diff --git a/demos/turtlebot3_integration/config/turtlebot3_world.yaml b/demos/turtlebot3_integration/config/turtlebot3_world.yaml new file mode 100644 index 0000000..ad2e683 --- /dev/null +++ b/demos/turtlebot3_integration/config/turtlebot3_world.yaml @@ -0,0 +1,7 @@ +image: /opt/ros/jazzy/share/turtlebot3_navigation2/map/map.pgm +mode: trinary +resolution: 0.05 +origin: [-1.76, -2.42, 0.0] +negate: 0 +occupied_thresh: 0.65 +free_thresh: 0.196 diff --git a/demos/turtlebot3_integration/docker-compose.nvidia.yml b/demos/turtlebot3_integration/docker-compose.nvidia.yml deleted file mode 100644 index 70cf042..0000000 --- a/demos/turtlebot3_integration/docker-compose.nvidia.yml +++ /dev/null @@ -1,34 +0,0 @@ -# NVIDIA GPU accelerated version -# Requires: nvidia-container-toolkit -# Install: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html - -services: - turtlebot3-demo: - build: - context: . - dockerfile: Dockerfile - container_name: turtlebot3_medkit_demo_nvidia - environment: - - DISPLAY=${DISPLAY} - - TURTLEBOT3_MODEL=burger - - ROS_DOMAIN_ID=30 - - NVIDIA_VISIBLE_DEVICES=all - - NVIDIA_DRIVER_CAPABILITIES=all - volumes: - - /tmp/.X11-unix:/tmp/.X11-unix:rw - ports: - - "8080:8080" - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: all - capabilities: [gpu] - stdin_open: true - tty: true - command: > - bash -c "source /opt/ros/jazzy/setup.bash && - source /root/demo_ws/install/setup.bash && - export TURTLEBOT3_MODEL=burger && - ros2 launch turtlebot3_medkit_demo demo.launch.py" diff --git a/demos/turtlebot3_integration/docker-compose.yml b/demos/turtlebot3_integration/docker-compose.yml index 44bad8d..1e8833f 100644 --- a/demos/turtlebot3_integration/docker-compose.yml +++ b/demos/turtlebot3_integration/docker-compose.yml @@ -1,4 +1,5 @@ services: + # CPU-only version (default) turtlebot3-demo: build: context: . @@ -19,3 +20,51 @@ services: source /root/demo_ws/install/setup.bash && export TURTLEBOT3_MODEL=burger && ros2 launch turtlebot3_medkit_demo demo.launch.py" + + # NVIDIA GPU accelerated version + # Use with: docker compose --profile nvidia up + # Requires: nvidia-container-toolkit + # Install: + # https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html + turtlebot3-demo-nvidia: + profiles: ["nvidia"] + build: + context: . + dockerfile: Dockerfile + container_name: turtlebot3_medkit_demo_nvidia + environment: + - DISPLAY=${DISPLAY} + - TURTLEBOT3_MODEL=burger + - ROS_DOMAIN_ID=30 + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_DRIVER_CAPABILITIES=all + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix:rw + ports: + - "8080:8080" + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + stdin_open: true + tty: true + command: > + bash -c "source /opt/ros/jazzy/setup.bash && + source /root/demo_ws/install/setup.bash && + export TURTLEBOT3_MODEL=burger && + ros2 launch turtlebot3_medkit_demo demo.launch.py" + + sovd-web-ui: + # TODO: Replace with Docker Hub image once sovd_web_ui is published + # For now, we clone and build from GitHub + build: + context: https://github.com/selfpatch/sovd_web_ui.git + dockerfile: Dockerfile + container_name: sovd_web_ui + ports: + - "3000:80" + depends_on: + - turtlebot3-demo diff --git a/demos/turtlebot3_integration/launch/demo.launch.py b/demos/turtlebot3_integration/launch/demo.launch.py index b1321d2..01c80a9 100644 --- a/demos/turtlebot3_integration/launch/demo.launch.py +++ b/demos/turtlebot3_integration/launch/demo.launch.py @@ -1,34 +1,62 @@ -"""Launch TurtleBot3 simulation with ros2_medkit gateway for discovery demo.""" +"""Launch TurtleBot3 simulation with Nav2 and ros2_medkit gateway for discovery demo.""" import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch.actions import IncludeLaunchDescription, SetEnvironmentVariable +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, SetEnvironmentVariable from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node def generate_launch_description(): # Get package directories turtlebot3_gazebo_dir = get_package_share_directory('turtlebot3_gazebo') + nav2_bringup_dir = get_package_share_directory('nav2_bringup') demo_pkg_dir = get_package_share_directory('turtlebot3_medkit_demo') - # Path to medkit params from installed package + # Path to config files from installed package medkit_params_file = os.path.join(demo_pkg_dir, 'config', 'medkit_params.yaml') + nav2_params_file = os.path.join(demo_pkg_dir, 'config', 'nav2_params.yaml') + map_file = os.path.join(demo_pkg_dir, 'config', 'turtlebot3_world.yaml') + + # Launch configuration variables + use_sim_time = LaunchConfiguration('use_sim_time', default='True') return LaunchDescription([ + # Declare launch arguments + DeclareLaunchArgument( + 'use_sim_time', + default_value='True', + description='Use simulation (Gazebo) clock if true' + ), + # Set TurtleBot3 model (can be overridden by environment variable) SetEnvironmentVariable( name='TURTLEBOT3_MODEL', value=os.environ.get('TURTLEBOT3_MODEL', 'burger') ), - # Launch TurtleBot3 Gazebo simulation (empty world) + # Launch TurtleBot3 Gazebo simulation (turtlebot3_world) + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(turtlebot3_gazebo_dir, 'launch', 'turtlebot3_world.launch.py') + ), + launch_arguments={'use_sim_time': use_sim_time}.items() + ), + + # Launch Nav2 navigation stack IncludeLaunchDescription( PythonLaunchDescriptionSource( - os.path.join(turtlebot3_gazebo_dir, 'launch', 'empty_world.launch.py') - ) + os.path.join(nav2_bringup_dir, 'launch', 'bringup_launch.py') + ), + launch_arguments={ + 'map': map_file, + 'params_file': nav2_params_file, + 'use_sim_time': use_sim_time, + 'autostart': 'True', + }.items() ), # Launch ros2_medkit gateway @@ -37,6 +65,6 @@ def generate_launch_description(): executable='gateway_node', name='ros2_medkit_gateway', output='screen', - parameters=[medkit_params_file], + parameters=[medkit_params_file, {'use_sim_time': use_sim_time}], ), ]) diff --git a/demos/turtlebot3_integration/package.xml b/demos/turtlebot3_integration/package.xml index b208d0f..4bffd0a 100644 --- a/demos/turtlebot3_integration/package.xml +++ b/demos/turtlebot3_integration/package.xml @@ -3,7 +3,7 @@ turtlebot3_medkit_demo 0.1.0 - TurtleBot3 + ros2_medkit integration demo + TurtleBot3 + ros2_medkit integration demo with Nav2 navigation Demo Maintainer Apache-2.0 @@ -12,6 +12,15 @@ ros2launch turtlebot3_gazebo ros2_medkit_gateway + nav2_bringup + nav2_bt_navigator + nav2_controller + nav2_planner + nav2_behaviors + nav2_costmap_2d + nav2_lifecycle_manager + nav2_map_server + nav2_amcl ament_cmake diff --git a/demos/turtlebot3_integration/run-demo.sh b/demos/turtlebot3_integration/run-demo.sh index 429709c..a9e188f 100755 --- a/demos/turtlebot3_integration/run-demo.sh +++ b/demos/turtlebot3_integration/run-demo.sh @@ -1,13 +1,19 @@ #!/bin/bash -# TurtleBot3 + ros2_medkit Demo Runner +# TurtleBot3 + ros2_medkit Demo Runner with Nav2 Navigation set -eu SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" -echo "πŸ€– TurtleBot3 + ros2_medkit Demo" -echo "==================================" +echo "πŸ€– TurtleBot3 + ros2_medkit + Nav2 Demo" +echo "=========================================" + +# Set TurtleBot3 environment variables +export TURTLEBOT3_MODEL=${TURTLEBOT3_MODEL:-burger} +export GAZEBO_MODEL_PATH=${GAZEBO_MODEL_PATH:-}:/opt/ros/jazzy/share/turtlebot3_gazebo/models + +echo "TurtleBot3 Model: $TURTLEBOT3_MODEL" # Check for Docker if ! command -v docker &> /dev/null; then @@ -15,8 +21,8 @@ if ! command -v docker &> /dev/null; then exit 1 fi -# Setup X11 forwarding for GUI (Gazebo) -echo "πŸ–₯️ Setting up X11 forwarding..." +# Setup X11 forwarding for GUI (Gazebo, RViz) +echo "Setting up X11 forwarding..." xhost +local:docker 2>/dev/null || { echo " Warning: xhost failed. GUI may not work." echo " Install with: sudo apt install x11-xserver-utils" @@ -31,21 +37,31 @@ cleanup() { } trap cleanup EXIT -# Select compose file -if [[ "$1" == "--nvidia" ]]; then +# Select compose profile +if [[ "${1:-}" == "--nvidia" ]]; then echo "Using NVIDIA GPU acceleration" - COMPOSE_FILE="docker-compose.nvidia.yml" + COMPOSE_ARGS="--profile nvidia" else - COMPOSE_FILE="docker-compose.yml" + echo "Using CPU-only mode (use --nvidia flag for GPU acceleration)" + COMPOSE_ARGS="" fi # Build and run echo " Building and starting demo..." echo " (First run takes ~5-10 min, downloading ~4GB image)" echo "" +echo "πŸ“ After launch, use the following to send navigation goals:" +echo " ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose \\" +echo " \"{pose: {header: {frame_id: 'map'}, pose: {position: {x: 2.0, y: 0.5, z: 0.0}, orientation: {w: 1.0}}}}\"" +echo "" +echo "🌐 REST API available at: http://localhost:8080/api/v1/" +echo "🌐 Web UI available at: http://localhost:3000/" +echo "" if docker compose version &> /dev/null; then - docker compose -f "$COMPOSE_FILE" up --build + # shellcheck disable=SC2086 + docker compose ${COMPOSE_ARGS} up --build else - docker-compose -f "$COMPOSE_FILE" up --build + # shellcheck disable=SC2086 + docker-compose ${COMPOSE_ARGS} up --build fi From 76c792a966e11867f7353e937af07bc6cd4b719b Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Tue, 2 Dec 2025 16:45:58 +0000 Subject: [PATCH 3/4] fix: correct CI find commands and improve security - Fix operator precedence in find commands for shellcheck and yamllint by adding parentheses around -name patterns - Update README to reflect actual docker-compose profiles approach (removed reference to non-existent docker-compose.nvidia.yml) - Use HTTPS instead of HTTP for ROS2 APT repository --- .devcontainer/Dockerfile | 2 +- .github/workflows/ci.yml | 4 ++-- demos/turtlebot3_integration/README.md | 3 +-- demos/turtlebot3_integration/docker-compose.yml | 2 -- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 2cb425e..13a9d9b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -43,7 +43,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \ # Add ROS 2 repository and install ROS 2 Jazzy RUN curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \ | gpg --dearmor -o /usr/share/keyrings/ros-archive-keyring.gpg && \ - echo "deb [signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) main" \ + echo "deb [signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] https://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) main" \ > /etc/apt/sources.list.d/ros2.list RUN apt-get update && apt-get install -y \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b34731f..2cb40d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,13 +22,13 @@ jobs: - name: Check shell scripts with shellcheck run: | sudo apt-get update && sudo apt-get install -y shellcheck - find . -name "*.sh" -o -name "*.bash" -type f | xargs shellcheck + find . \( -name "*.sh" -o -name "*.bash" \) -type f | xargs shellcheck - name: Validate YAML files run: | sudo apt-get update && sudo apt-get install -y yamllint YAMLLINT_CONFIG="{extends: relaxed, rules: {line-length: {max: 120}}}" - find . -name "*.yaml" -o -name "*.yml" | xargs yamllint -d "$YAMLLINT_CONFIG" + find . \( -name "*.yaml" -o -name "*.yml" \) -type f | xargs yamllint -d "$YAMLLINT_CONFIG" docker-build: runs-on: ubuntu-24.04 diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md index 7fecf1f..e41734f 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -196,8 +196,7 @@ These appear as **components** in the ros2_medkit REST API, organized into **are ```text demos/turtlebot3_integration/ β”œβ”€β”€ Dockerfile # ROS 2 Jazzy + TurtleBot3 + Nav2 + ros2_medkit -β”œβ”€β”€ docker-compose.yml # Standard (CPU) configuration -β”œβ”€β”€ docker-compose.nvidia.yml # NVIDIA GPU configuration +β”œβ”€β”€ docker-compose.yml # Docker Compose (CPU & GPU via profiles) β”œβ”€β”€ run-demo.sh # One-click demo launcher β”œβ”€β”€ config/ β”‚ β”œβ”€β”€ medkit_params.yaml # ros2_medkit gateway config diff --git a/demos/turtlebot3_integration/docker-compose.yml b/demos/turtlebot3_integration/docker-compose.yml index 1e8833f..cebc44a 100644 --- a/demos/turtlebot3_integration/docker-compose.yml +++ b/demos/turtlebot3_integration/docker-compose.yml @@ -66,5 +66,3 @@ services: container_name: sovd_web_ui ports: - "3000:80" - depends_on: - - turtlebot3-demo From 200788728b9f10c6eda9a9a3b69d70feed4ae669 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Tue, 2 Dec 2025 18:05:37 +0000 Subject: [PATCH 4/4] Remove .devcontainer from demo repo because it is not working well with docker-outside-docker and docker compose. --- .devcontainer/Dockerfile | 96 --------------------------------- .devcontainer/devcontainer.json | 58 -------------------- .devcontainer/setup-env.sh | 29 ---------- 3 files changed, 183 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json delete mode 100755 .devcontainer/setup-env.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 13a9d9b..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,96 +0,0 @@ -FROM ubuntu:24.04 - -# Environment setup -ENV ROS_DISTRO=jazzy -ENV LANG=en_US.UTF-8 -ENV LC_ALL=en_US.UTF-8 -ENV HADOLINT_VERSION=v2.14.0 - -# Args -ARG USERNAME=devuser -ARG USER_UID=1000 -ARG USER_GID=1000 - -# System dependencies -RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \ - locales \ - build-essential \ - cmake \ - python3-pip \ - python3-venv \ - python3-dev \ - python3-setuptools \ - python3-wheel \ - python-is-python3 \ - wget \ - curl \ - vim \ - nano \ - sudo \ - software-properties-common \ - lsb-release \ - gnupg2 \ - yq \ - ca-certificates \ - clang-format \ - graphviz \ - plantuml \ - shellcheck \ - yamllint \ - && locale-gen en_US.UTF-8 \ - && rm -rf /var/lib/apt/lists/* - -# Add ROS 2 repository and install ROS 2 Jazzy -RUN curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \ - | gpg --dearmor -o /usr/share/keyrings/ros-archive-keyring.gpg && \ - echo "deb [signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] https://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) main" \ - > /etc/apt/sources.list.d/ros2.list - -RUN apt-get update && apt-get install -y \ - ros-jazzy-desktop-full \ - python3-colcon-common-extensions \ - python3-rosdep \ - && rm -rf /var/lib/apt/lists/* - -# Install additional Python tools via pip -RUN pip install --break-system-packages \ - vcstool \ - setuptools \ - pytest \ - argcomplete \ - black \ - flake8-blind-except \ - flake8-builtins \ - flake8-class-newline \ - flake8-comprehensions \ - flake8-deprecated \ - flake8-docstrings \ - flake8-import-order \ - flake8-quotes \ - pytest-repeat \ - pytest-rerunfailures \ - sphinx \ - sphinx-rtd-theme \ - sphinx-needs \ - sphinxcontrib-plantuml \ - sphinx-autobuild \ - myst-parser - -# Install hadolint for Dockerfile linting -RUN wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-Linux-x86_64 && \ - chmod +x /usr/local/bin/hadolint - -# Create non-root user -# Remove existing user/group with the same UID/GID if they exist -RUN if id -u ${USER_UID} &>/dev/null; then userdel -r "$(id -un ${USER_UID})" 2>/dev/null || true; fi && \ - if getent group ${USER_GID} &>/dev/null; then groupdel "$(getent group ${USER_GID} | cut -d: -f1)" 2>/dev/null || true; fi && \ - groupadd --gid ${USER_GID} ${USERNAME} && \ - useradd --uid ${USER_UID} --gid ${USER_GID} -m ${USERNAME} && \ - usermod -aG sudo ${USERNAME} && \ - echo "${USERNAME} ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/${USERNAME} && \ - chmod 0440 /etc/sudoers.d/${USERNAME} - -# Switch to user context -USER ${USERNAME} -WORKDIR /home/${USERNAME} -RUN mkdir -p /home/${USERNAME}/workspace diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index da976d6..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "ROS2 ros2_medkit Development", - "build": { - "dockerfile": "Dockerfile", - "context": "..", - "args": { - "USERNAME": "${localEnv:USER}" - } - }, - "workspaceFolder": "/home/${localEnv:USER}/workspace", - "workspaceMount": "source=${localWorkspaceFolder},target=/home/${localEnv:USER}/workspace,type=bind", - "mounts": [ - "source=${localEnv:HOME}/.ssh,target=/home/${localEnv:USER}/.ssh,type=bind,consistency=cached,readonly", - "source=${localEnv:HOME}/.gitconfig,target=/home/${localEnv:USER}/.gitconfig,type=bind,consistency=cached" - ], - "features": { - // Comment this out if you don't want zsh and oh-my-zsh - "ghcr.io/devcontainers/features/common-utils:2": { - "installZsh": true, - "configureZshAsDefaultShell": true, - "installOhMyZsh": true - }, - "ghcr.io/devcontainers/features/git:1": {}, - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {} - }, - "customizations": { - "vscode": { - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cpptools-extension-pack", - "ms-python.python", - "ms-python.pylint", - "ms-python.vscode-pylance", - "ms-python.black-formatter", - "ms-python.isort", - "redhat.vscode-xml", - "dotjoshjohnson.xml", - "twxs.cmake", - "ms-vscode.cmake-tools", - "xaver.clang-format", - "redhat.vscode-yaml", - "usernamehw.errorlens", - "GitHub.copilot", - "GitHub.copilot-chat", - "eamodio.gitlens", - "christian-kohler.path-intellisense" - ], - "settings": { - "terminal.integrated.defaultProfile.linux": "zsh", - "python.defaultInterpreterPath": "/usr/bin/python3", - "cmake.configureOnOpen": false - } - } - }, - "remoteUser": "${localEnv:USER}", - "postCreateCommand": "bash .devcontainer/setup-env.sh" -} diff --git a/.devcontainer/setup-env.sh b/.devcontainer/setup-env.sh deleted file mode 100755 index 4e4a922..0000000 --- a/.devcontainer/setup-env.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -set -e - -# Add ROS2 setup to shell configs -grep -Fxq "source /opt/ros/jazzy/setup.bash" ~/.bashrc || echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc -grep -Fxq "source /opt/ros/jazzy/setup.zsh" ~/.zshrc || echo "source /opt/ros/jazzy/setup.zsh" >> ~/.zshrc - -# Source the current shell to apply changes immediately -if [ -n "$ZSH_VERSION" ]; then - # Running in zsh - # shellcheck disable=SC1090,SC1091 - source ~/.zshrc 2>/dev/null || true -elif [ -n "$BASH_VERSION" ]; then - # Running in bash - # shellcheck disable=SC1090,SC1091 - source ~/.bashrc 2>/dev/null || true -fi - -# Initialize rosdep if not already initialized -if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then - sudo rosdep init -fi -rosdep update - -# Test installations -echo "Testing installations..." -echo "ROS2 Jazzy: $(test -f /opt/ros/jazzy/setup.bash && echo 'Installed' || echo 'Not found')" - -echo "Environment setup complete!" \ No newline at end of file