sntp.rs
1 use defmt::info; 2 use embassy_executor::Spawner; 3 use embassy_net::Stack; 4 use embassy_time::{Duration, Timer}; 5 6 use crate::config::app; 7 8 #[derive(Clone, Copy)] 9 pub struct TimestampGenerator; 10 11 impl sntpc::NtpTimestampGenerator for TimestampGenerator { 12 fn init(&mut self) {} 13 fn timestamp_sec(&self) -> u64 { 14 embassy_time::Instant::now().as_secs() 15 } 16 fn timestamp_subsec_micros(&self) -> u32 { 17 (embassy_time::Instant::now().as_micros() % 1_000_000) as u32 18 } 19 } 20 21 pub struct UdpSocket<'a> { 22 pub socket: embassy_net::udp::UdpSocket<'a>, 23 } 24 25 impl sntpc::NtpUdpSocket for UdpSocket<'_> { 26 async fn send_to( 27 &self, 28 buf: &[u8], 29 addr: core::net::SocketAddr, 30 ) -> sntpc::Result<usize> { 31 let core::net::SocketAddr::V4(addr_v4) = addr else { 32 return Err(sntpc::Error::Network); 33 }; 34 35 self.socket 36 .send_to(buf, embassy_net::IpEndpoint::from(addr_v4)) 37 .await 38 .map(|()| buf.len()) 39 .map_err(|_| sntpc::Error::Network) 40 } 41 42 async fn recv_from( 43 &self, 44 buf: &mut [u8], 45 ) -> sntpc::Result<(usize, core::net::SocketAddr)> { 46 let (bytes_read, udp_meta) = self 47 .socket 48 .recv_from(buf) 49 .await 50 .map_err(|_| sntpc::Error::Network)?; 51 52 let embassy_net::IpEndpoint { 53 addr: embassy_net::IpAddress::Ipv4(addr_v4), 54 port, 55 } = udp_meta.endpoint; 56 57 let octets = addr_v4.octets(); 58 Ok(( 59 bytes_read, 60 core::net::SocketAddr::V4(core::net::SocketAddrV4::new( 61 core::net::Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]), 62 port, 63 )), 64 )) 65 } 66 } 67 68 pub async fn sntp_sync_loop(stack: Stack<'static>, ntp_server: &str) -> ! { 69 use embassy_net::dns::DnsQueryType; 70 71 loop { 72 let ntp_addrs = match stack.dns_query(ntp_server, DnsQueryType::A).await { 73 Ok(addrs) if !addrs.is_empty() => addrs, 74 Ok(_) => { 75 info!("SNTP: DNS resolution for {} returned empty", ntp_server); 76 Timer::after(Duration::from_secs(app::sntp::RETRY_INTERVAL_SECS)).await; 77 continue; 78 } 79 Err(error) => { 80 info!("SNTP: DNS resolution failed: {:?}", error); 81 Timer::after(Duration::from_secs(app::sntp::RETRY_INTERVAL_SECS)).await; 82 continue; 83 } 84 }; 85 86 let ntp_addr: core::net::IpAddr = ntp_addrs[0].into(); 87 info!("SNTP: Resolved {} to {}", ntp_server, ntp_addr); 88 89 let mut sync_succeeded = false; 90 91 for attempt in 0..app::sntp::MAX_ATTEMPTS { 92 info!("SNTP: sync attempt {}/{}", attempt + 1, app::sntp::MAX_ATTEMPTS); 93 94 let mut rx_meta = [embassy_net::udp::PacketMetadata::EMPTY; 16]; 95 let mut rx_buffer = alloc::vec![0u8; 4096]; 96 let mut tx_meta = [embassy_net::udp::PacketMetadata::EMPTY; 16]; 97 let mut tx_buffer = alloc::vec![0u8; 4096]; 98 99 let mut udp_socket = embassy_net::udp::UdpSocket::new( 100 stack, 101 &mut rx_meta, 102 &mut rx_buffer, 103 &mut tx_meta, 104 &mut tx_buffer, 105 ); 106 107 if let Err(error) = udp_socket.bind(123) { 108 info!("SNTP: failed to bind UDP socket: {:?}", error); 109 Timer::after(Duration::from_secs(app::sntp::ATTEMPT_INTERVAL_SECS)).await; 110 continue; 111 } 112 113 let ntp_context = sntpc::NtpContext::new(TimestampGenerator); 114 115 match sntpc::get_time( 116 core::net::SocketAddr::from((ntp_addr, 123)), 117 &UdpSocket { socket: udp_socket }, 118 ntp_context, 119 ) 120 .await 121 { 122 Ok(ntp_time) => { 123 let epoch_secs = ntp_time.sec() as u64; 124 125 crate::time::set_time_synced(epoch_secs); 126 127 info!( 128 "SNTP: sync successful, epoch={} ({})", 129 epoch_secs, 130 crate::time::format_iso8601(epoch_secs).as_str() 131 ); 132 133 sync_succeeded = true; 134 break; 135 } 136 Err(_error) => { 137 info!("SNTP: sync attempt {} failed", attempt + 1); 138 Timer::after(Duration::from_secs(app::sntp::ATTEMPT_INTERVAL_SECS)).await; 139 } 140 } 141 } 142 143 if !sync_succeeded { 144 info!( 145 "SNTP: all {} attempts failed, retrying in {}s", 146 app::sntp::MAX_ATTEMPTS, app::sntp::RETRY_INTERVAL_SECS 147 ); 148 } 149 150 Timer::after(Duration::from_secs(app::sntp::RETRY_INTERVAL_SECS)).await; 151 } 152 } 153 154 #[embassy_executor::task] 155 pub async fn task(stack: Stack<'static>) { 156 sntp_sync_loop(stack, app::NTP_SERVER).await 157 } 158 159 pub fn spawn(spawner: &Spawner, stack: Stack<'static>) { 160 spawner.spawn(task(stack).unwrap()); 161 }