ros2_client/builtin_interfaces.rs
1//! Defines message types `Duration` and `Time`. See [builtin_interfaces](https://index.ros.org/p/builtin_interfaces/)
2//!
3//!
4//! The name "builtin_interfaces" is not very descriptive, but that is how
5//! it is in ROS.
6//!
7//! Type "Time" in ROS 2 can mean either
8//! * `builtin_interfaces::msg::Time`, which is the message type over the wire,
9//! or
10//! * `rclcpp::Time`, which is a wrapper for `rcl_time_point_value_t` (in RCL),
11//! which again is a typedef for `rcutils_time_point_value_t`, which is in
12//! package `rclutils` and is a typedef for `int64_t`. Comment specifies this
13//! to be "A single point in time, measured in nanoseconds since the Unix
14//! epoch." This type is used for time-related computations.
15//!
16//! This module defines the over-the wire `Time` and `Duration` types.
17//! The module [`ros_time`] defines types intended for computaiton and
18//! in-memory representation.
19//!
20//! As the over-the wire time representation uses signed 32-bit integer for
21//! seconds since the unix epoch, it is susceptible to the
22//! [Year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem).
23//!
24//! This implementation uses 64-bit nanosecond count in memory, which will not
25//! overflow until the year 2262, but the serialization will saturate in 2038.
26
27use serde::{Deserialize, Serialize};
28use log::{error, warn};
29
30use crate::{message::Message, ros_time::ROSTime};
31
32/// Over-the wire representation of a timestamp.
33///
34/// The recommended constructor is [`From`]-conversion from [`ROSTime`].
35///
36/// The most useful things to do with these is send in a [`Message`] or
37/// convert into a `ROSTime`.
38#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
39#[serde(from = "repr::Time", into = "repr::Time")]
40pub struct Time {
41 /// Nanoseconds since the Unix epoch
42 nanos_since_epoch: i64,
43}
44
45impl Time {
46 pub const ZERO: Time = Time {
47 nanos_since_epoch: 0,
48 };
49
50 pub const DUMMY: Time = Time {
51 nanos_since_epoch: 1234567890123,
52 };
53
54 /// Returns the current time for the system clock.
55 ///
56 /// To use simulation-capable time, ask from `Node`.
57 pub(crate) fn now() -> Self {
58 chrono::Utc::now()
59 .timestamp_nanos_opt()
60 .map(Self::from_nanos)
61 .unwrap_or_else(|| {
62 error!("Timestamp out of range.");
63 Time::ZERO // Since we have to return something
64 // But your clock would have to rather far from year 2024 AD
65 // in order to trigger this default.
66 })
67 }
68
69 pub fn from_nanos(nanos_since_epoch: i64) -> Self {
70 Self { nanos_since_epoch }
71 }
72
73 pub fn to_nanos(&self) -> i64 {
74 self.nanos_since_epoch
75 }
76}
77
78// Conversions between `Time` and `repr::Time`.
79//
80// These are non-trivial, because
81// the fractional part of `repr::Time`is (by definition) always positive,
82// whereas the integer part is signed and may be negative.
83
84impl From<repr::Time> for Time {
85 fn from(rt: repr::Time) -> Time {
86 // sanity check
87 if rt.nanosec >= 1_000_000_000 {
88 warn!(
89 "builtin_interfaces::Time fractional part at 1 or greater: {} / 10^9 ",
90 rt.nanosec
91 );
92 }
93
94 // But convert in any case
95 Time::from_nanos((rt.sec as i64) * 1_000_000_000 + (rt.nanosec as i64))
96
97 // This same conversion formula works for both positive and negative Times.
98 //
99 // Positive numbers: No surprise, this is what you would expect.
100 //
101 // Negative: E.g. -1.5 sec is represented as -2 whole and 0.5 *10^9 nanosec
102 // fractional. Then we have -2 * 10^9 + 0.5 * 10^9 = -1.5 * 10^9 .
103 }
104}
105
106// Algorithm from https://github.com/ros2/rclcpp/blob/rolling/rclcpp/src/rclcpp/time.cpp#L278
107// function `convert_rcl_time_to_sec_nanos`
108impl From<Time> for repr::Time {
109 fn from(t: Time) -> repr::Time {
110 let t = t.to_nanos();
111 let quot = t / 1_000_000_000;
112 let rem = t % 1_000_000_000;
113
114 // https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
115 // "Rust uses a remainder defined with truncating division.
116 // Given remainder = dividend % divisor,
117 // the remainder will have the same sign as the dividend."
118
119 if rem >= 0 {
120 // positive time, no surprise here
121 // OR, negative time, but a whole number of seconds, fractional part is zero
122 repr::Time {
123 // Saturate seconds to i32. This is different from C++ implementation
124 // in rclcpp, which just uses
125 // `ret.sec = static_cast<std::int32_t>(result.quot)`.
126 sec: if quot > (i32::MAX as i64) {
127 warn!("rcl_interfaces::Time conversion overflow");
128 i32::MAX
129 } else if quot < (i32::MIN as i64) {
130 warn!("rcl_interfaces::Time conversion underflow");
131 i32::MIN
132 } else {
133 quot as i32
134 },
135 nanosec: rem as u32,
136 }
137 } else {
138 // Now `t` is negative AND `rem` is non-zero.
139 // We do some non-obvious arithmetic:
140
141 // saturate whole seconds
142 let quot_sat = if quot >= (i32::MIN as i64) {
143 quot as i32
144 } else {
145 warn!("rcl_interfaces::Time conversion underflow");
146 i32::MIN
147 };
148
149 // Now, `rem` is between -999_999_999 and -1, inclusive.
150 // Case rem = 0 is included in the positive branch.
151 //
152 // Adding 1_000_000_000 will make it positive, so cast to u32 is ok.
153 //
154 // It is also the right thing to do, because
155 // * 0.0 sec = 0 sec and 0 nanosec
156 // * -0.000_000_001 sec = -1 sec and 999_999_999 nanosec
157 // * ...
158 // * -0.99999999999 sec = -1 sec and 000_000_001 nanosec
159 // * -1.0 sec = -1 sec and 0 nanosec
160 // * -1.00000000001 sec = -2 sec and 999_999_999 nanosec
161 repr::Time {
162 sec: quot_sat - 1, // note -1
163 nanosec: (1_000_000_000 + rem) as u32,
164 }
165 }
166 }
167}
168
169// This private module defines the wire representation of Time
170mod repr {
171 use serde::{Deserialize, Serialize};
172
173 use crate::message::Message;
174
175 #[derive(Clone, Copy, Serialize, Deserialize, Debug)]
176 pub struct Time {
177 pub sec: i32,
178 pub nanosec: u32,
179 }
180 impl Message for Time {}
181}
182
183// NOTE:
184// This may panic, if the source ROSTime is unreasonably far in the past or
185// future. If this is not ok, then TryFrom should be implemented and used.
186impl From<ROSTime> for Time {
187 fn from(rt: ROSTime) -> Time {
188 Time::from_nanos(rt.to_nanos())
189 }
190}
191
192impl From<Time> for ROSTime {
193 fn from(t: Time) -> ROSTime {
194 ROSTime::from_nanos(t.to_nanos())
195 }
196}
197
198// TODO: Implement constructors and conversions to/from usual Rust time formats
199// Note that this type does not specify a zero point in time.
200
201// Converting a straight 64-bit nanoseconds value to Duration is non-trivial.
202// See function `Duration::operator builtin_interfaces::msg::Duration() const`
203// in https://github.com/ros2/rclcpp/blob/rolling/rclcpp/src/rclcpp/duration.cpp
204//
205// If dividing the raw nanosecond duration by 10^9 would overflow `i32`, then
206// saturate to either to {sec = i32::max , nanosec = u32::max} (positive
207// overflow) or { sec = i32::min , nanosec = 0 }.
208//
209// Converting non-negative nanoseconds to Duration is straightforward. Just use
210// integer division by 10^9 and store quotient and remainder.
211//
212// Negative nanoseconds are converted by similar integer divsion, and the result
213// is { sec = quotient - 1 , nanosec = 10^9 + remainder}
214//
215// E.g. -1.5*10^9 nanosec --> quotient = -1 , remainder = -5*10^8
216// (We are using division with invariant: quotient * divisor + remainder ==
217// dividend ) Now { sec = -2 , nanosec = +5 * 10^8 }
218//
219// -1 nanosec --> quotient = 0, remainder = -1 -->
220// { sec = -1 , nanosec = 999_999_999 }
221
222/// Over-the wire representation of Duration, i.e. difference between two
223/// timestamps.
224///
225/// To actually compute a time difference, use types [`ROSTime`] and
226/// [`ROSDuration`](crate::ros_time::ROSDuration), and convert to [`Duration`]
227/// for sending in a [`Message`].
228#[derive(Clone, Serialize, Deserialize, Debug)]
229pub struct Duration {
230 sec: i32, // ROS2: Seconds component, range is valid over any possible int32 value.
231 nanosec: u32, /* ROS2: Nanoseconds component in the range of [0, 10e9). */
232}
233impl Message for Duration {}
234
235impl Duration {
236 pub const fn zero() -> Self {
237 Self { sec: 0, nanosec: 0 }
238 }
239
240 pub const fn from_secs(sec: i32) -> Self {
241 Self { sec, nanosec: 0 }
242 }
243
244 pub const fn from_millis(millis: i64) -> Self {
245 let nanos = millis * 1_000_000; // Maybe overflow, but result will also.
246 Self::from_nanos(nanos)
247 }
248
249 pub const fn from_nanos(nanos: i64) -> Self {
250 // This algorithm is from
251 // https://github.com/ros2/rclcpp/blob/ea8daa37845e6137cba07a18eb653d97d87e6174/rclcpp/src/rclcpp/duration.cpp
252 // lines 61-88
253
254 // Except that we also test for quot underflow in case rem == 0
255
256 let quot = nanos / 1_000_000_000;
257 let rem = nanos % 1_000_000_000;
258 // Rust `%` is the remainder operator.
259 // If rem is negative, so is nanos
260 if rem >= 0 {
261 // positive or zero duration
262 if quot > (i32::MAX as i64) {
263 // overflow => saturate to max
264 Duration {
265 sec: i32::MAX,
266 nanosec: u32::MAX,
267 }
268 } else if quot <= (i32::MIN as i64) {
269 // underflow => saturate to min
270 Duration {
271 sec: i32::MIN,
272 nanosec: 0,
273 }
274 } else {
275 // normal case
276 Duration {
277 sec: quot as i32,
278 nanosec: rem as u32,
279 }
280 // as-conversions will succeed: we know 0 <= quot <= i32::MAX, and
281 // also 0 <= rem <= 1_000_000_000
282 }
283 } else {
284 // duration was negative
285 if quot <= (i32::MIN as i64) {
286 // underflow => saturate to min
287 Duration {
288 sec: i32::MIN,
289 nanosec: 0,
290 }
291 } else {
292 // normal negative result
293 Duration {
294 sec: (quot + 1) as i32,
295 nanosec: (1_000_000_000 + rem) as u32,
296 }
297 // i32::MIN <= quot < 0 => quot+1 is valid i32
298 // -999_999_999 <= rem < 0 =>
299 // 1 <= 1_000_000_000 + rem < 1_000_000_000 => valid u32
300 }
301 }
302 }
303
304 pub fn to_nanos(&self) -> i64 {
305 let s = self.sec as i64;
306 let ns = self.nanosec as i64;
307
308 1_000_000_000 * s + ns
309 }
310}
311
312#[cfg(test)]
313mod test {
314 use super::{repr, Time};
315
316 fn repr_conv_test(t: Time) {
317 let rt: repr::Time = t.into();
318 println!("{rt:?}");
319 assert_eq!(t, Time::from(rt))
320 }
321
322 #[test]
323 fn repr_conversion() {
324 repr_conv_test(Time::from_nanos(999_999_999));
325 repr_conv_test(Time::from_nanos(1_000_000_000));
326 repr_conv_test(Time::from_nanos(1_000_000_001));
327
328 repr_conv_test(Time::from_nanos(1_999_999_999));
329 repr_conv_test(Time::from_nanos(2_000_000_000));
330 repr_conv_test(Time::from_nanos(2_000_000_001));
331
332 repr_conv_test(Time::from_nanos(-999_999_999));
333 repr_conv_test(Time::from_nanos(-1_000_000_000));
334 repr_conv_test(Time::from_nanos(-1_000_000_001));
335
336 repr_conv_test(Time::from_nanos(-1_999_999_999));
337 repr_conv_test(Time::from_nanos(-2_000_000_000));
338 repr_conv_test(Time::from_nanos(-2_000_000_001));
339
340 repr_conv_test(Time::from_nanos(0));
341 repr_conv_test(Time::from_nanos(1));
342 repr_conv_test(Time::from_nanos(-1));
343 }
344}