ROS2学习笔记16--详述ros2接口

Posted 鸿_H

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ROS2学习笔记16--详述ros2接口相关的知识,希望对你有一定的参考价值。

概要:这篇主要进一步介绍ros2接口.

环境:ubuntu20.04,ros2-foxy,vscode

最后如果没有陈述实操过程中碰到问题的话,则表示该章节都可被本人正常复现.

2.2.8拓展ros2接口(原文:https://docs.ros.org/en/foxy/Tutorials/Single-Package-Define-And-Use-Interface.html

>>教程>>拓展ros2接口

你正阅读的是ros2较老版本(Foxy),但仍然支持的说明文档.想查看最新版本的信息,请看galactic版本链接( https://docs.ros.org/en/galactic/Tutorials.html

详述ros2接口

目标:学习更多的方法来使用自定义ros2接口

课程等级:初级

时长:15min

目录

1.背景
2.预备知识
3.步骤
3.1创建一个包
3.2创建一个msg文件
3.3使用同一个包的同一个接口
3.4测试
3.5(额外的)使用现有的接口定义
4.总结
5.下一步
6.相关内容

1.背景

前面课程中,你学了怎样创建定制化msgsrv接口.

虽然在专用接口包里面声明接口是最佳的,但有时声明,创建和使用接口都在同一包里面(进行)是(更)方便的.

之前说过,接口当前只能定义在CMake包里面.然而,(使用ament_cmake_python的话)pyhon库和节点是有可能在CMake包里面的,所以在一个包里面一起定义python节点和接口是可行的.简单起见,这里使用的是cmake包和c++节点.

课程这里专注于msg类型接口,但是这些步骤适用于所有类型接口的.

2.预备知识

假设在开始本课程前,你温习了前面创建自定义msgsrv文件课程的基础部分.

你应该安装了ros2,有了工作空间,并且懂得包的创建.

老规矩,新开终端别忘了source一下ros2环境变量.

3.步骤

3.1创建一个包

在你的工作空间src目录线,创建一个名为more_interfaces包,也里面创建一个文件夹放msg文件:

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

3.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个部分组成:

姓:字符串类型
名:字符串类型
性别:布尔类型,不是男就是女
年龄:unit8类型
地址:字符串类型

注意,有些地方是可以设置默认参数的.看这里(https://docs.ros.org/en/foxy/Concepts/About-ROS-Interfaces.html#interfaceconcept),你可以找到很多方式来自定义接口.

接着,我们需要确认msg文件是可以用到c++,python或者其他语言的源码的.

3.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文件之后,cmake一定会重置系统配置.

生成消息:

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
)

也要保证你导出信息运行时的依赖:

ament_export_dependencies(rosidl_default_runtime)

现在,你可以用自定义msg文件来生成一个源文件.当完成上面四步,我们直接跳过编译步骤.

3.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.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.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");

创建一个节点和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函数.

3.3.2编译发布者

CMakeLists.txt文件中,我们需要为这个节点创建新target:

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.3连接指定的接口

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

rosidl_target_interfaces(publish_address_book
  ${PROJECT_NAME} "rosidl_typesupport_cpp")

这会找到用到AddressBook.msg编写的相关c++代码,让你target针对性连接它.

你可能会发现,当使用的接口是来自别的包里面的,这一步是没有必要的.当我们使用的接口来同一个包(操作包和接口包属于同一个时),这个CMake代码才是必要的.

3.4测试

返回工作空间的根目录,编译这个包:

linux:

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

然后source一下工作空间(环境变量),运行发布器:

linux:

. install/local_setup.bash
ros2 run more_interfaces publish_address_book

我们这次不创建侦听器,但是你可以尝试写一个练习一下(参考 编写简单发布器和侦听器(c++)课程)

3.5(特别)使用现有的接口定义

注意:

你可以将一个现有的定义接口用于新接口定义.例如,我们看这里,一个消息类型为Contact.msg,它就是属于ros2包里面的rosidl_tutorials_msgs,假设它的定义等同于前面自定义的AddressBook.msg接口.

在这种情况下,你已经定义了AddressBook.msg(接口和你节点在同一个包里面)作为类型Contact(接口和操作节点的包不相同),像这样子:

rosidl_tutorials_msgs/Contact[] address_book

为了生成这个消息(类型),你应该在Contact.msg包里面的package.xml文件声明依赖,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
)

为了能够把contacts加到你的address_book发布器节点里面,你要添加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阵列.

4.总结

在本课程,你尝试了不同方式来定义接口,然后(练习了)构建一个接口和其被使用都在同一包进行的方式.

你也应该学习如何使用另外一个接口作为一个类型,package.xml, CMakeLists.txt,#include声明(如何写)来使用(该接口功能)也是很必要的.

5.下一步

下面,你会创建一个简单的ros2包,会学习launch文件里面定制化参数如何设置.当然,你可以选择c++或者python来写它(包源文件).

6.相关内容

这里有几个设计文档(https://design.ros2.org/#interfaces)关于ros2接口和idl(接口定义语言)的.

其他

个人认为重点:

同一包,接口定义和调用的实现,包内文件配置方式;

将其他现有的接口作为新接口时,包内文件配置方式

****************
个人碰到问题:自定义msg是无法生成头文件的,缘由未知;由于前面编译通过不了,后面的使用现有接口定义文件练习这些也是没有实操的.

报错理由是cmake出错了,也就是CMakeLists.txt文件里面下面语句有问题:

rosidl_target_interfaces(publish_address_book
  ${PROJECT_NAME} "rosidl_typesupport_cpp")

编译报错窗口提示:

CMake Error at /opt/ros/foxy/share/rosidl_cmake/cmake/rosidl_target_interfaces.cmake:40 (message):
  rosidl_target_interfaces() the second argument 'more_interfaces' must be a
  valid target name
Call Stack (most recent call first):
  CMakeLists.txt:31 (rosidl_target_interfaces)

目前还没有解决这个问题,有小伙伴知道的话,麻烦留言告知一下哈,多谢了.
*****************
这课程是在等毕业证那十几天搞的,室友问,现在在线翻译这么强大,为啥还在这里瞎折腾呢?我说,我的目地是好好认真看一下,了解一下,自己折腾,目前是我想到最好的办法来获得最佳效果,即使这翻译有点别扭,哈哈哈.

#####################
不积硅步,无以至千里
好记性不如烂笔头
感觉有点收获的话,麻烦大大们点赞收藏哈

以上是关于ROS2学习笔记16--详述ros2接口的主要内容,如果未能解决你的问题,请参考以下文章

ROS2学习笔记18-velodyne 16雷达点云在ros2中可视化案例参考

ROS2学习笔记0--ROS2:Foxy学习目录

ROS2学习笔记1--配置ros2环境

ROS2学习笔记3--认识ros2节点node

ROS2学习笔记6--认识ros2参数parameters

ROS2学习笔记5--认识ros2服务services