├── multi_machine
├── resource
│ └── multi_machine
├── multi_machine
│ ├── __init__.py
│ └── node_1.py
├── setup.cfg
├── launch
│ └── multi_launch.py
├── package.xml
├── test
│ ├── test_copyright.py
│ ├── test_pep257.py
│ └── test_flake8.py
└── setup.py
├── img
└── chart.png
├── snap
└── snapcraft.yaml
├── LICENCE
└── README.md
/multi_machine/resource/multi_machine:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/multi_machine/multi_machine/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/img/chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anhbantre/multi_robot_ros2/HEAD/img/chart.png
--------------------------------------------------------------------------------
/multi_machine/setup.cfg:
--------------------------------------------------------------------------------
1 | [develop]
2 | script-dir=$base/lib/multi_machine
3 | [install]
4 | install-scripts=$base/lib/multi_machine
5 |
--------------------------------------------------------------------------------
/multi_machine/launch/multi_launch.py:
--------------------------------------------------------------------------------
1 | import os
2 | import yaml
3 | from ament_index_python.packages import get_package_share_directory
4 | from launch import LaunchDescription
5 | from launch_ros.actions import Node
6 |
7 | from launch.actions import DeclareLaunchArgument, ExecuteProcess
8 | from launch.substitutions import LaunchConfiguration, Command, PythonExpression
9 |
10 | ROBOT_NAME = 'robot1'
11 |
12 | def generate_launch_description():
13 |
14 | launch = LaunchDescription()
15 |
16 | node_1 = Node(
17 | package = "multi_machine",
18 | executable = "node_1",
19 | name = f"node_1_{ROBOT_NAME}",
20 | parameters = [
21 | {'robot_name': ROBOT_NAME},
22 | {'my_param': "param1"}
23 | ],
24 | )
25 |
26 | launch.add_action(node_1)
27 |
28 | return launch
--------------------------------------------------------------------------------
/multi_machine/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | multi_machine
5 | 0.0.0
6 | TODO: Package description
7 | rtr
8 | TODO: License declaration
9 |
10 | rclpy
11 | std_msgs
12 | ros2launch
13 |
14 | ament_copyright
15 | ament_flake8
16 | ament_pep257
17 | python3-pytest
18 |
19 |
20 | ament_python
21 |
22 |
23 |
--------------------------------------------------------------------------------
/multi_machine/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 |
--------------------------------------------------------------------------------
/multi_machine/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | import os
3 | import glob
4 |
5 | package_name = 'multi_machine'
6 |
7 | setup(
8 | name=package_name,
9 | version='0.0.0',
10 | packages=[package_name],
11 | data_files=[
12 | ('share/ament_index/resource_index/packages',
13 | ['resource/' + package_name]),
14 | ('share/' + package_name, ['package.xml']),
15 | (os.path.join('share', package_name, 'launch'), glob.glob('launch/*.py'))
16 | ],
17 | install_requires=['setuptools'],
18 | zip_safe=True,
19 | maintainer='huyan',
20 | maintainer_email='anhuynguyen001@gmail.com',
21 | description='TODO: Package description',
22 | license='TODO: License declaration',
23 | tests_require=['pytest'],
24 | entry_points={
25 | 'console_scripts': [
26 | 'node_1 = multi_machine.node_1:main'
27 | ],
28 | },
29 | )
30 |
--------------------------------------------------------------------------------
/multi_machine/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 |
--------------------------------------------------------------------------------
/snap/snapcraft.yaml:
--------------------------------------------------------------------------------
1 | name: ros2-multi # you probably want to 'snapcraft register '
2 | base: core20 # the base snap is the execution environment for this snap
3 | version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
4 | summary: ROS 2 Multi-machine # 79 char long summary
5 | description: |
6 | This is example for runing ROS 2 system on multi-machine
7 |
8 | # grade: devel # must be 'stable' to release into candidate/stable channels
9 | confinement: devmode # use 'strict' once you have the right plugs and slots
10 |
11 | parts:
12 | ros2-multi-test:
13 | # See 'snapcraft plugins'
14 | plugin: colcon
15 | source-type: git
16 | source: https://github.com/anhbantre/multi_robot_ros2.git
17 | source-branch: main
18 | source-subdir: multi_machine
19 | stage-packages: [ros-foxy-ros2launch]
20 |
21 | apps:
22 | ros2-multi:
23 | command: opt/ros/foxy/bin/ros2 launch multi_machine multi_launch.py
24 | extensions: [ros2-foxy]
25 |
--------------------------------------------------------------------------------
/multi_machine/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 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Huy An
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 |
--------------------------------------------------------------------------------
/multi_machine/multi_machine/node_1.py:
--------------------------------------------------------------------------------
1 | import rclpy
2 | from rclpy.node import Node
3 | from std_msgs.msg import String
4 |
5 | class MyNode(Node):
6 |
7 | def __init__(self):
8 | super().__init__('node_1')
9 |
10 | # Load params
11 | self.declare_parameter('robot_name', None)
12 | self.declare_parameter('my_param', None)
13 | self.robot_name = self.get_parameter('robot_name').value
14 | self.my_param = self.get_parameter('my_param').value
15 | self.add_on_set_parameters_callback(self.parameters_callback)
16 |
17 | # Use the node name to create the topic name
18 | self.node_name = self.get_name()
19 |
20 | # Create a publisher using the topic name
21 | self.publisher = self.create_publisher(String, f'{self.robot_name}/my_topic', 10)
22 |
23 | timer_period = 1 # seconds
24 | self.timer = self.create_timer(timer_period, self._timer_callback)
25 |
26 | def parameters_callback(self, params):
27 | # do some actions, validate parameters, update class attributes, etc.
28 | return SetParametersResult(successful=True)
29 |
30 | def _timer_callback(self):
31 | # Publish a message on the topic
32 | message = String()
33 | message.data = self.node_name
34 | self.publisher.publish(message)
35 | self.get_logger().info(F"Parameter: {self.my_param}")
36 |
37 | def main(args=None):
38 | rclpy.init(args=args)
39 | node = MyNode()
40 | rclpy.spin(node)
41 | node.destroy_node()
42 | rclpy.shutdown()
43 |
44 | if __name__ == '__main__':
45 | main()
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Multi-robot in ROS2
2 |
3 | This is a fundamental repository used when multiple system robots (ROS2) have the same architecture (also the same topics, nodes, ...) within the same network. It can recognize itself and others without conflict. Each robot has a unique name `ROBOT_NAME`. For instance, in this repo, the robot has the name `robot1`.
4 |
5 | You can run two robots by cloning this repository in two devices, ensuring both are connected to the same network and changing the different name `ROBOT_NAME` for each robot.
6 |
7 |
8 |

9 |
10 |
11 | ## Usage
12 |
13 | To change the different name for each robot, change the `ROBOT_NAME` variable at line 12 in the launch file `multi_machine/launch/multi_launch.py`:
14 | ```
15 | ROBOT_NAME = 'robot1'
16 | ```
17 |
18 | To run launch file:
19 | ```
20 | ros2 launch multi_machine multi_launch.py
21 | ```
22 |
23 | > Note: Make sure you downloaded the repo in your workspace and built it successfully
24 |
25 | To check all topics:
26 | ```
27 | ros2 topic list
28 | ```
29 | Giving:
30 | ```
31 | /parameter_events
32 | /robot1/my_topic
33 | /rosout
34 | ```
35 |
36 | The name topic will be `/robot1/my_topic` where `robot1` is the `ROBOT_NAME` variable you have set in launch file, `my_topic` is your topic name.
37 |
38 | To check data the node is received:
39 | ```
40 | ros2 topic echo /robot1/my_topic
41 | ```
42 |
43 | ## Build a snap package
44 |
45 | Let's package this application as a snap to deploy on other robotics devices. This repository uses Jetson Xavier NX with:
46 | - Jetpack 5.0.2 GA
47 | - Ubuntu 20.04
48 | - `arm64` architecture platform
49 |
50 | First, install snapcraft:
51 | ```
52 | sudo snap install --classic snapcraft
53 | ```
54 |
55 | To install core20:
56 | ```
57 | sudo snap install core20
58 | ```
59 |
60 | Now let's build the snap:
61 | ```
62 | snapcraft --enable-experimental-extensions --destructive-mode
63 | ```
64 |
65 | For quick running without building, if you use it on the same device, download the built snap [here](https://drive.google.com/file/d/1XrUCOwf83eowDTVol0eeAhx3A_yk6k3e/view?usp=sharing)
66 |
67 | When this snap build completely, let's install it:
68 | ```
69 | sudo snap install .snap --devmode
70 | ```
71 |
72 | Finally, run it:
73 | ```
74 | ros2-multi
75 | ```
76 |
77 | Result:
78 | ```
79 | [node_1-1] [INFO] [1679278253.059005344] [node_1_robot1]: Parameter: param1
80 | [node_1-1] [INFO] [1679278254.019700651] [node_1_robot1]: Parameter: param1
81 | [node_1-1] [INFO] [1679278255.018450358] [node_1_robot1]: Parameter: param1
82 | ```
83 |
84 | It will get parameters in launch file and print it out.
85 |
86 | ## Reference
87 | [Packaging your ROS 2 application as a snap](https://docs.ros.org/en/foxy/Tutorials/Miscellaneous/Packaging-your-ROS-2-application-as-a-snap.html#id5)
88 |
89 | ## Author
90 |
91 | Nguyen Huy An
92 | Email: anhuynguyen001@gmail.com
--------------------------------------------------------------------------------