【ROS2指南-15】创建自定义消息统一管理包

目标:了解更多在 ROS 2 中实现自定义接口的方法

教程级别:初学者

时间: 15分钟

内容

  • 背景

  • 先决条件

  • 任务

    • 1 创建一个包

    • 2 创建消息文件

    • 3 使用来自同一个包的接口

    • 4 尝试一下

    • 5(额外)使用现有的接口定义

  • 概括

  • 下一步

  • 相关内容

背景

在之前的教程中,您学习了如何创建自定义 msg 和 srv 接口。

虽然最佳实践是在专用接口包中声明接口,但有时在一个包中声明、创建和使用一个接口会很方便。

回想一下,接口目前只能在 CMake 包中定义。但是,可以在 CMake 包中包含 Python 库和节点(使用ament_cmake_python),因此您可以在一个包中一起定义接口和 Python 节点。为了简单起见,我们将在此处使用 CMake 包和 C++ 节点。

本教程将重点介绍 msg 接口类型,但此处的步骤适用于所有接口类型。

先决条件

我们假设您在完成本教程之前已经查看了创建自定义 ROS 2 msg 和 srv 文件教程中的基础知识。

您应该安装了 ROS 2、一个工作区,并且了解创建包。

与往常一样,不要忘记在您打开的每个新终端中获取 ROS 2。

任务

1 创建一个包

在您的工作区src目录中,创建一个包more_interfaces并在其中为 msg 文件创建一个文件夹:

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

2 创建消息文件

在里面more_interfaces/msg,创建一个新文件AddressBook.msg

粘贴以下代码以创建一条消息,用于携带有关个人的信息:

bool FEMALE=true
bool MALE=false

string first_name
string last_name
bool gender
uint8 age
string address

此消息由 5 个字段组成:

  • first_name:字符串类型

  • last_name:字符串类型

  • 性别:bool 类型,可以是 MALE 或 FEMALE

  • 年龄:uint8类型

  • 地址:字符串类型

请注意,可以为消息定义中的字段设置默认值。有关自定义界面的更多方法,请参阅关于 ROS 2 界面。

接下来,我们需要确保将 msg 文件转换为 C++、Python 和其他语言的源代码。

2.1 构建一个msg文件

打开package.xml,并添加以下行:

rosidl_default_generators

rosidl_default_runtime

rosidl_interface_packages

请注意,在构建时,我们需要rosidl_default_generators,而在运行时,我们只需要rosidl_default_runtime.

打开CMakeLists.txt并添加以下行:

找到从 msg/srv 文件生成消息代码的包:

find_package(rosidl_default_generators REQUIRED)

声明要生成的消息列表:

set(msg_files
  "msg/AddressBook.msg"
)

通过手动添加 .msg 文件,我们确保 CMake 知道在您添加其他 .msg 文件后何时必须重新配置项目。

生成消息:

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
)

还要确保导出消息运行时依赖项:

ament_export_dependencies(rosidl_default_runtime)

现在您已准备好从 msg 定义生成源文件。我们现在将跳过编译步骤,因为我们将在下面的第 4 步中一起完成。

2.2 (Extra) 设置多个接口

笔记

您可以使用set来整齐地列出所有接口:

set(msg_files
  "msg/Message1.msg"
  "msg/Message2.msg"
  # etc
  )

set(srv_files
  "srv/Service1.srv"
  "srv/Service2.srv"
   # etc
  )

并像这样一次生成所有列表:

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
  ${srv_files}
)

3 使用来自同一个包的接口

现在我们可以开始编写使用此消息的代码。

more_interfaces/src创建一个名为的文件publish_address_book.cpp并粘贴以下代码:

#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.age = 30;
        message.gender = message.MALE;
        message.address = "unknown";

        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 代码解释

#include "more_interfaces/msg/address_book.hpp"

包括我们新创建的AddressBook.msg.

using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node
{
public:
  AddressBookPublisher()
  : Node("address_book_publisher")
  {
    address_book_publisher_ =
      this->create_publisher("address_book");

创建节点和AddressBook发布者。

auto publish_msg = [this]() -> void {

创建回调以定期发布消息。

auto message = more_interfaces::msg::AddressBook();

创建AddressBook我们稍后将发布的消息实例。

message.first_name = "John";
message.last_name = "Doe";
message.age = 30;
message.gender = message.MALE;
message.address = "unknown";

填充AddressBook字段。

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);

publish_msg创建一个 1 秒计时器以每秒调用我们的函数。

3.2 构建发布者

我们需要在以下位置为此节点创建一个新目标CMakeLists.txt

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})

3.3 链接对接口

为了使用在同一个包中生成的消息,我们需要使用以下 CMake 代码:

rosidl_target_interfaces(publish_address_book
  ${PROJECT_NAME} "rosidl_typesupport_cpp")

4 尝试一下

返回到工作区的根目录来构建包:

cd ~/dev_ws
colcon build --packages-up-to more_interfaces

然后获取工作区并运行发布者:

. install/local_setup.bash
ros2 run more_interfaces publish_address_book

您应该看到发布者转发您定义的消息,包括您在 中设置的值publish_address_book.cpp

要确认正在该address_book主题上发布消息,请打开另一个终端,找到工作区,然后调用:topic echo

. install/setup.bash
ros2 topic echo /address_book

我们不会在本教程中创建订阅者,但您可以尝试自己编写一个用于练习(使用编写简单的发布者和订阅者 (C++)来提供帮助)。

5(额外)使用现有的接口定义

笔记

您可以在新的接口定义中使用现有的接口定义。例如,假设有一条名为 的消息Contact.msg属于现有的名为 的 ROS 2 包rosidl_tutorials_msgsAddressBook.msg假设它的定义与我们之前定制的接口相同。

AddressBook.msg在那种情况下,您可以将(包中节点的接口)定义为类型(单独Contact包中的接口)。您甚至可以定义为type 的数组,如下所示:AddressBook.msgContact

rosidl_tutorials_msgs/Contact[] address_book

要生成此消息,您需要声明对Contact.msg'spackage, rosidl_tutorials_msgs, in 的依赖package.xml

rosidl_tutorials_msgs

rosidl_tutorials_msgs

并在CMakeLists.txt

find_package(rosidl_tutorials_msgs REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
  DEPENDENCIES rosidl_tutorials_msgs
)

您还需要在您的发布者节点中包含标题Contact.msg,以便能够添加contacts到您的address_book.

#include "rosidl_tutorials_msgs/msg/contact.hpp"

您可以将调用改回如下所示:

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.age = 30;
     contact.gender = contact.MALE;
     contact.address = "unknown";
     msg->address_book.push_back(contact);
   }
   {
     rosidl_tutorials_msgs::msg::Contact contact;
     contact.first_name = "Jane";
     contact.last_name = "Doe";
     contact.age = 20;
     contact.gender = contact.FEMALE;
     contact.address = "unknown";
     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);
 };

构建并运行这些更改将显示按预期定义的消息,以及上面定义的消息数组。

概括

在本教程中,您尝试了不同的字段类型来定义接口,然后在使用它的同一个包中构建了一个接口。

您还学习了如何将另一个接口用作字段类型,以及使用该功能所需的package.xmlCMakeLists.txt和语句。#include

下一步

接下来,您将创建一个简单的 ROS 2 程序包,其中包含您将学习如何从启动文件设置的自定义参数。同样,您可以选择用C++或Python编写它。

你可能感兴趣的:(ROS2,机器人)