Attention

This is older information related to ROS2 Humble. Newer versions, such as Jazzy, are part of the ongoing versions of this tutorial.

Warning

This topic is under heavy construction. Don’t forget your PPE if you’re venturing forward.

Parameters: creating configurable Nodes

The Nodes we have made in the past few sections are interesting because they take advantage of the interprocess communication provided by ROS2.

Other capabilities of ROS2 that we must take advantage of are ROS2 parameters and ROS2 launch files. We can use them to modify the behavior of Nodes without having to modify their source code.

For Python users, that might sound less appealing than for users of compiled languages. However, users of your package might not want nor be able to modify the source code directly, if the package is installable or part of a larger system with multiple users.

Create the package

First, let us create an ament_python package that depends on our packages_with_interfaces and build from there.

cd ~/ros2_tutorial_workspace/src
ros2 pkg create python_package_that_uses_parameters_and_launch_files \
--build-type ament_python \
--dependencies rclpy package_with_interfaces

Overview

Before we start exploring the elements of the package, let us

  1. Create the Node with a configurable publisher using parameters, mostly as we saw in Create the Node with a publisher.

  2. Create a launch file to configure the Node without modifying its source code.

Create the Node using parameters

TL;DR Using parameters in a Node

  1. Declare the parameter with Node.declare_parameter(), usually in the class’s __init__.

  2. Get the parameter with Node.get_parameter() either once or continuously.

In this step, we’ll work on this.

src/python_package_that_uses_parameters_and_launch_files
  └── python_package_that_uses_parameters_and_launch_files/
        └── __init__.py
        └── amazing_quote_configurable_publisher_node.py

For the sake of the example, let us suppose that we want to make an AmazingQuote publisher that is, now, configurable.

Let’s start by creating an amazing_quote_configurable_publisher_node.py in python_package_that_uses_parameters_and_launch_files/python_package_that_uses_parameters_and_launch_files with the following contents

amazing_quote_configurable_publisher_node.py

 1import rclpy
 2from rclpy.node import Node
 3from package_with_interfaces.msg import AmazingQuote
 4
 5
 6class AmazingQuoteConfigurablePublisherNode(Node):
 7    """A configurable ROS2 Node that publishes a configurable amazing quote."""
 8
 9    def __init__(self):
10        super().__init__('amazing_quote_configurable_publisher_node')
11
12        # Periodically-obtained parameters
13        self.declare_parameter('quote', 'Use the force, Pikachu!')
14        self.declare_parameter('philosopher_name', 'Uncle Ben')
15
16        # One-off parameters
17        self.declare_parameter('topic_name', 'amazing_quote')
18        topic_name: str = self.get_parameter('topic_name').get_parameter_value().string_value
19        self.declare_parameter('period', 0.5)
20        timer_period: float = self.get_parameter('period').get_parameter_value().double_value
21
22        self.configurable_amazing_quote_publisher = self.create_publisher(
23            msg_type=AmazingQuote,
24            topic=topic_name,
25            qos_profile=1)
26
27        self.timer = self.create_timer(timer_period, self.timer_callback)
28
29        self.incremental_id: int = 0
30
31    def timer_callback(self):
32        """Method that is periodically called by the timer."""
33
34        quote: str = self.get_parameter('quote').get_parameter_value().string_value
35        philosopher_name: str = self.get_parameter('philosopher_name').get_parameter_value().string_value
36
37        amazing_quote = AmazingQuote()
38        amazing_quote.id = self.incremental_id
39        amazing_quote.quote = quote
40        amazing_quote.philosopher_name = philosopher_name
41
42        self.configurable_amazing_quote_publisher.publish(amazing_quote)
43
44        self.incremental_id = self.incremental_id + 1
45
46
47def main(args=None):
48    """
49    The main function.
50    :param args: Not used directly by the user, but used by ROS2 to configure
51    certain aspects of the Node.
52    """
53    try:
54        rclpy.init(args=args)
55
56        amazing_quote_configurable_publisher_node = AmazingQuoteConfigurablePublisherNode()
57
58        rclpy.spin(amazing_quote_configurable_publisher_node)
59    except KeyboardInterrupt:
60        pass
61    except Exception as e:
62        print(e)
63
64
65if __name__ == '__main__':
66    main()

Don’t forget to declare the parameter!

Note

According to the official documentation, it is possible to work with undeclared parameters, but I recommend against this for basic usage.

It’s easy to forget it, but Node.get_parameter() will not work if the parameter was not first declared with Node.declare_parameter(). Don’t forget it!

One-off parameters

For one-off parameters, we just get them once after declaring them. Because we’re using those attributes directly in the __init__ method, they are not made attributes of the class, but they could be.

        # One-off parameters
        self.declare_parameter('topic_name', 'amazing_quote')
        topic_name: str = self.get_parameter('topic_name').get_parameter_value().string_value
        self.declare_parameter('period', 0.5)
        timer_period: float = self.get_parameter('period').get_parameter_value().double_value

        self.configurable_amazing_quote_publisher = self.create_publisher(
            msg_type=AmazingQuote,
            topic=topic_name,
            qos_profile=1)

        self.timer = self.create_timer(timer_period, self.timer_callback)

In this case, we’re making the topic name and publication periodicity as one-off configurable parameters.

Continuously-obtained parameters

Note

According to the official documentation, it is possible to assign callbacks to manage changes in parameters. It is not the best-documented feature and has some caveats, so we will skip that for now.

For parameters that we obtain continuously through the lifetime of the Node, we can, for example, declare them in the __init__ method, like so

        # Periodically-obtained parameters
        self.declare_parameter('quote', 'Use the force, Pikachu!')
        self.declare_parameter('philosopher_name', 'Uncle Ben')

then obtain them in another method, like so

    def timer_callback(self):
        """Method that is periodically called by the timer."""

        quote: str = self.get_parameter('quote').get_parameter_value().string_value
        philosopher_name: str = self.get_parameter('philosopher_name').get_parameter_value().string_value

        amazing_quote = AmazingQuote()
        amazing_quote.id = self.incremental_id
        amazing_quote.quote = quote
        amazing_quote.philosopher_name = philosopher_name

        self.configurable_amazing_quote_publisher.publish(amazing_quote)

        self.incremental_id = self.incremental_id + 1

In this example, we are making the quote and the philosopher_name as configurable parameters that can be changed continuously, during the lifetime of the Node. After they are changed, the node will publish a message with different contents.

Truly configurable: using _launch.py files

TL;DR Using launch files

  1. (Once) Create a launch folder in the project.

  2. Create the launch file named as launch/<something>_launch.py.

  3. (Once) modify the setup.py to correctly install launch files.

Differently from ROS1, in ROS2 we can use Python launch files. They are quite powerful, well documented, and mentioned first in the official documentation, so we will use them instead of XML or YAML files.

(Once) create the launch folder

In this step, we’ll work on this.

src/python_package_that_uses_parameters_and_launch_files
  └── python_package_that_uses_parameters_and_launch_files/
        └── __init__.py
        └── amazing_quote_configurable_publisher_node.py
  └── launch

Well, without further ado

cd ~/ros2_tutorial_workspace/src/python_package_that_uses_parameters_and_launch_files
mkdir launch

Create the launch file

In this step, we’ll work on this.

src/python_package_that_uses_parameters_and_launch_files
  └── python_package_that_uses_parameters_and_launch_files/
        └── __init__.py
        └── amazing_quote_configurable_publisher_node.py
  └── launch
        └── peanut_butter_falcon_quote_publisher_launch.py

Suppose that we are tired of all the meme quotes and want to make our Node publish a truly inspirational quote. We start by making the launch file named peanut_butter_falcon_quote_publisher_launch.py within the launch folder we just created, with the following contents

peanut_butter_falcon_quote_publisher_launch.py

 1from launch import LaunchDescription
 2from launch_ros.actions import Node
 3
 4
 5def generate_launch_description():
 6    return LaunchDescription([
 7        Node(
 8            output='screen',
 9            emulate_tty=True,
10            package='python_package_that_uses_parameters_and_launch_files',
11            executable='amazing_quote_configurable_publisher_node',
12            name='peanut_butter_falcon_quote_publisher_node',
13            parameters=[{
14                "topic_name": "truly_inspirational_quote",
15                "period": 0.25,
16                "quote": "Yeah, you're gonna die, it's a matter of time. That ain't the question. The question's, "
17                         "whether they're gonna have a good story to tell about you when you're gone",
18                "philosopher_name": "Tyler",
19            }]
20        )
21    ])

We’re relying on the LaunchDescription, which expects a list of launch_ros.actions.

from launch import LaunchDescription
from launch_ros.actions import Node

When using a launch_ros.actions.Node, we need to define which package it belongs to and the executable which must match the name we set for the executable in the setup.py

            package='python_package_that_uses_parameters_and_launch_files',
            executable='amazing_quote_configurable_publisher_node',

Besides the parameters, we can configure the name of the Node, such that each is unique

            name='peanut_butter_falcon_quote_publisher_node',

Finally, our parameters are defined using a dictionary within a list, namely

                "topic_name": "truly_inspirational_quote",
                "period": 0.25,
                "quote": "Yeah, you're gonna die, it's a matter of time. That ain't the question. The question's, "
                         "whether they're gonna have a good story to tell about you when you're gone",
                "philosopher_name": "Tyler",

The setup.py

In this step, we’ll work on this.

src/python_package_that_uses_parameters_and_launch_files
  └── python_package_that_uses_parameters_and_launch_files/
        └── __init__.py
        └── amazing_quote_configurable_publisher_node.py
  └── launch
        └── peanut_butter_falcon_quote_publisher_launch.py
  setup.py

Modify the setup.py to look like this

setup.py

 1import os
 2from glob import glob
 3from setuptools import setup
 4
 5package_name = 'python_package_that_uses_parameters_and_launch_files'
 6
 7setup(
 8    name=package_name,
 9    version='0.0.0',
10    packages=[package_name],
11    data_files=[
12        ('share/ament_index/resource_index/packages',
13         ['resource/' + package_name]),
14        ('share/' + package_name, ['package.xml']),
15        (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))),
16    ],
17    install_requires=['setuptools'],
18    zip_safe=True,
19    maintainer='murilo',
20    maintainer_email='murilomarinho@ieee.org',
21    description='TODO: Package description',
22    license='TODO: License declaration',
23    tests_require=['pytest'],
24    entry_points={
25        'console_scripts': [
26            'amazing_quote_configurable_publisher_node = '
27            'python_package_that_uses_parameters_and_launch_files.amazing_quote_configurable_publisher_node:main',
28        ],
29    },
30)

We have already seen a setup.py so many times we’re almost calling it Wilson. The only difference is emphasized above inside the data_files, which is the line that will specify that launch files will be installed as well. Notice that the setup.py looks for files with a specific pattern in the folder launch, so be sure that your launch files have the correct name otherwise they might not be installed as expected.

Build and source

Before we proceed, let us build and source once.

cd ~/ros2_tutorial_workspace
colcon build
source install/setup.bash

Note

If you don’t remember why we’re building with these commands, see Always source after you build.