ros程序源码分析3--action篇

客户端程序

头文件部分:

1 #include <new_pkg/DoDishesAction.h> // Note: "Action" is appended
2 #include <actionlib/client/simple_action_client.h>

此处,actionlibaction编程的库,包含action编程的所有函数、类及命名空间,详细列举见http://docs.ros.org/api/actionlib/html/namespaceactionlib.html

本代码应用的是简单action建立的两个类SimpleActionClientSimpleActionServer两个类,分别定义于simple_action_client.hsimple_action_server.h中。

进入ROS安装的目录可以看到

actionlib下有clientserver两个文件夹,上述两个头文件分别在这两个文件夹中。

另外一点注意到,本程序开始没有包含ros/ros.h头文件,是因为在上面两个action头文件中都包含了ros.h

在编译完自定义的DoDishes.action文件后,在以下目录中会生成7个头文件。

DoDishesAction.h中,包含了另外带Action字样的头文件,

1 #include <new_pkg/DoDishesActionGoal.h>
2 #include <new_pkg/DoDishesActionResult.h>
3 #include <new_pkg/DoDishesActionFeedback.h>

在其余三个头文件中,又分别包含了另外三个不带Action字样的头文件,比如在DoDishesActionGoal.h中有

1 #include <new_pkg/DoDishesGoal.h>

所以只需要包含DoDishesAction.h,其他所有的头文件就都包含了进来。

主程序

 1 typedef actionlib::SimpleActionClient<new_pkg::DoDishesAction> Client;
 2 int main(int argc, char** argv)
 3 {
 4     ros::init(argc, argv, "do_dishes_client");
 5     Client client("do_dishes", true); // true -> don't need ros::spin()
 6     client.waitForServer();
 7     new_pkg::DoDishesGoal goal;
 8     goal.dishwasher_id=3; //id为3的盘子为目标
 9     client.sendGoal(goal,&doneCb,&activeCb,&feedbackCb);
10     client.waitForResult(ros::Duration(11.0));
11     printf("Current State: %s
", client.getState().toString().c_str());
12     return 0;

在定义对象之初就需要用数据类型对抽象类进行实例化,因为类型名称长所以用typedef另起一个短的名字(第一行)

SimpleActionClient类的构造函数中

第一个参数是action名称,第二个参数表示是否使用内置spin函数,如果为真则始终开启消息回调函数,如果为假则需要自定义调用回调函数。构造函数有带NodeHandle参数和不带的类型,不带的就在函数内自动加,更省事。

客户端要用到的函数:

client.waitForServer(),客户端等待服务器函数,可以传递一个ros::Duration作为最大等待值,程序进入到这个函数开始挂起等待,服务器就位或者达到等待最大时间退出,前者返回true,后者返回false.

client.waitForResult(),客户端等待服务器本次结束函数,同上可以传递最大等待值。接收到True意味着本次交互服务器端已完成。

client.sendGoal(),客户端发送目标函数,本函数一共需要四个参数。第一个必须,另三个可选。

第一个参数就是本次交互的目标,第二个参数是服务端运行结束时调用的函数,第三个是服务器刚刚收到参数激活时调用的函数,第四个是服务器在进程中不停回传反馈时调用的函数。

其中三个函数的返回值类型定义如下

在这出现了boost::function这个东西,它是一个函数对象的容器,尖括号里是函数签名,圆括号外是返回类型,圆括号里是参数类型,符合函数签名的函数可以被存放到boost::function定义的对象中。比如

 1 bool bigger(int i,int j)
 2 {
 3     return (i>j); 
 4 }
 5 int main()
 6 {
 7     boost::function<bool (int,int)> f;
 8     f=&bigger;
 9     f(10 , 9);
10 }

main函数中f(10,9)就等同于bigger(10,9).

所以上面的三个类型也定义了要传递的三个函数的类型。

本程序中这三个程序如下

 1 void activeCb()
 2 {
 3     ROS_INFO("Goal just went active");
 4 }
 5 void doneCb(const actionlib::SimpleClientGoalState& state,const new_pkg::DoDishesResultConstPtr& result)
 6 {
 7     ROS_INFO("Yay! %u dish(s) are now clean",result->total_dishes_cleaned);
 8     ROS_INFO("Current State: %s",state.toString().c_str());
 9     ros::shutdown();//ROS_INFO也将不能用,不影响printf
10 }
11 void feedbackCb(const new_pkg::DoDishesFeedbackConstPtr& feedback)
12 {
13     ROS_INFO(" percent_complete : %f ", feedback->percent_complete);
14 }

注意里面的Ptr结尾的是指针类型,在索引其成员时用->而不能直接用.运算符。

当调用ros::shutdown()函数来关闭节点时,会终结所有开放的订阅,发布,服务,调用。

对结果信息和对结束状态信息的处理在doneCb()函数中,对反馈信息的处理在feedbackCb()函数中,如果不需要处理这些信息,可以在sendGoal()函数参数处缺省。

注意传递函数的时候在sendGoal中加&.

服务器端程序

1 typedef actionlib::SimpleActionServer<new_pkg::DoDishesAction> Server;             
2 int main(int argc, char** argv)
3 {
4     ros::init(argc, argv, "do_dishes_server");
5     Server server("do_dishes", boost::bind(&execute, _1, &server), false);
6     server.start();
7     ros::spin();
8     return 0;
9 }

程序中用到的构造函数原型如下:

其中typedef boost::function< void(const GoalConstPtr &)>  ExecuteCallback

最后一个参数auto_start为是否自动启动,若为true则服务器一构建就开始启动,若为false就不自动启动等程序中的server.start()函数来启动。      

中间execute_callback这个参数也是一个函数容器,接收<void(const GoalConstPtr &)>类型的函数。

这个函数在server接收到新goal是运行一次,如果在这个执行函数中要用到server本身,就需要代码中这样使用。

因为构造函数中的这个函数参数只允许有一个形参,要把server本身也传进去使用boost::bind(&execute,_1,&server)这个语句。

boost::bind()函数功能的简要说明:

boost::bind(&fun, _2, 6)( 2, 5); // 等同与 fun(5, 6)

也就是说代码中的boost语句相当于  execute(  ,&server),第一个参数没有值,形成的新函数就相当于只有一个形参。第二个参数怎么传递看自定义的execute第二个形参是怎么使用的。

execute()函数的定义如下:

 1 void execute(const new_pkg::DoDishesGoalConstPtr& goal, Server* as)
 2 {
 3     new_pkg::DoDishesFeedback feedback;
 4     ROS_INFO("Dishwasher %u is working.",goal->dishwasher_id);
 5 
 6     ros::Rate r(2);
 7     for(int i=1;i<=10;i++)
 8     {
 9         feedback.percent_complete=i*10;
10         as->publishFeedback(feedback);
11         r.sleep();
12     }
13     ROS_INFO("Dishwasher %u finish working.",goal->dishwasher_id); 
14     new_pkg::DoDishesResult result;
15     result.total_dishes_cleaned=1; 
16     as->setSucceeded(result);
17 }

goal的接收和结果及反馈的发布都在这个函数当中。

发布反馈用publishFeedback()函数,函数重载为两种形式,参数可以是反馈信息本身也可以是其地址。

发布成功结果用setSucceeded()函数,可以传递两个参数,第一个就是result,第二个是文本信息,关于文本信息在客户端的接收目前我还没有去学习。

发布失败结果用setAborted(),参数同上。

原文地址:https://www.cnblogs.com/xuhaoforwards/p/9452510.html