服务对机制对于查询、管理配置等简单的操作很方便,但如果需要一个很耗时的操作时就不适用了
如果需要机器人运动到比较远的地方,这个操作比较费时间,而且完成的时间也无法预知,如果半路上有任何其他因素会导致整个操作的时间增加
比如对机器人操作:首先要发一个包含目标位置的请求,然后等一段不确定的时间,知道接受到相应。在等待过程中,请求程序会被强行阻塞,因而无法完全获知机器人的操作进度,更不能取消操作或者改变目标的位置,极其不方便,因此有了动作
动作机制很适合作为时间不确定,目标导向性接口。
服务机制是同步的,动作则是异步的,
动作就是规定了一系列话题的组合使用:目标、结果和反馈。
动作使用目标来启动一次操作,在执行过程中反馈操作的进度信息,还支持取消当前操作,并在操作完成后发送结果
动作接口实现操作机器人则变成:首先发一个目标,然后转去作其他事情。在操作过程中,会周期性的接受到执行进度的消息(已经移动的距离、预计完成的时间等),知道操作完成,受到最终的结果(顺利完成或者提前终止)
而且如果来了更重要的任务可以随时取消但前操作,并开始一个新的操作。
创建一个动作
首先要在动作中定义目标、结果和反馈。
与服务的.srv 相似动作为 .action 。在编程过程中.action文件也会被打包为一个消息类型。
这里先定义一个简单的倒计时的动作,它可以进行倒计时,并在倒计时停止时发出信号,并会周期的发送剩余的时间。计数结束后会告诉我们总共计数的时间。(反馈会在下篇幅中在加入)
首先定义一个满足定时需求的动作:
将这个动作文件放在ROS包的action目录下, 类似与之前的服务 位于basic包下
就像服务定义文件一样,用三个短横线表示不同定义部分的分隔符
该文件中由三个部分:目标、结果和反馈
TImer.action
1 #This is an action definition dile, which has three parts :the goal ,the 2 #result, and the feedback. 3 # 4 #Part 1 :the goal , to be sent by client 5 # 6 #The amount of time we want ti wait duration time_to_wait 7 duration time_to_wait 8 --- 9 #Part 2 : the result , tobe sent by the server upon completion 10 # 11 # How much time wo waited duration time_elapsed 12 duration time_elapsed 13 #How many updates wo provided along the way uint32 updates_sent 14 uint32 updates_sent 15 --- 16 #Part 3 : the feedback ,to be sent periodically by the server during execution 17 # 18 # The anount id time that has elapsed from the start duration time_elapsed 19 duration time_elapsed 20 # The amount od time remaining until we're done duration time_remaining 21 duration time_remaining
编写好之后就是在根目录下运行catkin_make
创建好该动作实际使用的代码和类定义,需要在CMakeLists.txt文件中添加写内容
首先在find_package_files下添加actionlib_msgs
find_package(catkin REQUIRED COMPONENTS
rospy
roscpp
std_msgs
message_generation
actionlib_msgs
)
在确保在generate_message()中列出所有的消息依赖后加入另外的 actionlib_msgs
generate_messages(
DEPENDENCIES
std_msgs
actionlib_msgs
)
最后在catkin_package中添加actionlib_msgs依赖
catkin_package(
CATKIN_DEPENDS message_runtime
actionlib_msgs
)
在package.xml中加入
<build_depend>actionlib_msgs</build_depend> <exec_depend>actionlib_msgs</exec_depend>
上述步骤完成后在工作区顶层目录下运行catkin_make编译后会生成一些消息文件
包括:Timer.action , TimerActionFeedback.msg , TimerActionGoal.msg , TImerActionResult.msg , TimerFeedback.msg , TImerGoal.msg , TimerResult.msg
这些消息文件被用于实现动作的client/server协议
消息类文件会被转换为相应的类定义
动作与话题和服务一样,都是用回调函数机制,即回调函数在收到消息时被唤醒和调用
Simple_action_server.py
1 #!/usr/bin/env python 2 3 import rospy 4 5 import time #导入time库,提供定时器的计时功能 6 7 import actionlib #提供将要使用的SimpleActionServer. 8 9 from basic_action.msg import TimerAction , TimerGoal , TimerResult #导入自动生成的消息类 10 11 def do_timer(goal): #定义函数,将会在收到新的目标时被执行 goal参数本质上是一个TimerGoal类型的对象,其成员是Timer.action中goal部分中的内容 12 start_time = time.time() #time.time()函数保存当前的时间 13 time.sleep(goal.time_to_wait.to_sec()) #按照目标中需要等待的时间进行休眠 注意应将time_to_wait对象从ROS的Duration类型转换为秒 14 result = TimerResult() #构造结果消息类型为TimweResult,成员就是result部分中的内容 15 result.time_elapsed = rospy.Duration.from_sec(time.time() - start_time) #time_elapsed部分由当前时间与开始时间做差得到(需从秒转换为ROS的Duration类型) 16 result.updates_sent = 0 #这里为零 因为实际上没有发送任何反馈 17 server.set_succeeded(result) #最后以结果为参数,调用set_succeeded()告诉SimpleActionServer我们成功的执行了目标 18 19 rospy.init_node('timer_action_server') #初始化结点提供名字 20 server = actionlib.SimpleActionServer('timer',TimerAction,do_timer,False) #第一个参数为动作服务器的名称,第二个为动作服务器的类型,第三个为回调函数,即do_timer(),最后一个通过传递False参数来关闭动作服务器的自动启动功能。
21 server.start() #显示调用start()来启动动作服务器 22 rospy.spin() #进入ROS的spin()循环
编写完成后检查工作是否正常。启动roscore,然后于运行动作服务器
miao@openlib:~$ rosrun basic simple_action_server.py
然后检查相应话题有没有出现
miao@openlib:~$ rostopic list /rosout /rosout_agg /timer/cancel /timer/feedback /timer/goal /timer/result /timer/status
然后可以通过rsotopic 看看timer/goal 话题
miao@openlib:~$ rostopic info /timer/goal Type: basic_action/TimerActionGoal Publishers: None Subscribers: * /timer_action_server (http://openlib:32831/)
进一步查看TimerActionGoal是什么
miao@openlib:~$ rosmsg show TimerActionGoal [basic/TimerActionGoal]: std_msgs/Header header uint32 seq time stamp string frame_id actionlib_msgs/GoalID goal_id time stamp string id basic/TimerGoal goal duration time_to_wait
goal.time_to_wait部分看到时我们的目标定义,其他部分的额外信息是被服务器和客户端用来追踪动作执行状态的,但是在目标传入服务器端的回调函数前,这些信息会被去除,最后只剩下TimerGoal消息,可以在.action文件中定义
miao@openlib:~$ rosmsg show TimerGoal [basic/TimerGoal]: duration time_to_wait miao@openlib:~$ rosmsg show TimerResult [basic/TimerResult]: duration time_elapsed uint32 updates_sent miao@openlib:~$ rosmsg show TimerFeedback [basic/TimerFeedback]: duration time_elapsed duration time_remaining
然后使用actionlib中的SimpleActionClient作为客户端向服务器发送目标
simple_action_cient.py
1 #!/usr/bin/env python 2 3 import rospy 4 5 import actionlib #导入动作包 6 from basic_action.msg import TimerAction ,TimerGoal ,TimerResult #到如创建的.action文件生成的类 7 8 rospy.init_node('timer_action_client') #声明timer_action_client的节点 9 client = actionlib.SimpleActionClient('timer',TimerAction) #创建一个SimpleActionClient,构造函数的第一个参数为动作服务器的名称,要与之前的服务器名称相同,第二个参数为动作类型也要与服务器匹配 10 11 client.wait_for_server() #这里是等待动作服务器启动,,等待过程是通过检查先前看到的5个话题实现的。??? 12 13 goal = TimerGoal() #构造TimerGoal对象,并填入希望等待的时间, 14 15 goal.time_to_wait = rospy.Duration.from_sec(5.0) #将秒转换为ROS的Durantion类型 16 17 client.send_goal(goal) #发送给服务器 18 19 client.wait_for_result() #等待结果的到来
print('Time elapsed: %f' % (client.get_result().time_elapsed.to_sec())) #将得到的结果即等待时间转换为秒输出
运行一下
miao@openlib:~$ rosrun basic simple_action_client.py
Time elapsed: 5.005122
运行会出现5s的延迟,而结果则因为time.sleep()比5s更长