Skip to content

Commit 429391b

Browse files
Add time source and clock API to nodes (#325)
* WIP Add minimal TimeSource implementation Signed-off-by: Luca Della Vedova <[email protected]> * Minor cleanup and code reorganization Signed-off-by: Luca Della Vedova <[email protected]> * Minor cleanups, add debug code Signed-off-by: Luca Della Vedova <[email protected]> * Minor cleanup Signed-off-by: Luca Della Vedova <[email protected]> * Change safety note to reflect get_now safety Signed-off-by: Luca Della Vedova <[email protected]> * Fix comment spelling Co-authored-by: jhdcs <[email protected]> * Cleanup and add some docs / tests Signed-off-by: Luca Della Vedova <[email protected]> * Avoid duplicated clocks in TimeSource Signed-off-by: Luca Della Vedova <[email protected]> * Change _rcl_clock to just Mutex Signed-off-by: Luca Della Vedova <[email protected]> * Remove Mutex from node clock Signed-off-by: Luca Della Vedova <[email protected]> * Change Mutex<bool> to AtomicBool Signed-off-by: Luca Della Vedova <[email protected]> * Avoid cloning message Signed-off-by: Luca Della Vedova <[email protected]> * Minor cleanup, add dependency Signed-off-by: Luca Della Vedova <[email protected]> * Remove hardcoded use_sim_time Signed-off-by: Luca Della Vedova <[email protected]> * Cleanup remaining warnings / clippy Signed-off-by: Luca Della Vedova <[email protected]> * Fix tests Signed-off-by: Luca Della Vedova <[email protected]> * Refactor API to use ClockSource Signed-off-by: Luca Della Vedova <[email protected]> * Fix Drop implementation, finish builder and add comments Signed-off-by: Luca Della Vedova <[email protected]> * Minor cleanups Signed-off-by: Luca Della Vedova <[email protected]> * Fix graph_tests Signed-off-by: Luca Della Vedova <[email protected]> * Remove Arc from time source Signed-off-by: Luca Della Vedova <[email protected]> * Remove Sync trait, format Signed-off-by: Luca Della Vedova <[email protected]> * Add comparison function for times, use reference to clock Signed-off-by: Luca Della Vedova <[email protected]> * Implement Add<Duration> and Sub<Duration> for Time Signed-off-by: Luca Della Vedova <[email protected]> * Make node pointer Weak to avoid memory leaks Signed-off-by: Luca Della Vedova <[email protected]> * Cleanups Signed-off-by: Luca Della Vedova <[email protected]> * WIP change clock type when use_sim_time parameter is changed Signed-off-by: Luca Della Vedova <[email protected]> * Remove need for mutex in node TimeSource Signed-off-by: Luca Della Vedova <[email protected]> * Change get_clock() to return cloned Clock object Signed-off-by: Luca Della Vedova <[email protected]> * Minor cleanup Signed-off-by: Luca Della Vedova <[email protected]> * Make get_parameter pub(crate) Signed-off-by: Luca Della Vedova <[email protected]> * Address review feedback Signed-off-by: Luca Della Vedova <[email protected]> * Make time_source pub(crate), remove unused APIs Signed-off-by: Luca Della Vedova <[email protected]> * Use MandatoryParameter for use_sim_time Signed-off-by: Luca Della Vedova <[email protected]> * Remove error variant from clock builder Signed-off-by: Luca Della Vedova <[email protected]> * Add integration test for use_sim_time = true Signed-off-by: Luca Della Vedova <[email protected]> * Minor cleanup for attach_node Signed-off-by: Luca Della Vedova <[email protected]> --------- Signed-off-by: Luca Della Vedova <[email protected]> Co-authored-by: jhdcs <[email protected]>
1 parent c2d8a29 commit 429391b

File tree

10 files changed

+542
-10
lines changed

10 files changed

+542
-10
lines changed

rclrs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ ament_rs = { version = "0.2", optional = true }
2020
futures = "0.3"
2121
# Needed for dynamic messages
2222
libloading = { version = "0.8", optional = true }
23+
# Needed for /clock topic subscription when using simulation time
24+
rosgraph_msgs = "*"
2325
# Needed for the Message trait, among others
2426
rosidl_runtime_rs = "0.3"
2527

rclrs/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<build_depend>rcl</build_depend>
1919
<depend>builtin_interfaces</depend>
2020
<depend>rcl_interfaces</depend>
21+
<depend>rosgraph_msgs</depend>
2122

2223
<export>
2324
<build_type>ament_cargo</build_type>

rclrs/src/clock.rs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
use crate::rcl_bindings::*;
2+
use crate::{error::ToResult, time::Time, to_rclrs_result};
3+
use std::sync::{Arc, Mutex};
4+
5+
/// Enum to describe clock type. Redefined for readability and to eliminate the uninitialized case
6+
/// from the `rcl_clock_type_t` enum in the binding.
7+
#[derive(Clone, Debug, Copy)]
8+
pub enum ClockType {
9+
/// Time with behavior dependent on the `set_ros_time(bool)` function. If called with `true`
10+
/// it will be driven by a manual value override, otherwise it will be System Time
11+
RosTime = 1,
12+
/// Wall time depending on the current system
13+
SystemTime = 2,
14+
/// Steady time, monotonically increasing but not necessarily equal to wall time.
15+
SteadyTime = 3,
16+
}
17+
18+
impl From<ClockType> for rcl_clock_type_t {
19+
fn from(clock_type: ClockType) -> Self {
20+
match clock_type {
21+
ClockType::RosTime => rcl_clock_type_t::RCL_ROS_TIME,
22+
ClockType::SystemTime => rcl_clock_type_t::RCL_SYSTEM_TIME,
23+
ClockType::SteadyTime => rcl_clock_type_t::RCL_STEADY_TIME,
24+
}
25+
}
26+
}
27+
28+
/// Struct that implements a Clock and wraps `rcl_clock_t`.
29+
#[derive(Clone, Debug)]
30+
pub struct Clock {
31+
_type: ClockType,
32+
_rcl_clock: Arc<Mutex<rcl_clock_t>>,
33+
// TODO(luca) Implement jump callbacks
34+
}
35+
36+
/// A clock source that can be used to drive the contained clock. Created when a clock of type
37+
/// `ClockType::RosTime` is constructed
38+
pub struct ClockSource {
39+
_rcl_clock: Arc<Mutex<rcl_clock_t>>,
40+
}
41+
42+
impl Clock {
43+
/// Creates a new Clock with `ClockType::SystemTime`
44+
pub fn system() -> Self {
45+
Self::make(ClockType::SystemTime)
46+
}
47+
48+
/// Creates a new Clock with `ClockType::SteadyTime`
49+
pub fn steady() -> Self {
50+
Self::make(ClockType::SteadyTime)
51+
}
52+
53+
/// Creates a new Clock with `ClockType::RosTime` and a matching `ClockSource` that can be used
54+
/// to update it
55+
pub fn with_source() -> (Self, ClockSource) {
56+
let clock = Self::make(ClockType::RosTime);
57+
let clock_source = ClockSource::new(clock._rcl_clock.clone());
58+
(clock, clock_source)
59+
}
60+
61+
/// Creates a new clock of the given `ClockType`.
62+
pub fn new(type_: ClockType) -> (Self, Option<ClockSource>) {
63+
let clock = Self::make(type_);
64+
let clock_source =
65+
matches!(type_, ClockType::RosTime).then(|| ClockSource::new(clock._rcl_clock.clone()));
66+
(clock, clock_source)
67+
}
68+
69+
fn make(type_: ClockType) -> Self {
70+
let mut rcl_clock;
71+
unsafe {
72+
// SAFETY: Getting a default value is always safe.
73+
rcl_clock = Self::init_generic_clock();
74+
let mut allocator = rcutils_get_default_allocator();
75+
// Function will return Err(_) only if there isn't enough memory to allocate a clock
76+
// object.
77+
rcl_clock_init(type_.into(), &mut rcl_clock, &mut allocator)
78+
.ok()
79+
.unwrap();
80+
}
81+
Self {
82+
_type: type_,
83+
_rcl_clock: Arc::new(Mutex::new(rcl_clock)),
84+
}
85+
}
86+
87+
/// Returns the clock's `ClockType`.
88+
pub fn clock_type(&self) -> ClockType {
89+
self._type
90+
}
91+
92+
/// Returns the current clock's timestamp.
93+
pub fn now(&self) -> Time {
94+
let mut clock = self._rcl_clock.lock().unwrap();
95+
let mut time_point: i64 = 0;
96+
unsafe {
97+
// SAFETY: No preconditions for this function
98+
rcl_clock_get_now(&mut *clock, &mut time_point);
99+
}
100+
Time {
101+
nsec: time_point,
102+
clock: Arc::downgrade(&self._rcl_clock),
103+
}
104+
}
105+
106+
/// Helper function to privately initialize a default clock, with the same behavior as
107+
/// `rcl_init_generic_clock`. By defining a private function instead of implementing
108+
/// `Default`, we avoid exposing a public API to create an invalid clock.
109+
// SAFETY: Getting a default value is always safe.
110+
unsafe fn init_generic_clock() -> rcl_clock_t {
111+
let allocator = rcutils_get_default_allocator();
112+
rcl_clock_t {
113+
type_: rcl_clock_type_t::RCL_CLOCK_UNINITIALIZED,
114+
jump_callbacks: std::ptr::null_mut::<rcl_jump_callback_info_t>(),
115+
num_jump_callbacks: 0,
116+
get_now: None,
117+
data: std::ptr::null_mut::<std::os::raw::c_void>(),
118+
allocator,
119+
}
120+
}
121+
}
122+
123+
impl Drop for ClockSource {
124+
fn drop(&mut self) {
125+
self.set_ros_time_enable(false);
126+
}
127+
}
128+
129+
impl PartialEq for ClockSource {
130+
fn eq(&self, other: &Self) -> bool {
131+
Arc::ptr_eq(&self._rcl_clock, &other._rcl_clock)
132+
}
133+
}
134+
135+
impl ClockSource {
136+
/// Sets the value of the current ROS time.
137+
pub fn set_ros_time_override(&self, nanoseconds: i64) {
138+
let mut clock = self._rcl_clock.lock().unwrap();
139+
// SAFETY: Safe if clock jump callbacks are not edited, which is guaranteed
140+
// by the mutex
141+
unsafe {
142+
// Function will only fail if timer was uninitialized or not RosTime, which should
143+
// not happen
144+
rcl_set_ros_time_override(&mut *clock, nanoseconds)
145+
.ok()
146+
.unwrap();
147+
}
148+
}
149+
150+
fn new(clock: Arc<Mutex<rcl_clock_t>>) -> Self {
151+
let source = Self { _rcl_clock: clock };
152+
source.set_ros_time_enable(true);
153+
source
154+
}
155+
156+
/// Sets the clock to use ROS Time, if enabled the clock will report the last value set through
157+
/// `Clock::set_ros_time_override(nanoseconds: i64)`.
158+
fn set_ros_time_enable(&self, enable: bool) {
159+
let mut clock = self._rcl_clock.lock().unwrap();
160+
if enable {
161+
// SAFETY: Safe if clock jump callbacks are not edited, which is guaranteed
162+
// by the mutex
163+
unsafe {
164+
// Function will only fail if timer was uninitialized or not RosTime, which should
165+
// not happen
166+
rcl_enable_ros_time_override(&mut *clock).ok().unwrap();
167+
}
168+
} else {
169+
// SAFETY: Safe if clock jump callbacks are not edited, which is guaranteed
170+
// by the mutex
171+
unsafe {
172+
// Function will only fail if timer was uninitialized or not RosTime, which should
173+
// not happen
174+
rcl_disable_ros_time_override(&mut *clock).ok().unwrap();
175+
}
176+
}
177+
}
178+
}
179+
180+
impl Drop for rcl_clock_t {
181+
fn drop(&mut self) {
182+
// SAFETY: No preconditions for this function
183+
let rc = unsafe { rcl_clock_fini(&mut *self) };
184+
if let Err(e) = to_rclrs_result(rc) {
185+
panic!("Unable to release Clock. {:?}", e)
186+
}
187+
}
188+
}
189+
190+
// SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread
191+
// they are running in. Therefore, this type can be safely sent to another thread.
192+
unsafe impl Send for rcl_clock_t {}
193+
194+
#[cfg(test)]
195+
mod tests {
196+
use super::*;
197+
198+
fn assert_send<T: Send>() {}
199+
fn assert_sync<T: Sync>() {}
200+
201+
#[test]
202+
fn clock_is_send_and_sync() {
203+
assert_send::<Clock>();
204+
assert_sync::<Clock>();
205+
}
206+
207+
#[test]
208+
fn clock_system_time_now() {
209+
let clock = Clock::system();
210+
assert!(clock.now().nsec > 0);
211+
}
212+
213+
#[test]
214+
fn clock_ros_time_with_override() {
215+
let (clock, source) = Clock::with_source();
216+
// No manual time set, it should default to 0
217+
assert!(clock.now().nsec == 0);
218+
let set_time = 1234i64;
219+
source.set_ros_time_override(set_time);
220+
// Ros time is set, should return the value that was set
221+
assert_eq!(clock.now().nsec, set_time);
222+
}
223+
}

rclrs/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
mod arguments;
99
mod client;
10+
mod clock;
1011
mod context;
1112
mod error;
1213
mod executor;
@@ -16,6 +17,8 @@ mod publisher;
1617
mod qos;
1718
mod service;
1819
mod subscription;
20+
mod time;
21+
mod time_source;
1922
mod vendor;
2023
mod wait;
2124

@@ -29,6 +32,7 @@ use std::time::Duration;
2932

3033
pub use arguments::*;
3134
pub use client::*;
35+
pub use clock::*;
3236
pub use context::*;
3337
pub use error::*;
3438
pub use executor::*;
@@ -39,6 +43,8 @@ pub use qos::*;
3943
pub use rcl_bindings::rmw_request_id_t;
4044
pub use service::*;
4145
pub use subscription::*;
46+
pub use time::*;
47+
use time_source::*;
4248
pub use wait::*;
4349

4450
/// Polls the node for new messages and executes the corresponding callbacks.
@@ -80,7 +86,7 @@ pub fn spin(node: Arc<Node>) -> Result<(), RclrsError> {
8086
/// # Ok::<(), RclrsError>(())
8187
/// ```
8288
pub fn create_node(context: &Context, node_name: &str) -> Result<Arc<Node>, RclrsError> {
83-
Ok(Arc::new(Node::builder(context, node_name).build()?))
89+
Node::new(context, node_name)
8490
}
8591

8692
/// Creates a [`NodeBuilder`][1].

rclrs/src/node.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ pub use self::builder::*;
1313
pub use self::graph::*;
1414
use crate::rcl_bindings::*;
1515
use crate::{
16-
Client, ClientBase, Context, GuardCondition, ParameterBuilder, ParameterInterface,
16+
Client, ClientBase, Clock, Context, GuardCondition, ParameterBuilder, ParameterInterface,
1717
ParameterVariant, Parameters, Publisher, QoSProfile, RclrsError, Service, ServiceBase,
18-
Subscription, SubscriptionBase, SubscriptionCallback, ToResult,
18+
Subscription, SubscriptionBase, SubscriptionCallback, TimeSource, ToResult,
1919
};
2020

2121
impl Drop for rcl_node_t {
@@ -71,6 +71,7 @@ pub struct Node {
7171
pub(crate) guard_conditions_mtx: Mutex<Vec<Weak<GuardCondition>>>,
7272
pub(crate) services_mtx: Mutex<Vec<Weak<dyn ServiceBase>>>,
7373
pub(crate) subscriptions_mtx: Mutex<Vec<Weak<dyn SubscriptionBase>>>,
74+
_time_source: TimeSource,
7475
_parameter: ParameterInterface,
7576
}
7677

@@ -95,10 +96,15 @@ impl Node {
9596
///
9697
/// See [`NodeBuilder::new()`] for documentation.
9798
#[allow(clippy::new_ret_no_self)]
98-
pub fn new(context: &Context, node_name: &str) -> Result<Node, RclrsError> {
99+
pub fn new(context: &Context, node_name: &str) -> Result<Arc<Node>, RclrsError> {
99100
Self::builder(context, node_name).build()
100101
}
101102

103+
/// Returns the clock associated with this node.
104+
pub fn get_clock(&self) -> Clock {
105+
self._time_source.get_clock()
106+
}
107+
102108
/// Returns the name of the node.
103109
///
104110
/// This returns the name after remapping, so it is not necessarily the same as the name that

0 commit comments

Comments
 (0)