7-实现自定义接口

背景

    在上一篇教程中,学习了如何创建自定义msg和srv接口。
    实际中在专用的接口包中声明接口比较合适,但有时在一个包中声明、创建和使用一个接口也是可以的。
    回想一下,接口目前只能在CMake包中定义。然而,在CMake包中可能有Python库和节点,可以在一个包中定义接口和Python节点。。
    本节将使用CMake包和c++节点介绍msg接口类型,但这里的步骤适用于所有接口类型。

基础准备

    在学习本节之前应该熟悉创建自定义msg和srv文件教程中的基础知识。
    安装ROS 2,创建工作空间,并且熟悉如何创建包。
    与往常一样,不要忘记在打开的每个新终端中source ROS 2。

任务

1创建一个包

    在你的工作空间src目录下,创建一个名为more_interfaces的包,并为msg文件创建一个目录:

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

2 创建一个msg文件

    在more_interfaces/msg中,创建一个新文件AddressBook.Msg,并粘贴以下代码,以创建一个携带个人信息的消息:

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

    该消息由以下字段组成:

    first_name:字符串类型
    last_name:字符串类型
    phone_number:字符串类型
    phone_type:类型为uint8,定义了几个命名常量值

    注意,可以在消息定义中为字段设置默认值。
    接下来,我们需要确保将msg文件转换为c++代码。

2.1 编译一个msg文件

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

<buildtool_depend>rosidl_default_generators</buildtool_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

    注意,在编译时,我们需要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 配置多个接口

    你可以在CMakeLists.txt中使用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 <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;
}

3.1 代码释义

    包含新创建的AddressBook.msg的头文件:

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

    创建回调函数以定期发布消息:

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

    创建稍后将发布的AddressBook消息实例:

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

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

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

timer_ = this->create_wall_timer(1s, publish_msg);

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_get_typesupport_target(cpp_typesupport_target
  ${PROJECT_NAME} rosidl_typesupport_cpp)

target_link_libraries(publish_address_book "${cpp_typesupport_target}")

    这将从AddressBook中找到相关的生成的c++代码,并允许你的目标链接到它。
    注意:当使用的接口来自独立构建的不同包时,此步骤是不必要的。只有当你想在定义接口的包中使用接口时,才需要使用CMake代码。

4 实例操作

    返回到工作区的根目录来编译包:

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

    在工作空间下source并运行发布者:

source install/local_setup.bash
ros2 run more_interfaces publish_address_book

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

    为了确认消息正在address_book话题上发布,请打开另一个终端,source一下工作区,并调用topic echo命令:

source install/setup.bash
ros2 topic echo /address_book

本节不再编写订阅节点,可以尝试自己编写一个订阅者作为练习(参照之前的教程)。

5 使用已有的接口

    可以在新的接口定义中使用现有的接口定义。例如,假设有一条名为Contact.msg。它属于一个名为rosidl_tutorials_msgs的现有ROS 2包。假设它的定义与我们之前的AddressBook.msg相同。
    在这种情况下,你可以定义AddressBook.msg作为类型Contact,你甚至可以定义AddressBook.msg作为Contact类型的数组,如下所示:

rosidl_tutorials_msgs/Contact[] address_book

    要生成此消息,您需要在package.xml中声明对Contact.msg包的依赖项,rosidl_tutorials_msgs:

<build_depend>rosidl_tutorials_msgs</build_depend>

<exec_depend>rosidl_tutorials_msgs</exec_depend>

    在CMakeLists.txt中增加:

find_package(rosidl_tutorials_msgs REQUIRED)

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

     在发布者节点中还需要包含Contact.msg的头文件,以便能够将contacts添加到address_book中。

    修改回调函数为:

auto publish_msg = [this]() -> void {
   auto msg = std::make_shared<more_interfaces::msg::AddressBook>();
   {
     rosidl_tutorials_msgs::msg::Contact contact;
     contact.first_name = "John";
     contact.last_name = "Doe";
     contact.phone_number = "1234567890";
     contact.phone_type = message.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 = message.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);
 };

    编译和运行这些更改将显示按预期定义的msg,以及上面定义的msg数组。

总结

    在本节中尝试了用于定义接口的不同字段类型,然后在使用接口的同一个包中编译接口。此外,学习了如何使用另一个接口作为字段类型,以及使用该特性所需的package.xml、CMakeLists.txt和#include内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值