備註

您正在閱讀開發版本的文件。對於最新發行的版本,請參見 Lyrical

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: Framework | Content-type: how-to | Experience: beginner, intermediate

摘要

Interfaces define how nodes exchange data. ROS offers three main interface types:

  • Topics (.msg files)

  • Services (.srv files)

  • Actions (.action files)

Learn more about interfaces

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.

小訣竅

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.

先備條件

  1. Install ROS, and create your workspace.

  2. Make sure you understand how to create packages.

步驟

備註

For our examples, we are using the msg interface type, but the steps below apply to all interface types.

  1. In your workspace src folder, create a more_interfaces CMake package with a folder for interface definitions. For example:

    $ ros2 pkg create --build-type ament_cmake more_interfaces
    $ mkdir -p more_interfaces/msg
    

    備註

    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.

  2. 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.

    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
    geometry_msgs/Point location
    
  3. 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.

    <build_depend>rosidl_default_generators</build_depend>
    <exec_depend>rosidl_default_runtime</exec_depend>
    <depend>geometry_msgs</depend>
    <member_of_group>rosidl_interface_packages</member_of_group>
    
  4. In CMakeLists.txt, add the required code to make the runtime libraries available and to generate source files from your interface definition. For example:

    find_package(rosidl_default_generators REQUIRED)
    find_package(geometry_msgs REQUIRED)
    set(msg_files "msg/AddressBook.msg")
    rosidl_generate_interfaces(${PROJECT_NAME} ${msg_files}
      DEPENDENCIES geometry_msgs
    )
    ament_export_dependencies(rosidl_default_runtime)
    
  5. 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.

    #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;
            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;
    
            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;
    }
    
  6. In CMakeLists.txt, create a new target so the node builds correctly. For example:

    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})
    
  7. In CMakeLists.txt, link the node to your interface. For example:

    rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} rosidl_typesupport_cpp)
    target_link_libraries(publish_address_book "${cpp_typesupport_target}")
    
  8. To test your new interface, do the following:

    1. In your workspace root, build the package.

    2. Source the workspace and run the node that uses the interface.

      For example:

      $ cd ~/ros2_ws
      $ colcon build --packages-up-to more_interfaces
      $ source install/local_setup.bash
      $ ros2 run more_interfaces publish_address_book
      
    3. Check the interface or interact with it.

      For example, for a message interface, you could open another terminal and use the following code:

      $ source install/setup.bash
      $ ros2 topic echo /address_book