Creating a dedicated package for custom interfaces

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.

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.

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: /home/murilo/git/ROS2_Tutorial/ros2_tutorial_workspace/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['murilo <murilomarinho@ieee.org>']
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 identitifers:
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

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.

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

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

With the AmazingQuote.msg, we have seen how to use built-in types. Let’s use the service to learn two more possibilities. Let us use a message from the same package and a message from another package. Services cannot be used to define other services.

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

WhatIsThePoint.srv

1# WhatIsThePoint.srv from https://ros2-tutorial.readthedocs.io
2# Receives an AmazingQuote and returns what is the point
3AmazingQuote quote
4---
5geometry_msgs/Point point

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

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

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  
21  # Services
22  "srv/WhatIsThePoint.srv"
23
24)
25
26rosidl_generate_interfaces(${PROJECT_NAME}
27  ${interface_files}
28  DEPENDENCIES
29  geometry_msgs
30
31)
32
33ament_export_dependencies(
34  rosidl_default_runtime
35)
36#### ROS2 Interface Directives [END] ####
37
38if(BUILD_TESTING)
39  find_package(ament_lint_auto REQUIRED)
40  # the following line skips the linter which checks for copyrights
41  # comment the line when a copyright and license is added to all source files
42  set(ament_cmake_copyright_FOUND TRUE)
43  # the following line skips cpplint (only works in a git repo)
44  # comment the line when this package is in a git repo and when
45  # a copyright and license is added to all source files
46  set(ament_cmake_cpplint_FOUND TRUE)
47  ament_lint_auto_find_test_dependencies()
48endif()
49
50ament_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"
      
      # Services
      "srv/WhatIsThePoint.srv"
    
    )
    

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

If you don’t remember why we’re building with these commands, see Always source after you build.

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/srv/WhatIsThePoint
package_with_interfaces/msg/AmazingQuote

and we can further get more specific info on AmazingQuote.msg

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 WhatIsThePoint.srv

ros2 interface show package_with_interfaces/srv/WhatIsThePoint

which returns expanded information on each field of the service

# WhatIsThePoint.srv from https://ros2-tutorial.readthedocs.io
# Receives an AmazingQuote and returns what is the point
AmazingQuote quote
   int32 id
   string quote
   string philosopher_name
---
geometry_msgs/Point point
   float64 x
   float64 y
   float64 z