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.背景
前面课程中,你学了怎样创建定制化msg
和srv
接口.
虽然在专用接口包里面声明接口是最佳的,但有时声明,创建和使用接口都在同一包里面(进行)是(更)方便的.
之前说过,接口当前只能定义在CMake
包里面.然而,(使用ament_cmake_python
的话)pyhon
库和节点是有可能在CMake
包里面的,所以在一个包里面一起定义python
节点和接口是可行的.简单起见,这里使用的是cmake
包和c++
节点.
课程这里专注于msg
类型接口,但是这些步骤适用于所有类型接口的.
2.预备知识
假设在开始本课程前,你温习了前面创建自定义msg
和srv
文件课程的基础部分.
你应该安装了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);
创建1
秒的定时器来实现每秒调用一次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接口的主要内容,如果未能解决你的问题,请参考以下文章