Skip to content

Add ROS2 timers #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions rclrs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dyn_msg = ["ament_rs", "libloading"]
# This feature is solely for the purpose of being able to generate documetation without a ROS installation
# The only intended usage of this feature is for docs.rs builders to work, and is not intended to be used by end users
generate_docs = ["rosidl_runtime_rs/generate_docs"]
serde = []

[package.metadata.docs.rs]
features = ["generate_docs"]
2 changes: 1 addition & 1 deletion rclrs/src/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl From<ClockType> for rcl_clock_type_t {
#[derive(Clone, Debug)]
pub struct Clock {
kind: ClockType,
rcl_clock: Arc<Mutex<rcl_clock_t>>,
pub(crate) rcl_clock: Arc<Mutex<rcl_clock_t>>,
// TODO(luca) Implement jump callbacks
}

Expand Down
29 changes: 29 additions & 0 deletions rclrs/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ impl SingleThreadedExecutor {
for ready_service in ready_entities.services {
ready_service.execute()?;
}

for ready_timer in ready_entities.timers {
ready_timer.lock().unwrap().execute()?;
}
}

Ok(())
Expand All @@ -82,3 +86,28 @@ impl SingleThreadedExecutor {
Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::{spin_once, Context};

use super::*;

#[test]
fn spin_once_fires_timer() -> Result<(), RclrsError> {
let context = Context::new([])?;
let node = Node::new(&context, "test_spin_timer")?;

let callback_triggered = Arc::new(Mutex::new(0));
let callback_flag = Arc::clone(&callback_triggered);

let _timer = node.create_timer(Duration::from_secs(0), move |_| {
*callback_flag.lock().unwrap() += 1;
})?;

spin_once(node, Some(Duration::ZERO))?;

assert_eq!(*callback_triggered.lock().unwrap(), 1);
Ok(())
}
}
2 changes: 2 additions & 0 deletions rclrs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod service;
mod subscription;
mod time;
mod time_source;
mod timer;
mod vendor;
mod wait;

Expand Down Expand Up @@ -49,6 +50,7 @@ pub use service::*;
pub use subscription::*;
pub use time::*;
use time_source::*;
pub use timer::*;
pub use wait::*;

/// Polls the node for new messages and executes the corresponding callbacks.
Expand Down
6 changes: 6 additions & 0 deletions rclrs/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ pub struct LogConditions {
pub log_if_true: bool,
}

impl Default for LogConditions {
fn default() -> Self {
Self::new()
}
}

impl LogConditions {
/// Default construct an instance
pub fn new() -> Self {
Expand Down
44 changes: 42 additions & 2 deletions rclrs/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{
fmt,
os::raw::c_char,
sync::{atomic::AtomicBool, Arc, Mutex, Weak},
time::Duration,
vec::Vec,
};

Expand All @@ -16,7 +17,7 @@ use crate::{
rcl_bindings::*, Client, ClientBase, Clock, Context, ContextHandle, GuardCondition,
ParameterBuilder, ParameterInterface, ParameterVariant, Parameters, Publisher, QoSProfile,
RclrsError, Service, ServiceBase, Subscription, SubscriptionBase, SubscriptionCallback,
TimeSource, ENTITY_LIFECYCLE_MUTEX,
TimeSource, Timer, TimerBase, TimerCallback, ENTITY_LIFECYCLE_MUTEX,
};

// SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread
Expand Down Expand Up @@ -47,7 +48,7 @@ unsafe impl Send for rcl_node_t {}
/// The namespace and name given when creating the node can be overridden through the command line.
/// In that sense, the parameters to the node creation functions are only the _default_ namespace and
/// name.
/// See also the [official tutorial][1] on the command line arguments for ROS nodes, and the
/// See also the [official tutorial][2] on the command line arguments for ROS nodes, and the
/// [`Node::namespace()`] and [`Node::name()`] functions for examples.
///
/// ## Rules for valid names
Expand All @@ -63,6 +64,7 @@ pub struct Node {
pub(crate) guard_conditions_mtx: Mutex<Vec<Weak<GuardCondition>>>,
pub(crate) services_mtx: Mutex<Vec<Weak<dyn ServiceBase>>>,
pub(crate) subscriptions_mtx: Mutex<Vec<Weak<dyn SubscriptionBase>>>,
pub(crate) timers_mtx: Mutex<Vec<Weak<Mutex<dyn TimerBase>>>>,
time_source: TimeSource,
parameter: ParameterInterface,
pub(crate) handle: Arc<NodeHandle>,
Expand Down Expand Up @@ -339,6 +341,35 @@ impl Node {
Ok(subscription)
}

/// Create a [`Timer`][1] that will use the node's clock.
///
/// A Timer may be modified via the `Arc` returned by this function or from
/// within its callback.
/// A weak reference counter to the `Timer` is stored within this node.
///
/// [1]: crate::Timer
pub fn create_timer<F>(
&self,
period: Duration,
callback: F,
) -> Result<Arc<Mutex<Timer>>, RclrsError>
where
F: TimerCallback + 'static,
{
let timer = Arc::new(Mutex::new(Timer::new_with_context_handle(
Arc::clone(&self.handle.context_handle),
self.get_clock(),
period,
callback,
)?));

self.timers_mtx
.lock()
.unwrap()
.push(Arc::downgrade(&timer) as Weak<Mutex<dyn TimerBase>>);
Ok(timer)
}

/// Returns the subscriptions that have not been dropped yet.
pub(crate) fn live_subscriptions(&self) -> Vec<Arc<dyn SubscriptionBase>> {
{ self.subscriptions_mtx.lock().unwrap() }
Expand Down Expand Up @@ -368,6 +399,15 @@ impl Node {
.collect()
}

pub(crate) fn live_timers(&self) -> Vec<Arc<Mutex<dyn TimerBase>>> {
self.timers_mtx
.lock()
.unwrap()
.iter()
.filter_map(Weak::upgrade)
.collect()
}

/// Returns the ROS domain ID that the node is using.
///
/// The domain ID controls which nodes can send messages to each other, see the [ROS 2 concept article][1].
Expand Down
1 change: 1 addition & 0 deletions rclrs/src/node/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ impl NodeBuilder {
guard_conditions_mtx: Mutex::new(vec![]),
services_mtx: Mutex::new(vec![]),
subscriptions_mtx: Mutex::new(vec![]),
timers_mtx: Mutex::new(vec![]),
time_source: TimeSource::builder(self.clock_type)
.clock_qos(self.clock_qos)
.build(),
Expand Down
2 changes: 1 addition & 1 deletion rclrs/src/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ where
// * The rcl_node is kept alive by the NodeHandle because it is a dependency of the publisher.
// * The topic name and the options are copied by this function, so they can be dropped afterwards.
// * The entity lifecycle mutex is locked to protect against the risk of global
// variables in the rmw implementation being unsafely modified during cleanup.
// variables in the rmw implementation being unsafely modified during initialization.
rcl_publisher_init(
&mut rcl_publisher,
&*rcl_node,
Expand Down
2 changes: 1 addition & 1 deletion rclrs/src/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ where
// * The rcl_node is kept alive by the NodeHandle because it is a dependency of the subscription.
// * The topic name and the options are copied by this function, so they can be dropped afterwards.
// * The entity lifecycle mutex is locked to protect against the risk of global
// variables in the rmw implementation being unsafely modified during cleanup.
// variables in the rmw implementation being unsafely modified during initialization.
rcl_subscription_init(
&mut rcl_subscription,
&*rcl_node,
Expand Down
Loading
Loading