Repo of no-std crates for my personal embedded projects

feat: SNTP crate

Changed files
+465 -11
sachy-fmt
sachy-sntp
+220 -8
Cargo.lock
···
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
+
name = "chrono"
+
version = "0.4.42"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
+
dependencies = [
+
"num-traits",
+
]
+
+
[[package]]
name = "core-foundation"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+
[[package]]
+
name = "critical-section"
+
version = "1.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "defmt"
···
]
[[package]]
+
name = "document-features"
+
version = "0.2.12"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+
dependencies = [
+
"litrs",
+
]
+
+
[[package]]
+
name = "embassy-net"
+
version = "0.7.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0558a231a47e7d4a06a28b5278c92e860f1200f24821d2f365a2f40fe3f3c7b2"
+
dependencies = [
+
"document-features",
+
"embassy-net-driver",
+
"embassy-sync",
+
"embassy-time",
+
"embedded-io-async",
+
"embedded-nal-async",
+
"heapless 0.8.0",
+
"managed",
+
"smoltcp",
+
]
+
+
[[package]]
+
name = "embassy-net-driver"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d"
+
+
[[package]]
+
name = "embassy-sync"
+
version = "0.7.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b"
+
dependencies = [
+
"cfg-if",
+
"critical-section",
+
"embedded-io-async",
+
"futures-core",
+
"futures-sink",
+
"heapless 0.8.0",
+
]
+
+
[[package]]
+
name = "embassy-time"
+
version = "0.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65"
+
dependencies = [
+
"cfg-if",
+
"critical-section",
+
"document-features",
+
"embassy-time-driver",
+
"embedded-hal 0.2.7",
+
"embedded-hal 1.0.0",
+
"embedded-hal-async",
+
"futures-core",
+
]
+
+
[[package]]
+
name = "embassy-time-driver"
+
version = "0.2.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6"
+
dependencies = [
+
"document-features",
+
]
+
+
[[package]]
+
name = "embedded-hal"
+
version = "0.2.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
+
dependencies = [
+
"nb 0.1.3",
+
"void",
+
]
+
+
[[package]]
name = "embedded-hal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
dependencies = [
-
"embedded-hal",
+
"embedded-hal 1.0.0",
]
[[package]]
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a0f04f8886106faf281c47b6a0e4054a369baedaf63591fdb8da9761f3f379"
dependencies = [
-
"embedded-hal",
+
"embedded-hal 1.0.0",
"embedded-hal-nb",
]
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605"
dependencies = [
-
"embedded-hal",
-
"nb",
+
"embedded-hal 1.0.0",
+
"nb 1.1.0",
+
]
+
+
[[package]]
+
name = "embedded-io"
+
version = "0.6.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+
[[package]]
+
name = "embedded-io-async"
+
version = "0.6.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f"
+
dependencies = [
+
"embedded-io",
+
]
+
+
[[package]]
+
name = "embedded-nal"
+
version = "0.9.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c56a28be191a992f28f178ec338a0bf02f63d7803244add736d026a471e6ed77"
+
dependencies = [
+
"nb 1.1.0",
+
]
+
+
[[package]]
+
name = "embedded-nal-async"
+
version = "0.8.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "76959917cd2b86f40a98c28dd5624eddd1fa69d746241c8257eac428d83cb211"
+
dependencies = [
+
"embedded-io-async",
+
"embedded-nal",
]
[[package]]
···
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
+
name = "futures-core"
+
version = "0.3.31"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+
[[package]]
+
name = "futures-sink"
+
version = "0.3.31"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+
[[package]]
name = "gpio-cdev"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
name = "heapless"
+
version = "0.8.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
+
dependencies = [
+
"hash32",
+
"stable_deref_trait",
+
]
+
+
[[package]]
+
name = "heapless"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed"
···
checksum = "2a8a605c95f708c78554738a12153b213f107d3bd5323f7ce32d6deb3faafb40"
dependencies = [
"cast",
-
"embedded-hal",
+
"embedded-hal 1.0.0",
"embedded-hal-nb",
"gpio-cdev",
"i2cdev",
-
"nb",
+
"nb 1.1.0",
"nix 0.27.1",
"serialport",
"spidev",
···
]
[[package]]
+
name = "litrs"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+
[[package]]
name = "mach2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "managed"
+
version = "0.8.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
+
+
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
+
]
+
+
[[package]]
+
name = "nb"
+
version = "0.1.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
+
dependencies = [
+
"nb 1.1.0",
]
[[package]]
···
]
[[package]]
+
name = "num-traits"
+
version = "0.2.19"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+
dependencies = [
+
"autocfg",
+
]
+
+
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
version = "0.1.0"
dependencies = [
"defmt 1.0.1",
-
"heapless",
+
"heapless 0.9.2",
"sachy-fmt",
]
···
version = "0.1.0"
dependencies = [
"defmt 1.0.1",
-
"embedded-hal",
+
"embedded-hal 1.0.0",
"embedded-hal-async",
"embedded-hal-mock",
"linux-embedded-hal",
]
[[package]]
+
name = "sachy-sntp"
+
version = "0.1.0"
+
dependencies = [
+
"chrono",
+
"defmt 1.0.1",
+
"embassy-net",
+
"embassy-time",
+
"sachy-fmt",
+
]
+
+
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+
[[package]]
+
name = "smoltcp"
+
version = "0.12.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb"
+
dependencies = [
+
"bitflags 1.3.2",
+
"byteorder",
+
"cfg-if",
+
"heapless 0.8.0",
+
"managed",
+
]
[[package]]
name = "spidev"
···
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+
[[package]]
+
name = "void"
+
version = "1.0.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "windows-sys"
+9 -2
Cargo.toml
···
[workspace]
resolver = "3"
-
members = ["sachy-battery","sachy-bthome", "sachy-fmt", "sachy-fnv", "sachy-shtc3"]
+
members = ["sachy-battery","sachy-bthome", "sachy-fmt", "sachy-fnv", "sachy-shtc3", "sachy-sntp"]
[workspace.package]
authors = ["Sachymetsu <sachymetsu@tutamail.com>"]
···
repository = "https://tangled.org/sachy.dev/sachy-embed-core/"
license = "MIT OR Apache-2.0"
version = "0.1.0"
-
rust-version = "1.88.0"
+
rust-version = "1.89.0"
+
+
[workspace.dependencies]
+
embassy-futures = { version = "0.1" }
+
embassy-time = { version = "0.5" }
+
embassy-sync = { version = "0.7" }
+
embassy-net = { version = "0.7" }
+
defmt = { version = "1" }
+1 -1
sachy-fmt/Cargo.toml
···
rust-version = { workspace = true }
[dependencies]
-
defmt = { version = "1", optional = true }
+
defmt = { workspace = true, optional = true }
[features]
defmt = ["dep:defmt"]
+26
sachy-sntp/Cargo.toml
···
+
[package]
+
name = "sachy-sntp"
+
authors.workspace = true
+
edition.workspace = true
+
repository.workspace = true
+
license.workspace = true
+
version.workspace = true
+
rust-version.workspace = true
+
+
[dependencies]
+
chrono = { version = "0.4.41", default-features = false, optional = true }
+
defmt = { workspace = true, optional = true }
+
embassy-net = { workspace = true, features = [
+
"udp",
+
"proto-ipv4",
+
"medium-ip",
+
"medium-ethernet",
+
], optional = true }
+
embassy-time = { workspace = true, optional = true }
+
sachy-fmt = { path = "../sachy-fmt" }
+
+
[features]
+
default = ["chrono", "embassy-net"]
+
chrono = ["dep:chrono"]
+
embassy-net = ["dep:embassy-net", "dep:embassy-time"]
+
defmt = ["dep:defmt"]
+209
sachy-sntp/src/lib.rs
···
+
#![no_std]
+
+
#[derive(Debug, PartialEq, Eq)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub enum SntpError {
+
InvalidPacket,
+
InvalidVersion,
+
InvalidMode,
+
KissOfDeath,
+
InvalidTime,
+
NetFailure,
+
NetTimeout,
+
}
+
+
pub struct SntpRequest;
+
+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
/// SNTP timestamp.
+
pub struct SntpTimestamp(u64);
+
+
impl SntpTimestamp {
+
pub fn microseconds(&self) -> u64 {
+
(self.0 >> 32) * 1_000_000 + ((self.0 & 0xFFFFFFFF) * 1_000_000 / 0x100000000)
+
}
+
+
/// Returns true if the most significant bit is set.
+
///
+
/// Relevant documentation from RFC 2030:
+
///
+
/// ```text
+
/// Note that, since some time in 1968 (second 2,147,483,648) the most
+
/// significant bit (bit 0 of the integer part) has been set and that the
+
/// 64-bit field will overflow some time in 2036 (second 4,294,967,296).
+
/// Should NTP or SNTP be in use in 2036, some external means will be
+
/// necessary to qualify time relative to 1900 and time relative to 2036
+
/// (and other multiples of 136 years). There will exist a 200-picosecond
+
/// interval, henceforth ignored, every 136 years when the 64-bit field
+
/// will be 0, which by convention is interpreted as an invalid or
+
/// unavailable timestamp.
+
/// As the NTP timestamp format has been in use for the last 17 years,
+
/// it remains a possibility that it will be in use 40 years from now
+
/// when the seconds field overflows. As it is probably inappropriate
+
/// to archive NTP timestamps before bit 0 was set in 1968, a
+
/// convenient way to extend the useful life of NTP timestamps is the
+
/// following convention: If bit 0 is set, the UTC time is in the
+
/// range 1968-2036 and UTC time is reckoned from 0h 0m 0s UTC on 1
+
/// January 1900. If bit 0 is not set, the time is in the range 2036-
+
/// 2104 and UTC time is reckoned from 6h 28m 16s UTC on 7 February
+
/// 2036. Note that when calculating the correspondence, 2000 is not a
+
/// leap year. Note also that leap seconds are not counted in the
+
/// reckoning.
+
///```
+
pub fn msb_set(&self) -> bool {
+
self.0 & (1 << 63) != 0
+
}
+
+
/// Microseconds since the UNIX epoch.
+
pub fn utc_micros(&self) -> i64 {
+
let ntp_epoch_micros = self.microseconds() as i64;
+
let offset: i64 = if self.msb_set() {
+
-2208988800000000
+
} else {
+
2085978496000000
+
};
+
+
ntp_epoch_micros + offset
+
}
+
+
#[cfg(feature = "chrono")]
+
pub fn try_to_naive_datetime(self) -> Result<chrono::NaiveDateTime, SntpError> {
+
self.try_into()
+
}
+
}
+
+
impl SntpRequest {
+
pub const SNTP_PACKET_SIZE: usize = 48;
+
+
pub const fn create_packet() -> [u8; Self::SNTP_PACKET_SIZE] {
+
let mut packet = [0u8; Self::SNTP_PACKET_SIZE];
+
packet[0] = (3 << 6) | (4 << 3) | 3;
+
packet
+
}
+
+
pub fn create_packet_from_buffer(packet: &mut [u8]) -> Result<(), SntpError> {
+
if packet.len() != Self::SNTP_PACKET_SIZE {
+
return Err(SntpError::InvalidPacket);
+
}
+
+
packet[0] = (3 << 6) | (4 << 3) | 3;
+
+
Ok(())
+
}
+
+
pub fn read_timestamp(packet: &[u8]) -> Result<SntpTimestamp, SntpError> {
+
if packet.len() == Self::SNTP_PACKET_SIZE {
+
let header = packet[0];
+
let version = (header & 0x38) >> 3;
+
+
if version != 4 {
+
return Err(SntpError::InvalidVersion);
+
}
+
+
let mode = header & 0x7;
+
+
if !(4..=5).contains(&mode) {
+
return Err(SntpError::InvalidMode);
+
}
+
+
let kiss_of_death = packet[1] == 0;
+
+
if kiss_of_death {
+
return Err(SntpError::KissOfDeath);
+
}
+
+
let timestamp = SntpTimestamp(read_be_u64(&packet[40..48]));
+
+
return Ok(timestamp);
+
}
+
+
Err(SntpError::InvalidPacket)
+
}
+
+
#[cfg(feature = "chrono")]
+
pub fn as_naive_datetime(raw_time: SntpTimestamp) -> Result<chrono::NaiveDateTime, SntpError> {
+
raw_time.try_into()
+
}
+
}
+
+
#[cfg(feature = "chrono")]
+
impl TryFrom<SntpTimestamp> for chrono::NaiveDateTime {
+
type Error = SntpError;
+
+
fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> {
+
Ok(chrono::DateTime::<chrono::Utc>::try_from(timestamp)?.naive_utc())
+
}
+
}
+
+
#[cfg(feature = "chrono")]
+
impl TryFrom<SntpTimestamp> for chrono::DateTime<chrono::Utc> {
+
type Error = SntpError;
+
+
fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> {
+
chrono::DateTime::<chrono::Utc>::from_timestamp_micros(timestamp.utc_micros())
+
.ok_or(SntpError::InvalidTime)
+
}
+
}
+
+
#[inline]
+
fn read_be_u64(input: &[u8]) -> u64 {
+
let (int_bytes, _) = input.split_at(core::mem::size_of::<u64>());
+
u64::from_be_bytes(int_bytes.try_into().unwrap())
+
}
+
+
#[cfg(feature = "embassy-net")]
+
pub trait SntpSocket {
+
fn resolve_time(
+
&mut self,
+
addrs: &[embassy_net::IpAddress],
+
) -> impl Future<Output = Result<SntpTimestamp, SntpError>>;
+
}
+
+
#[cfg(feature = "embassy-net")]
+
impl SntpSocket for embassy_net::udp::UdpSocket<'_> {
+
async fn resolve_time(
+
&mut self,
+
addrs: &[embassy_net::IpAddress],
+
) -> Result<SntpTimestamp, SntpError> {
+
use embassy_time::{Duration, WithTimeout};
+
use sachy_fmt::{debug, error};
+
+
if addrs.is_empty() {
+
return Err(SntpError::NetFailure);
+
}
+
+
debug!("SNTP address list: {}", addrs);
+
+
let addr = addrs[0];
+
+
debug!("Binding to port 123");
+
self.bind(123).map_err(|_| SntpError::NetFailure)?;
+
+
debug!("Sending SNTP request");
+
self.send_to_with(
+
SntpRequest::SNTP_PACKET_SIZE,
+
embassy_net::IpEndpoint::new(addr, 123),
+
SntpRequest::create_packet_from_buffer,
+
)
+
.await
+
.map_err(|e| {
+
error!("Failed to send: {}", e);
+
SntpError::NetFailure
+
})??;
+
+
debug!("Waiting for a response...");
+
let res = self
+
.recv_from_with(|buf, _from| SntpRequest::read_timestamp(buf))
+
.with_timeout(Duration::from_secs(5))
+
.await
+
.map_err(|_| SntpError::NetTimeout)
+
.flatten();
+
+
debug!("Received: {}", &res);
+
debug!("Closing SNTP port...");
+
self.close();
+
+
res
+
}
+
}