ros2 / ros2/rclcpp

RCL_ROS_TIME: how to get sim-time-aware monotonic timing?

Open
#3,122 24 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
C++
Stars
805
Forks
564
Avg merge
1d 17h
Merged PRs (30d)
27

Description

Description

When use_sim_time=true, an RCL_ROS_TIME clock sources time from the /clock topic. When use_sim_time=false, it falls back to RCL_SYSTEM_TIME unconditionally. This fallback is hardcoded in rcl.

There is currently no native way to create a clock that uses /clock in simulation and RCL_STEADY_TIME (monotonic wall time) on real hardware. The only two options available are:

  • RCL_STEADY_TIME — monotonic but ignores sim time entirely
  • RCL_ROS_TIME — sim-time-aware, but falls back to RCL_SYSTEM_TIME (not RCL_STEADY_TIME) on real hardware

This is a problem for any node that needs different clock behaviors for different purposes within the same node. For example, a controller node typically needs:

Use case Real hardware Simulation
Control loop timer RCL_STEADY_TIME (monotonic, NTP-immune) /clock
Message timestamps RCL_SYSTEM_TIME (wall clock) /clock

Today, both of these are impossible to satisfy with a single RCL_ROS_TIME clock because the fallback is always RCL_SYSTEM_TIME. Using the node's clock for a control loop timer silently introduces NTP sensitivity on real hardware.

Motivation

This came up in ros-navigation/navigation2#6063, which makes various Nav2 components sim-time-aware by switching from rclcpp::WallRate to rclcpp::Rate(freq, node->get_clock()). This is the right thing to do for simulation, but it silently changes the real-hardware behavior from RCL_STEADY_TIME (monotonic) to RCL_SYSTEM_TIME (NTP-adjustable), because that's the only fallback RCL_ROS_TIME supports.

Current workaround

The only workaround is to check use_sim_time at construction and branch between create_timer and create_wall_timer:

class Controller : public rclcpp::Node
{
public:
  Controller() : Node("controller")
  {
    if (get_parameter("use_sim_time").as_bool()) {
      timer_ = create_timer(100ms, std::bind(&Controller::control_loop, this));
    } else {
      timer_ = create_wall_timer(100ms, std::bind(&Controller::control_loop, this));
    }
  }

  void control_loop()
  {
    auto cmd = compute_velocity();
    cmd.header.stamp = now();
    pub_->publish(cmd);
  }
};

This is fragile — use_sim_time is checked once at construction, so runtime parameter changes are ignored. Every node or library that needs monotonic + sim-aware timing must duplicate this pattern. The same issue applies to rclcpp::Rate and rclcpp::WallRate, where there is no equivalent workaround at all.

Question

What is the intended way to get sim-time-aware monotonic timing in rclcpp? Is there an existing mechanism we are missing, or does rclcpp need a way to configure the fallback clock type for RCL_ROS_TIME?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Review the RCL_ROS_TIME fallback behavior and the rclcpp::Rate, rclcpp::WallRate, create_timer, and create_wall_timer entry points. Determine whether an existing mechanism supports a steady-time fallback, or define the API behavior needed to use /clock in simulation and monotonic time on hardware, including runtime use_sim_time changes.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
api
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.