This commit is contained in:
Invariantspace 2024-10-14 23:56:20 -07:00
parent 97238b56f7
commit 4d087a56d0
No known key found for this signature in database
GPG key ID: EBC4A20067373921
7 changed files with 382 additions and 169 deletions

52
Cargo.lock generated
View file

@ -125,6 +125,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"num-traits",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -191,7 +200,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "cyw43"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"cortex-m",
"cortex-m-rt",
@ -208,7 +217,7 @@ dependencies = [
[[package]]
name = "cyw43-pio"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"cyw43",
"embassy-rp",
@ -303,7 +312,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "embassy-embedded-hal"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"embassy-futures",
"embassy-sync",
@ -319,7 +328,7 @@ dependencies = [
[[package]]
name = "embassy-executor"
version = "0.6.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"cortex-m",
"critical-section",
@ -332,7 +341,7 @@ dependencies = [
[[package]]
name = "embassy-executor-macros"
version = "0.5.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"darling",
"proc-macro2",
@ -343,12 +352,12 @@ dependencies = [
[[package]]
name = "embassy-futures"
version = "0.1.1"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
[[package]]
name = "embassy-hal-internal"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"cortex-m",
"critical-section",
@ -358,7 +367,7 @@ dependencies = [
[[package]]
name = "embassy-net"
version = "0.4.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"document-features",
"embassy-net-driver",
@ -374,12 +383,12 @@ dependencies = [
[[package]]
name = "embassy-net-driver"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
[[package]]
name = "embassy-net-driver-channel"
version = "0.3.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"embassy-futures",
"embassy-net-driver",
@ -389,7 +398,7 @@ dependencies = [
[[package]]
name = "embassy-rp"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"atomic-polyfill",
"cfg-if",
@ -426,7 +435,7 @@ dependencies = [
[[package]]
name = "embassy-sync"
version = "0.6.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"cfg-if",
"critical-section",
@ -438,7 +447,7 @@ dependencies = [
[[package]]
name = "embassy-time"
version = "0.3.2"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"cfg-if",
"critical-section",
@ -455,7 +464,7 @@ dependencies = [
[[package]]
name = "embassy-time-driver"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"document-features",
]
@ -463,12 +472,12 @@ dependencies = [
[[package]]
name = "embassy-time-queue-driver"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
[[package]]
name = "embassy-usb"
version = "0.3.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"embassy-futures",
"embassy-net-driver-channel",
@ -482,12 +491,12 @@ dependencies = [
[[package]]
name = "embassy-usb-driver"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
[[package]]
name = "embassy-usb-logger"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#c84495ef2eb99580fea5392b2b3aff5ad66043a0"
source = "git+https://github.com/embassy-rs/embassy.git#4f08d5bc5ff0f765198665b1f37b6372f43b8567"
dependencies = [
"embassy-futures",
"embassy-sync",
@ -1001,15 +1010,18 @@ dependencies = [
name = "picow"
version = "0.1.0"
dependencies = [
"chrono",
"cortex-m-rt",
"cyw43",
"cyw43-pio",
"embassy-executor",
"embassy-net",
"embassy-rp",
"embassy-sync",
"embassy-time",
"embassy-usb-logger",
"log",
"no-std-net",
"panic-halt",
"portable-atomic",
"rand",
@ -1236,9 +1248,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]]
name = "ryu"

View file

@ -9,19 +9,22 @@ serde = { version = "*", features = ["derive"] }
serde_yaml = "*"
[dependencies]
chrono = { version = "*", default-features = false}
cortex-m-rt = "*"
cyw43 = { git = "https://github.com/embassy-rs/embassy.git" }
cyw43-pio = { git = "https://github.com/embassy-rs/embassy.git" }
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "integrated-timers", "nightly"] }
embassy-net = { git = "https://github.com/embassy-rs/embassy.git", features = ["tcp", "udp", "raw", "dhcpv4", "dhcpv4-hostname", "dns", "proto-ipv4", "multicast"] }
embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", features = ["critical-section-impl", "time-driver", "rp2040"] }
embassy-net = { git = "https://github.com/embassy-rs/embassy.git", features = ["raw", "dhcpv4", "dhcpv4-hostname", "dns", "multicast", "proto-ipv4", "proto-ipv6", "tcp", "udp" ] }
embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", features = ["critical-section-impl", "rp2040", "time-driver" ] }
embassy-sync = { git = "https://github.com/embassy-rs/embassy.git" }
embassy-time = { git = "https://github.com/embassy-rs/embassy.git" }
embassy-usb-logger = { git = "https://github.com/embassy-rs/embassy.git" }
log ="*"
log = "*"
no-std-net = "*"
panic-halt = "*"
portable-atomic = { version = "*", features = ["critical-section"] }
rand = { version = "*", default-features = false }
sntpc = { version = "*", default-features = false }
sntpc = { version = "*", default-features = false, features = ["async"] }
static_cell = "*"
[profile.release]

18
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": {
"crane": {
"locked": {
"lastModified": 1728344376,
"narHash": "sha256-lxTce2XE6mfJH8Zk6yBbqsbu9/jpwdymbSH5cCbiVOA=",
"lastModified": 1728776144,
"narHash": "sha256-fROVjMcKRoGHofDm8dY3uDUtCMwUICh/KjBFQnuBzfg=",
"owner": "ipetkov",
"repo": "crane",
"rev": "fd86b78f5f35f712c72147427b1eb81a9bd55d0b",
"rev": "f876e3d905b922502f031aeec1a84490122254b7",
"type": "github"
},
"original": {
@ -21,11 +21,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1728628307,
"narHash": "sha256-GRMRHZyU+R0RqKPFFgi7BBMDIRFPnHaAhOIxlqyvbZQ=",
"lastModified": 1728887700,
"narHash": "sha256-i+WCARuldFmXlNW6XlEYiL8UGMzjdg5lMQ9gpACQL/A=",
"owner": "nix-community",
"repo": "fenix",
"rev": "b0a014d5b9dba793ebc205bcf12a93b5f6a4c66c",
"rev": "bcf74e45d5a818fe3aadf5cb3099189770e18579",
"type": "github"
},
"original": {
@ -95,11 +95,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1728505432,
"narHash": "sha256-QFPMazeiGLo7AGy4RREmTgko0Quch/toMVKhGUjDEeo=",
"lastModified": 1728719115,
"narHash": "sha256-SLcHmKzkIuahBbSCWr2gzu6bXo6wF12JP4myDzH1CvQ=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "0fb804acb375b02a3beeaceeb75b71969ef37b15",
"rev": "d7628c0a8b95cadefe89d9a45f9be5ee4898c6b1",
"type": "github"
},
"original": {

View file

@ -18,7 +18,7 @@
fenixPkgs = fenix.packages.${system};
fenixToolchain = fenixPkgs.fromToolchainFile {
dir = ./.;
sha256 = "sha256-HAFn+jo7K/dwbCKRHNXQU+x9b+8LJ8xlQGL/tE0rNlE=";
sha256 = "sha256-xN1aESBFCg2dq0LYOHWtU0FtIwkswFbmMoC1GoWCz9c=";
};
craneLib = (crane.mkLib pkgs).overrideToolchain fenixToolchain;
in {

View file

@ -2,16 +2,17 @@
#![no_std]
#![no_main]
mod picow_time;
mod time;
extern crate panic_halt;
use chrono::DateTime;
use cyw43::JoinOptions;
use cyw43_pio::PioSpi;
use embassy_executor::Spawner;
use embassy_net::{
dns::{DnsQueryType, DnsSocket},
Config, DhcpConfig, IpAddress, StackResources,
Config, DhcpConfig, IpAddress, Stack, StackResources,
};
use embassy_rp::{
bind_interrupts,
@ -21,17 +22,24 @@ use embassy_rp::{
pio::{self, Pio},
usb::{self, Driver},
};
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
use embassy_time::Timer;
use log::info;
use picow_time::PicowUDPBuffer;
use no_std_net::{IpAddr, Ipv4Addr, SocketAddr};
use rand::RngCore;
use sntpc::{async_impl::get_time, NtpContext};
use static_cell::StaticCell;
use time::{PicowClock, PicowTimestampGenerator, PicowUDPBuffer};
const DNS_WAIT_SECOND: u64 = 1 << 3;
const HOSTNAME: &str = "picow";
const NTP_ENDPOINT: &str = "time.google.com";
const WAIT_SECOND: u64 = 7;
const NTP_PORT: u16 = 123;
const NTP_WAIT_SECOND: u64 = 1 << 6;
const TIMER_WAIT_SECOND: u64 = 1 << 1;
const WIRELESS_CREDENTIALS: &[(&str, &str)] =
&include!(concat!(env!("OUT_DIR"), "/wireless-credentials.rs"));
const WIRELESS_SCAN_WAIT_SECOND: u64 = 1 << 3;
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
@ -50,6 +58,124 @@ async fn network_task(
network_runner.run().await;
}
#[embassy_executor::task]
async fn ntp_task(
clock: &'static PicowClock,
network_stack: Stack<'static>,
offline_signal: &'static Signal<CriticalSectionRawMutex, ()>,
) -> ! {
// dns client
let dns_client = DnsSocket::new(network_stack);
loop {
// wait for network stack
info!("Wait for network setup");
network_stack.wait_config_up().await;
network_stack.wait_link_up().await;
info!("Successfully setup network stack");
info!("Network configuration: {:?}", network_stack.config_v4());
let socket_address = match dns_client.query(NTP_ENDPOINT, DnsQueryType::A).await {
Ok(address) => match address.first() {
Some(IpAddress::Ipv4(ipv4)) => {
let octets = ipv4.octets();
SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3])),
NTP_PORT,
)
}
_ => {
info!("DNS A record lookup returned no IPv4 address: {NTP_ENDPOINT}",);
Timer::after_secs(DNS_WAIT_SECOND).await;
continue;
}
},
Err(error) => {
info!("DNS A record lookup for {NTP_ENDPOINT} returned error: {error:?}",);
Timer::after_secs(DNS_WAIT_SECOND).await;
continue;
}
};
info!("NTP endpoint identified for {NTP_ENDPOINT}: {socket_address}");
loop {
// udp client
let mut udp_buffer = PicowUDPBuffer::default();
let udp_client = udp_buffer.socket(network_stack, NTP_PORT);
let ntp_context = NtpContext::new(PicowTimestampGenerator::from_clock(clock).await);
match get_time(socket_address, udp_client, ntp_context).await {
Ok(result) => {
if let Some(now) = DateTime::from_timestamp(result.sec() as i64, 0) {
clock.set_time(now).await;
info!("Calibrated system clock with NTP time: {now}");
} else {
info!("Invalid NTP result: {result:?}");
}
}
Err(error) => {
info!("Failed to run NTP protocol: {error:?}");
offline_signal.signal(());
break;
}
}
Timer::after_secs(NTP_WAIT_SECOND).await;
}
}
}
#[embassy_executor::task]
async fn online_task(
offline_signal: &'static Signal<CriticalSectionRawMutex, ()>,
mut wireless_control: cyw43::Control<'static>,
) -> ! {
loop {
// search and join wireless network
for (ssid, password) in WIRELESS_CREDENTIALS.iter().cycle() {
info!("Search for wireless network: {ssid}");
if wireless_control
.join(ssid, JoinOptions::new(password.as_bytes()))
.await
.is_ok()
{
info!("Successfully joined wireless network: {ssid}");
break;
} else if ssid
== &WIRELESS_CREDENTIALS
.last()
.expect("The list of wireless credentials should not be empty")
.0
{
info!("Unable to join any wireless network with provided credentials");
info!("Sleep for {WIRELESS_SCAN_WAIT_SECOND} seconds before rescan");
Timer::after_secs(WIRELESS_SCAN_WAIT_SECOND).await;
}
}
// turn on led for connected wireless
wireless_control.gpio_set(0, true).await;
// wait for offline signal
offline_signal.wait().await;
info!("Network is down");
// disconnect wireless
info!("Reset wireless");
wireless_control.leave().await;
// turn off led for disconnected network
wireless_control.gpio_set(0, false).await;
}
}
#[embassy_executor::task]
async fn timer_task(clock: &'static PicowClock) -> ! {
loop {
info!("The current time is: {}", clock.now().await);
Timer::after_secs(TIMER_WAIT_SECOND).await;
}
}
#[embassy_executor::task]
async fn wireless_task(
wireless_runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>,
@ -58,7 +184,11 @@ async fn wireless_task(
}
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
async fn main(spawner: Spawner) {
// system clock
static CLOCK: StaticCell<PicowClock> = StaticCell::new();
let clock = CLOCK.init(PicowClock::new());
// hardware abstraction layer
let hal = embassy_rp::init(Default::default());
@ -126,73 +256,27 @@ async fn main(spawner: Spawner) -> ! {
RoscRng.next_u64(),
);
// offline signal
static OFFLINE_SIGNAL: StaticCell<Signal<CriticalSectionRawMutex, ()>> = StaticCell::new();
let offline_signal = OFFLINE_SIGNAL.init(Signal::new());
// spawn online task
spawner
.spawn(online_task(offline_signal, wireless_control))
.expect("Online task should not fail to spawn");
// spawn network task
spawner
.spawn(network_task(network_runner))
.expect("Network task should not fail to spawn");
// search and join wireless network
for (ssid, password) in WIRELESS_CREDENTIALS.iter().cycle() {
info!("Searching for wireless network: {ssid}");
if let Ok(_) = wireless_control
.join(ssid, JoinOptions::new(password.as_bytes()))
.await
{
info!("Successfully joined wireless network: {ssid}");
break;
} else if ssid
== &WIRELESS_CREDENTIALS
.last()
.expect("The list of wireless credentials should not be empty")
.0
{
info!("Unable to join any wireless network with provided credentials");
info!("Sleep for {WAIT_SECOND} seconds before rescan");
Timer::after_secs(WAIT_SECOND).await;
}
}
// span ntp task
spawner
.spawn(ntp_task(clock, network_stack, offline_signal))
.expect("NTP task should not fail to spawn");
// wait for network stack
info!("Wait for network setup");
network_stack.wait_config_up().await;
network_stack.wait_link_up().await;
info!("Successfully setup network stack");
info!("Network configuration: {:?}", network_stack.config_v4());
// turn on led to indicate network connection
wireless_control.gpio_set(0, true).await;
// dns client
let dns_client = DnsSocket::new(network_stack);
// udp client
// let mut udp_buffer = PicowUDPBuffer::default();
// let udp_client = udp_buffer.socket(network_stack);
loop {
let ntp_ipv4 = match dns_client.query(NTP_ENDPOINT, DnsQueryType::A).await {
Ok(address) => match address.first() {
Some(IpAddress::Ipv4(ipv4)) => ipv4.octets(),
None => {
info!(
"DNS A record lookup returned no IPv4 address: {}",
NTP_ENDPOINT
);
continue;
}
},
Err(error) => {
info!(
"DNS A record lookup returned error: {}, {:?}",
NTP_ENDPOINT, error
);
continue;
}
};
info!(
"Resolved IPv4 address for {}: {}.{}.{}.{}",
NTP_ENDPOINT, ntp_ipv4[0], ntp_ipv4[1], ntp_ipv4[1], ntp_ipv4[3]
);
Timer::after_secs(WAIT_SECOND).await;
}
// spawn timer task
spawner
.spawn(timer_task(clock))
.expect("Timer task should not fail to spawn");
}

View file

@ -1,67 +0,0 @@
use embassy_net::{
udp::{PacketMetadata, UdpSocket},
Stack,
};
use embassy_time::{Duration, Instant};
use sntpc::NtpTimestampGenerator;
const UDP_BUFFER_SIZE_BYTE: usize = 1 << 12;
const UDP_METADATA_SIZE: usize = 1 << 3;
#[derive(Copy, Clone)]
pub struct PicowTimestampGenerator {
duration: Duration,
}
impl Default for PicowTimestampGenerator {
fn default() -> Self {
Self {
duration: Instant::now().duration_since(Instant::MIN),
}
}
}
impl NtpTimestampGenerator for PicowTimestampGenerator {
fn init(&mut self) {
self.duration = Instant::now().duration_since(Instant::MIN);
}
fn timestamp_sec(&self) -> u64 {
self.duration.as_secs()
}
fn timestamp_subsec_micros(&self) -> u32 {
(self.duration.as_micros() % 1000000) as u32
}
}
#[derive(Debug)]
pub struct PicowUDPBuffer {
receive_buffer: [u8; UDP_BUFFER_SIZE_BYTE],
receive_metadata: [PacketMetadata; UDP_METADATA_SIZE],
transmit_buffer: [u8; UDP_BUFFER_SIZE_BYTE],
transmit_metadata: [PacketMetadata; UDP_METADATA_SIZE],
}
impl Default for PicowUDPBuffer {
fn default() -> Self {
Self {
receive_buffer: [0; UDP_BUFFER_SIZE_BYTE],
receive_metadata: [PacketMetadata::EMPTY; UDP_METADATA_SIZE],
transmit_buffer: [0; UDP_BUFFER_SIZE_BYTE],
transmit_metadata: [PacketMetadata::EMPTY; UDP_METADATA_SIZE],
}
}
}
impl PicowUDPBuffer {
pub fn socket<'me>(&'me mut self, network_stack: Stack<'me>) -> UdpSocket<'me> {
UdpSocket::new(
network_stack,
&mut self.receive_metadata,
&mut self.receive_buffer,
&mut self.transmit_metadata,
&mut self.transmit_buffer,
)
}
}

181
src/time.rs Normal file
View file

@ -0,0 +1,181 @@
use core::fmt;
use chrono::{DateTime, Duration, Utc};
use embassy_net::{
udp::{PacketMetadata, UdpSocket},
IpAddress, IpEndpoint, Stack,
};
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
use embassy_time::Instant;
use no_std_net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
use sntpc::{async_impl::NtpUdpSocket, NtpTimestampGenerator};
const UDP_BUFFER_SIZE_BYTE: usize = 1 << 12;
const UDP_METADATA_SIZE: usize = 1 << 4;
pub struct PicowClock {
startup_timestamp: Mutex<CriticalSectionRawMutex, DateTime<Utc>>,
}
impl PicowClock {
pub fn new() -> Self {
Self {
startup_timestamp: Mutex::new(DateTime::UNIX_EPOCH),
}
}
pub async fn now(&self) -> DateTime<Utc> {
*self.startup_timestamp.lock().await
+ Duration::microseconds(Instant::now().as_micros() as i64)
}
pub async fn set_time(&self, now: DateTime<Utc>) {
if let Some(corrected_startup_timestamp) =
now.checked_sub_signed(Duration::microseconds(Instant::now().as_micros() as i64))
{
*self.startup_timestamp.lock().await = corrected_startup_timestamp;
}
}
}
#[derive(Clone, Copy)]
pub struct PicowTimestampGenerator {
now: DateTime<Utc>,
}
impl NtpTimestampGenerator for PicowTimestampGenerator {
fn init(&mut self) {}
fn timestamp_sec(&self) -> u64 {
self.now.timestamp() as u64
}
fn timestamp_subsec_micros(&self) -> u32 {
self.now.timestamp_subsec_micros()
}
}
impl PicowTimestampGenerator {
pub async fn from_clock(clock: &PicowClock) -> Self {
Self {
now: clock.now().await,
}
}
}
pub struct PicowUDPSocket<'me> {
socket: UdpSocket<'me>,
}
impl fmt::Debug for PicowUDPSocket<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PicowUDPSocket")
.field("socket", &self.socket.endpoint())
.finish()
}
}
impl<'me> NtpUdpSocket for PicowUDPSocket<'me> {
async fn send_to<T: ToSocketAddrs + Send>(&self, buf: &[u8], addr: T) -> sntpc::Result<usize> {
use sntpc::Error::*;
let socket_address = addr
.to_socket_addrs()
.map_err(|_| AddressResolve)?
.next()
.ok_or(AddressResolve)?;
// TODO: Simplify conversion once no-std-net is deprecated
let endpoint = IpEndpoint::new(
match socket_address.ip() {
IpAddr::V4(ipv4) => {
let octets = ipv4.octets();
IpAddress::v4(octets[0], octets[1], octets[2], octets[3])
}
IpAddr::V6(ipv6) => {
let segments = ipv6.segments();
IpAddress::v6(
segments[0],
segments[1],
segments[2],
segments[3],
segments[4],
segments[5],
segments[6],
segments[7],
)
}
},
socket_address.port(),
);
self.socket
.send_to(buf, endpoint)
.await
.map_err(|_| Network)?;
Ok(buf.len())
}
async fn recv_from(&self, buf: &mut [u8]) -> sntpc::Result<(usize, SocketAddr)> {
use sntpc::Error::*;
let (size, metadata) = self.socket.recv_from(buf).await.map_err(|_| Network)?;
Ok((
size,
SocketAddr::new(
// TODO: Simplify conversion once no-std-net is deprecated
match metadata.endpoint.addr {
IpAddress::Ipv4(ipv4) => {
let octets = ipv4.octets();
IpAddr::V4(Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]))
}
IpAddress::Ipv6(ipv6) => {
let segments = ipv6.segments();
IpAddr::V6(Ipv6Addr::new(
segments[0],
segments[1],
segments[2],
segments[3],
segments[4],
segments[5],
segments[6],
segments[7],
))
}
},
metadata.endpoint.port,
),
))
}
}
pub struct PicowUDPBuffer {
receive_buffer: [u8; UDP_BUFFER_SIZE_BYTE],
receive_metadata: [PacketMetadata; UDP_METADATA_SIZE],
transmit_buffer: [u8; UDP_BUFFER_SIZE_BYTE],
transmit_metadata: [PacketMetadata; UDP_METADATA_SIZE],
}
impl Default for PicowUDPBuffer {
fn default() -> Self {
Self {
receive_buffer: [0; UDP_BUFFER_SIZE_BYTE],
receive_metadata: [PacketMetadata::EMPTY; UDP_METADATA_SIZE],
transmit_buffer: [0; UDP_BUFFER_SIZE_BYTE],
transmit_metadata: [PacketMetadata::EMPTY; UDP_METADATA_SIZE],
}
}
}
impl PicowUDPBuffer {
pub fn socket<'me>(&'me mut self, network_stack: Stack<'me>, port: u16) -> PicowUDPSocket<'me> {
let mut socket = UdpSocket::new(
network_stack,
&mut self.receive_metadata,
&mut self.receive_buffer,
&mut self.transmit_metadata,
&mut self.transmit_buffer,
);
socket
.bind(port)
.expect("UDP socket should be able to bind to local port");
PicowUDPSocket { socket }
}
}