├── 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 | 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
--------------------------------------------------------------------------------
/docs/fig5-solution.drawio:
--------------------------------------------------------------------------------
1 | 7VzZcqM6EP0aP4bSwubHbL6Tqlkyk1mfUhhkmxls+YKcOPfrrwRilZzgBNtkHM9UBSQhoE+fbqlbYoDP5+t/Ym85+0ADEg0QCNYDfDFACJoIDcR/EDzIEuDgrGQah4EsKwtuwv9I3lCWrsKAJLWGjNKIhct6oU8XC+KzWpkXx/S+3mxCo/pdl96UKAU3vheppT/CgM1kKbSHZcU7Ek5n8tYucrKKuZc3lm+SzLyA3leK8OUAn8eUsuxovj4nkZBeLpfsutGG2uLBYrJgbS5wP18H9+Y8+XyTnI+v3Jsf3z+Rk/w17rxoJd9YPi17yEUQ09UiIKIXOMBn97OQkZul54vae446L5uxeSSrJ3TBJIr8efCZF4XTBT/x+VOSWDQIo+icRjROO8djz3cDzMsTFtM/pFKDsGla/K5n8glJzMh647vDQqJcFwmdExY/8Cb5BViCINVwaMrz+xJTN28zq8Bp5g09qUfTou9S1PxASnsLyWNLkfyMMaHVp6JjNPKWS4PNCFdsLpqVz5Jwbvh0rsDDpcLqGNRFuaAL0pC7LMqxichE9CAkHHLNP5XF8zAIxE20kJdKATSodwCZ6yLDqoMGVNCwZamgoV1hhuxt2AK2Z4s4r1Ag+6lINTg0scQ/HYfs9NcNIA5scMhV4UA6DmF7Z3jAI8YDWi3wQHvFA6riJwF3p/KUxmxGp3ThRZdlacOSlG3eU7qUsPwmjD1IXLwVo3XQyDpkPyvHv2RX4vhiXT15yE8W/G1/Vk/Sawxk5efldelZfmEGaD4QQGl9cCqGGfx0HFH/z9dZuMiKR2GUP2EmFSGKx7HmkqOr2CePiViOgbx4Stgj7Wy97sQk8lh4V3+O7vVAQ0s7YpJQNQWx/13RvOIkSSHm/g8gtFyXlfxomv0d+YJMt9wpciSiiI8oZMe8Kus7b9nUwyjiQ8ZN3qxqAupcDjziTnwtl32XjCcdcdmucxkCW0NmoCGzuzMuA1WGB+KyYT2Lzf0nM2pJZuuQZEa7I/Oc3pG/n8uFEz4cl81DchkMtvbLsM5kp/9UNvfkl9NL+Zt5D5UGSxouWFLp+VoUVIbulllXSQhBQ6myLksVK57t+Vpn7s5wsFXMIsJnxLs2GRPXJ77WZIxdy7Q6mus6yGrgo4lP7NlkQFWGfXf/pcf/VbUhvbUZVkubYR7S/athqo5ZDMWofh7c3onQcTs2bxWR7ICfRRSwcOlt+bmz8CHE/eEneh5BQe8Jar8GgkKrN4M7OVDbcnzXf0PdVg82WJH96IEuNt25oU4Iu12Sxasx1EAXFN2roXZeDAt0dbAwugz9tji8PEdU5O9ekCXqAuBh0xNDFeB8drOfpJC7I4ATEt+FXJhHB7HTmAwBSwOxuU+IoYrxO5oIbD/d7ACIXuZjC+k+ko3dLyoIK6h8G68WbCVcGTCAGvT6S6GBTgOa4cGhUWM/XwRVgCgc0fXDsUIDgSYYvGds1Bn9BR9Di/A7OOcS8MJFeqwGgPrhYnQ5+nyyA7uArJlX16Ti4HCviKljSM6m32K52/HCpCxHOTxM6ijhOP1RM5zdA3+kLnM8Tn+kZBoO74+wulrk6lpOjBxkQNcABjoWfGBzfosPzh2MFHwELsg0gHMssDjNYdzhYTFfX4LuhDtBiAbVFIBhDouCDeHf9OyaxCGXmxiUpIWBl8yKuOITqzZB+ktV0YuZLnCcVlRCx63iy2pYOgOkk4gzbhtyxkO94u4n5myqhnmfuYdC8bZZ8rldlvhN0fIY2EFXlGI1u/EjPBmFvOgLXQm70NTDF+1a2QSsMkHqxOU3hmSmo/EtuoXcEOzMuRxm8eczOdoWvWeyrTWba7aiS44O2zqDlyaiNywfsxsaajVHNdmTycsautfBSjKsTt+uv529v+K9gauPXy+/fLz8qihsRbHyweR7b0yia5qELKSCxmPKGJ3reB2Jlmee/2eaanV1RVj604xQmdByrfo0Vo4B4IJR6ibyHYE1RyNrluI15uup2FdphDRxjFBsBTOmER2TLZS+AwtlOo7RtFG6aAvS7MWCOxsAW+q85BEn8IzNP40ZSQeCLLboPLJlp1g+vJ8tO5aaRMkzjkF4l2cby3AjquQjKw0UyR9HEHLYsIyHD0JaW23o7QUrIHTry5N7QIu847a0wHntOC4p8UaUCsb5TvPeMMVW445FuF4EHY8mXA8BaLilgwe3bNW3Z/H6DySigVj3dKTQ9CBeb78tuyiGtg2/dXjevK272IBNH4ijxrAsZLimAYeO4agRl78UGreZ4jLxwZHRLdJtoJFPycN5+sWgp2MKzw4heMky+5LRJFwLIM7SW57mpSAv4cf1L8bMVokXLwjLPhMzCufTSuFtwu/B65K7aUc4utDATyf6TWzYmriAtTMsdetx37B8HEvcXEzTAyDhVh/baRPkVw1lN56mJjpXt+PA0Vgza3dbDjZPXlsvSbe1W0Gss4k/NEWT+n/sDz3+h5gYDayL17Vk/YlA6mgkA6ldT4lRc68K1qxz36sbdDZ/t6UWwdipKhHocKGdukM02aRHNQ17nbGVfSiYC6y+KZg6rd/omycRWcukWzVx50dekoTZjvxKWq4sbp/VLQSvZGhGI/mdr3o2p/xw19NpPRWV6uhWtwOtKHxhts5srr4Ew3oXWb5RSdYpHdnWEx3tOOvntEpNfL/+uMkaKG2vCYlPGD0Rf1uaEIF0Oiysq1Z7+xATbgq9cdqfUDb5rQ7euZVasvZqutEkyG+fypsMCv5WlfIRPm40IMCwTMet6QDuREWLGJLstVjekXdBJ5OE7CaVrPsqSf9Glfbw6VGlo9vm+JxRJT8tP0ObCbr8mi++/B8=
--------------------------------------------------------------------------------
/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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
--------------------------------------------------------------------------------