Timer
This tutorial does not use colcon
to build.
We use only cargo
, which is a Rust's standard build system.
Don't forget loading ROS2's environment as follows. If you already done so, you do not need this.
$ . /opt/ros/iron/setup.bash
Wall-timer
A wall-timer is a timer which periodically invoked. This section describes how to use a wall-timer.
Let's create a package by using a cargo
as follows.
$ cargo new wall_timer
$ cd wall_timer
Then, add safe_drive
to the dependencies of Cargo.toml
.
[dependencies]
safe_drive = "0.4"
The following code is an example using a wall-timer.
The important method is Selector::add_wall_timer()
which takes
a name, a duration, and a callback function.
use safe_drive::{ context::Context, error::DynError, logger::Logger, msg::common_interfaces::std_msgs, pr_info, }; use std::{rc::Rc, time::Duration}; fn main() -> Result<(), DynError> { // Create a context, a node, a subscriber, a publisher, and a selector. let ctx = Context::new()?; let node = ctx.create_node("my_node", None, Default::default())?; let subscriber = node.create_subscriber::<std_msgs::msg::UInt64>("my_topic", None)?; let publisher = node.create_publisher::<std_msgs::msg::UInt64>("my_topic", None)?; let mut selector = ctx.create_selector()?; // Create a logger. // To share this by multiple callback functions, use Rc. let logger = Rc::new(Logger::new("wall timer example")); // Add a wall timer to publish periodically. let mut cnt = Box::new(0); let mut msg = std_msgs::msg::UInt64::new().unwrap(); let logger1 = logger.clone(); selector.add_wall_timer( "publisher", // the name of timer Duration::from_secs(1), Box::new(move || { msg.data = *cnt; *cnt += 1; publisher.send(&msg).unwrap(); pr_info!(logger1, "send: msg.data = {}", msg.data); }), ); // Add a subscriber. selector.add_subscriber( subscriber, Box::new(move |msg| { pr_info!(logger, "received: msg.data = {}", msg.data); }), ); // Spin. loop { selector.wait()?; } }
Timers can be set by a method of selector as follows,
and the timers will be invoked when calling the Selector::wait()
methods.
#![allow(unused)] fn main() { selector.add_wall_timer( "publisher", // the name of timer Duration::from_secs(1), Box::new(move || { msg.data = *cnt; *cnt += 1; publisher.send(&msg).unwrap(); pr_info!(logger1, "send: msg.data = {}", msg.data); }), ); }
"publisher"
is the name of this timer. The name is used for statistics. You can use any name.Duration::from_secs(1)
is the duration for periodic invoking. This argument means the callback function is invoked every 1 second.Box::new(move || ...)
is the callback function.
There is a publisher invoked by a timer, and a subscriber in this code. When executing this, transmission and reception will be confirmed as follows.
$ cargo run
[INFO] [1656557242.842509800] [wall timer example]: send: msg.data = 0
[INFO] [1656557242.842953300] [wall timer example]: received: msg.data = 0
[INFO] [1656557243.843103800] [wall timer example]: send: msg.data = 1
[INFO] [1656557243.843272900] [wall timer example]: received: msg.data = 1
[INFO] [1656557244.843574600] [wall timer example]: send: msg.data = 2
[INFO] [1656557244.844021200] [wall timer example]: received: msg.data = 2
[INFO] [1656557245.844349800] [wall timer example]: send: msg.data = 3
[INFO] [1656557245.844702900] [wall timer example]: received: msg.data = 3
One-shot Timer
A wall-timer is invoked periodically,
but one-shot timer is invoked only once.
A one-shot can be set by the Selector::add_timer()
method as follows.
use safe_drive::{context::Context, error::DynError, logger::Logger, pr_info}; use std::{cell::RefCell, collections::VecDeque, rc::Rc, time::Duration}; pub fn main() -> Result<(), DynError> { // Create a context, a publisher, and a logger. let ctx = Context::new()?; let mut selector = ctx.create_selector()?; let logger = Rc::new(Logger::new("one-shot timer example")); let queue = Rc::new(RefCell::new(VecDeque::new())); // Add a one-shot timer. let queue1 = queue.clone(); selector.add_timer( Duration::from_secs(2), Box::new(move || { pr_info!(logger, "fired!"); // Insert a timer to the queue. let mut q = queue1.borrow_mut(); let logger1 = logger.clone(); q.push_back(( Duration::from_secs(2), (Box::new(move || pr_info!(logger1, "fired! again!"))), )); }), ); // Spin. loop { { // Set timers. let mut q = queue.borrow_mut(); while let Some((dur, f)) = q.pop_front() { selector.add_timer(dur, f); } } selector.wait()?; } }
Selector::add_timer()
does not take the name,
but other arguments are same as Selector::add_wall_timer()
.
#![allow(unused)] fn main() { selector.add_timer( Duration::from_secs(2), Box::new(move || ...), ); }
Duration::from_secs(2)
is a duration indicating when the timer will be invoked.Box::new(move || ...)
is the callback function.
This code reenables a timer in the callback function.
To reenable, the callback takes a queue
and
timers in the queue
is reenabled in the spin as follows.
#![allow(unused)] fn main() { // Spin. loop { { // Set timers. let mut q = queue.borrow_mut(); while let Some((dur, f)) = q.pop_front() { selector.add_timer(dur, f); } } selector.wait()?; } }
The important thing is that the borrowed resources must be released.
To release definitely, the code fraction borrowing the queue
is surrounded by braces.
The following is a execution result of this code.
$ cargo run
[INFO] [1657070943.324438900] [one-shot timer example]: fired!
[INFO] [1657070945.324675600] [one-shot timer example]: fired! again!