作者:teddyluo
Emai: huiwu.luo@aliyun.com
文档日期(首发):2019年07月19日
(注:2019年07月22日已同步更新到ROS Wiki)
ROS Wiki: http://wiki.ros.org/cn/actionlib/DetailedDescription
欢迎留言勘误
文章目录
ROS actioinlib
通讯机制
actioinlib
是ROS的一个功能包集,实现了基于action的通讯机制。action也是一种类似于service的问答通讯机制,不一样的地方是action还带有一个反馈机制,可以不断反馈任务的实施进度,而且可以在任务实施过程中,中止运行。action采用了服务器端/客户端(client and server)的工作模式,如下图所示。
actioinlib
服务器端描述
服务器状态机:
goal 由ActionClient启动。ActionServer接收到目标后,ActionServer会创建一个状态机来跟踪目标的状态(如下图所示。
请注意,此状态机跟踪单个目标,而不是ActionServer本身。因此,对于系统中的每个目标都有一个状态机。
服务器转换:大多数状态转换由服务器实现的一小组命令进行触发:
-
setAccepted
:检查目标后,决定开始处理它 -
setRejected
: 检查目标后,决定从不处理它,因为它是一个无效的请求(超出范围,资源不可用,无效等) -
setSucceeded
:通知目标已成功处理 -
setAborted
: 通知目标在处理过程中遇到错误,必须中止 -
setCanceled
: 由于取消请求,通知该目标不再处理
当然,客户端也能以异步方式触发状态转换:
CancelRequest
:客户端通知操作服务器它希望服务器停止处理的目标。
服务器状态:
-
中间状态
Pending
待处理 - 目标尚未由操作服务器处理Active 活动
- 目标正由操作服务器处理Recalling
已调用 - 目标尚未处理,并且已收到来自操作客户端的取消请求,但操作服务器尚未确认目标已取消Preempting
抢占 - 目标正在处理,并且已从操作客户端接收到取消请求,但操作服务器尚未确认目标已取消
-
终端状态
-
Rejected
已拒绝 - 目标被操作服务器拒绝,未经处理,没有来自操作客户端的取消请求 -
Succeeded
成功 - 操作服务器成功完成了目标 -
Aborted
中止 -我们的目标是终止了行动服务器,而不从操作客户端的外部请求取消 -
Recalled
已调用 - 在操作服务器开始处理目标之前,目标已被另一个目标或取消请求取消 -
Preempted
抢占 - 目标的处理被另一个目标取消,或取消请求发送到操作服务器
-
并发问题:
setAccepted-CancelRequest vs. CancelRequest-setAccepted:
即使在收到CancelRequest
之后,action server也可以设置接受目标,这有点不直观。 这是因为一个异步处理CancelRequest
的竞争条件。 也就是说,由于server implementer代码之外的其他内容正在触发转换,因此server implementer无法确定它们是处于[PENDING
]还是[RECALLING
]状态。
actioinlib
客户端描述
客户端的状态机
在actionlib
中,我们将服务器状态机视为主状态机,同时将客户端的状态机视为辅助/耦合(secondary/coupled)状态机,以尝试跟踪服务器的状态(如下图所示)。
客户端状态的过渡
-
服务器触发的转换
-
报告的[State(状态)]:由于客户端正在尝试跟踪服务器的状态,大多数转换由服务器报告其状态到Action Client触发。
-
接收结果消息:在这种情况下,该服务器发送一个结果消息给客户端。接收结果将始终表示跟踪结束的目标。
-
-
客户端触发转换:
- 取消目标:请求服务器停止处理此目标
-
“跳过(Skipping)”的状态
-
给定我们的基于ROS的传输层,客户端可能没有从服务器接收所有的状态更新。因此,我们必须允许客户端状态机“跳过”服务器触发状态。\par
例如:如果客户端在[WAITING FOR GOAL ACK
] ,从服务器状态接收PREEMPTED
的更新,客户端的状态可以跳过过去的[ACTIVE
],并直接过渡到[WAITING FOR RESULT
] -
由于多个action clients可以连接到单个action server,所以第二个client可以取消由第一个client发送的目标。因此,如果[
RECALLING
]是从server接收到的状态,则client从[PENDING
]过渡至[RECALLING
]应该判为有效的。
-
Action的接口和传输层
action的client和server之间通过actionlib
定义的“action protocol”进行通讯。这种通讯协议是基于ROS的消息机制实现的,为用户提供了client和server的接口,接口如下图所示。
可以看到,在action的接口框图上,client向server端发布任务目标以及在必要的时候取消任务,server会向client发布当前的状态、实时的反馈和最终的任务结果。ROS Messages有:
goal
:任务目标cancel
:请求取消任务status
:通知client当前的状态feedback
:周期反馈任务运行的监控数据result
:向client发送任务的执行结果,这个topic只会发布一次。
数据关联和Goal的ID
目标ID是一个字符串字段,用于操作界面中的所有消息。 这为动作服务器和客户端提供了一种强大的方法,可以将通过ROS传输的消息与正在处理的特定目标相关联。 目标ID通常是节点名称,计数器和时间戳的组合。 注意:目标ID的格式仍然不稳定,因此用户永远不应解析目标ID,也不应依赖其格式。
消息介绍
goal
topic:发送目标点
goal
topic使用自动生成的ActionGoal
消息(例如:actionlib/TestActionGoal
),用于将新目标发送到action server。 实质上,ActionGoal
消息包装的是goal
消息,并将其与Goal ID
捆绑在一起。
在发送目标时,action client通常会生成唯一的Goal ID
和时间戳(timestamp
)。 但是,天真(或:愚蠢的)的客户端可能会将其中任何一个留空。 如果发生这种情况,action server会对它们的内容进行填充。
Empty stamp
: 根据action server收到数据报的时间,stamp
被填充为now()
。Empty id
:在action server收到后,自动生成ID
。 注意,此ID
不是非常有用,因为操作客户端无法知道服务器为goal生成的ID
。
cancel
topic: 取消目标点
cancel
topic使用actionlib_msgs/GoalID
消息,并允许action clients将cancel请求发送到action server。 每个cancel
消息都有一个timestamp
(时间戳)和goal ID
。这些消息字段的填充方式将影响被取消的goal(见下图)。
status
topic: server端目标点的状态更新
状态topic使用的topic名字为actionlib_msgs/GoalStatusArray
,它为action clients端提供有关action server当前正在跟踪的每个goal的server端的目标状态信息。 状态由action server以某种固定速率(通常为10 Hz)发送,并在任意服务器进行状态转换后异步发送目标服务器的状态。
action server会一直跟踪goal,直到达到terminal
状态。 但是,为了提高通信稳健性,server在达到terminal
状态后会将此目标的状态持续发布几秒钟。
feedback
topic:异步目标信息
feedback
主题使用自动生成的ActionFeedback
消息,并为server implementers提供在处理goal期间向action clients发送定期更新的方法。 由于ActionFeedback
具有goal的ID
属性,因此action client可以确定它是否应该使用或丢弃它收到的feedback(反馈)消息。 发送feedback
消息完全是一种可选的行为。
result
topic:完成后的目标信息
result
主题使用自动生成的ActionResult
消息(例如:actionlib/TestActionResult
),并为server implementers提供在完成目标后向action clients发送信息的方法。 由于ActionResult
具有目标ID
属性,因此action client可以自行决定是否使用还是丢弃result消息。 虽然result可以是空消息,但它是action接口的必需部分,并且必须始终在完成目标时发送。 因此,必须在转换到任何Terminal
状态时发送结果(Rejected
, Recalled
, Preempted
, Aborted
, Succeeded
)。
Simple Action Client
一般来说,高级应用程序和执行程序只关心目标是否正在处理,或者是否完成。它们很少关心所有的中间状态。Simple Action Client将原始客户端状态机分为三种状态:Pending
,Active
和Done
。
客户状态的歧义
注意,仅客户端状态不足以确定简单的客户端状态。 但是,通过查看客户端状态转换可以轻松解决此问题。 如果客户端状态转换未在任何simple client states之间交叉,则不更新简单客户端状态。
示例:如果client 从RECALLING
转换为WAITING FOR RESULT
,则simple client state仍将保留为PENDING
。
多个目标点的政策
为简单起见,Simple Action Client一次只跟踪一个目标。 当用户使用简单客户端发送目标时,它会禁用与上一个目标关联的所有回调,并停止跟踪其状态。 注意,它不会取消之前的目标!
线程模型(C++)
在构造simple action client时,用户决定是否启动额外的线程。
- 无额外的线程(推荐)
- action client的所有subscribers都使用全局回调队列(global callback queue)进行注册。
- 用户的action callbacks是从
ros::spin()
中调用的。 因此,阻止用户的action callbacks将阻止全局回调队列(global callback queue)服务。
- 启动线程(Spins up a thread)。
- action client中的所有subscribers都注册一个回调队列,与全局回调队列(global callback queue)分开。此队列由启动线程(spun up thread)提供服务。
- 用户的action callbacks(动作回调)从启动线程(spun up thread)调用。尽管在action callbacks的阻塞不会阻止其他ROS服务消息,但仍然是一个不好的做法,因为无法为此操作提供状态(status),反馈(feedback)和结果(result)消息。。
- 启动额外线程的一个(也是唯一的)优势是用户可以避免在他们的应用程序中调用
ros::spin()
。
Simple Action Server
许多action servers遵循类似的模式:其中一次只能有一个目标是活动的,并且每个新目标都优先于前一个目标。simple action server是一个action server的包装器,旨在强制执行这个简单的策略来处理目标。
在从ction client接收到新goal后,simple action server将该goal移动到其待定槽(pending slot)中。 如果待定插槽已有目标占用,则simple action server会将该目标设置为取消,并将其替换为通过网络传入的目标。
一旦simple action server接收到新目标并将其移动到待定槽中,则通知simple action server的用户新目标可用。 此通知以下面两种方式之一,如下图的目标通知部分所述。 在接收到通知时,用户可以选择接受(accept),移动待定槽中的目标使它变成当前目标槽的目标,同时允许用户修改与新接受的目标相关联的状态机。
以下用一个实例详细讨论一下actionlib的Goal执行流程。首先看上图,
在Action接收到goal C时,并不立即抢断正在执行的goal,而是进入到Pending Goal待定槽里,如果待定槽里已经有一个goal,则将其清出并进入Recalled
状态。到此都是actionlib
底层架构自动完成的。
接下来,Simple Action Server的用户就可以收到新goal到来的通知(有两种方式,这里是通过回调函数的方式)。用户可以接收新的goal,将Current Goal槽指向新的goal,并将状态机跟新的goal关联起来,将其改为active状态,其他的就会自动变为待定或者完成状态状态。这个过程可参见下图。
这里需要注意的是,一个goal从客户端发送过来是通过goal话题,cancel是通过cancel
话题发送的,一个Simple Action Client发布的goal有 Pending
、Active
、Down
三种状态,cancel后并不是立马就消失的,还存在与内存中,需要服务器去清除。这个过程的执行可参照move_base
的executeCb()
函数进行分析。
Goals的通知方式
用户可以通过两种方式接收simple action server已收到新目标的通知:
-
回调(Callback)通知:此处用户使用构造时的simple action server注册callback,当新目标移动到simple action server的待定槽(pending slot)时调用该callback。 用户可以接受callback中的新goal,或者在准备就绪时发信号通知另一个线程接受目标。
-
轮询(Polling)通知:此处用户明确询问simple action server是否有新目标。 simple action server根据上次对新目标查询的情况确定新目标是否已移入待定槽来回答此查询。
线程模型(C++)
在构造simple action server时,用户可以自行决定是否启动额外线程以允许在目标回调中长时间运行actions(动作)。
- 没有额外线程(推荐)
- 在新目标收到的回调中,任何actions都不应该长时间运行。 当然,用户可以发信号通知另一个线程来工作,但不应进行阻止。
- 或者,用户可以使用轮询(polling)实现检查新目标的可用性以完全避免callbacks方式。
- 启动线程(Spins up a thread)
- 生成单独的线程,以允许用户发现新goal可用时在收到的回调中执行long running或blocking actions。 在此callback中,用户还可以轮询simple action server以检查新目标是否可用。
- simple action server为用户启动线程的优势在于用户不必对管理另一个线程的开销进行处理。 但是,用户必须意识到该线程的存在性,以保证它们遵循标准的线程安全约定,例如锁定。
参考来源:
[1] actionlib
Wiki: http://wiki.ros.org/actionlib
[1] actionlib
Detailed Description: http://wiki.ros.org/actionlib/DetailedDescription