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.
In today’s post we’ll follow along step-by-step with the ROS 2 humble docs, as we learn to implement a ROS 2 custom interface.
Let’s go!
In yesterday’s post we created custom msg
and srv
interfaces.
We will be creating a single package to hold the custom interface we are creating today. However, note that the general best practice is to use separate packages. To quote the docs:
While best practice is to declare interfaces in dedicated interface packages, sometimes it can be convenient to declare, create and use an interface all in one package.
Also, remember from yesterday’s post that interface packages can only be CMake
packages. While true, it is possible to include Python code in a CMake package using ament_cmake_python
. Just a note.
Creating a package Link to heading
Let’s just reuse the workspace from yesterday’s tutorial. In the workspace’s src/
folder, run:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 more_interfaces
mkdir more_interfaces/msg
Done? Great. Now we need to create a msg
file inside more_interfaces/msg
which will desribe the fields (and datatypes) of the content of our messages.
Create a new file called AddressBook.msg
with the content:
uint8 PHONE_TYPE_HOME=0
uint8 PHONE_TYPE_WORK=1
uint8 PHONE_TYPE_MOBILE=2
string first_name
string last_name
string phone_number
uint8 phone_type
Open up package.xml
and add the following lines (to ensure our package gets built for C++, Python, and other languages to use):
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
The updated python.xml
file should loke 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>more_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>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
Also, open up CMakeLists.txt
and add the following lines:
find_package(rosidl_default_generators REQUIRED)
set(msg_files
"msg/AddressBook.msg"
)
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
)
ament_export_dependencies(rosidl_default_runtime)
The updated CMakeLists.txt
file should looke like this:
cmake_minimum_required(VERSION 3.8)
project(more_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(rosidl_default_generators REQUIRED)
set(msg_files
"msg/AddressBook.msg"
)
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
)
ament_export_dependencies(rosidl_default_runtime)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
Write code to use the interface Link to heading
Let’s add code that uses this new interface. Create a file in more_interfaces/src
called publish_address_book.cpp
. It should look like this:
#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "more_interfaces/msg/address_book.hpp"
using namespace std::chrono_literals;
class AddressBookPublisher : public rclcpp::Node
{
public:
AddressBookPublisher()
: Node("address_book_publisher")
{
address_book_publisher_ =
this->create_publisher<more_interfaces::msg::AddressBook>("address_book", 10);
auto publish_msg = [this]() -> void {
auto message = more_interfaces::msg::AddressBook();
message.first_name = "John";
message.last_name = "Doe";
message.phone_number = "1234567890";
message.phone_type = message.PHONE_TYPE_MOBILE;
std::cout << "Publishing Contact\nFirst:" << message.first_name <<
" Last:" << message.last_name << std::endl;
this->address_book_publisher_->publish(message);
};
timer_ = this->create_wall_timer(1s, publish_msg);
}
private:
rclcpp::Publisher<more_interfaces::msg::AddressBook>::SharedPtr address_book_publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<AddressBookPublisher>());
rclcpp::shutdown();
return 0;
}
It’s this line which actually includes our new msg
:
#include "more_interfaces/msg/address_book.hpp"
We need to add the below code to CMakeLists.txt
as a target for the new node:
find_package(rclcpp REQUIRED)
add_executable(publish_address_book src/publish_address_book.cpp)
ament_target_dependencies(publish_address_book rclcpp)
install(TARGETS
publish_address_book
DESTINATION lib/${PROJECT_NAME})
Because our msg
is defined inside the current package, we also need to include these lines:
rosidl_get_typesupport_target(cpp_typesupport_target
${PROJECT_NAME} rosidl_typesupport_cpp)
target_link_libraries(publish_address_book "${cpp_typesupport_target}")
Note: The lines above are only necessary because interface is defined in the same package as our node.
The (newly updated) complete CMakeLists.txt
file should look like this:
cmake_minimum_required(VERSION 3.8)
project(more_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(rosidl_default_generators REQUIRED)
set(msg_files
"msg/AddressBook.msg"
)
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
)
ament_export_dependencies(rosidl_default_runtime)
find_package(rclcpp REQUIRED)
add_executable(publish_address_book src/publish_address_book.cpp)
ament_target_dependencies(publish_address_book rclcpp)
install(TARGETS
publish_address_book
DESTINATION lib/${PROJECT_NAME})
rosidl_get_typesupport_target(cpp_typesupport_target
${PROJECT_NAME} rosidl_typesupport_cpp)
target_link_libraries(publish_address_book "${cpp_typesupport_target}")
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
Build and test Link to heading
Return to the root of your workspace, and run:
colcon build --packages-up-to more_interfaces
We can test that our publisher sends messages in the AddressBook
format with:
source install/local_setup.bash
ros2 run more_interfaces publish_address_book
You should see the following message repeating over and over:
Publishing Contact
First:John Last:Doe
Of course, the C++ code is only logging the first and last name to the console’s output.
If we want to see the whole message, we need to open up another terminal (while the publisher is still running) and type:
source install/setup.bash
ros2 topic echo /address_book
Which will of course show the entire message, as published to the address_book
topic (repeating):
first_name: John
last_name: Doe
phone_number: '1234567890'
phone_type: 2
---
Next Link to heading
Awesome! We built a new interface as part of a single package. Next we’ll look at using customer parameters in C++ (these are parameters we can set from a launch file).
We’ll do this for both C++ and Python.