ROS2-013-Communication Mechanisms, Wrapping Up: Time APIs
Introduction
Suppose there's a robot with a lidar on it. The lidar has a related node that publishes all the information it detects, and a node belonging to one of the robot's navigation modules subscribes to and receives it. To reduce the errors that communication delays introduce into path planning while the robot is moving, when the two exchange data we obviously need to guarantee the information is real-time.
So we need the time APIs, which stamp the data with the time it was produced as it is transmitted, letting the receiving side judge how fresh the data is. Beyond that, lidar coordinate transforms and multi-message fusion need these APIs too.
Types
There are the following kinds of time API:
- Rate class
- Time class
- Duration class
Here is a detailed explanation of each:
1. The Rate class
In the topic communication case, we asked the topic publisher to publish messages at a certain frequency, and back then it was done by controlling the publishing frequency with a timer.
Actually, besides timers, ROS2 also provides the Rate class, and an object of that class can control the program's running frequency too.
To use the class we first create an object, which takes a time interval or a frequency interval; then, when we need it, we combine it with a loop such as while and the rate.sleep() function to control the program's running frequency.
①. Using the Rate class in rclcpp
For example, we want to print a line of text periodically: ----&----
// 1. Include the header files
#include "rclcpp/rclcpp.hpp"
using namespace std::chrono_literals;
// 3. Define the node class:
class TImeTestNode : public rclcpp::Node
{
public:
TImeTestNode() : Node("time_test_node_cpp")
{
demo_rate();
return;
};
private:
// Using the Rate class
void demo_rate()
{
// 1. Create the Rate object
rclcpp::Rate rate1(1000ms); // way one
rclcpp::Rate rate2(2.0); // way two (Unit: Hz/s)
// 2. Call Rate's sleep function
while (rclcpp::ok())
{
RCLCPP_INFO(this->get_logger(), "----&----");
// rate1.sleep();
rate2.sleep();
}
}
};
int main(int argc, char **argv)
{
// 2. Initialise the ROS2 client
rclcpp::init(argc, argv);
// 4. Call the spin function, passing in the node object pointer.
rclcpp::spin(std::make_shared<TImeTestNode>());
// 5. Release resources;
rclcpp::shutdown();
return 0;
}Note
To use the time units, remember using namespace std::chrono_literals;
②. Using the Rate class in rclpy
For example, we want to print a line of text periodically: ----**----
# 1. Import the package;
import rclpy
from rclpy.node import Node
import threading
# 3. Define the node class:
class TImeTestNode(Node):
def __init__(self):
super().__init__("time_test_node_py")
self.demo_rate()
def demo_rate(self):
# 1. Create the Rate object
self.rate = self.create_rate(1.0) # (Unit: Hz/s)
# fake 2. Call Rate's sleep function directly -- will block the program
# while rclpy.ok():
# self.get_logger().info("----**----")
# self.rate.sleep()
# 2. Call Rate's sleep function from a child thread (unique to Python)
thread = threading.Thread(target=self.do_rate)
thread.start()
def do_rate(self):
while rclpy.ok():
self.get_logger().info("----**----")
self.rate.sleep()
def main():
# 2. Initialise the ROS2 client
rclpy.init()
# 4. Call the spin function, passing in the node object.
rclpy.spin(TImeTestNode())
# 5. Release resources;
rclpy.shutdown()
if __name__ == '__main__':
main()Note
In Python you have to create a child thread to call Rate's sleep function. Calling it directly blocks the program (in this example that means: only one line of "----**----" gets printed instead of repeated output at the set frequency)
2. The Time class
The Time class is fairly simple to use. To use it we first create an object, which takes a set time; then, when we need it, we call its built-in functions. We can also get the current moment from the node to create the object.
①. Using the Time class in rclcpp
// 1. Include the header files
#include "rclcpp/rclcpp.hpp"
using namespace std::chrono_literals;
// 3. Define the node class:
class TImeTestNode : public rclcpp::Node
{
public:
TImeTestNode() : Node("time_test_node_cpp")
{
demo_time();
return;
};
private:
// Demonstrating the use of Time
void demo_time()
{
// 1. Create a Time object -- custom time
rclcpp::Time t1(500000000L); // pass in nanoseconds
rclcpp::Time t2(2, 300000000L); // pass in seconds and nanoseconds
// 1. Create a Time object -- taken straight from the node
// rclcpp::Time t_now = this->get_clock()->now();
rclcpp::Time t3_now = this->now();
// 2. Call Time's related functions
RCLCPP_INFO(this->get_logger(), "t1: s = %.2f, ns = %ld", t1.seconds(), t1.nanoseconds());
RCLCPP_INFO(this->get_logger(), "t2: s = %.2f, ns = %ld", t2.seconds(), t2.nanoseconds());
RCLCPP_INFO(this->get_logger(), "t3_now: s = %.2f, ns = %ld", t3_now.seconds(), t3_now.nanoseconds());
}
};
int main(int argc, char **argv)
{
// 2. Initialise the ROS2 client
rclcpp::init(argc, argv);
// 4. Call the spin function, passing in the node object pointer.
rclcpp::spin(std::make_shared<TImeTestNode>());
// 5. Release resources;
rclcpp::shutdown();
return 0;
}②. Using the Time class in rclpy
# 1. Import the package;
import rclpy
from rclpy.node import Node
from rclpy.time import Time
# 3. Define the node class:
class TImeTestNode(Node):
def __init__(self):
super().__init__("time_test_node_py")
self.demo_time()
def demo_time(self):
# 1. Create a Time object -- custom time
t1 = Time(seconds=2, nanoseconds=500000000)
# Create a Time object -- taken straight from the node
t2_now = self.get_clock().now()
# 2. Call the Time functions
self.get_logger().info("t1: s = %.2f, ns = %d" % (t1.seconds_nanoseconds()[0], t1.seconds_nanoseconds()[1]))
self.get_logger().info("t2_now: s = %.2f, ns = %d" % (t2_now.seconds_nanoseconds()[0], t2_now.seconds_nanoseconds()[1]))
self.get_logger().info("t2_now using nanoseconds: ns = %d" % (t2_now.nanoseconds))
def main():
# 2. Initialise the ROS2 client
rclpy.init()
# 4. Call the spin function, passing in the node object.
rclpy.spin(TImeTestNode())
# 5. Release resources;
rclpy.shutdown()
if __name__ == '__main__':
main()Note
Remember to import it with from rclpy.time import Time
3. The Duration class
The Duration class is used much like the Time class. To use it we first create an object, which takes a time; then, when we need it, we call its built-in functions.
①. Using the Duration class in rclcpp
// 1. Include the header files
#include "rclcpp/rclcpp.hpp"
using namespace std::chrono_literals;
// 3. Define the node class:
class TImeTestNode : public rclcpp::Node
{
public:
TImeTestNode() : Node("time_test_node_cpp")
{
demo_duration();
return;
};
private:
// Demonstrating the use of Duration
void demo_duration()
{
// 1. Create the Duration object
rclcpp::Duration d1(1s);
rclcpp::Duration d2(1, 500000000);
// 1. Call the Duration object's functions
RCLCPP_INFO(this->get_logger(), "d1: s = %.2f, ns = %ld", d1.seconds(), d1.nanoseconds());
RCLCPP_INFO(this->get_logger(), "d2: s = %.2f, ns = %ld", d2.seconds(), d2.nanoseconds());
}
};
int main(int argc, char **argv)
{
// 2. Initialise the ROS2 client
rclcpp::init(argc, argv);
// 4. Call the spin function, passing in the node object pointer.
rclcpp::spin(std::make_shared<TImeTestNode>());
// 5. Release resources;
rclcpp::shutdown();
return 0;
}Note
Remember using namespace std::chrono_literals;
②. Using the Duration class in rclpy
# 1. Import the package;
import rclpy
from rclpy.node import Node
from rclpy.duration import Duration
# 3. Define the node class:
class TImeTestNode(Node):
def __init__(self):
super().__init__("time_test_node_py")
self.demo_duration()
def demo_duration(self):
# Create the Duration object
d1 = Duration(seconds=1, nanoseconds=500000000)
# 2. Call the relevant functions inside the Duration object
self.get_logger().info("t1: ns = %d" % (d1.nanoseconds))
def main():
# 2. Initialise the ROS2 client
rclpy.init()
# 4. Call the spin function, passing in the node object.
rclpy.spin(TImeTestNode())
# 5. Release resources;
rclpy.shutdown()
if __name__ == '__main__':
main()Note
Remember to import it with from rclpy.duration import Duration
How the Time and Duration classes differ, and how they compute
①. Differences
Only their API usage is similar; the two classes are fundamentally different:
- Time refers to the timestamp of a moment
- Duration refers to the length of a stretch of time
②. Computation
C++
Suppose we have the following variables:
rclcpp::Time t1(1,500000000);
rclcpp::Time t2(10,0);
rclcpp::Duration d1(2,0);
rclcpp::Duration d2(4,0);Then we can do computations including but not limited to the following:
Comparison:
RCLCPP_INFO(node->get_logger(), "t1 >= t2 ? %d", t1 >= t2); RCLCPP_INFO(node->get_logger(), "t1 < t2 ? %d", t1 < t2); RCLCPP_INFO(node->get_logger(), "d1 >= d2 ? %d", d1 >= d2); RCLCPP_INFO(node->get_logger(), "d1 < d2 ? %d", d1 < d2);Arithmetic:
rclcpp::Time t3 = t1 + du1; rclcpp::Time t4 = t2 - du2; rclcpp::Duration d3 = t2 - t1; rclcpp::Duration d4 = d1 * 3.0; rclcpp::Duration d5 = d1 + d2; rclcpp::Duration d6 = d2 - d1;
Python
Suppose we have the following variables:
t1 = Time(seconds = 1);
t2 = Time(seconds = 10);
d1 = Duration(seconds = 2);
d2 = Duration(seconds = 4);Then we can do computations including but not limited to the following:
Comparison:
node.get_logger().info("t1 >= t2 ? %d" % (t1 >= t2)); node.get_logger().info("t1 < t2 ? %d" % (t1 < t2)); node.get_logger().info("d1 >= d2 ? %d" % (d1 >= d2)); node.get_logger().info("d1 < d2 ? %d" % (d1 < d2));Arithmetic:
t3 = t1 + du1; t4 = t2 - du2; d3 = t2 - t1; d4 = d1 * 3.0; d5 = d1 + d2; d6 = d2 - d1;
Summary
This section went through the time APIs, covering the three big families — the Rate, Time and Duration classes — one by one. When nodes communicate, these APIs come in very handy.
Important
This article was translated by Hermes Agent; the original text can be found at here. The translation is for reference only. Translation time: 2026-11-05
