embedded_timers/
timer.rs

1// Copyright Open Logistics Foundation
2//
3// Licensed under the Open Logistics Foundation License 1.3.
4// For details on the licensing terms, see the LICENSE file.
5// SPDX-License-Identifier: OLFL-1.3
6
7use crate::clock::Clock;
8use crate::instant::Instant;
9
10/// Errors which can occur when dealing with this module
11#[derive(Debug, PartialEq, Eq, Clone, Copy)]
12pub enum TimerError {
13    /// A Timer has not been started
14    NotRunning,
15    /// The underlying Instant type has an overflow
16    Overflow,
17}
18
19#[derive(Debug, Clone, Copy)]
20struct Timing<I> {
21    start_time: I,
22    expiration_time: I,
23}
24
25/// An instance of a timer tied to a specific Clock and Timing Duration type.
26#[derive(Debug)]
27pub struct Timer<'a, CLOCK>
28where
29    CLOCK: Clock,
30{
31    /// The Clock this timer is working on
32    clock: &'a CLOCK,
33    /// The Timing information of this time. Only present when the timer is running
34    timing: Option<Timing<CLOCK::Instant>>,
35}
36
37impl<'a, CLOCK> Timer<'a, CLOCK>
38where
39    CLOCK: Clock,
40{
41    /// Create a new Timer tied to a specific clock
42    pub fn new(clock: &'a CLOCK) -> Self {
43        Self {
44            clock,
45            timing: None,
46        }
47    }
48
49    /// Get the Duration of the current timer, or Err if it is not running
50    pub fn duration(&self) -> Result<core::time::Duration, TimerError> {
51        if let Some(t) = &self.timing {
52            Ok(t.expiration_time.duration_since(t.start_time))
53        } else {
54            Err(TimerError::NotRunning)
55        }
56    }
57
58    /// If the timer is running, this is true. If not, it is false.
59    /// It can also return an error from the Clock.
60    pub fn is_running(&self) -> Result<bool, TimerError> {
61        if self.timing.is_some() {
62            self.is_expired().map(|expired| !expired)
63        } else {
64            Ok(false)
65        }
66    }
67
68    /// If the timer is expired, this is true. If not, it is false.
69    /// It can also return an error from the Clock.
70    pub fn is_expired(&self) -> Result<bool, TimerError> {
71        if let Some(timing) = &self.timing {
72            let expired = self.clock.now() >= timing.expiration_time;
73            Ok(expired)
74        } else {
75            Err(TimerError::NotRunning)
76        }
77    }
78
79    /// Get the Duration until expire of the current timer, or Err if it is not running
80    /// or if "now" could not be calculated
81    pub fn duration_left(&self) -> Result<core::time::Duration, TimerError> {
82        if let Some(t) = &self.timing {
83            Ok(t.expiration_time.duration_since(self.clock.now()))
84        } else {
85            Err(TimerError::NotRunning)
86        }
87    }
88
89    /// Start the timer. Can fail due to overflow in the Instant type
90    pub fn try_start(&mut self, duration: core::time::Duration) -> Result<(), TimerError> {
91        let start_time = self.clock.now();
92        let expiration_time = start_time
93            .checked_add(duration)
94            .ok_or(TimerError::Overflow)?;
95
96        self.timing = Some(Timing {
97            start_time,
98            expiration_time,
99        });
100
101        Ok(())
102    }
103
104    /// Wait for the timer. Fails if the timer is not running.
105    pub fn try_wait(&mut self) -> nb::Result<(), TimerError> {
106        if self.is_expired()? {
107            self.timing = None;
108            Ok(())
109        } else {
110            Err(nb::Error::WouldBlock)
111        }
112    }
113
114    /// Start the timer. Can fail due to overflow in the Instant type
115    ///
116    /// __Note__: Panics on failure, use [try_start](Self::try_start) if you want to handle errors.
117    pub fn start(&mut self, duration: core::time::Duration) {
118        // As prescribed by the trait, we cannot return an error here, so we panic.
119        self.try_start(duration).expect("Could not start timer!");
120    }
121
122    /// Wait for the timer. Fails if the timer is not running
123    ///
124    /// __Note__: Panics on failure or if the timer is not running, use [try_wait](Self::try_wait) if you want to handle errors.
125    pub fn wait(&mut self) -> nb::Result<(), void::Void> {
126        match self.try_wait() {
127            Err(nb::Error::Other(_)) => {
128                // panic if the timer is not running, as prescribed in this traits contract
129                panic!("Error during wait for timer, probably not running!")
130            }
131            Ok(()) => Ok(()),
132            Err(nb::Error::WouldBlock) => Err(nb::Error::WouldBlock),
133        }
134    }
135
136    /// Cancel the timer. Return a [NotRunning](TimerError::NotRunning) error if the timer is not running
137    pub fn cancel(&mut self) -> Result<(), TimerError> {
138        if self.timing.is_some() && !self.is_expired()? {
139            self.timing = None;
140            Ok(())
141        } else {
142            self.timing = None;
143            Err(TimerError::NotRunning)
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use mockall::predicate::*;
151    use mockall::*;
152
153    use crate::clock::Clock;
154    use crate::instant::Instant32;
155
156    mock!(
157        MyClock {
158        }
159
160        impl Clock for MyClock {
161            type Instant = Instant32<1000>;
162            fn now(&self) -> Instant32<1000>;
163        }
164
165        impl core::fmt::Debug for MyClock {
166            fn fmt<'a>(&self, f: &mut core::fmt::Formatter<'a>) -> Result<(), core::fmt::Error> {
167                write!(f, "Clock Debug")
168            }
169        }
170    );
171
172    #[test]
173    fn test_clock_mock() {
174        let mut mymock = MockMyClock::new();
175
176        mymock
177            .expect_now()
178            .once()
179            .return_once(move || Instant32::new(23));
180        assert!(mymock.now() == Instant32::new(23));
181    }
182
183    #[test]
184    fn test_timer_basic() {
185        let mut clock = MockMyClock::new();
186
187        clock
188            .expect_now()
189            .times(5)
190            .returning(move || Instant32::new(0));
191        clock
192            .expect_now()
193            .times(2)
194            .returning(move || Instant32::new(10));
195
196        clock
197            .expect_now()
198            .times(5)
199            .returning(|| Instant32::new(1000));
200        clock
201            .expect_now()
202            .times(2)
203            .returning(move || Instant32::new(2001));
204
205        // Test Creation
206        let mut timer: super::Timer<'_, _> = super::Timer::new(&clock);
207
208        assert_eq!(timer.is_running(), Ok(false));
209        assert_eq!(timer.duration(), Err(super::TimerError::NotRunning));
210        assert_eq!(
211            timer.try_wait(),
212            Err(nb::Error::Other(super::TimerError::NotRunning))
213        );
214
215        // Test timer Start
216        timer.start(core::time::Duration::from_millis(10));
217
218        assert_eq!(timer.is_running(), Ok(true));
219        assert_eq!(timer.duration(), Ok(core::time::Duration::from_millis(10)));
220        assert_eq!(
221            timer.duration_left(),
222            Ok(core::time::Duration::from_millis(10))
223        );
224
225        assert_eq!(timer.is_expired(), Ok(false)); // First call should not be expired
226        assert_eq!(timer.wait(), Err(nb::Error::WouldBlock)); // Same call but different api
227        assert_eq!(timer.is_expired(), Ok(true)); // but Third call should be expired
228        assert_eq!(timer.wait(), Ok(())); // Same call but different api
229        assert_eq!(timer.is_running(), Ok(false)); // Timer should not be running anymore now
230
231        // Restart the timer
232        timer.start(core::time::Duration::from_secs(1));
233        assert_eq!(timer.is_running(), Ok(true));
234        assert_eq!(
235            timer.duration(),
236            Ok(core::time::Duration::from_millis(1000))
237        );
238        assert_eq!(
239            timer.duration_left(),
240            Ok(core::time::Duration::from_millis(1000))
241        );
242
243        assert_eq!(timer.is_expired(), Ok(false)); // First call should not be expired
244        assert_eq!(timer.wait(), Err(nb::Error::WouldBlock)); // Same call but different api
245        assert_eq!(timer.is_expired(), Ok(true)); // but Third call should be expired
246        assert_eq!(timer.wait(), Ok(())); // Same call but different api
247        assert_eq!(timer.is_running(), Ok(false)); // Timer should not be running anymore now
248    }
249
250    #[test]
251    fn test_timer_cancel() {
252        let mut clock = MockMyClock::new();
253        clock
254            .expect_now()
255            .times(3)
256            .returning(move || Instant32::new(0));
257
258        let mut timer: super::Timer<'_, _> = super::Timer::new(&clock);
259
260        assert_eq!(timer.cancel(), Err(super::TimerError::NotRunning));
261
262        timer.start(core::time::Duration::from_millis(10));
263        assert_eq!(timer.is_running(), Ok(true));
264        assert_eq!(timer.cancel(), Ok(()));
265        assert_eq!(timer.is_running(), Ok(false));
266        assert_eq!(timer.is_expired(), Err(super::TimerError::NotRunning));
267        assert_eq!(timer.cancel(), Err(super::TimerError::NotRunning));
268        assert_eq!(timer.duration(), Err(super::TimerError::NotRunning));
269        assert_eq!(timer.duration_left(), Err(super::TimerError::NotRunning));
270    }
271}