Launch Files

A robot system in ROS or ROS2 consist of several interconnected nodes, which are responsible for different tasks, e.g.:

  • localization,

  • locomotion,

  • path planning,

  • mapping,

  • reading out sensor data,

  • visualization and so on.

Instead of invoking every node on its own, use a launch file! One launch file can start multiple nodes. It is a perfect tool for managing the processes of a more complex ROS2 application. On top, a launch file can include other launch files, which makes it even easier to structure the complex starting process of a robot system. For better organization launch files should be placed in a folder named launch inside the ROS2 package.

Create a launch file named turtle.launch.py:

$ cd ~/robot_ws/src/myfirstpackage/

$ mkdir launch

$ cd launch

$ touch turtle.launch.py

Instead, you can also create the launch folder and the launch file using the graphical user interface or inside your IDE.

Multiple Nodes in one launch file

Include the nodes turtlesim_node and draw_square of the package turtlesim in the turtle.launch.py file. The example given below shows the basic syntax of a launch file.

Listing 2 A simple launch.py file to start two nodes
#!/usr/bin/env python3

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            node_executable='turtlesim_node',
            node_name='my_turtle',
            output='screen'),
        Node(
            package='turtlesim',
            node_executable='draw_square',
            node_name='draw_square',
            output='log'),
    ])

For a Node you can define parameters in a launch file, e.g.:

  • package → name of the package that holds the executable

  • node_executable → name of the executable

  • node_name → (free to choose) name for the running process in ROS2

  • output → location of the output (screen or log, as desired)

Every launch file needs the generate_launch_description function and the return value LaunchDescription to be able to startup the dedicated Nodes.

Before we can start our launch file, it is necessary to once add launch files to our setup.py file in our package. Open the setup.py file and add the following two imports at the beginning of the file:

import os
from glob import glob

Afterwards add the following code to the data_files parameter in line 15:

--- /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/scripts/node_setup.py
+++ /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/scripts/launch_setup.py
@@ -1,3 +1,5 @@
+import os
+from glob import glob
 from setuptools import setup
 
 package_name = 'myfirstpackage'
@@ -10,6 +12,7 @@
         ('share/ament_index/resource_index/packages',
             ['resource/' + package_name]),
         ('share/' + package_name, ['package.xml']),
+        (os.path.join('share', package_name), glob('launch/*.launch.py')),        
     ],
     install_requires=['setuptools'],
     zip_safe=True,

We only need to do that once. Using this line all future launch files will be included automatically after building the package with colcon. So build the package again:

$ cd ~/robot_ws

$ colcon build

Start the launch file:

$ ros2 launch myfirstpackage turtle.launch.py

# ros2 launch <package_name> <launch_file_name>

The ros2 launch command executes a launch file.

Start one Node multiple times

It is also possible to run the same Node multiple times, using namespaces. In the following example we add another turtlesim instance to our turtle.launch.py file and just add the parameter namespace with the value new_turtle to run the same node just with a different name:

Listing 3 A simple launch.py file to start one node multiple times using namespaces
#!/usr/bin/env python3

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            node_executable='turtlesim_node',
            node_name='my_turtle',
            output='screen'),
        Node(
            package='turtlesim',
            node_executable='turtlesim_node',
            node_name='my_turtle',
            node_namespace='new_turtle',
            output='screen'),
        Node(
            package='turtlesim',
            node_executable='draw_square',
            node_name='draw_square',
            node_namespace='new_turtle',
            output='log'),
    ])

If you build the package and start the launch file again, you should see two turtlesim instances now. This new one is quite boring, because it does not move yet.

→ Please add another draw_circle Node to the launch file using the same namespace new_turtle.

→ Create another launch file turtle2.launch.py and move the 2nd turtlesim and the second draw_circle including the namespace parameter to that new turtle2.launch.py file. Don’t forget to delete them from your initial turtle.launch.py.

Include a launch file in a launch file

We should now have organized our two turtles in two seperate launch files. If we now want to start both of them, we can create another launch file starting both turtles. Create another launch file and name it multi_turtle.launch.py. It should include the previous launch files turtle.launch.py and turtle2.launch.py. Use the given example below.

Listing 4 A simple launch.py file to start multiple launch files
#!/usr/bin/env python3

import os
from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource

def generate_launch_description():
    my_first_pkg = get_package_share_directory('myfirstpackage')
    return LaunchDescription([
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(my_first_pkg, 'turtle.launch.py')
            ),
        ),
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(my_first_pkg, 'turtle2.launch.py')
            ),
        ),
    ])

→ Start the multi_turtle.launch.py launch file.

→ Include your node myfirstnode into the multi_turtle.launch.py file, build and restart it and check all active nodes of your system.

For further information: https://index.ros.org/doc/ros2/Tutorials/Launch-system/

Parameters

A parameter is a configuration value of a node. You can think of parameters as node settings. A node can store parameters as integers, floats, booleans, strings and lists. In ROS 2, each node maintains its own parameters. All parameters are dynamically reconfigurable. Parameters are accessible via:

  • command line,

  • *.yaml file or

  • launch file.

Access Parameters via command line

Start the ROS2 nodes turtlesim_node and turtle_teleop_key of the package turtlesim and list all available parameters:

$ ros2 param list

You will see the node namespaces, /teleop_turtle and /turtlesim, followed by the dedicated parameters of the nodes.

Every node has the parameter use_sim_time; it’s not unique to turtlesim.

To read out the current value of a parameter, e.g. the background_g parameter of the node turtlesim, use:

$ ros2 param get /turtlesim background_g

# ros2 param get <node_name> <parameter name>

You should receive the output:

Integer value is: 86

This is the green part of the RGB color code from the background of the turtlesim window. Now that we know that it is an integer value, we can change it to a different integer value:

$ ros2 param set /turtlesim background_g 177

# ros2 param set <node_name> <parameter name> <value>

Hint

If you additionally set the red value to 0 and the blue value to 172, you will get a nice FH Aachen mint green background.

You can save the current parameters of the Node for a later use:

$ cd ~/robot_ws/src/myfirstpackage

$ mkdir config

$ cd config

$ ros2 param dump /turtlesim

# ros2 param dump <node_name>

This command will save the parameter setup in a *.yaml file in the current directory where the command is executed.

For now in ROS2 we need to slightly modify this file so that it will also work with namespaces, e.g. in launch files. Open the turtlesim.yaml now and replace the node name turtlesim by double asterisk /**:

--- /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/config/turtlesim_dump.yaml
+++ /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/config/turtlesim_asterisk.yaml
@@ -1,4 +1,4 @@
-turtlesim:
+/**:
   ros__parameters:
     background_b: 172
     background_g: 177

Access parameters via *.yaml files

Go to the directory, where you stored your turtlesim.yaml file that you have created. Create a copy for further steps:

$ cd ~/robot_ws/src/myfirstpackage/config

$ cp turtlesim.yaml turtlesim_fh.yaml

Open the file now. It should have the following content:

Listing 5 Example 4. config file for turtlesim with parameters.
/**:
  ros__parameters:
    background_b: 172
    background_g: 177
    background_r: 0
    use_sim_time: false

We can now start turtlesim with this configuration using the --ros-args and --param-file flag:

$ cd ~/robot_ws/src/myfirstpackage/config

$ ros2 run turtlesim turtlesim_node --ros-args --params-file ./turtlesim_fh.yaml

# ros2 run <package_name> <executable_name> --ros-args --params-file <file_name>

This method does only work, if we pass the complete path to the *.yaml file. A more smart way is to use a launch file instead.

Access parameters via launch file

We can include paramater files in launch files. This is very useful to start ROS2 Nodes with a predifened setup. This could for example be a camera driver Node with parameters for resolution and frame rate. So it would be possible to create different configurations for resolution and frame rate and start the dedicated launch file - always using the same Node, but with a different configuration.

Let’s modify our turtle.launch.py file for this purpose:

$ cd ~/robot_ws/src/myfirstpackage/launch

In line 12 we add:

parameters=[os.path.join(my_first_pkg, 'turtlesim_fh.yaml')],

Of course we need to define the my_first_pkg variable and import the necessary libraries as shown in the following:

--- /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/launch/simple_launch_file.launch.py
+++ /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/launch/simple_params_launch_file.launch.py
@@ -1,14 +1,19 @@
 #!/usr/bin/env python3
+
+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():
+    my_first_pkg = get_package_share_directory('myfirstpackage')
     return LaunchDescription([
         Node(
             package='turtlesim',
             node_executable='turtlesim_node',
             node_name='my_turtle',
+            parameters=[os.path.join(my_first_pkg, 'turtlesim_fh.yaml')],
             output='screen'),
         Node(
             package='turtlesim',

Before we can start our modified launch file with parameters, it is necessary to once add *.yaml files from our config folder to our setup.py, like we did for launch files.

$ cd ~/robot_ws/src/myfirstpackage

Open the setup.py file and add the the following code to the data_files parameter in line 16:

--- /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/scripts/launch_setup.py
+++ /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/scripts/params_setup.py
@@ -12,7 +12,8 @@
         ('share/ament_index/resource_index/packages',
             ['resource/' + package_name]),
         ('share/' + package_name, ['package.xml']),
-        (os.path.join('share', package_name), glob('launch/*.launch.py')),        
+        (os.path.join('share', package_name), glob('launch/*.launch.py')),
+        (os.path.join('share', package_name), glob('config/*.yaml')),
     ],
     install_requires=['setuptools'],
     zip_safe=True,

Finally build the package and start the modified turtle.launch.py:

$ cd ~/robot_ws

$ colcon build --packages-select myfirstpackage

# colcon build --packages-select <package_name>

$ ros2 launch myfirstpackage turtle.launch.py

Hint

You can use the flag --packages-select with colcon build to only build one specific package in the workspace.

You should now see the FH Aachen mint green turtlesim background like in the following figure:

../../../_images/turtlesim_fh_mint.png

Figure 3 Turtlesim with FH Aachen mint green background.

Now create a copy of the turtlesim_fh.yaml file and name it turtlesim_random.yaml

$ cd ~/robot_ws/src/myfirstpackage/config

$ cp turtlesim_fh.yaml turtlesim_random.yaml

→ Open the new file and change the RGB values to any integer number that you like (only values from 0 to 255 are valid for color codes).

→ Modify the turtle2.launch.py in the launch folder of your package myfirstpackage and include the new turtlesim_random.yaml as a paramater for your second turtlesim instance.

→ Now start the multi_turtle.launch.py launch file from myfirstpackage. Don’t forget to build your package first to install new *.yaml parameter file and the modified launch file.

For further information: http://design.ros2.org/articles/ros_parameters.html

Create your own parameters in a Node

In ROS2 it is quite simple to create parameters in your Node. For that purpose create a copy of myfirstnode.py:

$ cd ~/robot_ws/src/myfirstpackage/myfirstpackage

$ cp myfirstnode.py param_node.py

Don’t forget to add an entry point for this node in the setup.py:

--- /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/scripts/params_setup.py
+++ /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/scripts/multiple_nodes_setup.py
@@ -25,6 +25,7 @@
     entry_points={
         'console_scripts': [
             'myfirstnode = myfirstpackage.myfirstnode:main',
+            'param_node = myfirstpackage.param_node:main',
         ],
     },
 )

In some special cases you do not want your executable to do ROS related stuff only. In that cases and only in such cases, it makes sense to replace the rclpy.spin() by a rclpy.spin_once(). Remember: the spin() function loops our Node and waits for work for our Node to do. spin_once() instead will only ask the ROS2 system once, if there is work for the Node to do and not continously in a loop. That allows us to do something after the spin_once() request within the executable. Again: This is only useful in special cases and not recommended as a general approach. We will anyway do this now for our parameter example.

Open your param_node.py Node and replace the `rclpy.spin` by `rclpy.spin_once` and rename the Node meanwhile:

--- /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/scripts/simple_python_node2_with_shutdown.py
+++ /mnt/c/Users/Patrick/Documents/academy-sphinx/_resources/code/tutorials/scripts/simple_python_node2_with_spin_once.py
@@ -4,12 +4,11 @@
 
 def main():
     rclpy.init()
-    myfirstnode = rclpy.create_node('myfirstnode')
+    myfirstnode = rclpy.create_node('my_param_node')
     try:
-        rclpy.spin(myfirstnode)
+        rclpy.spin_once(myfirstnode, timeout_sec=1.0)
     except KeyboardInterrupt:
         pass
-
     myfirstnode.destroy_node()
     rclpy.shutdown()
 

We should set the timeout_sec to 0.0 in case there is some blocking function.

To create our own parameter, we will add the following code in line 8 after creating the Node:

myfirstnode.declare_parameter('my_param', 13)

#<nodename>.declare_parameter('<parameter_name>', <parameter_default_value>)

That’s already it.

→ To see that it is working, run the Node and check the parameter value from command line.

To see that our parameter is actively affected by changing its value, we can also ask the current parameter value in our Node using:

myfirstnode.get_parameter('my_param').value

#nodename.get_parameter('<parameter_name>').value

We can print the parameter value in a while loop so that we see the current value within the Node.

Using rclpy we can loop around our spin_once unless it is ok:

while rclpy.ok():

To not spam in our terminal, it is also useful to use a delay in the loop, let’s say of 1 second. For that purpose we can use the Python sleep() function from the time library:

from time import sleep # add this line in the beginning of your Python script

sleep(1.0) # blocks the code for 1 second inside the loop

Your final Python Node should now look like this:

Listing 6 A simple Node to continously receive a parameter.
#!/usr/bin/env python3

import rclpy
from time import sleep

def main():
    rclpy.init()
    myfirstnode = rclpy.create_node('my_param_node')
    myfirstnode.declare_parameter('my_param', 13)
    while rclpy.ok():
        try:
            rclpy.spin_once(myfirstnode, timeout_sec=1.0)
        except KeyboardInterrupt:
            pass
        print(myfirstnode.get_parameter('my_param').value)
        sleep(1.0)
    myfirstnode.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Congratulations! You are now familiar with the basics of the ROS2 Filesystem.