Note: This is part of a series where I follow along with the ROS 2 Humble and Gazebo Fortress tutorials and demos. This is not original content.
Today we’ll follow along with this tutorial from the ROS 2 Humble docs, which will teach us how to create a ROS 2 service and client using Python.
It begins!
Background Link to heading
In the last two blog posts on ROS, we followed along line-by-line with the ROS docs for ROS 2 humble to create services and clients in C++ and Python.
However, we did not define our own interfaces (msg and srv files) which determine how the client and service should communicate. Today, we’ll fix that.
We will continue using the ROS 2 workspace we set up previously (used in the last 2 posts). You’ll need to cd
into your workspace’s src
directory, for example:
cd ~/ros2_ws/src
Alter the command above to match your path.
Next, create a new interface package with:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 tutorial_interfaces
We see the usual outputs:
going to create a new package
package name: tutorial_interfaces
destination directory: /home/ubuntu/Documents/ros2_ws_srvcli/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['ubuntu <ubuntu@todo.todo>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: []
creating folder ./tutorial_interfaces
creating ./tutorial_interfaces/package.xml
creating source and include folder
creating folder ./tutorial_interfaces/src
creating folder ./tutorial_interfaces/include/tutorial_interfaces
creating ./tutorial_interfaces/CMakeLists.txt
Our new package is called tutorial_interfaces
. Note that interfaces are always CMake packages.
Create two new msg definitions Link to heading
Now, we descend into tutorial_interfaces
and make new msg
and srv
directories to hold our .msg
and .srv
files, respectively:
cd tutorial_interfaces
mkdir -p msg srv
Inside the msg
directory, create a new file called Num.msg
, containing a single line, like this:
int64 num
This is a custom message that transmits a single 64-bit integer.
In the msg
directory, create a new file called Sphere.msg
, with the following content:
geometry_msgs/Point center
float64 radius
Notice how this message builds on existing message primitives, in this case geometry_msgs/Point
. Messages can build on top of existing message types.
Create the srv definition Link to heading
Inside the srv
folder, create a new file called AddThreeInts.srv
, and add the following content:
int64 a
int64 b
int64 c
---
int64 sum
This is the definition for our custom service, which will accept three integers (a, b, and c) and return their total (sum).
Update CMakeLists.txt
and package.xml
Link to heading
To make the interface code we just created usable, we need to add some code to CMakeLists.txt
. Add these lines:
find_package(geometry_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Num.msg"
"msg/Sphere.msg"
"srv/AddThreeInts.srv"
DEPENDENCIES geometry_msgs # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
)
CMakeLists.txt
should now look like this:
cmake_minimum_required(VERSION 3.8)
project(tutorial_interfaces)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Num.msg"
"msg/Sphere.msg"
"srv/AddThreeInts.srv"
DEPENDENCIES geometry_msgs # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
)
ament_package()
Note: The ROS project docs warn us about naming, here:
The first argument (library name) in the rosidl_generate_interfaces must match ${PROJECT_NAME} (see https://github.com/ros2/rosidl/issues/441#issuecomment-591025515).
We also need to add the following code to package.xml
:
<depend>geometry_msgs</depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
package.xml
should now look like this:
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>tutorial_interfaces</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="ubuntu@todo.todo">ubuntu</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<depend>geometry_msgs</depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
Building the tutorial_interfaces
package
Link to heading
As usual, we return to the root of our workspace and run the colcon build
command:
colcon build --packages-select tutorial_interfaces
Confirm that msg
and srv
have built correctly
Link to heading
Open a terminal and source the workspace with:
source install/setup.bash
Now, check that the Num and Sphere message types are there:
ros2 interface show tutorial_interfaces/msg/Num
Which should return:
int64 num
And:
ros2 interface show tutorial_interfaces/msg/Sphere
Which should return:
geometry_msgs/Point center
float64 x
float64 y
float64 z
float64 radius
And of course:
ros2 interface show tutorial_interfaces/srv/AddThreeInts
Which should return:
int64 a
int64 b
int64 c
---
int64 sum
Did that all work for you? Awesome! Now it’s time to test these new interfaces.
Testing the new interfaces Link to heading
In order to make the testing process as painless as possible, I skip over a couple of the steps in the official tutorial and simply provide you with a zip file you can download which contains a copy of my ROS 2 workspace.
Once you’ve downloaded and unpacked it, cd
into the ros2_ws
directory, and make sure everything builds successfully with:
rosdep install -i --from-path src --rosdistro humble -y
colcon build
Test our new interfaces with Python pub/sub Link to heading
Open two new terminals. In both, remember to source
your workspace:
source install/setup.bash
In one terminal, start the talker
with:
ros2 run py_pubsub talker
In the other, start the listener
with:
ros2 run py_pubsub listener
You should see some messages from the talker
which look like:
[INFO] [1724146489.995891186] [minimal_publisher]: Publishing: "0"
[INFO] [1724146490.477579931] [minimal_publisher]: Publishing: "1"
[INFO] [1724146490.977581266] [minimal_publisher]: Publishing: "2"
[INFO] [1724146491.477577261] [minimal_publisher]: Publishing: "3"
[INFO] [1724146491.977580724] [minimal_publisher]: Publishing: "4"
[INFO] [1724146492.477359366] [minimal_publisher]: Publishing: "5"
And some messages from the listener
which look like:
[INFO] [1724146489.995884167] [minimal_subscriber]: I heard: "0"
[INFO] [1724146490.477875794] [minimal_subscriber]: I heard: "1"
[INFO] [1724146490.977892813] [minimal_subscriber]: I heard: "2"
[INFO] [1724146491.477865809] [minimal_subscriber]: I heard: "3"
[INFO] [1724146491.977882157] [minimal_subscriber]: I heard: "4"
[INFO] [1724146492.477613637] [minimal_subscriber]: I heard: "5"
Hit Ctrl+C
in each terminal to kill the talker and listnener.
Test our new interfaces with C++ pub/sub Link to heading
Open two new terminals. In both, remember to source
your workspace:
source install/setup.bash
In one terminal, start the talker
with:
ros2 run cpp_pubsub talker
In the other, start the listener
with:
ros2 run cpp_pubsub listener
The talker
should print some messages like:
[INFO] [1724146675.582516320] [minimal_publisher]: Publishing: '0'
[INFO] [1724146676.082509010] [minimal_publisher]: Publishing: '1'
[INFO] [1724146676.582511911] [minimal_publisher]: Publishing: '2'
[INFO] [1724146677.082505348] [minimal_publisher]: Publishing: '3'
[INFO] [1724146677.582508951] [minimal_publisher]: Publishing: '4'
[INFO] [1724146678.082502819] [minimal_publisher]: Publishing: '5'
[INFO] [1724146678.582506141] [minimal_publisher]: Publishing: '6'
[INFO] [1724146679.082501894] [minimal_publisher]: Publishing: '7'
[INFO] [1724146679.582508311] [minimal_publisher]: Publishing: '8'
And the listener
will print something like this:
[INFO] [1724146675.582825938] [minimal_subscriber]: I heard: '0'
[INFO] [1724146676.082742174] [minimal_subscriber]: I heard: '1'
[INFO] [1724146676.582728884] [minimal_subscriber]: I heard: '2'
[INFO] [1724146677.082720658] [minimal_subscriber]: I heard: '3'
[INFO] [1724146677.582744049] [minimal_subscriber]: I heard: '4'
[INFO] [1724146678.082721738] [minimal_subscriber]: I heard: '5'
[INFO] [1724146678.582733552] [minimal_subscriber]: I heard: '6'
[INFO] [1724146679.082714553] [minimal_subscriber]: I heard: '7'
[INFO] [1724146679.582729141] [minimal_subscriber]: I heard: '8'
Again, use Ctrl+C
to stop the talker and listener once you’re done testing.
Test our new interfaces with Python service/client Link to heading
Open two new terminals. In both, remember to source
your workspace:
source install/setup.bash
In each of your new terminals, start the service with:
ros2 run py_srvcli service
And the client with:
ros2 run py_srvcli client 2 3 1
The service
prints out:
[INFO] [1724147039.690000770] [minimal_service]: Incoming request
a: 2 b: 3 c: 1
And the client
prints out:
[INFO] [1724147039.700704690] [minimal_client_async]: Result of add_three_ints: for 2 + 3 + 1 = 6
When you’re done testing, use Ctrl+C
in the service
terminal to kill the service (the client should have exited on its own after making the call and receiving a response).
Test our new interfaces with C++ service/client Link to heading
Open two new terminals. In both, remember to source
your workspace:
source install/setup.bash
In each of your new terminals, start the service with:
ros2 run cpp_srvcli server
And the client with:
ros2 run cpp_srvcli client 2 3 1
The server
prints:
[INFO] [1724147132.237475787] [rclcpp]: Ready to add three ints.
[INFO] [1724147138.500488742] [rclcpp]: Incoming request
a: 2 b: 3 c: 1
[INFO] [1724147138.500539510] [rclcpp]: sending back response: [6]
And the client
prints:
[INFO] [1724147138.500713048] [rclcpp]: Sum: 6
When you’re done testing, use Ctrl+C
in the service
terminal to kill the service (the client should have exited on its own after making the call and receiving a response).
Next steps Link to heading
Fun fact: you can “mix and match” the C++ and Python services and clients. You can also do the same with the C++ and Python publishers and subscribers. Why? Becacuse they use the same message format. Pretty neat, huh? This is what allows ROS packages written in different languages (and with different dependencies) to communicate effectively with one another.
Awesome! In our next post we’ll take a look at implementing custom interfaces, following along with this tutorial from the ROS 2 Humble docs. See you around!