diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8120b24..2cb40d3 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" \) -type f | 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/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..e0cfdae --- /dev/null +++ b/demos/turtlebot3_integration/Dockerfile @@ -0,0 +1,68 @@ +# TurtleBot3 + ros2_medkit Discovery Demo with Nav2 Navigation +# 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 GAZEBO_MODEL_PATH=/opt/ros/jazzy/share/turtlebot3_gazebo/models +ENV COLCON_WS=/root/demo_ws + +# 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 \ + libcpp-httplib-dev \ + git \ + curl \ + && 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 + +# 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 && \ + echo "export GAZEBO_MODEL_PATH=/opt/ros/jazzy/share/turtlebot3_gazebo/models" >> ~/.bashrc + +EXPOSE 8080 + +CMD ["bash"] diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md index 13c0c7d..e41734f 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -1,40 +1,241 @@ -# TurtleBot3 Integration Demo +# TurtleBot3 Integration Demo with Nav2 Navigation -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 and Nav2 navigation stack +to provide modern diagnostics and control for a mobile robot system via REST API. ## Status -🚧 **Work in Progress** +βœ… **Demo Ready** - Full navigation demo with Web UI ## 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 with turtlebot3_world +- Running Nav2 navigation stack (AMCL, planner, controller) +- Running ros2_medkit gateway alongside the robot +- Discovering TurtleBot3 nodes through REST API +- Querying and publishing to ROS2 topics via HTTP +- **NEW:** Controlling the robot via sovd_web_ui ## 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* +### 1. Start the ROS2 Backend -## Usage +```bash +cd demos/turtlebot3_integration +./run-demo.sh +``` -*Coming soon* +That's it! The script will: + +1. Build the Docker images (first run takes ~5-10 min, downloads ~4GB) +2. Setup X11 forwarding for Gazebo GUI +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 hardware-accelerated Gazebo rendering with NVIDIA GPU: + +```bash +./run-demo.sh --nvidia +``` + +**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 +``` + +## REST API Endpoints + +### Discovery + +```bash +# Check gateway health +curl http://localhost:8080/api/v1/health + +# List discovered areas +curl http://localhost:8080/api/v1/areas + +# List all discovered components (nodes) +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 -*Coming soon* +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 +- `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 -*Coming soon* +```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 + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό β–Ό + sovd_web_ui curl/browser External tools + (localhost:3000) +``` + +## File Structure + +```text +demos/turtlebot3_integration/ +β”œβ”€β”€ Dockerfile # ROS 2 Jazzy + TurtleBot3 + Nav2 + ros2_medkit +β”œβ”€β”€ docker-compose.yml # Docker Compose (CPU & GPU via profiles) +β”œβ”€β”€ run-demo.sh # One-click demo launcher +β”œβ”€β”€ 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 +``` + +## 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. 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 + +- Wait for AMCL to localize (give an initial pose in RViz or via CLI) +- Check lifecycle states with `ros2 lifecycle list` + +### Robot doesn't move with /cmd_vel + +- 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 new file mode 100644 index 0000000..bdc7c01 --- /dev/null +++ b/demos/turtlebot3_integration/config/medkit_params.yaml @@ -0,0 +1,11 @@ +# ros2_medkit gateway configuration for TurtleBot3 demo +ros2_medkit_gateway: + ros__parameters: + 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.yml b/demos/turtlebot3_integration/docker-compose.yml new file mode 100644 index 0000000..cebc44a --- /dev/null +++ b/demos/turtlebot3_integration/docker-compose.yml @@ -0,0 +1,68 @@ +services: + # CPU-only version (default) + 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" + + # 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" diff --git a/demos/turtlebot3_integration/launch/demo.launch.py b/demos/turtlebot3_integration/launch/demo.launch.py new file mode 100644 index 0000000..01c80a9 --- /dev/null +++ b/demos/turtlebot3_integration/launch/demo.launch.py @@ -0,0 +1,70 @@ +"""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 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 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 (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(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 + Node( + package='ros2_medkit_gateway', + executable='gateway_node', + name='ros2_medkit_gateway', + output='screen', + parameters=[medkit_params_file, {'use_sim_time': use_sim_time}], + ), + ]) diff --git a/demos/turtlebot3_integration/package.xml b/demos/turtlebot3_integration/package.xml new file mode 100644 index 0000000..4bffd0a --- /dev/null +++ b/demos/turtlebot3_integration/package.xml @@ -0,0 +1,28 @@ + + + + turtlebot3_medkit_demo + 0.1.0 + TurtleBot3 + ros2_medkit integration demo with Nav2 navigation + Demo Maintainer + Apache-2.0 + + ament_cmake + + 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 new file mode 100755 index 0000000..a9e188f --- /dev/null +++ b/demos/turtlebot3_integration/run-demo.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# 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 + 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 + echo "Error: Docker is not installed" + exit 1 +fi + +# 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" +} + +# Cleanup function +cleanup() { + echo "" + echo "Cleaning up..." + xhost -local:docker 2>/dev/null || true + echo "Done!" +} +trap cleanup EXIT + +# Select compose profile +if [[ "${1:-}" == "--nvidia" ]]; then + echo "Using NVIDIA GPU acceleration" + COMPOSE_ARGS="--profile nvidia" +else + 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 + # shellcheck disable=SC2086 + docker compose ${COMPOSE_ARGS} up --build +else + # shellcheck disable=SC2086 + docker-compose ${COMPOSE_ARGS} up --build +fi