Directory of series articles
ROS2 important concepts
ament_cmake_python user documentation
ROS2 ament_cmake user documentation
Managing ROS 2 dependencies using rosdep
Article directory
- Table of Contents of Series Articles
- Preface
- 1. Launch file example
-
- 1.1 Python version
- 1.2 XML version
- 1.3 YAML version
- 2. Use the Launch file from the command line
-
- 1. Launching
- 2. Set parameters
- 3. Control turtles
- 3. Python, XML or YAML: Which language should I use?
- 4. Why use ROS 2 Launch
Foreword
ROS 2 launch files can be written in Python, XML, and YAML. This guide explains how to use these different formats to accomplish the same task and discusses when to use each format.
1. Launch file example
Below is a Launch file implemented in Python, XML and YAML. Each Launch file does the following:
-
Set command line parameters with default values
-
Contains another launch file
-
Include another startup file in another namespace
-
Start the node and set its namespace
-
Start a node, set its namespace, and set parameters in the node (using parameters)
-
Create a node to remap messages from one topic to another
1.1 Python version
First, we will introduce the several classes
and methods
involved.
ament_index_python.get_package_share_path(package_name, print_warning=True)
Returns the shared directory of the given package as pathlib.Path. For example, if you install package "foo" to "/home/user/ros2_ws/install" and call this function with "foo" as argument, then it will return a value representing "/home /user/ros2_ws/install/share/foo", then you can use it to build the path to the shared file, i.e. get_package_share_path('foo') /'urdf/robot.urdf'
launch.LaunchDescription
Basics: LaunchDescriptionEntity A description of the bootable system. The description consists of a sequence of entities that represent the system designer's intent. The description may also have parameters, declared by the launch.actions.DeclareLaunchArgument action in the launch description. The description's arguments are accessible through the get_launch_arguments() method. Parameters are gathered by searching for entities in this launch description and the description of each entity (which may include entities resulting from these entities).
launch.actions.declare_launch_argument.DeclareLaunchArgument(Action)
Basics: Action Action declaring new startup parameters. Startup parameters are stored in the "startup configuration" with the same name. See launch.actions.SetLaunchConfiguration and launch.substitutions.LaunchConfiguration. Any launch parameters declared in launch.LaunchDescription will be displayed as parameters when including that launch description, for example, as additional parameters in the launch.actions.IncludeLaunchDescription action, or as a command line when launching with ros2 launch.... parameter. In addition to a name (which is also where the parameter results are stored), startup parameters may have a default value, a selection list of valid values, and a description. If a default value is given, the parameter becomes optional and the default value will be placed in the launch configuration. If no default value is given and no value is given when the startup instructions are included, an error occurs. If a selection list is given and the given value is not in it, an error occurs. Substitutions can be used for the default value, but the name and description can only be Text since they require a meaningful value before starting, such as when listing command line arguments. It should be noted that the declaration of startup parameters must be in a certain part of the startup description, and this part can be described without startup. For example, if you declare a launch parameter in a conditional group or as a callback to an event handler, tools like ros2 launch may not know that parameter before launching the description. In this case, the parameter will not be visible on the command line, but an exception may be thrown if the parameter does not meet the requirements (and has no default value) after accessing it. In other words, the postcondition accessing the operation either has a value set by the launch configuration with the same name, or an exception is thrown because no value is set and there is no default value. However, preconditions do not guarantee that parameters following the condition or case inclusion will be visible. For example; ld = LaunchDescription([ DeclareLaunchArgument('simple_argument'), DeclareLaunchArgument('with_default_value', default_value='default'), DeclareLaunchArgument( 'with_default_and_description', default_value='some_default', description='this argument is used to configure ...'), DeclareLaunchArgument( 'mode', default_value='A', description='Choose between mode A and mode B', choices=['A', 'B']), # other actions here, ... ])
launch.actions.GroupAction
Basics: Action Actions that can produce other operations. This action is used to nest other actions without including separate launch instructions, while also optionally having a condition (like all other actions), extending and forwarding launch configuration and environment variables, and/or just the group and its spawn The operation declares the startup configuration. When Scoped=True, changes to the launch configuration and environment variables are limited to the scope of the operation within the group operation. When scoped=True and forwarding=True , all existing launch configuration and environment variables are available in the scoped context. When scope=True and forwarding=False, all existing launch configuration and environment variables are removed from the scope context. Any launch configurations defined in the launch_configurations dictionary will be set in the current context. When scoped=False, these configurations will persist even after the GroupAction has completed. When scoped=True, these configurations will only be available to actions within the GroupAction. When scope=True and forwarding=False, the launch_configurations dictionary will be evaluated before clearing and then reset in the cleared scope context.
launch.actions.IncludeLaunchDescription
Basics: Action Contains actions that start a description source and generate its entities when accessed. You can pass parameters to the launch description, which are declared through the launch.actions.DeclareLaunchArgument action. If the given parameter does not match a declared launch parameter name, it will still be set as the launch configuration using the launch.actions.SetLaunchConfiguration action. The reason for this is that it is not always possible to detect all instances of a declared launch parameter class in a given launch description. On the other hand, if a given launch description declares a launch parameter, but does not provide its value to this operation, an error will sometimes be thrown. However, this error will only occur if the declared startup parameter is unconditional (sometimes the action that declares the startup parameter will only be accessed under specific circumstances) and there is no default value to choose from. Conditionally included startup parameters without default values will eventually cause an error if unsatisfied parameters cannot be discovered in advance despite best-effort parameter checking.
launch.launch_description_sources.PythonLaunchDescriptionSource(LaunchDescriptionSource)
Basics: LaunchDescriptionSource A wrapper for Python startup files that can be loaded during the startup process.
launch.substitutions.LaunchConfiguration(Substitution)
Substitute variables for accessing startup configuration variables.
launch.substitutions.TextSubstitution(Substitution)
Can replace a single string text.
PushROSNamespace(Action)
Action to push the ros namespace. It will automatically pop up when used within a scoped `GroupAction`. There is no other way to pop it.
XMLLaunchDescriptionSource(FrontendLaunchDescriptionSource)
Encapsulates the XML startup file, which can be loaded during the startup process.
YAMLLaunchDescriptionSource(FrontendLaunchDescriptionSource)
Encapsulates the YAML startup file, which can be loaded during the startup process.
Node(ExecuteProcess)
Execute the operation of a ROS node.
The Python code is as follows
# example_launch.py import os from ament_index_python import get_package_share_directory from launch import LaunchDescription from launch.actions import DeclareLaunchArgument from launch.actions import GroupAction from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration from launch.substitutions import TextSubstitution from launch_ros.actions import Node from launch_ros.actions import PushRosNamespace from launch_xml.launch_description_sources import XMLLaunchDescriptionSource from launch_yaml.launch_description_sources import YAMLLaunchDescriptionSource def generate_launch_description(): # args that can be set from the command line or a default will be used background_r_launch_arg = DeclareLaunchArgument( "background_r", default_value=TextSubstitution(text="0") ) background_g_launch_arg = DeclareLaunchArgument( "background_g", default_value=TextSubstitution(text="255") ) background_b_launch_arg = DeclareLaunchArgument( "background_b", default_value=TextSubstitution(text="0") ) chatter_py_ns_launch_arg = DeclareLaunchArgument( "chatter_py_ns", default_value=TextSubstitution(text="chatter/py/ns") ) chatter_xml_ns_launch_arg = DeclareLaunchArgument( "chatter_xml_ns", default_value=TextSubstitution(text="chatter/xml/ns") ) chatter_yaml_ns_launch_arg = DeclareLaunchArgument( "chatter_yaml_ns", default_value=TextSubstitution(text="chatter/yaml/ns") ) # include another launch file launch_include = IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join( get_package_share_directory('demo_nodes_cpp'), 'launch/topics/talker_listener_launch.py')) ) # include a Python launch file in the chatter_py_ns namespace launch_py_include_with_namespace = GroupAction( actions=[ # push_ros_namespace to set namespace of included nodes PushRosNamespace('chatter_py_ns'), IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join( get_package_share_directory('demo_nodes_cpp'), 'launch/topics/talker_listener_launch.py')) ), ] ) # include a xml launch file in the chatter_xml_ns namespace launch_xml_include_with_namespace = GroupAction( actions=[ # push_ros_namespace to set namespace of included nodes PushRosNamespace('chatter_xml_ns'), IncludeLaunchDescription( XMLLaunchDescriptionSource( os.path.join( get_package_share_directory('demo_nodes_cpp'), 'launch/topics/talker_listener_launch.xml')) ), ] ) # include a yaml launch file in the chatter_yaml_ns namespace launch_yaml_include_with_namespace = GroupAction( actions=[ # push_ros_namespace to set namespace of included nodes PushRosNamespace('chatter_yaml_ns'), IncludeLaunchDescription( YAMLLaunchDescriptionSource( os.path.join( get_package_share_directory('demo_nodes_cpp'), 'launch/topics/talker_listener_launch.yaml')) ), ] ) # start a turtlesim_node in the turtlesim1 namespace turtlesim_node = Node( package='turtlesim', namespace='turtlesim1', executable='turtlesim_node', name='sim' ) # start another turtlesim_node in the turtlesim2 namespace # and use args to set parameters turtlesim_node_with_parameters = Node( package='turtlesim', namespace='turtlesim2', executable='turtlesim_node', name='sim', parameters=[{<!-- --> "background_r": LaunchConfiguration('background_r'), "background_g": LaunchConfiguration('background_g'), "background_b": LaunchConfiguration('background_b'), }] ) # perform remap so both turtles listen to the same command topic forward_turtlesim_commands_to_second_turtlesim_node = Node( package='turtlesim', executable='mimic', name='mimic', remappings=[ ('/input/pose', '/turtlesim1/turtle1/pose'), ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'), ] ) return LaunchDescription([ background_r_launch_arg, background_g_launch_arg, background_b_launch_arg, chatter_py_ns_launch_arg, chatter_xml_ns_launch_arg, chatter_yaml_ns_launch_arg, launch_include, launch_py_include_with_namespace, launch_xml_include_with_namespace, launch_yaml_include_with_namespace, turtlesim_node, turtlesim_node_with_parameters, forward_turtlesim_commands_to_second_turtlesim_node, ])
1.2 XML version
<!-- example_launch.xml --> <launch> <!-- args that can be set from the command line or a default will be used --> <arg name="background_r" default="0" /> <arg name="background_g" default="255" /> <arg name="background_b" default="0" /> <arg name="chatter_py_ns" default="chatter/py/ns" /> <arg name="chatter_xml_ns" default="chatter/xml/ns" /> <arg name="chatter_yaml_ns" default="chatter/yaml/ns" /> <!-- include another launch file --> <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.py" /> <!-- include a Python launch file in the chatter_py_ns namespace--> <group> <!-- push_ros_namespace to set namespace of included nodes --> <push_ros_namespace namespace="$(var chatter_py_ns)" /> <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.py" /> </group> <!-- include a xml launch file in the chatter_xml_ns namespace--> <group> <!-- push_ros_namespace to set namespace of included nodes --> <push_ros_namespace namespace="$(var chatter_xml_ns)" /> <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.xml" /> </group> <!-- include a yaml launch file in the chatter_yaml_ns namespace--> <group> <!-- push_ros_namespace to set namespace of included nodes --> <push_ros_namespace namespace="$(var chatter_yaml_ns)" /> <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.yaml" /> </group> <!-- start a turtlesim_node in the turtlesim1 namespace --> <node pkg="turtlesim" exec="turtlesim_node" name="sim" namespace="turtlesim1" /> <!-- start another turtlesim_node in the turtlesim2 namespace and use args to set parameters --> <node pkg="turtlesim" exec="turtlesim_node" name="sim" namespace="turtlesim2"> <param name="background_r" value="$(var background_r)" /> <param name="background_g" value="$(var background_g)" /> <param name="background_b" value="$(var background_b)" /> </node> <!-- perform remap so both turtles listen to the same command topic --> <node pkg="turtlesim" exec="mimic" name="mimic"> <remap from="/input/pose" to="/turtlesim1/turtle1/pose" /> <remap from="/output/cmd_vel" to="/turtlesim2/turtle1/cmd_vel" /> </node> </launch>
1.3 YAML version
# example_launch.yaml launch: # args that can be set from the command line or a default will be used - arg: name: "background_r" default: "0" - arg: name: "background_g" default: "255" - arg: name: "background_b" default: "0" - arg: name: "chatter_py_ns" default: "chatter/py/ns" - arg: name: "chatter_xml_ns" default: "chatter/xml/ns" - arg: name: "chatter_yaml_ns" default: "chatter/yaml/ns" # include another launch file -include: file: "$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.py" # include a Python launch file in the chatter_py_ns namespace - group: - push_ros_namespace: namespace: "$(var chatter_py_ns)" -include: file: "$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.py" # include a xml launch file in the chatter_xml_ns namespace - group: - push_ros_namespace: namespace: "$(var chatter_xml_ns)" -include: file: "$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.xml" # include a yaml launch file in the chatter_yaml_ns namespace - group: - push_ros_namespace: namespace: "$(var chatter_yaml_ns)" -include: file: "$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.yaml" # start a turtlesim_node in the turtlesim1 namespace -node: pkg: "turtlesim" exec: "turtlesim_node" name: "sim" namespace: "turtlesim1" # start another turtlesim_node in the turtlesim2 namespace and use args to set parameters -node: pkg: "turtlesim" exec: "turtlesim_node" name: "sim" namespace: "turtlesim2" param: - name: "background_r" value: "$(var background_r)" - name: "background_g" value: "$(var background_g)" - name: "background_b" value: "$(var background_b)" # perform remap so both turtles listen to the same command topic -node: pkg: "turtlesim" exec: "mimic" name: "mimic" remap: - from: "/input/pose" to: "/turtlesim1/turtle1/pose" - from: "/output/cmd_vel" to: "/turtlesim2/turtle1/cmd_vel"
2. Using Launch files from the command line
1. Launching
Any of the above launch files can be run via ros2 launch. To try them out locally, you can create a new package and then use
ros2 launch <package_name> <launch_file_name>
Or run the launch file directly by specifying the path to the file
ros2 launch <path_to_launch_file>
2. Set parameters
To set parameters passed to the startup file, the key:=value syntax should be used. For example, you can set the value of background_r in the following way:
ros2 launch <package_name> <launch_file_name> background_r:=255
ros2 launch <path_to_launch_file> background_r:=255
3. Control turtles
To test whether the remapping is working, you can run the following command in another terminal to control the turtle:
ros2 run turtlesim turtle_teleop_key --ros-args --remap __ns:=/turtlesim1
3. Python, XML or YAML: Which language should I use?
Startup files in ROS 1 are written in XML, so for users coming from ROS 1, XML is probably the most familiar.
For most applications, the choice of ROS 2 boot format depends on developer preference. However, if your startup file requires flexibility that isn’t possible with XML or YAML, you can use Python to write your startup file. Writing ROS 2 startup files in Python is more flexible for two reasons:
-
Python is a scripting language, so you can use the language and its libraries in your startup file.
-
Both ros2/launch (general launch functionality) and ros2/launch_ros (ROS 2 specific launch functionality) are written in Python, so you can access lower-level launch functionality that XML and YAML may not provide.
Still, startup files written in Python can be more complex and verbose than files written in XML or YAML.
4. Why use ROS 2 Launch
ROS 2 systems typically consist of multiple nodes running on multiple different processes (or even different machines). While each node can be run independently, this can quickly become cumbersome.
The startup system in ROS 2 is designed to automatically run multiple nodes with a single command. It helps users describe system configuration and then execute it as described. System configuration includes which programs are run, where they are run, which parameters are passed, and ROS-specific conventions that make it easy to reuse components throughout the system by providing different configurations for each component. It is also responsible for monitoring the status of started processes and reporting and/or reacting to changes in the status of these processes.
All of the above is specified in a startup file, which can be written in Python, XML, or YAML. After running this launch file using the ros2 launch command, all specified nodes will be running.
The design document details the design goals of the ROS 2 boot system (not all features are currently available).