embedded_time/
timer.rs

1//! Software timers coupled to a [crate::Clock] implementation
2
3use crate::fraction::Fraction;
4use crate::{
5    duration::{self, *},
6    fixed_point::FixedPoint,
7    timer::param::*,
8    ConversionError, Instant, TimeError,
9};
10use core::{convert::TryFrom, marker::PhantomData, ops::Add, prelude::v1::*};
11
12/// Timer type/state parameters
13pub mod param {
14    /// Parameter not set
15    #[derive(Debug, Hash)]
16    pub struct None;
17
18    /// Timer is ready to start
19    #[derive(Debug, Hash)]
20    pub struct Armed;
21
22    /// Timer is running
23    #[derive(Debug, Hash)]
24    pub struct Running;
25
26    /// Timer will automatically restart when it expires
27    #[derive(Debug, Hash)]
28    pub struct Periodic;
29
30    /// Timer will stop when it expires
31    #[derive(Debug, Hash)]
32    pub struct OneShot;
33}
34
35/// A `Timer` counts toward an expiration, can be polled for elapsed and remaining time, and can be
36/// one-shot or continuous/periodic.
37#[derive(Debug, Hash)]
38pub struct Timer<'a, Type, State, Clock: crate::Clock, Dur: Duration> {
39    clock: &'a Clock,
40    duration: Dur,
41    expiration: Instant<Clock>,
42    _type: PhantomData<Type>,
43    _state: PhantomData<State>,
44}
45
46impl<'a, Clock: crate::Clock, Dur: Duration> Timer<'_, param::None, param::None, Clock, Dur> {
47    /// Construct a new, `OneShot` `Timer`
48    #[allow(clippy::new_ret_no_self)]
49    pub fn new(clock: &Clock, duration: Dur) -> Timer<OneShot, Armed, Clock, Dur> {
50        Timer::<OneShot, Armed, Clock, Dur> {
51            clock,
52            duration,
53            expiration: Instant::new(Clock::T::from(0)),
54            _type: PhantomData,
55            _state: PhantomData,
56        }
57    }
58}
59
60impl<'a, Type, State, Clock: crate::Clock, Dur: Duration> Timer<'a, Type, State, Clock, Dur> {
61    /// Change timer type to one-shot
62    pub fn into_oneshot(self) -> Timer<'a, OneShot, State, Clock, Dur> {
63        Timer::<OneShot, State, Clock, Dur> {
64            clock: self.clock,
65            duration: self.duration,
66            expiration: self.expiration,
67            _type: PhantomData,
68            _state: PhantomData,
69        }
70    }
71
72    /// Change timer type into periodic
73    pub fn into_periodic(self) -> Timer<'a, Periodic, State, Clock, Dur> {
74        Timer::<Periodic, State, Clock, Dur> {
75            clock: self.clock,
76            duration: self.duration,
77            expiration: self.expiration,
78            _type: PhantomData,
79            _state: PhantomData,
80        }
81    }
82}
83
84impl<'a, Type, Clock: crate::Clock, Dur: Duration> Timer<'a, Type, Armed, Clock, Dur> {
85    /// Start the timer from this instant
86    pub fn start(self) -> Result<Timer<'a, Type, Running, Clock, Dur>, TimeError>
87    where
88        Clock::T: TryFrom<Dur::T>,
89        Dur: FixedPoint,
90    {
91        Ok(Timer::<Type, Running, Clock, Dur> {
92            clock: self.clock,
93            duration: self.duration,
94            expiration: self
95                .clock
96                .try_now()?
97                .checked_add(self.duration)
98                .ok_or(ConversionError::Overflow)?,
99            _type: PhantomData,
100            _state: PhantomData,
101        })
102    }
103}
104
105impl<Type, Clock: crate::Clock, Dur: Duration> Timer<'_, Type, Running, Clock, Dur> {
106    fn _is_expired(&self) -> Result<bool, TimeError> {
107        Ok(self.clock.try_now()? >= self.expiration)
108    }
109
110    /// Returns the [`Duration`] of time elapsed since it was started
111    ///
112    /// **The duration is truncated, not rounded**.
113    ///
114    /// The units of the [`Duration`] are the same as that used to construct the `Timer`.
115    pub fn elapsed(&self) -> Result<Dur, TimeError>
116    where
117        Dur: FixedPoint + TryFrom<duration::Generic<Clock::T>, Error = ConversionError>,
118        Dur::T: TryFrom<Clock::T>,
119        Clock::T: TryFrom<Dur::T>,
120    {
121        let generic_duration = self
122            .clock
123            .try_now()?
124            .checked_duration_since(
125                &(self
126                    .expiration
127                    .checked_sub(self.duration)
128                    .ok_or(ConversionError::Overflow)?),
129            )
130            .ok_or(TimeError::Overflow)?;
131
132        Ok(Dur::try_from(generic_duration)?)
133    }
134
135    /// Returns the [`Duration`] until the expiration of the timer
136    ///
137    /// **The duration is truncated, not rounded**.
138    ///
139    /// The units of the [`Duration`] are the same as that used to construct the `Timer`.
140    pub fn remaining(&self) -> Result<Dur, TimeError>
141    where
142        Dur: FixedPoint + TryFrom<duration::Generic<Clock::T>, Error = ConversionError>,
143        Dur::T: TryFrom<u32> + TryFrom<Clock::T>,
144        Clock::T: TryFrom<Dur::T>,
145    {
146        let result = self
147            .expiration
148            .checked_duration_since(&self.clock.try_now()?)
149            .or_else(|| {
150                Some(duration::Generic::<Clock::T>::new(
151                    0.into(),
152                    Fraction::default(),
153                ))
154            })
155            .ok_or(TimeError::NegDuration)?;
156
157        Ok(Dur::try_from(result)?)
158    }
159}
160
161impl<'a, Clock: crate::Clock, Dur: Duration> Timer<'a, OneShot, Running, Clock, Dur> {
162    /// Block until the timer has expired
163    pub fn wait(self) -> Result<Timer<'a, OneShot, Armed, Clock, Dur>, TimeError> {
164        // since the timer is running, _is_expired() will return a value
165        while !self._is_expired()? {}
166
167        Ok(Timer::<param::None, param::None, Clock, Dur>::new(
168            self.clock,
169            self.duration,
170        ))
171    }
172
173    /// Check whether the timer has expired
174    ///
175    /// The timer is not restarted
176    pub fn is_expired(&self) -> Result<bool, TimeError> {
177        self._is_expired()
178    }
179}
180
181impl<Clock: crate::Clock, Dur: Duration> Timer<'_, Periodic, Running, Clock, Dur> {
182    /// Block until the timer has expired
183    ///
184    /// The timer is restarted
185    pub fn wait(self) -> Result<Self, TimeError>
186    where
187        Instant<Clock>: Add<Dur, Output = Instant<Clock>>,
188    {
189        // since the timer is running, _is_expired() will return a value
190        while !self._is_expired()? {}
191
192        Ok(Self {
193            clock: self.clock,
194            duration: self.duration,
195            // The `+` will never panic since this duration has already applied to the same
196            // `Instant` type without a problem
197            expiration: self.expiration + self.duration,
198            _type: PhantomData,
199            _state: PhantomData,
200        })
201    }
202
203    /// Check whether a _periodic_ timer has elapsed
204    ///
205    /// The timer is restarted if it has elapsed.
206    pub fn period_complete(&mut self) -> Result<bool, TimeError>
207    where
208        Instant<Clock>: Add<Dur, Output = Instant<Clock>>,
209    {
210        // since the timer is running, _is_expired() will return a value
211        if self._is_expired()? {
212            // The `+` will never panic since this duration has already applied to the same
213            // `Instant` type without a problem
214            self.expiration = self.expiration + self.duration;
215
216            Ok(true)
217        } else {
218            Ok(false)
219        }
220    }
221}
222
223#[cfg(test)]
224mod test {}