警告
您正在阅读的 ROS 2 文档版本已达到 EOL(生命周期结束),不再受官方支持。如果您想了解最新信息,请访问 Jazzy.
编写动作服务器和客户端(C++)
目标 用 C++ 实现动作服务器和客户端。
辅导水平: 中级
时间 15 分钟
背景介绍
操作是 ROS 中的一种异步通信形式。 行动客户 将目标申请发送至 行动服务器. 行动服务器 将目标反馈和结果发送至 行动客户.
先决条件
您需要 动作教程界面
软件包和 Fibonacci.action
接口、 创建行动.
任务
1 创建 action_tutorials_cpp 软件包
正如我们在 创建第一个 ROS 2 软件包 教程,我们需要创建一个新的软件包来保存我们的 C++ 和辅助代码。
1.1 创建 action_tutorials_cpp 软件包
进入在上一教程中创建的动作工作区,为 C++ 动作服务器创建一个新包:
记住 将上一教程中的工作区作为源文件 首先)。
CD ~/action_ws/src ros2 包 创建 --依赖 动作教程界面 rclcpp rclcpp_action rclcpp_components -- 动作教程_cpp
CD ~/action_ws/src ros2 包 创建 --依赖 动作教程界面 rclcpp rclcpp_action rclcpp_components -- 动作教程_cpp
CD \dev\action_ws\src ros2 包 创建 --依赖 动作教程界面 rclcpp rclcpp_action rclcpp_components -- 动作教程_cpp
1.2 加入可见度控制
为了使软件包能在 Windows 上编译和运行,我们需要添加一些 "可见性控制"。有关为什么需要这样做的详细信息,请参阅 这里.
打开 action_tutorials_cpp/include/action_tutorials_cpp/visibility_control.h
并输入以下代码:
#ifndef ACTION_TUTORIALS_CPP__VISIBILITY_CONTROL_H_
#define ACTION_TUTORIALS_CPP__VISIBILITY_CONTROL_H_
#ifdef __cplusplus
外部 "C";
{
#endif
// 这个逻辑是从 gcc wiki 上的示例中借用(然后命名)的:
// https://gcc.gnu.org/wiki/Visibility
#if defined _WIN32 || defined __CYGWIN__
#ifdef __GNUC__
#define ACTION_TUTORIALS_CPP_EXPORT __attribute__ ((dllexport))
#define ACTION_TUTORIALS_CPP_IMPORT __attribute__ ((dllimport))
#else
#define ACTION_TUTORIALS_CPP_EXPORT __declspec(dllexport)
#define ACTION_TUTORIALS_CPP_IMPORT __declspec(dllimport)
#endif
#ifdef ACTION_TUTORIALS_CPP_BUILDING_DLL
#define ACTION_TUTORIALS_CPP_PUBLIC ACTION_TUTORIALS_CPP_EXPORT
#else
#define ACTION_TUTORIALS_CPP_PUBLIC ACTION_TUTORIALS_CPP_IMPORT
#endif
#define ACTION_TUTORIALS_CPP_PUBLIC_TYPE ACTION_TUTORIALS_CPP_PUBLIC
#define ACTION_TUTORIALS_CPP_LOCAL
#else
#define ACTION_TUTORIALS_CPP_EXPORT __attribute__ ((visibility("default")))
#define ACTION_TUTORIALS_CPP_IMPORT
#if __GNUC__ >= 4
#define ACTION_TUTORIALS_CPP_PUBLIC attribute____ ((visibility("default")))
#define ACTION_TUTORIALS_CPP_LOCAL __attribute__ ((visibility("hidden")))
#else
#define ACTION_TUTORIALS_CPP_PUBLIC
#define ACTION_TUTORIALS_CPP_LOCAL
#endif
#define ACTION_TUTORIALS_CPP_PUBLIC_TYPE
#endif
#ifdef __cplusplus
}
#endif
#endif // action_tutorials_cpp__visibility_control_h_
2 编写行动服务器
让我们集中精力编写一个动作服务器,使用我们在 创建行动 教程。
2.1 编写行动服务器代码
打开 action_tutorials_cpp/src/fibonacci_action_server.cpp
并输入以下代码:
1#include 功能强大;
2#include <内存>;
3#include <thread>;
4
5#include "action_tutorials_interfaces/action/fibonacci.hpp";
6#include "rclcpp/rclcpp.hpp";
7#include "rclcpp_action/rclcpp_action.hpp";
8#include "rclcpp_components/register_node_macro.hpp";
9
10#include "action_tutorials_cpp/visibility_control.h";
11
12命名空间 动作教程_cpp
13{
14类 FibonacciActionServer : 公 rclcpp::节点
15{
16公:
17 使用 斐波那契 = 动作教程界面::行动::斐波那契;
18 使用 目标手柄斐波纳契 = rclcpp_action::服务器目标句柄<;斐波那契>;;
19
20 行动教程_cpp_public
21 不含糊 FibonacciActionServer(缢 rclcpp::节点选项 及样品; 选项 = rclcpp::节点选项())
22 : 节点("fibonacci_action_server";, 选项)
23 {
24 使用 命名空间 标准::占位符;
25
26 此->;行动服务器 = rclcpp_action::创建服务器<;斐波那契>;(
27 此,
28 "fibonacci";,
29 标准::约束(及样品;FibonacciActionServer::处理目标, 此, _1, _2),
30 标准::约束(及样品;FibonacciActionServer::处理取消, 此, _1),
31 标准::约束(及样品;FibonacciActionServer::已接受, 此, _1));
32 }
33
34私人:
35 rclcpp_action::服务器<;斐波那契>::SharedPtr 行动服务器;
36
37 rclcpp_action::目标响应 处理目标(
38 缢 rclcpp_action::目标 UUID 及样品; uuid,
39 标准::共享_ptr<;缢 斐波那契::目标>; 目标)
40 {
41 RCLCPP_INFO(此->;get_logger(), "收到目标请求,订单 %d";, 目标->;订单);
42 (空白)uuid;
43 返回 rclcpp_action::目标响应::接受并执行;
44 }
45
46 rclcpp_action::取消响应 处理取消(
47 缢 标准::共享_ptr<;目标手柄斐波纳契>; 目标句柄)
48 {
49 RCLCPP_INFO(此->;get_logger(), 收到取消目标的请求";);
50 (空白)目标句柄;
51 返回 rclcpp_action::取消响应::接受;
52 }
53
54 空白 已接受(缢 标准::共享_ptr<;目标手柄斐波纳契>; 目标句柄)
55 {
56 使用 命名空间 标准::占位符;
57 // 需要快速返回,以避免阻塞执行器,因此需要启动一个新线程
58 标准::线程{标准::约束(及样品;FibonacciActionServer::执行, 此, _1), 目标句柄}.脱离();
59 }
60
61 空白 执行(缢 标准::共享_ptr<;目标手柄斐波纳契>; 目标句柄)
62 {
63 RCLCPP_INFO(此->;get_logger(), "执行目标";);
64 rclcpp::费率 循环速率(1);
65 缢 汽车 目标 = 目标句柄->;get_goal();
66 汽车 反馈 = 标准::共享<;斐波那契::反馈意见>;();
67 汽车 及样品; 顺序 = 反馈->;部分序列;
68 顺序.推回(0);
69 顺序.推回(1);
70 汽车 结果 = 标准::共享<;斐波那契::结果>;();
71
72 对于 (int i = 1; (i <; 目标->;订单) &&; rclcpp::好的(); ++i) {
73 // 检查是否有取消请求
74 如果 (目标句柄->;is_canceling()) {
75 结果->;顺序 = 顺序;
76 目标句柄->;已取消(结果);
77 RCLCPP_INFO(此->;get_logger(), 目标取消";);
78 返回;
79 }
80 // 更新序列
81 顺序.推回(顺序[i] + 顺序[i - 1]);
82 // 发布反馈意见
83 目标句柄->;发布反馈(反馈);
84 RCLCPP_INFO(此->;get_logger(), "发布反馈";);
85
86 循环速率.睡眠();
87 }
88
89 // 检查目标是否完成
90 如果 (rclcpp::好的()) {
91 结果->;顺序 = 顺序;
92 目标句柄->;继承(结果);
93 RCLCPP_INFO(此->;get_logger(), "目标成功";);
94 }
95 }
96}; // 类 FibonacciActionServer
97
98} // 命名空间 action_tutorials_cpp
99
100rclcpp_components_register_node(动作教程_cpp::FibonacciActionServer)
前几行包含了我们需要编译的所有头文件。
接下来,我们创建一个从 rclcpp::Node
:
类 FibonacciActionServer : 公 rclcpp::节点
的构造函数 FibonacciActionServer
类将节点名称初始化为 fibonacci_action_server
:
不含糊 FibonacciActionServer(缢 rclcpp::节点选项 及样品; 选项 = rclcpp::节点选项())
: 节点("fibonacci_action_server";, 选项)
构造函数还会实例化一个新的动作服务器:
此->;行动服务器 = rclcpp_action::创建服务器<;斐波那契>;(
此,
"fibonacci";,
标准::约束(及样品;FibonacciActionServer::处理目标, 此, _1, _2),
标准::约束(及样品;FibonacciActionServer::处理取消, 此, _1),
标准::约束(及样品;FibonacciActionServer::已接受, 此, _1));
行动服务器需要具备 6 个条件:
模板化操作类型名称:
斐波那契
.要添加操作的 ROS 2 节点:
此
.行动名称:
斐波那契
.用于处理目标的回调函数:
处理目标
用于处理取消的回调函数:
处理取消
.用于处理目标接受的回调函数:
接受
.
文件的下一部分是各种回调的实现。需要注意的是,所有回调都需要快速返回,否则执行器就有可能处于饥饿状态。
我们从处理新目标的回调开始:
rclcpp_action::目标响应 处理目标(
缢 rclcpp_action::目标 UUID 及样品; uuid,
标准::共享_ptr<;缢 斐波那契::目标>; 目标)
{
RCLCPP_INFO(此->;get_logger(), "收到目标请求,订单 %d";, 目标->;订单);
(空白)uuid;
返回 rclcpp_action::目标响应::接受并执行;
}
这种实现方式只接受所有目标。
接下来是处理取消订单的回调:
rclcpp_action::取消响应 处理取消(
缢 标准::共享_ptr<;目标手柄斐波纳契>; 目标句柄)
{
RCLCPP_INFO(此->;get_logger(), 收到取消目标的请求";);
(空白)目标句柄;
返回 rclcpp_action::取消响应::接受;
}
这种实现方式只是告诉客户端它接受了取消。
最后一个回调接受一个新目标并开始处理:
空白 已接受(缢 标准::共享_ptr<;目标手柄斐波纳契>; 目标句柄)
{
使用 命名空间 标准::占位符;
// 需要快速返回,以避免阻塞执行器,因此需要启动一个新线程
标准::线程{标准::约束(及样品;FibonacciActionServer::执行, 此, _1), 目标句柄}.脱离();
}
由于执行的是一个长期运行的操作,我们会产生一个线程来执行实际工作,并从 已接受
很快。
所有进一步的处理和更新都在 执行
方法:
空白 执行(缢 标准::共享_ptr<;目标手柄斐波纳契>; 目标句柄)
{
RCLCPP_INFO(此->;get_logger(), "执行目标";);
rclcpp::费率 循环速率(1);
缢 汽车 目标 = 目标句柄->;get_goal();
汽车 反馈 = 标准::共享<;斐波那契::反馈意见>;();
汽车 及样品; 顺序 = 反馈->;部分序列;
顺序.推回(0);
顺序.推回(1);
汽车 结果 = 标准::共享<;斐波那契::结果>;();
对于 (int i = 1; (i <; 目标->;订单) &&; rclcpp::好的(); ++i) {
// 检查是否有取消请求
如果 (目标句柄->;is_canceling()) {
结果->;顺序 = 顺序;
目标句柄->;已取消(结果);
RCLCPP_INFO(此->;get_logger(), 目标取消";);
返回;
}
// 更新序列
顺序.推回(顺序[i] + 顺序[i - 1]);
// 发布反馈意见
目标句柄->;发布反馈(反馈);
RCLCPP_INFO(此->;get_logger(), "发布反馈";);
循环速率.睡眠();
}
// 检查目标是否完成
如果 (rclcpp::好的()) {
结果->;顺序 = 顺序;
目标句柄->;继承(结果);
RCLCPP_INFO(此->;get_logger(), "目标成功";);
}
}
该工作线程每秒处理一个斐波那契数列的序列号,每一步都会发布反馈更新。完成处理后,它将 目标句柄
并退出。
我们现在有了一个功能完备的行动服务器。让我们构建并运行它。
2.2 编译行动服务器
在上一节中,我们将动作服务器代码安装到位。为了让它编译和运行,我们还需要做几件事。
首先,我们需要设置 CMakeLists.txt,以便编译动作服务器。打开 action_tutorials_cpp/CMakeLists.txt
之后添加以下内容 查找软件包
电话
add_library(action_server SHARED
src/fibonacci_action_server.cpp)
target_include_directories(action_server PRIVATE
$<BUILD_INTERFACE:${cmake_current_source_dir}/include>;
$<INSTALL_INTERFACE:include>;)
target_compile_definitions(action_server
PRIVATE "ACTION_TUTORIALS_CPP_BUILDING_DLL")
ament_target_dependencies(action_server
"action_tutorials_interfaces";
"rclcpp";
"rclcpp_action";
"rclcpp_components")
rclcpp_components_register_node(action_server PLUGIN "action_tutorials_cpp::FibonacciActionServer" EXECUTABLE fibonacci_action_server)
install(TARGETS
行动服务器
存档目的地 lib
图书目录 lib
运行时间目的地 bin)
现在我们可以编译软件包了。转到 action_ws
然后运行:
胶管 构建
这将编译整个工作区,包括 fibonacci_action_server
在 动作教程_cpp
包装
2.3 运行行动服务器
现在我们已经构建了行动服务器,可以运行它了。将我们刚刚构建的工作区(action_ws
),并尝试运行动作服务器:
玫瑰2 运行 动作教程_cpp fibonacci_action_server
3 撰写行动客户
3.1 编写动作客户端代码
打开 action_tutorials_cpp/src/fibonacci_action_client.cpp
并输入以下代码:
1#include 功能强大;
2#include 未来;
3#include <内存>;
4#include <字符串>;
5#include 流>;
6
7#include "action_tutorials_interfaces/action/fibonacci.hpp";
8
9#include "rclcpp/rclcpp.hpp";
10#include "rclcpp_action/rclcpp_action.hpp";
11#include "rclcpp_components/register_node_macro.hpp";
12
13命名空间 动作教程_cpp
14{
15类 FibonacciActionClient : 公 rclcpp::节点
16{
17公:
18 使用 斐波那契 = 动作教程界面::行动::斐波那契;
19 使用 目标手柄斐波纳契 = rclcpp_action::客户目标句柄<;斐波那契>;;
20
21 不含糊 FibonacciActionClient(缢 rclcpp::节点选项 及样品; 选项)
22 : 节点("fibonacci_action_client";, 选项)
23 {
24 此->;客户端ptr_ = rclcpp_action::创建客户端<;斐波那契>;(
25 此,
26 "fibonacci";);
27
28 此->;定时器 = 此->;创建隔离墙计时器(
29 标准::计时器::毫秒数(500),
30 标准::约束(及样品;FibonacciActionClient::发送目标, 此));
31 }
32
33 空白 发送目标()
34 {
35 使用 命名空间 标准::占位符;
36
37 此->;定时器->;取消();
38
39 如果 (!此->;客户端ptr_->;等待行动服务器()) {
40 RCLCPP_ERROR(此->;get_logger(), "等待后行动服务器不可用";);
41 rclcpp::关闭();
42 }
43
44 汽车 goal_msg = 斐波那契::目标();
45 goal_msg.订单 = 10;
46
47 RCLCPP_INFO(此->;get_logger(), 发送目标";);
48
49 汽车 发送目标选项 = rclcpp_action::客户<;斐波那契>::发送目标选项();
50 发送目标选项.目标响应回调 =
51 标准::约束(及样品;FibonacciActionClient::目标响应回调, 此, _1);
52 发送目标选项.反馈回调 =
53 标准::约束(及样品;FibonacciActionClient::反馈回调, 此, _1, _2);
54 发送目标选项.result_callback =
55 标准::约束(及样品;FibonacciActionClient::result_callback, 此, _1);
56 此->;客户端ptr_->;异步发送目标(goal_msg, 发送目标选项);
57 }
58
59私人:
60 rclcpp_action::客户<;斐波那契>::SharedPtr 客户端ptr_;
61 rclcpp::定时器基数::SharedPtr 定时器;
62
63 空白 目标响应回调(标准::共享未来<;目标手柄斐波纳契::SharedPtr>; 未来)
64 {
65 汽车 目标句柄 = 未来.获取();
66 如果 (!目标句柄) {
67 RCLCPP_ERROR(此->;get_logger(), 目标被服务器拒绝";);
68 } 不然 {
69 RCLCPP_INFO(此->;get_logger(), 服务器接受目标,等待结果";);
70 }
71 }
72
73 空白 反馈回调(
74 目标手柄斐波纳契::SharedPtr,
75 缢 标准::共享_ptr<;缢 斐波那契::反馈意见>; 反馈)
76 {
77 标准::字符串流 ss;
78 ss <<; "收到序列中的下一个号码:";;
79 对于 (汽车 编号 : 反馈->;部分序列) {
80 ss <<; 编号 <<; " ";;
81 }
82 RCLCPP_INFO(此->;get_logger(), ss.字符串().c_str());
83 }
84
85 空白 result_callback(缢 目标手柄斐波纳契::包裹结果 及样品; 结果)
86 {
87 开关 (结果.代码) {
88 个案 rclcpp_action::结果代码::成功:
89 断裂;
90 个案 rclcpp_action::结果代码::中止:
91 RCLCPP_ERROR(此->;get_logger(), 目标失败";);
92 返回;
93 个案 rclcpp_action::结果代码::取消:
94 RCLCPP_ERROR(此->;get_logger(), 目标被取消";);
95 返回;
96 默认:
97 RCLCPP_ERROR(此->;get_logger(), 未知结果代码";);
98 返回;
99 }
100 标准::字符串流 ss;
101 ss <<; 已收到结果: ";;
102 对于 (汽车 编号 : 结果.结果->;顺序) {
103 ss <<; 编号 <<; " ";;
104 }
105 RCLCPP_INFO(此->;get_logger(), ss.字符串().c_str());
106 rclcpp::关闭();
107 }
108}; // 类 FibonacciActionClient
109
110} // 命名空间 action_tutorials_cpp
111
112rclcpp_components_register_node(动作教程_cpp::FibonacciActionClient)
前几行包含了我们需要编译的所有头文件。
接下来,我们创建一个从 rclcpp::Node
:
类 FibonacciActionClient : 公 rclcpp::节点
的构造函数 FibonacciActionClient
类将节点名称初始化为 斐波那契行动客户端
:
不含糊 FibonacciActionClient(缢 rclcpp::节点选项 及样品; 选项)
: 节点("fibonacci_action_client";, 选项)
构造函数还会实例化一个新的动作客户端:
此->;客户端ptr_ = rclcpp_action::创建客户端<;斐波那契>;(
此,
"fibonacci";);
行动客户需要具备 3 个条件:
模板化操作类型名称:
斐波那契
.要添加动作客户端的 ROS 2 节点:
此
.行动名称:
斐波那契
.
我们还实例化了一个 ROS 定时器,它将启动对 发送目标
:
此->;定时器 = 此->;创建隔离墙计时器(
标准::计时器::毫秒数(500),
标准::约束(及样品;FibonacciActionClient::发送目标, 此));
计时器到期时,它会调用 发送目标
:
空白 发送目标()
{
使用 命名空间 标准::占位符;
此->;定时器->;取消();
如果 (!此->;客户端ptr_->;等待行动服务器()) {
RCLCPP_ERROR(此->;get_logger(), "等待后行动服务器不可用";);
rclcpp::关闭();
}
汽车 goal_msg = 斐波那契::目标();
goal_msg.订单 = 10;
RCLCPP_INFO(此->;get_logger(), 发送目标";);
汽车 发送目标选项 = rclcpp_action::客户<;斐波那契>::发送目标选项();
发送目标选项.目标响应回调 =
标准::约束(及样品;FibonacciActionClient::目标响应回调, 此, _1);
发送目标选项.反馈回调 =
标准::约束(及样品;FibonacciActionClient::反馈回调, 此, _1, _2);
发送目标选项.result_callback =
标准::约束(及样品;FibonacciActionClient::result_callback, 此, _1);
此->;客户端ptr_->;异步发送目标(goal_msg, 发送目标选项);
}
该功能执行以下操作
取消计时器(因此只调用一次)。
等待行动服务器出现。
实例化一个新的
Fibonacci::Goal
.设置响应、反馈和结果回调。
将目标发送到服务器。
服务器接收并接受目标后,会向客户端发送一个响应。该响应由 目标响应回调
:
空白 目标响应回调(标准::共享未来<;目标手柄斐波纳契::SharedPtr>; 未来)
{
汽车 目标句柄 = 未来.获取();
如果 (!目标句柄) {
RCLCPP_ERROR(此->;get_logger(), 目标被服务器拒绝";);
} 不然 {
RCLCPP_INFO(此->;get_logger(), 服务器接受目标,等待结果";);
}
}
假设服务器接受了目标,就会开始处理。对客户端的任何反馈都将由 反馈回调
:
空白 反馈回调(
目标手柄斐波纳契::SharedPtr,
缢 标准::共享_ptr<;缢 斐波那契::反馈意见>; 反馈)
{
标准::字符串流 ss;
ss <<; "收到序列中的下一个号码:";;
对于 (汽车 编号 : 反馈->;部分序列) {
ss <<; 编号 <<; " ";;
}
RCLCPP_INFO(此->;get_logger(), ss.字符串().c_str());
}
服务器处理完毕后,会向客户端返回一个结果。结果由 result_callback
:
空白 result_callback(缢 目标手柄斐波纳契::包裹结果 及样品; 结果)
{
开关 (结果.代码) {
个案 rclcpp_action::结果代码::成功:
断裂;
个案 rclcpp_action::结果代码::中止:
RCLCPP_ERROR(此->;get_logger(), 目标失败";);
返回;
个案 rclcpp_action::结果代码::取消:
RCLCPP_ERROR(此->;get_logger(), 目标被取消";);
返回;
默认:
RCLCPP_ERROR(此->;get_logger(), 未知结果代码";);
返回;
}
标准::字符串流 ss;
ss <<; 已收到结果: ";;
对于 (汽车 编号 : 结果.结果->;顺序) {
ss <<; 编号 <<; " ";;
}
RCLCPP_INFO(此->;get_logger(), ss.字符串().c_str());
rclcpp::关闭();
}
我们现在有了一个功能完备的行动客户端。让我们构建并运行它。
3.2 编译行动客户端
在上一节中,我们将动作客户端代码安装到位。为了让它编译和运行,我们还需要做几件事。
首先,我们需要设置 CMakeLists.txt,以便编译动作客户端。打开 action_tutorials_cpp/CMakeLists.txt
之后添加以下内容 查找软件包
电话
add_library(action_client SHARED
src/fibonacci_action_client.cpp)
target_include_directories(action_client PRIVATE
$<BUILD_INTERFACE:${cmake_current_source_dir}/include>;
$<INSTALL_INTERFACE:include>;)
target_compile_definitions(action_client
PRIVATE "ACTION_TUTORIALS_CPP_BUILDING_DLL")
ament_target_dependencies(action_client
"action_tutorials_interfaces";
"rclcpp";
"rclcpp_action";
"rclcpp_components")
rclcpp_components_register_node(action_client PLUGIN "action_tutorials_cpp::FibonacciActionClient" EXECUTABLE fibonacci_action_client)
install(TARGETS
动作客户端
存档目的地 lib
图书目录 lib
运行时间目的地 bin)
现在我们可以编译软件包了。转到 action_ws
然后运行:
胶管 构建
这将编译整个工作区,包括 斐波那契行动客户端
在 动作教程_cpp
包装
3.3 运行行动客户端
现在我们已经创建了动作客户端,可以运行它了。首先,确保动作服务器在单独的终端中运行。现在,将我们刚刚构建的工作区 (action_ws
),并尝试运行动作客户端:
玫瑰2 运行 动作教程_cpp 斐波那契行动客户端
你应该能看到目标被接受、反馈被打印以及最终结果的记录信息。
摘要
在本教程中,您将逐行组装 C++ 动作服务器和动作客户端,并配置它们以交换目标、反馈和结果。