How to set/get parameters from another node | ROS 2 Humble Python Tutorial

Written by Ernest Cheong

22/08/2024

What you will learn:

Parameters in ROS 2 are associated with individual nodes, it is not possible to work with another node’s parameters simply by using the Node API. For example, a node calling get_parameter() can only get its own parameters.

In this tutorial, you’ll learn how to get and set another node’s parameters by using a service call in code. All other parameter services should be straightforward as they are similar.

If you want to learn more advanced ROS 2 topics, including parameters and more, in a practical, hands-on way, check out the course Intermediate ROS 2: https://app.theconstruct.ai/courses/113

Opening the rosject

In order to follow this tutorial, we need to have ROS 2 installed in our system, and ideally a ros2_ws (ROS2 Workspace). To make your life easier, we have already prepared a rosject for that: https://app.theconstruct.ai/l/639b9a55/

Just by copying the rosject (clicking the link above), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject.

After pressing the Run button, you should have the rosject loaded. Now, let’s head to the next section to get some real practice.

In order to interact with ROS2, we need a terminal.

Let’s open a terminal by clicking the Open a new terminal button.

Open a new Terminal

Create a ROS2 python package with a simple node that has two parameters

In a terminal, create a python package called python_parameters:

cd ~/ros2_ws/src/
ros2 pkg create --build-type ament_python python_parameters --dependencies rclpy

In the same terminal, create a python node called python_parameters_node.py:

touch python_parameters/python_parameters/python_parameters_node.py

Here is a simple node with two parameters of type string (to paste in python_parameters_node.py):

  • The parameters are:
    • my_parameter with value me and description This parameter is mine!
    • your_parameter with value you and description This parameter is yours!

    You can also have other types as listed in the documentation.

  • The node has a timer callback with a period of 1 second just to log what the current parameters are.
import rclpy
import rclpy.node
from rcl_interfaces.msg import ParameterDescriptor

class MinimalParam(rclpy.node.Node):
    def __init__(self):
        super().__init__('minimal_param_node')
        
        my_parameter_descriptor = ParameterDescriptor(description='This parameter is mine!')
        self.declare_parameter('my_parameter', 'me', my_parameter_descriptor)

        your_parameter_descriptor = ParameterDescriptor(description='This parameter is yours!')
        self.declare_parameter('your_parameter', 'you', your_parameter_descriptor)

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

    def timer_callback(self):
        my_param = self.get_parameter('my_parameter').get_parameter_value().string_value

        self.get_logger().info('I am %s!' % my_param)

        your_param = self.get_parameter('your_parameter').get_parameter_value().string_value

        self.get_logger().info('You are %s!' % your_param)

def main():
    rclpy.init()
    node = MinimalParam()
    rclpy.spin(node)

if __name__ == '__main__':
    main()

Add an entry point to the setup.py file so that it looks like this:

    entry_points={
        'console_scripts': [
            'minimal_param_node = python_parameters.python_parameters_node:main',
        ],
    },

In a terminal, compile the package:

cd ~/ros2_ws/
colcon build --packages-select python_parameters

Then start the node:

source ~/ros2_ws/install/setup.bash
ros2 run python_parameters minimal_param_node

You should get something like this:

[INFO] [1713695285.349594469] [minimal_param_node]: I am me!
[INFO] [1713695285.349875641] [minimal_param_node]: You are you!
[INFO] [1713695286.337758113] [minimal_param_node]: I am me!
[INFO] [1713695286.338776447] [minimal_param_node]: You are you!
[INFO] [1713695287.337323765] [minimal_param_node]: I am me!
[INFO] [1713695287.338010397] [minimal_param_node]: You are you!

Create a simple client node that retrieves another node’s parameters

The goal is to get the parameter values of the node that we just created in the previous section (python_parameters_node.py) from within a new client node.

Create a new Python file for the client node called get_parameters_client_node.py:

touch ~/ros2_ws/src/python_parameters/python_parameters/get_parameters_client_node.py

Here is a simple asynchronous client (to paste in get_parameters_client_node.py):

  • It creates a client with service type GetParameters and service name /minimal_param_node/get_parameters
  • The request message needs to be a list of names of parameters whose values we want to get. In this case, we chose to retrieve the values of both my_parameter and your_parameter
    • Remember that we can use the terminal command ros2 interface show rcl_interfaces/srv/GetParameters to determine the request (and response) fields
  • The send_request method is passed the request message and handles the service call
import rclpy
from rclpy.node import Node
from rcl_interfaces.srv import GetParameters

class MinimalGetParamClientAsync(Node):

    def __init__(self):
        super().__init__('minimal_get_param_client_async')

        self.cli = self.create_client(GetParameters, '/minimal_param_node/get_parameters')

        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        
        self.req = GetParameters.Request()

    def send_request(self, params_name_list):
        self.req.names = params_name_list

        self.future = self.cli.call_async(self.req)
        rclpy.spin_until_future_complete(self, self.future)
        return self.future.result()

def main():
    rclpy.init()

    minimal_get_param_client = MinimalGetParamClientAsync()

    list_of_params_to_get = ['my_parameter', 'your_parameter']
    response = minimal_get_param_client.send_request(list_of_params_to_get)
    minimal_get_param_client.get_logger().info('First value: %s, Second value: %s'  % (response.values[0].string_value, response.values[1].string_value))

    minimal_get_param_client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Add the entry point to the setup.py file so that it looks like this:

...

    entry_points={
        'console_scripts': [
            'minimal_param_node = python_parameters.python_parameters_node:main',
            'minimal_get_param_client_async = python_parameters.get_parameters_client_node:main',
        ],
    },
)

Compile the package:

cd ~/ros2_ws/
colcon build --packages-select python_parameters

Run the client node in one terminal:

source ~/ros2_ws/install/setup.bash
ros2 run python_parameters minimal_get_param_client_async

And the service node in a different terminal:

source ~/ros2_ws/install/setup.bash
ros2 run python_parameters minimal_param_node

You should get something like this in the terminal of the client:

[INFO] [1719039475.331557220] [minimal_get_param_client_async]: service not available, waiting again...
[INFO] [1719039476.333952497] [minimal_get_param_client_async]: service not available, waiting again...
[INFO] [1719039477.336702012] [minimal_get_param_client_async]: service not available, waiting again...
[INFO] [1719039478.338849982] [minimal_get_param_client_async]: service not available, waiting again...
[INFO] [1719039478.591154332] [minimal_get_param_client_async]: First value: me, Second value: you
            
...

Since the client was started first, it was initially waiting for the GetParameters service to be available. After the service started, the client successfully retrieved the parameter values before shutting down.

End all terminal processes before continuing.

Create a simple client node that modifies another node’s parameters

The process here is similar to the previous section, only this time we are setting the parameter values.

Create a new Python file for the client node called set_parameters_client_node.py:

touch ~/ros2_ws/src/python_parameters/python_parameters/set_parameters_client_node.py

Here is a simple asynchronous client (to paste in set_parameters_client_node.py):

  • It creates a client with service type SetParameters and service name /minimal_param_node/set_parameters
  • The request message needs to be a list of the Parameter msg type which has sub-fields to store the parameter name, data type and value
    • Remember that we can use the terminal command ros2 interface show rcl_interfaces/srv/SetParameters to determine the request (and response) fields
  • The send_request method is passed the new parameter values to set and handles the service call
import rclpy
from rclpy.node import Node
from rcl_interfaces.srv import SetParameters
from rcl_interfaces.msg import Parameter, ParameterType

class MinimalSetParamClientAsync(Node):

    def __init__(self):
        super().__init__('minimal_set_param_client_async')

        self.cli = self.create_client(SetParameters, '/minimal_param_node/set_parameters')

        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        
        self.req = SetParameters.Request()

    def send_request(self, my_parameter_value, your_parameter_value):
        param = Parameter()
        param.name = "my_parameter"
        param.value.type = ParameterType.PARAMETER_STRING
        param.value.string_value = my_parameter_value
        self.req.parameters.append(param)

        param = Parameter()
        param.name = "your_parameter"
        param.value.type = ParameterType.PARAMETER_STRING
        param.value.string_value = your_parameter_value
        self.req.parameters.append(param)

        self.future = self.cli.call_async(self.req)
        rclpy.spin_until_future_complete(self, self.future)
        return self.future.result()

def main():
    rclpy.init()

    minimal_set_param_client = MinimalSetParamClientAsync()

    my_parameter_value = 'a teacher'
    your_parameter_value = 'a student'
    response = minimal_set_param_client.send_request(my_parameter_value, your_parameter_value)
    minimal_set_param_client.get_logger().info('Results: %s'  % (response))

    minimal_set_param_client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Add the entry point to the setup.py file so that it looks like this:

...

    entry_points={
        'console_scripts': [
            'minimal_param_node = python_parameters.python_parameters_node:main',
            'minimal_get_param_client_async = python_parameters.get_parameters_client_node:main',
            'minimal_set_param_client_async = python_parameters.set_parameters_client_node:main',
        ],
    },
)

Compile the package:

cd ~/ros2_ws/
colcon build --packages-select python_parameters

Run the service node first:

source ~/ros2_ws/install/setup.bash
ros2 run python_parameters minimal_param_node

Then the client node in another terminal:

source ~/ros2_ws/install/setup.bash
ros2 run python_parameters minimal_set_param_client_async

You should get something like this in the terminal of the client:

[INFO] [1719045088.071267960] [minimal_set_param_client_async]: Results: rcl_interfaces.srv.SetParameters_Response(results=[rcl_interfaces.msg.SetParametersResult(successful=True, reason=''), rcl_interfaces.msg.SetParametersResult(successful=True, reason='')])

And this in the terminal of the service node:

[INFO] [1719045085.623969364] [minimal_param_node]: You are you!
[INFO] [1719045086.613542065] [minimal_param_node]: I am me!
[INFO] [1719045086.614106714] [minimal_param_node]: You are you!
[INFO] [1719045087.613402451] [minimal_param_node]: I am me!
[INFO] [1719045087.613898409] [minimal_param_node]: You are you!
[INFO] [1719045088.613406942] [minimal_param_node]: I am a teacher!
[INFO] [1719045088.613964772] [minimal_param_node]: You are a student!
[INFO] [1719045088.613406942] [minimal_param_node]: I am a teacher!
[INFO] [1719045088.613964772] [minimal_param_node]: You are a student!
    
...

Just like in my previous tutorial (How to manipulate multiple parameters at once from the command line using parameter services), you managed to set multiple parameters at once- only this time it was from within another node! This opens up the possibility of scripted interaction with external parameters rather than being restricted to just the CLI!

Now you know how to interact with parameter services in code.

Congratulations. You now have a basic understanding of parameters and are familiar with creating and using services.

To learn more advanced topics about ROS 2, have a look at the course below:

We hope this post was really helpful to you.

This tutorial is created by Robotics Ambassador Ernest.

Video Tutorial

 

Topics: parameter | service
Masterclass 2023 batch2 blog banner

Check Out These Related Posts

0 Comments

Pin It on Pinterest

Share This