ros程序源码分析3--action篇
客户端程序
头文件部分:
1 #include <new_pkg/DoDishesAction.h> // Note: "Action" is appended 2 #include <actionlib/client/simple_action_client.h>
此处,actionlib是action编程的库,包含action编程的所有函数、类及命名空间,详细列举见http://docs.ros.org/api/actionlib/html/namespaceactionlib.html
本代码应用的是简单action建立的两个类SimpleActionClient和SimpleActionServer两个类,分别定义于simple_action_client.h和simple_action_server.h中。
进入ROS安装的目录可以看到
在actionlib下有client和server两个文件夹,上述两个头文件分别在这两个文件夹中。
另外一点注意到,本程序开始没有包含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\n", 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(),参数同上。