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.
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.
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.
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:
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:
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:
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:
[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!
...
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.
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.
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.
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:
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:
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:
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:
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
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?
[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:
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:
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).
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.
Recreating the error: Creating the custom srv
Create a new package named custom_interfaces. This package, however, has to be a CMakepackage (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
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
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: