Skip to content

ROS2 Launch

Concepts

The launch and launch_ros packages provide a set of API to model the configuration and starting of ROS2 nodes. The "launch" package provides the base classes and the "launch_ros" package provides ROS2 specific extensions.

  • launch.LaunchDescription: this is the main class used to describe the launch configuration. It is a list of "actions" that are executed sequentially. The launch configuration is executed by the launch service.
  • launch.Action: this class represents the various user intentions.
    • launch.actions.IncludeLaunchDescription: the action to include another launch file
    • launch.actions.DeclareLaunchArgument: the action to declare a launch argument
    • launch.actions.SetEnvironmentVariable: the action to set an environment variable by name
    • launch.actions.AppendEnvironmentVariable: the action to append a value to an environment variable
    • launch.actions.GroupAction: the action to group other actions, and it can be associated with conditions
    • launch.actions.TimerAction: the action to start other actions after a specified amount of time
    • launch.actions.ExecuteProcess: the action to execute a process with path and argutments

There are a few more actions defined in the launch package and you can find more information in the documentation. A commonly used action extended from launch and implemented in launch_ros is from launch_ros.actions.Node. You can express the intention of starting a ROS node with this action.

You can expect the outline of a typical ROS2 launch file to be like:

from launch import LaunchDescription
from launch.actions import ABC
from launch.actions import XYZ

def generate_launch_description():
    action_abc = ABC()
    action_xyz = XYZ()

    ld = LaunchDescription()
    ld.add_action(action_abc)
    ld.add_action(action_xyz)

    return ld
  • launch.Substitution: this class represents the various ways to substitute a string. The substitution is performed when the launch configuration is executed by the launch service. Substitutions can help to make the launch configuration more flexible and easier to be reused.
    • launch.substitutions.Text: the substitution to get the given string when evaluated
    • launch.substitutions.LaunchConfiguration: the substitution to get the value of a launch argument
    • launch.substitutions.EnvironmentVariable: the substitution to get the value of an environment variable

In most cases, what you need to do to create a launch file is to use substitutions to construct actions and then use actions to construct the launch description.

  • launch.LaunchService: "Launch descriptions, and the actions contained therein, can either be introspected directly or launched by a launch.LaunchService. A launch service is a long running activity that handles the event loop and dispatches actions."[3] This means other than creating a LaunchDescription() object and launching with "ros2 launch", you can also manually create a LaunchService object and run with the LaunchDescription object from a plain Python script.
  • launch.EventHandler: "Event handlers can be registered for specific events and can be useful for monitoring the state of processes." [4] Predefined event handlers from the launch package includes (but not limited to):
    • launch.event_handlers.OnExecutionComplete
    • launch.event_handlers.OnProcessStart
    • launch.event_handlers.OnProcessExit
    • launch.event_handlers.OnProcessIO
    • launch.event_handlers.OnShutdown

Sample Launch

The following launch file is copied from the ROS2 documentation [5] with minor modifications.

# example.launch.py

import os

from launch import LaunchDescription

from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.actions import GroupAction

from launch.substitutions import LaunchConfiguration
from launch.substitutions import TextSubstitution
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python import get_package_share_directory

from launch_ros.actions import Node
from launch_ros.actions import PushRosNamespace


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_ns_launch_arg = DeclareLaunchArgument(
        "chatter_ns", default_value=TextSubstitution(text="my/chatter/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 another launch file in the chatter_ns namespace
    launch_include_with_namespace = GroupAction(
        actions=[
            # push_ros_namespace to set namespace of included nodes
            PushRosNamespace('chatter_ns'),
            IncludeLaunchDescription(
                PythonLaunchDescriptionSource(
                    os.path.join(
                        get_package_share_directory('demo_nodes_cpp'),
                        'launch/topics/talker_listener.launch.py'))
            ),
        ]
    )

    # 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_ns_launch_arg,
        launch_include,
        launch_include_with_namespace,
        turtlesim_node,
        turtlesim_node_with_parameters,
        forward_turtlesim_commands_to_second_turtlesim_node,
    ])

Typical Use Cases

Launch a node

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            namespace='turtlesim1',
            executable='turtlesim_node',
            name='sim',
            output='screen',
            remapping=[
                ('origin1', 'other1'),
                ('origin2', 'other2')
            ]
        )
    ])

You can find more arguments to the Node() class in the source code [7].

Launch a launch file

from launch import LaunchDescription

from launch.actions import IncludeLaunchDescription

from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import PathJoinSubstitution, TextSubstitution
from launch_ros.substitutions import FindPackageShare

def generate_launch_description():
    colors = {
        'background_r': '200'
    }

    return LaunchDescription([
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource([
                PathJoinSubstitution([
                    FindPackageShare('launch_tutorial'),
                    'example_substitutions.launch.py'
                ])
            ]),
            launch_arguments={
                'turtlesim_ns': 'turtlesim2',
                'use_provided_red': 'True',
                'new_background_r': TextSubstitution(text=str(colors['background_r']))
            }.items()
        )
    ])

Reference

  • [1] https://docs.ros.org/en/humble/Concepts/About-Domain-ID.html
  • [2] https://github.com/chargerKong/learning_ros2_launch_by_example
  • [3] https://github.com/ros2/launch/blob/humble/launch/doc/source/architecture.rst
  • [4] https://docs.ros.org/en/humble/Tutorials/Intermediate/Launch/Using-Event-Handlers.html#
  • [5] https://docs.ros.org/en/humble/How-To-Guides/Launch-file-different-formats.html
  • [6] https://answers.ros.org/question/322874/ros2-what-is-different-between-declarelaunchargument-and-launchconfiguration/
  • [7] https://github.com/ros2/launch_ros/blob/humble/launch_ros/launch_ros/actions/node.py#L187