Creating a dedicated package for custom interfaces#

Changed in version Jazzy: Added AmazingQuoteStamped.msg, MoveStraightIn2D.action and simplified service discussion to use AddPoints.srv.

Warning

Despite this push in ROS2 towards having the users define even the simplest of message types, to define new interfaces in ROS2 we must use an ament_cmake package. It cannot be done with an ament_python package.

See also

The contents of this session were simplified in this version. A more complex example is shown in https://ros2-tutorial.readthedocs.io/en/humble/service_servers_and_clients.html.

All interfaces in ROS2 must be made in an ament_cmake package. We have so far not needed it, but for this scenario we cannot escape. Thankfully, for this we don’t need to dig too deep into CMake, so fear not.

Overview#

We will create a package with the following structure. Besides our good and old package.xml, everything else might be unfamiliar. We will go through those in detail.

package_with_interfaces/
|-- CMakeLists.txt
|-- action
|   `-- MoveStraightIn2D.action
|-- msg
|   |-- AmazingQuote.msg
|   `-- AmazingQuoteStamped.msg
|-- package.xml
`-- srv
    `-- AddPoints.srv

Creating the package#

There isn’t a template for message-only packages using ros2 pkg create. We’ll need to build on top of a mostly empty ament_cmake package.

To take this chance to also learn how to nest messages on other interfaces, we also add the dependency on geometry_msgs.

cd ~/ros2_tutorial_workspace/src
ros2 pkg create package_with_interfaces \
--build-type ament_cmake \
--dependencies geometry_msgs

which again shows our beloved wall of text, with a few highlighted differences because of it being a ament_cmake package.

going to create a new package
package name: package_with_interfaces
destination directory: /root/ros2_tutorial_workspace/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['root <murilo.marinho@manchester.ac.uk>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: ['geometry_msgs']
creating folder ./package_with_interfaces
creating ./package_with_interfaces/package.xml
creating source and include folder
creating folder ./package_with_interfaces/src
creating folder ./package_with_interfaces/include/package_with_interfaces
creating ./package_with_interfaces/CMakeLists.txt

[WARNING]: Unknown license 'TODO: License declaration'.  This has been set in the package.xml, but no LICENSE file has been created.
It is recommended to use one of the ament license identifiers:
Apache-2.0
BSL-1.0
BSD-2.0
BSD-2-Clause
BSD-3-Clause
GPL-3.0-only
LGPL-3.0-only
MIT
MIT-0

The package.xml works the same way as when using ament_python. However, we no longer have a setup.py or setup.cfg, everything is handled by the CMakeLists.txt.

The package.xml dependencies#

Whenever the package has any type of interface, the package.xml must include three specific dependencies. Namely, the ones highlighted below. Edit the package_with_interfaces/package.xml like so

~/ros2_tutorial_workspace/src/package_with_interfaces/package.xml

 1<?xml version="1.0"?>
 2<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
 3<package format="3">
 4  <name>package_with_interfaces</name>
 5  <version>0.0.0</version>
 6  <description>TODO: Package description</description>
 7  <maintainer email="murilomarinho@ieee.org">murilo</maintainer>
 8  <license>TODO: License declaration</license>
 9
10  <buildtool_depend>ament_cmake</buildtool_depend>
11
12  <depend>geometry_msgs</depend>
13
14  <buildtool_depend>rosidl_default_generators</buildtool_depend>
15  <exec_depend>rosidl_default_runtime</exec_depend>
16  <member_of_group>rosidl_interface_packages</member_of_group>
17
18  <test_depend>ament_lint_auto</test_depend>
19  <test_depend>ament_lint_common</test_depend>
20
21  <export>
22    <build_type>ament_cmake</build_type>
23  </export>
24</package>

The message folder#

The convention is to add all messages to a folder called msg. Let’s follow that convention

cd ~/ros2_tutorial_workspace/src/package_with_interfaces
mkdir msg

The message file#

Note

Here is a list of available built-in types for ROS2 interfaces.

Let us create a message file to transfer inspirational quotes between Nodes. For example, the one below.

Use the force, Pikachu!

—Uncle Ben

There are many ways to represent this, but for the sake of the example let us give each message an id and two rather obvious fields. Create a file called AmazingQuote.msg in the folder msg that we just created with the following contents.

~/ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuote.msg

1# AmazingQuote.msg from https://ros2-tutorial.readthedocs.io
2# An inspirational quote a day keeps the therapist away
3int32 id
4string quote
5string philosopher_name

Re-using a message from the same package#

Note

Only messages can be used to define other interfaces. For instance, they can be used to define other messages, services, and actions.

With the AmazingQuote.msg, we have seen how to use built-in types. Let’s use another message, AmazingQuoteStamped.msg, to learn two more possibilities, namely using messages from the same package and messages defined elsewhere.

~/ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuoteStamped.msg

1# AmazingQuoteStamped.msg from https://ros2-tutorial.readthedocs.io
2# An AmazingQuote.msg with a header
3std_msgs/Header header
4AmazingQuote quote

Note that if the message is defined in the same package, the package name does not appear in the message (or service) definition. If the message is defined elsewhere, we have to fully specify the package.

In many ROS2 packages, messages with the suffix Stamped exist. As a rule, those are the same messages but with a additional std_msgs/Header so that they can be timestamped.

The service folder#

The convention is to add all services to a folder called srv. Let’s follow that convention

cd ~/ros2_tutorial_workspace/src/package_with_interfaces
mkdir srv

The service file#

Add the file AddPoints.srv in the srv folder with the following contents

~/ros2_tutorial_workspace/src/package_with_interfaces/srv/AddPoints.srv

1# AddPoints.srv from https://ros2-tutorial.readthedocs.io
2# Adds the values of points `a` and `b` to give the output `result`
3geometry_msgs/Point a
4geometry_msgs/Point b
5---
6geometry_msgs/Point result

The action folder#

The convention is to add all actions to a folder called action. Let’s follow that convention.

cd ~/ros2_tutorial_workspace/src/package_with_interfaces
mkdir action

The action file#

Add the file MoveStraightIn2D.action in the action folder with the following contents

~/ros2_tutorial_workspace/src/package_with_interfaces/action/MoveStraightIn2D.action

1# MoveStraightIn2D.action from https://ros2-tutorial.readthedocs.io
2# Attempts to move from initial position to `desired_position`.
3# Returns `final_position` achieved.
4# Feedback is the Euclidean distance between `initial_position` and the current position.
5geometry_msgs/Point desired_position
6---
7geometry_msgs/Point final_position
8---
9float32 distance

The CMakeLists.txt directives#

Note

The order of the CMake directives is very important and getting the order wrong can result in bugs with cryptic error messages.

If a package is dedicated to interfaces, there is no need to worry too much about the CMake details. We can follow the boilerplate as shown below. Edit the package_with_interfaces/CMakeLists.txt like so

~/ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt

 1cmake_minimum_required(VERSION 3.8)
 2project(package_with_interfaces)
 3
 4if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
 5  add_compile_options(-Wall -Wextra -Wpedantic)
 6endif()
 7
 8# find dependencies
 9find_package(ament_cmake REQUIRED)
10find_package(geometry_msgs REQUIRED)
11# uncomment the following section in order to fill in
12# further dependencies manually.
13# find_package(<dependency> REQUIRED)
14find_package(rosidl_default_generators REQUIRED)
15
16#### ROS2 Interface Directives ####
17set(interface_files
18  # Messages
19  "msg/AmazingQuote.msg"
20  "msg/AmazingQuoteStamped.msg"
21  
22  # Services
23  "srv/AddPoints.srv"
24
25  # Actions
26  "action/MoveStraightIn2D.action"
27
28)
29
30rosidl_generate_interfaces(${PROJECT_NAME}
31  ${interface_files}
32  DEPENDENCIES
33  geometry_msgs
34
35)
36
37ament_export_dependencies(
38  rosidl_default_runtime
39)
40#### ROS2 Interface Directives [END] ####
41
42if(BUILD_TESTING)
43  find_package(ament_lint_auto REQUIRED)
44  # the following line skips the linter which checks for copyrights
45  # comment the line when a copyright and license is added to all source files
46  set(ament_cmake_copyright_FOUND TRUE)
47  # the following line skips cpplint (only works in a git repo)
48  # comment the line when this package is in a git repo and when
49  # a copyright and license is added to all source files
50  set(ament_cmake_cpplint_FOUND TRUE)
51  ament_lint_auto_find_test_dependencies()
52endif()
53
54ament_package()

What to do when adding new interfaces?#

TL;DR Adding new interfaces

  1. Add new dependencies to package.xml

  2. Add each new interface file to set(interface_files ...)

  3. Add new dependencies to rosidl_generate_interfaces(... DEPENDENCIES ...)

Yes, you MUST add the same dependency in two places!

If additional interfaces are required

  1. Modify the package.xml to have any additional dependencies. See Handling dependencies (package.xml) for more details.

  2. Add each new interface file to set(interface_files ...)

    set(interface_files
      # Messages
      "msg/AmazingQuote.msg"
      "msg/AmazingQuoteStamped.msg"
      
      # Services
      "srv/AddPoints.srv"
    
      # Actions
      "action/MoveStraightIn2D.action"
    
    )
    

Note

There are ways to use CMake directives to automatically add all files in a given folder and provide other conveniences. In hindsight, that might seem to reduce our burden. However, the method described herein is the one used in the official ROS2 packages (e.g. geometry_msgs), so let us trust that they have good reasons for it.

Build and source#

Before we proceed, let us build and source once.

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

Note

For additional explanation and troubleshooting tips, see Always source after you build.

Warning

colcon will not work properly if your terminal has an active venv.

Getting info on custom interfaces#

As long as the package has been correctly built and sourced, we can easily get information on its interfaces using ros2 interface.

For instance, running

ros2 interface package package_with_interfaces

returns

package_with_interfaces/msg/AmazingQuote
package_with_interfaces/msg/AmazingQuoteStamped
package_with_interfaces/action/MoveStraightIn2D
package_with_interfaces/srv/AddPoints

and we can further get more specific info on AmazingQuote (or AmazingQuoteStamped)

ros2 interface show package_with_interfaces/msg/AmazingQuote

which returns

# AmazingQuote.msg from https://ros2-tutorial.readthedocs.io
# An inspirational quote a day keeps the therapist away
int32 id
string quote
string philosopher_name

alternatively, we can do the same for AddPoints

ros2 interface show package_with_interfaces/srv/AddPoints

which returns expanded information on each field of the service

# AddPoints.srv from https://ros2-tutorial.readthedocs.io
# Adds the values of points `a` and `b` to give the output `result`
geometry_msgs/Point a
        float64 x
        float64 y
        float64 z
geometry_msgs/Point b
        float64 x
        float64 y
        float64 z
---
geometry_msgs/Point result
        float64 x
        float64 y
        float64 z

Lastly, we can do the same for MoveStraightIn2D

ros2 interface show package_with_interfaces/action/MoveStraightIn2D

which returns expanded information about all fields of the action

# MoveStraightIn2D.action from https://ros2-tutorial.readthedocs.io
# Attempts to move from initial position to `desired_position`.
# Returns `final_position` achieved.
# Feedback is the norm of the error between `initial_position` and the current position.
geometry_msgs/Point desired_position
        float64 x
        float64 y
        float64 z
---
geometry_msgs/Point final_position
        float64 x
        float64 y
        float64 z
---
float32 error_norm