/ firmware / src / networking / sntp.rs
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  }