ROS中常用的通讯机制是topic和service,但是在很多场景下,这两种通讯机制往往满足不了我们的需求,比如上一篇博客我们讲到的机械臂控制,如果用topic发布一个运动目标,由于topic没有反馈,还需要另外订阅一个机械臂状态的topic,来获得运动过程的状态。如果用service来发布运动目标,虽然可以获得反馈,但是反馈只有一次,对于我们的控制来讲数据太少了,而且如果反馈迟迟没收到,也只能傻傻等待,干不了其他事情。那么有没有一种更加适合的通讯机制,来满足类似这样场景的需求呢?当然是有的,就是我们这篇要讲到的action通讯机制。
一、什么是action
ROS中有一个actinlib的功能包集,实现了action的通讯机制。那么什么是action呢?action也是一种类似于service的问答通讯机制,不一样的地方是action还带有一个反馈机制,可以不断反馈任务的实施进度,而且可以在任务实施过程中,中止运行。
回到我们前边提到的场景,我们使用action来发布一个机器人的运动目标,机器人接到这个action后,就开始运动,在运动过程中不断反馈当前的运动状态,在运动过程中我们可以取消运动,让机器人停止,如果机器人完成了运动目标,那么action会返回任务完成的标志。
二、action的工作机制
action采用了服务器端/客户端(client and server)的工作模式,如下图所示:
client和server之间通过actionlib定义的“action protocol”进行通讯。这种通讯协议是基于ROS的消息机制实现的,为用户提供了client和server的接口,接口如下图所示:
在上边的action的接口框图上,我们可以看到,client向server端发布任务目标以及在必要的时候取消任务,server会向client发布当前的状态、实时的反馈和最终的任务结果。
- goal:任务目标
- cancel:请求取消任务
- status:通知client当前的状态
- feedback:周期反馈任务运行的监控数据
- result:向client发送任务的执行结果,这个topic只会发布一次。
三、action的定义
ROS中的message是通过.msg文件来定义的,service是通过.srv定义,那么action是不是也是通过类似的方法定义呢?答案是肯定的,action通过.action文件定义,放置在功能包的action文件夹下,格式类似如下:
# Define the goal
uint32 dishwasher_id # Specify which dishwasher we want to use
---
# Define the result
uint32 total_dishes_cleaned
---
# Define a feedback message
float32 percent_complete
从上边的.action文件示例中,我们可以发现,定义一个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确实是基于message实现的。
四、例程学习
接下来,我们就来学习一下,如何实现一个action的客户端和服务器端,这里需要创建一个功能包,并且按照上边的方法完成action数据的定义。我这里创建的功能包命名为:action__tutorials,也可以在我的github上找到:https://github.com/huchunxu/ros_blog_sources
4.1 客户端
在上节的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;
}
4.2 服务器端
接下来要实现服务器端的节点,完成洗盘子的任务,并且反馈洗盘子的实时进度。
#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;
}
4.3 运行效果
编译运行,首先启动客户端的节点,由于服务端没有启动,客户端会保持等待;然后启动服务器端的节点,会立刻收到服务器端的请求,并且开始任务、发送反馈,在客户端可以看到反馈的进度信息。
· 客户端:
· 服务器端:
这个例程是我根据官方的例程修改而来,加入了反馈的回调,有兴趣的读者也可以参考action的wiki,进行更加深入的学习。
评论列表(22条)
博主,您好,我想问有没有办法实现在gazebo多机器人仿真中用action分别给不同机器人发送不同的目标点?
博主,您好!能否请教一个问题:生成的7个msgs 如何区别,什么时候用怎样的消息体,另外能否用topic来代替action的发送?
可以动手试一下,名称和功能都有区别,可以通过topic代替action
古月大哥,你好!请教个 问题:如果我想使用action的client.sendGoal函数来发送多个目标点给小车进行导航定位,是不是每一次都等一个目标点执行完,才能执行下一个client.sendGoal目标点的发送?不能通过client.sendGoal一次性发完所有目标点给move_base里面的server服务吗?
我在疑惑一个问题:如果客户端client.sendGoal函数每次发送一个目标点,都要等服务器server到达该目标点之后返还信号,客户端才能继续发下一个目标点的话,那么我的客户端程序就一直停留在client.sendGoal等待函数在等目标点的执行,而造成了有点类似while()函数的效果,其他功能函数程序就无法正常执行。只有等待所有的目标点执行完之后,才释放。。。
我是用Qt来写的界面:把要设定的目标点放到一个数组里面,比如说:有4个目标点,故设定了4数组存放目标点;然后通过一个for循环,每次通过client.sendGoal发送一个目标点数据;因为Qt是信号与槽的操作关系,我想通过Qt的一个按键点击,一键执行这个的功能并发送目标点数据给小车服务器端执行航点任务。虽然所有目标点都能够正常发送给小车执行,但这里就存在了这个问题:在小车执行航点任务过程中,Qt控制界面变成灰色,好像进入了一个死循环等待过程,无法点击控制面板的其他功能按钮….望指点迷糊的我,谢谢各位❀❀❀!
直到执行完所有导航点之后,Qt控制面板恢复正常(注:此过程小车是正常根据设定目标点导航的,写目标点没有问题)
古月大哥,你好!请教个问题:如果我想使用action的client.sendGoal函数来发送多个目标点给小车进行导航定位,是不是每一次都等一个目标点执行完,才能执行下一个client.sendGoal目标点的发送?不能通过client.sendGoal一次性发完所有目标点给move_base里面的server服务吗?
是的
古老师您好:
我想要更换末端执行器,(比如 现场有多个执行器 机械臂要更换某一个)
测试了 robot_model::LinkModel::setParentLinkModel robot_model::JointModelGroup::setEndEffectorParent 都不管用.
请问操作步骤怎么做呢?
谢谢您!
这个需要在urdf模型中修改,动态修改的话我也不确定如何操作
古老师,又是我。之前在您的另一篇文章下和您有很多互动。看了您这篇博客,想请教您:
1、这里提到的使用action来发布一个机器人的运动目标和之前moveit编程设置一个运动目标是一个道理吗?如果不是,两者有什么区别?
2、之前我向您问的那个,ctrl+c后最后一根手指仍有规划的痕迹,可以通过这里的cancel解决吗?
谢谢您的回答!!
1. 一样的,action是ROS的一种通信机制
2. 如果是重复显示,就是rviz配置的问题,如果是动作没结束,就是action的问题
古月老师,请问一下。我可以在action的基础上结合qt进行ui编程么?如果可以的话,具体应该怎么做呢?
可以的,qt编程本身就是一个独立的系统,可以直接调用ROS的部分,就像C++调用其他库一样,需要包含头文件,链接相应的库,然后在程序中调用。
月哥 我照着《learning ros robotics programming programming 》上面的actionlib教程写了一个.cpp文件,给机器人发送一个目标点,都按照教程做了,结果运行rusrun 时报错 具体信息为 [rosrun] Couldn’t find executable named talker below /home/catkin_ws/src/beginner2_tutorials .cpp文件在beginner2_tutorials文件夹下的src文件里面, 想问一下怎么解决,卡在这里好久了,谢谢
c++代码得编译,最后运行的是生成的可执行文件,你这个路径不对呀,如果编译没问题的话,可执行文件应该是在catkin_ws/devel里边的
我用catkin_make_isolated –install –use-ninja 编译了(因为安装了cartographer),编译没有报错, 我在devel文件夹下面只有一个_setup_util.py,没有找到类似的可执行文件, 还有 我在catkin_ws/src文件下面新建一个功能包,在功能包内的src文件夹下面写了一个.py的程序,然后运行rosrun package nav.py 也是报错 跟这个一样 ,
建议cartographer单独做一个工作空间,编译不太一样,路径上会有问题,我之前也遇到过这样的问题,分成两个工作空间是最有效的
我试过单独再建一个工作空间,在里面建一个功能包,在功能包的src文件夹下面写了一个.py程序,运行时也是同样的错误 那我直接把 我现在装了cartographer的作为一个工作空间,主要用作建图 然后再建一个工作空间,用作turtlebot3的导航等后续功能,可以把
是这样的,工作空间互不干扰,记得配置环境变量
帮了大忙了,谢谢!