Skip to main content

sntpc_time_embassy/
lib.rs

1//! [`NtpTimestampGenerator`] implementation backed by [`embassy_time`] for the [`sntpc`] SNTP client library.
2//!
3//! This crate provides an [`EmbassyTimestampGenerator`] that uses the Embassy async runtime's
4//! monotonic clock to produce timestamps suitable for SNTP round-trip time measurements in
5//! embedded `no_std` environments.
6//!
7//! # Design Rationale
8//!
9//! The timestamp generator is separated into its own crate to:
10//! - **Independent versioning**: Update `embassy-time` without requiring `sntpc` core updates
11//! - **Version flexibility**: Works with `embassy-time` 0.5.x (`>=0.5, <0.6`)
12//! - **Embedded focus**: Minimal dependencies suitable for `no_std` embedded systems
13//! - **Clean separation**: Core SNTP protocol logic remains independent of the async runtime
14//!
15//! # Important
16//!
17//! [`EmbassyTimestampGenerator`] provides **monotonic** timestamps based on [`embassy_time::Instant`],
18//! not wall-clock time. This is enough for SNTP request/response delay calculations, where
19//! the client measures the elapsed time between sending a request and receiving a response.
20//! The actual wall-clock offset is computed by the `sntpc` library using the server's timestamps.
21//!
22//! # Example
23//!
24//! ```ignore
25//! use sntpc::{get_time, NtpContext};
26//! use sntpc_time_embassy::EmbassyTimestampGenerator;
27//!
28//! // Create an NtpContext with the Embassy timestamp generator
29//! let ntp_context = NtpContext::new(EmbassyTimestampGenerator::default());
30//!
31//! // Use with an Embassy UDP socket adapter
32//! let result = get_time(server_addr, &socket, ntp_context).await;
33//! ```
34//!
35//! For complete examples, see the [sntpc examples](https://github.com/vpetrigo/sntpc/tree/master/examples/embassy-net).
36#![no_std]
37
38use embassy_time::Instant;
39use sntpc::NtpTimestampGenerator;
40
41/// Monotonic timestamp generator backed by [`embassy_time::Instant`].
42///
43/// This type implements [`NtpTimestampGenerator`] by capturing the current monotonic
44/// time via [`Instant::now`] on each call to [`init`](NtpTimestampGenerator::init),
45/// then reporting the elapsed seconds and sub-second microseconds.
46///
47/// It does **not** provide wall-clock time. Instead, it provides a monotonic timestamp
48/// source suitable for SNTP round-trip delay calculations, where only the relative
49/// elapsed time between request and response matters.
50///
51/// # Example
52///
53/// ```ignore
54/// use sntpc::NtpContext;
55/// use sntpc_time_embassy::EmbassyTimestampGenerator;
56///
57/// let ntp_context = NtpContext::new(EmbassyTimestampGenerator::default());
58/// ```
59#[derive(Copy, Clone)]
60pub struct EmbassyTimestampGenerator {
61    instant: Instant,
62}
63
64impl Default for EmbassyTimestampGenerator {
65    /// Returns a default `EmbassyTimestampGenerator` with the instant set to the epoch.
66    ///
67    /// The instant is initialized to `Instant::from_secs(0)` and will be updated
68    /// to the current time when [`init`](NtpTimestampGenerator::init) is called
69    /// before each timestamp measurement.
70    fn default() -> Self {
71        Self {
72            instant: Instant::from_secs(0),
73        }
74    }
75}
76
77impl NtpTimestampGenerator for EmbassyTimestampGenerator {
78    /// Captures the current monotonic time from [`embassy_time::Instant::now`].
79    ///
80    /// This method should be called before each pair of
81    /// [`timestamp_sec`](NtpTimestampGenerator::timestamp_sec) and
82    /// [`timestamp_subsec_micros`](NtpTimestampGenerator::timestamp_subsec_micros)
83    /// calls to ensure the timestamp reflects the current moment.
84    fn init(&mut self) {
85        self.instant = Instant::now();
86    }
87
88    /// Returns the whole seconds component of the captured monotonic instant.
89    ///
90    /// The value represents seconds elapsed since the Embassy runtime's monotonic
91    /// clock epoch, not since the UNIX epoch.
92    fn timestamp_sec(&self) -> u64 {
93        self.instant.as_secs()
94    }
95
96    /// Returns the sub-second microsecond component of the captured monotonic instant.
97    ///
98    /// This is the fractional part of the timestamp in whole microseconds
99    /// (i.e., microseconds within the current second, in the range `0..1_000_000`).
100    fn timestamp_subsec_micros(&self) -> u32 {
101        (self.instant.as_micros() % 1_000_000) as u32
102    }
103}