├── eg8 ├── dds_updater │ ├── requirements.txt │ ├── Dockerfile │ ├── cyclonedds.xml │ └── dds_xml_updater.py ├── dev2 │ ├── .env │ ├── cyclonedds.xml │ └── docker-compose.yml └── dev1 │ ├── .env │ ├── turtle_controller │ └── Dockerfile │ ├── cyclonedds.xml │ └── docker-compose.yml ├── ros2_ws └── src │ ├── my_demo_pkg │ ├── my_demo_pkg │ │ ├── __init__.py │ │ ├── move_controller.py │ │ └── color_controller.py │ ├── resource │ │ └── my_demo_pkg │ ├── setup.cfg │ ├── package.xml │ ├── setup.py │ └── test │ │ ├── test_copyright.py │ │ ├── test_pep257.py │ │ └── test_flake8.py │ └── my_turtle_bringup │ ├── CMakeLists.txt │ ├── launch │ ├── only_controller.launch.py │ └── turtlesim_demo.launch.py │ └── package.xml ├── .gitignore ├── docs ├── logo.png ├── ip-log.png ├── eg3_turtle.png ├── screenshot.png ├── fig5-solution.png ├── fig5a-solution.png ├── fig6b-solution.png ├── rosject-turtle.png ├── project-structure.png ├── eg3_dev1_dashboard.png ├── eg3_dev2_dashboard.png ├── fig2-one-container.png ├── fig3-two-containers.png ├── fig6-sidecar-husarnet.png ├── fig7-sidecar-husarnet.png ├── fig8-sidecar-husarnet.png ├── fig1-system-architecture.png ├── fig4-two-containers-two-networks.png ├── fig1-system-architecture.drawio ├── fig2-one-container.drawio ├── fig3-two-containers.drawio ├── fig4-two-containers-two-networks.drawio ├── fig5a-solution.drawio ├── fig5-solution.drawio ├── fig6-sidecar-husarnet.drawio ├── fig6b-solution.drawio ├── fig7-sidecar-husarnet.drawio └── fig8-sidecar-husarnet.drawio ├── eg3 ├── dev2 │ ├── .env │ ├── Dockerfile │ ├── docker-compose.yml │ ├── cyclonedds.xml │ └── ros_entrypoint.sh └── dev1 │ ├── .env │ ├── docker-compose.yml │ ├── Dockerfile │ ├── cyclonedds.xml │ └── ros_entrypoint.sh ├── eg4 ├── dev2 │ ├── .env │ ├── ros_entrypoint.sh │ ├── Dockerfile │ ├── docker-compose.yml │ └── cyclonedds.xml └── dev1 │ ├── .env │ ├── ros_entrypoint.sh │ ├── Dockerfile │ ├── docker-compose.yml │ └── cyclonedds.xml ├── eg5 ├── dev2 │ ├── .env │ ├── cyclonedds.xml │ └── docker-compose.yml └── dev1 │ ├── .env │ ├── ros_entrypoint.sh │ ├── Dockerfile │ ├── cyclonedds.xml │ └── docker-compose.yml ├── eg6 ├── dev2 │ ├── .env │ ├── cyclonedds.xml │ └── docker-compose.yml └── dev1 │ ├── .env │ ├── ros_entrypoint.sh │ ├── Dockerfile │ ├── cyclonedds.xml │ └── docker-compose.yml ├── eg1 ├── ros_entrypoint.sh └── Dockerfile ├── eg2 ├── ros_entrypoint.sh ├── Dockerfile └── docker-compose.yml ├── .vscode ├── settings.json └── c_cpp_properties.json ├── LICENSE └── README.md /eg8/dds_updater/requirements.txt: -------------------------------------------------------------------------------- 1 | xmltodict 2 | -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/my_demo_pkg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/resource/my_demo_pkg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ros2_ws/build 2 | ros2_ws/install 3 | ros2_ws/log 4 | # **/.env -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/logo.png -------------------------------------------------------------------------------- /docs/ip-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/ip-log.png -------------------------------------------------------------------------------- /docs/eg3_turtle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/eg3_turtle.png -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/screenshot.png -------------------------------------------------------------------------------- /eg3/dev2/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/z4TkSuT7zLAasU4tH7aXPc 2 | HOSTNAME=turtlesim -------------------------------------------------------------------------------- /eg4/dev2/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/z4TkSuT7zLAasU4tH7aXPc 2 | HOSTNAME=turtlesim -------------------------------------------------------------------------------- /eg5/dev2/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/z4TkSuT7zLAasU4tH7aXPc 2 | HOSTNAME=turtlesim -------------------------------------------------------------------------------- /eg6/dev2/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/z4TkSuT7zLAasU4tH7aXPc 2 | HOSTNAME=turtlesim -------------------------------------------------------------------------------- /docs/fig5-solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig5-solution.png -------------------------------------------------------------------------------- /docs/fig5a-solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig5a-solution.png -------------------------------------------------------------------------------- /docs/fig6b-solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig6b-solution.png -------------------------------------------------------------------------------- /docs/rosject-turtle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/rosject-turtle.png -------------------------------------------------------------------------------- /eg8/dev2/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/p97XvRtXLdjccFv2p8j3S9 2 | HOSTNAME=turtlesim-8 -------------------------------------------------------------------------------- /docs/project-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/project-structure.png -------------------------------------------------------------------------------- /eg3/dev1/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/z4TkSuT7zLAasU4tH7aXPc 2 | HOSTNAME=turtle-controller -------------------------------------------------------------------------------- /eg4/dev1/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/z4TkSuT7zLAasU4tH7aXPc 2 | HOSTNAME=turtle-controller -------------------------------------------------------------------------------- /eg5/dev1/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/z4TkSuT7zLAasU4tH7aXPc 2 | HOSTNAME=turtle-controller -------------------------------------------------------------------------------- /eg6/dev1/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/z4TkSuT7zLAasU4tH7aXPc 2 | HOSTNAME=turtle-controller -------------------------------------------------------------------------------- /eg8/dev1/.env: -------------------------------------------------------------------------------- 1 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/p97XvRtXLdjccFv2p8j3S9 2 | HOSTNAME=turtle-controller-8 -------------------------------------------------------------------------------- /docs/eg3_dev1_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/eg3_dev1_dashboard.png -------------------------------------------------------------------------------- /docs/eg3_dev2_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/eg3_dev2_dashboard.png -------------------------------------------------------------------------------- /docs/fig2-one-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig2-one-container.png -------------------------------------------------------------------------------- /docs/fig3-two-containers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig3-two-containers.png -------------------------------------------------------------------------------- /docs/fig6-sidecar-husarnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig6-sidecar-husarnet.png -------------------------------------------------------------------------------- /docs/fig7-sidecar-husarnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig7-sidecar-husarnet.png -------------------------------------------------------------------------------- /docs/fig8-sidecar-husarnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig8-sidecar-husarnet.png -------------------------------------------------------------------------------- /docs/fig1-system-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig1-system-architecture.png -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/setup.cfg: -------------------------------------------------------------------------------- 1 | [develop] 2 | script-dir=$base/lib/my_demo_pkg 3 | [install] 4 | install-scripts=$base/lib/my_demo_pkg 5 | -------------------------------------------------------------------------------- /docs/fig4-two-containers-two-networks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominikN/ros2_docker_examples/HEAD/docs/fig4-two-containers-two-networks.png -------------------------------------------------------------------------------- /eg1/ros_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source /opt/ros/foxy/setup.bash 5 | source /app/ros2_ws/install/setup.bash 6 | 7 | exec "$@" -------------------------------------------------------------------------------- /eg2/ros_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source /opt/ros/foxy/setup.bash 5 | source /app/ros2_ws/install/setup.bash 6 | 7 | exec "$@" -------------------------------------------------------------------------------- /eg5/dev1/ros_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source /opt/ros/galactic/setup.bash 5 | source /app/ros2_ws/install/setup.bash 6 | 7 | exec "$@" -------------------------------------------------------------------------------- /eg6/dev1/ros_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source /opt/ros/galactic/setup.bash 5 | source /app/ros2_ws/install/setup.bash 6 | 7 | exec "$@" -------------------------------------------------------------------------------- /eg4/dev2/ros_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source /opt/ros/foxy/setup.bash 5 | export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp 6 | export CYCLONEDDS_URI=file:///app/cyclonedds.xml 7 | 8 | exec "$@" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.languageServer": "Pylance", 3 | "python.analysis.autoSearchPaths": true, 4 | "python.analysis.extraPaths": [], 5 | "python.analysis.autoImportCompletions": true 6 | } 7 | 8 | -------------------------------------------------------------------------------- /eg4/dev1/ros_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source /opt/ros/foxy/setup.bash 5 | source /app/ros2_ws/install/setup.bash 6 | export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp 7 | export CYCLONEDDS_URI=file:///app/cyclonedds.xml 8 | 9 | exec "$@" -------------------------------------------------------------------------------- /eg8/dev1/turtle_controller/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osrf/ros:galactic-desktop 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | 5 | # build custom ROS 2 nodes 6 | COPY ros2_ws ros2_ws/ 7 | RUN cd ros2_ws && \ 8 | source /opt/ros/galactic/setup.bash && \ 9 | colcon build -------------------------------------------------------------------------------- /eg1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osrf/ros:foxy-desktop 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | 5 | WORKDIR /app 6 | 7 | COPY ros2_ws ros2_ws/ 8 | RUN cd ros2_ws && \ 9 | source /opt/ros/foxy/setup.bash && \ 10 | colcon build 11 | 12 | COPY eg1/ros_entrypoint.sh / 13 | 14 | ENTRYPOINT ["/ros_entrypoint.sh"] 15 | CMD ["bash"] -------------------------------------------------------------------------------- /eg2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osrf/ros:foxy-desktop 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | 5 | WORKDIR /app 6 | 7 | COPY ros2_ws ros2_ws/ 8 | RUN cd ros2_ws && \ 9 | source /opt/ros/foxy/setup.bash && \ 10 | colcon build 11 | 12 | COPY eg2/ros_entrypoint.sh / 13 | 14 | ENTRYPOINT ["/ros_entrypoint.sh"] 15 | CMD ["bash"] -------------------------------------------------------------------------------- /eg8/dds_updater/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | RUN mkdir /dds 4 | COPY ./dds_updater/cyclonedds.xml / 5 | WORKDIR /usr/src/app 6 | 7 | COPY ./dds_updater/requirements.txt ./ 8 | RUN pip install --no-cache-dir -r requirements.txt 9 | 10 | COPY ./dds_updater//dds_xml_updater.py . 11 | 12 | CMD [ "python", "./dds_xml_updater.py" ] -------------------------------------------------------------------------------- /eg5/dev1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osrf/ros:galactic-desktop 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | 5 | WORKDIR /app 6 | 7 | # build custom ROS 2 nodes 8 | COPY ros2_ws ros2_ws/ 9 | RUN cd ros2_ws && \ 10 | source /opt/ros/galactic/setup.bash && \ 11 | colcon build 12 | 13 | COPY eg5/dev1/ros_entrypoint.sh / 14 | 15 | ENTRYPOINT ["/ros_entrypoint.sh"] 16 | CMD ["bash"] -------------------------------------------------------------------------------- /eg6/dev1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osrf/ros:galactic-desktop 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | 5 | WORKDIR /app 6 | 7 | # build custom ROS 2 nodes 8 | COPY ros2_ws ros2_ws/ 9 | RUN cd ros2_ws && \ 10 | source /opt/ros/galactic/setup.bash && \ 11 | colcon build 12 | 13 | COPY eg6/dev1/ros_entrypoint.sh / 14 | 15 | ENTRYPOINT ["/ros_entrypoint.sh"] 16 | CMD ["bash"] -------------------------------------------------------------------------------- /eg4/dev2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osrf/ros:foxy-desktop 2 | 3 | # Install ROS 2 deppendencies (Cyclone DDS) 4 | RUN sudo apt update && \ 5 | sudo apt install -y \ 6 | ros-foxy-rmw-cyclonedds-cpp 7 | RUN sudo rm -rf /var/lib/apt/lists/* 8 | 9 | WORKDIR /app 10 | 11 | COPY cyclonedds.xml . 12 | COPY ros_entrypoint.sh / 13 | 14 | ENTRYPOINT ["/ros_entrypoint.sh"] 15 | CMD ["bash"] -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/opt/ros/foxy/include" 8 | ], 9 | "defines": [], 10 | "compilerPath": "/usr/bin/gcc", 11 | "cStandard": "gnu17", 12 | "cppStandard": "gnu++14", 13 | "intelliSenseMode": "linux-gcc-x64" 14 | } 15 | ], 16 | "version": 4 17 | } 18 | -------------------------------------------------------------------------------- /ros2_ws/src/my_turtle_bringup/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(my_turtle_bringup) 3 | 4 | # Default to C++14 5 | if(NOT CMAKE_CXX_STANDARD) 6 | set(CMAKE_CXX_STANDARD 14) 7 | endif() 8 | 9 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 10 | add_compile_options(-Wall -Wextra -Wpedantic) 11 | endif() 12 | 13 | # find dependencies 14 | find_package(ament_cmake REQUIRED) 15 | 16 | install(DIRECTORY 17 | launch 18 | DESTINATION share/${PROJECT_NAME} 19 | ) 20 | 21 | ament_package() 22 | -------------------------------------------------------------------------------- /eg2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | turtle_controller: 5 | build: 6 | context: ../ 7 | dockerfile: eg2/Dockerfile 8 | command: ros2 launch my_turtle_bringup only_controller.launch.py 9 | turtle_sim: 10 | image: osrf/ros:foxy-desktop 11 | environment: 12 | - DISPLAY 13 | volumes: 14 | - /tmp/.X11-unix:/tmp/.X11-unix:rw 15 | command: ros2 run turtlesim turtlesim_node 16 | depends_on: 17 | - turtle_controller -------------------------------------------------------------------------------- /eg4/dev1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osrf/ros:foxy-desktop 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | 5 | # Install ROS 2 deppendencies (Cyclone DDS) 6 | RUN sudo apt update && \ 7 | sudo apt install -y \ 8 | ros-foxy-rmw-cyclonedds-cpp 9 | RUN sudo rm -rf /var/lib/apt/lists/* 10 | 11 | WORKDIR /app 12 | 13 | # build custom ROS 2 nodes 14 | COPY ros2_ws ros2_ws/ 15 | RUN cd ros2_ws && \ 16 | source /opt/ros/foxy/setup.bash && \ 17 | colcon build 18 | 19 | COPY eg4/dev1/cyclonedds.xml . 20 | COPY eg4/dev1/ros_entrypoint.sh / 21 | 22 | ENTRYPOINT ["/ros_entrypoint.sh"] 23 | CMD ["bash"] -------------------------------------------------------------------------------- /ros2_ws/src/my_turtle_bringup/launch/only_controller.launch.py: -------------------------------------------------------------------------------- 1 | from launch import LaunchDescription 2 | from launch_ros.actions import Node 3 | 4 | def generate_launch_description(): 5 | ld = LaunchDescription() 6 | 7 | move_controller_node = Node( 8 | package="my_demo_pkg", 9 | executable="move_controller" 10 | ) 11 | 12 | color_controller_node = Node( 13 | package="my_demo_pkg", 14 | executable="color_controller" 15 | ) 16 | 17 | ld.add_action(move_controller_node) 18 | ld.add_action(color_controller_node) 19 | return ld 20 | -------------------------------------------------------------------------------- /ros2_ws/src/my_turtle_bringup/launch/turtlesim_demo.launch.py: -------------------------------------------------------------------------------- 1 | from launch import LaunchDescription 2 | from launch_ros.actions import Node 3 | 4 | def generate_launch_description(): 5 | ld = LaunchDescription() 6 | 7 | turtlesim_node = Node( 8 | package="turtlesim", 9 | executable="turtlesim_node" 10 | ) 11 | 12 | move_controller_node = Node( 13 | package="my_demo_pkg", 14 | executable="move_controller" 15 | ) 16 | 17 | color_controller_node = Node( 18 | package="my_demo_pkg", 19 | executable="color_controller" 20 | ) 21 | 22 | ld.add_action(turtlesim_node) 23 | ld.add_action(move_controller_node) 24 | ld.add_action(color_controller_node) 25 | return ld 26 | -------------------------------------------------------------------------------- /eg3/dev2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osrf/ros:foxy-desktop 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | 5 | # Install Husarnet Client 6 | RUN apt update -y 7 | RUN apt install -y curl gnupg2 systemd 8 | RUN curl https://install.husarnet.com/install.sh | bash 9 | RUN update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy 10 | 11 | # Install ROS 2 deppendencies (Cyclone DDS) 12 | RUN sudo apt update && \ 13 | sudo apt install -y \ 14 | ros-foxy-rmw-cyclonedds-cpp 15 | RUN sudo rm -rf /var/lib/apt/lists/* 16 | 17 | # Find your JOINCODE at https://app.husarnet.com 18 | ENV JOINCODE="" 19 | ENV HOSTNAME=my-container-1 20 | 21 | WORKDIR /app 22 | 23 | COPY eg3/dev2/ros_entrypoint.sh / 24 | COPY eg3/dev2/cyclonedds.xml . 25 | 26 | ENTRYPOINT ["/ros_entrypoint.sh"] 27 | CMD ["bash"] -------------------------------------------------------------------------------- /ros2_ws/src/my_turtle_bringup/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | my_turtle_bringup 5 | 0.0.0 6 | TODO: Package description 7 | dominik 8 | TODO: License declaration 9 | 10 | ament_cmake 11 | 12 | my_demo_pkg 13 | 14 | ament_lint_auto 15 | ament_lint_common 16 | 17 | 18 | ament_cmake 19 | 20 | 21 | -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | my_demo_pkg 5 | 0.0.0 6 | TODO: Package description 7 | dominik 8 | TODO: License declaration 9 | 10 | rclpy 11 | geometry_msgs 12 | turtlesim 13 | 14 | ament_copyright 15 | ament_flake8 16 | ament_pep257 17 | python3-pytest 18 | 19 | 20 | ament_python 21 | 22 | 23 | -------------------------------------------------------------------------------- /eg3/dev1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.3" # https://docs.docker.com/compose/compose-file/ 2 | 3 | services: 4 | turtle_controller: 5 | build: 6 | context: ../../ 7 | dockerfile: eg3/dev1/Dockerfile 8 | volumes: 9 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between boots 10 | env_file: 11 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there 12 | sysctls: 13 | - net.ipv6.conf.all.disable_ipv6=0 14 | cap_add: 15 | - NET_ADMIN 16 | devices: 17 | - /dev/net/tun 18 | stdin_open: true # docker run -i 19 | tty: true # docker run -t 20 | command: ros2 launch my_turtle_bringup only_controller.launch.py 21 | -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | package_name = 'my_demo_pkg' 4 | 5 | setup( 6 | name=package_name, 7 | version='0.0.0', 8 | packages=[package_name], 9 | data_files=[ 10 | ('share/ament_index/resource_index/packages', 11 | ['resource/' + package_name]), 12 | ('share/' + package_name, ['package.xml']), 13 | ], 14 | install_requires=['setuptools'], 15 | zip_safe=True, 16 | maintainer='dominik', 17 | maintainer_email='dominik.nwk@gmail.com', 18 | description='TODO: Package description', 19 | license='TODO: License declaration', 20 | tests_require=['pytest'], 21 | entry_points={ 22 | 'console_scripts': [ 23 | "move_controller = my_demo_pkg.move_controller:main", 24 | "color_controller = my_demo_pkg.color_controller:main", 25 | ], 26 | }, 27 | ) 28 | -------------------------------------------------------------------------------- /eg3/dev1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osrf/ros:foxy-desktop 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | 5 | # Install Husarnet Client 6 | RUN apt update -y 7 | RUN apt install -y curl gnupg2 systemd 8 | RUN curl https://install.husarnet.com/install.sh | bash 9 | RUN update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy 10 | 11 | # Install ROS 2 deppendencies (Cyclone DDS) 12 | RUN sudo apt update && \ 13 | sudo apt install -y \ 14 | ros-foxy-rmw-cyclonedds-cpp 15 | RUN sudo rm -rf /var/lib/apt/lists/* 16 | 17 | # Find your JOINCODE at https://app.husarnet.com 18 | ENV JOINCODE="" 19 | ENV HOSTNAME=my-container-1 20 | 21 | 22 | WORKDIR /app 23 | 24 | COPY ros2_ws ros2_ws/ 25 | RUN cd ros2_ws && \ 26 | source /opt/ros/foxy/setup.bash && \ 27 | colcon build 28 | 29 | COPY eg3/dev1/ros_entrypoint.sh / 30 | COPY eg3/dev1/cyclonedds.xml . 31 | 32 | ENTRYPOINT ["/ros_entrypoint.sh"] 33 | CMD ["bash"] -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/test/test_copyright.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_copyright.main import main 16 | import pytest 17 | 18 | 19 | @pytest.mark.copyright 20 | @pytest.mark.linter 21 | def test_copyright(): 22 | rc = main(argv=['.', 'test']) 23 | assert rc == 0, 'Found errors' 24 | -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/test/test_pep257.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_pep257.main import main 16 | import pytest 17 | 18 | 19 | @pytest.mark.linter 20 | @pytest.mark.pep257 21 | def test_pep257(): 22 | rc = main(argv=['.', 'test']) 23 | assert rc == 0, 'Found code style errors / warnings' 24 | -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/my_demo_pkg/move_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import rclpy 4 | from rclpy.node import Node 5 | from geometry_msgs.msg import Twist 6 | import math 7 | 8 | class MoveControllerNode(Node): 9 | def __init__(self): 10 | super().__init__("move_controller") 11 | 12 | self.cmd_vel_publisher_ = self.create_publisher( 13 | Twist, "turtle1/cmd_vel", 10) 14 | 15 | self.control_loop_timer_ = self.create_timer(0.01, self.control_loop) 16 | 17 | def control_loop(self): 18 | msg = Twist() 19 | msg.linear.x = 2.0 20 | msg.angular.z =0.3*math.pi 21 | 22 | # self.get_logger().info("publishing to turtle1/cmd_vel") 23 | self.cmd_vel_publisher_.publish(msg) 24 | 25 | def main(args=None): 26 | rclpy.init(args=args) 27 | node = MoveControllerNode() 28 | rclpy.spin(node) 29 | rclpy.shutdonw() 30 | 31 | if __name__ == "__main__": 32 | main() -------------------------------------------------------------------------------- /eg3/dev2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.3" # https://docs.docker.com/compose/compose-file/ 2 | 3 | services: 4 | turtle_sim: 5 | build: 6 | context: ../../ 7 | dockerfile: eg3/dev2/Dockerfile 8 | volumes: 9 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between boots 10 | - /tmp/.X11-unix:/tmp/.X11-unix:rw 11 | environment: 12 | - DISPLAY 13 | env_file: 14 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there 15 | sysctls: 16 | - net.ipv6.conf.all.disable_ipv6=0 17 | cap_add: 18 | - NET_ADMIN 19 | devices: 20 | - /dev/net/tun 21 | stdin_open: true # docker run -i 22 | tty: true # docker run -t 23 | command: ros2 run turtlesim turtlesim_node 24 | -------------------------------------------------------------------------------- /eg8/dev1/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg8/dev2/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg8/dds_updater/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/test/test_flake8.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_flake8.main import main_with_errors 16 | import pytest 17 | 18 | 19 | @pytest.mark.flake8 20 | @pytest.mark.linter 21 | def test_flake8(): 22 | rc, errors = main_with_errors(argv=[]) 23 | assert rc == 0, \ 24 | 'Found %d code style errors / warnings:\n' % len(errors) + \ 25 | '\n'.join(errors) 26 | -------------------------------------------------------------------------------- /eg4/dev1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | turtle_controller: 5 | build: 6 | context: ../../ 7 | dockerfile: eg4/dev1/Dockerfile 8 | command: ros2 launch my_turtle_bringup only_controller.launch.py 9 | network_mode: service:husarnet # This is the most important line in this setup. This will put the Husarnet Client in the same network namespace as your app (in this example: turtle_controller) 10 | 11 | husarnet: 12 | image: husarnet/husarnet 13 | restart: unless-stopped 14 | volumes: 15 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between (re)boots 16 | sysctls: 17 | - net.ipv6.conf.all.disable_ipv6=0 # Husarnet is using IPv6 for the internal connections 18 | cap_add: 19 | - NET_ADMIN 20 | devices: 21 | - /dev/net/tun 22 | env_file: 23 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there -------------------------------------------------------------------------------- /eg4/dev2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | turtle_sim: 5 | build: ./ 6 | environment: 7 | - DISPLAY 8 | volumes: 9 | - /tmp/.X11-unix:/tmp/.X11-unix:rw 10 | command: ros2 run turtlesim turtlesim_node 11 | network_mode: service:husarnet # This is the most important line in this setup. This will put the Husarnet Client in the same network namespace as your app (in this example: turtle_sim) 12 | 13 | husarnet: 14 | image: husarnet/husarnet 15 | restart: unless-stopped 16 | volumes: 17 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between (re)boots 18 | sysctls: 19 | - net.ipv6.conf.all.disable_ipv6=0 # Husarnet is using IPv6 for the internal connections 20 | cap_add: 21 | - NET_ADMIN 22 | devices: 23 | - /dev/net/tun 24 | env_file: 25 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DominikN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /eg4/dev1/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg5/dev1/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg6/dev1/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg8/dds_updater/dds_xml_updater.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import xmltodict 4 | import json 5 | 6 | xml_file = os.path.join('/', 'cyclonedds.xml') 7 | 8 | while True: 9 | with open(xml_file, 'r') as fd: 10 | raw_xml = fd.read() 11 | fd.close() 12 | 13 | doc = xmltodict.parse(raw_xml) 14 | 15 | myjson = json.loads(json.dumps(doc, indent=2)) 16 | 17 | myjson['CycloneDDS']['Domain']['Discovery']['Peers'] = {'Peer':[]} 18 | 19 | with open('/etc/hosts','r') as hosts_file: 20 | for line in hosts_file: 21 | if(line.split()[2:6] == ['#', 'managed', 'by', 'Husarnet']): 22 | myjson['CycloneDDS']['Domain']['Discovery']['Peers']['Peer'].append({'@address': '[' + line.split()[0] + ']'}) 23 | print('Husarnet IPv6: ' + str(line.split()[0]) + ' | Hostname: ' + str(line.split()[1])) 24 | hosts_file.close() 25 | 26 | print(json.dumps(myjson, indent=2)) 27 | 28 | new_raw_xml = xmltodict.unparse(myjson, pretty=True) 29 | 30 | with open(xml_file, 'w') as fd: 31 | fd.write(new_raw_xml) 32 | fd.close() 33 | 34 | time.sleep(5) -------------------------------------------------------------------------------- /eg4/dev2/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg5/dev2/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg6/dev2/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg3/dev1/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg3/dev2/cyclonedds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hnet0 6 | false 7 | 65500B 8 | 4000B 9 | udp6 10 | 11 | 12 | 13 | 14 | 15 | auto 16 | 17 | 18 | 19 | 500kB 20 | 21 | 22 | 23 | severe 24 | stdout 25 | 26 | 27 | -------------------------------------------------------------------------------- /eg5/dev1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | turtle_controller: 5 | build: 6 | context: ../../ 7 | dockerfile: eg5/dev1/Dockerfile 8 | volumes: 9 | - ./cyclonedds.xml:/cyclonedds.xml 10 | command: 11 | - bash 12 | - -c 13 | - | 14 | export CYCLONEDDS_URI=file:///cyclonedds.xml 15 | ros2 launch my_turtle_bringup only_controller.launch.py 16 | network_mode: service:husarnet # This will put the Husarnet Client in the same network namespace as your app (in this example: turtle_controller) 17 | 18 | husarnet: 19 | image: husarnet/husarnet 20 | restart: unless-stopped 21 | volumes: 22 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between (re)boots 23 | sysctls: 24 | - net.ipv6.conf.all.disable_ipv6=0 # Husarnet is using IPv6 for the internal connections 25 | cap_add: 26 | - NET_ADMIN 27 | devices: 28 | - /dev/net/tun 29 | env_file: 30 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there -------------------------------------------------------------------------------- /eg5/dev2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | turtle_sim: 5 | image: osrf/ros:galactic-desktop 6 | environment: 7 | - DISPLAY 8 | volumes: 9 | - /tmp/.X11-unix:/tmp/.X11-unix:rw 10 | - ./cyclonedds.xml:/cyclonedds.xml 11 | command: 12 | - bash 13 | - -c 14 | - | 15 | export CYCLONEDDS_URI=file:///cyclonedds.xml 16 | ros2 run turtlesim turtlesim_node 17 | network_mode: service:husarnet # This will put the Husarnet Client in the same network namespace as your app (in this example: turtle_sim) 18 | 19 | husarnet: 20 | image: husarnet/husarnet 21 | restart: unless-stopped 22 | volumes: 23 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between (re)boots 24 | sysctls: 25 | - net.ipv6.conf.all.disable_ipv6=0 # Husarnet is using IPv6 for the internal connections 26 | cap_add: 27 | - NET_ADMIN 28 | devices: 29 | - /dev/net/tun 30 | env_file: 31 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there -------------------------------------------------------------------------------- /eg6/dev2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | turtle_sim: 5 | image: osrf/ros:galactic-desktop 6 | environment: 7 | - DISPLAY 8 | volumes: 9 | - /tmp/.X11-unix:/tmp/.X11-unix:rw 10 | - ./cyclonedds.xml:/cyclonedds.xml 11 | command: 12 | - bash 13 | - -c 14 | - | 15 | export CYCLONEDDS_URI=file:///cyclonedds.xml 16 | ros2 run turtlesim turtlesim_node 17 | network_mode: service:husarnet # This is the most important line in this setup. This will put the Husarnet Client in the same network namespace as your app (in this example: turtle_sim) 18 | 19 | husarnet: 20 | image: husarnet/husarnet 21 | restart: unless-stopped 22 | volumes: 23 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between (re)boots 24 | sysctls: 25 | - net.ipv6.conf.all.disable_ipv6=0 # Husarnet is using IPv6 for the internal connections 26 | cap_add: 27 | - NET_ADMIN 28 | devices: 29 | - /dev/net/tun 30 | env_file: 31 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there -------------------------------------------------------------------------------- /docs/fig1-system-architecture.drawio: -------------------------------------------------------------------------------- 1 | 5VnbctowEP0aHpPxHfMYSGg7006S0jbJU0bYi61GtlxZ5tKvr4zlGzYpSTCQdgYG7UWytGfPShY9fRQsPzAU+V+oC6SnKe6yp1/2NE01NK2XfhR3JTVKX880HsOu1JWKCf4NuaPUJtiFuObIKSUcR3WlQ8MQHF7TIcboou42o6T+1Ah50FBMHESa2jvscl9qVWtQGj4C9nz5aFvrZ4YA5c5yJbGPXLqoqPSrnj5ilPKsFSxHQNLo5XHJ+o23WIuJMQj5Lh3s2xt3YQTx7SQeTT/Zk7sf13CmWtkwc0QSuWI5W77KQ8BoErqQjqL29OHCxxwmEXJS60KgLnQ+D4g0y+GAcVhunahaLF8kDtAAOFsJl7xDHjGZM7Yt5UUJQN+SOr8Se0ORSiRB94qxy7iIhgzNS8LUjAq4Ik+kSBn3qUdDRK5K7bCMmyKk0uczpZGM1k/gfCWTHiWc1mMJS8zvK+0HOVTavlxWhVUuhGK191Vh3edcM3O57LeW8o4xZ/SpyHBtbXcvUv4IcUqo8/TNx2GmHmNSRzsNxfNYi8jRhDnwXIgluRHzgD/jZ7XnDgOCOJ7X57H/PGhhi0XEdIczKhZcTRDrV0Jzw1m8hvhCOGhatCyNouVlv2OHEsoeRRkTSBACLB9YmLKxc8/NPCRE1EL4OzNnArZR+pB1P91FYM+cAvqKxXJsmM72xGWrzuWB1eSyprRw2e6MykozhEei8rn5KjKfPpe1HblsHpPLWndcDugc/nkqq8U+fTwuG8fkstJ78bas1pncP30qGwfaltddxcrQquIQURzyuDLyTaooU9LKT+HF24WqbCRVNmSZYsXcXp91RneFgyeME4hx0HXJmNkOOK0lY2qbhqnsp2RYxkbJUIyjlwy1GcNT3/7LHf+hWkNOtmaYO9YM45jbv9k1i9X0UB+4j/P0SmQ3Nh/6Tbt4YS629F35aXTGT/10+Km9jqDKyRPUeg8EVc2TOdzJg9oLz3enX6h3zYMtVeQwedB2Q7n3Qh0Df4wgfC+FetByJXrYOt1/Myqq3YYKpxF2doVBBJTXY10/zoY0hI2zr1Qhgj1BrUtHACBe0PVhCg92ELmQhgC7Ltl2rq5XmX3ga25uxGoT4EJXBVjrCmC7I4BjYHMsgvnfQaybdQqbLQgbh0RYbUL8fZqEPElLpnKuNC9X3g4GgRl/GxRptsjdW9sTNH11o7oqR4dm0IDm6/UkxUV8x3S5+m+h0buDRojlv7HZZVH5p7Z+9Qc= -------------------------------------------------------------------------------- /eg8/dev2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | dds_updater: 5 | build: 6 | context: ../ 7 | dockerfile: ./dds_updater/Dockerfile 8 | volumes: 9 | - ./cyclonedds.xml:/cyclonedds.xml 10 | stdin_open: true # docker run -i 11 | tty: true # docker run -t 12 | network_mode: service:husarnet 13 | 14 | turtle_sim: 15 | image: osrf/ros:galactic-desktop 16 | environment: 17 | - DISPLAY 18 | volumes: 19 | - /tmp/.X11-unix:/tmp/.X11-unix:rw 20 | - ./cyclonedds.xml:/cyclonedds.xml 21 | command: 22 | - bash 23 | - -c 24 | - | 25 | export CYCLONEDDS_URI=file:///cyclonedds.xml 26 | ros2 run turtlesim turtlesim_node 27 | network_mode: service:husarnet 28 | 29 | husarnet: 30 | image: husarnet/husarnet 31 | restart: unless-stopped 32 | volumes: 33 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between (re)boots 34 | sysctls: 35 | - net.ipv6.conf.all.disable_ipv6=0 # Husarnet is using IPv6 for the internal connections 36 | cap_add: 37 | - NET_ADMIN 38 | devices: 39 | - /dev/net/tun 40 | env_file: 41 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there 42 | -------------------------------------------------------------------------------- /docs/fig2-one-container.drawio: -------------------------------------------------------------------------------- 1 | 5Vrbcts2EP0aPcYDgBdRj7ZsN51JJkmVNsmThyIhEQ1IsCCoS76+gAiIV7u0I0pMLXtGwO4CIvbs2QVBTqx5vPuN+2n0noWYThAIdxPrdoIQtBGaqH8Q7rUETK1CsuYk1LJSsCA/sDHU0pyEOKsZCsaoIGldGLAkwYGoyXzO2bZutmK0/qupv8YtwSLwaVv6hYQi0lLozkrFW0zWkf5pD00LRewbY72SLPJDtq2IrLuJNeeMiaIV7+aYKu8ZvxTj7h/RHi+M40T0GeB9+hhu7Tj7tMjmy9+9xZe/PuA30C2m2fg01yvWVyv2xgWc5UmI1SxwYt1sIyLwIvUDpd1K1KUsEjHVaj0d5gLvHr1QeFy+DBzMYiz4XpqYAQZ7HTOep/vbEoDpVMuiiu9tY+hr0NfHuUu/yIZ2zTPcZBbRz03gv920YonQwY6A7s8ZZfwwl1V8pNynZJ1IGcUrocwIpRWzlaP+pDwTnH3HFY17+JwID7uOBwSgAxDYAYiFhgIEtv2PQ0lc3WVcRGzNEp/eldKbOkKlzTvGUo3L31iIvQbGzwWro4Z3RHyttL/pqVT7dlft7E0nkav9Wu0cxlwhx/TLcYeeGVggalIOOujDa5XQZHdJWfD9c0SSQnxPaJ1+yhVPgy09x3Ie4KdcrLOtz9dYPGHndgcPx9QXZFO/jtPHQQcvXSo0o2oB4v6TM6N4kx0gvpYGCKW7Uilb6+L7PlBsepB1RSJBKeZmYqkq5jaWzTikVBYn3CMH1Mkc+thbBZ1kDjy8XJ2IzG6TzHabzAh0kHmw5Goyxwi4fOW8iM3jJzPqSWbnkmRGw5E5Zhv8/+cy6ijMZ+ayfUkug8mz6zKsM3k6firbZ6rLh6FyZf6+YpAykoisMvNHJShD0jX3RcfyMgONoCqmLEPseG0vjzp7uMQhci4ozkg8dMpYeQEOOlPG0nNsB5wmZbjNvTxEF08ZsO3DsZf/suJ/q+aQ0eYMp2fOsC9Z/p2hWQzVrj4OHzbqkKofm8999mGDZknvy097MH5a4+EnehlBwegJ6v4KBIXOaDZ3eqP2zP3d+BN13zh4JIucJw66zoxPnqgzLB5SnPwyiRq4l07U05+GBXpdsAiWkqAvDtKjou7s+n42YQlubH61yBx1BxIBeYdu3Sh8SODTa62ISRjSxzbW9TRzCoCd5k551gYYdp16D3bo7Q0EcIb5hkhnvjqILafBYasDYvucEMM2xm9ZprD9sBgACP1Y6SdgaD7IOgEs00ZqncFLo4KsFip/LvNE5KqUgSvQPvR6JdBAcHls2oc/fyiuACW8Z7v968Wm48nOmbFp39Lfyk20On8Hc+kBnySqPcr60vWE3tzpwBPA1XyqPut4ywF2veXwArRkt3zPpDh0LV/Xse7+BQ== -------------------------------------------------------------------------------- /eg6/dev1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | color_controller: 5 | build: 6 | context: ../../ 7 | dockerfile: eg6/dev1/Dockerfile 8 | volumes: 9 | - ./cyclonedds.xml:/cyclonedds.xml 10 | command: 11 | - bash 12 | - -c 13 | - | 14 | export CYCLONEDDS_URI=file:///cyclonedds.xml 15 | ros2 run my_demo_pkg color_controller 16 | network_mode: service:husarnet 17 | 18 | move_controller: 19 | build: 20 | context: ../../ 21 | dockerfile: eg6/dev1/Dockerfile 22 | volumes: 23 | - ./cyclonedds.xml:/cyclonedds.xml 24 | command: 25 | - bash 26 | - -c 27 | - | 28 | export CYCLONEDDS_URI=file:///cyclonedds.xml 29 | ros2 run my_demo_pkg move_controller 30 | network_mode: service:husarnet 31 | 32 | husarnet: 33 | image: husarnet/husarnet 34 | restart: unless-stopped 35 | volumes: 36 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between (re)boots 37 | sysctls: 38 | - net.ipv6.conf.all.disable_ipv6=0 # Husarnet is using IPv6 for the internal connections 39 | cap_add: 40 | - NET_ADMIN 41 | devices: 42 | - /dev/net/tun 43 | env_file: 44 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there -------------------------------------------------------------------------------- /docs/fig3-two-containers.drawio: -------------------------------------------------------------------------------- 1 | 7Zpdc5s4FIZ/jS/rkcSH8WXiJNvO7E7T9e62vcpgkI1agViQv/bXr2Qkg4AkTmpsGtfJjNGRkOE8es+RBANrEm9+y/w0+oOFmA4QCDcD62aAELQRGsh/EG6VBYyswrLISKhspWFK/sO6obIuSYhzoyFnjHKSmsaAJQkOuGHzs4ytzWZzRs1fTf0FbhimgU+b1s8k5JGyQndcVrzHZBGpn/bQqKiIfd1Y3Uke+SFbV0zW7cCaZIzx4ijeTDCV3tN+Kc67e6R2f2EZTvghJ3if7sO1Heefpvlk9sGbfv7nI34H3aKblU+X6o7V1fKtdkHGlkmIZS9wYF2vI8LxNPUDWbsW1IUt4jFV1ao7nHG8efRC4f72xcDBLMY824om+gTtMTVmPE+V1yWA0UjZoorvHT1ofAV9se+79Is4UK55gZvQi9wEnnfTnCVcDXYEVHnCKMt2fVnFR9h9ShaJsFE857IZobTSbO7IP2HPeca+40qNu/sch4eLHIPHuIUHslp4WG5nPOAF84AOeJ4HOikP2HQ/DkUcVUWW8YgtWOLT29J6bQIq2/zOWKqwfMOcbxUXf8mZCQ1vCP9SOf6qupLHN5tqYasLibjbL9XC7pwhcnS5PG9X0icWQHUGQLv68ErmF1GcURZ8/ysiSWG+I9SMhtIVT7MWnmPLLMBPuVglPz9bYP5EO7d97GSY+pyszOs4/jhokaVLuRKUMUDcf5dMV7zLd4ivRAOE0k1ZKY4WxfddIMX0INK8IEEpznTHoqroW7esj0NKxVwBHxACTC2HPvbmQauWAw/P5kfSsmtqGQK3RcygRcxeZ1oGTR+eSctD51Vq7r+Y0YFids4pZtSdmGO2wm9fy/skfD4t2+fUMhi8OC9DU8mj/kvZPlFe3p0q7szfVhqkjCQ8r/R8Lw2VqbtepuohCSGoDaqiy3KI7a/t9aPO7i5w8GXGKc5J3HXImHsBDlpDxsxzbAccaWll10IGtM8eMmDTh31P/2XG/1qNIb2NGc6BMcM+Z/p3ulYxlLP6OHxYyT3Dw9R86q0oG9RT+qH6tDvTp9UffaLXCRT0XqDuzyBQ6PRmcqcmai+c3/U/UB86Dh6JIqcZB21700cP1DnmDylOfppADdo2RU8aqEc/jAV6bVg4S0lwKAfhUW4625zPJizBtcmvMumd7kAQECt061ryIYFPr1RFTMKQPjaxNsPMMQA79UwMm4D16sYAjLoC7HUEOMfZighnXhxiy3zOBIHTgtg+JWLYZPye5ZLtx2kHINRTpR/AUH+OdQQso1poHbcJ76RUkNWg8vdsmfClTGVgCJqbXm8UDRzV0IzPjqa59/OnlAqQxju22V4qGghaNoNPzKa5or8Rc2i5/Q4mwgM+SXbHzQ2gfqSYtmf0erEDj4Gs/ly95VEcHJ+UWHMO2UoMXSixxpsp5yfWnDBcZmqq72z3IDWNf6WmVjQ9SE1W88WRD/dqjTRCQ+gNwbCLINdLPvuIpflYZ18HWeh5Ps196TfKx61P7brkI4rlK8LFA9ryTWvr9n8= -------------------------------------------------------------------------------- /eg3/dev2/ros_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source /opt/ros/foxy/setup.bash 5 | export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp 6 | export CYCLONEDDS_URI=file:///app/cyclonedds.xml 7 | 8 | function get_status() { 9 | local status="success" 10 | 11 | while read line; do 12 | if [[ $line == *"ERROR"* ]]; then 13 | status="waiting..." 14 | fi 15 | done 16 | echo $status 17 | } 18 | 19 | function print_instruction() { 20 | local ipv6addr="::" 21 | 22 | while read line; do 23 | if [[ $line == *"Husarnet IP address:"* ]]; then 24 | ipv6addr=${line#*"Husarnet IP address: "} 25 | fi 26 | done 27 | 28 | echo "*******************************************" 29 | echo "💡 Tip" 30 | echo "IPv6 addr of this container is: ${ipv6addr}" 31 | echo "*******************************************" 32 | echo "" 33 | } 34 | 35 | if [[ ${JOINCODE} == "" ]]; then 36 | echo "" 37 | echo "ERROR: No JOINCODE provided in \"docker run ... \" command. Visit app.husarnet.com to get a JOINCODE" 38 | echo "" 39 | /bin/bash 40 | exit 41 | fi 42 | 43 | echo "" 44 | echo "⏳ [1/2] Initializing Husarnet Client:" 45 | sudo husarnet daemon > /dev/null 2>&1 & 46 | 47 | for i in {1..10} 48 | do 49 | sleep 1 50 | 51 | output=$( get_status < <(sudo husarnet status) ) 52 | echo "$output" 53 | 54 | if [[ $output != "waiting..." ]]; then 55 | break 56 | fi 57 | done 58 | 59 | echo "" 60 | echo "🔥 [2/2] Connecting to Husarnet network as \"${HOSTNAME}\":" 61 | sudo husarnet join ${JOINCODE} ${HOSTNAME} 62 | echo "done" 63 | echo "" 64 | 65 | print_instruction < <(sudo husarnet status) 66 | 67 | exec "$@" -------------------------------------------------------------------------------- /eg3/dev1/ros_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source /opt/ros/foxy/setup.bash 5 | source /app/ros2_ws/install/setup.bash 6 | export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp 7 | export CYCLONEDDS_URI=file:///app/cyclonedds.xml 8 | 9 | function get_status() { 10 | local status="success" 11 | 12 | while read line; do 13 | if [[ $line == *"ERROR"* ]]; then 14 | status="waiting..." 15 | fi 16 | done 17 | echo $status 18 | } 19 | 20 | function print_instruction() { 21 | local ipv6addr="::" 22 | 23 | while read line; do 24 | if [[ $line == *"Husarnet IP address:"* ]]; then 25 | ipv6addr=${line#*"Husarnet IP address: "} 26 | fi 27 | done 28 | 29 | echo "*******************************************" 30 | echo "💡 Tip" 31 | echo "IPv6 addr of this container is: ${ipv6addr}" 32 | echo "*******************************************" 33 | echo "" 34 | } 35 | 36 | if [[ ${JOINCODE} == "" ]]; then 37 | echo "" 38 | echo "ERROR: No JOINCODE provided in \"docker run ... \" command. Visit app.husarnet.com to get a JOINCODE" 39 | echo "" 40 | /bin/bash 41 | exit 42 | fi 43 | 44 | echo "" 45 | echo "⏳ [1/2] Initializing Husarnet Client:" 46 | sudo husarnet daemon > /dev/null 2>&1 & 47 | 48 | for i in {1..10} 49 | do 50 | sleep 1 51 | 52 | output=$( get_status < <(sudo husarnet status) ) 53 | echo "$output" 54 | 55 | if [[ $output != "waiting..." ]]; then 56 | break 57 | fi 58 | done 59 | 60 | echo "" 61 | echo "🔥 [2/2] Connecting to Husarnet network as \"${HOSTNAME}\":" 62 | sudo husarnet join ${JOINCODE} ${HOSTNAME} 63 | echo "done" 64 | echo "" 65 | 66 | print_instruction < <(sudo husarnet status) 67 | 68 | exec "$@" -------------------------------------------------------------------------------- /ros2_ws/src/my_demo_pkg/my_demo_pkg/color_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import rclpy 4 | from rclpy.node import Node 5 | from functools import partial 6 | 7 | from turtlesim.srv import SetPen 8 | import random 9 | 10 | class ColorControllerNode(Node): 11 | def __init__(self): 12 | super().__init__("color_controller") 13 | self.r_ = 100 14 | self.g_ = 100 15 | self.b_ = 100 16 | self.control_loop_timer_ = self.create_timer(1.0, self.control_loop) 17 | 18 | def control_loop(self): 19 | self.r_ = random.randint(0, 255) 20 | self.g_ = random.randint(0, 255) 21 | self.b_ = random.randint(0, 255) 22 | self.call_set_pen_server(self.r_, self.g_, self.b_) 23 | 24 | def call_set_pen_server(self, r, g, b): 25 | client = self.create_client(SetPen, "/turtle1/set_pen") 26 | while not client.wait_for_service(1.0): 27 | self.get_logger().warn("Waiting for server Set Pen....") 28 | 29 | request = SetPen.Request() 30 | request.r = r 31 | request.g = g 32 | request.b = b 33 | request.width = 10 34 | 35 | future = client.call_async(request) 36 | future.add_done_callback(partial(self.callback_set_pen, r=r, g=g, b=b)) 37 | 38 | def callback_set_pen(self, future, r, g, b): 39 | try: 40 | response = future.result() 41 | self.get_logger().info("[" +str(r) + ", " + str(g) + ", " + str(b) + "] SetPen srv response: " + str(response)) 42 | 43 | except Exception as e: 44 | self.get_logger().error("Service call failed %r" % (e,)) 45 | 46 | def main(args=None): 47 | rclpy.init(args=args) 48 | node = ColorControllerNode() 49 | rclpy.spin(node) 50 | rclpy.shutdonw() 51 | 52 | if __name__ == "__main__": 53 | main() -------------------------------------------------------------------------------- /eg8/dev1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | dds_updater: 5 | build: 6 | context: ../ 7 | dockerfile: ./dds_updater/Dockerfile 8 | volumes: 9 | - ./cyclonedds.xml:/cyclonedds.xml 10 | stdin_open: true # docker run -i 11 | tty: true # docker run -t 12 | network_mode: service:husarnet 13 | 14 | color_controller: 15 | build: 16 | context: ../../ 17 | dockerfile: eg8/dev1/turtle_controller/Dockerfile 18 | volumes: 19 | - ./cyclonedds.xml:/cyclonedds.xml 20 | command: 21 | - bash 22 | - -c 23 | - | 24 | source /ros2_ws/install/setup.bash 25 | export CYCLONEDDS_URI=file:///cyclonedds.xml 26 | ros2 run my_demo_pkg color_controller 27 | network_mode: service:husarnet 28 | 29 | move_controller: 30 | build: 31 | context: ../../ 32 | dockerfile: eg8/dev1/turtle_controller/Dockerfile 33 | volumes: 34 | - ./cyclonedds.xml:/cyclonedds.xml 35 | command: 36 | - bash 37 | - -c 38 | - | 39 | source /ros2_ws/install/setup.bash 40 | export CYCLONEDDS_URI=file:///cyclonedds.xml 41 | ros2 run my_demo_pkg move_controller 42 | network_mode: service:husarnet 43 | 44 | husarnet: 45 | image: husarnet/husarnet 46 | restart: unless-stopped 47 | volumes: 48 | - /var/lib/husarnet # This will persist your Husarnet Client keys, thus IP of the container will be stable/the same between (re)boots 49 | sysctls: 50 | - net.ipv6.conf.all.disable_ipv6=0 # Husarnet is using IPv6 for the internal connections 51 | cap_add: 52 | - NET_ADMIN 53 | devices: 54 | - /dev/net/tun 55 | env_file: 56 | - ./.env # create .env file in the same folder as Dockerfile and specify HOSTNAME and JOINCODE there 57 | # network_mode: service:turtle_controller # This is the most important line in this setup. This will put the Husarnet Client in the same network namespace as your app (in this example: turtle_sim) 58 | 59 | volumes: 60 | shared_cyclonedds_xml: -------------------------------------------------------------------------------- /docs/fig4-two-containers-two-networks.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/fig5a-solution.drawio: -------------------------------------------------------------------------------- 1 | 7Vxbd9o4EP41PMLxFZvHkIS257QpLW3T7kuOsQVoKyzWFrf++pVs+SoBhhhwliUPsUaysPXNNzMaSbT0+/nmXeAsZp+wB1BLU7xNS39oaZratXT6j0m2XKLbsWAaQI+LMsEI/gFcqHDpEnogLDQkGCMCF0Whi30fuKQgc4IAr4vNJhgVv3XhTIEgGLkOEqXP0CMzLlW7vaziPYDTGf9qW7PiirmTNOZvEs4cD69zIv2xpd8HGJP4ar65B4gNXjIu8X2DHbXpgwXAJ1VusL8MvbUxD7+MwvvxB3v0/OMzaCevsXLQkr8xf1qyTYYgwEvfA6wXtaX31zNIwGjhuKx2TUGnshmZI149wT7hKNLn0fsOglOfFlz6lCBgDSBC9xjhIOpcHzuu7elUHpIA/wa5Gk03DJN+a198Uf7uKxAQsMmJ+Iu/A3gOSLClTZLaRJ24FvYMXl5nmNoWl81ycBpJQ4fr0TTtOxtqesFH+4iR17rHjLxy/Mizcm44408OEQQmRMRjYrI/GR7d6FMPHlYZD1vEQ9MleOjds+Gh3jAeqlkBD+2ieKji8AOPmmZexAGZ4Sn2HfSYSftFgLI2HzFecFj+BoRsOS7OkuAiaGADyc/c9S/eFbt+2OQL26Tg07f9mS9E93Q0Myln90Wl5MYY0MSpaFG9d8dcFi2OEXZ/f5tBPxYPIEqecCfaIV4GLtg3otx9OsEUkD3tuGFio71XdwKAHAJXRUdZvx5IaNlFhBOqoCDdf5Y4qWiHEcR3tIGmLTZZJb2axv8HLiPTC40cKBIIUe/EO6ZVcd9Jy7IeIkTDD1DBBBS57DnAnrhSLrs2GE9q4nK3yGVV6UrIrEjIbJ+Ny4o4hlficsc8ic2NI7NWkcxmo8isnY/Mc7wC/30up074elw2rsllpXW0X1aLTLYaR2XjWn45upW+mbPNNVhg6JMw1/OQCXKhu2kUVTKZW2VKFXeZqVj6bKdrnXE+w0GWAUEghPNzm4yJ7QJXajLGtmmYSk1TK80s4SOZ617YZKjiGDbd/Wce/1fehjTFZpgVbYbRKPdvnpvFKovq597LimUhq7H5qOxWDfxMM0qpS6/Kz7OlolS9OfzUTiOo0jSCdt8kQVWzMcEdD9SOjO8aZ6ir6oHaKD2Q5aZrN9QhIC8L4L8ZQ63IkqIXNdTWq2FRbRksBC+gWxUHOnykONjFeNbHPigFv1wkrAUxMKDroDteMYeeh3YF1kUzUwfAvbInVkWA05WjPMDauQC2zwRwCIIVpIN5cxBbpcmQYkogNi4JsSpi/B6HDNvPo4YCUV7JqgEY1SotOCnXxkXTBVy+j5c+WTJnpnQUMe31enD4kl/DoeldHRox+/OVkUVhwgHebG8VGlWRpIMvjI04p3+gUTRLwCv3dAQc6EfXYgqombYtKvOHlGyxOR6yUhTZky3GXRYxMYqUIqbdKGKWUgEx86KIiSHDbbqmcm67Aa5J3D93m65JWHa4vmsy3t6yQ5uyWdXyCS2lY/RSwY6kVlQaggDScWPWNRJ6TjhLsyUH9qIp0SdSRScgsnRYVJFLiFXKmonJtp2KdjCPpldNpOmckQ3JpBmiG71kRjVVvGM2sh239nWriqY2a5+cLuZsn2F7AKnoK14yu/CqhGpVYIVIr44Y2i5NewxJJlaVbU9VlRqcS/AwmDx9+guGv8CL9q63xuPR4lpr2iduTzuRRQVqV6aslBYnkrEnklEOhyLXoAtxT4wCh9/7Hz/Q3pQPT98evz49fhPUJQdrEsp9dMYADXEICcSMRGNMCJ7LWIVYy77j/p5GOpXfZRJ9JPEhYTomAVnYjaIotjKIjHRyYqVg5nnNgr3GfDNlx346EIdWB7rYDztThMfgCINRg30wLKtTij4NS2YhTImFOFv4aWmCUgg6kIzjBIENp1aeqy5ywhC6ZfJl4uoGejBIPW8J7sGAH0QoqsbekwUHfV5+2GVLZKnwuM1qwm40ozwjVHrFLmITw+/KEBU66poHOopNkNBRXRvcLDH9nKzWeHCVrNT8GD7l1nByFZK2QwCCNsFt9n/XTRIPFtmgompVz94EIIR/nHHUH1M2vpmQdm72W+bDEWoqqt5+kpWNRXoikD9NK3/oTmZEqBM1LLugA3otKtru6oVe00gt6QJPJiF4rVJJveIRFgjOo4OQh13RyZ7HCRfxAc0J3DBH0o++8i6RKomEXs8IYcc779iAaIPZMnQCH5COSx9AG8D5NCd8Cel30LpwNU21RqIMgg3bqQd6+WiaJMdkap3kGGveqNVxFkqKoxhevM0lw1cBY1llC33BBJM86LzO3P7EeXrFOUDFIP/AZEEaJhycA+wJ7atkfgy5/tQ+B9j7mM2cf7+OeXY5ur7+/LvKcd3/fVsJSNu+qm/bw9o6f4FA9Gw7jU/1OWa3tFnMllBAl+0VM8+3bfuoXxC42shVGTjZFPGUgaPF7Ac14ig6+1kS/fFf -------------------------------------------------------------------------------- /docs/fig5-solution.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/fig6-sidecar-husarnet.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/fig6b-solution.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/fig7-sidecar-husarnet.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/fig8-sidecar-husarnet.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ros2_docker_examples 2 | 3 | This repo shows few different ways to deal with ROS 2 node interconnectivity depending whether you: 4 | 5 | - use nodes on a single machine or on multiple machines 6 | - use nodes with or without docker 7 | - connect ROS 2 nodes over LAN or WAN 8 | 9 | To focus purely on connectivity, not on running fancy ROS 2 software or robots in Gazebo, I will go through different scenarios based on a simple ROS 2 system containing of 3 nodes: 10 | 11 | - `/turtlesim` - a very simple, simulator for learning ROS where you simulate ... a turtle :) 12 | - `/move_controller` - node for controlling the movement of the turtle 13 | - `/color_controller` - node that is changing the color of the line drawn by a turtle each second 14 | 15 | The solution is scalable, so what you will learn can be applied in very complex distributed ROS 2 systems as well! 16 | 17 | Below, there are 4 example use cases using the same code base, launched in different architecture scenarios. 18 | 19 | ------------- 20 | ## Before you start ... 21 | 22 | Make sure you have Docker and Docker-Compose installed on your laptop. 23 | 24 | [The official instruction](https://docs.docker.com/get-docker/) is the best tutorial but here's a quick rundown for you (for Linux): 25 | 26 | ```bash 27 | sudo -E apt-get -y install apt-transport-https ca-certificates software-properties-common && \ 28 | curl -sL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - && \ 29 | arch=$(dpkg --print-architecture) && \ 30 | sudo -E add-apt-repository "deb [arch=${arch}] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \ 31 | sudo -E apt-get update && \ 32 | sudo -E apt-get -y install docker-ce docker-compose 33 | ``` 34 | 35 | ```bash 36 | sudo systemctl daemon-reload 37 | sudo systemctl restart docker 38 | ``` 39 | 40 | ROS 2 Foxy installed on your laptop is needed only for **[Eg. 0]** section. 41 | 42 | 43 | ## [Eg. 0] Running without Docker 44 | 45 | ![use ROS 2 on host machine without Docker](docs/fig1-system-architecture.png) 46 | 47 | ```bash 48 | cd ros2_ws 49 | colcon build 50 | 51 | source /opt/ros/foxy/setup.bash 52 | source install/setup.bash # full path might be: ~/ros2_docker_examples/ros2_ws/install/setup.bash 53 | 54 | ros2 launch my_turtle_bringup turtlesim_demo.launch.py 55 | ``` 56 | 57 | 58 | ## [Eg. 1] Running in a single container 59 | 60 | ![launch the whole ROS 2 app in a single Docker container](docs/fig2-one-container.png) 61 | 62 | **Please stay in `ros2_docker_examples/` directory while executing those commands:** 63 | 64 | ``` 65 | sudo chmod +x eg1/ros_entrypoint.sh 66 | 67 | docker build -t turtle_demo -f eg1/Dockerfile . 68 | 69 | xhost local:root 70 | 71 | sudo docker run --rm -it \ 72 | --env DISPLAY \ 73 | --volume /tmp/.X11-unix:/tmp/.X11-unix:rw \ 74 | turtle_demo \ 75 | ros2 launch my_turtle_bringup turtlesim_demo.launch.py 76 | ``` 77 | 78 | ## [Eg. 2] Running in two containers (using `docker-compose`) 79 | 80 | ![launch multi-container ROS 2 app using docker-compose](docs/fig3-two-containers.png) 81 | 82 | ```bash 83 | cd eg2 84 | docker-compose up --build 85 | ``` 86 | 87 | ## [Eg. 3] PROBLEM: Running on two computers in different networks 88 | 89 | ![ROS 2 DDS discovery doesn't work if devices are in different networks](docs/fig4-two-containers-two-networks.png) 90 | 91 | Because two ROS 2 devices are in different networks, DDS can not perform auto-discovery. 92 | 93 | Also devices can not reach each other because they do not have neither public nor static IP addresses and are behind Wi-Fi router NAT. 94 | 95 | ## [Eg. 3] SOLUTION: Connecting ROS 2 machines using VPN 96 | 97 | ![install VPN client inside ROS 2 docker container](docs/fig5a-solution.png) 98 | 99 | Ready to use example is available in `eg3/` folder. There are two separate subfolders with a `docker-compose.yml` file which should be launched on two separate devices from different networks. 100 | 101 | ### Connecting containers to the same VPN network 102 | 103 | At first modify `eg3/dev1/.env` and `eg3/dev2/.env` files by providing the same Husarnet network Join Code there. 104 | 105 | ``` 106 | JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/xxxxxxxxxxxxxxxxxxxxxx 107 | HOSTNAME=turtle-controller 108 | ``` 109 | 110 | You will find your Join Code at **https://app.husarnet.com 111 | -> Click on the desired network 112 | -> `Add element` button 113 | -> `Join Code` tab** 114 | 115 | 1. Start the first device: 116 | 117 | ```bash 118 | cd eg3/dev1 119 | docker-compose up --build 120 | ``` 121 | 122 | After a while you should see your first device connected to the Husarnet network: 123 | 124 | ![IPv6 address of the first device](docs/eg3_dev1_dashboard.png) 125 | 126 | 2. Add the first device address to Peers list of the second device, by editing: `eg3/dev2/cyclonedds.xml` file: 127 | 128 | ```xml 129 | ... 130 | 131 | 132 | 133 | 134 | auto 135 | 136 | ... 137 | ``` 138 | 139 | 3. Start the second device: 140 | 141 | ```bash 142 | cd eg3/dev2 143 | docker-compose up --build 144 | ``` 145 | 146 | You should see now two devices in the Dashboard: 147 | 148 | ![IPv6 address of the second device](docs/eg3_dev2_dashboard.png) 149 | 150 | Problem: turtle is not moving! 151 | Solution: while starting `dev1` we didn't new the IPv6 address of the second device, but we know it now. 152 | 153 | 4. Kill `Docker-Compose` on the first device (`ctrl` + `c`). Add the seconds device address to the Peers list of the first device, by editing: `eg3/dev1/cyclonedds.xml` file: 154 | 155 | ```xml 156 | ... 157 | 158 | 159 | 160 | 161 | auto 162 | 163 | ... 164 | ``` 165 | 166 | 5. Restart the first device container and everything looks fine now: 167 | 168 | ```bash 169 | cd eg3/dev1 170 | docker-compose up --build 171 | ``` 172 | 173 | ![turtle is finally working on two devices](docs/eg3_turtle.png) 174 | 175 | 176 | ------- 177 | It works, but there are a few things that we don't like: 178 | 179 | - we don't know IPv6 addressed of the containers before starting them. So we need to make a dummy start of the first container, just to get the IPv6 address to be written in `cyclonedds.xml` of the second container. 180 | 181 | - we need to modify containers that we already have to connect them over the Internet (installing VPN client within a containers) 182 | 183 | We will fix those issues in the example number 4.s 184 | 185 | ## [Eg. 4] Using a separate VPN container 186 | 187 | Instead of modyfing your own containers, you can launch a separate official [Husarnet VPN container](https://hub.docker.com/r/husarnet/husarnet) next to your existing app container. 188 | 189 | `hnet0` network interface from Husarnet container is shared with any container you specify in the `docker-compose.yml`. Thanks to that without modyfying your exisitng container with ROS 2 nodes, you can connect them with remote nodes without any effor. 190 | 191 | Moreover instead of long IPv6 addresses you can use Husarnet hostnames of the Husarnet Container (specified in `eg4/dev*/.env` files). 192 | 193 | ![connect remote machines running ROS 2 app by using a separate VPN container](docs/fig6-sidecar-husarnet.png) 194 | 195 | That's a truely zero effort solution that simply works. 196 | 197 | TL;DR: 198 | 199 | ### DEVICE 1 200 | 201 | Clone this repo to the first device, then execute in the terminal: 202 | 203 | ```bash 204 | cd ros2_docker_examples/eg4/dev1 205 | 206 | # Add your own join code to the .env file in the current directory. 207 | # Example .env file content: 208 | # 209 | # JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/tTZtwiqM59iXtnCWABUEKH 210 | # HOSTNAME=turtle-controller-1 211 | 212 | docker-compose up --build 213 | ``` 214 | 215 | ### DEVICE 2 216 | 217 | Clone this repo to the second device, then execute in the terminal: 218 | 219 | ```bash 220 | xhost local:root 221 | 222 | cd ros2_docker_examples/eg4/dev2 223 | 224 | # Add your own join code to the .env file in the current directory. 225 | # Example .env file content: 226 | # 227 | # JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/tTZtwiqM59iXtnCWABUEKH 228 | # HOSTNAME=turtlesim-1 229 | 230 | docker-compose up --build 231 | ``` 232 | 233 | ### Result: 234 | 235 | ![turtlesim](docs/screenshot.png) 236 | 237 | Note that we haven't modified `cyclonedds.xml` file, because we specified there hostnames of Husarnet containers (in `.env` file) insted of IPv6 address. 238 | 239 | -------------------- 240 | 241 | OK, pretty nice, but we still needed to create a custom Dockerfile even for just running a turtlesime. Our next goal is to find a way to do not build a special Docker Images "working with Husarnet", but run any existing ROS 2 image, by just applying the proper configuration in the `Docker Compose`. 242 | 243 | ## [Eg. 5] Run VPN network without modyfing existing Docker images 244 | 245 | ### DEVICE 1 246 | 247 | ```bash 248 | cd ros2_docker_examples/eg5/dev1 249 | 250 | # Add your own join code to the .env file in the current directory. 251 | # Example .env file content: 252 | # 253 | # JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/tTZtwiqM59iXtnCWABUEKH 254 | # HOSTNAME=turtle-controller-2 255 | 256 | docker-compose up --build 257 | ``` 258 | 259 | ### DEVICE 2 260 | 261 | Clone this repo to the second device, then execute in the terminal: 262 | 263 | ```bash 264 | xhost local:root 265 | 266 | cd ros2_docker_examples/eg5/dev2 267 | 268 | # Add your own join code to the .env file in the current directory. 269 | # Example .env file content: 270 | # 271 | # JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/tTZtwiqM59iXtnCWABUEKH 272 | # HOSTNAME=turtlesim-2 273 | 274 | docker-compose up --build 275 | ``` 276 | 277 | ### Result: 278 | 279 | The same :) 280 | 281 | -------------------- 282 | 283 | OK, so we can run our system containing 3 containers on two machines. It's completely fine, but to make our system more understabable by only reading a `docker-compose.yml`, instead of running a single `turtle_controller` container, we can run all ROS nodes in the separate containers. 284 | 285 | ## [Eg. 6] Running all ROS 2 nodes in separate containers 286 | 287 | ![Running all ROS 2 nodes in separate containers](docs/fig7-sidecar-husarnet.png) 288 | 289 | ### DEVICE 1 290 | 291 | ```bash 292 | cd ros2_docker_examples/eg6/dev1 293 | 294 | # Add your own join code to the .env file in the current directory. 295 | # Example .env file content: 296 | # 297 | # JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/tTZtwiqM59iXtnCWABUEKH 298 | # HOSTNAME=turtle-controller-6 299 | 300 | docker-compose up --build 301 | ``` 302 | 303 | ### DEVICE 2 304 | 305 | Clone this repo to the second device, then execute in the terminal: 306 | 307 | ```bash 308 | xhost local:root 309 | 310 | cd ros2_docker_examples/eg6/dev2 311 | 312 | # Add your own join code to the .env file in the current directory. 313 | # Example .env file content: 314 | # 315 | # JOINCODE=fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/tTZtwiqM59iXtnCWABUEKH 316 | # HOSTNAME=turtlesim-6 317 | 318 | docker-compose up --build 319 | ``` 320 | 321 | ### Result: 322 | 323 | The same :) 324 | 325 | -------------------- 326 | 327 | Our network works pretty well, configuration is nice, however if we would like to add 3rd device to the network, we would need to add new Peer's hostname in `cyclonedds.xml` files of 1st and 2nd device. In the next example we will introduce a separate `DDS updater` container that will update a Peer's list automatically by using `/etc/hosts` file being updated by Husarnet VPN. 328 | 329 | 330 | ....TODO 331 | 332 | ## [Eg. 8] Introducing auto DDS update container 333 | 334 | ![Introducing auto DDS update container](docs/fig8-sidecar-husarnet.png) --------------------------------------------------------------------------------