05 ROS2 Launch
05 ROS2 Launch
05 ROS2 Launch
Launch
ROS 2 launch system
The launch system in ROS 2 is responsible for helping the user describe the configuration of their
system and then execute it as described. The configuration of the system includes what programs
to run, where to run them, what arguments to pass them, and ROS specific conventions which
make it easy to reuse components throughout the system by giving them each different
configurations. It is also responsible for monitoring the state of the processes launched, and
reporting and/or reacting to changes in the state of those processes.
Launch files written in Python can start and stop different nodes as well as trigger and act on
various events. The package providing this framework is launch_ros , which uses the non-ROS-
specific launch framework underneath.
The design document details the goal of the design of ROS 2’s launch system (not all functionality
is currently available).
Writing a ROS 2 launch file
If you haven’t already, make sure you go through the tutorial on how to create a ROS 2 package.
One way to create launch files in ROS 2 is using a Python file, which are executed by the ROS 2
CLI tool, ros2 launch . We start by creating a ROS 2 package using ros2 pkg create <pkg-name> --
dependencies [deps] in our workspace and creating a new launch directory.
Python Packages
For Python packages, your directory should look like this:
src/
my_package/
launch/
setup.py
setup.cfg
package.xml
In order for colcon to find the launch files, we need to inform Python’s setup tools of our launch
files using the data_files parameter of setup .
Inside our setup.py file:
import os
from glob import glob
from setuptools import setup
package_name = 'my_package'
setup(
# Other parameters ...
data_files=[
# ... Other data files
# Include all launch files. This is the most important line here!
(os.path.join('share', package_name), glob('launch/*.launch.py'))
]
)
C++ Packages
If you are creating a C++ package, we will only be adjusting the CMakeLists.txt file by adding:
# Install launch files.
install(DIRECTORY
launch
DESTINATION share/${PROJECT_NAME}/
)
is _launch.py , used in the beginner level launch files tutorial. If you do change the suffix, make sure
to adjust the glob() argument in your setup.py file accordingly.
Your launch file should define the generate_launch_description() which returns
a launch.LaunchDescription() to be used by the ros2 launch verb.
import launch
import launch.actions
import launch.substitutions
import launch_ros.actions
def generate_launch_description():
return launch.LaunchDescription([
launch.actions.DeclareLaunchArgument(
'node_prefix',
default_value=[launch.substitutions.EnvironmentVariable('USER'), '_'],
description='Prefix for node names'),
launch_ros.actions.Node(
package='demo_nodes_cpp', executable='talker', output='screen',
name=[launch.substitutions.LaunchConfiguration('node_prefix'), 'talker']),
])
Usage
While launch files can be written as standalone scripts, the typical usage in ROS is to have launch
files invoked by ROS 2 tools.
After running colcon build and sourcing your workspace, you should be able to launch the launch
file as follows:
ros2 launch my_package my_script.launch.py
def generate_launch_description():
turtlesim_world_1 = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/turtlesim_world_1.launch.py'])
)
turtlesim_world_2 = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/turtlesim_world_2.launch.py'])
)
broadcaster_listener_nodes = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/broadcaster_listener.launch.py']),
launch_arguments={'target_frame': 'carrot1'}.items(),
)
mimic_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/mimic.launch.py'])
)
fixed_frame_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/fixed_broadcaster.launch.py'])
)
rviz_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/turtlesim_rviz.launch.py'])
)
return LaunchDescription([
turtlesim_world_1,
turtlesim_world_2,
broadcaster_listener_nodes,
mimic_node,
fixed_frame_node,
rviz_node
])
This launch file includes a set of other launch files. Each of these included launch files contains
nodes, parameters, and possibly, nested includes, which pertain to one part of the system. To be
exact, we launch two turtlesim simulation worlds, TF broadcaster, TF listener, mimic, fixed frame
broadcaster, and RViz nodes.
Note
Design Tip: Top-level launch files should be short, consist of includes to other files corresponding
to subcomponents of the application, and commonly changed parameters.
Writing launch files in the following manner makes it easy to swap out one piece of the system, as
we’ll see later. However, there are cases when some nodes or launch files have to be launched
separately due to performance and usage reasons.
Note
Design tip: Be aware of the tradeoffs when deciding how many top-level launch files your
application requires.
2 Parameters
2.1 Setting parameters in the launch file
We will begin by writing a launch file that will start our first turtlesim simulation. First, create a new
file called turtlesim_world_1.launch.py .
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, TextSubstitution
def generate_launch_description():
background_r_launch_arg = DeclareLaunchArgument(
'background_r', default_value=TextSubstitution(text='0')
)
background_g_launch_arg = DeclareLaunchArgument(
'background_g', default_value=TextSubstitution(text='84')
)
background_b_launch_arg = DeclareLaunchArgument(
'background_b', default_value=TextSubstitution(text='122')
)
return LaunchDescription([
background_r_launch_arg,
background_g_launch_arg,
background_b_launch_arg,
Node(
package='turtlesim',
executable='turtlesim_node',
name='sim',
parameters=[{
'background_r': LaunchConfiguration('background_r'),
'background_g': LaunchConfiguration('background_g'),
'background_b': LaunchConfiguration('background_b'),
}]
),
])
This launch file starts the turtlesim_node node, which starts the turtlesim simulation, with simulation
configuration parameters that are defined and passed to the nodes.
2.2 Loading parameters from YAML file
In the second launch, we will start a second turtlesim simulation with a different configuration. Now
create a turtlesim_world_2.launch.py file.
import os
def generate_launch_description():
config = os.path.join(
get_package_share_directory('launch_tutorial'),
'config',
'turtlesim.yaml'
)
return LaunchDescription([
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtlesim2',
name='sim',
parameters=[config]
)
])
This launch file will launch the same turtlesim_node with parameter values that are loaded directly
from the YAML configuration file. Defining arguments and parameters in YAML files make it easy
to store and load a large number of variables. In addition, YAML files can be easily exported from
the current ros2 param list. To learn how to do that, refer to the Understanding ROS 2
parameters tutorial.
Let’s now create a configuration file, turtlesim.yaml , in the /config folder of our package, which will
be loaded by our launch file.
/turtlesim2/sim:
ros__parameters:
background_b: 255
background_g: 86
background_r: 150
If we now start the turtlesim_world_2.launch.py launch file, we will start the turtlesim_node with
preconfigured background colors.
To learn more about using parameters and using YAML files, take a look at the Understanding
ROS 2 parameters tutorial.
2.3 Using wildcards in YAML files
There are cases when we want to set the same parameters in more than one node. These nodes
could have different namespaces or names but still have the same parameters. Defining separate
YAML files that explicitly define namespaces and node names is not efficient. A solution is to use
wildcard characters, which act as substitutions for unknown characters in a text value, to apply
parameters to several different nodes.
Now let’s create a new turtlesim_world_3.launch.py file similar to turtlesim_world_2.launch.py to
include one more turtlesim_node node.
...
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtlesim3',
name='sim',
parameters=[config]
)
Loading the same YAML file, however, will not affect the appearance of the third turtlesim world.
The reason is that its parameters are stored under another namespace as shown below:
/turtlesim3/sim:
background_b
background_g
background_r
Therefore, instead of creating a new configuration for the same node that use the same
parameters, we can use wildcards syntax. /** will assign all the parameters in every node,
despite differences in node names and namespaces.
We will now update the turtlesim.yaml , in the /config folder in the following manner:
/**:
ros__parameters:
background_b: 255
background_g: 86
background_r: 150
Now include the turtlesim_world_3.launch.py launch description in our main launch file. Using that
configuration file in our launch descriptions will assign background_b , background_g ,
and background_r parameters to specified values in turtlesim3/sim and turtlesim2/sim nodes.
3 Namespaces
As you may have noticed, we have defined the namespace for the turlesim world in
the turtlesim_world_2.launch.py file. Unique namespaces allow the system to start two similar nodes
without node name or topic name conflicts.
namespace='turtlesim2',
However, if the launch file contains a large number of nodes, defining namespaces for each of
them can become tedious. To solve that issue, the PushRosNamespace action can be used to define
the global namespace for each launch file description. Every nested node will inherit that
namespace automatically.
To do that, firstly, we need to remove the namespace='turtlesim2' line from
the turtlesim_world_2.launch.py file. Afterwards, we need to update the launch_turtlesim.launch.py to
include the following lines:
from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace
...
turtlesim_world_2 = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/turtlesim_world_2.launch.py'])
)
turtlesim_world_2_with_namespace = GroupAction(
actions=[
PushRosNamespace('turtlesim2'),
turtlesim_world_2,
]
)
def generate_launch_description():
return LaunchDescription([
DeclareLaunchArgument(
'target_frame', default_value='turtle1',
description='Target frame name.'
),
Node(
package='turtle_tf2_py',
executable='turtle_tf2_broadcaster',
name='broadcaster1',
parameters=[
{'turtlename': 'turtle1'}
]
),
Node(
package='turtle_tf2_py',
executable='turtle_tf2_broadcaster',
name='broadcaster2',
parameters=[
{'turtlename': 'turtle2'}
]
),
Node(
package='turtle_tf2_py',
executable='turtle_tf2_listener',
name='listener',
parameters=[
{'target_frame': LaunchConfiguration('target_frame')}
]
),
])
In this file, we have declared the target_frame launch argument with a default value of turtle1 . The
default value means that the launch file can receive an argument to forward to its nodes, or in
case the argument is not provided, it will pass the default value to its nodes.
Afterwards, we use the turtle_tf2_broadcaster node two times using different names and
parameters during launch. This allows us to duplicate the same node without conflicts.
We also start a turtle_tf2_listener node and set its target_frame parameter that we declared and
acquired above.
5 Parameter overrides
Recall that we called the broadcaster_listener.launch.py file in our top-level launch file. In addition to
that, we have passed it target_frame launch argument as shown below:
broadcaster_listener_nodes = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/broadcaster_listener.launch.py']),
launch_arguments={'target_frame': 'carrot1'}.items(),
)
This syntax allows us to change the default goal target frame to carrot1 . If you would
like turtle2 to follow turtle1 instead of the carrot1 , just remove the line that
defines launch_arguments . This will assign target_frame its default value, which is turtle1 .
6 Remapping
Now create a mimic.launch.py file.
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='turtlesim',
executable='mimic',
name='mimic',
remappings=[
('/input/pose', '/turtle2/pose'),
('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
]
)
])
This launch file will start the mimic node, which will give commands to one turtlesim to follow the
other. The node is designed to receive the target pose on the topic /input/pose . In our case, we
want to remap the target pose from /turtle2/pose topic. Finally, we remap the /output/cmd_vel topic
to /turtlesim2/turtle1/cmd_vel . This way turtle1 in our turtlesim2 simulation world will
follow turtle2 in our initial turtlesim world.
7 Config files
Let’s now create a file called turtlesim_rviz.launch.py .
import os
def generate_launch_description():
rviz_config = os.path.join(
get_package_share_directory('turtle_tf2_py'),
'rviz',
'turtle_rviz.rviz'
)
return LaunchDescription([
Node(
package='rviz2',
executable='rviz2',
name='rviz2',
arguments=['-d', rviz_config]
)
])
This launch file will start the RViz with the configuration file defined in the turtle_tf2_py package.
This RViz configuration will set the world frame, enable TF visualization, and start RViz with a top-
down view.
8 Environment Variables
Let’s now create the last launch file called fixed_broadcaster.launch.py in our package.
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import EnvironmentVariable, LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
DeclareLaunchArgument(
'node_prefix',
default_value=[EnvironmentVariable('USER'), '_'],
description='prefix for node name'
),
Node(
package='turtle_tf2_py',
executable='fixed_frame_tf2_broadcaster',
name=[LaunchConfiguration('node_prefix'), 'fixed_broadcaster'],
),
])
This launch file shows the way environment variables can be called inside the launch files.
Environment variables can be used to define or push namespaces for distinguishing nodes on
different computers or robots.
Running launch files
1 Update setup.py
Open setup.py and add the following lines so that the launch files from the launch/ folder and
configuration file from the config/ would be installed. The data_files field should now look like
this:
data_files=[
...
(os.path.join('share', package_name, 'launch'),
glob(os.path.join('launch', '*.launch.py'))),
(os.path.join('share', package_name, 'config'),
glob(os.path.join('config', '*.yaml'))),
],
You will now see the two turtlesim simulations started. There are two turtles in the first one and
one in the second one. In the first simulation, turtle2 is spawned in the bottom-left part of the
world. Its aim is to reach the carrot1 frame which is five meters away on the x-axis relative to
the turtle1 frame.
The turtlesim2/turtle1 in the second is designed to mimic the behavior of the turtle2 .
If you want to control the turtle1 , run the teleop node.
ros2 run turtlesim turtle_teleop_key
As a result, you will see a similar picture:
In addition to that, the RViz should have started. It will show all turtle frames relative to
the world frame, whose origin is at the bottom-left corner.
Summary
In this tutorial, you learned about various tips and practices of managing large projects using ROS
2 launch files
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
def generate_launch_description():
colors = {
'background_r': '200'
}
return LaunchDescription([
IncludeLaunchDescription(
PythonLaunchDescriptionSource([
PathJoinSubstitution([
FindPackageShare('launch_tutorial'),
'launch',
'example_substitutions.launch.py'
])
]),
launch_arguments={
'turtlesim_ns': 'turtlesim2',
'use_provided_red': 'True',
'new_background_r': TextSubstitution(text=str(colors['background_r']))
}.items()
)
])
In the example_main.launch.py file, the FindPackageShare substitution is used to find the path to
the launch_tutorial package. The PathJoinSubstitution substitution is then used to join the path to
that package path with the example_substitutions.launch.py file name.
PathJoinSubstitution([
FindPackageShare('launch_tutorial'),
'launch',
'example_substitutions.launch.py'
])
def generate_launch_description():
turtlesim_ns = LaunchConfiguration('turtlesim_ns')
use_provided_red = LaunchConfiguration('use_provided_red')
new_background_r = LaunchConfiguration('new_background_r')
turtlesim_ns_launch_arg = DeclareLaunchArgument(
'turtlesim_ns',
default_value='turtlesim1'
)
use_provided_red_launch_arg = DeclareLaunchArgument(
'use_provided_red',
default_value='False'
)
new_background_r_launch_arg = DeclareLaunchArgument(
'new_background_r',
default_value='200'
)
turtlesim_node = Node(
package='turtlesim',
namespace=turtlesim_ns,
executable='turtlesim_node',
name='sim'
)
spawn_turtle = ExecuteProcess(
cmd=[[
'ros2 service call ',
turtlesim_ns,
'/spawn ',
'turtlesim/srv/Spawn ',
'"{x: 2, y: 2, theta: 0.2}"'
]],
shell=True
)
change_background_r = ExecuteProcess(
cmd=[[
'ros2 param set ',
turtlesim_ns,
'/sim background_r ',
'120'
]],
shell=True
)
change_background_r_conditioned = ExecuteProcess(
condition=IfCondition(
PythonExpression([
new_background_r,
' == 200',
' and ',
use_provided_red
])
),
cmd=[[
'ros2 param set ',
turtlesim_ns,
'/sim background_r ',
new_background_r
]],
shell=True
)
return LaunchDescription([
turtlesim_ns_launch_arg,
use_provided_red_launch_arg,
new_background_r_launch_arg,
turtlesim_node,
spawn_turtle,
change_background_r,
TimerAction(
period=2.0,
actions=[change_background_r_conditioned],
)
])
turtlesim_ns_launch_arg = DeclareLaunchArgument(
'turtlesim_ns',
default_value='turtlesim1'
)
use_provided_red_launch_arg = DeclareLaunchArgument(
'use_provided_red',
default_value='False'
)
new_background_r_launch_arg = DeclareLaunchArgument(
'new_background_r',
default_value='200'
)
The turtlesim_node node with the namespace set to turtlesim_ns LaunchConfiguration substitution is
defined.
turtlesim_node = Node(
package='turtlesim',
namespace=turtlesim_ns,
executable='turtlesim_node',
name='sim'
)
Afterwards, the ExecuteProcess action called spawn_turtle is defined with the
corresponding cmd argument. This command makes a call to the spawn service of the turtlesim
node.
Additionally, the LaunchConfiguration substitution is used to get the value of the turtlesim_ns launch
argument to construct a command string.
spawn_turtle = ExecuteProcess(
cmd=[[
'ros2 service call ',
turtlesim_ns,
'/spawn ',
'turtlesim/srv/Spawn ',
'"{x: 2, y: 2, theta: 0.2}"'
]],
shell=True
)
The same approach is used for the change_background_r and change_background_r_conditioned actions
that change the turtlesim background’s red color parameter. The difference is that
the change_background_r_conditioned action is only executed if the
provided new_background_r argument equals 200 and the use_provided_red launch argument is set
to True . The evaluation inside the IfCondition is done using the PythonExpression substitution.
change_background_r = ExecuteProcess(
cmd=[[
'ros2 param set ',
turtlesim_ns,
'/sim background_r ',
'120'
]],
shell=True
)
change_background_r_conditioned = ExecuteProcess(
condition=IfCondition(
PythonExpression([
new_background_r,
' == 200',
' and ',
use_provided_red
])
),
cmd=[[
'ros2 param set ',
turtlesim_ns,
'/sim background_r ',
new_background_r
]],
shell=True
)
Launching example
Now you can launch the example_main.launch.py file using the ros2 launch command.
ros2 launch launch_tutorial example_main.launch.py
This will show the arguments that may be given to the launch file and their default values.
Arguments (pass arguments as '<name>:=<value>'):
'turtlesim_ns':
no description given
(default: 'turtlesim1')
'use_provided_red':
no description given
(default: 'False')
'new_background_r':
no description given
(default: '200')
Now you can pass the desired arguments to the launch file as follows:
ros2 launch launch_tutorial example_substitutions.launch.py turtlesim_ns:='turtlesim3'
use_provided_red:='True' new_background_r:=200
Documentation
The launch documentation provides detailed information about available substitutions.
Summary
In this tutorial, you learned about using substitutions in launch files. You learned about their
possibilities and capabilities to create reusable launch files.
You can now learn more about using event handlers in launch files which are used to define a
complex set of rules which can be used to dynamically modify the launch file.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
def generate_launch_description():
turtlesim_ns = LaunchConfiguration('turtlesim_ns')
use_provided_red = LaunchConfiguration('use_provided_red')
new_background_r = LaunchConfiguration('new_background_r')
turtlesim_ns_launch_arg = DeclareLaunchArgument(
'turtlesim_ns',
default_value='turtlesim1'
)
use_provided_red_launch_arg = DeclareLaunchArgument(
'use_provided_red',
default_value='False'
)
new_background_r_launch_arg = DeclareLaunchArgument(
'new_background_r',
default_value='200'
)
turtlesim_node = Node(
package='turtlesim',
namespace=turtlesim_ns,
executable='turtlesim_node',
name='sim'
)
spawn_turtle = ExecuteProcess(
cmd=[[
FindExecutable(name='ros2'),
' service call ',
turtlesim_ns,
'/spawn ',
'turtlesim/srv/Spawn ',
'"{x: 2, y: 2, theta: 0.2}"'
]],
shell=True
)
change_background_r = ExecuteProcess(
cmd=[[
FindExecutable(name='ros2'),
' param set ',
turtlesim_ns,
'/sim background_r ',
'120'
]],
shell=True
)
change_background_r_conditioned = ExecuteProcess(
condition=IfCondition(
PythonExpression([
new_background_r,
' == 200',
' and ',
use_provided_red
])
),
cmd=[[
FindExecutable(name='ros2'),
' param set ',
turtlesim_ns,
'/sim background_r ',
new_background_r
]],
shell=True
)
return LaunchDescription([
turtlesim_ns_launch_arg,
use_provided_red_launch_arg,
new_background_r_launch_arg,
turtlesim_node,
RegisterEventHandler(
OnProcessStart(
target_action=turtlesim_node,
on_start=[
LogInfo(msg='Turtlesim started, spawning turtle'),
spawn_turtle
]
)
),
RegisterEventHandler(
OnProcessIO(
target_action=spawn_turtle,
on_stdout=lambda event: LogInfo(
msg='Spawn request says "{}"'.format(
event.text.decode().strip())
)
)
),
RegisterEventHandler(
OnExecutionComplete(
target_action=spawn_turtle,
on_completion=[
LogInfo(msg='Spawn finished'),
change_background_r,
TimerAction(
period=2.0,
actions=[change_background_r_conditioned],
)
]
)
),
RegisterEventHandler(
OnProcessExit(
target_action=turtlesim_node,
on_exit=[
LogInfo(msg=(EnvironmentVariable(name='USER'),
' closed the turtlesim window')),
EmitEvent(event=Shutdown(
reason='Window closed'))
]
)
),
RegisterEventHandler(
OnShutdown(
on_shutdown=[LogInfo(
msg=['Launch was asked to shutdown: ',
LocalSubstitution('event.reason')]
)]
)
),
])
The OnProcessIO event handler is used to register a callback function that is executed when
the spawn_turtle action writes to its standard output. It logs the result of the spawn request.
RegisterEventHandler(
OnProcessIO(
target_action=spawn_turtle,
on_stdout=lambda event: LogInfo(
msg='Spawn request says "{}"'.format(
event.text.decode().strip())
)
)
),
The OnExecutionComplete event handler is used to register a callback function that is executed when
the spawn_turtle action completes. It logs a message to the console and executes
the change_background_r and change_background_r_conditioned actions when the spawn action
completes.
RegisterEventHandler(
OnExecutionComplete(
target_action=spawn_turtle,
on_completion=[
LogInfo(msg='Spawn finished'),
change_background_r,
TimerAction(
period=2.0,
actions=[change_background_r_conditioned],
)
]
)
),
The OnProcessExit event handler is used to register a callback function that is executed when the
turtlesim node exits. It logs a message to the console and executes the EmitEvent action to emit
a Shutdown event when the turtlesim node exits. It means that the launch process will shutdown
when the turtlesim window is closed.
RegisterEventHandler(
OnProcessExit(
target_action=turtlesim_node,
on_exit=[
LogInfo(msg=(EnvironmentVariable(name='USER'),
' closed the turtlesim window')),
EmitEvent(event=Shutdown(
reason='Window closed'))
]
)
),
Finally, the OnShutdown event handler is used to register a callback function that is executed when
the launch file is asked to shutdown. It logs a message to the console why the launch file is asked
to shutdown. It logs the message with a reason for shutdown like the closure of turtlesim window
or ctrl-c signal made by the user.
RegisterEventHandler(
OnShutdown(
on_shutdown=[LogInfo(
msg=['Launch was asked to shutdown: ',
LocalSubstitution('event.reason')]
)]
)
),
Launching example
Now you can launch the example_event_handlers.launch.py file using the ros2 launch command.
ros2 launch launch_tutorial example_event_handlers.launch.py turtlesim_ns:='turtlesim3'
use_provided_red:='True' new_background_r:=200
The command will return the unique ID of the loaded component as well as the node name.
Now the first shell should show a message that the component was loaded as well as repeated
message for publishing a message.
Run another command in the second shell to load the listener component (see listener source
code):
$ ros2 component load /ComponentManager composition composition::Listener
Loaded component 2 into '/ComponentManager' container node as '/listener'
The ros2 command line utility can now be used to inspect the state of the container:
$ ros2 component list
/ComponentManager
1 /talker
2 /listener
Now the first shell should show repeated output for each received message.
Run-time composition using ROS services with a server and client
The example with a server and a client is very similar.
In the first shell:
$ ros2 run rclcpp_components component_container
In this case the client sends a request to the server, the server processes the request and replies
with a response, and the client prints the received response.
Compile-time composition using ROS services
This demos shows that the same shared libraries can be reused to compile a single executable
running multiple components. The executable contains all four components from above: talker and
listener as well as server and client.
In the shell call (see source code):
$ ros2 run composition manual_composition
This should show repeated messages from both pairs, the talker and the listener as well as the
server and the client.
Note
Manually-composed components will not be reflected in the ros2 component list command line tool
output.
Run-time composition using dlopen
This demo presents an alternative to run-time composition by creating a generic container process
and explicitly passing the libraries to load without using ROS interfaces. The process will open
each library and create one instance of each “rclcpp::Node” class in the library source code).
LinuxmacOSWindows
$ ros2 run composition dlopen_composition `ros2 pkg prefix composition`/lib/libtalker_component.so `ros2
pkg prefix composition`/lib/liblistener_component.so
Now the shell should show repeated output for each sent and received message.
Note
dlopen-composed components will not be reflected in the ros2 component list command line tool
output.
Composition using launch actions
While the command line tools are useful for debugging and diagnosing component configurations,
it is frequently more convenient to start a set of components at the same time. To automate this
action, we can use the functionality in ros2 launch .
$ ros2 launch composition composition_demo.launch.py
Advanced Topics
Now that we have seen the basic operation of components, we can discuss a few more advanced
topics.
Unloading components
In the first shell, start the component container:
$ ros2 run rclcpp_components component_container
Verify that the container is running via ros2 command line tools:
$ ros2 component list
/ComponentManager
In the second shell load both the talker and listener as we have before:
$ ros2 component load /ComponentManager composition composition::Talker
Loaded component 1 into '/ComponentManager' container node as '/talker'
$ ros2 component load /ComponentManager composition composition::Listener
Loaded component 2 into '/ComponentManager' container node as '/listener'
Use the unique ID to unload the node from the component container.
$ ros2 component unload /ComponentManager 1 2
Unloaded component 1 from '/ComponentManager' container
Unloaded component 2 from '/ComponentManager' container
In the first shell, verify that the repeated messages from talker and listener have stopped.
Remapping container name and namespace
The component manager name and namespace can be remapped via standard command line
arguments:
$ ros2 run rclcpp_components component_container --ros-args -r __node:=MyContainer -r __ns:=/ns
In a second shell, components can be loaded by using the updated container name:
$ ros2 component load /ns/MyContainer composition composition::Listener
Note
Namespace remappings of the container do not affect loaded components.
Remap component names and namespaces
Component names and namespaces may be adjusted via arguments to the load command.
In the first shell, start the component container:
$ ros2 run rclcpp_components component_container
Note
Namespace remappings of the container do not affect loaded components.
Composable nodes as shared libraries
If you want to export a composable node as a shared library from a package and use that node in
another package that does link-time composition, add code to the CMake file which imports the
actual targets in downstream packages.
Then install the generated file and export the generated file.
A practical example can be seen here: ROS Discourse - Ament best practice for sharing libraries
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Using colcon to build packages
Table of Contents
Background
Prerequisites
Install colcon
Install ROS 2
o Basics
Create a workspace
Add some sources
Source an underlay
Build the workspace
Run tests
Source the environment
Try a demo
o Create your own package
o Setup colcon_cd
o Setup colcon tab completion
o Tips
This is a brief tutorial of how to create and build a ROS 2 workspace with colcon . It is a practical
tutorial and not designed to replace the core documentation.
Background
colcon is an iteration on the ROS build
tools catkin_make , catkin_make_isolated , catkin_tools and ament_tools . For more information on the
design of colcon see this document.
The source code can be found in the colcon GitHub organization.
Prerequisites
Install colcon
LinuxmacOSWindows
sudo apt install python3-colcon-common-extensions
Install ROS 2
To build the samples, you will need to install ROS 2.
Follow the installation instructions.
Attention
If installing from Debian packages, this tutorial requires the desktop installation.
Basics
A ROS workspace is a directory with a particular structure. Commonly there is a src subdirectory.
Inside that subdirectory is where the source code of ROS packages will be located. Typically the
directory starts otherwise empty.
colcon does out of source builds. By default it will create the following directories as peers of
the src directory:
The build directory will be where intermediate files are stored. For each package a
subfolder will be created in which e.g. CMake is being invoked.
The install directory is where each package will be installed to. By default each package
will be installed into a separate subdirectory.
The log directory contains various logging information about each colcon invocation.
Note
Compared to catkin there is no devel directory.
Create a workspace
First, create a directory ( ros2_example_ws ) to contain our workspace:
LinuxmacOSWindows
mkdir -p ~/ros2_example_ws/src
cd ~/ros2_example_ws
1 directory, 0 files
Now the workspace should have the source code to the ROS 2 examples:
.
└── src
└── examples
├── CONTRIBUTING.md
├── LICENSE
├── rclcpp
├── rclpy
└── README.md
4 directories, 3 files
Source an underlay
It is important that we have sourced the environment for an existing ROS 2 installation that will
provide our workspace with the necessary build dependencies for the example packages. This is
achieved by sourcing the setup script provided by a binary installation or a source installation, ie.
another colcon workspace (see Installation). We call this environment an underlay.
Our workspace, ros2_examples_ws , will be an overlay on top of the existing ROS 2 installation. In
general, it is recommended to use an overlay when you plan to iterate on a small number of
packages, rather than putting all of your packages into the same workspace.
Build the workspace
Attention
To build packages on Windows you need to be in a Visual Studio environment, see Building the
ROS 2 Code for more details.
In the root of the workspace, run colcon build . Since build types such as ament_cmake do not
support the concept of the devel space and require the package to be installed, colcon supports
the option --symlink-install . This allows the installed files to be changed by changing the files in
the source space (e.g. Python files or other not compiled resourced) for faster iteration.
LinuxmacOSWindows
colcon build --symlink-install
After the build is finished, we should see the build , install , and log directories:
.
├── build
├── install
├── log
└── src
4 directories, 0 files
Run tests
To run tests for the packages we just built, run the following:
LinuxmacOSWindows
colcon test
Try a demo
With the environment sourced we can run executables built by colcon. Let’s run a subscriber node
from the examples:
ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function
In another terminal, let’s run a publisher node (don’t forget to source the setup script):
ros2 run examples_rclcpp_minimal_publisher publisher_member_function
You should see messages from the publisher and subscriber with numbers incrementing.
Create your own package
colcon uses the package.xml specification defined in REP 149 (format 2 is also supported).
colcon supports multiple build types. The recommended build types
are ament_cmake and ament_python . Also supported are pure cmake packages.
An example of an ament_python build is the ament_index_python package , where the setup.py is
the primary entry point for building.
A package such as demo_nodes_cpp uses the ament_cmake build type, and uses CMake as the
build tool.
For convenience, you can use the tool ros2 pkg create to create a new package based on a
template.
Note
For catkin users, this is the equivalent of catkin_create_package .
Setup colcon_cd
The command colcon_cd allows you to quickly change the current working directory of your shell to
the directory of a package. As an example colcon_cd some_ros_package would quickly bring you to the
directory ~/ros2_install/src/some_ros_package .
LinuxmacOSWindows
echo "source /usr/share/colcon_cd/function/colcon_cd.sh" >> ~/.bashrc
echo "export _colcon_cd_root=/opt/ros/galactic/" >> ~/.bashrc
Depending to the way you installed colcon_cd and where your workspace is, the instructions above
may vary, please refer to the documentation for more details. To undo this in Linux and macOS,
locate your system’s shell startup script and remove the appended source and export commands.
Setup colcon tab completion
The command colcon supports command completion for bash and bash-like shells if the colcon-
argcomplete package is installed.
LinuxmacOSWindows
echo "source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash" >> ~/.bashrc
Depending to the way you installed colcon and where your workspace is, the instructions above
may vary, please refer to the documentation for more details. To undo this in Linux and macOS,
locate your system’s shell startup script and remove the appended source command.
Tips
If you do not want to build a specific package place an empty file named COLCON_IGNORE in
the directory and it will not be indexed.
If you want to avoid configuring and building tests in CMake packages you can pass: --
cmake-args -DBUILD_TESTING=0 .
#include "rclcpp/rclcpp.hpp"
return 0;
}
Following the SampleNodeWithParameters is a typical main function which initializes ROS, spins the
sample node so that it can send and receive messages, and then shuts down after the user enters
^C at the console.
int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<SampleNodeWithParameters>());
rclcpp::shutdown();
return 0;
}
2.2 Add executable
To build this code, first open the CMakeLists.txt file and add the following lines of code below the
dependency find_package(rclcpp REQUIRED)
add_executable(parameter_event_handler src/parameter_event_handler.cpp)
ament_target_dependencies(parameter_event_handler rclcpp)
install(TARGETS
parameter_event_handler
DESTINATION lib/${PROJECT_NAME}
)
Navigate back to the root of your workspace, dev_ws , and build your new package:
colcon build --packages-select cpp_parameter_event_handler
Open a new terminal, navigate to dev_ws , and source the setup files:
LinuxmacOSWindows
. install/setup.bash
The node is now active and has a single parameter and will print a message whenever this
parameter is updated. To test this, open up another terminal and source the ROS setup file as
before (. install/setup.bash) and execute the following command:
ros2 param set node_with_parameters an_int_param 43
The terminal running the node will display a message similar to the following:
[INFO] [1606950498.422461764] [node_with_parameters]: cb: Received an update to parameter "an_int_param"
of type integer: "43"
The callback we set previously in the node has been invoked and has displayed the new updated
value. You can now terminate the running parameter_event_handler sample using ^C in the
terminal.
3.1 Monitor changes to another node’s parameters
You can also use the ParameterEventHandler to monitor parameter changes to another node’s
parameters. Let’s update the SampleNodeWithParameters class to also monitor for changes to a
parameter in another node. We will use the parameter_blackboard demo application to host a
double parameter that we will monitor for updates.
First update the constructor to add the following code after the existing code:
// Now, add a callback to monitor any changes to the remote node's parameter. In this
// case, we supply the remote node name.
auto cb2 = [this](const rclcpp::Parameter & p) {
RCLCPP_INFO(
this->get_logger(), "cb2: Received an update to parameter \"%s\" of type: %s: \"%.02lf\"",
p.get_name().c_str(),
p.get_type_name().c_str(),
p.as_double());
};
auto remote_node_name = std::string("parameter_blackboard");
auto remote_param_name = std::string("a_double_param");
cb_handle2_ = param_subscriber_->add_parameter_callback(remote_param_name, cb2, remote_node_name);
Then add another member variable, cb_handle2 for the additional callback handle:
private:
std::shared_ptr<rclcpp::ParameterEventHandler> param_subscriber_;
std::shared_ptr<rclcpp::ParameterCallbackHandle> cb_handle_;
std::shared_ptr<rclcpp::ParameterCallbackHandle> cb_handle2_; // Add this
};
In a terminal, navigate back to the root of your workspace, dev_ws , and build your updated
package as before:
colcon build --packages-select cpp_parameter_event_handler
Now, to test monitoring of remote parameters, first run the newly-built parameter_event_handler
code:
ros2 run cpp_parameter_event_handler parameter_event_handler
Next, from another teminal (with ROS initialized), run the parameter_blackboard demo application,
as follows:
ros2 run demo_nodes_cpp parameter_blackboard
Finally, from a third terminal (with ROS initialized), let’s set a parameter on the
parameter_blackboard node:
ros2 param set parameter_blackboard a_double_param 3.45
Upon executing this command, you should see output in the parameter_event_handler window,
indicating that the callback function was invoked upon the parameter update:
[INFO] [1606952588.237531933] [node_with_parameters]: cb2: Received an update to parameter
"a_double_param" of type: double: "3.45"
Summary
You created a node with a parameter and used the ParameterEventHandler class to set a callback
to monitor changes to that parameter. You also used the same class to monitor changes to a
remote node. The ParameterEventHandler is a convenient way to monitor for parameter changes
so that you can then respond to the updated values.
Related content
To learn how to adapt ROS 1 parameter files for ROS 2, see the Migrating YAML parameter files
from ROS 1 to ROS2 tutorial.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%