N! T! P!
This commit is contained in:
parent
97238b56f7
commit
4d087a56d0
7 changed files with 382 additions and 169 deletions
52
Cargo.lock
generated
52
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
11
Cargo.toml
11
Cargo.toml
|
|
@ -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
18
flake.lock
generated
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
220
src/main.rs
220
src/main.rs
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
181
src/time.rs
Normal 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 }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue