From dbd32a1aeffed4a2453c9167eb9a41fa7147ec61 Mon Sep 17 00:00:00 2001 From: Kacper Bojakowski Date: Fri, 20 Mar 2026 17:25:26 +0100 Subject: [PATCH 1/5] Update page on custom interfaces Updated the article "Implementing custom interfaces - how to" as approved by Geoff and 3di. --- source/How-To-Guides.rst | 1 + ...ingle-Package-Define-And-Use-Interface.rst | 244 +++++++++ .../Tutorials/Beginner-Client-Libraries.rst | 1 - .../Custom-ROS2-Interfaces.rst | 2 +- ...ingle-Package-Define-And-Use-Interface.rst | 471 ------------------ 5 files changed, 246 insertions(+), 473 deletions(-) create mode 100644 source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst delete mode 100644 source/Tutorials/Beginner-Client-Libraries/Single-Package-Define-And-Use-Interface.rst diff --git a/source/How-To-Guides.rst b/source/How-To-Guides.rst index c8f3cb4627c..3450b7829dd 100644 --- a/source/How-To-Guides.rst +++ b/source/How-To-Guides.rst @@ -51,3 +51,4 @@ If you are new and looking to learn the ropes, start with the :doc:`Tutorials `__ + +While predefined interface definitions are useful at the beginning, you soon realize that they can't meet all your needs. +That's why the ability to create custom interfaces is essential. + +Creating custom interfaces involves preparing a package, specifying interface definitions, and registering the interfaces in ``package.xml`` and ``CMakeLists.txt``. +Using custom interfaces involves configuring a node to include the interfaces in its source, and configuring the node to build with the interfaces in ``CMakeLists.txt``. + +.. tip:: + + The best practice is to declare interfaces in dedicated interface packages, but sometimes it may be more convenient for you to declare, create and use an interface all in one package. + +Prerequisites +------------- + +#. Install :doc:`ROS 2 <../Installation>`, and create your :doc:`workspace <../Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace>`. +#. Make sure you understand how to :doc:`create packages <../Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package>`. + +Steps +----- + +.. note:: + + For our examples, we are using the ``msg`` interface type, but the steps below apply to all interface types. + +#. In your workspace ``src`` folder, create a ``more_interfaces`` CMake package with a folder for interface definitions. + For example: + + .. code-block:: console + + $ ros2 pkg create --build-type ament_cmake more_interfaces + $ mkdir -p more_interfaces/msg + + .. note:: + + In ROS 2, interfaces can only be defined in CMake packages. + You can also use `ament_cmake_python `__ to include Python libraries and nodes in a CMake package. + +#. In your interface definitions folder, create a file in which you provide the definitions for the interface. + For example, for a message interface, you can create an ``AddressBook.msg`` file that collects personal data: + + .. code-block:: text + + 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 + +#. In ``package.xml``, add the following code to register your package as part of interface groups: + ``rosidl_default_generators``: Needed to generate the code during the build. + ``rosidl_default_runtime``: Needed only at run time. + + .. code-block:: xml + + rosidl_default_generators + rosidl_default_runtime + rosidl_interface_packages + +#. In ``CMakeLists.txt``, add the required code to make the runtime libraries available and to generate source files from your interface definition. + For example: + + .. code-block:: cmake + + 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) + +#. In the ``more_interfaces/src`` folder, create a node to interact with your new interface. + For example, for a message interface, create ``publish_address_book.cpp`` with code to publish the message periodically. + + .. code-block:: c++ + + #include + #include + + #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("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::SharedPtr address_book_publisher_; + rclcpp::TimerBase::SharedPtr timer_; + }; + + + int main(int argc, char * argv[]) + { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + + return 0; + } + +#. In ``CMakeLists.txt``, create a new target so the node builds correctly. + For example: + + .. code-block:: cmake + + find_package(rclcpp REQUIRED) + add_executable(publish_address_book src/publish_address_book.cpp) + target_link_libraries(publish_address_book rclcpp::rclcpp) + install(TARGETS publish_address_book DESTINATION lib/${PROJECT_NAME}) + +#. In ``CMakeLists.txt``, link the node to your interface. + For example: + + .. code-block:: cmake + + rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} rosidl_typesupport_cpp) + target_link_libraries(publish_address_book "${cpp_typesupport_target}") + +#. To test your new interface, do the following: + a. In your workspace root, build the package. + b. Source the workspace and run the node that uses the interface. + For example: + + .. tabs:: + + .. group-tab:: Linux + + .. code-block:: console + + $ cd ~/ros2_ws + $ colcon build --packages-up-to more_interfaces + $ source install/local_setup.bash + $ ros2 run more_interfaces publish_address_book + + .. group-tab:: macOS + + .. code-block:: console + + $ cd ~/ros2_ws + $ colcon build --packages-up-to more_interfaces + $ . install/local_setup.bash + $ ros2 run more_interfaces publish_address_book + + .. group-tab:: Windows + + .. code-block:: console + + $ cd /ros2_ws + $ colcon build --merge-install --packages-up-to more_interfaces + $ call install/local_setup.bat + $ ros2 run more_interfaces publish_address_book + + Or using Powershell: + + .. code-block:: console + + $ install/local_setup.ps1 + $ ros2 run more_interfaces publish_address_book + + c. Check the interface or interact with it. + For example, for a message interface, you could open another terminal and use the following code: + + .. tabs:: + + .. group-tab:: Linux + + .. code-block:: console + + $ source install/setup.bash + $ ros2 topic echo /address_book + + .. group-tab:: macOS + + .. code-block:: console + + $ . install/setup.bash + $ ros2 topic echo /address_book + + .. group-tab:: Windows + + .. code-block:: console + + $ call install/setup.bat + $ ros2 topic echo /address_book + + Or using Powershell: + + .. code-block:: console + + $ install/setup.ps1 + $ ros2 topic echo /address_book diff --git a/source/Tutorials/Beginner-Client-Libraries.rst b/source/Tutorials/Beginner-Client-Libraries.rst index 69bb4572f97..6f597acd3be 100644 --- a/source/Tutorials/Beginner-Client-Libraries.rst +++ b/source/Tutorials/Beginner-Client-Libraries.rst @@ -12,7 +12,6 @@ Beginner: Client libraries Beginner-Client-Libraries/Writing-A-Simple-Cpp-Service-And-Client Beginner-Client-Libraries/Writing-A-Simple-Py-Service-And-Client Beginner-Client-Libraries/Custom-ROS2-Interfaces - Beginner-Client-Libraries/Single-Package-Define-And-Use-Interface Beginner-Client-Libraries/Using-Parameters-In-A-Class-CPP Beginner-Client-Libraries/Using-Parameters-In-A-Class-Python Beginner-Client-Libraries/Getting-Started-With-Ros2doctor diff --git a/source/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.rst b/source/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.rst index c045a56cea5..e976834d546 100644 --- a/source/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.rst +++ b/source/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.rst @@ -796,4 +796,4 @@ You can learn more about it in :doc:`About ROS 2 interfaces <../../Concepts/Basi Next steps ---------- -The :doc:`next tutorial <./Single-Package-Define-And-Use-Interface>` covers more ways to use interfaces in ROS 2. +The :doc:`next article <../../How-To-Guides/Single-Package-Define-And-Use-Interface>` covers more ways to use interfaces in ROS 2. diff --git a/source/Tutorials/Beginner-Client-Libraries/Single-Package-Define-And-Use-Interface.rst b/source/Tutorials/Beginner-Client-Libraries/Single-Package-Define-And-Use-Interface.rst deleted file mode 100644 index e3e406d6378..00000000000 --- a/source/Tutorials/Beginner-Client-Libraries/Single-Package-Define-And-Use-Interface.rst +++ /dev/null @@ -1,471 +0,0 @@ -.. _SinglePkgInterface: - -.. redirect-from:: - - Rosidl-Tutorial - Tutorials/Single-Package-Define-And-Use-Interface - -Implementing custom interfaces -============================== - -**Goal:** Learn more ways to implement custom interfaces in ROS 2. - -**Tutorial level:** Beginner - -**Time:** 15 minutes - -.. contents:: Contents - :depth: 2 - :local: - -Background ----------- - -In a :doc:`previous tutorial <./Custom-ROS2-Interfaces>`, you learned how to create custom msg and srv interfaces. - -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. - -Recall that interfaces can currently only be defined in CMake packages. -It is possible, however, to have Python libraries and nodes in CMake packages (using `ament_cmake_python `_), so you could define interfaces and Python nodes together in one package. -We'll use a CMake package and C++ nodes here for the sake of simplicity. - -This tutorial will focus on the msg interface type, but the steps here are applicable to all interface types. - -Prerequisites -------------- - -We assume you've reviewed the basics in the :doc:`./Custom-ROS2-Interfaces` tutorial before working through this one. - -You should have :doc:`ROS 2 installed <../../Installation>`, a :doc:`workspace <./Creating-A-Workspace/Creating-A-Workspace>`, and an understanding of :doc:`creating packages <./Creating-Your-First-ROS2-Package>`. - -As always, don't forget to :doc:`source ROS 2 <../Beginner-CLI-Tools/Configuring-ROS2-Environment>` in every new terminal you open. - -Tasks ------ - -1 Create a package -^^^^^^^^^^^^^^^^^^ - -In your workspace ``src`` directory, create a package ``more_interfaces`` and make a directory within it for msg files: - -.. code-block:: console - - $ ros2 pkg create --build-type ament_cmake --license Apache-2.0 more_interfaces - $ mkdir more_interfaces/msg - -2 Create a msg file -^^^^^^^^^^^^^^^^^^^ - -Inside ``more_interfaces/msg``, create a new file ``AddressBook.msg``, and paste the following code to create a message meant to carry information about an individual: - -:: - - 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 - -This message is composed of these fields: - -* first_name: of type string -* last_name: of type string -* phone_number: of type string -* phone_type: of type uint8, with several named constant values defined - -Note that it's possible to set default values for fields within a message definition. -See :doc:`../../Concepts/Basic/About-Interfaces` for more ways you can customize interfaces. - -Next, we need to make sure that the msg file is turned into source code for C++, Python, and other languages. - -2.1 Build a msg file -~~~~~~~~~~~~~~~~~~~~ - -Open ``package.xml`` and add the following lines: - -.. code-block:: xml - - rosidl_default_generators - - rosidl_default_runtime - - rosidl_interface_packages - -Note that at build time, we need ``rosidl_default_generators``, while at runtime, we only need ``rosidl_default_runtime``. - -Open ``CMakeLists.txt`` and add the following lines: - -Find the package that generates message code from msg/srv files: - -.. code-block:: cmake - - find_package(rosidl_default_generators REQUIRED) - -Declare the list of messages you want to generate: - -.. code-block:: cmake - - set(msg_files - "msg/AddressBook.msg" - ) - -By adding the .msg files manually, we make sure that CMake knows when it has to reconfigure the project after you add other .msg files. - -Generate the messages: - -.. code-block:: cmake - - rosidl_generate_interfaces(${PROJECT_NAME} - ${msg_files} - ) - -Also make sure you export the message runtime dependency: - -.. code-block:: cmake - - ament_export_dependencies(rosidl_default_runtime) - -Now you're ready to generate source files from your msg definition. -We'll skip the compile step for now as we'll do it all together below in step 4. - -3 Use an interface from the same package -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Now we can start writing code that uses this message. - -In ``more_interfaces/src`` create a file called ``publish_address_book.cpp`` and paste the following code: - -.. code-block:: c++ - - #include - #include - - #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("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::SharedPtr address_book_publisher_; - rclcpp::TimerBase::SharedPtr timer_; - }; - - - int main(int argc, char * argv[]) - { - rclcpp::init(argc, argv); - rclcpp::spin(std::make_shared()); - rclcpp::shutdown(); - - return 0; - } - -3.1 The code explained -~~~~~~~~~~~~~~~~~~~~~~ - -Include the header of our newly created ``AddressBook.msg``. - -.. code-block:: c++ - - #include "more_interfaces/msg/address_book.hpp" - -Create a node and an ``AddressBook`` publisher. - -.. code-block:: c++ - - using namespace std::chrono_literals; - - class AddressBookPublisher : public rclcpp::Node - { - public: - AddressBookPublisher() - : Node("address_book_publisher") - { - address_book_publisher_ = - this->create_publisher("address_book"); - -Create a callback to publish the messages periodically. - -.. code-block:: c++ - - auto publish_msg = [this]() -> void { - -Create an ``AddressBook`` message instance that we will later publish. - -.. code-block:: c++ - - auto message = more_interfaces::msg::AddressBook(); - -Populate ``AddressBook`` fields. - -.. code-block:: c++ - - message.first_name = "John"; - message.last_name = "Doe"; - message.phone_number = "1234567890"; - message.phone_type = message.PHONE_TYPE_MOBILE; - -Finally send the message periodically. - -.. code-block:: c++ - - std::cout << "Publishing Contact\nFirst:" << message.first_name << - " Last:" << message.last_name << std::endl; - - this->address_book_publisher_->publish(message); - -Create a 1 second timer to call our ``publish_msg`` function every second. - -.. code-block:: c++ - - timer_ = this->create_wall_timer(1s, publish_msg); - -3.2 Build the publisher -~~~~~~~~~~~~~~~~~~~~~~~ - -We need to create a new target for this node in the ``CMakeLists.txt``: - -.. code-block:: cmake - - find_package(rclcpp REQUIRED) - - add_executable(publish_address_book src/publish_address_book.cpp) - target_link_libraries(publish_address_book rclcpp::rclcpp) - - install(TARGETS - publish_address_book - DESTINATION lib/${PROJECT_NAME}) - -3.3 Link against the interface -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to use the messages generated in the same package we need to use the following CMake code: - -.. code-block:: cmake - - rosidl_get_typesupport_target(cpp_typesupport_target - ${PROJECT_NAME} rosidl_typesupport_cpp) - - target_link_libraries(publish_address_book "${cpp_typesupport_target}") - -This finds the relevant generated C++ code from ``AddressBook.msg`` and allows your target to link against it. - -You may have noticed that this step was not necessary when the interfaces being used were from a different package that was built independently. -This CMake code is only required when you want to use interfaces in the same package as the one in which they are defined. - -4 Try it out -^^^^^^^^^^^^ - -Return to the root of the workspace to build the package: - -.. tabs:: - - .. group-tab:: Linux - - .. code-block:: console - - $ cd ~/ros2_ws - $ colcon build --packages-up-to more_interfaces - - .. group-tab:: macOS - - .. code-block:: console - - $ cd ~/ros2_ws - $ colcon build --packages-up-to more_interfaces - - .. group-tab:: Windows - - .. code-block:: console - - $ cd /ros2_ws - $ colcon build --merge-install --packages-up-to more_interfaces - -Then source the workspace and run the publisher: - -.. tabs:: - - .. group-tab:: Linux - - .. code-block:: console - - $ source install/local_setup.bash - $ ros2 run more_interfaces publish_address_book - - .. group-tab:: macOS - - .. code-block:: console - - $ . install/local_setup.bash - $ ros2 run more_interfaces publish_address_book - - .. group-tab:: Windows - - .. code-block:: console - - $ call install/local_setup.bat - $ ros2 run more_interfaces publish_address_book - - Or using Powershell: - - .. code-block:: console - - $ install/local_setup.ps1 - $ ros2 run more_interfaces publish_address_book - -You should see the publisher relaying the msg you defined, including the values you set in ``publish_address_book.cpp``. - -To confirm the message is being published on the ``address_book`` topic, open another terminal, source the workspace, and call ``topic echo``: - -.. tabs:: - - .. group-tab:: Linux - - .. code-block:: console - - $ source install/setup.bash - $ ros2 topic echo /address_book - - .. group-tab:: macOS - - .. code-block:: console - - $ . install/setup.bash - $ ros2 topic echo /address_book - - .. group-tab:: Windows - - .. code-block:: console - - $ call install/setup.bat - $ ros2 topic echo /address_book - - Or using Powershell: - - .. code-block:: console - - $ install/setup.ps1 - $ ros2 topic echo /address_book - -We won't create a subscriber in this tutorial, but you can try to write one yourself for practice (use :doc:`./Writing-A-Simple-Cpp-Publisher-And-Subscriber` to help). - -5 (Extra) Use an existing interface definition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. note:: - - You can use an existing interface definition in a new interface definition. - For example, let's say there is a message named ``Contact.msg`` that belongs to an existing ROS 2 package named ``rosidl_tutorials_msgs``. - Assume that its definition is identical to our custom-made ``AddressBook.msg`` interface from earlier. - - In that case you could have defined ``AddressBook.msg`` (an interface in the package *with* your nodes) as type ``Contact`` (an interface in a *separate* package). - You could even define ``AddressBook.msg`` as an *array* of type ``Contact``, like so: - - :: - - rosidl_tutorials_msgs/Contact[] address_book - - To generate this message you would need to declare a dependency on ``Contact.msg's`` package, ``rosidl_tutorials_msgs``, in ``package.xml``: - - .. code-block:: xml - - rosidl_tutorials_msgs - - rosidl_tutorials_msgs - - And in ``CMakeLists.txt``: - - .. code-block:: cmake - - find_package(rosidl_tutorials_msgs REQUIRED) - - rosidl_generate_interfaces(${PROJECT_NAME} - ${msg_files} - DEPENDENCIES rosidl_tutorials_msgs - ) - - You would also need to include the header of ``Contact.msg`` in your publisher node in order to be able to add ``contacts`` to your ``address_book``. - - .. code-block:: c++ - - #include "rosidl_tutorials_msgs/msg/contact.hpp" - - You could change the callback to something like this: - - .. code-block:: c++ - - auto publish_msg = [this]() -> void { - auto msg = std::make_shared(); - { - rosidl_tutorials_msgs::msg::Contact contact; - contact.first_name = "John"; - contact.last_name = "Doe"; - contact.phone_number = "1234567890"; - contact.phone_type = contact.PHONE_TYPE_MOBILE; - msg->address_book.push_back(contact); - } - { - rosidl_tutorials_msgs::msg::Contact contact; - contact.first_name = "Jane"; - contact.last_name = "Doe"; - contact.phone_number = "4254242424"; - contact.phone_type = contact.PHONE_TYPE_HOME; - msg->address_book.push_back(contact); - } - - std::cout << "Publishing address book:" << std::endl; - for (auto contact : msg->address_book) { - std::cout << "First:" << contact.first_name << " Last:" << contact.last_name << - std::endl; - } - - address_book_publisher_->publish(*msg); - }; - - Building and running these changes would show the msg defined as expected, as well as the array of msgs defined above. - -Summary -------- - -In this tutorial, you tried out different field types for defining interfaces, then built an interface in the same package where it's being used. - -You also learned how to use another interface as a field type, as well as the ``package.xml``, ``CMakeLists.txt``, and ``#include`` statements necessary for utilizing that feature. - -Next steps ----------- - -Next you will create a simple ROS 2 package with a custom parameter that you will learn to set from a launch file. -Again, you can choose to write it in either :doc:`C++ <./Using-Parameters-In-A-Class-CPP>` or :doc:`Python <./Using-Parameters-In-A-Class-Python>`. - -Related content ---------------- - -There are `several design articles `_ on ROS 2 interfaces and the IDL (interface definition language). From 05c6a7f767be86423b657feec2d66323758a1532 Mon Sep 17 00:00:00 2001 From: Kacper Bojakowski Date: Sun, 5 Apr 2026 23:55:21 +0200 Subject: [PATCH 2/5] Fix against linter errors --- ...ingle-Package-Define-And-Use-Interface.rst | 94 ++++++++++--------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst index 191e4f8df06..41345b84795 100644 --- a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst +++ b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst @@ -170,75 +170,79 @@ Steps target_link_libraries(publish_address_book "${cpp_typesupport_target}") #. To test your new interface, do the following: - a. In your workspace root, build the package. - b. Source the workspace and run the node that uses the interface. - For example: - .. tabs:: + a) In your workspace root, build the package. + + b) Source the workspace and run the node that uses the interface. + + For example: + + .. tabs:: + + .. group-tab:: Linux - .. group-tab:: Linux + .. code-block:: console - .. code-block:: console + $ cd ~/ros2_ws + $ colcon build --packages-up-to more_interfaces + $ source install/local_setup.bash + $ ros2 run more_interfaces publish_address_book - $ cd ~/ros2_ws - $ colcon build --packages-up-to more_interfaces - $ source install/local_setup.bash - $ ros2 run more_interfaces publish_address_book + .. group-tab:: macOS - .. group-tab:: macOS + .. code-block:: console - .. code-block:: console + $ cd ~/ros2_ws + $ colcon build --packages-up-to more_interfaces + $ . install/local_setup.bash + $ ros2 run more_interfaces publish_address_book - $ cd ~/ros2_ws - $ colcon build --packages-up-to more_interfaces - $ . install/local_setup.bash - $ ros2 run more_interfaces publish_address_book + .. group-tab:: Windows - .. group-tab:: Windows + .. code-block:: console - .. code-block:: console + $ cd /ros2_ws + $ colcon build --merge-install --packages-up-to more_interfaces + $ call install/local_setup.bat + $ ros2 run more_interfaces publish_address_book - $ cd /ros2_ws - $ colcon build --merge-install --packages-up-to more_interfaces - $ call install/local_setup.bat - $ ros2 run more_interfaces publish_address_book + Or using Powershell: - Or using Powershell: + .. code-block:: console - .. code-block:: console + $ install/local_setup.ps1 + $ ros2 run more_interfaces publish_address_book - $ install/local_setup.ps1 - $ ros2 run more_interfaces publish_address_book + c) Check the interface or interact with it. - c. Check the interface or interact with it. - For example, for a message interface, you could open another terminal and use the following code: + For example, for a message interface, you could open another terminal and use the following code: - .. tabs:: + .. tabs:: - .. group-tab:: Linux + .. group-tab:: Linux - .. code-block:: console + .. code-block:: console - $ source install/setup.bash - $ ros2 topic echo /address_book + $ source install/setup.bash + $ ros2 topic echo /address_book - .. group-tab:: macOS + .. group-tab:: macOS - .. code-block:: console + .. code-block:: console - $ . install/setup.bash - $ ros2 topic echo /address_book + $ . install/setup.bash + $ ros2 topic echo /address_book - .. group-tab:: Windows + .. group-tab:: Windows - .. code-block:: console + .. code-block:: console - $ call install/setup.bat - $ ros2 topic echo /address_book + $ call install/setup.bat + $ ros2 topic echo /address_book - Or using Powershell: + Or using Powershell: - .. code-block:: console + .. code-block:: console - $ install/setup.ps1 - $ ros2 topic echo /address_book + $ install/setup.ps1 + $ ros2 topic echo /address_book From 51f8c50810fbe5aff6666e87fa1e451a81d14d5b Mon Sep 17 00:00:00 2001 From: Kacper Bojakowski Date: Wed, 6 May 2026 01:35:47 +0200 Subject: [PATCH 3/5] Update after SME review --- ...ingle-Package-Define-And-Use-Interface.rst | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst index 41345b84795..302ab8fc1dd 100644 --- a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst +++ b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst @@ -5,13 +5,11 @@ Implementing custom interfaces - how-to ======================================= -.. centered:: **When predefined interface definitions are not enough, you need to create custom interfaces. - In this article, you will learn how to define and build interfaces with different field types. - This will help you implement custom interfaces in ROS to suit your needs.** +When predefined interface definitions are not enough, you need to create custom interfaces. +In this article, you will learn how to define and build interfaces with different field types. +This will help you implement custom interfaces in ROS to suit your needs. -:: - - Area: ROS-framework | Content-type: how-to | Experience: beginner, intermediate +**Area: ROS-framework | Content-type: how-to | Experience: beginner, intermediate** .. contents:: Contents :depth: 2 @@ -29,8 +27,12 @@ ROS offers three main interface types: `Learn more about interfaces `__ -While predefined interface definitions are useful at the beginning, you soon realize that they can't meet all your needs. -That's why the ability to create custom interfaces is essential. +Before creating a custom interface, do the following: + +1. Check whether a suitable standard message already exists. +2. If no single standard message fits your use case, consider creating a new message composed of standard messages. See standard messages here: https://github.com/ros2/common_interfaces. + +Creating a completely custom message should be the last resort. Creating custom interfaces involves preparing a package, specifying interface definitions, and registering the interfaces in ``package.xml`` and ``CMakeLists.txt``. Using custom interfaces involves configuring a node to include the interfaces in its source, and configuring the node to build with the interfaces in ``CMakeLists.txt``. @@ -38,6 +40,7 @@ Using custom interfaces involves configuring a node to include the interfaces in .. tip:: The best practice is to declare interfaces in dedicated interface packages, but sometimes it may be more convenient for you to declare, create and use an interface all in one package. + Using a dedicated interface package is preferred because it allows multiple packages to share message definitions without sharing any other code contained in the package. Prerequisites ------------- @@ -67,6 +70,7 @@ Steps #. In your interface definitions folder, create a file in which you provide the definitions for the interface. For example, for a message interface, you can create an ``AddressBook.msg`` file that collects personal data: + The ``PHONE_TYPE_*`` constants in this example form an enumerated type pattern for ``phone_type`` values. .. code-block:: text @@ -77,6 +81,7 @@ Steps string last_name string phone_number uint8 phone_type + geometry_msgs/Point location #. In ``package.xml``, add the following code to register your package as part of interface groups: ``rosidl_default_generators``: Needed to generate the code during the build. @@ -86,6 +91,7 @@ Steps rosidl_default_generators rosidl_default_runtime + geometry_msgs rosidl_interface_packages #. In ``CMakeLists.txt``, add the required code to make the runtime libraries available and to generate source files from your interface definition. @@ -94,8 +100,11 @@ Steps .. code-block:: cmake find_package(rosidl_default_generators REQUIRED) + find_package(geometry_msgs REQUIRED) set(msg_files "msg/AddressBook.msg") - rosidl_generate_interfaces(${PROJECT_NAME} ${msg_files}) + rosidl_generate_interfaces(${PROJECT_NAME} ${msg_files} + DEPENDENCIES geometry_msgs + ) ament_export_dependencies(rosidl_default_runtime) #. In the ``more_interfaces/src`` folder, create a node to interact with your new interface. @@ -127,6 +136,9 @@ Steps message.last_name = "Doe"; message.phone_number = "1234567890"; message.phone_type = message.PHONE_TYPE_MOBILE; + message.location.x = 37.7749; + message.location.y = -122.4194; + message.location.z = 0.0; std::cout << "Publishing Contact\nFirst:" << message.first_name << " Last:" << message.last_name << std::endl; From 772d07dbb52ed314f4ab0e828c4a11da5c8d5663 Mon Sep 17 00:00:00 2001 From: Tully Foote Date: Fri, 29 May 2026 13:55:51 -0400 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Tomoya Fujita Co-authored-by: Katherine Scott Signed-off-by: Tully Foote --- .../Single-Package-Define-And-Use-Interface.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst index 302ab8fc1dd..3d6d2e1e937 100644 --- a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst +++ b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst @@ -2,8 +2,8 @@ How-To-Guides/Implementing-custom-interfaces -Implementing custom interfaces - how-to -======================================= +Implementing custom interfaces +============================== When predefined interface definitions are not enough, you need to create custom interfaces. In this article, you will learn how to define and build interfaces with different field types. @@ -41,6 +41,7 @@ Using custom interfaces involves configuring a node to include the interfaces in The best practice is to declare interfaces in dedicated interface packages, but sometimes it may be more convenient for you to declare, create and use an interface all in one package. Using a dedicated interface package is preferred because it allows multiple packages to share message definitions without sharing any other code contained in the package. + Using a dedicated interface package is preferred because it allows multiple packages to share message definitions without sharing any other code contained in the package. Prerequisites ------------- From 5f6f3d0cd630c6b22b9c27a28da56df938ed62d0 Mon Sep 17 00:00:00 2001 From: Kacper Bojakowski Date: Wed, 17 Jun 2026 02:38:54 +0200 Subject: [PATCH 5/5] Update title and metadata --- .../Single-Package-Define-And-Use-Interface.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst index 3d6d2e1e937..85c2443fe86 100644 --- a/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst +++ b/source/How-To-Guides/Single-Package-Define-And-Use-Interface.rst @@ -2,14 +2,14 @@ How-To-Guides/Implementing-custom-interfaces -Implementing custom interfaces -============================== +Implementing custom interfaces - how-to +======================================= When predefined interface definitions are not enough, you need to create custom interfaces. In this article, you will learn how to define and build interfaces with different field types. This will help you implement custom interfaces in ROS to suit your needs. -**Area: ROS-framework | Content-type: how-to | Experience: beginner, intermediate** +**Area: Framework | Content-type: how-to | Experience: beginner, intermediate** .. contents:: Contents :depth: 2 @@ -30,7 +30,8 @@ ROS offers three main interface types: Before creating a custom interface, do the following: 1. Check whether a suitable standard message already exists. -2. If no single standard message fits your use case, consider creating a new message composed of standard messages. See standard messages here: https://github.com/ros2/common_interfaces. +2. If no single standard message fits your use case, consider creating a new message composed of standard messages. + See standard messages here: https://github.com/ros2/common_interfaces. Creating a completely custom message should be the last resort.