05 ROS2 Launch

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 34

Launching/monitoring multiple nodes with

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}/
)

to the end of the file (but before  ament_package() ).


Writing the launch file
Inside your launch directory, create a new launch file with the  .launch.py  suffix. For
example  my_script.launch.py .
.launch.py  is not specifically required as the file suffix for launch files. Another popular option

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

Example of ROS 2 launch concepts


The launch file in this example launches two nodes, one of which is a node with a managed
lifecycle (a “lifecycle node”). Lifecycle nodes launched through  launch_ros  automatically
emit events when they transition between states. The events can then be acted on through the
launch framework. For example, by emitting other events (such as requesting another state
transition, which lifecycle nodes launched through  launch_ros  automatically have event handlers
for) or triggering other actions (e.g. starting another node).
In the aforementioned example, various transition requests are requested of the  talker  lifecycle
node, and its transition events are reacted to by, for example, launching a  listener  node when the
lifecycle talker reaches the appropriate state.
Documentation
The launch documentation provides more details on concepts that are also used in  launch_ros .
Additional documentation/examples of launch capabilities are forthcoming. See the source code in
the meantime
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Using ROS 2 launch for large projects


Goal: Learn best practices of managing large projects using ROS 2 launch files
Tutorial level: Intermediate
Time: 20 minutes
Contents
 Background
 Prerequisites
 Introduction
 Writing launch files
 1 Top-level organization
 2 Parameters
 2.1 Setting parameters in the launch file
 2.2 Loading parameters from YAML file
 2.3 Using wildcards in YAML files
 3 Namespaces
 4 Reusing nodes
 5 Parameter overrides
 6 Remapping
 7 Config files
 8 Environment Variables
o Running launch files
 1 Update setup.py
 2 Build and run
o Summary
Background
This tutorial describes some tips for writing launch files for large projects. The focus is on how to
structure launch files so they may be reused as much as possible in different situations.
Additionally, it covers usage examples of different ROS 2 launch tools, like parameters, YAML
files, remappings, namespaces, default arguments, and RViz configs.
Prerequisites
This tutorial uses the turtlesim and turtle_tf2_py packages. This tutorial also assumes you
have created a new package of build type  ament_python  called  launch_tutorial .
Introduction
Large applications on a robot typically involve several interconnected nodes, each of which can
have many parameters. Simulation of multiple turtles in the turtle simulator can serve as a good
example. The turtle simulation consists of multiple turtle nodes, the world configuration, and the TF
broadcaster and listener nodes. Between all of the nodes, there are a large number of ROS
parameters that affect the behavior and appearance of these nodes. ROS 2 launch files allow us
to start all nodes and set corresponding parameters in one place. By the end of a tutorial, you will
build the  launch_turtlesim.launch.py  launch file in the  launch_tutorial  package. This launch file will
bring up different nodes responsible for the simulation of two turtlesim simulations, starting TF
broadcasters and listener, loading parameters, and launching an RViz configuration. In this
tutorial, we’ll go over this launch file and all related features used.
Writing launch files
1 Top-level organization
One of the aims in the process of writing launch files should be making them as reusable as
possible. This could be done by clustering related nodes and configurations into separate launch
files. Afterwards, a top-level launch file dedicated to a specific configuration could be written. This
would allow moving between identical robots to be done without changing the launch files at all.
Even a change such as moving from a real robot to a simulated one can be done with only a few
changes.
We will now go over the top-level launch file structure that makes this possible. Firstly, we will
create a launch file that will call separate launch files. To do this, let’s create
a  launch_turtlesim.launch.py  file in the  /launch  folder of our  launch_tutorial  package.
import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription


from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource

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

from launch_ros.actions import Node

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

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription


from launch_ros.actions import Node

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,
]
)

Finally, we replace the  turtlesim_world_2  to  turtlesim_world_2_with_namespace  in


the  return LaunchDescription  statement. As a result, each node in
the  turtlesim_world_2.launch.py  launch description will have a  turtlesim2  namespace.
4 Reusing nodes
Now create a  broadcaster_listener.launch.py  file.
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration

from launch_ros.actions import Node

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

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription


from launch_ros.actions import Node

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'))),
],

2 Build and run


To finally see the result of our code, build the package and launch the top-level launch file using
the following command:
ros2 launch launch_tutorial launch_turtlesim.launch.py

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
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Using substitutions in launch files


Goal: Learn about substitutions in ROS 2 launch files
Tutorial level: Advanced
Time: 15 minutes
Table of Contents
 Background
 Prerequisites
 Using substitutions
 Launching example
 Modifying launch arguments
 Documentation
 Summary
Background
Launch files are used to start nodes, services and execute processes. This set of actions may
have arguments, which affect their behavior. Substitutions can be used in arguments to provide
more flexibility when describing reusable launch files. Substitutions are variables that are only
evaluated during execution of the launch description and can be used to acquire specific
information like a launch configuration, an environment variable, or to evaluate an arbitrary Python
expression.
This tutorial shows usage examples of substitutions in ROS 2 launch files.
Prerequisites
This tutorial uses the turtlesim package. This tutorial also assumes you have created a new
package of build type  ament_python  called  launch_tutorial .
Using substitutions
1 Parent launch file
Firstly, we will create a launch file that will call and pass arguments to another launch file. To do
this, create an  example_main.launch.py  file in the  /launch  folder of the  launch_tutorial  package.
from launch_ros.substitutions import FindPackageShare

from launch import LaunchDescription


from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import PathJoinSubstitution, TextSubstitution

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'
])

The  launch_arguments  dictionary with  turtlesim_ns  and  use_provided_red  arguments is passed to


the  IncludeLaunchDescription  action. The  TextSubstitution  substitution is used to define
the  new_background_r  argument with the value of the  background_r  key in the  colors  dictionary.
launch_arguments={
'turtlesim_ns': 'turtlesim2',
'use_provided_red': 'True',
'new_background_r': TextSubstitution(text=str(colors['background_r']))
}.items()

2 Substitutions example launch file


Now create an  example_substitutions.launch.py  file in the same folder.
from launch_ros.actions import Node

from launch import LaunchDescription


from launch.actions import DeclareLaunchArgument, ExecuteProcess, TimerAction
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration, PythonExpression

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],
)
])

In the  example_substitutions.launch.py  file,  turtlesim_ns ,  use_provided_red ,


and  new_background_r  launch configurations are defined. They are used to store values of launch
arguments in the above variables and to pass them to required actions.
These  LaunchConfiguration  substitutions allow us to acquire the value of the launch argument in any
part of the launch description.
DeclareLaunchArgument  is used to define the launch argument that can be passed from the above

launch file or from the console.


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'
)

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 do the following:


1. Start a turtlesim node with a blue background
2. Spawn the second turtle
3. Change the color to purple
4. Change the color to pink after two seconds if the provided  background_r  argument
is  200  and  use_provided_red  argument is  True
Modifying launch arguments
If you want to change the provided launch arguments, you can either update them
in  launch_arguments  dictionary in the  example_main.launch.py  or launch
the  example_substitutions.launch.py  with preferred arguments. To see arguments that may be given
to the launch file, run the following command:
ros2 launch launch_tutorial example_substitutions.launch.py --show-args

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.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Using event handlers in launch files


Goal: Learn about event handlers in ROS 2 launch files
Tutorial level: Advanced
Time: 15 minutes
Table of Contents
 Background
 Prerequisites
 Using event handlers
 Launching example
 Documentation
 Summary
Background
Launch in ROS 2 is a system that executes and manages user-defined processes. It is
responsible for monitoring the state of processes it launched, as well as reporting and reacting to
changes in the state of those processes. These changes are called events and can be handled by
registering an event handler with the launch system. Event handlers can be registered for specific
events and can be useful for monitoring the state of processes. Additionally, they can be used to
define a complex set of rules which can be used to dynamically modify the launch file.
This tutorial shows usage examples of event handlers in ROS 2 launch files.
Prerequisites
This tutorial uses the turtlesim package. This tutorial also assumes you have created a new
package of build type  ament_python  called  launch_tutorial .
This tutorial extends the code shown in the Using substitutions in launch files tutorial.
Using event handlers
1 Event hanlders example launch file
Create a new file called  example_event_handlers.launch.py  file in the  /launch  folder of
the  launch_tutorial  package.
from launch_ros.actions import Node

from launch import LaunchDescription


from launch.actions import (DeclareLaunchArgument, EmitEvent, ExecuteProcess,
LogInfo, RegisterEventHandler, TimerAction)
from launch.conditions import IfCondition
from launch.event_handlers import (OnExecutionComplete, OnProcessExit,
OnProcessIO, OnProcessStart, OnShutdown)
from launch.events import Shutdown
from launch.substitutions import (EnvironmentVariable, FindExecutable,
LaunchConfiguration, LocalSubstitution,
PythonExpression)

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')]
)]
)
),
])

RegisterEventHandler  actions for the  OnProcessStart ,  OnProcessIO ,  OnExecutionComplete ,  OnProcessExit ,


and  OnShutdown  events were defined in the launch description.
The  OnProcessStart  event handler is used to register a callback function that is executed when the
turtlesim node starts. It logs a message to the console and executes the  spawn_turtle  action when
the turtlesim node starts.
RegisterEventHandler(
OnProcessStart(
target_action=turtlesim_node,
on_start=[
LogInfo(msg='Turtlesim started, spawning turtle'),
spawn_turtle
]
)
),

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

This will do the following:


1. Start a turtlesim node with a blue background
2. Spawn the second turtle
3. Change the color to purple
4. Change the color to pink after two seconds if the provided  background_r  argument
is  200  and  use_provided_red  argument is  True
5. Shutdown the launch file when the turtlesim window is closed
Additionally, it will log messages to the console when:
1. The turtlesim node starts
2. The spawn action is executed
3. The  change_background_r  action is executed
4. The  change_background_r_conditioned  action is executed
5. The turtlesim node exits
6. The launch process is asked to shutdown.
Documentation
The launch documentation provides detailed information about available event handlers.
Summary
In this tutorial, you learned about using event hanlders in launch files. You learned about their
syntax and usage examples to define a complex set of rules to dynamically modify launch files.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Composing multiple nodes in a single process
Table of Contents
 Background
 Run the demos
 Discover available components
 Run-time composition using ROS services with a publisher and subscriber
 Run-time composition using ROS services with a server and client
 Compile-time composition using ROS services
 Run-time composition using dlopen
 Composition using launch actions
o Advanced Topics
 Unloading components
 Remapping container name and namespace
 Remap component names and namespaces
o Composable nodes as shared libraries
Background
See the conceptual article.
Run the demos
The demos use executables from rclcpp_components, ros2component,
and composition packages, and can be run with the following commands.
Discover available components
To see what components are registered and available in the workspace, execute the following in a
shell:
$ ros2 component types
(... components of other packages here)
composition
composition::Talker
composition::Listener
composition::NodeLikeListener
composition::Server
composition::Client
(... components of other packages here)

Run-time composition using ROS services with a publisher and


subscriber
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 the talker component (see talker source code):


$ ros2 component load /ComponentManager composition composition::Talker
Loaded component 1 into '/ComponentManager' container node as '/talker'

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 the second shell (see server and client source code):


$ ros2 component load /ComponentManager composition composition::Server
$ ros2 component load /ComponentManager composition composition::Client

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

Some examples of how to remap names and namespaces:


# Remap node name
$ ros2 component load /ComponentManager composition composition::Talker --node-name talker2
# Remap namespace
$ ros2 component load /ComponentManager composition composition::Talker --node-namespace /ns
# Remap both
$ ros2 component load /ComponentManager composition composition::Talker --node-name talker3 --node-
namespace /ns2

The corresponding entries appear in  ros2 component list :


$ ros2 component list
/ComponentManager
1 /talker2
2 /ns/talker
3 /ns2/talker3

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

At this point the workspace contains a single empty directory  src :


.
└── src

1 directory, 0 files

Add some sources


Let’s clone the examples repository into the  src  directory of the workspace:
git clone https://github.com/ros2/examples src/examples -b galactic

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

Source the environment


When colcon has completed building successfully, the output will be in the  install  directory.
Before you can use any of the installed executables or libraries, you will need to add them to your
path and library paths. colcon will have generated bash/bat files in the  install  directory to help
setup the environment. These files will add all of the required elements to your path and library
paths as well as provide any bash or shell commands exported by packages.
LinuxmacOSWindows
. install/setup.bash

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 .

 If you want to run a single particular test from a package:


colcon test --packages-select YOUR_PKG_NAME --ctest-args -R YOUR_TEST_IN_PKG
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Monitoring for parameter changes (C++)


Goal: Learn to use the ParameterEventHandler class to monitor and respond to parameter
changes.
Tutorial level: Beginner
Time: 20 minutes
Minimum Platform: Galactic
Contents
 Background
 Prerequisites
 Tasks
 1 Create a package
 2 Write the C++ node
 3 Build and run
o Summary
o Related content
Background
Often a node needs to respond to changes to its own parameters or another node’s parameters.
The ParameterEventHandler class makes it easy to listen for parameter changes so that your
code can respond to them. This tutorial will show you how to use the C++ version of the
ParameterEventHandler class to monitor for changes to a node’s own parameters as well as
changes to another node’s parameters.
Prerequisites
Before starting this tutorial, you should first complete the following tutorials:
 Understanding ROS 2 parameters
 Using parameters in a class (C++)
In addition, you must be running the Galactic distribution of ROS 2.
Tasks
In this tutorial, you will create a new package to contain some sample code, write some C++ code
to use the ParameterEventHandler class, and test the resulting code.
1 Create a package
First, open a new terminal and source your ROS 2 installation so that  ros2  commands will work.
Navigate into the  dev_ws  directory created in a previous tutorial (or follow these instructions if you
no longer have the directory and need to create it again).
Recall that packages should be created in the  src  directory, not the root of the workspace. So,
navigate into  dev_ws/src  and then create a new package there:
ros2 pkg create --build-type ament_cmake cpp_parameter_event_handler --dependencies rclcpp

Your terminal will return a message verifying the creation of your


package  cpp_parameter_event_handler  and all its necessary files and folders.
The  --dependencies  argument will automatically add the necessary dependency lines
to  package.xml  and  CMakeLists.txt .
1.1 Update  package.xml 
Because you used the  --dependencies  option during package creation, you don’t have to manually
add dependencies to  package.xml  or  CMakeLists.txt . As always, though, make sure to add the
description, maintainer email and name, and license information to  package.xml .
<description>C++ parameter events client tutorial</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>

2 Write the C++ node


Inside the  dev_ws/src/cpp_parameter_event_handler/src  directory, create a new file
called  parameter_event_handler.cpp  and paste the following code within:
#include <memory>

#include "rclcpp/rclcpp.hpp"

class SampleNodeWithParameters : public rclcpp::Node


{
public:
SampleNodeWithParameters()
: Node("node_with_parameters")
{
this->declare_parameter("an_int_param", 0);

// Create a parameter subscriber that can be used to monitor parameter changes


// (for this node's parameters as well as other nodes' parameters)
param_subscriber_ = std::make_shared<rclcpp::ParameterEventHandler>(this);

// Set a callback for this node's integer parameter, "an_int_param"


auto cb = [this](const rclcpp::Parameter & p) {
RCLCPP_INFO(
this->get_logger(), "cb: Received an update to parameter \"%s\" of type %s: \"%ld\"",
p.get_name().c_str(),
p.get_type_name().c_str(),
p.as_int());
};
cb_handle_ = param_subscriber_->add_parameter_callback("an_int_param", cb);
}
private:
std::shared_ptr<rclcpp::ParameterEventHandler> param_subscriber_;
std::shared_ptr<rclcpp::ParameterCallbackHandle> cb_handle_;
};

int main(int argc, char ** argv)


{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<SampleNodeWithParameters>());
rclcpp::shutdown();

return 0;
}

2.1 Examine the code


The first statement,  #include <memory>  is included so that the code can utilize the std::make_shared
template. The next,  #include "rclcpp/rclcpp.hpp"  is included to allow the code to reference the
various functionality provided by the rclcpp interface, including the ParameterEventHandler class.
After the class declaration, the code defines a class,  SampleNodeWithParameters . The constructor for
the class, declares an integer parameter  an_int_param , with a default value of 0. Next, the code
creates a  ParameterEventHandler  that will be used to monitor changes to parameters. Finally, the
code creates a lambda function and sets it as the callback to invoke whenever  an_int_param  is
updated.
SampleNodeWithParameters()
: Node("node_with_parameters")
{
this->declare_parameter("an_int_param", 0);

// Create a parameter subscriber that can be used to monitor parameter changes


// (for this node's parameters as well as other nodes' parameters)
param_subscriber_ = std::make_shared<rclcpp::ParameterEventHandler>(this);

// Set a callback for this node's integer parameter, "an_int_param"


auto cb = [this](const rclcpp::Parameter & p) {
RCLCPP_INFO(
this->get_logger(), "cb: Received an update to parameter \"%s\" of type %s: \"%ld\"",
p.get_name().c_str(),
p.get_type_name().c_str(),
p.as_int());
};
cb_handle_ = param_subscriber_->add_parameter_callback("an_int_param", cb);
}

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}
)

3 Build and run


It’s good practice to run  rosdep  in the root of your workspace ( dev_ws ) to check for missing
dependencies before building:
LinuxmacOSWindows
rosdep install -i --from-path src --rosdistro $ROS_DISTRO -y

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

Now run the node:


ros2 run cpp_parameter_event_handler parameter_event_handler

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

Then source the setup files:


LinuxmacOSWindows
. install/setup.bash

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.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

You might also like