mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-26 20:00:32 +00:00
feat(example): Add SNTP example to show how to update Rtc to the current time (#3995)
* feat(example): Add SNTP example to show how to update Rtc * Bump embassy-net to 0.7.0 for compatibility * Revert back to task-arena to build on stable.
This commit is contained in:
parent
5f1c1feed9
commit
3f29f0571c
26
examples/wifi/sntp/.cargo/config.toml
Normal file
26
examples/wifi/sntp/.cargo/config.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[target.'cfg(target_arch = "riscv32")']
|
||||
runner = "espflash flash --monitor"
|
||||
rustflags = [
|
||||
"-C", "link-arg=-Tlinkall.x",
|
||||
"-C", "force-frame-pointers",
|
||||
]
|
||||
|
||||
[target.'cfg(target_arch = "xtensa")']
|
||||
runner = "espflash flash --monitor"
|
||||
rustflags = [
|
||||
# GNU LD
|
||||
"-C", "link-arg=-Wl,-Tlinkall.x",
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
|
||||
# LLD
|
||||
# "-C", "link-arg=-Tlinkall.x",
|
||||
# "-C", "linker=rust-lld",
|
||||
]
|
||||
|
||||
[env]
|
||||
ESP_LOG = "info"
|
||||
SSID = "SSID"
|
||||
PASSWORD = "PASSWORD"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
94
examples/wifi/sntp/Cargo.toml
Normal file
94
examples/wifi/sntp/Cargo.toml
Normal file
@ -0,0 +1,94 @@
|
||||
[package]
|
||||
name = "sntp"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0.0"
|
||||
embassy-executor = { version = "0.7.0", features = ["task-arena-size-10240"] }
|
||||
embassy-net = { version = "0.7.0", features = [
|
||||
"dhcpv4",
|
||||
"medium-ethernet",
|
||||
"udp",
|
||||
"dns",
|
||||
] }
|
||||
embassy-time = "0.4.0"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = { path = "../../../esp-alloc" }
|
||||
esp-backtrace = { path = "../../../esp-backtrace", features = [
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-bootloader-esp-idf = { path = "../../../esp-bootloader-esp-idf" }
|
||||
esp-hal-embassy = { path = "../../../esp-hal-embassy" }
|
||||
esp-hal = { path = "../../../esp-hal", features = ["log-04", "unstable"] }
|
||||
esp-println = { path = "../../../esp-println", features = ["log-04"] }
|
||||
esp-preempt = { path = "../../../esp-preempt", features = ["log-04"] }
|
||||
log = "0.4.17"
|
||||
esp-radio = { path = "../../../esp-radio", features = [
|
||||
"log-04",
|
||||
"unstable",
|
||||
"wifi",
|
||||
] }
|
||||
static_cell = "2.1.0"
|
||||
sntpc = { version = "0.6.0", default-features = false, features = [
|
||||
"embassy-socket",
|
||||
] }
|
||||
jiff = { version = "0.2.10", default-features = false, features = ["static"] }
|
||||
|
||||
[features]
|
||||
esp32 = [
|
||||
"esp-backtrace/esp32",
|
||||
"esp-bootloader-esp-idf/esp32",
|
||||
"esp-hal-embassy/esp32",
|
||||
"esp-hal/esp32",
|
||||
"esp-preempt/esp32",
|
||||
"esp-radio/esp32",
|
||||
]
|
||||
esp32c2 = [
|
||||
"esp-backtrace/esp32c2",
|
||||
"esp-bootloader-esp-idf/esp32c2",
|
||||
"esp-hal-embassy/esp32c2",
|
||||
"esp-hal/esp32c2",
|
||||
"esp-preempt/esp32c2",
|
||||
"esp-radio/esp32c2",
|
||||
]
|
||||
esp32c3 = [
|
||||
"esp-backtrace/esp32c3",
|
||||
"esp-bootloader-esp-idf/esp32c3",
|
||||
"esp-hal-embassy/esp32c3",
|
||||
"esp-hal/esp32c3",
|
||||
"esp-preempt/esp32c3",
|
||||
"esp-radio/esp32c3",
|
||||
]
|
||||
esp32c6 = [
|
||||
"esp-backtrace/esp32c6",
|
||||
"esp-bootloader-esp-idf/esp32c6",
|
||||
"esp-hal-embassy/esp32c6",
|
||||
"esp-hal/esp32c6",
|
||||
"esp-preempt/esp32c6",
|
||||
"esp-radio/esp32c6",
|
||||
]
|
||||
esp32s2 = [
|
||||
"esp-backtrace/esp32s2",
|
||||
"esp-bootloader-esp-idf/esp32s2",
|
||||
"esp-hal-embassy/esp32s2",
|
||||
"esp-hal/esp32s2",
|
||||
"esp-preempt/esp32s2",
|
||||
"esp-radio/esp32s2",
|
||||
]
|
||||
esp32s3 = [
|
||||
"esp-backtrace/esp32s3",
|
||||
"esp-bootloader-esp-idf/esp32s3",
|
||||
"esp-hal-embassy/esp32s3",
|
||||
"esp-hal/esp32s3",
|
||||
"esp-preempt/esp32s3",
|
||||
"esp-radio/esp32s3",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
debug-assertions = true
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
254
examples/wifi/sntp/src/main.rs
Normal file
254
examples/wifi/sntp/src/main.rs
Normal file
@ -0,0 +1,254 @@
|
||||
//! Embassy SNTP example
|
||||
//!
|
||||
//!
|
||||
//! Set SSID and PASSWORD env variable before running this example.
|
||||
//!
|
||||
//! This gets an ip address via DHCP then performs an SNTP request to update the RTC time with the
|
||||
//! response. The RTC time is then compared with the received data parsed with jiff.
|
||||
//! You can change the timezone to your local timezone.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::net::{IpAddr, SocketAddr};
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_net::{
|
||||
Runner,
|
||||
StackResources,
|
||||
dns::DnsQueryType,
|
||||
udp::{PacketMetadata, UdpSocket},
|
||||
};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_alloc as _;
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{clock::CpuClock, rng::Rng, rtc_cntl::Rtc, timer::timg::TimerGroup};
|
||||
use esp_println::println;
|
||||
use esp_radio::{
|
||||
Controller,
|
||||
wifi::{ClientConfig, Config, ScanConfig, WifiController, WifiDevice, WifiEvent, WifiState},
|
||||
};
|
||||
use log::{error, info};
|
||||
use sntpc::{NtpContext, NtpTimestampGenerator, get_time};
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html
|
||||
macro_rules! mk_static {
|
||||
($t:ty,$val:expr) => {{
|
||||
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
||||
#[deny(unused_attributes)]
|
||||
let x = STATIC_CELL.uninit().write(($val));
|
||||
x
|
||||
}};
|
||||
}
|
||||
|
||||
const SSID: &str = env!("SSID");
|
||||
const PASSWORD: &str = env!("PASSWORD");
|
||||
const TIMEZONE: jiff::tz::TimeZone = jiff::tz::get!("UTC");
|
||||
const NTP_SERVER: &str = "pool.ntp.org";
|
||||
|
||||
/// Microseconds in a second
|
||||
const USEC_IN_SEC: u64 = 1_000_000;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Timestamp<'a> {
|
||||
rtc: &'a Rtc<'a>,
|
||||
current_time_us: u64,
|
||||
}
|
||||
|
||||
impl NtpTimestampGenerator for Timestamp<'_> {
|
||||
fn init(&mut self) {
|
||||
self.current_time_us = self.rtc.current_time_us();
|
||||
}
|
||||
|
||||
fn timestamp_sec(&self) -> u64 {
|
||||
self.current_time_us / 1_000_000
|
||||
}
|
||||
|
||||
fn timestamp_subsec_micros(&self) -> u32 {
|
||||
(self.current_time_us % 1_000_000) as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
esp_println::logger::init_logger_from_env();
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
let rtc = Rtc::new(peripherals.LPWR);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 72 * 1024);
|
||||
|
||||
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||
esp_preempt::init(timg0.timer0);
|
||||
|
||||
let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());
|
||||
|
||||
let (controller, interfaces) = esp_radio::wifi::new(esp_radio_ctrl, peripherals.WIFI).unwrap();
|
||||
|
||||
let wifi_interface = interfaces.sta;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "esp32")] {
|
||||
let timg1 = TimerGroup::new(peripherals.TIMG1);
|
||||
esp_hal_embassy::init(timg1.timer0);
|
||||
} else {
|
||||
use esp_hal::timer::systimer::SystemTimer;
|
||||
let systimer = SystemTimer::new(peripherals.SYSTIMER);
|
||||
esp_hal_embassy::init(systimer.alarm0);
|
||||
}
|
||||
}
|
||||
|
||||
let config = embassy_net::Config::dhcpv4(Default::default());
|
||||
|
||||
let rng = Rng::new();
|
||||
let seed = (rng.random() as u64) << 32 | rng.random() as u64;
|
||||
|
||||
// Init network stack
|
||||
let (stack, runner) = embassy_net::new(
|
||||
wifi_interface,
|
||||
config,
|
||||
mk_static!(StackResources<3>, StackResources::<3>::new()),
|
||||
seed,
|
||||
);
|
||||
|
||||
spawner.spawn(connection(controller)).ok();
|
||||
spawner.spawn(net_task(runner)).ok();
|
||||
|
||||
let mut rx_meta = [PacketMetadata::EMPTY; 16];
|
||||
let mut rx_buffer = [0; 4096];
|
||||
let mut tx_meta = [PacketMetadata::EMPTY; 16];
|
||||
let mut tx_buffer = [0; 4096];
|
||||
|
||||
loop {
|
||||
if stack.is_link_up() {
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
println!("Waiting to get IP address...");
|
||||
loop {
|
||||
if let Some(config) = stack.config_v4() {
|
||||
println!("Got IP: {}", config.address);
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
let ntp_addrs = stack.dns_query(NTP_SERVER, DnsQueryType::A).await.unwrap();
|
||||
|
||||
if ntp_addrs.is_empty() {
|
||||
panic!("Failed to resolve DNS. Empty result");
|
||||
}
|
||||
|
||||
let mut socket = UdpSocket::new(
|
||||
stack,
|
||||
&mut rx_meta,
|
||||
&mut rx_buffer,
|
||||
&mut tx_meta,
|
||||
&mut tx_buffer,
|
||||
);
|
||||
|
||||
socket.bind(123).unwrap();
|
||||
|
||||
// Display initial Rtc time before synchronization
|
||||
let now = jiff::Timestamp::from_microsecond(rtc.current_time_us() as i64).unwrap();
|
||||
info!("Rtc: {now}");
|
||||
|
||||
loop {
|
||||
let addr: IpAddr = ntp_addrs[0].into();
|
||||
let result = get_time(
|
||||
SocketAddr::from((addr, 123)),
|
||||
&socket,
|
||||
NtpContext::new(Timestamp {
|
||||
rtc: &rtc,
|
||||
current_time_us: 0,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(time) => {
|
||||
// Set time immediately after receiving to reduce time offset.
|
||||
rtc.set_current_time_us(
|
||||
(time.sec() as u64 * USEC_IN_SEC)
|
||||
+ ((time.sec_fraction() as u64 * USEC_IN_SEC) >> 32),
|
||||
);
|
||||
|
||||
// Compare RTC to parsed time
|
||||
info!(
|
||||
"Response: {:?}\nTime: {}\nRtc : {}",
|
||||
time,
|
||||
// Create a Jiff Timestamp from seconds and nanoseconds
|
||||
jiff::Timestamp::from_second(time.sec() as i64)
|
||||
.unwrap()
|
||||
.checked_add(
|
||||
jiff::Span::new()
|
||||
.nanoseconds((time.seconds_fraction as i64 * 1_000_000_000) >> 32),
|
||||
)
|
||||
.unwrap()
|
||||
.to_zoned(TIMEZONE),
|
||||
jiff::Timestamp::from_microsecond(rtc.current_time_us() as i64)
|
||||
.unwrap()
|
||||
.to_zoned(TIMEZONE)
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error getting time: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
Timer::after(Duration::from_secs(10)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn connection(mut controller: WifiController<'static>) {
|
||||
println!("start connection task");
|
||||
println!("Device capabilities: {:?}", controller.capabilities());
|
||||
loop {
|
||||
if esp_radio::wifi::wifi_state() == WifiState::StaConnected {
|
||||
// wait until we're no longer connected
|
||||
controller.wait_for_event(WifiEvent::StaDisconnected).await;
|
||||
Timer::after(Duration::from_millis(5000)).await
|
||||
}
|
||||
if !matches!(controller.is_started(), Ok(true)) {
|
||||
let client_config = Config::Client({
|
||||
let mut config = ClientConfig::default();
|
||||
config.ssid = SSID.into();
|
||||
config.password = PASSWORD.into();
|
||||
config
|
||||
});
|
||||
controller.set_configuration(&client_config).unwrap();
|
||||
println!("Starting wifi");
|
||||
controller.start_async().await.unwrap();
|
||||
println!("Wifi started!");
|
||||
|
||||
println!("Scan");
|
||||
let scan_config = ScanConfig::default().with_max(10);
|
||||
let result = controller
|
||||
.scan_with_config_async(scan_config)
|
||||
.await
|
||||
.unwrap();
|
||||
for ap in result {
|
||||
println!("{:?}", ap);
|
||||
}
|
||||
}
|
||||
println!("About to connect...");
|
||||
|
||||
match controller.connect_async().await {
|
||||
Ok(_) => println!("Wifi connected!"),
|
||||
Err(e) => {
|
||||
println!("Failed to connect to wifi: {e:?}");
|
||||
Timer::after(Duration::from_millis(5000)).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
|
||||
runner.run().await
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user