Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8c51b3a
Refactor to use ROS clock for rate control in various components
tonynajjar Apr 4, 2026
f923090
lint
tonynajjar Apr 4, 2026
a710d62
fix test
tonynajjar Apr 4, 2026
131f7ea
fix other test
tonynajjar Apr 4, 2026
e47e80e
Refactor LoopRate to accept a shared clock for sim-time awareness and…
tonynajjar Apr 5, 2026
009d0c0
update underlay
tonynajjar Apr 6, 2026
c5e3dad
Refactor timer creation to use member function for consistency
tonynajjar Apr 7, 2026
eb23fb4
Merge remote-tracking branch 'upstream/main' into use-ros-time
tonynajjar Apr 8, 2026
4c4e524
Refactor timer and rate handling across navigation2 components to use…
tonynajjar Apr 8, 2026
e163c86
Refactor timer usage across navigation2 components to utilize nav2::c…
tonynajjar Apr 8, 2026
a1ac3e6
fix tests as well
tonynajjar Apr 8, 2026
fba5bd6
Revert "fix tests as well"
tonynajjar Apr 8, 2026
7f65c8c
Revert "Refactor timer usage across navigation2 components to utilize…
tonynajjar Apr 8, 2026
597e6cb
Revert "Refactor timer and rate handling across navigation2 component…
tonynajjar Apr 8, 2026
17c38d8
revert everything
tonynajjar Apr 8, 2026
dfadb81
Add nav2::WallRate for improved time handling across components
tonynajjar Apr 8, 2026
935edb8
remove unsude includes
tonynajjar Apr 8, 2026
8a7385c
fix build
tonynajjar Apr 8, 2026
398de8b
fix date
tonynajjar Apr 8, 2026
c109d83
pr review
tonynajjar Apr 10, 2026
28ce958
more wallrate conversions
tonynajjar Apr 10, 2026
16b7272
convert create_timer to create_wall_timer
tonynajjar Apr 10, 2026
cab2842
refactor: replace create_wall_timer and WallRate with create_timer an…
tonynajjar Apr 10, 2026
95090a0
store node
tonynajjar Apr 10, 2026
10706e9
rename to rate
tonynajjar Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions nav2_amcl/src/amcl_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
#include "nav2_amcl/amcl_node.hpp"

#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <ctime>
#include <cstdint>
#include <iomanip>
#include <memory>
#include <string>
Expand Down Expand Up @@ -148,7 +148,7 @@ AmclNode::on_activate(const rclcpp_lifecycle::State & /*state*/)

// Create pose save timer if save_pose_rate > 0
if (save_pose_rate_ > 0.0) {
save_pose_timer_ = create_wall_timer(
save_pose_timer_ = this->create_timer(
std::chrono::duration<double>(1.0 / save_pose_rate_),
std::bind(&AmclNode::savePoseTimerCallback, this));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ class BehaviorTreeEngine
// The factory that will be used to dynamically construct the behavior tree
BT::BehaviorTreeFactory factory_;

// Clock
rclcpp::Clock::SharedPtr clock_;
// Node handle used to obtain clocks at run time
nav2::LifecycleNode::WeakPtr node_;

// Groot2 monitor
std::unique_ptr<BT::Groot2Publisher> groot_monitor_;
Expand Down
22 changes: 15 additions & 7 deletions nav2_behavior_tree/include/nav2_behavior_tree/utils/loop_rate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#define NAV2_BEHAVIOR_TREE__UTILS__LOOP_RATE_HPP_

#include <memory>
#include <utility>

#include "rclcpp/rclcpp.hpp"
#include "behaviortree_cpp/bt_factory.h"
Expand All @@ -27,8 +28,10 @@ namespace nav2_behavior_tree
class LoopRate
{
public:
LoopRate(const rclcpp::Duration & period, BT::Tree * tree)
: clock_(std::make_shared<rclcpp::Clock>(RCL_STEADY_TIME)), period_(period),
LoopRate(
const rclcpp::Duration & period, BT::Tree * tree,
rclcpp::Clock::SharedPtr clock)
: clock_(std::move(clock)), period_(period),
last_interval_(clock_->now()), tree_(tree)
{}

Expand Down Expand Up @@ -57,11 +60,16 @@ class LoopRate
// Either way do not sleep and return false
return false;
}
// Calculate the time to sleep
auto time_to_sleep = next_interval - now;
std::chrono::nanoseconds time_to_sleep_ns(time_to_sleep.nanoseconds());
// Sleep (can get interrupted by emitWakeUpSignal())
tree_->sleep(std::chrono::duration_cast<std::chrono::microseconds>(time_to_sleep_ns));
auto wake_up = tree_->wakeUpSignal();
static constexpr auto poll_interval = std::chrono::milliseconds(10);
while (clock_->now() < next_interval) {
// Sleep using the wake-up signal directly so we can poll the target clock.
// tree_->sleep() always waits in wall-clock time, which diverges from sim
// time. Instead we do short wall-clock waits and re-check our clock.
if (wake_up->waitFor(poll_interval)) { // Preempted by emitWakeUpSignal()
Comment on lines +65 to +69
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopRate::sleep() uses a fixed 10ms wall-clock polling interval (poll_interval) when waiting for next_interval. This can oversleep when the requested period is <10ms (and adds up to 10ms of jitter even for slightly larger periods), which breaks the contract of sleeping until next_interval for steady-clock operation. Consider computing the wait timeout from the remaining time until next_interval (e.g., wait for min(poll_interval, remaining)), and only fall back to a coarse poll interval when the underlying clock is ROS/sim time.

Suggested change
while (clock_->now() < next_interval) {
// Sleep using the wake-up signal directly so we can poll the target clock.
// tree_->sleep() always waits in wall-clock time, which diverges from sim
// time. Instead we do short wall-clock waits and re-check our clock.
if (wake_up->waitFor(poll_interval)) { // Preempted by emitWakeUpSignal()
const bool is_ros_time = clock_->get_clock_type() == RCL_ROS_TIME;
while ((now = clock_->now()) < next_interval) {
// Sleep using the wake-up signal directly so we can poll the target clock.
// tree_->sleep() always waits in wall-clock time, which diverges from sim
// time. For ROS/sim time, poll in short wall-clock intervals and re-check
// the target clock. For steady/system clocks, wait only for the remaining
// time to avoid oversleeping past next_interval.
const auto remaining = next_interval - now;
const auto remaining_ns = std::chrono::nanoseconds(remaining.nanoseconds());
const auto wait_duration =
is_ros_time && remaining_ns > poll_interval ? poll_interval : remaining_ns;
if (wake_up->waitFor(wait_duration)) { // Preempted by emitWakeUpSignal()

Copilot uses AI. Check for mistakes.
return true;
}
}
return true;
}

Expand Down
17 changes: 13 additions & 4 deletions nav2_behavior_tree/src/behavior_tree_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "behaviortree_cpp/utils/shared_library.h"
#include "nav2_behavior_tree/json_utils.hpp"
#include "nav2_behavior_tree/utils/loop_rate.hpp"
#include "nav2_ros_common/rate.hpp"

namespace nav2_behavior_tree
{
Expand All @@ -39,8 +40,7 @@ BehaviorTreeEngine::BehaviorTreeEngine(
factory_.registerFromPlugin(loader.getOSName(p));
}

// clock for throttled debug log
clock_ = node->get_clock();
node_ = node;
}

BtStatus
Expand All @@ -50,7 +50,16 @@ BehaviorTreeEngine::run(
std::function<bool()> cancelRequested,
std::chrono::milliseconds loopTimeout)
{
nav2_behavior_tree::LoopRate loopRate(loopTimeout, tree);
auto node = node_.lock();
if (!node) {
RCLCPP_ERROR(
rclcpp::get_logger("BehaviorTreeEngine"),
"BehaviorTreeEngine node expired. Exiting with failure.");
return BtStatus::FAILED;
}

auto rate_clock = nav2::selectClock(node);
nav2_behavior_tree::LoopRate loopRate(loopTimeout, tree, rate_clock);
BT::NodeStatus result = BT::NodeStatus::RUNNING;

// Loop until something happens with ROS or the node completes
Expand All @@ -70,7 +79,7 @@ BehaviorTreeEngine::run(
if (!loopRate.sleep()) {
RCLCPP_DEBUG_THROTTLE(
rclcpp::get_logger("BehaviorTreeEngine"),
*clock_, 1000,
*rate_clock, 1000,
"Behavior Tree tick rate %0.2f was exceeded!",
1.0 / (loopRate.period().count() * 1.0e-9));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ TEST_F(BTActionNodeTestFixture, test_server_timeout_success)
BT::NodeStatus result = BT::NodeStatus::RUNNING;

// BT loop execution rate
nav2_behavior_tree::LoopRate loopRate(10ms, tree_.get());
nav2_behavior_tree::LoopRate loopRate(
10ms, tree_.get(), std::make_shared<rclcpp::Clock>(RCL_STEADY_TIME));

// main BT execution loop
while (rclcpp::ok() && result == BT::NodeStatus::RUNNING) {
Expand Down
3 changes: 2 additions & 1 deletion nav2_behaviors/include/nav2_behaviors/timed_behavior.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "nav2_util/robot_utils.hpp"
#include "nav2_util/twist_publisher.hpp"
#include "nav2_ros_common/simple_action_server.hpp"
#include "nav2_ros_common/rate.hpp"
#include "nav2_core/behavior.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
Expand Down Expand Up @@ -226,7 +227,7 @@ class TimedBehavior : public nav2_core::Behavior
}

auto start_time = clock_->now();
rclcpp::WallRate loop_rate(cycle_frequency_);
nav2::Rate loop_rate(node_.lock(), cycle_frequency_);

while (rclcpp::ok()) {
elapsed_time_ = clock_->now() - start_time;
Expand Down
3 changes: 2 additions & 1 deletion nav2_controller/src/controller_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "lifecycle_msgs/msg/state.hpp"
#include "nav2_core/controller_exceptions.hpp"
#include "nav2_ros_common/node_utils.hpp"
#include "nav2_ros_common/rate.hpp"
#include "nav2_util/geometry_utils.hpp"
#include "nav2_util/path_utils.hpp"
#include "nav2_controller/controller_server.hpp"
Expand Down Expand Up @@ -494,7 +495,7 @@ void ControllerServer::computeControl()
}

last_valid_cmd_time_ = now();
rclcpp::WallRate loop_rate(params_->controller_frequency);
nav2::Rate loop_rate(this, params_->controller_frequency);
while (rclcpp::ok()) {
auto start_time = this->now();

Expand Down
3 changes: 2 additions & 1 deletion nav2_costmap_2d/src/costmap_2d_ros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "nav2_costmap_2d/layered_costmap.hpp"
#include "nav2_util/execution_timer.hpp"
#include "nav2_ros_common/node_utils.hpp"
#include "nav2_ros_common/rate.hpp"
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
#include "tf2_ros/create_timer_ros.hpp"
#include "nav2_util/robot_utils.hpp"
Expand Down Expand Up @@ -532,7 +533,7 @@ Costmap2DROS::mapUpdateLoop(double frequency)

RCLCPP_DEBUG(get_logger(), "Entering loop");

rclcpp::WallRate r(frequency); // 200ms by default
nav2::Rate r(this, frequency); // 200ms by default

while (rclcpp::ok() && !map_update_thread_shutdown_) {
nav2_util::ExecutionTimer timer;
Expand Down
15 changes: 8 additions & 7 deletions nav2_docking/opennav_docking/src/docking_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

#include "angles/angles.h"
#include "nav2_ros_common/rate.hpp"
#include "opennav_docking/docking_server.hpp"
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
#include "tf2/utils.hpp"
Expand Down Expand Up @@ -175,7 +176,7 @@ void DockingServer::dockRobot()
{
std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
action_start_time_ = this->now();
rclcpp::Rate loop_rate(params_->controller_frequency);
nav2::Rate loop_rate(this, params_->controller_frequency);

auto goal = docking_action_server_->get_current_goal();
auto result = std::make_shared<DockRobot::Result>();
Expand Down Expand Up @@ -393,7 +394,7 @@ void DockingServer::doInitialPerception(Dock * dock, geometry_msgs::msg::PoseSta
throw opennav_docking_core::FailedToDetectDock("Failed to start the detection process.");
}

rclcpp::Rate loop_rate(params_->controller_frequency);
nav2::Rate loop_rate(this, params_->controller_frequency);
auto start = this->now();
auto timeout = rclcpp::Duration::from_seconds(params_->initial_perception_timeout);
while (!dock->plugin->getRefinedPose(dock_pose, dock->id)) {
Expand All @@ -419,7 +420,7 @@ void DockingServer::rotateToDock(const geometry_msgs::msg::PoseStamped & dock_po
target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
tf2::getYaw(target_pose.pose.orientation) + M_PI);

rclcpp::Rate loop_rate(params_->controller_frequency);
nav2::Rate loop_rate(this, params_->controller_frequency);
auto start = this->now();
auto timeout = rclcpp::Duration::from_seconds(params_->rotate_to_dock_timeout);

Expand Down Expand Up @@ -452,7 +453,7 @@ void DockingServer::rotateToDock(const geometry_msgs::msg::PoseStamped & dock_po
bool DockingServer::approachDock(
Dock * dock, geometry_msgs::msg::PoseStamped & dock_pose, bool backward)
{
rclcpp::Rate loop_rate(params_->controller_frequency);
nav2::Rate loop_rate(this, params_->controller_frequency);
auto start = this->now();
auto timeout = rclcpp::Duration::from_seconds(params_->dock_approach_timeout);

Expand Down Expand Up @@ -522,7 +523,7 @@ bool DockingServer::waitForCharge(Dock * dock)
return true;
}

rclcpp::Rate loop_rate(params_->controller_frequency);
nav2::Rate loop_rate(this, params_->controller_frequency);
auto start = this->now();
auto timeout = rclcpp::Duration::from_seconds(params_->wait_charge_timeout);
while (rclcpp::ok()) {
Expand Down Expand Up @@ -550,7 +551,7 @@ bool DockingServer::waitForCharge(Dock * dock)
bool DockingServer::resetApproach(
const geometry_msgs::msg::PoseStamped & staging_pose, bool backward)
{
rclcpp::Rate loop_rate(params_->controller_frequency);
nav2::Rate loop_rate(this, params_->controller_frequency);
auto start = this->now();
auto timeout = rclcpp::Duration::from_seconds(params_->dock_approach_timeout);
while (rclcpp::ok()) {
Expand Down Expand Up @@ -621,7 +622,7 @@ void DockingServer::undockRobot()
{
std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
action_start_time_ = this->now();
rclcpp::Rate loop_rate(params_->controller_frequency);
nav2::Rate loop_rate(this, params_->controller_frequency);

auto goal = undocking_action_server_->get_current_goal();
auto result = std::make_shared<UndockRobot::Result>();
Expand Down
5 changes: 3 additions & 2 deletions nav2_following/opennav_following/src/following_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.

#include "angles/angles.h"
#include "nav2_ros_common/rate.hpp"
#include "opennav_docking_core/docking_exceptions.hpp"
#include "opennav_following/following_server.hpp"
#include "nav2_util/geometry_utils.hpp"
Expand Down Expand Up @@ -180,7 +181,7 @@ void FollowingServer::followObject()
{
std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
action_start_time_ = this->now();
rclcpp::Rate loop_rate(params_->controller_frequency);
nav2::Rate loop_rate(this, params_->controller_frequency);

auto goal = following_action_server_->get_current_goal();
auto result = std::make_shared<FollowObject::Result>();
Expand Down Expand Up @@ -525,7 +526,7 @@ bool FollowingServer::getRefinedPose(geometry_msgs::msg::PoseStamped & pose)
if (detected.header.stamp == rclcpp::Time(0)) {
auto start = this->now();
auto timeout = rclcpp::Duration::from_seconds(params_->detection_timeout);
rclcpp::Rate wait_rate(params_->controller_frequency);
nav2::Rate wait_rate(this, params_->controller_frequency);
while (this->now() - start < timeout) {
// Check if a new detection arrived
if (detected_dynamic_pose_.header.stamp != rclcpp::Time(0)) {
Expand Down
13 changes: 9 additions & 4 deletions nav2_lifecycle_manager/src/lifecycle_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <vector>

#include "rclcpp/rclcpp.hpp"
#include "nav2_ros_common/interface_factories.hpp"

using namespace std::chrono_literals;
using namespace std::placeholders;
Expand Down Expand Up @@ -73,15 +74,17 @@ LifecycleManager::LifecycleManager(const rclcpp::NodeOptions & options)
transition_label_map_[Transition::TRANSITION_UNCONFIGURED_SHUTDOWN] =
std::string("Shutting down ");

init_timer_ = this->create_wall_timer(
init_timer_ = nav2::create_timer(
this,
0s,
[this]() -> void {
init_timer_->cancel();
createLifecyclePublishers();
createLifecycleServiceClients();
createLifecycleServiceServers();
if (autostart_) {
init_timer_ = this->create_wall_timer(
init_timer_ = nav2::create_timer(
this,
0s,
[this]() -> void {
init_timer_->cancel();
Expand Down Expand Up @@ -481,7 +484,8 @@ LifecycleManager::createBondTimer()
}

message("Creating bond timer...");
bond_timer_ = this->create_wall_timer(
bond_timer_ = nav2::create_timer(
this,
200ms,
std::bind(&LifecycleManager::checkBondConnections, this),
callback_group_);
Expand Down Expand Up @@ -556,7 +560,8 @@ LifecycleManager::checkBondConnections()
// Initialize the bond respawn timer to check if server comes back online
// after a failure, within a maximum timeout period.
if (attempt_respawn_reconnection_) {
bond_respawn_timer_ = this->create_wall_timer(
bond_respawn_timer_ = nav2::create_timer(
this,
1s,
std::bind(&LifecycleManager::checkBondRespawnConnection, this),
callback_group_);
Expand Down
7 changes: 4 additions & 3 deletions nav2_lifecycle_manager/test/test_bond.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "rclcpp/rclcpp.hpp"
#include "nav2_ros_common/lifecycle_node.hpp"
#include "nav2_ros_common/node_thread.hpp"
#include "nav2_ros_common/rate.hpp"
#include "nav2_lifecycle_manager/lifecycle_manager.hpp"
#include "nav2_lifecycle_manager/lifecycle_manager_client.hpp"

Expand Down Expand Up @@ -128,7 +129,7 @@ class TestFixture
TEST(LifecycleBondTest, POSITIVE)
{
// let the lifecycle server come up
rclcpp::Rate(1).sleep();
nav2::WallRate(1).sleep();

auto node = std::make_shared<rclcpp::Node>("lifecycle_manager_test_service_client");
nav2_lifecycle_manager::LifecycleManagerClient client("lifecycle_manager_test", node);
Expand All @@ -140,14 +141,14 @@ TEST(LifecycleBondTest, POSITIVE)
EXPECT_TRUE(client.startup());

// check if bond is connected after being activated
rclcpp::Rate(5).sleep();
nav2::WallRate(5).sleep();
EXPECT_TRUE(bond_tester->isBondConnected());
EXPECT_EQ(bond_tester->getState(), "activated");

bond_tester->breakBond();

// bond should be disconnected now and lifecycle manager should know and react to reset
rclcpp::Rate(5).sleep();
nav2::WallRate(5).sleep();
EXPECT_EQ(
nav2_lifecycle_manager::SystemStatus::INACTIVE,
client.is_active(std::chrono::nanoseconds(1000000000)));
Expand Down
Loading
Loading