Creating custom msg and srv files
创建自定义 msg 和 srv 文件

Goal: Define custom interface files (.msg and .srv) and use them with Python and C++ nodes.
目标: 定义自定义接口文件 (.msg.srv) 并在 Python 和 C++ 节点中使用它们。

Tutorial level: Beginner
教程级别: 初学者

Time: 20 minutes
时间: 20 分钟

Background
背景

In previous tutorials you utilized message and service interfaces to learn about topics, services, and simple publisher/subscriber (C++/Python) and service/client (C++/Python) nodes. The interfaces you used were predefined in those cases.
在之前的教程中,您利用消息和服务接口学习了主题服务以及简单的发布/订阅(C++/Python)和服务/客户端(C++/Python)节点。您使用的接口在这些情况下是预定义的。

While it’s good practice to use predefined interface definitions, you will probably need to define your own messages and services sometimes as well. This tutorial will introduce you to the simplest method of creating custom interface definitions.
虽然使用预定义的接口定义是一个好习惯,但有时您可能需要自己定义消息和服务。这个教程将向您介绍创建自定义接口定义的最简单方法。

Prerequisites
先决条件

You should have a ROS 2 workspace.
您应该有一个 ROS 2 工作区

This tutorial also uses the packages created in the publisher/subscriber (C++ and Python) and service/client (C++ and Python) tutorials to try out the new custom messages.
本教程还使用了在发布者/订阅者(C++Python)以及服务/客户端(C++Python)教程中创建的包来尝试新的自定义消息。

Tasks 

1 Create a new package
1 创建一个新包

For this tutorial you will be creating custom .msg and .srv files in their own package, and then utilizing them in a separate package. Both packages should be in the same workspace.
在本教程中,您将创建自定义 .msg.srv 文件,并将它们放在自己的包中,然后在一个单独的包中使用它们。两个包应位于同一个工作区。

Since we will use the pub/sub and service/client packages created in earlier tutorials, make sure you are in the same workspace as those packages (ros2_ws/src), and then run the following command to create a new package:
由于我们将使用在之前教程中创建的 pub/sub 和 service/client 包,请确保您在与这些包相同的工作区中(ros2_ws/src),然后运行以下命令以创建新包:

ros2 pkg create --build-type ament_cmake --license Apache-2.0 tutorial_interfaces

tutorial_interfaces is the name of the new package. Note that it is, and can only be, a CMake package, but this doesn’t restrict in which type of packages you can use your messages and services. You can create your own custom interfaces in a CMake package, and then use it in a C++ or Python node, which will be covered in the last section.
tutorial_interfaces 是新包的名称。请注意,它是且只能是一个 CMake 包,但这并不限制您可以在何种类型的包中使用您的消息和服务。您可以在 CMake 包中创建自己的自定义接口,然后在 C++ 或 Python 节点中使用它,这将在最后一节中介绍。

The .msg and .srv files are required to be placed in directories called msg and srv respectively. Create the directories in ros2_ws/src/tutorial_interfaces:
.msg.srv 文件放置在名为 msgsrv 的目录中是必需的。请在 ros2_ws/src/tutorial_interfaces 中创建这些目录:

mkdir msg srv

2 Create custom definitions
2 创建自定义定义

2.1 msg definition
2.1 消息定义

In the tutorial_interfaces/msg directory you just created, make a new file called Num.msg with one line of code declaring its data structure:
在您刚刚创建的 tutorial_interfaces/msg 目录中,创建一个名为 Num.msg 的新文件,文件中包含一行代码声明其数据结构:

int64 num

This is a custom message that transfers a single 64-bit integer called num.
这是一个自定义消息,传输一个名为num的 64 位整数。

Also in the tutorial_interfaces/msg directory you just created, make a new file called Sphere.msg with the following content:
在您刚刚创建的 tutorial_interfaces/msg 目录中,创建一个名为 Sphere.msg 的新文件,内容如下:

geometry_msgs/Point center
float64 radius

This custom message uses a message from another message package (geometry_msgs/Point in this case).
此自定义消息使用来自另一个消息包的消息(在这种情况下是geometry_msgs/Point)。

2.2 srv definition
2.2 srv 定义

Back in the tutorial_interfaces/srv directory you just created, make a new file called AddThreeInts.srv with the following request and response structure:
在您刚刚创建的 tutorial_interfaces/srv 目录中,创建一个名为 AddThreeInts.srv 的新文件,文件内容包含以下请求和响应结构:

int64 a
int64 b
int64 c
---
int64 sum

This is your custom service that requests three integers named a, b, and c, and responds with an integer called sum.
这是您的自定义服务,它请求三个整数,分别名为 abc,并返回一个名为 sum 的整数。

3 CMakeLists.txt

To convert the interfaces you defined into language-specific code (like C++ and Python) so that they can be used in those languages, add the following lines to CMakeLists.txt:
要将您定义的接口转换为特定语言的代码(如 C++ 和 Python),以便可以在这些语言中使用,请将以下行添加到 CMakeLists.txt

find_package(geometry_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/Num.msg"
  "msg/Sphere.msg"
  "srv/AddThreeInts.srv"
  DEPENDENCIES geometry_msgs # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
)

Note 注意

The first argument (library name) in the rosidl_generate_interfaces must match ${PROJECT_NAME} (see https://github.com/ros2/rosidl/issues/441#issuecomment-591025515).
rosidl_generate_interfaces 中的第一个参数(库名称)必须与${PROJECT_NAME}匹配(请参见https://github.com/ros2/rosidl/issues/441#issuecomment-591025515)。

4 package.xml

Because the interfaces rely on rosidl_default_generators for generating language-specific code, you need to declare a build tool dependency on it. rosidl_default_runtime is a runtime or execution-stage dependency, needed to be able to use the interfaces later. The rosidl_interface_packages is the name of the dependency group that your package, tutorial_interfaces, should be associated with, declared using the <member_of_group> tag.
因为接口依赖于 rosidl_default_generators 来生成特定语言的代码,所以您需要声明对它的构建工具依赖。 rosidl_default_runtime 是一个运行时或执行阶段的依赖,后续使用接口时需要它。 rosidl_interface_packages 是您的包 tutorial_interfaces 应该关联的依赖组的名称,通过 <member_of_group> 标签声明。

Add the following lines within the <package> element of package.xml:
<package>元素中添加以下行,位于package.xml

<depend>geometry_msgs</depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

5 Build the tutorial_interfaces package
5 构建 tutorial_interfaces

Now that all the parts of your custom interfaces package are in place, you can build the package. In the root of your workspace (~/ros2_ws), run the following command:
现在您的自定义接口包的所有部分都已就位,您可以构建该包。在您的工作区根目录(~/ros2_ws)中,运行以下命令:

colcon build --packages-select tutorial_interfaces

Now the interfaces will be discoverable by other ROS 2 packages.
现在接口将可以被其他 ROS 2 包发现。

6 Confirm msg and srv creation
6 确认消息和服务创建

In a new terminal, run the following command from within your workspace (ros2_ws) to source it:
在新的终端中,从您的工作区(ros2_ws)运行以下命令以进行源设置:

source install/setup.bash

Now you can confirm that your interface creation worked by using the ros2 interface show command:
现在您可以通过使用 ros2 interface show 命令来确认您的接口创建是否成功:

ros2 interface show tutorial_interfaces/msg/Num

should return: 应该返回:

int64 num

And 

ros2 interface show tutorial_interfaces/msg/Sphere

should return: 应该返回:

geometry_msgs/Point center
        float64 x
        float64 y
        float64 z
float64 radius

And 

ros2 interface show tutorial_interfaces/srv/AddThreeInts

should return: 应该返回:

int64 a
int64 b
int64 c
---
int64 sum

7 Test the new interfaces
7 测试新接口

For this step you can use the packages you created in previous tutorials. A few simple modifications to the nodes, CMakeLists.txt and package.xml files will allow you to use your new interfaces.
在此步骤中,您可以使用之前教程中创建的包。对节点 CMakeLists.txtpackage.xml 文件进行一些简单的修改,将允许您使用新的接口。

7.1 Testing Num.msg with pub/sub
7.1 测试 Num.msg 与 pub/sub

With a few modifications to the publisher/subscriber package created in a previous tutorial (C++ or Python), you can see Num.msg in action. Since you’ll be changing the standard string msg to a numerical one, the output will be slightly different.
通过对之前教程中创建的发布/订阅包进行一些修改(C++Python),您可以看到 Num.msg 的实际效果。由于您将把标准字符串 msg 更改为数字,因此输出会略有不同。

Publisher 出版商

#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp"                                            // CHANGE

using namespace std::chrono_literals;

class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<tutorial_interfaces::msg::Num>("topic", 10);  // CHANGE

    auto timer_callback = [this](){
      auto message = tutorial_interfaces::msg::Num();                                   // CHANGE
      message.num = this->count_++;                                                     // CHANGE
      RCLCPP_INFO_STREAM(this->get_logger(), "Publishing: '" << message.num << "'");    // CHANGE
      publisher_->publish(message);
    };
    timer_ = this->create_wall_timer(500ms, timer_callback);
  }

private:
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<tutorial_interfaces::msg::Num>::SharedPtr publisher_;             // CHANGE
  size_t count_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

Subscriber 订阅者

#include <functional>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp"                                       // CHANGE

using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    auto topic_callback = [this](const tutorial_interfaces::msg::Num & msg){     // CHANGE
      RCLCPP_INFO_STREAM(this->get_logger(), "I heard: '" << msg.num << "'");    // CHANGE
    };
    subscription_ = this->create_subscription<tutorial_interfaces::msg::Num>(    // CHANGE
      "topic", 10, topic_callback);
  }

private:
  rclcpp::Subscription<tutorial_interfaces::msg::Num>::SharedPtr subscription_;  // CHANGE
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

CMakeLists.txt

Add the following lines (C++ only):
添加以下行(仅限 C++):

#...

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)                      # CHANGE

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces)    # CHANGE

add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces)  # CHANGE

install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

ament_package()

package.xml

Add the following line: 添加以下行:

<depend>tutorial_interfaces</depend>

After making the above edits and saving all the changes, build the package:
在进行上述编辑并保存所有更改后,构建该包:

On Linux/macOS: 在 Linux/macOS 上:

colcon build --packages-select cpp_pubsub

On Windows: 在 Windows 上:

colcon build --merge-install --packages-select cpp_pubsub

Then open two new terminals, source ros2_ws in each, and run:
然后打开两个新的终端,在每个终端中源 ros2_ws,并运行:

ros2 run cpp_pubsub talker
ros2 run cpp_pubsub listener

Since Num.msg relays only an integer, the talker should only be publishing integer values, as opposed to the string it published previously:
由于Num.msg仅传递一个整数,讲话者应该只发布整数值,而不是之前发布的字符串:

[INFO] [minimal_publisher]: Publishing: '0'
[INFO] [minimal_publisher]: Publishing: '1'
[INFO] [minimal_publisher]: Publishing: '2'

7.2 Testing AddThreeInts.srv with service/client
7.2 测试 AddThreeInts.srv 与服务/客户端

With a few modifications to the service/client package created in a previous tutorial (C++ or Python), you can see AddThreeInts.srv in action. Since you’ll be changing the original two integer request srv to a three integer request srv, the output will be slightly different.
通过对之前教程中创建的服务/客户端包进行一些修改(C++Python),您可以看到 AddThreeInts.srv 的实际应用。由于您将把原始的两个整数请求 srv 更改为三个整数请求 srv,因此输出会略有不同。

Service 服务

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp"                                        // CHANGE

#include <memory>

void add(const std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Request> request,     // CHANGE
          std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Response>       response)  // CHANGE
{
  response->sum = request->a + request->b + request->c;                                      // CHANGE
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld" " c: %ld",  // CHANGE
                request->a, request->b, request->c);                                         // CHANGE
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);

  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_server");   // CHANGE

  rclcpp::Service<tutorial_interfaces::srv::AddThreeInts>::SharedPtr service =               // CHANGE
    node->create_service<tutorial_interfaces::srv::AddThreeInts>("add_three_ints",  &add);   // CHANGE

  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add three ints.");                     // CHANGE

  rclcpp::spin(node);
  rclcpp::shutdown();
}

Client 客户

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp"                                       // CHANGE

#include <chrono>
#include <cstdlib>
#include <memory>

using namespace std::chrono_literals;

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);

  if (argc != 4) { // CHANGE
      RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_three_ints_client X Y Z");      // CHANGE
      return 1;
  }

  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_client");  // CHANGE
  rclcpp::Client<tutorial_interfaces::srv::AddThreeInts>::SharedPtr client =                // CHANGE
    node->create_client<tutorial_interfaces::srv::AddThreeInts>("add_three_ints");          // CHANGE

  auto request = std::make_shared<tutorial_interfaces::srv::AddThreeInts::Request>();       // CHANGE
  request->a = atoll(argv[1]);
  request->b = atoll(argv[2]);
  request->c = atoll(argv[3]);                                                              // CHANGE

  while (!client->wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
      return 0;
    }
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
  }

  auto result = client->async_send_request(request);
  // Wait for the result.
  if (rclcpp::spin_until_future_complete(node, result) ==
    rclcpp::FutureReturnCode::SUCCESS)
  {
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
  } else {
    RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_three_ints");    // CHANGE
  }

  rclcpp::shutdown();
  return 0;
}

CMakeLists.txt

Add the following lines (C++ only):
添加以下行(仅限 C++):

#...

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)         # CHANGE

add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
  rclcpp tutorial_interfaces)                      # CHANGE

add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
  rclcpp tutorial_interfaces)                      # CHANGE

install(TARGETS
  server
  client
  DESTINATION lib/${PROJECT_NAME})

ament_package()

package.xml

Add the following line: 添加以下行:

<depend>tutorial_interfaces</depend>

After making the above edits and saving all the changes, build the package:
在进行上述编辑并保存所有更改后,构建该包:

On Linux/macOS: 在 Linux/macOS 上:

colcon build --packages-select cpp_srvcli

On Windows: 在 Windows 上:

colcon build --merge-install --packages-select cpp_srvcli

Then open two new terminals, source ros2_ws in each, and run:
然后打开两个新的终端,在每个终端中源 ros2_ws,并运行:

ros2 run cpp_srvcli server
ros2 run cpp_srvcli client 2 3 1

Summary
摘要

In this tutorial, you learned how to create custom interfaces in their own package and how to utilize those interfaces in other packages.
在本教程中,您学习了如何在自己的包中创建自定义接口,以及如何在其他包中使用这些接口。

This tutorial only scratches the surface about defining custom interfaces. You can learn more about it in About ROS 2 interfaces.
本教程仅仅触及了定义自定义接口的表面。您可以在关于 ROS 2 接口中了解更多信息。

Next steps
下一步

The next tutorial covers more ways to use interfaces in ROS 2.
下一个教程将介绍在 ROS 2 中使用接口的更多方法。