1use core::cmp::min;
4use core::marker::PhantomData;
5use core::time::Duration;
6
7use ::log::*;
8
9use crate::private::cstr::to_cstring_arg;
10use crate::private::cstr::CString;
11use crate::private::mutex;
12
13#[cfg(feature = "alloc")]
14extern crate alloc;
15
16#[cfg(not(any(
17 esp_idf_version_major = "4",
18 all(esp_idf_version_major = "5", esp_idf_version_minor = "0"),
19)))] mod esp_sntp {
21 use super::OperatingMode;
22 pub use crate::sys::*;
23
24 impl From<esp_sntp_operatingmode_t> for OperatingMode {
25 #[allow(non_upper_case_globals)]
26 fn from(from: esp_sntp_operatingmode_t) -> Self {
27 match from {
28 esp_sntp_operatingmode_t_ESP_SNTP_OPMODE_POLL => OperatingMode::Poll,
29 esp_sntp_operatingmode_t_ESP_SNTP_OPMODE_LISTENONLY => OperatingMode::ListenOnly,
30 _ => unreachable!(),
31 }
32 }
33 }
34
35 impl From<OperatingMode> for esp_sntp_operatingmode_t {
36 #[allow(non_upper_case_globals)]
37 fn from(from: OperatingMode) -> Self {
38 match from {
39 OperatingMode::Poll => esp_sntp_operatingmode_t_ESP_SNTP_OPMODE_POLL,
40 OperatingMode::ListenOnly => esp_sntp_operatingmode_t_ESP_SNTP_OPMODE_LISTENONLY,
41 }
42 }
43 }
44
45 pub use esp_sntp_init as sntp_init;
46 pub use esp_sntp_setoperatingmode as sntp_setoperatingmode;
47 pub use esp_sntp_setservername as sntp_setservername;
48 pub use esp_sntp_stop as sntp_stop;
49}
50
51#[cfg(any(
52 esp_idf_version_major = "4",
53 all(esp_idf_version_major = "5", esp_idf_version_minor = "0"),
54))] mod esp_sntp {
56 use super::OperatingMode;
57 pub use crate::sys::*;
58
59 impl From<u8_t> for OperatingMode {
60 fn from(from: u8_t) -> Self {
61 match from as u32 {
62 SNTP_OPMODE_POLL => OperatingMode::Poll,
63 SNTP_OPMODE_LISTENONLY => OperatingMode::ListenOnly,
64 _ => unreachable!(),
65 }
66 }
67 }
68
69 impl From<OperatingMode> for u8_t {
70 fn from(from: OperatingMode) -> Self {
71 match from {
72 OperatingMode::Poll => SNTP_OPMODE_POLL as u8_t,
73 OperatingMode::ListenOnly => SNTP_OPMODE_LISTENONLY as u8_t,
74 }
75 }
76 }
77}
78
79use esp_sntp::*;
80
81const SNTP_SERVER_NUM: usize = SNTP_MAX_SERVERS as usize;
82
83const DEFAULT_SERVERS: [&str; 4] = [
84 "0.pool.ntp.org",
85 "1.pool.ntp.org",
86 "2.pool.ntp.org",
87 "3.pool.ntp.org",
88];
89
90#[derive(Copy, Clone, Debug, PartialEq, Eq)]
91#[cfg_attr(feature = "std", derive(Hash))]
92pub enum OperatingMode {
93 Poll,
94 ListenOnly,
95}
96
97#[derive(Copy, Clone, Debug, PartialEq, Eq)]
98#[cfg_attr(feature = "std", derive(Hash))]
99pub enum SyncMode {
100 Smooth,
101 Immediate,
102}
103
104impl From<sntp_sync_mode_t> for SyncMode {
105 #[allow(non_upper_case_globals)]
106 fn from(from: sntp_sync_mode_t) -> Self {
107 match from {
108 sntp_sync_mode_t_SNTP_SYNC_MODE_SMOOTH => SyncMode::Smooth,
109 sntp_sync_mode_t_SNTP_SYNC_MODE_IMMED => SyncMode::Immediate,
110 _ => unreachable!(),
111 }
112 }
113}
114
115impl From<SyncMode> for sntp_sync_mode_t {
116 fn from(from: SyncMode) -> Self {
117 match from {
118 SyncMode::Smooth => sntp_sync_mode_t_SNTP_SYNC_MODE_SMOOTH,
119 SyncMode::Immediate => sntp_sync_mode_t_SNTP_SYNC_MODE_IMMED,
120 }
121 }
122}
123
124#[derive(Copy, Clone, Debug, PartialEq, Eq)]
125#[cfg_attr(feature = "std", derive(Hash))]
126pub enum SyncStatus {
127 Reset,
128 Completed,
129 InProgress,
130}
131
132impl From<sntp_sync_status_t> for SyncStatus {
133 #[allow(non_upper_case_globals)]
134 fn from(from: sntp_sync_status_t) -> Self {
135 match from {
136 sntp_sync_status_t_SNTP_SYNC_STATUS_RESET => SyncStatus::Reset,
137 sntp_sync_status_t_SNTP_SYNC_STATUS_COMPLETED => SyncStatus::Completed,
138 sntp_sync_status_t_SNTP_SYNC_STATUS_IN_PROGRESS => SyncStatus::InProgress,
139 _ => unreachable!(),
140 }
141 }
142}
143
144pub struct SntpConf<'a> {
145 pub servers: [&'a str; SNTP_SERVER_NUM],
146 pub operating_mode: OperatingMode,
147 pub sync_mode: SyncMode,
148}
149
150impl Default for SntpConf<'_> {
151 fn default() -> Self {
152 let mut servers: [&str; SNTP_SERVER_NUM] = Default::default();
153 let copy_len = min(servers.len(), DEFAULT_SERVERS.len());
154
155 servers[..copy_len].copy_from_slice(&DEFAULT_SERVERS[..copy_len]);
156
157 Self {
158 servers,
159 operating_mode: OperatingMode::Poll,
160 sync_mode: SyncMode::Immediate,
161 }
162 }
163}
164
165#[cfg(feature = "alloc")]
166type SyncCallback = alloc::boxed::Box<dyn FnMut(Duration) + Send + 'static>;
167#[cfg(feature = "alloc")]
168static SYNC_CB: mutex::Mutex<Option<SyncCallback>> = mutex::Mutex::new(None);
169static TAKEN: mutex::Mutex<bool> = mutex::Mutex::new(false);
170
171pub struct EspSntp<'a> {
172 _sntp_servers: [CString; SNTP_SERVER_NUM],
174 _ref: PhantomData<&'a ()>,
175}
176
177impl EspSntp<'static> {
178 pub fn new_default() -> Result<Self, EspError> {
179 Self::new(&Default::default())
180 }
181
182 pub fn new(conf: &SntpConf) -> Result<Self, EspError> {
183 let mut taken = TAKEN.lock();
184
185 if *taken {
186 return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
187 }
188
189 let sntp = Self::init(conf)?;
190
191 *taken = true;
192 Ok(sntp)
193 }
194
195 #[cfg(feature = "alloc")]
196 pub fn new_with_callback<F>(conf: &SntpConf, callback: F) -> Result<Self, EspError>
197 where
198 F: FnMut(Duration) + Send + 'static,
199 {
200 Self::internal_new_with_callback(conf, callback)
201 }
202}
203
204impl<'a> EspSntp<'a> {
205 #[cfg(feature = "alloc")]
229 pub unsafe fn new_nonstatic_with_callback<F>(
230 conf: &SntpConf,
231 callback: F,
232 ) -> Result<Self, EspError>
233 where
234 F: FnMut(Duration) + Send + 'a,
235 {
236 Self::internal_new_with_callback(conf, callback)
237 }
238
239 #[cfg(feature = "alloc")]
240 fn internal_new_with_callback<F>(conf: &SntpConf, callback: F) -> Result<Self, EspError>
241 where
242 F: FnMut(Duration) + Send + 'a,
243 {
244 let mut taken = TAKEN.lock();
245
246 if *taken {
247 esp!(ESP_ERR_INVALID_STATE)?;
248 }
249
250 #[allow(clippy::type_complexity)]
251 let callback: alloc::boxed::Box<dyn FnMut(Duration) + Send + 'a> =
252 alloc::boxed::Box::new(callback);
253 #[allow(clippy::type_complexity)]
254 let callback: alloc::boxed::Box<dyn FnMut(Duration) + Send + 'static> =
255 unsafe { core::mem::transmute(callback) };
256
257 *SYNC_CB.lock() = Some(callback);
258 let sntp = Self::init(conf)?;
259
260 *taken = true;
261 Ok(sntp)
262 }
263
264 fn init(conf: &SntpConf) -> Result<Self, EspError> {
265 info!("Initializing");
266
267 unsafe { sntp_setoperatingmode(conf.operating_mode.into()) };
268 unsafe { sntp_set_sync_mode(sntp_sync_mode_t::from(conf.sync_mode)) };
269
270 let mut c_servers: [CString; SNTP_SERVER_NUM] = Default::default();
271 for (i, s) in conf.servers.iter().enumerate() {
272 let c_server = to_cstring_arg(s)?;
273 unsafe { sntp_setservername(i as u8, c_server.as_ptr()) };
274 c_servers[i] = c_server;
275 }
276
277 unsafe {
278 sntp_set_time_sync_notification_cb(Some(Self::sync_cb));
279
280 sntp_init();
281 };
282
283 info!("Initialization complete");
284
285 Ok(Self {
286 _sntp_servers: c_servers,
287 _ref: PhantomData,
288 })
289 }
290
291 #[cfg(feature = "alloc")]
292 fn unsubscribe(&mut self) {
293 *SYNC_CB.lock() = None;
294 }
295
296 pub fn get_sync_status(&self) -> SyncStatus {
297 SyncStatus::from(unsafe { sntp_get_sync_status() })
298 }
299
300 unsafe extern "C" fn sync_cb(tv: *mut timeval) {
301 debug!(
302 " Sync cb called: sec: {}, usec: {}",
303 (*tv).tv_sec,
304 (*tv).tv_usec,
305 );
306
307 #[cfg(feature = "alloc")]
308 if let Some(cb) = &mut *SYNC_CB.lock() {
309 let duration = Duration::from_secs((*tv).tv_sec as u64)
310 + Duration::from_micros((*tv).tv_usec as u64);
311
312 cb(duration);
313 }
314 }
315}
316
317impl Drop for EspSntp<'_> {
318 fn drop(&mut self) {
319 {
320 let mut taken = TAKEN.lock();
321
322 unsafe { sntp_stop() };
323
324 #[cfg(feature = "alloc")]
325 self.unsubscribe();
326
327 *taken = false;
328 }
329
330 info!("Dropped");
331 }
332}