ROS技术点滴 —— action通信

ROS中常用的通信机制是话题(Topic)和服务(Service),但是在很多场景下,这两种通信机制往往满足不了所有需求。

比如机械臂控制,如果用话题发布运动目标,由于话题是单向通信,那就需要另外订阅一个话题,来获得机器人运动过程中的状态反馈。如果用服务发布运动目标,虽然可以获得一次反馈信息,但是对于控制来讲数据太少,而且如果反馈迟迟没有收到,也只能傻傻等待,干不了其他事情。简单来讲就是控制方发送一次命令,之后接收方在一段时间内反馈自己的信息。

那么有没有一种更加适合的通信机制,来满足类似这样场景的需求呢?当然有,那就是action。

一、什么是action

ROS中有一个名为actionlib的功能包,实现了action的通信机制。那什么是action呢?

action是一种类似于Service的问答通信机制,不同之处在于action带有连续反馈,可以不断反馈任务进度,也可以在任务过程中中止运行。

回到前边提到的场景,使用action发布机器人的运动目标。机器人接收到这个action后,就开始运动,在运动过程中不断反馈当前运动状态;过程中也可以随时取消运动,让机器人停止;当机器人完成运动目标后,action返回任务完成的消息。

二、action的工作机制

action也采用服务器/客户端(Client/Server)的工作模式,如下图所示。
在这里插入图片描述
Client和Server之间通过actionlib定义的“action protocol”进行通信。这种通信协议基于ROS的话题机制实现,为用户提供如下图所示Client和Server的接口。
在这里插入图片描述
Client向Server端发布任务目标以及在必要的时候取消任务,Server会向Client发布当前状态、实时反馈和任务执行的最终结果。
(1)goal:发布任务目标;
(2)cancel:请求取消任务;
(3)status:通知Client当前的状态;
(4)feedback:周期反馈任务运行的监控数据;
(5)result:向Client发送任务的执行结果,只发布一次。

三、action的定义

ROS中的message通过.msg文件定义,service通过.srv文件定义,那么action是不是也是通过类似的方法定义呢?

答案是肯定的。action通过.action文件定义,放置在功能包的action文件夹下,格式如下:

# 定义目标信息
uint32 dishwasher_id # Specify which dishwasher we want to use

---
# 定义结果信息
uint32 total_dishes_cleaned

---
# 定义周期反馈的消息
float32 percent_complete

可见,一个 action的定义需要三个部分:goal、result、feedback。

创建.action文件之后,还需要将这个文件编译,在CMakeLists.txt文件中添加如下编译规则:

find_package(catkin REQUIRED genmsg actionlib_msgs actionlib)
add_action_files(DIRECTORY action FILES DoDishes.action)
generate_messages(DEPENDENCIES actionlib_msgs)

在功能包的package.xml中添加如下配置:

<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<run_depend>actionlib</run_depend>
<run_depend>actionlib_msgs</run_depend>

现在就可以进行编译了,编译完成后会产生一系列.msg文件:

  • DoDishesAction.msg
  • DoDishesActionGoal.msg
  • DoDishesActionResult.msg
  • DoDishesActionFeedback.msg
  • DoDishesGoal.msg
  • DoDishesResult.msg
  • DoDishesFeedback.msg

这些不同的消息类型,在调用action时根据需要使用。从.action编译生成的这些文件也可以看到,action确实是一种基于话题的、更加高层的通信机制。

四、实现action通信

接下来,我们就来学习如何实现一个action的客户端和服务端节点。这里需要创建一个功能包action_tutorials,并且按照上小节的方法完成.action文件的创建。

  • 创建客户端

在action的定义中,描述了一个洗盘子的任务。首先实现客户端,发出action请求,例程源码具体内容如下,实现流程可以参见代码中的注释:

#include <actionlib/client/simple_action_client.h>
#include "action_tutorials/DoDishesAction.h"

typedef actionlib::SimpleActionClient<action_tutorials::DoDishesAction> Client;

// 当action完成后会调用该回调函数一次
void doneCb(const actionlib::SimpleClientGoalState& state,
       const action_tutorials::DoDishesResultConstPtr& result)
{
   ROS_INFO("Yay! The dishes are now clean");
   ros::shutdown();
}

// 当action激活后会调用该回调函数一次
void activeCb()
{
   ROS_INFO("Goal just went active");
}

// 收到feedback后调用该回调函数
void feedbackCb(const action_tutorials::DoDishesFeedbackConstPtr& feedback)
{
   ROS_INFO(" percent_complete : %f ", feedback->percent_complete);
}

int main(int argc, char** argv)
{
   ros::init(argc, argv, "do_dishes_client");

   // 定义一个客户端
   Client client("do_dishes", true);

   // 等待服务器端
   ROS_INFO("Waiting for action server to start.");
   client.waitForServer();
   ROS_INFO("Action server started, sending goal.");

   // 创建一个action的goal
   action_tutorials::DoDishesGoal goal;
   goal.dishwasher_id = 1;

   // 发送action的goal给服务器端,并且设置回调函数
   client.sendGoal(goal,  &doneCb, &activeCb, &feedbackCb);

   ros::spin();

   return 0;
}
  • 创建服务端

接下来要实现服务端节点,完成洗盘子的任务,并且反馈洗盘子的实时进度,实现源码action_tutorials/src/DoDishes_server.cpp的具体内容如下:

#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h>
#include "action_tutorials/DoDishesAction.h"

typedef actionlib::SimpleActionServer<action_tutorials::DoDishesAction> Server;

// 收到action的goal后调用该回调函数
void execute(const action_tutorials::DoDishesGoalConstPtr& goal, Server* as)
{
   ros::Rate r(1);
   action_tutorials::DoDishesFeedback feedback;

   ROS_INFO("Dishwasher %d is working.", goal->dishwasher_id);

   // 假设洗盘子的进度,并且按照1hz的频率发布进度feedback
   for(int i=1; i<=10; i++)
   {
       feedback.percent_complete = i * 10;
       as->publishFeedback(feedback);
       r.sleep();
   }

   // 当action完成后,向客户端返回结果
   ROS_INFO("Dishwasher %d finish working.", goal->dishwasher_id);
   as->setSucceeded();
}

int main(int argc, char** argv)
{
   ros::init(argc, argv, "do_dishes_server");
   ros::NodeHandle n;

   // 定义一个服务器
   Server server(n, "do_dishes", boost::bind(&execute, _1, &server), false);
   
   // 服务器开始运行
   server.start();

   ros::spin();

   return 0;
}
  • 运行效果

编译成功后,首先使用如下命令启动客户端节点,由于服务端没有启动,客户端会保持等待:

$ rosrun action_tutorials DoDishes_client

然后使用如下命令启动服务端节点后,会立刻收到客户端的请求,并且开始任务、发送反馈,在客户端可以看到反馈的进度信息。

$ rosrun action_tutorials DoDishes_server

运行效果如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值