18. 时钟与时间

本文描述了用于支持编程的ROS时间原语(primitives),这些原语既能以实际时间运行,也能以更快或更慢的仿真时间运行。

作者:塔利·富特(Tully Foote)

撰写日期:2018-07

最后修改时间:2018-12

18.1 背景

许多机器人算法在本质上依赖于时间和同步。为此,我们要求在ROS网络中运行的节点具有同步的系统时钟,以便这些节点能够准确地报告各种事件的时间戳。

然而,对一些用例而言,重要的是能够控制系统的时间进展。

18.1.1 实际时间与实时计算(Real Time vs real-time computing)

在本文中,术语“实际时间(real time)”用于表示时间进展的真实速率。该术语与具有确定性截止时间(deterministic deadlines)的“实时(real-time)”计算不相关。

18.2 需要时间抽象的用例

在回放记录的数据时,支持对时间进展进行加速、减慢或步进控制往往非常有价值。这种控件可以让用户到达某个特定时间并暂停系统,以便用户可以进行深入调试。可以通过传感器数据的日志来做到这一点,但如果传感器数据与系统其余部分不同步,则会无法利用许多算法。

使用抽象时间源的另一个重要用例就是当针对仿真机器人而不是真实机器人运行记录数据时。取决于仿真特性,仿真程序可能会以比实际时间快得多的速度运行,或者可能需要以比实际时间慢得多的速度运行。运行速度比实际时间快对于高级测试以及允许重复的系统测试很有价值。对于精度比速度更重要的复杂系统,仿真速度比实际时间更慢很有必要。通常仿真是系统的限制因素,因此仿真程序可以是比实际时间更快或更慢回放的一个时间源。此外,如果仿真暂停,则系统也可以使用相同机制暂停。

18.3 方法

为了提供简化的时间接口,我们将会提供ROS time和duration数据类型。会提供一个ROS Clock接口用于查询最新时间。一个TimeSource可以管理一个或多个Clock实例。

18.4 时钟

18.4.1 使用抽象时间的挑战

同步算法有很多,且这些同步算法通常可以实现比ROS网络上各设备之间网络通信延迟更好的精度。但是,这些算法利用了关于时间恒定和连续性质的假设。

使用抽象时间的一个重要方面就是能够操纵时间。在某些情况下,加快时间进展、减慢时间进展或者完全暂停时间进展对调试很重要。

支持暂停时间进展的能力要求我们不要假设时间值总是在增长的。

在对时间传播中的变化进行通信时,通信网络中的延迟会成为一个挑战。时间抽象中的任何变化都必须传达给ROS计算图中的其他节点,但这会受到正常的网络通信延迟的影响。这种不准确性既与通信延迟成正比,也与仿真时间与实际时间相比的前进速率(即“实时因子”)成正比。如果在使用时间抽象时需要非常精确的时间戳,则可以通过降低实时因子来实现,这样会使得通信延迟相对较小。

最后一个挑战就是时间抽象必须能够在时间上向后跳转,这一特性对于日志文件回放很有用。这种行为类似于负日期更改后的系统时钟,并且要求开发人员使用时间抽象来确保他们的算法可以处理不连续性。必须为开发人员提供适当的API以启用时间向前和向后跳转的通知功能。

18.4.2 时间抽象

至少将会有使用下述类型的三个时间抽象版本:SystemTime、SteadyTime和ROSTime。选择这些时间抽象的设计初衷在于与std::chrono system_clock和stable_clock保持平行。期望的默认时间选择会使用ROSTime时间源,但是会为需要替代时间源的用例保留支持stable_clock和system_clock的并行实现。

(1)系统时间

为了在这些用例中使用方便起见,我们还会提供与上述相同的API,但使用名称SystemTime。SystemTime会直接绑定到系统时钟。

(2)ROS时间

当ROS时间源未激活时,ROSTime对事件时间戳的报告会与SystemTime的报告相同。当ROS时间源处于活动状态时,ROSTime会返回该时间源报告的最新值。当在节点上设置参数use_sim_time时,ROSTime会被认为是活动的。

(3)稳定时间

稳定时间(Steady Time)的示例用例包括与具有硬件超时的外围设备交互的硬件驱动程序。

在需要使用SteadyTime或SystemTime与硬件或其他外围设备交互的节点中,期望这些节点尽最大努力隔离其实现中的任何SystemTime或SteadyTime信息,并在ROS网络上进行通信时转换外部接口以使用ROS时间抽象。

18.4.3 ROS时间源

(1)默认时间源

为了实现时间抽象,将会使用以下方法。

时间抽象可以由某个时间源在/clock话题上进行发布。该话题会包含ROS系统的最新时间。如果该话题有一个发布者,则在使用ROS时间抽象时,该发布者会覆盖系统时间。如果/clock话题正在发布中,则对ROS时间抽象的调用会返回接收自/clock话题的最新时间。如果尚未设置时间,则如果未接收到任何内容,则会返回零。一个值为零的时间应视作错误,意味着时间尚未进行初始化。

如果时钟上的时间向后跳转,则会调用一个回调处理程序,并要求在任何对ROS时间抽象的调用报告新时间之前完成该回调处理程序的调用。在此之前传入的调用必须阻塞。开发人员有机会用该处理程序注册回调函数,以便必要时在时间来到过去之前清除其系统中的任何状态。

并不会具体指定发布/clock话题的频率和粒度,因为这些参数是特定于应用程序的。

1)默认情况下没有先进的估计时钟

可以包含更先进的技术来尝试估计时间传播特性并在不同的时间时标(ticks)之间进行外插(extrapolate)。然而,所有这些技术都需要对时间抽象的未来行为做出假设。而在回放或仿真瞬间暂停的用例中,都会打破所有这些假设。有一些技术允许潜在的插值,但要使其成为可能,就需要提供关于未来时间连续性的保证。为了更加精确,可以放慢时间的进展,或者可以加大发布的频率。调整/clock话题的这些参数可以让您以时间换取计算工作量和/或带宽。

(2)自定义时间源

用户可能可以访问某个带外时间源,该时间源可以提供比/clock话题默认时间源更好的性能。对于他们的用例,可能需要更先进的算法以足够的精度或延迟并以限制的带宽或连接性来传播仿真时间。用户将能够为其Time对象实例切换时间源,并具有覆盖进程默认时间源的能力。

可以使用诸如GPS等外部时间源作为ROSTime的来源,但建议使用标准的NTP集成将类似这样的时间源与系统时钟进行集成,因为这已经是一种建立好的机制,且不需要处理诸如时间跳转等更复杂的变化。

对于当前实现而言,将会定义一个TimeSource API,以便可以在代码中覆盖它。如果将来能找到一个通常会很有用的通用实现,就可以将其扩展为能通过类似于启用仿真时间的一个参数来可选地动态选择替代的时间源(TimeSource)。

18.5 实现

每个客户端库会以惯用的方式提供SystemTime、SteadyTime和ROSTime等API,但这些客户端库可能会共享一个例如由rcl提供的通用实现。但是,如果某个客户端库选择不使用该共享实现,那么它必须自己实现相关功能。

SteadyTime的类型不同于可互换的SystemTime和ROSTime。这是因为SystemTime和ROSTime有一个具有运行时检测功能的公共基类,可以检测它们是否可以有效地进行相互比较。而SteadyTime永远无法与SystemTime或ROSTime相比较,因此它实际上会具有相同API的一个单独实现。这样,可以防止在编译时(在编译语言中)误用这些API,而不是仅仅在运行时捕获该API。

18.5.1 公共API

客户端库的实现将会为所有三个时间源抽象提供Time、Duration和Rate数据类型。

Clock会支持分别使用Duration或Time参数的sleep_for函数和sleep_until方法。该实现还将会提供一个Timer对象,该对象会为所有时间抽象提供定期回调函数。

Clock还会支持在时间跳转之前和之后注册回调函数。第一个回调函数(即时间跳转之前注册的回调函数)会允许为时间跳转做适当的准备。后者(即时间跳转之后注册的回调函数)会允许代码对时间变化做出响应,并包括具体的新时间以及跳转的时间量。

任何阻塞的API都会允许使用一组标志来指示时间跳转情况下的适当行为。这允许用户在时间跳转时选择立即出错或选择忽略。在为时间跳转注册回调函数时,可以使用向后或向前最小距离过滤器以及用于是否包括时钟更改的过滤器。

18.5.2 RCL实现

在rcl中,将会有数据类型和方法来实现每种核心数据类型的所有三种时间抽象。但是在rcl层次,其实现会是不完整的,因为它没有线程模型,且会依赖更高层次的实现来提供sleep方法要求的任何线程功能。RCL还需要适当的线程来支持TimeSource数据的接收。

RCL会提供与客户端库可以依赖和可以委托的公共数据结构和存储并行的实现。底层数据类型还会提供注册通知的方法,而收集和分派用户回调则要由客户端库实现来负责。

*待办事项(TODO):在此处枚举rcl的各种数据结构和方法。

18.6 参考文献

默认时间源以ROS 1.0中使用的ROS时钟和ROS时间系统为模型。有关ROS 1.0中实现的更多信息,请参见下列文档:

 ROS时钟文档

 rospy时间文档

 roscpp时间文档

*英语原文网址:design.ros2.org/article