||[[actionlib/DetailedDescription|English]]||||[[cn/actionlib/DetailedDescription|简体中文]]|| <> == 简介 == 此页面不适合 ''''刚入门'''' actionlib的人。相反,它描述了动作客户端和动作服务器(action clients & servers)之间相互作用的底层机制。 如果您仅想使用[[actionlib#Using_the_ActionClient|SimpleActionClient]]或[[actionlib#Implementing_an_ActionServer|SimpleActionServer]],则无需了解此页面上的概念。 但是,如果简单的客户端和服务器还不具备足够的描述性,则理解这些概念可深刻理解调试客户端/服务器之间的交互或者实现客户端/服务器的策略。 == 高级客户端/服务器交互技术 == === 服务器描述 === ==== 服务器的状态机 ==== 目标点(Goals) 由 !ActionClient 启动。每当 !ActionServer 接收到目标点后,!ActionServer 会创建一个状态机来跟踪目标点的状态:<
> <
> {{attachment:server_states_detailed.png}} 注意,该状态机仅跟踪单个目标点状态,而不是 !ActionServer 本身。因此,对于系统中的每个目标点系统都存在一个状态机与之对应。 ==== 服务器状态转换 ==== 多数状态转换由服务器实现的一小组命令进行触发: * '''setAccepted''' - 检查目标后,决定开始处理它。 * '''setRejected''' - 检查目标后,决定不处理它,因为它是一个无效的请求(超出范围,资源不可用,无效等)。 * '''setSucceeded''' - 通知目标已成功处理。 * '''setAborted''' - 通知目标在处理过程中遇到错误,必须中止。 * '''setCanceled''' - 由于取消请求,通知该目标不再进行处理。 动作客户端(the action client)同样以异步方式触发状态转换: * '''!CancelRequest''': 客户端通知动作服务器(the action server)它希望服务器停止处理的目标。 ===== 服务器状态 ===== 中间状态: * '''Pending'''(待处理) - 目标尚未由动作服务器处理。 * '''Active'''(活动) - 目标正由动作服务器处理。 * '''Recalling'''(重新调用中) - 目标尚未处理,并且已收到来自动作客户端的取消请求,但动作服务器尚未确认目标已被取消。 * '''Preempting'''(抢占) - 目标正在处理,并且已从动作客户端接收到取消请求,但动作服务器尚未确认目标已被取消。 终端状态: * '''Rejected'''(已拒绝 ) - 目标被动作服务器拒绝,未经处理,没有来自动作客户端的取消请求。 * '''Succeeded'''(成功) - 动作服务器成功完成了目标。 * '''Aborted'''(中止) - 目标被动作服务器终止,而不是被动作客户端的外部请求取消。 * '''Recalled'''(已调用) - 在动作服务器开始处理目标之前,目标已被新目标进行了替换、或被外部取消请求。取消 * '''Preempted'''(抢占) - 目标的处理进程被另一个目标(替换)取消,或被发送到动作服务器的取消请求所取消。 ===== 并发问题 ===== '''setAccepted-!CancelRequest''' ''vs'' '''!CancelRequest-setAccepted:''' <
> 即使在收到''!CancelRequest''(取消请求目标)之后,action server也可以通过''!setAccepted''(设置接受)新目标,这有点不直观。 因为这是一个异步处理''!CancelRequest''的竞争条件。 也就是说,由于server implementer代码之外的其他内容正在触发状态转换,因此server implementer无法确定它们是处于''[PENDING]''状态还是''[RECALLING]''状态。 === 客户端描述 === ==== 客户端的状态机 ==== 在`actionlib`中,我们将服务器状态机视为主状态机,同时将客户端的状态机视为辅助/耦合(secondary/coupled)状态机,以尝试跟踪服务器的状态: <
> <
> {{attachment:client_state_transitions.png}} ==== Client Transitions客户端状态的转换 ==== 服务器触发的转换: * '''Reported [State]'''(报告[状态]): 由于客户端正在尝试跟踪服务器的状态,大多数转换状态由服务器向 !ActionClient 进行报告。 * '''Receive Result Message'''(接收结果消息): 在这种情况下,服务器给客户端发送一个''result''(结果)消息。接收到''result''将始终会触发终止跟踪目标的信号。 客户端触发转换: * '''Cancel Goal'''(取消目标): 请求服务器停止处理此目标。 "跳过(Skipping)"的状态: * 指定基于ROS的传输层,客户端可能没有从服务器接收所有的状态更新。因此,必须允许客户端状态机“跳过”服务器的触发状态(server triggered states)。 Example: 如果客户端在''[`WAITING FOR GOAL ACK`]'',从服务器状态接收''[`PREEMPTED`]''(抢先)的更新通知,客户端的状态可以跳过过去的''[`ACTIVE`]'',并直接过渡到''[`WAITING FOR RESULT`]'' 。 * 由于多个动作客户端可以连接到单个动作服务器,所以第二个客户端可以取消由第一个客户端发送的目标。因此,如果''[`RECALLING`]''是从server接收到的状态,则client从''[`PENDING`]''过渡至''[`RECALLING`]''应该是有效的。 == 动作(Action)的接口和传输层 == action的client和server之间通过预定义的''action protocol''进行通讯。为传输消息(messages),这种通讯协议在特定的命名空间中依赖于ROS的话题。 {{attachment:action_interface.png}} <
> ROS 消息 * '''goal''' - 用于向服务器发送目标。 * '''cancel''' - 用于向服务器发送取消请求。 * '''status''' - 用于通知客户端系统中每个目标的当前状态。 * '''feedback''' - 用于周期反馈目标的辅助信息。 * '''result''' - 用于向client发送任务的执行结果,这个topic只会发布一次。 === 数据关联和目标的ID === 目标的ID属性是一个字符串字段,用于动作接口中的所有消息通讯。 为动作服务器和动作客户端提供了一种强大的方法,可以通过ROS传输的消息与正在处理的特定目标进行关联。 目标ID通常为节点名称、计数器和时间戳的组合。 注意:目标ID的格式不稳定,因此用户永远不要解析目标ID,也不应依赖其格式。 === 消息介绍 === ==== `goal` 话题:发送目标点 ==== `goal` 话题使用自动生成的''!ActionGoal''消息(例如:<>),用于将新目标发送到动作服务器。 实质上,''!ActionGoal'' 消息包装的是一个`goal`消息,并将其与目标`ID`捆绑在一起。 <
><
> 发送目标时,动作客户端通常会生成唯一的目标`ID`和时间戳(`timestamp`)。 但是,天真(或:愚蠢的)的客户端可能会将其中任何一个留空。 如果发生这种情况,action server会对它们的内容进行填充。 * '''Empty ''stamp''''': 根据action server收到数据报的时间,`stamp`被填充为`now()`。 * '''Empty ''id''''': action server收到消息后,自动生成`ID`。 注意,此`ID`不是非常有用,因为动作客户端无法知道服务器为goal生成的`ID`。 ==== cancel 话题: 取消目标点 ==== `cancel` topic使用<>消息,并允许动作客户端将cancel请求发送到action server。 每个`cancel`消息都有一个`timestamp`(时间戳)和goal `ID`。这些消息字段的填充方式将影响被取消的目标 <
> {{attachment:cancel_policy.png}} ==== status 话题: 服务器目标点的状态更新 ==== 状态话题使用的话题名为<>,它为动作客户端提供有关动作服务器当前正在跟踪的每个''目标服务器状态''信息。 状态由动作服务器以某种固定速率(通常为10 Hz)发送,并在任意服务器进行状态转换后异步发送目标服务器的状态。<
> <
> 动作服务器会一直跟踪目标,直到达到`terminal`状态。 但是,为了提高通信稳健性,服务器在达到`terminal`状态后会将此目标的状态持续发布几秒钟。 ==== `feedback` 话题:异步目标信息 ==== `feedback`主题使用自动生成的''!ActionFeedback''消息,并为服务实现(server implementers)提供在处理目标期间向动作客户端发送定期更新的方法。 由于''!ActionFeedback''包含有目标的`ID`属性,因此动作客户端可以确定它是否应该使用或丢弃它收到的feedback(反馈)消息。 发送`feedback`消息完全是一种可选的行为。 ==== `result` 话题:完成目标后的信息 ==== `result`话题使用自动生成的''!ActionResult''消息(例如:<>),并为服务实现(server implementers)提供完成目标后向客户端发送信息的方法。 由于''!ActionResult'' 包含有目标`ID`属性,因此动作客户端可以自行决定是否使用还是丢弃result消息。 虽然result可以是空消息,但它是action接口的必需部分,并且必须始终在完成目标时发送。 因此,必须在转换到任何`terminal`状态时发送结果(`Rejected`, `Recalled`, `Preempted`, `Aborted`, `Succeeded`)。 == 策略 == === 简单动作客户端(Simple Action Client) === 一般来说,高级应用程序和执行程序只关心目标是否正在处理,或者是否完成。它们很少关心所有的中间状态。Simple Action Client将原始客户端状态机分为三种状态:`Pending`,`Active` & `Done`。 <
><
> {{attachment:simple_client_state_transitions.png}} ==== 客户端状态的分歧 ==== 注意,仅根据客户端的状态不足以确定简单的客户端状态。 但是,通过查看客户端状态转换可以轻松解决此问题。 如果客户端状态转换未在任何简单客户端状态(simple client states)之间交叉,则不用更新简单客户端状态。 '''Example:''' 如果客户端从''RECALLING''转换为''WAITING FOR RESULT'',则简单客户端状态仍将保留为`PENDING`。 ==== 多目标点的策略 ==== 为简单起见,简单动作客户端(Simple Action Client)一次只跟踪一个目标。 当用户使用简单客户端发送目标时,它会禁用与上一个目标关联的所有回调,并停止跟踪其状态。 注意,'''它不会取消之前的目标!''' ==== 线程模型(C++) ==== 在[[http://www.ros.org/doc/api/actionlib/html/classactionlib_1_1SimpleActionClient.html#eab5909c913c52486b569806f117f54e | 构造]]简单动作客户端时,用户可以决定是否启动额外的线程。 * '''无额外线程'''(推荐) * 动作客户端的所有订阅者(subscribers)皆使用全局回调队列(global callback queue)进行注册。 * 用户的action callbacks是从`ros::spin()`中调用的。 因此,阻止用户的action callbacks将阻止全局回调队列(global callback queue)服务。 * '''启动线程'''(Spins up a thread) * 动作客户端中的所有订阅者都注册一个回调队列,与全局回调队列(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的包装器,旨在强制执行这个简单的策略来处理目标。 {{attachment:simple_goal_reception.png}} 在从动作客户端接收到新goal后,simple action server将该goal移动到其待定槽(pending slot)中。 如果待定插槽已被其他目标占用,则simple action server会将该占用的目标设置为取消,并将其替换为通过网络传入的新目标。 {{attachment:simple_goal_accept.png}} 一旦simple action server接收到新目标并将其移动到待定槽中,则通知simple action server的用户新目标可用。 此通知采用[[#目标的通知方式 | 目标的通知方式]]描述的方式。 在接收到通知时,用户可以选择接受(accept),移动待定槽中的目标使它变成当前目标槽的目标,同时允许用户修改与新接受的目标相关联的状态机。 ==== 目标的通知方式 ==== 用户可以通过两种方式通知simple action server接收到新目标的通知: * '''回调(Callback)通知''':此处用户使用构造时的simple action server注册callback,当新目标移动到simple action server的待定槽(pending slot)时调用该callback。 用户可以接受callback中的新goal,或者在准备就绪时发信号通知另一个线程接受目标。 * '''轮询(Polling)通知''':此处用户明确询问simple action server是否有新目标。 simple action server根据上次对新目标查询的情况确定新目标是否已移入待定槽来回答此查询。 ==== 线程模型(C++) ==== 在[[http://www.ros.org/doc/api/actionlib/html/classactionlib_1_1SimpleActionServer.html#e3c4bcedcfa871e3c670ee535297b36c | 构造]]simple action server时,用户可以自行决定是否启动额外线程以允许在目标回调中长时间运行actions(动作)。 * '''没有额外线程'''(推荐) * 在新目标收到的回调中,任何actions都不应该长时间运行。 当然,用户可以发信号通知另一个线程来工作,但不应进行阻止。 * 或者,用户可以使用轮询(polling)实现检查新目标的可用性以完全避免callbacks方式。 * '''启动线程'''(Spins up a thread) * 生成单独的线程,以允许用户发现新goal可用时在收到的回调中执行long running或blocking actions。 在此callback中,用户还可以轮询simple action server以检查新目标是否可用。 * simple action server为用户启动线程的优势在于用户不必对管理另一个线程的开销进行处理。 但是,用户必须意识到该线程的存在性,以保证它们遵循标准的线程安全约定,例如锁定。 ## AUTOGENERATED DON'T DELETE ## M3Package