embedded-timers 0.4.0

Softwaretimers and -delays (ms/us) based on a Clock implementation
Documentation
// Copyright Open Logistics Foundation
//
// Licensed under the Open Logistics Foundation License 1.3.
// For details on the licensing terms, see the LICENSE file.
// SPDX-License-Identifier: OLFL-1.3

use crate::clock::Clock;
use crate::instant::Instant;

/// Errors which can occur when dealing with this module
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TimerError {
    /// A Timer has not been started
    NotRunning,
    /// The underlying Instant type has an overflow
    Overflow,
}

#[derive(Debug, Clone, Copy)]
struct Timing<I> {
    start_time: I,
    expiration_time: I,
}

/// An instance of a timer tied to a specific Clock and Timing Duration type.
#[derive(Debug)]
pub struct Timer<'a, CLOCK>
where
    CLOCK: Clock,
{
    /// The Clock this timer is working on
    clock: &'a CLOCK,
    /// The Timing information of this time. Only present when the timer is running
    timing: Option<Timing<CLOCK::Instant>>,
}

impl<'a, CLOCK> Timer<'a, CLOCK>
where
    CLOCK: Clock,
{
    /// Create a new Timer tied to a specific clock
    pub fn new(clock: &'a CLOCK) -> Self {
        Self {
            clock,
            timing: None,
        }
    }

    /// Get the Duration of the current timer, or Err if it is not running
    pub fn duration(&self) -> Result<core::time::Duration, TimerError> {
        if let Some(t) = &self.timing {
            Ok(t.expiration_time.duration_since(t.start_time))
        } else {
            Err(TimerError::NotRunning)
        }
    }

    /// If the timer is running, this is true. If not, it is false.
    /// It can also return an error from the Clock.
    pub fn is_running(&self) -> Result<bool, TimerError> {
        if self.timing.is_some() {
            self.is_expired().map(|expired| !expired)
        } else {
            Ok(false)
        }
    }

    /// If the timer is expired, this is true. If not, it is false.
    /// It can also return an error from the Clock.
    pub fn is_expired(&self) -> Result<bool, TimerError> {
        if let Some(timing) = &self.timing {
            let expired = self.clock.now() >= timing.expiration_time;
            Ok(expired)
        } else {
            Err(TimerError::NotRunning)
        }
    }

    /// Get the Duration until expire of the current timer, or Err if it is not running
    /// or if "now" could not be calculated
    pub fn duration_left(&self) -> Result<core::time::Duration, TimerError> {
        if let Some(t) = &self.timing {
            Ok(t.expiration_time.duration_since(self.clock.now()))
        } else {
            Err(TimerError::NotRunning)
        }
    }

    /// Start the timer. Can fail due to overflow in the Instant type
    pub fn try_start(&mut self, duration: core::time::Duration) -> Result<(), TimerError> {
        let start_time = self.clock.now();
        let expiration_time = start_time
            .checked_add(duration)
            .ok_or(TimerError::Overflow)?;

        self.timing = Some(Timing {
            start_time,
            expiration_time,
        });

        Ok(())
    }

    /// Wait for the timer. Fails if the timer is not running.
    pub fn try_wait(&mut self) -> nb::Result<(), TimerError> {
        if self.is_expired()? {
            self.timing = None;
            Ok(())
        } else {
            Err(nb::Error::WouldBlock)
        }
    }

    /// Start the timer. Can fail due to overflow in the Instant type
    ///
    /// __Note__: Panics on failure, use [try_start](Self::try_start) if you want to handle errors.
    pub fn start(&mut self, duration: core::time::Duration) {
        // As prescribed by the trait, we cannot return an error here, so we panic.
        self.try_start(duration).expect("Could not start timer!");
    }

    /// Wait for the timer. Fails if the timer is not running
    ///
    /// __Note__: Panics on failure or if the timer is not running, use [try_wait](Self::try_wait) if you want to handle errors.
    pub fn wait(&mut self) -> nb::Result<(), void::Void> {
        match self.try_wait() {
            Err(nb::Error::Other(_)) => {
                // panic if the timer is not running, as prescribed in this traits contract
                panic!("Error during wait for timer, probably not running!")
            }
            Ok(()) => Ok(()),
            Err(nb::Error::WouldBlock) => Err(nb::Error::WouldBlock),
        }
    }

    /// Cancel the timer. Return a [NotRunning](TimerError::NotRunning) error if the timer is not running
    pub fn cancel(&mut self) -> Result<(), TimerError> {
        if self.timing.is_some() && !self.is_expired()? {
            self.timing = None;
            Ok(())
        } else {
            self.timing = None;
            Err(TimerError::NotRunning)
        }
    }
}

#[cfg(test)]
mod tests {
    use mockall::predicate::*;
    use mockall::*;

    use crate::clock::Clock;
    use crate::instant::Instant32;

    mock!(
        MyClock {
        }

        impl Clock for MyClock {
            type Instant = Instant32<1000>;
            fn now(&self) -> Instant32<1000>;
        }

        impl core::fmt::Debug for MyClock {
            fn fmt<'a>(&self, f: &mut core::fmt::Formatter<'a>) -> Result<(), core::fmt::Error> {
                write!(f, "Clock Debug")
            }
        }
    );

    #[test]
    fn test_clock_mock() {
        let mut mymock = MockMyClock::new();

        mymock
            .expect_now()
            .once()
            .return_once(move || Instant32::new(23));
        assert!(mymock.now() == Instant32::new(23));
    }

    #[test]
    fn test_timer_basic() {
        let mut clock = MockMyClock::new();

        clock
            .expect_now()
            .times(5)
            .returning(move || Instant32::new(0));
        clock
            .expect_now()
            .times(2)
            .returning(move || Instant32::new(10));

        clock
            .expect_now()
            .times(5)
            .returning(|| Instant32::new(1000));
        clock
            .expect_now()
            .times(2)
            .returning(move || Instant32::new(2001));

        // Test Creation
        let mut timer: super::Timer<'_, _> = super::Timer::new(&clock);

        assert_eq!(timer.is_running(), Ok(false));
        assert_eq!(timer.duration(), Err(super::TimerError::NotRunning));
        assert_eq!(
            timer.try_wait(),
            Err(nb::Error::Other(super::TimerError::NotRunning))
        );

        // Test timer Start
        timer.start(core::time::Duration::from_millis(10));

        assert_eq!(timer.is_running(), Ok(true));
        assert_eq!(timer.duration(), Ok(core::time::Duration::from_millis(10)));
        assert_eq!(
            timer.duration_left(),
            Ok(core::time::Duration::from_millis(10))
        );

        assert_eq!(timer.is_expired(), Ok(false)); // First call should not be expired
        assert_eq!(timer.wait(), Err(nb::Error::WouldBlock)); // Same call but different api
        assert_eq!(timer.is_expired(), Ok(true)); // but Third call should be expired
        assert_eq!(timer.wait(), Ok(())); // Same call but different api
        assert_eq!(timer.is_running(), Ok(false)); // Timer should not be running anymore now

        // Restart the timer
        timer.start(core::time::Duration::from_secs(1));
        assert_eq!(timer.is_running(), Ok(true));
        assert_eq!(
            timer.duration(),
            Ok(core::time::Duration::from_millis(1000))
        );
        assert_eq!(
            timer.duration_left(),
            Ok(core::time::Duration::from_millis(1000))
        );

        assert_eq!(timer.is_expired(), Ok(false)); // First call should not be expired
        assert_eq!(timer.wait(), Err(nb::Error::WouldBlock)); // Same call but different api
        assert_eq!(timer.is_expired(), Ok(true)); // but Third call should be expired
        assert_eq!(timer.wait(), Ok(())); // Same call but different api
        assert_eq!(timer.is_running(), Ok(false)); // Timer should not be running anymore now
    }

    #[test]
    fn test_timer_cancel() {
        let mut clock = MockMyClock::new();
        clock
            .expect_now()
            .times(3)
            .returning(move || Instant32::new(0));

        let mut timer: super::Timer<'_, _> = super::Timer::new(&clock);

        assert_eq!(timer.cancel(), Err(super::TimerError::NotRunning));

        timer.start(core::time::Duration::from_millis(10));
        assert_eq!(timer.is_running(), Ok(true));
        assert_eq!(timer.cancel(), Ok(()));
        assert_eq!(timer.is_running(), Ok(false));
        assert_eq!(timer.is_expired(), Err(super::TimerError::NotRunning));
        assert_eq!(timer.cancel(), Err(super::TimerError::NotRunning));
        assert_eq!(timer.duration(), Err(super::TimerError::NotRunning));
        assert_eq!(timer.duration_left(), Err(super::TimerError::NotRunning));
    }
}