diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a0b90dd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +*.md +.github/* +discourse/* +docker/* +scripts/* +LICENSE \ No newline at end of file diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base index 122af87..2f80689 100644 --- a/docker/Dockerfile.base +++ b/docker/Dockerfile.base @@ -1,3 +1,13 @@ +# Base image for next_gen_prototype docker compose +# Purpose: reproducible ROS 2 Jazzy + Rust dev/test with prebuilt underlay workspace +# Components built in underlay /nav2_traffic_ws: +# sp_demo_nav2_bringup, rmf_nav2_traffic, rmf_path_server_demo, +# rmf_path_server_test +# Used by docker/docker-compose.yml services: gazebo_server, robot0_nav2_sim, +# robot1_nav2_sim, next_gen_prototype +# Overrides: +# docker-compose.headless.override.yml → no RViz +# docker-compose.zenoh.override.yml → RMW Zenoh FROM ros:jazzy-ros-base # Disable interactive prompts during package installation @@ -13,11 +23,11 @@ RUN apt-get update && apt-get upgrade -y && \ clang \ libclang-dev \ build-essential \ + ros-${ROS_DISTRO}-rmw-zenoh-cpp \ python3-colcon-common-extensions \ python3-colcon-cargo\ libssl-dev \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* + pkg-config # Install colcon extensions for Rust RUN pip3 install --no-cache-dir --break-system-packages colcon-ros-cargo @@ -30,7 +40,6 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no cargo install cargo-ament-build && \ chmod -R a+rwX /usr/local/cargo /usr/local/rustup - # Initialize Underlay Workspace WORKDIR /opt/ros2_rust_ws @@ -42,15 +51,22 @@ RUN apt-get update && \ rosdep init || true && \ rosdep update && \ rosdep install --from-paths /tmp/next_gen_prototype --ignore-src -yr --skip-keys="rclrs ament_cargo ament_python" && \ - rm -rf /var/lib/apt/lists/* && \ - rm -rf /tmp/next_gen_prototype + rm -rf /var/lib/apt/lists/* + +WORKDIR /nav2_traffic_ws # Build the Underlay Workspace -#RUN /bin/bash -c "source /opt/ros/jazzy/setup.bash && \ -# colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release" +RUN mkdir src && mv /tmp/next_gen_prototype /nav2_traffic_ws/src/next_gen_prototype +RUN ls /nav2_traffic_ws/src/next_gen_prototype +RUN /bin/bash -c "source /opt/ros/${ROS_DISTRO}/setup.bash && \ + colcon build \ + --cmake-args -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_FLAGS=\"-w\" \ + -DCMAKE_C_FLAGS=\"-w\"" +RUN rm -rf build/ log/ src/ # Provide automatic sourcing entrypoint configuration inside ~/.bashrc -RUN echo "source /opt/ros/jazzy/setup.bash" >> /root/.bashrc -#echo "source /opt/ros2_rust_ws/install/setup.bash" >> /root/.bashrc +RUN echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> /root/.bashrc +RUN echo "source /nav2_traffic_ws/install/setup.bash" >> /root/.bashrc CMD ["/bin/bash"] diff --git a/docker/docker-compose.headless.override.yml b/docker/docker-compose.headless.override.yml new file mode 100644 index 0000000..1ed888b --- /dev/null +++ b/docker/docker-compose.headless.override.yml @@ -0,0 +1,23 @@ +services: + robot0_nav2_sim: + command: + - bash + - -c + - > + source /nav2_traffic_ws/install/setup.bash && + ros2 launch sp_demo_nav2_bringup + cloned_multi_tb3_simulation_launch.py + robots:='robot0={x: 0.0, y: 5.0, yaw: 0.0};' + use_rviz:=False + use_simulator:=False + robot1_nav2_sim: + command: + - bash + - -c + - > + source /nav2_traffic_ws/install/setup.bash && + ros2 launch sp_demo_nav2_bringup + cloned_multi_tb3_simulation_launch.py + robots:='robot1={x: 3.0, y: 5.0, yaw: 0.0};' + use_rviz:=False + use_simulator:=False diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..1bcaae3 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,115 @@ +x-ros2-base: &ros2-base + image: ros2_rust_base:jazzy + build: + context: .. + dockerfile: docker/Dockerfile.base + +services: + + gazebo_server: + <<: *ros2-base + + container_name: gazebo_server + restart: no + + networks: + - ros2_net + + environment: + GZ_PARTITION: sim + + command: + - bash + - -c + - > + source /nav2_traffic_ws/install/setup.bash && + ros2 launch sp_demo_nav2_bringup + cloned_multi_tb3_simulation_launch.py + use_simulator:=True + + robot0_nav2_sim: + <<: *ros2-base + + container_name: robot0_nav2_sim + restart: no + + networks: + - ros2_net + + environment: + DISPLAY: ${DISPLAY:-} + WAYLAND_DISPLAY: ${WAYLAND_DISPLAY:-} + XDG_RUNTIME_DIR: ${XDG_RUNTIME_DIR:-/tmp/runtime-root} + QT_X11_NO_MITSHM: "1" + GZ_PARTITION: sim + + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix:rw + - ${XDG_RUNTIME_DIR:-/tmp}:/tmp/host-runtime:rw + + command: + - bash + - -c + - > + source /nav2_traffic_ws/install/setup.bash && + ros2 launch sp_demo_nav2_bringup + cloned_multi_tb3_simulation_launch.py + robots:='robot0={x: 0.0, y: 5.0, yaw: 0.0};' + use_rviz:=True + use_simulator:=False + + + robot1_nav2_sim: + <<: *ros2-base + + container_name: robot1_nav2_sim + restart: no + + networks: + - ros2_net + + environment: + DISPLAY: ${DISPLAY:-} + WAYLAND_DISPLAY: ${WAYLAND_DISPLAY:-} + XDG_RUNTIME_DIR: ${XDG_RUNTIME_DIR:-/tmp/runtime-root} + QT_X11_NO_MITSHM: "1" + GZ_PARTITION: sim + + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix:rw + - ${XDG_RUNTIME_DIR:-/tmp}:/tmp/host-runtime:rw + + command: + - bash + - -c + - > + source /nav2_traffic_ws/install/setup.bash && + ros2 launch sp_demo_nav2_bringup + cloned_multi_tb3_simulation_launch.py + robots:='robot1={x: 3.0, y: 5.0, yaw: 0.0};' + use_rviz:=True + use_simulator:=False + + + next_gen_prototype: + <<: *ros2-base + + container_name: next_gen_prototype + restart: no + + networks: + - ros2_net + + command: + - bash + - -c + - > + source /nav2_traffic_ws/install/setup.bash && + ros2 launch rmf_path_server_demo + demo_nav2.launch.py + robots:='robot0 robot1' + + +networks: + ros2_net: + driver: bridge \ No newline at end of file diff --git a/docker/docker-compose.zenoh.override.yml b/docker/docker-compose.zenoh.override.yml new file mode 100644 index 0000000..60b6276 --- /dev/null +++ b/docker/docker-compose.zenoh.override.yml @@ -0,0 +1,46 @@ +services: + + gazebo_server: + environment: + RMW_IMPLEMENTATION: rmw_zenoh_cpp + ZENOH_CONFIG_OVERRIDE: 'mode="client";connect/endpoints=["tcp/zenoh_router:7447"]' + depends_on: + - zenoh_router + + robot0_nav2_sim: + environment: + RMW_IMPLEMENTATION: rmw_zenoh_cpp + ZENOH_CONFIG_OVERRIDE: 'mode="client";connect/endpoints=["tcp/zenoh_router:7447"]' + depends_on: + - gazebo_server + - zenoh_router + + robot1_nav2_sim: + environment: + RMW_IMPLEMENTATION: rmw_zenoh_cpp + ZENOH_CONFIG_OVERRIDE: 'mode="client";connect/endpoints=["tcp/zenoh_router:7447"]' + depends_on: + - gazebo_server + - zenoh_router + + next_gen_prototype: + environment: + RMW_IMPLEMENTATION: rmw_zenoh_cpp + ZENOH_CONFIG_OVERRIDE: 'mode="client";connect/endpoints=["tcp/zenoh_router:7447"]' + depends_on: + - robot0_nav2_sim + - robot1_nav2_sim + - zenoh_router + + zenoh_router: + image: eclipse/zenoh:latest + container_name: zenoh_router + restart: no + + networks: + - ros2_net + + command: + - --listen + - tcp/0.0.0.0:7447 + diff --git a/nav2_integration/sp_demo_nav2_bringup/launch/cloned_multi_tb3_simulation_launch.py b/nav2_integration/sp_demo_nav2_bringup/launch/cloned_multi_tb3_simulation_launch.py index 65a7598..c037789 100644 --- a/nav2_integration/sp_demo_nav2_bringup/launch/cloned_multi_tb3_simulation_launch.py +++ b/nav2_integration/sp_demo_nav2_bringup/launch/cloned_multi_tb3_simulation_launch.py @@ -29,11 +29,12 @@ LogInfo, OpaqueFunction, RegisterEventHandler, + SetEnvironmentVariable, ) from launch.conditions import IfCondition, UnlessCondition from launch.event_handlers import OnShutdown from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration, TextSubstitution +from launch.substitutions import LaunchConfiguration, PythonExpression, TextSubstitution from launch_ros.actions import Node from nav2_common.launch import ParseMultiRobotPose @@ -122,19 +123,53 @@ def generate_launch_description(): description='Start each robot stack after its transforms are ready.', ) + declare_use_simulator_cmd = DeclareLaunchArgument( + 'use_simulator', + default_value='True', + description='Whether to start the Gazebo simulation server', + ) + + declare_start_gazebo_cmd = DeclareLaunchArgument( + 'start_gazebo', + default_value=LaunchConfiguration('use_simulator'), + description='Alias for use_simulator: whether to start the Gazebo simulation server', + ) + + start_sim = PythonExpression([ + "str('", LaunchConfiguration('use_simulator'), "').lower() in ('true', '1') and ", + "str('", LaunchConfiguration('start_gazebo'), "').lower() in ('true', '1')" + ]) + + has_clock = LaunchConfiguration('has_clock') + declare_has_clock_cmd = DeclareLaunchArgument( + 'has_clock', + default_value=start_sim, + description='Whether to bridge the simulation clock to /clock (defaults to true if starting simulator)', + ) + + declare_gz_partition_cmd = DeclareLaunchArgument( + 'gz_partition', + default_value=os.environ.get('GZ_PARTITION', 'sim'), + description='Gazebo Transport partition name', + ) + # Start Gazebo with plugin providing the robot spawning service world_sdf = tempfile.mktemp(prefix='nav2_', suffix='.sdf') world_sdf_xacro = ExecuteProcess( + condition=IfCondition(start_sim), cmd=['xacro', '-o', world_sdf, ['headless:=', 'False'], world]) start_gazebo_cmd = ExecuteProcess( + condition=IfCondition(start_sim), cmd=['gz', 'sim', '-r', '-s', world_sdf], output='screen', ) - remove_temp_sdf_file = RegisterEventHandler(event_handler=OnShutdown( - on_shutdown=[ - OpaqueFunction(function=lambda _: os.remove(world_sdf)) - ])) + remove_temp_sdf_file = RegisterEventHandler( + condition=IfCondition(start_sim), + event_handler=OnShutdown( + on_shutdown=[ + OpaqueFunction(function=lambda _: os.path.exists(world_sdf) and os.remove(world_sdf)) + ])) robots_list = ParseMultiRobotPose('robots').value() @@ -183,13 +218,12 @@ def make_group(robot_autostart, condition, ready_node=None): 'headless': 'False', 'use_robot_state_pub': use_robot_state_pub, 'use_navigation': use_navigation, - 'clock_topic': TextSubstitution( - text=( - '/clock' - if robot_index == 0 - else f'/{namespace}/clock' - ) - ), + 'clock_topic': PythonExpression([ + "'/clock' if (str('", + has_clock, + "').lower() in ('true', '1') and ", + f"{robot_index} == 0) else '/{namespace}/clock'", + ]), 'x_pose': TextSubstitution(text=str(init_pose['x'])), 'y_pose': TextSubstitution(text=str(init_pose['y'])), 'z_pose': TextSubstitution(text=str(init_pose['z'])), @@ -244,6 +278,11 @@ def make_group(robot_autostart, condition, ready_node=None): ld.add_action(declare_use_robot_state_pub_cmd) ld.add_action(declare_use_navigation_cmd) ld.add_action(declare_staged_startup_cmd) + ld.add_action(declare_use_simulator_cmd) + ld.add_action(declare_start_gazebo_cmd) + ld.add_action(declare_has_clock_cmd) + ld.add_action(declare_gz_partition_cmd) + ld.add_action(SetEnvironmentVariable('GZ_PARTITION', LaunchConfiguration('gz_partition'))) # initial localization node for robot_name in robots_list: