Until now, every time we launched a ROS node, we had to open a new terminal and run a command. With so many nodes in a robotic system, doing this every time is cumbersome. Is there a way to launch all nodes at once? The answer is, of course, a launch file, a script that launches and configures multiple nodes in the ROS system.
In ROS2, launch is used to launch multiple nodes and configure program parameters. ROS2 launch files are available in XML, YAML, and Python formats. This lesson uses a Python launch file as an example. Compared to the other two formats, the Python format is more flexible:
The key to writing ROS2 launch files in Python is to abstract each node, file, script, etc. into an action, launching them using a unified interface.
References:
ros2 pkg create learn_launch --build-type ament_pythonCreate a launch folder under the package, then create a file called [single_node_launch.py] within the launch folder. Copy the following content into the file:
xfrom launch import LaunchDescriptionfrom launch_ros.actions import Nodedef generate_launch_description(): node = Node( package='pkg_helloworld_py', executable='helloworld', output='screen' ) return LaunchDescription([node])
The launch file is often named "LaunchName_launch.py." LaunchName is customizable, while _launch.py is considered fixed. You need to modify the setup.py file in the package to add the files in the launch path and compile to generate the executable .py file.
xxxxxxxxxx#1. Import related header filesimport osfrom glob import glob#2. In the data_files list, add the launch path and the launch.py ••file under the path(os.path.join('share',package_name,'launch'),glob(os.path.join('launch','*launch.py')))
xxxxxxxxxxcolcon build --packages-select learn_launch
xxxxxxxxxxros2 launch learn_launch single_node_launch.py
xxxxxxxxxxfrom launch import LaunchDescriptionfrom launch_ros.actions import Node
xxxxxxxxxxdef generate_launch_description(): node = Node( package='pkg_helloworld_py', executable='helloworld', ) return LaunchDescription([node])We define a variable called node as the return value of a node startup. We then call the Node function with two important parameters: package and executable.
Finally, we call the LaunchDescription function, passing in the node parameter, and execute the function.
xxxxxxxxxxreturn LaunchDescription([node])
Create a new file called [multi_node_launch.py] and add the following content:
xxxxxxxxxxfrom launch import LaunchDescriptionfrom launch_ros.actions import Nodedef generate_launch_description(): publisher_node = Node( package='pkg_topic', executable='publisher_demo', output='screen' ) subscriber_node = Node( package='pkg_topic', executable='subscriber_demo', output='screen' ) return LaunchDescription([ publisher_node, subscriber_node ])
xxxxxxxxxxcolcon build --packages-select learn_launch
xxxxxxxxxxros2 launch learn_launch multi_node_launch.py

If the terminal does not print anything, we can verify that the nodes have started successfully by checking which nodes have started. In the terminal, enter:
xxxxxxxxxxros2 node list

Similar to simple_node_launch.py, except for one more node.
Create a new file called [remap_name_launch.py] in the same directory as multi_node_launch.py and add the following content:
xxxxxxxxxxfrom launch import LaunchDescriptionfrom launch_ros.actions import Nodedef generate_launch_description(): publisher_node = Node( package='pkg_topic', executable='publisher_demo', output='screen', remappings=[("/topic_demo", "/topic_update")] ) return LaunchDescription([ publisher_node ])
xxxxxxxxxxcolcon build --packages-select learn_launch
Let's first see what topics the publisher_demo node publishes before remapping topics:
xxxxxxxxxxros2 launch learn_launch multi_node_launch.pyros2 topic list

The topic here is [/topic_demo]
xxxxxxxxxxros2 launch learn_launch remap_name_launch.pyros2 topic list
As shown above, the topic name has been remapped to [/topic_update]
The following sections have been added:
xxxxxxxxxxremappings=[("/topic_demo", "/topic_update")]
Here, the original /topic_demo topic is remapped to /topic_update
Create a new file, [include_launch.py], in the same directory as multi_node_launch.py and add the following content:
xxxxxxxxxxfrom launch import LaunchDescriptionfrom launch_ros.actions import Node import osfrom launch.actions import IncludeLaunchDescriptionfrom launch.launch_description_sources import PythonLaunchDescriptionSourcefrom ament_index_python.packages import get_package_share_directorydef generate_launch_description(): hello_launch = IncludeLaunchDescription(PythonLaunchDescriptionSource( [os.path.join(get_package_share_directory('learn_launch'), 'launch'), '/multi_node_launch.py']), ) return LaunchDescription([ hello_launch ])
xxxxxxxxxxcolcon build --packages-select learn_launch
xxxxxxxxxxros2 launch learn_launch include_launch.py

This example primarily demonstrates how to write a complex launch file; the program's functionality is ignored.
Create a new file, [complex_launch.py], in the same directory as [multi_node_launch.py] and add the following content:
xxxxxxxxxximport osfrom ament_index_python import get_package_share_directoryfrom launch import LaunchDescriptionfrom launch.actions import DeclareLaunchArgumentfrom launch.actions import IncludeLaunchDescriptionfrom launch.actions import GroupActionfrom launch.launch_description_sources import PythonLaunchDescriptionSourcefrom launch.substitutions import LaunchConfigurationfrom launch.substitutions import TextSubstitutionfrom launch_ros.actions import Nodefrom launch_ros.actions import PushRosNamespacedef 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(LaunchConfiguration('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, ])
xxxxxxxxxxcolcon build --packages-select learn_launch
xxxxxxxxxxros2 launch learn_launch complex_launch.pyTwo turtles will appear on the host machine's VNC.

xxxxxxxxxxros2 run turtlesim turtle_teleop_key --ros-args -r __ns:=/turtlesim1

The program mainly starts:
Create a file called [complex_launch.xml] in the same directory as complex_launch.py and add the following content:
xxxxxxxxxx<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_ns" default="my/chatter/ns"/> <!-- include another launch file --> <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener.launch.py"/> <!-- include another launch file in the chatter_ns namespace--> <group> <!-- push-ros-namespace to set namespace of included nodes --> <push-ros-namespace namespace="$(var chatter_ns)"/> <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener.launch.py"/> </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> 
xxxxxxxxxxcolcon build --packages-select learn_launch
Enter the terminal:
xxxxxxxxxxros2 launch learn_launch complex_launch.xml

xxxxxxxxxxros2 run turtlesim turtle_teleop_key --ros-args -r __ns:=/turtlesim1Use keyboard control to start Turtle 1. Turtle 2 will completely mimic Turtle 1's behavior.
Create a new file, [complex_launch.yaml], in the same directory as complex_launch.py and add the following content:
xxxxxxxxxxlaunch# args that can be set from the command line or a default will be usedarg name"background_r" default"0"arg name"background_g" default"255"arg name"background_b" default"0"arg name"chatter_ns" default"my/chatter/ns"# include another launch fileinclude file"$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener.launch.py"# include another launch file in the chatter_ns namespacegrouppush-ros-namespace namespace"$(var chatter_ns)"include file"$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener.launch.py"# start a turtlesim_node in the turtlesim1 namespacenode pkg"turtlesim" exec"turtlesim_node" name"sim" namespace"turtlesim1"# start another turtlesim_node in the turtlesim2 namespace and use args to set parametersnode 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 topicnode pkg"turtlesim" exec"mimic" name"mimic" remap - from"/input/pose" to"/turtlesim1/turtle1/pose" - from"/output/cmd_vel" to"/turtlesim2/turtle1/cmd_vel" 
xxxxxxxxxxcolcon build --packages-select learn_launch
xxxxxxxxxxros2 launch learn_launch complex_launch.yaml

xxxxxxxxxxros2 run turtlesim turtle_teleop_key --ros-args -r __ns:=/turtlesim1Start Turtle 1 using keyboard control. Turtle 2 will completely mimic Turtle 1's behavior.