ROS2动作通信的实现


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  编译

在这里插入图片描述

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chris·Bosh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值