Skip to main content

device_envoy_core/wasm/
clock.rs

1//! Browser wall-clock support for Device Envoy applications.
2//!
3//! ```rust,no_run
4//! use device_envoy_core::wasm::clock::ClockSyncWasm;
5//!
6//! let clock_sync = ClockSyncWasm::new();
7//! assert!(!clock_sync.control_is_visible());
8//! clock_sync.show();
9//! assert!(clock_sync.control_is_visible());
10//! ```
11
12use core::cell::Cell;
13use std::rc::Rc;
14
15use embassy_time::{Duration, Timer};
16use time::{OffsetDateTime, Time, UtcOffset};
17
18use crate::clock_sync::{ClockSync, ClockSyncTick, UnixSeconds};
19
20/// A [`ClockSync`] implementation backed by browser wall-clock time.
21/// See the compiled [`crate::wasm::clock`] example.
22pub struct ClockSyncWasm {
23    offset_minutes: Cell<i32>,
24    time_of_day: Rc<Cell<Option<u32>>>,
25    visible: Rc<Cell<bool>>,
26}
27
28impl ClockSyncWasm {
29    /// Construct a clock using the browser's current local UTC offset.
30    /// See the compiled [`crate::wasm::clock`] example.
31    pub fn new() -> Self {
32        Self::new_with_control_state(Rc::new(Cell::new(None)), Rc::new(Cell::new(false)))
33    }
34
35    pub(crate) fn new_with_control_state(
36        time_of_day: Rc<Cell<Option<u32>>>,
37        visible: Rc<Cell<bool>>,
38    ) -> Self {
39        Self {
40            offset_minutes: Cell::new(-(js_sys::Date::new_0().get_timezone_offset() as i32)),
41            time_of_day,
42            visible,
43        }
44    }
45
46    /// Request the shared browser shell to display the time control.
47    /// See the compiled [`crate::wasm::clock`] example.
48    pub fn show(&self) {
49        self.visible.set(true);
50    }
51
52    /// Return whether the shared browser shell should display the control.
53    /// See the compiled [`crate::wasm::clock`] example.
54    pub fn control_is_visible(&self) -> bool {
55        self.visible.get()
56    }
57
58    fn browser_local_time(&self) -> OffsetDateTime {
59        let unix_seconds = (js_sys::Date::now() / 1000.0) as i64;
60        let Ok(utc) = OffsetDateTime::from_unix_timestamp(unix_seconds) else {
61            return OffsetDateTime::UNIX_EPOCH;
62        };
63        let Ok(offset) = UtcOffset::from_whole_seconds(self.offset_minutes.get() * 60) else {
64            return utc;
65        };
66        let local = utc.to_offset(offset);
67        let Some(seconds_of_day) = self.time_of_day.get() else {
68            return local;
69        };
70        let hour = (seconds_of_day / 3600) as u8;
71        let minute = ((seconds_of_day % 3600) / 60) as u8;
72        let second = (seconds_of_day % 60) as u8;
73        let Ok(time) = Time::from_hms(hour, minute, second) else {
74            return local;
75        };
76        local.replace_time(time)
77    }
78}
79
80impl Default for ClockSyncWasm {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86#[cfg(feature = "wifi")]
87impl ClockSync for ClockSyncWasm {
88    async fn wait_for_tick(&self) -> ClockSyncTick {
89        Timer::after(Duration::from_secs(1)).await;
90        ClockSyncTick {
91            local_time: self.browser_local_time(),
92            since_last_sync: Duration::from_secs(0),
93        }
94    }
95
96    fn now_local(&self) -> OffsetDateTime {
97        self.browser_local_time()
98    }
99
100    fn set_offset_minutes(&self, minutes: i32) {
101        self.offset_minutes.set(minutes);
102    }
103
104    fn offset_minutes(&self) -> i32 {
105        self.offset_minutes.get()
106    }
107
108    fn set_tick_interval(&self, _interval: Option<Duration>) {}
109    fn set_speed(&self, _speed_multiplier: f32) {}
110    fn set_utc_time(&self, _unix_seconds: UnixSeconds) {}
111}
112
113#[cfg(not(feature = "wifi"))]
114impl ClockSync for ClockSyncWasm {
115    fn wait_for_tick(&self) -> impl core::future::Future<Output = ClockSyncTick> {
116        async {
117            Timer::after(Duration::from_secs(1)).await;
118            ClockSyncTick {
119                local_time: self.browser_local_time(),
120                since_last_sync: Duration::from_secs(0),
121            }
122        }
123    }
124
125    fn now_local(&self) -> OffsetDateTime {
126        self.browser_local_time()
127    }
128
129    fn set_offset_minutes(&self, minutes: i32) {
130        self.offset_minutes.set(minutes);
131    }
132
133    fn offset_minutes(&self) -> i32 {
134        self.offset_minutes.get()
135    }
136
137    fn set_tick_interval(&self, _interval: Option<Duration>) {}
138    fn set_speed(&self, _speed_multiplier: f32) {}
139    fn set_utc_time(&self, _unix_seconds: UnixSeconds) {}
140}