您正在阅读的是旧版本但仍受支持的 ROS 2 文档。 Jazzy.
创建自定义 msg 和 srv 文件
目标 定义自定义界面文件 (.msg
和 .srv
) 并与 Python 和 C++ 节点一起使用。
辅导水平: 初学者
时间 20 分钟
背景介绍
在前面的教程中,您利用报文和服务接口了解了 主题, 服务和简单的发布者/订阅者 (C++/Python) 和服务/客户 (C++/Python) 节点。在这些情况下,您使用的接口都是预定义的。
虽然使用预定义的接口定义是一种良好的做法,但有时您可能也需要定义自己的消息和服务。本教程将向你介绍创建自定义接口定义的最简单方法。
先决条件
您应该有一个 ROS 2 工作区.
本教程还使用在发布者/订阅者 (C++ 和 Python) 和服务/客户 (C++ 和 Python)教程来试用新的自定义信息。
任务
1 创建新软件包
在本教程中,您将创建自定义 .msg
和 .srv
文件在自己的软件包中,然后在另一个软件包中使用它们。两个软件包应在同一个工作区中。
由于我们将使用前面教程中创建的 pub/sub 和 service/client 软件包,请确保与这些软件包位于同一工作区 (ros2_ws/src
),然后运行以下命令创建一个新软件包:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 tutorial_interfaces
接口教程
是新软件包的名称。请注意,它是且只能是一个 CMake 包,但这并不限制你在哪种类型的包中使用你的信息和服务。你可以在 CMake 包中创建自己的自定义接口,然后在 C++ 或 Python 节点中使用,这将在最后一节中介绍。
"(《世界人权宣言》) .msg
和 .srv
文件必须放在名为 信息
和 服务
分别是在 ros2_ws/src/tutorial_interfaces
:
mkdir msg srv
2 创建自定义定义
2.1 msg 定义
在 tutorial_interfaces/msg
目录,新建一个名为 Num.msg
只需一行代码声明其数据结构:
int64 num
这是一个自定义报文,传输一个名为 木
.
还在 tutorial_interfaces/msg
目录,新建一个名为 Sphere.msg
内容如下
几何_msgs/点中心
float64 radius
该自定义报文使用了另一个报文包 (几何_msgs/点
在这种情况下)。
2.2 服务器定义
回到 tutorial_interfaces/srv
目录,新建一个名为 AddThreeInts.srv
其请求和响应结构如下
int64 a
int64 b
int64 c
---
int64 sum
这是您的自定义服务,请求三个名为 a
, b
和 c
并响应一个名为 数额
.
3 CMakeLists.txt
要将您定义的接口转换为特定语言(如 C++ 和 Python)的代码,以便在这些语言中使用,请将以下几行添加到 CMakeLists.txt
:
查找软件包(几何参数 要求)
查找软件包(默认生成器 要求)
生成接口(${项目名称}
"msg/Num.msg";
"msg/Sphere.msg";
"srv/AddThreeInts.srv";
依赖 几何参数 # 添加上述信息所依赖的软件包,在本例中,geometry_msgs 用于 Sphere.msg
)
备注
rosidl_generate_interfaces 中的第一个参数(库名)必须与 ${PROJECT_NAME} 匹配(请参阅 "PROJECT_NAME")。(参见 https://github.com/ros2/rosidl/issues/441#issuecomment-591025515).
4 package.xml
因为接口依赖于 默认生成器
生成特定语言代码时,需要声明构建工具对它的依赖性。
默认运行时间
是运行时或执行阶段的依赖关系,是以后使用接口所必需的。接口 接口包
是软件包依赖组的名称、 接口教程
,应与之相关联,并使用 <member_of_group>;
标签
在 <package>;
元素的 package.xml
:
<依赖>;几何参数依赖</depend>;
构建工具的依赖关系<buildtool_depend>;默认生成器</buildtool_depend>;
执行依赖关系;默认运行时间</exec_depend>;
<member_of_group>;接口包</member_of_group>;
5 建立 接口教程
包装
现在,自定义界面包的所有部分都已就位,您可以构建该包了。在工作区的根目录 (~/ros2_ws
),运行以下命令:
colcon build --packages-select tutorial_interfaces
colcon build --packages-select tutorial_interfaces
colcon build --merge-install --packages-select tutorial_interfaces
现在,其他 ROS 2 软件包也能发现这些接口。
6 确认创建 msg 和 srv
在新终端中,从工作区 (ros2_ws
)来获取:
source install/setup.bash
install/setup.bash
调用 install/setup.bat
现在,您可以使用 玫瑰2 界面 展览
指挥:
ros2 interface show tutorial_interfaces/msg/Num
应该返回:
int64 num
还有
ros2 interface show tutorial_interfaces/msg/Sphere
应该返回:
几何_msgs/点中心
float64 x
float64 y
float64 z
float64 radius
还有
ros2 interface show tutorial_interfaces/srv/AddThreeInts
应该返回:
int64 a
int64 b
int64 c
---
int64 sum
7 测试新界面
在这一步中,您可以使用之前教程中创建的软件包。对节点做一些简单的修改、 CMakeLists.txt
和 package.xml
文件将允许您使用新界面。
7.1 测试 Num.msg
带酒吧/分会
对上一教程中创建的发布者/订阅者软件包稍作修改 (C++ 或 Python),你可以看到 Num.msg
的操作。由于要将标准字符串 msg 改为数字 msg,输出结果会略有不同。
出版商
#include 时间顺序<chrono>;
#include <内存>;
#include "rclcpp/rclcpp.hpp";
#include "tutorial_interfaces/msg/num.hpp"; // 更改
使用 命名空间 标准::计时器;
类 最小出版商 : 公 rclcpp::节点
{
公:
最小出版商()
: 节点("minimal_publisher";), count_(0)
{
出版商_ = 此->;创建出版商<;接口教程::信息::编号>;("主题";, 10); // 更改
定时器 = 此->;创建隔离墙计时器(
500毫秒, 标准::约束(及样品;最小出版商::定时器回调, 此));
}
私人:
空白 定时器回调()
{
汽车 信息 = 接口教程::信息::编号(); // 更改
信息.木 = 此->;count_++; // 更改
rclcpp_info_stream(此->;get_logger(), 出版:';"; <<; 信息.木 <<; "'";); // 更改
出版商_->;发布(信息);
}
rclcpp::定时器基数::SharedPtr 定时器;
rclcpp::出版商<;接口教程::信息::编号>::SharedPtr 出版商_; // 更改
size_t count_;
};
int 主要(int 参数, 烧焦 * 参数[])
{
rclcpp::启动(参数, 参数);
rclcpp::后旋(标准::共享<;最小出版商>;());
rclcpp::关闭();
返回 0;
}
舶来品 rclpy
从 rclpy.node 舶来品 节点
从 tutorial_interfaces.msg 舶来品 编号 # 改变
类 最小出版商(节点):
捍卫 启动(自我):
棒极了().启动('minimal_publisher';)
自我.出版商_ = 自我.创建出版商(编号, '主题';, 10) # 改变
定时器周期 = 0.5
自我.定时器 = 自我.创建计时器(定时器周期, 自我.定时器回调)
自我.i = 0
捍卫 定时器回调(自我):
信息 = 编号() # 改变
信息.木 = 自我.i # 改变
自我.出版商_.发布(信息)
自我.get_logger().信息('出版:";%d"'; % 信息.木) # 改变
自我.i += 1
捍卫 主要(参数=无):
rclpy.启动(参数=参数)
最小出版商 = 最小出版商()
rclpy.后旋(最小出版商)
最小出版商.destroy_node()
rclpy.关闭()
如果 姓名____ == '__main__';:
主要()
订阅者
#include 功能强大;
#include <内存>;
#include "rclcpp/rclcpp.hpp";
#include "tutorial_interfaces/msg/num.hpp"; // 更改
使用 标准::占位符::_1;
类 最小订阅者 : 公 rclcpp::节点
{
公:
最小订阅者()
: 节点("minimal_subscriber";)
{
订阅_ = 此->;创建订阅<;接口教程::信息::编号>;( // 更改
"主题";, 10, 标准::约束(及样品;最小订阅者::topic_callback, 此, _1));
}
私人:
空白 topic_callback(缢 接口教程::信息::编号 及样品; 信息) 缢 // 更改
{
rclcpp_info_stream(此->;get_logger(), 我听说:';"; <<; 信息.木 <<; "'";); // 更改
}
rclcpp::订阅<;接口教程::信息::编号>::SharedPtr 订阅_; // 更改
};
int 主要(int 参数, 烧焦 * 参数[])
{
rclcpp::启动(参数, 参数);
rclcpp::后旋(标准::共享<;最小订阅者>;());
rclcpp::关闭();
返回 0;
}
舶来品 rclpy
从 rclpy.node 舶来品 节点
从 tutorial_interfaces.msg 舶来品 编号 # 改变
类 最小订阅者(节点):
捍卫 启动(自我):
棒极了().启动('minimal_subscriber';)
自我.订阅费 = 自我.创建订阅(
编号, # 改变
'主题';,
自我.监听器回调,
10)
自我.订阅费
捍卫 监听器回调(自我, 信息):
自我.get_logger().信息('我听说:";%d"'; % 信息.木) # 改变
捍卫 主要(参数=无):
rclpy.启动(参数=参数)
最小订阅者 = 最小订阅者()
rclpy.后旋(最小订阅者)
最小订阅者.destroy_node()
rclpy.关闭()
如果 姓名____ == '__main__';:
主要()
CMakeLists.txt
添加以下几行(仅限 C++):
#...
查找软件包(ament_cmake 要求)
查找软件包(rclcpp 要求)
查找软件包(接口教程 要求) # 改变
添加可执行(话匣子 src/publisher_member_function.cpp)
ament_target_dependencies(话匣子 rclcpp 接口教程) # 改变
添加可执行(听众 src/subscriber_member_function.cpp)
ament_target_dependencies(听众 rclcpp 接口教程) # 改变
安装(目标
话匣子
听众
目的地 lib/${项目名称})
ament_package()
package.xml
添加以下一行
<;取决>;接口教程</取决>;
<;执行依赖>;接口教程</执行依赖>;
完成上述编辑并保存所有更改后,构建软件包:
在 Linux/macOS 上:
colcon build --packages-select cpp_pubsub
在 Windows 上:
colcon build --merge-install --packages-select cpp_pubsub
在 Linux/macOS 上:
colcon build --packages-select py_pubsub
在 Windows 上:
colcon build --merge-install --packages-select py_pubsub
然后打开两个新终端,源 ros2_ws
然后运行:
ros2 运行 cpp_pubsub talker
ros2 运行 cpp_pubsub 监听器
ros2 run py_pubsub talker
ros2 运行 py_pubsub 监听器
因为 Num.msg
转发的只是一个整数,因此对话者应该只发布整数值,而不是之前发布的字符串:
[INFO] [minimal_publisher]:Publishing: '0';
[INFO] [minimal_publisher]:出版:'1';
[INFO] [minimal_publisher]:出版: '2';
7.2 测试 AddThreeInts.srv
与服务/客户
对上一教程中创建的服务/客户端软件包稍作修改 (C++ 或 Python),你可以看到 AddThreeInts.srv
的操作。由于要将原来的两整数请求 srv 改为三整数请求 srv,因此输出结果会略有不同。
服务
#include "rclcpp/rclcpp.hpp";
#include "tutorial_interfaces/srv/add_three_ints.hpp"; // 更改
#include <内存>;
空白 增加(缢 标准::共享_ptr<;接口教程::服务::添加三位数::要求>; 要求, // 更改
标准::共享_ptr<;接口教程::服务::添加三位数::回应>; 回应) // 更改
{
回应->;数额 = 要求->;a + 要求->;b + 要求->;c; // 更改
RCLCPP_INFO(rclcpp::get_logger("rclcpp";), 接收请求\na: %ld"; " b: %ld"; " c: %ld";, // 更改
要求->;a, 要求->;b, 要求->;c); // 更改
RCLCPP_INFO(rclcpp::get_logger("rclcpp";), "发回响应:[%ld]";, (长 int)回应->;数额);
}
int 主要(int 参数, 烧焦 **参数)
{
rclcpp::启动(参数, 参数);
标准::共享_ptr<;rclcpp::节点>; 网站 = rclcpp::节点::共享("add_three_ints_server";); // 更改
rclcpp::服务<;接口教程::服务::添加三位数>::SharedPtr 服务 = // 更改
网站->;创建服务<;接口教程::服务::添加三位数>;("add_three_ints";, 及样品;增加); // 更改
RCLCPP_INFO(rclcpp::get_logger("rclcpp";), 准备添加三个 ints;); // 更改
rclcpp::后旋(网站);
rclcpp::关闭();
}
从 tutorial_interfaces.srv 舶来品 添加三位数 # 改变
舶来品 rclpy
从 rclpy.node 舶来品 节点
类 最小服务(节点):
捍卫 启动(自我):
棒极了().启动('minimal_service';)
自我.服务 = 自我.创建服务(添加三位数, 'add_three_ints', 自我.add_three_ints_callback) # 改变
捍卫 add_three_ints_callback(自我, 要求, 回应):
回应.数额 = 要求.a + 要求.b + 要求.c # 改变
自我.get_logger().信息('收到请求\na: %d b: %d c: %d' % (要求.a, 要求.b, 要求.c)) # 改变
返回 回应
捍卫 主要(参数=无):
rclpy.启动(参数=参数)
最小服务 = 最小服务()
rclpy.后旋(最小服务)
rclpy.关闭()
如果 姓名____ == '__main__';:
主要()
客户
#include "rclcpp/rclcpp.hpp";
#include "tutorial_interfaces/srv/add_three_ints.hpp"; // 更改
#include 时间顺序<chrono>;
#include cstdlib>;
#include <内存>;
使用 命名空间 标准::计时器;
int 主要(int 参数, 烧焦 **参数)
{
rclcpp::启动(参数, 参数);
如果 (参数 != 4) { // 更改
RCLCPP_INFO(rclcpp::get_logger("rclcpp";), "usage: add_three_ints_client X Y Z";); // 更改
返回 1;
}
标准::共享_ptr<;rclcpp::节点>; 网站 = rclcpp::节点::共享("add_three_ints_client";); // 更改
rclcpp::客户<;接口教程::服务::添加三位数>::SharedPtr 客户 = // 更改
网站->;创建客户端<;接口教程::服务::添加三位数>;("add_three_ints";); // 更改
汽车 要求 = 标准::共享<;接口教程::服务::添加三位数::要求>;(); // 更改
要求->;a = 环礁(参数[1]);
要求->;b = 环礁(参数[2]);
要求->;c = 环礁(参数[3]); // 更改
虽然 (!客户->;等待服务(1s)) {
如果 (!rclcpp::好的()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp";), 等待服务时被中断。退出;);
返回 0;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp";), 服务不可用,请再次等待......";);
}
汽车 结果 = 客户->;异步发送请求(要求);
// 等待结果。
如果 (rclcpp::自旋直到未来完成(网站, 结果) ==
rclcpp::未来返回代码::成功)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp";), "总和:%ld";, 结果.获取()->;数额);
} 不然 {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp";), "调用 add_three_ints 服务失败";); // 更改
}
rclcpp::关闭();
返回 0;
}
从 tutorial_interfaces.srv 舶来品 添加三位数 # 改变
舶来品 系统
舶来品 rclpy
从 rclpy.node 舶来品 节点
类 最小客户端同步(节点):
捍卫 启动(自我):
棒极了().启动('minimal_client_async';)
自我.挛 = 自我.创建客户端(添加三位数, 'add_three_ints') # 改变
虽然 不 自我.挛.等待服务(超时秒数=1.0):
自我.get_logger().信息('服务不可用,请再次等待...';)
自我.要求 = 添加三位数.要求() # 改变
捍卫 发送请求(自我):
自我.要求.a = int(系统.参数[1])
自我.要求.b = int(系统.参数[2])
自我.要求.c = int(系统.参数[3]) # 改变
自我.未来 = 自我.挛.调用同步(自我.要求)
捍卫 主要(参数=无):
rclpy.启动(参数=参数)
最小客户端 = 最小客户端同步()
最小客户端.发送请求()
虽然 rclpy.好的():
rclpy.自旋一次(最小客户端)
如果 最小客户端.未来.完成的():
尝试:
回应 = 最小客户端.未来.结果()
除开 例外情况 作为 e:
最小客户端.get_logger().信息(
'服务调用失败 %r' % (e,))
不然:
最小客户端.get_logger().信息(
'add_three_ints 的结果: for %d + %d + %d = %d' % # 改变
(最小客户端.要求.a, 最小客户端.要求.b, 最小客户端.要求.c, 回应.数额)) # 改变
断裂
最小客户端.destroy_node()
rclpy.关闭()
如果 姓名____ == '__main__';:
主要()
CMakeLists.txt
添加以下几行(仅限 C++):
#...
查找软件包(ament_cmake 要求)
查找软件包(rclcpp 要求)
查找软件包(接口教程 要求) # 改变
添加可执行(服务器 src/add_twoo_ints_server.cpp)
ament_target_dependencies(服务器
rclcpp 接口教程) # 改变
添加可执行(客户 src/add_twoo_ints_client.cpp)
ament_target_dependencies(客户
rclcpp 接口教程) # 改变
安装(目标
服务器
客户
目的地 lib/${项目名称})
ament_package()
package.xml
添加以下一行
<;取决>;接口教程</取决>;
<;执行依赖>;接口教程</执行依赖>;
完成上述编辑并保存所有更改后,构建软件包:
在 Linux/macOS 上:
colcon build --packages-select cpp_srvcli
在 Windows 上:
colcon build --merge-install --packages-select cpp_srvcli
在 Linux/macOS 上:
colcon build --packages-select py_srvcli
在 Windows 上:
colcon build --merge-install --packages-select py_srvcli
然后打开两个新终端,源 ros2_ws
然后运行:
ros2 运行 cpp_srvcli 服务器
ros2 运行 cpp_srvcli 客户端 2 3 1
ros2 运行 py_srvcli 服务
ros2 run py_srvcli client 2 3 1
摘要
在本教程中,您将学习如何在自己的软件包中创建自定义接口,以及如何在其他软件包中使用这些接口。
本教程仅涉及定义自定义界面的皮毛。您可以在 关于 ROS 2 接口.
下一步工作
"(《世界人权宣言》) 下一个教程 涵盖了在 ROS 2 中使用接口的更多方法。