- name
- ros2-tf-clock
- description
- ROS 2 time, use_sim_time, clocks, and TF2 lookup/extrapolation failures. Use when TF lookup fails, transforms are old, bags replay with wrong time, timers ignore sim time, or the user mentions use_sim_time, tf2, Time, extrapolation, or /clock.
# ROS 2 Time and TF2
## Clocks
| Source | When |
|--------|------|
| `RCL_SYSTEM_TIME` | Default wall clock |
| `RCL_ROS_TIME` | `/clock` when `use_sim_time:=true` |
| `RCL_STEADY_TIME` | Monotonic; **not** for TF / message stamps |
Set **every** node (and launch) `use_sim_time: true` for sim **and** bag replay with `--clock`. One node on wall time + one on ROS time → TF "extrapolation into the future/past".
`rclcpp::Node::now()` follows the node clock. `std::chrono::system_clock::now()` does **not**.
Timers: `create_timer` (ROS time) vs `create_wall_timer` (wall). Rate loops in sim must use ROS timers or they race the bag.
## TF2 lookups
```cpp
tf_buffer.lookupTransform(target, source, tf2::TimePointZero); // latest
tf_buffer.lookupTransform(target, source, stamp, timeout);
```
| Error | Cause |
|-------|-------|
| `Lookup would require extrapolation` | Stamp newer/older than buffer; clock mismatch; publisher died |
| `Frame not in tree` | Wrong frame_id / namespace; static TF not latched (QoS) |
| `Connectivity` | Unconnected trees (`base_link` vs `odom` missing) |
Rules:
- Stamp outgoing msgs with **the same clock** as TF (`node->now()` or the sensor stamp you are expressing).
- `tf2_ros::TransformBroadcaster` / static broadcaster: static TF needs **TRANSIENT_LOCAL** (see `ros2-qos-dds`).
- `TimePointZero` / `rclcpp::Time(0)` = latest — OK for teleop, **wrong** for aligning a sensor frame to a specific scan stamp.
- Buffer cache time must exceed your worst sensor delay (default ~10 s).
## Bag replay
`ros2 bag play --clock` **and** `use_sim_time:=true` on consumers. Without `--clock`, `/clock` is absent and ROS time stays 0 or wall.
See `ros2-bag-replay`.
View on GitHub