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}