ROS2学习之旅(17)——ROS2创建接口方式拓展(接口在包内)

基于上篇简单创建自定义消息的文章,本文对创建接口方式进行拓展。

虽然最好的做法是是在专用接口包中声明接口,但有时在一个包中声明、创建和使用接口也很方便。

接口目前只能在CMake包中定义。然而,在CMake包中有Python库和节点是可能的(使用ament_cmake_python),所以可以在一个包中定义接口和Python节点。为了简单起见,b本文使用CMake包和C++节点。

本问将侧重于msg接口类型,但是这里的步骤适用于所有接口类型。

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

粘贴以下代码来创建个人信息:

bool FEMALE=true
bool MALE=false

string first_name
string last_name
bool gender
uint8 age
string address

该消息分为5个部分:

  • first_name: string 类型
  • last_name: string 类型
  • gender: bool 类型
  • age: uint8类型
  • address: string 类型

请注意,可以在消息定义中为字段设置默认值。更多ROS2接口的用法可以参考:[ROS2 interfaces](About ROS 2 interfaces — ROS 2 Documentation: Foxy documentation)。

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)

2.2设置多个接口

可以使用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.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<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代码解释

#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<more_interfaces::msg::AddressBook>("address_book");

创建一个名为address_book_publisher的发布者。

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

创建一个1秒的计时器,每秒调用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_target_interfaces(publish_address_book
  ${PROJECT_NAME} "rosidl_typesupport_cpp")

这将找到AddressBook.msg中生成的相关c++代码,并且允许链接到它。

当使用的接口来自单独构建的包时,此步骤是不必要的。这个CMake代码只有当你想要在同一个包中使用接口时才需要。

4.运行

返回工作空间根目录,编译功能包:

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

然后运行:

. install/local_setup.bash
ros2 run more_interfaces publish_address_book

此时会看到发布者转发自定义的msg,包括在publish_address_book.cpp中设置的值。

要确认消息发布在address_book话题上,打开另一个终端(不要忘记source),并调用topic echo:

. 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

要生成此消息,需要声明对Contact.msg包的依赖关系,rosidl_tutorials_msgs,在package.xml添加:

<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的头文件:

#include "rosidl_tutorials_msgs/msg/contact.hpp"

可以把这个调用改成这样:

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

编译和运行代码,将显示按照预期定义的msg,以及上面定义的msg数组。

6.总结

在本文中,尝试了定义接口的不同字段类型,然后在使用它的同一个包中构建了一个接口。还描述了如何使用另一个接口作为字段类型,以及利用该特性所需的package.xmlCMakeLists.txt#include语句。

如果给您带来帮助,希望能给点个关注,以后还会陆续更新有关机器人的内容,点个关注不迷路~欢迎大家一起交流学习。
都看到这了,点个推荐再走吧~
未经允许,禁止转载。

posted @ 2021-07-15 14:20  Love&Robot  阅读(2026)  评论(0编辑  收藏  举报