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}