| 时间模型 | 数据来源 | 适用场景 | 跳跃风险 |
|---|---|---|---|
| 系统时间 | 操作系统时钟 | 实车运行、与硬件同步 | 低(NTP调整时可能跳变) |
| 模拟时间 | 仿真器/回放器发布/clock话题 | Gazebo仿真、rosbag回放 | 可控(暂停、加速、跳跃由发布者决定) |
| ROS时间 | 自动选择:有/clock话题则用模拟时间,否则回退系统时间 | 统一代码无需手动切换 | 取决于实际使用的来源 |
我在项目中踩过这个坑:一个激光SLAM节点在实车测试时正常,换到仿真环境就频繁报"时间倒退"错误。原因就是节点硬编码了系统时间,而仿真器发送的/clock话题包含时间跳跃——两种模式不匹配导致的崩溃。ROS2通过抽象出Clock对象,让开发者只需关注时间从哪里来,而不必自己处理时间源的切换逻辑。
时间戳不是单纯的整数。ROS2使用builtin_interfaces/Time消息表示时间点,包含两个字段:sec(秒,int32)和nanosec(纳秒,uint32)。这种拆分设计的目的是兼容32位和64位平台,避免在嵌入式MCU上出现时间溢出问题。
// 正确的时间戳创建方式
auto now = clock.now();
rclcpp::Time later = now + rclcpp::Duration(5, 0); // 5秒后
rclcpp::Time earlier = now - rclcpp::Duration(1, 500000000); // 1.5秒前
// 错误示范:手动修改nanosec可能导致字段溢出
// now.nanosec += 800000000; // 如果now.nanosec=500000000,会变成1.3e9→溢出
rclcpp::Clock::set_ros_time_is_steady(true)让时钟忽略倒退事件(但代价是失去时间同步精度);二是在节点构造函数中注册on_ros_time_jump回调,在跳跃发生时自定义处理——比如清空历史缓冲区、重置里程计积分等。我在实际产品中选后者,因为机器人在时间跳跃后最危险的行为是用旧位置数据推算新位置。
| 接口 | 用途 | 注意事项 |
|---|---|---|
| clock.now() | 获取当前时间 | 返回类型为rclcpp::Time,依赖Clock类型 |
| rclcpp::sleep_for() | 挂起当前线程 | 模拟时间下可能被加速/暂停,不适合硬实时 |
| msg.header.stamp | 给消息打时间戳 | 必须使用clock.now()而非system_clock::now() |
| timer = create_wall_timer() | 创建周期性定时器 | wall_timer使用系统时钟,不受模拟时间影响 |
| timer = create_timer() | 创建ROS定时器 | 跟随模拟时间,仿真中自动加速/减速 |
system_clock::now()给create_wall_timer和create_timer。前者总是按真实时间触发,后者跟随模拟时间。在bag回放时,如果使用wall_timer控制数据采集,采集频率不会随回放速度缩放,导致数据密度异常。定时器是时间管理中最常用的功能。ROS2提供两类定时器:WallTimer和ROSTimer。它们的差异直接影响仿真与实车的行为一致性。
我维护过一个无人配送车的导航节点,开发者统一用了WallTimer控制路径规划周期。实车测试没问题,但用rosbag回放做回归测试时,规划频率从10Hz升到了100Hz——因为回放速度是10倍,而WallTimer不感知模拟时间加速。换成ROSTimer后,回放时规划频率稳定在10倍速下的100Hz,数据特征与实车一致,回归测试才真正有效。
策略B在实际产品中最常用。注册JumpHandler时可以选择关注RCL_ROS_TIME_JUMP_BACKWARD、RCL_ROS_TIME_JUMP_FORWARD或两者。回调函数接收一个TimeJumpInfo结构,包含跳变前后的时间值,方便你计算偏移量并补偿。
练习:时间感知数据采集器
create_wall_timer和create_timer各发布一个Int32消息,计数器每次加1。ros2 bag record录制数据,然后用ros2 bag play --rate 0.5回放。ros2 bag play --clock 100 --start-paused并向前跳10秒,观察回调触发。如果你看到"时间倒退"异常导致节点退出,检查是否忘记注册JumpHandler。
1. (判断)在仿真中使用WallTimer控制激光雷达数据发布频率,回放bag时数据频率会随回放速度变化。
2. (判断)Time消息的nanosec字段可以取到1.5e9(即1.5秒转换的纳秒数)。
3. (选择)当/clock话题发布的时间比上一次记录的时间早时,默认情况下Clock对象会:
A. 自动修正为最新时间 B. 抛出异常 C. 跳过该时间点 D. 回调JumpHandler
4. (判断)create_wall_timer和create_timer在实车运行时的行为完全一致。