ROS2 Programming Basics Using Python

ROS2 Programming Basics Using Python

What we are going to learn:

How to send a message using Topic Publisher and Subscriber using Python

To ensure stability, I’ll guide you through ROS2 Foxy on Ubuntu 20.04 system. I’ll be using Docker images for installation, but the process is similar to a local Ubuntu setup.

Prerequisites:

Ubuntu 20.04 installed on your computer.

We will actually create and run a ROS2 package that sends and receives messages through Publisher and Subscriber.

If you want to learn ROS 2 Python in a practical, hands-on way, check out the course ROS 2 Basics in 5 Days: https://app.theconstruct.ai/courses/132

In this course, you’ll cover the foundational concepts needed to start working with ROS 2, as well as more advanced topics, all while engaging in hands-on practice.

1. Make Package:


The command to create a package is as follows:

ros2 pkg create [package_name] --build-type [build_type] --dependencies [dependent package1][dependent package2]

Lets’ make ros2_ws and create the ros_topic_pkg package

mkdir ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create ros_topic_pkg --build-type ament_python --dependencies rclpy std_msgs

2. Write Publisher and Subscriber script

Firstly, we will make empty script publisher.py, subscriber.py in ros_topic_pkg_folder

cd ~/ros2_ws/src/ros_topic_pkg/ros_topic_pkg
touch publisher.py
touch subscriber.py

After make empty script, Using Vim or VsCode, write down this publisher and subscriber code

publihser.py
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
from std_msgs.msg import String

class Publisher(Node):
def __init__(self):
super().__init__('Publisher')
qos_profile = QoSProfile(depth=10)
self.publisher = self.create_publisher(String, 'topic', qos_profile)
self.timer = self.create_timer(1, self.publish_msg)
self.count = 0

def publish_msg(self):
msg = String()
msg.data = 'Number: {0}'.format(self.count)
self.publisher.publish(msg)
self.get_logger().info('Published message: {0}'.format(msg.data))
self.count += 1

def main(args=None):
rclpy.init(args=args)
node = Publisher()
try:
rclpy.spin(node)
except KeyboardInterrupt:
node.get_logger().info('Keyboard Interrupt')
finally:
node.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

subscriber.py
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
from std_msgs.msg import String​

class Subscriber(Node):

def __init__(self):
super().__init__('Subscriber')
qos_profile = QoSProfile(depth=10)
self.subscriber = self.create_subscription(
String,
'topic',
self.subscribe_topic_message,
qos_profile)

def subscribe_topic_message(self, msg):
self.get_logger().info('Received message: {0}'.format(msg.data))​

def main(args=None):
rclpy.init(args=args)
node = Subscriber()
try:
rclpy.spin(node)
except KeyboardInterrupt:
node.get_logger().info('Keyboard Interrupt')
finally:
node.destroy_node()
rclpy.shutdown()​

if __name__ == '__main__':
main()


3. Write a Python package configuration file

Lastly, we have to write down this setup.py file to build Ros2 package

from setuptools import find_packages, setup

package_name = 'ros_topic_pkg'

setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='user',
maintainer_email='user@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'publisher = ros_topic_pkg.publisher:main',
'subscriber = ros_topic_pkg.subscriber:main'
],
},
)

4 Build package:

If you write down all the code correctly, Now we will build ros2_topic pkg

cd ~/ros2_ws && colcon build --symlink-install

If the build is completely normal, you must load the configuration file and set the node for the executable package to run the build node

You can see the file that writes a lot of commands, at the end of the file, Input i to change insert mode and write
source install/setup.bash

Now let’s run the publisher and subscriber we created

First, run publisher to issue a topic

ros2 run ros_topic_pkg publisher

Then Run a new terminal and run the Subscriber after proceeding with the configuration file load

source install/setup.bash


If you wrote the code correctly, you can see that the subscriber is properly subscribing to the numbers issued by the publisher.

Congratulations. You have now learned how to send a message using Topic Publisher and Subscriber using Python.

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

We hope this tutorial was really helpful to you.

This tutorial is created by Robotics Ambassador Park.

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

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

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

 

Ubuntu 20.04 ROS2 Foxy Install Tutorial

Ubuntu 20.04 ROS2 Foxy Install Tutorial

What we are going to learn:

Installing ROS 2 Foxy on Ubuntu 20.04.

Prerequisites:

Ubuntu 20.04 installed on your computer.

I install ROS 2 Foxy in Docker images, but It is similar at Local Ubuntu, too
If you are using the Ubuntu 20.04 image in a Docker environment, nothing will be installed.
In this case, you can go through several steps for initial setup and then install by following the ROS2 Foxy Install Document above.

If you want to learn more about ROS 2 and dive deeper into robotics, check out the Robotics Developer Masterclass, where you’ll master robotics development from scratch and get 100% job-ready to work at robotics companies.

 

1. Initial Setup:

 

If you’re using the Ubuntu 20.04 image in a Docker environment, run the following commands for initial setup:
apt update
apt install sudo

For local installations, skip this step if sudo is already installed.

If sudo (Super User do) is installed, proceed according to the Document.
I used this link to install ros2 foxy : https://docs.ros.org/en/foxy/Installation/Ubuntu-Install-Debians.html#install-ros-2-packages

Proceeding through all steps in order is the fastest way to install without errors.

2. Setting Locale to UTF-8:

locale # check for UTF-8

sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8

locale # verify settings

3. After confirming UTF-8 with the locale command, proceed with environment settings.

1. Ubuntu Universe repository enable
sudo apt install software-properties-common
sudo add-apt-repository universe

2. add ROS 2 GPG key
sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg

3. add repository to sources list
echo “deb [arch=$(dpkg –print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main” | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

4. Installing ROS2 Package :
sudo apt update
sudo apt upgrade

Here is really Important step, Binary Install Ros2 foxy. This is not by Source file. I will take more than 10 minutes.
Desktop Install (Recommended): ROS, RViz, demos, tutorials.
sudo apt install ros-foxy-desktop python3-argcomplete

ROS-Base Install (Bare Bones): Communication libraries, message packages, command line tools. No GUI tools.
sudo apt install ros-foxy-ros-base python3-argcomplete

Install Development tools
sudo apt install ros-dev-tools

If you clear all the step, You can use Ros2 Foxy
use this command to check
cd /opt/ros/foxy
You can see /opt/ros/foxy folder.
If you want to check Ros2 is clearly install

4. Environment Setup:

Before Using Ros2 Foxy, You have to Environment setup.
In Document,
source /opt/ros/foxy/setup.bash
use this command, But by this way, You have to do this step all the time you close terminal.
So I recommend you write in ~/.bashrc file.

For this way, You have to install vim
sudo apt install vim

If you successfully install Vim, use this command to open ~/.bashrc
vi ~/.bashrc

You can see the file that write a lot of command, at the end of the file, Input i to change insert mode and write
source /opt/ros/foxy/setup.bash

then use :wq command, save the file.
And use this command to source ~/.bashrc file
source ~/.bashrc
Then You can use ROS2 Foxy, Without working on the source every time

 

5. Verification: To verify a successful installation, run an example:

Now, finally, we can check whether ROS2 is installed properly through one example.

You have to run two terminal.
At one terminal
ros2 run demo_nodes_cpp talker

another Terminal
ros2 run demo_nodes_py listener

You can see that messages are exchanged and the same result is shown. If this example runs well, you have performed a clean installation of ROS2 Foxy. Now you can freely use ROS2 Foxy.

To ensure stability, I’ll guide you through installing ROS2 Foxy on your Ubuntu 20.04 system. If you prefer watching a detailed video tutorial (in Korean), you can check this video below.

Video Tutorial (Korean)

How to manipulate parameters at runtime | ROS2 Humble Python Tutorial

How to manipulate parameters at runtime | ROS2 Humble Python Tutorial

What you will learn:

How to manipulate multiple parameters in ROS2 from the command line using parameter services

Overview

Parameters in ROS 2 can be get, set, listed, and described through a set of services for nodes that are already running. In fact, the well-known and convenient ros2 param command-line tool is a wrapper around these service calls that makes it easy to manipulate parameters from the command-line.

In this tutorial, you will learn how to manipulate multiple parameters in ROS2 from the command line using parameter services rather than ros2 param commands which can only work with one parameter at a time. You will get an introduction to using all of the parameter services via the CLI, not just SetParameters:

  • srv/GetParameterTypes
  • srv/DescribeParameters
  • srv/SetParametersAtomically
  • srv/GetParameters
  • srv/ListParameters
  • srv/SetParameters

Prerequisite:

  • Basic understanding of parameters
  • Familiarity with using the ros2 param command-line tool
  • Basic knowledge of services

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 ROS2 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/6111a6de/

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

Create a python package called python_parameters:

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

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()

Configure the package and test

Modify setup.py

State the entry point:

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

Compile the package

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

Run the node

In one terminal, 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!

Setting a parameter using ros2 param set

In a different terminal, get a list of all parameters:

ros2 param list

You should see both my_parameter and your_parameter in the output:

/minimal_param_node:
  my_parameter
  use_sim_time
  your_parameter

The usual way to set parameters dynamically during runtime is to use the ros2 param set. For example we can modify the value of your_parameter to be 'a student':

ros2 param set /minimal_param_node your_parameter 'a student'

You can see that the value of your_parameter has changed:

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

However if you want to set multiple parameters at once, you need to use the SetParameters service as shown in the following section.

Setting multiple parameters using SetParameters service type

Kill and restart the node to reset the parameter values before continuing onto this section.

Begin by listing all active services along with their types:

ros2 service list -t

You should see 6 services including the one that we need named /minimal_param_node/set_parameters with type rcl_interfaces/srv/SetParameters:

/minimal_param_node/describe_parameters [rcl_interfaces/srv/DescribeParameters]
/minimal_param_node/get_parameter_types [rcl_interfaces/srv/GetParameterTypes]
/minimal_param_node/get_parameters [rcl_interfaces/srv/GetParameters]
/minimal_param_node/list_parameters [rcl_interfaces/srv/ListParameters]
/minimal_param_node/set_parameters [rcl_interfaces/srv/SetParameters]
/minimal_param_node/set_parameters_atomically [rcl_interfaces/srv/SetParametersAtomically]

Then determine the request message fields by looking at the documentation or running this:

ros2 interface show rcl_interfaces/srv/SetParameters

The result is this:

# A list of parameters to set.
Parameter[] parameters
        string name
        ParameterValue value
                uint8 type
                bool bool_value
                int64 integer_value
                float64 double_value
                string string_value
                byte[] byte_array_value
                bool[] bool_array_value
                int64[] integer_array_value
                float64[] double_array_value
                string[] string_array_value

---
# Indicates whether setting each parameter succeeded or not and why.
SetParametersResult[] results
        bool successful
        string reason

This means we need to send a list of parameters each with a name and value. So our service call should look something like this:

ros2 service call /minimal_param_node/set_parameters rcl_interfaces/srv/SetParameters "{parameters: [{name: 'my_parameter', value: {type: 4, string_value: 'a teacher'}}, {name: 'your_parameter', value: {type: 4, string_value: 'a student'}}]}"

Here, we want to set the my_parameter value to ‘a teacher’ and the your_parameter value to ‘a student’. Note that each value contains a type that we specify using an enumerator integer. In this case, we used 4 to specify the type as a string.

As a result of running the service call, the terminal running the node should look like this

[INFO] [1713695815.623062990] [minimal_param_node]: I am a teacher!
[INFO] [1713695815.623966588] [minimal_param_node]: You are a student!
[INFO] [1713695816.623087905] [minimal_param_node]: I am a teacher!
[INFO] [1713695816.624100118] [minimal_param_node]: You are a student!
[INFO] [1713695817.622963432] [minimal_param_node]: I am a teacher!
[INFO] [1713695817.623739529] [minimal_param_node]: You are a student!

Note that this service only modifies parameters that succeed in being set. For example if you give an incorrect type, then it will not be modified. In the following service call, my_parameter is specified incorrectly as an integer type using 2, whilst your_parameter is correctly specified as a string. Kill and restart the node first to reset the parameter values before running:

ros2 service call /minimal_param_node/set_parameters rcl_interfaces/srv/SetParameters "{parameters: [{name: 'my_parameter', value: {type: 2, string_value: 'a teacher'}}, {name: 'your_parameter', value: {type: 4, string_value: 'a student'}}]}"

As you can see, only your_parameter has changed:

[INFO] [1713748978.412585151] [minimal_param_node]: I am me!
[INFO] [1713748978.413301978] [minimal_param_node]: You are a student!
[INFO] [1713748979.413749236] [minimal_param_node]: I am me!
[INFO] [1713748979.414882976] [minimal_param_node]: You are a student!
[INFO] [1713748980.413437082] [minimal_param_node]: I am me!
[INFO] [1713748980.414682416] [minimal_param_node]: You are a student!

Calling other parameter services

Kill and restart the node to reset the parameter values before continuing onto this section.

SetParametersAtomically

This attempts to set the given list of parameter values just like SetParameters. However if any parameter fails to be set, then none of them are set.

  • Sending the same request from before where my_parameter was set to the wrong type results in none of the parameters being modified.
ros2 service call /minimal_param_node/set_parameters_atomically rcl_interfaces/srv/SetParametersAtomically "{parameters: [{name: 'my_parameter', value: {type: 2, string_value: 'a teacher'}}, {name: 'your_parameter', value: {type: 4, string_value: 'a student'}}]}"
  • This service call (where both types are correct) successfully sets both values.
ros2 service call /minimal_param_node/set_parameters_atomically rcl_interfaces/srv/SetParametersAtomically "{parameters: [{name: 'my_parameter', value: {type: 4, string_value: 'a teacher'}}, {name: 'your_parameter', value: {type: 4, string_value: 'a student'}}]}"

GetParameters

This service call returns a list of parameter values.

ros2 service call /minimal_param_node/get_parameters rcl_interfaces/srv/GetParameters "{names: ['my_parameter', 'your_parameter']}"

GetParameterTypes

This service call returns a list of parameter types.

ros2 service call /minimal_param_node/get_parameter_types rcl_interfaces/srv/GetParameterTypes "{names: ['my_parameter', 'your_parameter']}"

DescribeParameters

This service call returns a list of parameter descriptors.

ros2 service call /minimal_param_node/describe_parameters rcl_interfaces/srv/DescribeParameters "{names: ['my_parameter', 'your_parameter']}"

ListParameters

This service call returns a list of available parameters given an optional list of parameter prefixes. In this case it returns a list of all parameters starting with ‘my’ and ‘your’.

ros2 service call /minimal_param_node/list_parameters rcl_interfaces/srv/ListParameters "{prefixes: ['my', 'your']}"

Now you know how to manipulate parameters at runtime

Congratulations. You now have a basic understanding of parameters.

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

Speed Up Data Processing with Multi-Threaded Execution

Speed Up Data Processing with Multi-Threaded Execution

What you will learn:

How to use multi-threading in ROS2 to execute multiple instances of a callback simultaneously to process received data in parallel and prevent the data queue from overfilling.

Prerequisite:

Basic understanding of publishers, subscribers, and callback functions.

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


Disclaimer

Before we start I’d like to clarify that this may not be the best way to handle the problem of when the rate of data received is faster than the time spent in the callback function as it depends on the specifics of your application. It is usually recommended to keep callbacks as fast and simple as possible and to feed your data to your classes rather than do all processing in the callback. However, the concepts in this tutorial are useful to know and worth understanding!

Opening the rosject

In order to follow this tutorial, we need to have ROS2 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/6111a6de/

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.

Create a ROS2 python package with a simple publisher and subscriber

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

Specify the directory as a Python package by creating an empty __init__.py file. Then add python subscriber and publisher nodes:

cd multithreaded_py_pkg
mkdir scripts
touch scripts/__init__.py
touch scripts/minimal_sub.py scripts/minimal_pub.py

Here is a simple publisher node (to paste in minimal_pub.py):

  • It publishes “Hello World: 1”, “Hello World: 2” and so on every half a second using a timer callback, incrementing the number by one each time.
  • Here we used the String() data type for simplicity but it could easily be an image from a camera
#!/usr/bin/env python3

import rclpy
from rclpy.node import Node

from std_msgs.msg import String

class MinimalPublisher(Node):

    def __init__(self):
        super().__init__('minimal_publisher')
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 0

    def timer_callback(self):
        msg = String()
        msg.data = 'Hello World: %d' % self.i
        self.publisher_.publish(msg)
        self.get_logger().info('Publishing: "%s"' % msg.data)
        self.i += 1

def main(args=None):
    rclpy.init(args=args)

    minimal_publisher = MinimalPublisher()

    rclpy.spin(minimal_publisher)

    minimal_publisher.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Here is a simple subscriber node (to paste in minimal_sub.py):

  • It subscribes to the messages sent by the publisher we created, but sleeps for 2 seconds in each callback to simulate processing the data inside the callback
  • Can guess as to what will happen when we launch the publisher and subscriber?
#!/usr/bin/env python3

import rclpy
from rclpy.node import Node

from std_msgs.msg import String

import time

class MinimalSubscriber(Node):

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

        self.subscription = self.create_subscription(
            String,
            'topic',
            self.listener_callback,
            10)
        self.subscription  # prevent unused variable warning

    def listener_callback(self, msg):
        self.get_logger().info('I heard: "%s"' % msg.data)
        time.sleep(2.0)
        

def main(args=None):
    rclpy.init(args=args)

    minimal_subscriber = MinimalSubscriber()
    
    rclpy.spin(minimal_subscriber)
    
    minimal_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Configure the package and test

Modify setup.py

State the entry points:

entry_points={
        'console_scripts': [
            'minimal_pub_executable = scripts.minimal_pub:main',
            'minimal_sub_executable = scripts.minimal_sub:main'
        ],

So your setup.py should look something like this:

from setuptools import find_packages, setup

package_name = 'multithreaded_py_pkg'

setup(
    name=package_name,
    version='0.0.0',
    packages=find_packages(exclude=['test']),
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='user',
    maintainer_email='user@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'minimal_pub_executable = scripts.minimal_pub:main',
            'minimal_sub_executable = scripts.minimal_sub:main'
        ],
    },
)

Compile the package

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

Run the node

In one terminal, start the subscriber:

source ~/ros2_ws/install/setup.bash
ros2 run multithreaded_py_pkg minimal_sub_executable

In another terminal, start the publisher:

source ~/ros2_ws/install/setup.bash
ros2 run multithreaded_py_pkg minimal_pub_executable

After awhile you should get something like this on the publisher’s side:

[INFO] [1711201052.213651723] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [1711201052.692100911] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [1711201053.192267050] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [1711201053.692179012] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [1711201054.192910371] [minimal_publisher]: Publishing: "Hello World: 4"
[INFO] [1711201054.692213964] [minimal_publisher]: Publishing: "Hello World: 5"
[INFO] [1711201055.192285741] [minimal_publisher]: Publishing: "Hello World: 6"
[INFO] [1711201055.692159941] [minimal_publisher]: Publishing: "Hello World: 7"
[INFO] [1711201056.192324184] [minimal_publisher]: Publishing: "Hello World: 8"
[INFO] [1711201056.692108815] [minimal_publisher]: Publishing: "Hello World: 9"
[INFO] [1711201057.192262238] [minimal_publisher]: Publishing: "Hello World: 10"
[INFO] [1711201057.692280665] [minimal_publisher]: Publishing: "Hello World: 11"
[INFO] [1711201058.192159660] [minimal_publisher]: Publishing: "Hello World: 12"
[INFO] [1711201058.692317751] [minimal_publisher]: Publishing: "Hello World: 13"
[INFO] [1711201059.192241851] [minimal_publisher]: Publishing: "Hello World: 14"
[INFO] [1711201059.692214363] [minimal_publisher]: Publishing: "Hello World: 15"
[INFO] [1711201060.192314571] [minimal_publisher]: Publishing: "Hello World: 16"
[INFO] [1711201060.692367678] [minimal_publisher]: Publishing: "Hello World: 17"
[INFO] [1711201061.192353913] [minimal_publisher]: Publishing: "Hello World: 18"
[INFO] [1711201061.692429832] [minimal_publisher]: Publishing: "Hello World: 19"
[INFO] [1711201062.192177421] [minimal_publisher]: Publishing: "Hello World: 20"

And this on the subscriber’s side:

[INFO] [1711202061.202686531] [minimal_subscriber]: I heard: "Hello World: 0"
[INFO] [1711202063.206075512] [minimal_subscriber]: I heard: "Hello World: 1"
[INFO] [1711202065.209073951] [minimal_subscriber]: I heard: "Hello World: 2"
[INFO] [1711202067.212279267] [minimal_subscriber]: I heard: "Hello World: 3"
[INFO] [1711202069.213966244] [minimal_subscriber]: I heard: "Hello World: 7"
[INFO] [1711202071.216851195] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [1711202073.219905422] [minimal_subscriber]: I heard: "Hello World: 12"

So what happened? Not only is the data delayed as we would expect, but because the data queue is overfilling, we lose data as well and not all published messages are shown on the subscriber’s side.

To fix this by way of multi-threading, we need to understand a little bit about Executors and Callback Groups.

Executors and Callback Groups

Executors

An Executor in ROS2 uses one or more threads of the underlying operating system to invoke callbacks on incoming messages and events:

  • A SingleThreadedExecutor executes callbacks in a single thread, one at a time, and thus the previous callback must always finish before a new one can begin execution.
  • A MultiThreadedExecutor, on the other hand, is capable of executing several callbacks simultaneously. You can create a configurable number of threads to allow for processing multiple messages or events in parallel.

ROS2 nodes invoke the SingleThreadedExecutor when using rclpy.spin. Thus if we want to execute multiple callbacks simultaneously, we will need to use the MultiThreadedExecutor.

With a MultiThreadedExecutor you can select how many threads you need, which is typically one per callback to guarantee that each one can be executed simultaneously.

If you want to know how many threads you can have in your system, you can try this in a Python interpreter:

python3
>>> import multiprocessing
>>> multiprocessing.cpu_count()

Callback groups

Topic subscribers, timers, service servers, action servers all have an argument where you can set the callback group their callbacks will be in.

ROS2 nodes have two types of callback groups:

  • ReentrantCallbackGroup: Callbacks of this group may be executed in parallel.
  • MutuallyExclusiveCallbackGroup: Callbacks of this group must not be executed in parallel.

If nothing is specified, their callbacks will be assigned to the default MutuallyExclusiveCallbackGroup from the node.

You can run different callbacks simultaneously, and execute different instances of the same callback simultaneously. In this tutorial, we will be doing the latter.

More information and examples about Executors and Callback Groups can be found on the ROS2 wiki.

Fixing the issue using multi-threading

Thus we need a MultiThreadedExecutor with the subscription callback in a ReentrantCallbackGroup.

Replace the code in minimal_sub.py with the following (the highlighted lines are areas of change or addition):

  • Note that we have not specified the number of threads e.g. executor = MultiThreadedExecutor(num_threads=4) so it defaults to the CPU count.
  • Note that the node is not managed directly by the rclpy.spin, but by the executor object.
#!/usr/bin/env python3

import rclpy
from rclpy.node import Node

from std_msgs.msg import String

import time

from rclpy.executors import MultiThreadedExecutor
from rclpy.callback_groups import ReentrantCallbackGroup

class MinimalSubscriber(Node):

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

        self.reentrant_callback_group = ReentrantCallbackGroup()

        self.subscription = self.create_subscription(
            String,
            'topic',
            self.listener_callback,
            10,
            callback_group=self.reentrant_callback_group)
        self.subscription  # prevent unused variable warning

    def listener_callback(self, msg):
        self.get_logger().info('I heard: "%s"' % msg.data)
        time.sleep(2.0)
        

def main(args=None):
    rclpy.init(args=args)

    minimal_subscriber = MinimalSubscriber()

    # Use MultiThreadedExecutor
    executor = MultiThreadedExecutor()
    executor.add_node(minimal_subscriber)
    
    try:
        executor.spin()
    except KeyboardInterrupt:
        minimal_subscriber.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

Compile and run the subscriber then publisher.

You should get something like this on the publisher’s side:

[INFO] [1711208685.497376531] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [1711208685.979489812] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [1711208686.479769383] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [1711208686.979284800] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [1711208687.479271344] [minimal_publisher]: Publishing: "Hello World: 4"
[INFO] [1711208687.979414332] [minimal_publisher]: Publishing: "Hello World: 5"
[INFO] [1711208688.479397837] [minimal_publisher]: Publishing: "Hello World: 6"
[INFO] [1711208688.979379973] [minimal_publisher]: Publishing: "Hello World: 7"
[INFO] [1711208689.479369576] [minimal_publisher]: Publishing: "Hello World: 8"
[INFO] [1711208689.979201123] [minimal_publisher]: Publishing: "Hello World: 9"
[INFO] [1711208690.479440648] [minimal_publisher]: Publishing: "Hello World: 10"
[INFO] [1711208690.979274539] [minimal_publisher]: Publishing: "Hello World: 11"
[INFO] [1711208691.479435731] [minimal_publisher]: Publishing: "Hello World: 12"
[INFO] [1711208691.979353466] [minimal_publisher]: Publishing: "Hello World: 13"

And this on the subscriber’s side:

[INFO] [1711208685.498698985] [minimal_subscriber]: I heard: "Hello World: 0"
[INFO] [1711208685.980588672] [minimal_subscriber]: I heard: "Hello World: 1"
[INFO] [1711208686.480816203] [minimal_subscriber]: I heard: "Hello World: 2"
[INFO] [1711208686.980613803] [minimal_subscriber]: I heard: "Hello World: 3"
[INFO] [1711208687.480707978] [minimal_subscriber]: I heard: "Hello World: 4"
[INFO] [1711208687.980819846] [minimal_subscriber]: I heard: "Hello World: 5"
[INFO] [1711208688.480733017] [minimal_subscriber]: I heard: "Hello World: 6"
[INFO] [1711208688.980382320] [minimal_subscriber]: I heard: "Hello World: 7"
[INFO] [1711208689.480969703] [minimal_subscriber]: I heard: "Hello World: 8"
[INFO] [1711208689.980889272] [minimal_subscriber]: I heard: "Hello World: 9"
[INFO] [1711208690.480743475] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [1711208690.980555923] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [1711208691.481019225] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [1711208691.980809084] [minimal_subscriber]: I heard: "Hello World: 13"

This means that it is working successfully and you are running multiple instances of the subscriber callback in parallel!

Congratulations. You now have a basic understanding of callback.

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

How to create custom interfaces using a field type that is a msg from a package | English ROS2 Tutorial

How to create custom interfaces using a field type that is a msg from a package | English ROS2 Tutorial

What you will learn:

How to create custom interfaces using a field type that is a msg from a package, and not just a primitive built-in-type.

Prerequisite:

Basic understanding of the basics of creating custom interfaces with the built-in field types (including best practices such as creating a package dedicated to your interface definitions).

If you want to learn ROS 2 Python in a practical, hands-on way, check out the course ROS 2 Basics in 5 Days: https://app.theconstruct.ai/courses/132

In this course, you’ll cover the foundational concepts needed to start working with ROS 2, as well as more advanced topics, all while engaging in hands-on practice.

 

Opening the rosject

In order to follow this tutorial, we need to have ROS2 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/5f773d8c/

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 ( see this example).

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

Recreating the error: Creating the custom srv

Create a new package named custom_interfaces. This package, however, has to be a CMake package (default when using ros2 pkg create). Currently, there is no way to generate custom interfaces in a pure Python package. However, you can create a custom interface in a CMake package and then use it in a Python node.

cd ~/ros2_ws/src/
ros2 pkg create custom_interfaces

Next, remove the include and src directories and create a directory named srv inside your package. Inside this directory, create the srv file TurnCamera.srv:

cd ~/ros2_ws/src/custom_interfaces
rm -rf include
rm -rf src
mkdir srv
touch srv/TurnCamera.srv

Paste this inside TurnCamera.srv:

float32 deg_turn
---
sensor_msgs/Image camera_image

Then add the following lines to the CMakeLists.txt file:

find_package(rosidl_default_generators REQUIRED)
find_package(sensor_msgs REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/TurnCamera.srv"
)

And also the following lines to the package.xml file within the <package> element:

<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

Finally compile the package by running this in the terminal:

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

You should get this error:

Starting >>> custom_interfaces
--- stderr: custom_interfaces
In file included from /home/user/ros2_ws/build/custom_interfaces/rosidl_typesupport_cpp/custom_interfaces/srv/turn_camera__type_support.cpp:7:
/home/user/ros2_ws/build/custom_interfaces/rosidl_generator_cpp/custom_interfaces/srv/detail/turn_camera__struct.hpp:134:10: fatal error: sensor_msgs/msg/detail/image__struct.hpp: No suchfile or directory
  134 | #include "sensor_msgs/msg/detail/image__struct.hpp"
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
gmake[2]: *** [CMakeFiles/custom_interfaces__rosidl_typesupport_cpp.dir/build.make:86: CMakeFiles/custom_interfaces__rosidl_typesupport_cpp.dir/rosidl_typesupport_cpp/custom_interfaces/srv/turn_camera__type_support.cpp.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:414: CMakeFiles/custom_interfaces__rosidl_typesupport_cpp.dir/all] Error 2
gmake: *** [Makefile:146: all] Error 2
---
Failed   <<< custom_interfaces [20.3s, exited with code 2]

Summary: 0 packages finished [20.8s]
  1 package failed: custom_interfaces
  1 package had stderr output: custom_interfaces

Fixing the error: Adding a dependency

So what was the issue?

Well the OP did not pass the dependencies to rosidl_generate_interfaces by specifying other packages that their custom interface used. In this case, they did not specify the dependency on sensor_msgs which their custom srv TurnCamera.srv used.

They added the find_package line (in green) but did not add the DEPENDENCIES line (in red) in the CMakeLists.txt file. Both lines are necessary when using a msg from another package as a field type:

find_package(rosidl_default_generators REQUIRED)
find_package(sensor_msgs REQUIRED) # Add packages that the custom interfaces depend on

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/TurnCamera.srv"
  DEPENDENCIES sensor_msgs # Add packages that the custom interfaces depend on
)

After adding this line, recompile and source the workspace:

cd ~/ros2_ws
colcon build --packages-select custom_interfaces
source install/setup.bash

It should work now!

Starting >>> custom_interfaces
Finished <<< custom_interfaces [21.1s]

Summary: 1 package finished [21.5s]

You can verify that your custom srv has been created successfully by running these two commands:

ros2 interface list | grep custom

ros2 interface show custom_interfaces/srv/TurnCamera

You should get these results respectively:

custom_interfaces/srv/TurnCamera

float32 deg_turn
---
sensor_msgs/Image camera_image
        std_msgs/Header header #
                builtin_interfaces/Time stamp
                        int32 sec
                        uint32 nanosec
                string frame_id
                                     # Header frame_id should be optical frame of camera
                                     # origin of frame should be optical center of cameara
                                     # +x should point to the right in the image
                                     # +y should point down in the image
                                     # +z should point into to plane of the image
                                     # If the frame_id here and the frame_id of the CameraInfo
                                     # message associated with the image conflict
                                     # the behavior is undefined
        uint32 height                #
        uint32 width                 #
        string encoding       #
                              # taken from the list of strings in include/sensor_msgs/image_encodings.hpp
        uint8 is_bigendian    #
        uint32 step           #
        uint8[] data          #

The same principles should apply for custom msgs and actions with field types that are msgs from packages rather than simply the primitive built-in-types.

Congratulations. You now know how to create ROS 2 Custom Interface.

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

Pin It on Pinterest