veecle_osal_embassy/
time.rs

1//! Time related system utilities.
2
3use core::sync::atomic::{AtomicU32, Ordering};
4use veecle_osal_api::Error;
5pub use veecle_osal_api::time::{
6    Duration, Instant, Interval, SystemTime, SystemTimeError, SystemTimeSync, TimeAbstraction,
7};
8
9/// Implements the [`TimeAbstraction`] trait for embassy.
10///
11/// ## Details
12///
13/// - Before using [`Self::duration_since_epoch`], you are expected to synchronize system time via
14///   [`Self::set_system_time`].
15/// - Maximum precision of system time synchronization is limited to seconds.
16#[derive(Debug)]
17pub struct Time;
18
19impl TimeAbstraction for Time {
20    fn now() -> Instant {
21        Instant::MIN + Duration::from_millis(embassy_time::Instant::now().as_millis())
22    }
23
24    async fn sleep_until(deadline: Instant) -> Result<(), Error> {
25        Self::sleep(
26            deadline
27                .duration_since(Self::now())
28                .unwrap_or(Duration::ZERO),
29        )
30        .await
31    }
32
33    async fn sleep(duration: Duration) -> Result<(), Error> {
34        // `embassy_time::Timer::after` panics on overflow, so we reduce the duration until it doesn't overflow.
35        // This does only reduce the chance of overflow and thus a panic.
36        // We cannot prevent all situations in which an overflow might occur.
37        let mut duration = duration;
38        while Self::now().checked_add(duration).is_none() {
39            duration = duration / 2;
40        }
41        embassy_time::Timer::after(embassy_time::Duration::from_millis(duration.as_millis())).await;
42        Ok(())
43    }
44
45    fn interval(period: Duration) -> impl Interval
46    where
47        Self: Sized,
48    {
49        struct IntervalInternal {
50            ticker: embassy_time::Ticker,
51            first_poll: bool,
52        }
53
54        impl Interval for IntervalInternal {
55            async fn tick(&mut self) -> Result<(), Error> {
56                // Embassy's ticker doesn't immediately yield an item, so we need to do that to conform to the trait contract.
57                if self.first_poll {
58                    self.first_poll = false;
59                    return Ok(());
60                }
61
62                self.ticker.next().await;
63                Ok(())
64            }
65        }
66
67        IntervalInternal {
68            ticker: embassy_time::Ticker::every(embassy_time::Duration::from_millis(
69                period.as_millis(),
70            )),
71            first_poll: true,
72        }
73    }
74}
75
76static SYSTEM_TIME_OFFSET_SECONDS: AtomicU32 = AtomicU32::new(0);
77
78impl SystemTime for Time {
79    fn duration_since_epoch() -> Result<Duration, SystemTimeError> {
80        let offset = SYSTEM_TIME_OFFSET_SECONDS.load(Ordering::Relaxed);
81        if offset == 0 {
82            Err(SystemTimeError::Unsynchronized)
83        } else {
84            let duration_since_start = Self::now()
85                .duration_since(Instant::MIN)
86                .expect("now can't be less than min time");
87            Ok(duration_since_start + Duration::from_secs(offset as u64))
88        }
89    }
90}
91
92impl SystemTimeSync for Time {
93    fn set_system_time(duration_since_epoch: Duration) -> Result<(), SystemTimeError> {
94        let duration_since_start = Self::now()
95            .duration_since(Instant::MIN)
96            .expect("now can't be less than min time");
97
98        if duration_since_epoch < duration_since_start {
99            Err(SystemTimeError::EpochIsLaterThanStartTime)
100        } else {
101            SYSTEM_TIME_OFFSET_SECONDS.store(
102                (duration_since_epoch - duration_since_start).as_secs() as u32,
103                Ordering::Relaxed,
104            );
105            Ok(())
106        }
107    }
108}