As I was researching, I noticed that ROBOTIS doesn’t provided a guide on how to run multiple TurtleBot3 robots together. It is especially dangerous if you run them in the same network because they all run on the same topic names and node names, which can interfere with their individual operation. So to help run multiple TurtleBots on the same network, you need to give each robot a unique namespace. The following guide will show you how to do this for the TurtleBot3.
For this guide, we will be using tb3_0
as the namespace we wish to use for our TurtleBot3 Burger robot. This helps us number our robots easier when running multiple robot experiments. This guide also assumes you have followed the procedure located here for installing and setting up your TurtleBot3 with ros2!
Step 1: Create a New ros2 Package
Start by changing into your src
directory of your workspace that also contains the turtlebot3
and utils
packages provided by ROBOTIS.
~$ cd ~/turtlebot3_ws/src
~$ ros2 pkg create my_tb3_launcher
Now, create two empty directories in the new package:
~$ cd ~/turtlebot3_ws/src/my_tb3_launcher
~$ mkdir launch
~$ mkdir param
Change into the launch
directory and create a new bringup launch file.
~$ cd launch
~$ touch my_tb3_bringup.launch.py
Step 2: Copy and Modify Contents from the TB3 Bringup Package into Your Package
In the turtlebot3/turtlebot3_bringup
ros2 package, copy the contents of robot.launch.py
into the my_tb3_bringup.launch.py
with the following changes marked as # comments
in the following code:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch.substitutions import ThisLaunchFileDir
from launch_ros.actions import Node
def generate_launch_description():
TURTLEBOT3_MODEL = os.environ['TURTLEBOT3_MODEL']
usb_port = LaunchConfiguration('usb_port', default='/dev/ttyACM0')
tb3_param_dir = LaunchConfiguration(
'tb3_param_dir',
default=os.path.join(
get_package_share_directory('my_tb3_launcher'), # <--- CHANGE THIS!
'param',
TURTLEBOT3_MODEL + '.yaml'))
use_sim_time = LaunchConfiguration('use_sim_time', default='false')
return LaunchDescription([
DeclareLaunchArgument(
'use_sim_time',
default_value=use_sim_time,
description='Use simulation (Gazebo) clock if true'),
DeclareLaunchArgument(
'usb_port',
default_value=usb_port,
description='Connected USB port with OpenCR'),
DeclareLaunchArgument(
'tb3_param_dir',
default_value=tb3_param_dir,
description='Full path to turtlebot3 parameter file to load'),
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[ThisLaunchFileDir(), '/turtlebot3_state_publisher.launch.py']),
launch_arguments={'use_sim_time': use_sim_time}.items(),
),
IncludeLaunchDescription(
PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/hlds_laser.launch.py']), <--- CHANGE THIS
launch_arguments={'port': '/dev/ttyUSB0', 'frame_id': 'base_scan'}.items(),
),
Node(
package='turtlebot3_node',
node_executable='turtlebot3_ros',
node_namespace='tb3_0', # <------------------- ADD THIS!
parameters=[tb3_param_dir],
arguments=['-i', usb_port],
output='screen'),
])
Next, copy the file turtlebot3_state_publisher.launch.py
from the turtlebot3_bringup/launch
directory into your package’s launch
directory. Make sure it has the same name! Once complete, make the following changes as marked by the following comments:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
TURTLEBOT3_MODEL = os.environ['TURTLEBOT3_MODEL']
use_sim_time = LaunchConfiguration('use_sim_time', default='false')
urdf_file_name = 'turtlebot3_' + TURTLEBOT3_MODEL + '.urdf'
print("urdf_file_name : {}".format(urdf_file_name))
urdf = os.path.join(
get_package_share_directory('turtlebot3_description'),
'urdf',
urdf_file_name)
return LaunchDescription([
DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation (Gazebo) clock if true'),
Node(
package='robot_state_publisher',
node_executable='robot_state_publisher',
node_name='robot_state_publisher',
node_namespace='tb3_0', # <------------------- ADD THIS!
output='screen',
parameters=[{'use_sim_time': use_sim_time}],
arguments=[urdf]),
])
Finally, copy the file hlds_laser.launch.py
from the hls_lfcd_lds_driver
package located in the launch
directory into your package’s launch
directory. Again, make sure it has the same name!. Modify the launch file with the following changes marked by the comments below:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import LogInfo
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
port = LaunchConfiguration('port', default='/dev/ttyUSB0')
frame_id = LaunchConfiguration('frame_id', default='laser')
return LaunchDescription([
DeclareLaunchArgument(
'port',
default_value=port,
description='Specifying usb port to connected lidar'),
DeclareLaunchArgument(
'frame_id',
default_value=frame_id,
description='Specifying frame_id of lidar. Default frame_id is \'laser\''),
Node(
package='hls_lfcd_lds_driver',
node_executable='hlds_laser_publisher',
node_name='hlds_laser_publisher',
node_namespace='tb3_0', # <------------------- ADD THIS!
parameters=[{'port': port, 'frame_id': frame_id}],
output='screen'),
])
Step 3: Modify the Parameter YAML File
Now copy the burger.yaml
file located in the param
directory of the turtlebot3_bringup
package, and make the following modification at the top!
tb3_0:
turtlebot3_node:
ros__parameters:
opencr:
id: 200
baud_rate: 1000000
protocol_version: 2.0
wheels:
separation: 0.160
radius: 0.033
motors:
profile_acceleration_constant: 214.577
# [rev/min2]
# ref) http://emanual.robotis.com/docs/en/dxl/x/xl430-w250/#profile-acceleration
profile_acceleration: 0.0
sensors:
bumper_1: false
bumper_2: false
illumination: false
ir: false
sonar: false
tb3_0:
diff_drive_controller:
ros__parameters:
odometry:
publish_tf: true
use_imu: true
frame_id: "odom"
child_frame_id: "base_footprint"
As you can see, the top most parameter used to be the node name (turtlebot3_node
and diff_drive_controller
). For the node namespace that you added to work, you will need to add the node namespace (tb3_0
) one level above the node name!
In Step 2, we already changed the launch file to point to this yaml file instead of the one located in the turtlebot3_bringup
package.
Step 4: Modify the CMakeLists File
For this section, we will just be adding a small code snipet to our CMakeLists.txt
that will install the launch
and param
contents of our my_tb3_launcher
package.
...
install(DIRECTORY
launch
param
DESTINATION share/${PROJECT_NAME}/
)
...
Add this snippet right before the if(BUILD_TESTING)
section of the CMakeLists.txt
file.
Step 5: Compile and Run!
Finally, compile the code on your TurtleBot3:
~$ cd ~/turtlebot3_ws
~$ colcon build --symlink-install --parallel-workers 1
~$ . install/setup.bash
Now, run your launch file to make sure it works!
~$ export TURTLEBOT3_MODEL=burger
~$ ros2 launch my_tb3_launcher my_tb3_bringup.launch.py
You should get the following topics when you run ros2 topic list
in another bash session:
/tb3_0/battery_state
/tb3_0/cmd_vel
/tb3_0/imu
/tb3_0/joint_states
/tb3_0/magnetic_field
/tb3_0/odom
/tb3_0/parameter_events
/tb3_0/robot_description
/tb3_0/rosout
/tb3_0/scan
/tb3_0/sensor_state
/tb3_0/tf
/tb3_0/tf_static
You can repeat these procedures with other TurtleBot3 robots with different namespaces to have multiple robots working in your network. Hope this helps and happy programming everyone!