文章目录
- 1.动作通信的概念及应用场景
- 1.1 概念
- 1.2 应用场景
- 2.准备工作
- 3.动作通信的实现
- 3.1 动作通信接口消息
- 3.2 服务端实现
- 3.3 客户端实现
- 3.4 编译及运行
1.动作通信的概念及应用场景
1.1 概念
动作通信适用于长时间运行的任务。就结构而言动作通信由目标、反馈和结果三部分组成;就功能而言动作通信类似于服务通信,动作客户端可以发送请求到动作服务端,并接收动作服务端响应的最终结果,不过动作通信可以在请求响应过程中获取连续反馈,并且也可以向动作服务端发送任务取消请求;就底层实现而言动作通信是建立在话题通信和服务通信之上的,目标发送实现是对服务通信的封装,结果的获取也是对服务通信的封装,而连续反馈则是对话题通信的封装。
1.2 应用场景
机器人导航到某个目标点,此过程需要一个节点A发布目标信息,然后一个节点B接收到请求并控制移动,最终响应目标达成状态信息。
其感觉很像是服务通信的过程
因为需求中要A发送目标,B执行并返回结果,这是一个典型的基于请求响应的应答模式,不过,如果只是使用基本的服务通信实现,存在一个问题:
导航是一个过程,是耗时操作,如果使用服务通信,那么只有在导航结束时,才会产生响应结果,而在导航过程中,节点A是不会获取到任何反馈的,从而可能出现程序"假死"的现象,过程的不可控意味着不良的用户体验,以及逻辑处理的缺陷(比如:导航中止的需求无法实现)。
更合理的方案应该是:导航过程中,可以连续反馈当前机器人状态信息,当导航终止时,再返回最终的执行结果。
一般适用于耗时的请求响应场景,用以获取连续的状态反馈。
提示:这里对文章进行总结:
2.准备工作
老样子:
3.动作通信的实现
3.1 动作通信接口消息
3.2 服务端实现
需求:编写动作通信,动作客户端提交一个整型数据N,动作服务端接收请求数据并累加1-N之间的所有整数,将最终结果返回给动作客户端,且每累加一次都需要计算当前运算进度并反馈给动作客户端。
// 1.包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"
using namespace std::placeholders;
using base_interfaces_demo::action::Progress;
using GoalHandleProgress = rclcpp_action::ServerGoalHandle<Progress>;// 3.定义节点类;
class MinimalActionServer : public rclcpp::Node
{
public:
explicit MinimalActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions())
: Node("minimal_action_server", options)
{
// 3-1.创建动作服务端;
this->action_server_ = rclcpp_action::create_server<Progress>( this, "get_sum", std::bind(&MinimalActionServer::handle_goal, this, _1, _2), std::bind(&MinimalActionServer::handle_cancel, this, _1), std::bind(&MinimalActionServer::handle_accepted, this, _1));
RCLCPP_INFO(this->get_logger(),"动作服务端创建,等待请求...");
}private:
rclcpp_action::Server<Progress>::SharedPtr action_server_;
// 3-2.处理请求数据;
rclcpp_action::GoalResponse handle_goal(const rclcpp_action::GoalUUID & uuid,std::shared_ptr<const Progress::Goal> goal)
{
(void)uuid;
RCLCPP_INFO(this->get_logger(), "接收到动作客户端请求,请求数字为 %ld", goal->num);
if (goal->num < 1) { return rclcpp_action::GoalResponse::REJECT;
}
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
} // 3-3.处理取消任务请求;
rclcpp_action::CancelResponse handle_cancel( const std::shared_ptr<GoalHandleProgress> goal_handle)
{ (void)goal_handle; RCLCPP_INFO(this->get_logger(), "接收到任务取消请求"); return rclcpp_action::CancelResponse::ACCEPT;
} void execute(const std::shared_ptr<GoalHandleProgress> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "开始执行任务");
rclcpp::Rate loop_rate(10.0);
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<Progress::Feedback>();
auto result = std::make_shared<Progress::Result>();
int64_t sum= 0; for (int i = 1; (i <= goal->num) && rclcpp::ok(); i++) { sum += i; // Check if there is a cancel request if (goal_handle->is_canceling()) { result->sum = sum; goal_handle->canceled(result); RCLCPP_INFO(this->get_logger(), "任务取消"); return; } feedback->progress = (double_t)i / goal->num; goal_handle->publish_feedback(feedback); RCLCPP_INFO(this->get_logger(), "连续反馈中,进度:%.2f", feedback->progress); loop_rate.sleep(); } if (rclcpp::ok()) {
result->sum = sum;
goal_handle->succeed(result);
RCLCPP_INFO(this->get_logger(), "任务完成!");
}
}// 3-4.生成连续反馈。 void handle_accepted(const std::shared_ptr<GoalHandleProgress> goal_handle)
{
std::thread{std::bind(&MinimalActionServer::execute, this, _1), goal_handle}.detach();
}
};int main(int argc, char ** argv)
{
// 2.初始化 ROS2 客户端;
rclcpp::init(argc, argv);
// 4.调用spin函数,并传入节点对象指针;
auto action_server = std::make_shared<MinimalActionServer>();
rclcpp::spin(action_server);
// 5.释放资源。
rclcpp::shutdown();
return 0;
}
3.3 客户端实现
需求:编写动作客户端实现,可以提交一个整型数据到服务端,并处理服务端的连续反馈以及最终返回结果。
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"
using base_interfaces_demo::action::Progress;
using GoalHandleProgress = rclcpp_action::ClientGoalHandle<Progress>;
using namespace std::placeholders;// 3.定义节点类;
class MinimalActionClient : public rclcpp::Node
{
public:
explicit MinimalActionClient(const rclcpp::NodeOptions & node_options = rclcpp::NodeOptions())
: Node("minimal_action_client", node_options)
{ // 3-1.创建动作客户端; this->client_ptr_ = rclcpp_action::create_client<Progress>(this,"get_sum"); } // 3-2.发送请求; void send_goal(int64_t num) { if (!this->client_ptr_) { RCLCPP_ERROR(this->get_logger(), "动作客户端未被初始化。"); } if (!this->client_ptr_->wait_for_action_server(std::chrono::seconds(10))) { RCLCPP_ERROR(this->get_logger(), "服务连接失败!"); return; } auto goal_msg = Progress::Goal(); goal_msg.num = num; RCLCPP_INFO(this->get_logger(), "发送请求数据!"); auto send_goal_options = rclcpp_action::Client<Progress>::SendGoalOptions(); send_goal_options.goal_response_callback =std::bind(&MinimalActionClient::goal_response_callback, this, _1); send_goal_options.feedback_callback =std::bind(&MinimalActionClient::feedback_callback, this, _1, _2); send_goal_options.result_callback =std::bind(&MinimalActionClient::result_callback, this, _1); auto goal_handle_future = this->client_ptr_->async_send_goal(goal_msg, send_goal_options);
}private:
rclcpp_action::Client<Progress>::SharedPtr client_ptr_;// 3-3.处理目标发送后的反馈;
void goal_response_callback(GoalHandleProgress::SharedPtr goal_handle)
{
if (!goal_handle)
{ RCLCPP_ERROR(this->get_logger(), "目标请求被服务器拒绝!"); } else { RCLCPP_INFO(this->get_logger(), "目标被接收,等待结果中"); }
} // 3-4.处理连续反馈;
void feedback_callback(GoalHandleProgress::SharedPtr,const std::shared_ptr<const Progress::Feedback> feedback)
{ int32_t progress = (int32_t)(feedback->progress * 100); RCLCPP_INFO(this->get_logger(), "当前进度: %d%%", progress); }// 3-5.处理最终响应。
void result_callback(const GoalHandleProgress::WrappedResult & result)
{ switch (result.code) { case rclcpp_action::ResultCode::SUCCEEDED: break; case rclcpp_action::ResultCode::ABORTED: RCLCPP_ERROR(this->get_logger(), "任务被中止"); return; case rclcpp_action::ResultCode::CANCELED: RCLCPP_ERROR(this->get_logger(), "任务被取消"); return; default: RCLCPP_ERROR(this->get_logger(), "未知异常"); return; } RCLCPP_INFO(this->get_logger(), "任务执行完毕,最终结果: %d", result.result->sum); }
};int main(int argc, char ** argv)
{
// 2.初始化 ROS2 客户端;
rclcpp::init(argc, argv);
// 4.调用spin函数,并传入节点对象指针;
auto action_client = std::make_shared<MinimalActionClient>();
action_client->send_goal(10);
rclcpp::spin(action_client);
// 5.释放资源。
rclcpp::shutdown();
return 0;
}
3.4 编译及运行
CMakeLists怎么该就不说了,前面两个已经说得很清楚了!
colcon build --packages-select cpp03_action 编译