From 1970d37d5137b9cf7ef3340142f440fd9adc2cd8 Mon Sep 17 00:00:00 2001 From: Brent Schroeter Date: Sat, 6 Sep 2025 12:31:06 -0700 Subject: [PATCH] refactor setup tasks --- src/main.rs | 142 +++++++++++++++++++++------------------------------- src/ntp.rs | 20 ++++++++ src/wifi.rs | 62 +++++++++++++++++++++++ 3 files changed, 138 insertions(+), 86 deletions(-) create mode 100644 src/ntp.rs create mode 100644 src/wifi.rs diff --git a/src/main.rs b/src/main.rs index 34f3896..2d346c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,26 +5,36 @@ use chrono::{NaiveTime, Utc}; use chrono_tz::US::Pacific; use esp_idf_svc::{ eventloop::EspSystemEventLoop, - hal::{delay::FreeRtos, gpio::*, modem::Modem, peripheral::Peripheral, prelude::Peripherals}, - sntp::{EspSntp, SntpConf, SyncMode, SyncStatus}, - wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi}, + hal::{ + delay::FreeRtos, + gpio::{self, Gpio7, OutputPin, PinDriver}, + prelude::Peripherals, + }, + sntp::{EspSntp, SntpConf, SyncMode}, + wifi::EspWifi, }; use log::info; mod config; +mod ntp; +mod wifi; -const SNTP_STATUS_POLL_INTVL_MS: u32 = 2000; /// Delay between executions of the main control loop, in milliseconds. const CONTROL_LOOP_INTVL: u32 = 60000; -fn main() -> Result<()> { - // It is necessary to call this function once. Otherwise some patches to - // the runtime implemented by esp-idf-sys might not link properly. Refer - // to https://github.com/esp-rs/esp-idf-template/issues/71. - esp_idf_svc::sys::link_patches(); +/// Struct holding resources which should remain in scope throughout execution. +struct AppState { + ntp: EspSntp<'static>, + switch_driver: PinDriver<'static, P, gpio::Output>, + wifi: EspWifi<'static>, +} - // Bind the log crate to the ESP Logging facilities - esp_idf_svc::log::EspLogger::initialize_default(); +fn main() -> Result<()> { + let AppState::<_> { + ntp: _ntp, + mut switch_driver, + wifi: _wifi, + } = *setup()?; let t_on = NaiveTime::parse_from_str(config::T_ON, "%H:%M")?; let t_off = NaiveTime::parse_from_str(config::T_OFF, "%H:%M")?; @@ -32,32 +42,6 @@ fn main() -> Result<()> { bail!("t_on and t_off must have distinct values"); } - let peripherals = Peripherals::take()?; - - let mut switch = PinDriver::output(peripherals.pins.gpio7)?; - switch.set_low()?; - - let sysloop = EspSystemEventLoop::take()?; - - // Dropping EspWifi shuts down the connection, so this variable must remain - // in scope. - let _wifi = init_wifi(peripherals.modem, sysloop); - - // SNTP client will continue running in the background until this value is - // dropped. - let ntp = EspSntp::new(&SntpConf { - servers: [config::SNTP_SERVER], - sync_mode: SyncMode::Smooth, - ..Default::default() - })?; - - // get_sync_status() returns "Completed" one time and then reverts to - // "Reset" on subsequent calls. - while ntp.get_sync_status() != SyncStatus::Completed { - info!("Waiting for SNTP to sync..."); - FreeRtos::delay_ms(SNTP_STATUS_POLL_INTVL_MS); - } - // ======== Main Control Loop ======== // loop { let now = Utc::now().with_timezone(&Pacific); @@ -78,9 +62,9 @@ fn main() -> Result<()> { }; if active { - switch.set_high()?; + switch_driver.set_high()?; } else { - switch.set_low()?; + switch_driver.set_low()?; } // TODO: enter low power mode ("light sleep" or "deep sleep") instead @@ -89,54 +73,40 @@ fn main() -> Result<()> { } } -/// Start WiFi module and connect to access point. Returns an error if either -/// of WiFi startup or connection fail. -fn init_wifi( - modem: impl Peripheral

+ 'static, - sysloop: EspSystemEventLoop, -) -> Result>> { - let auth_method = AuthMethod::WPA2Personal; - let mut esp_wifi = EspWifi::new(modem, sysloop.clone(), None)?; - let mut wifi = BlockingWifi::wrap(&mut esp_wifi, sysloop)?; - wifi.set_configuration(&Configuration::Client(ClientConfiguration::default()))?; +/// Runs initial application startup tasks, including connecting WiFi, syncing +/// system time over SNTP, and setting the initial switch state. May block for +/// several seconds, or indefinitely if initial SNTP sync is not successful. +fn setup() -> Result>> { + // It is necessary to call this function once. Otherwise some patches to + // the runtime implemented by esp-idf-sys might not link properly. Refer + // to https://github.com/esp-rs/esp-idf-template/issues/71. + esp_idf_svc::sys::link_patches(); - info!("Starting wifi..."); - wifi.start()?; - info!("Scanning..."); - let ap_infos = wifi.scan()?; - let ours = ap_infos.into_iter().find(|a| a.ssid == config::WIFI_SSID); - let channel = if let Some(ours) = ours { - info!( - "Found configured access point {} on channel {}", - config::WIFI_SSID, - ours.channel - ); - Some(ours.channel) - } else { - info!( - "Configured access point {} not found during scanning, will go with unknown channel", - config::WIFI_SSID - ); - None - }; - wifi.set_configuration(&Configuration::Client(ClientConfiguration { - ssid: config::WIFI_SSID - .try_into() - .expect("Could not parse the given SSID into WiFi config"), - password: config::WIFI_PASS - .try_into() - .expect("Could not parse the given password into WiFi config"), - channel, - auth_method, + // Bind the log crate to the ESP Logging facilities + esp_idf_svc::log::EspLogger::initialize_default(); + + let peripherals = Peripherals::take()?; + let sysloop = EspSystemEventLoop::take()?; + + let mut switch_driver = PinDriver::output(peripherals.pins.gpio7)?; + switch_driver.set_low()?; + + // Dropping EspWifi shuts down the connection, so this variable must remain + // in scope. + let wifi = crate::wifi::init(peripherals.modem, sysloop)?; + + // SNTP client will continue running in the background until this value is + // dropped. + let ntp = EspSntp::new(&SntpConf { + servers: [config::SNTP_SERVER], + sync_mode: SyncMode::Smooth, ..Default::default() - }))?; + })?; + crate::ntp::wait_for_sync(&ntp); - info!("Connecting wifi..."); - wifi.connect()?; - info!("Waiting for DHCP lease..."); - wifi.wait_netif_up()?; - let ip_info = wifi.wifi().sta_netif().get_ip_info()?; - info!("Wifi DHCP info: {:?}", ip_info); - - Ok(Box::new(esp_wifi)) + Ok(Box::new(AppState::<_> { + ntp, + switch_driver, + wifi: *wifi, + })) } diff --git a/src/ntp.rs b/src/ntp.rs new file mode 100644 index 0000000..ebe750c --- /dev/null +++ b/src/ntp.rs @@ -0,0 +1,20 @@ +use esp_idf_svc::{ + hal::delay::FreeRtos, + sntp::{EspSntp, SyncStatus}, +}; +use log::info; + +/// Delay between checks of the initial NTP sync check, in milliseconds. +const SNTP_STATUS_POLL_INTVL: u32 = 2000; + +/// Blocks until the SNTP client reports successful synchronization. +/// +/// Note that the SNTP client's status flag is reset after it is read. Calling +/// this function twice will cause the second invocation to wait for the next +/// synchronization event. +pub(crate) fn wait_for_sync(client: &EspSntp) { + while client.get_sync_status() != SyncStatus::Completed { + info!("Waiting for SNTP to sync..."); + FreeRtos::delay_ms(SNTP_STATUS_POLL_INTVL); + } +} diff --git a/src/wifi.rs b/src/wifi.rs new file mode 100644 index 0000000..de4cd39 --- /dev/null +++ b/src/wifi.rs @@ -0,0 +1,62 @@ +use anyhow::Result; +use esp_idf_svc::{ + eventloop::EspSystemEventLoop, + hal::{modem::Modem, peripheral::Peripheral}, + wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi}, +}; +use log::{debug, info}; + +use crate::config; + +/// Start WiFi module and connect to access point. Returns an error if either +/// of WiFi startup or connection fail. +pub(crate) fn init( + modem: impl Peripheral

+ 'static, + sysloop: EspSystemEventLoop, +) -> Result>> { + let auth_method = AuthMethod::WPA2Personal; + let mut esp_wifi = EspWifi::new(modem, sysloop.clone(), None)?; + let mut wifi = BlockingWifi::wrap(&mut esp_wifi, sysloop)?; + wifi.set_configuration(&Configuration::Client(ClientConfiguration::default()))?; + + info!("Starting wifi..."); + wifi.start()?; + debug!("Scanning..."); + let ap_infos = wifi.scan()?; + let ours = ap_infos.into_iter().find(|a| a.ssid == config::WIFI_SSID); + let channel = if let Some(ours) = ours { + debug!( + "Found configured access point {} on channel {}", + config::WIFI_SSID, + ours.channel + ); + Some(ours.channel) + } else { + debug!( + "Configured access point {} not found during scanning, will go with unknown channel", + config::WIFI_SSID + ); + None + }; + wifi.set_configuration(&Configuration::Client(ClientConfiguration { + ssid: config::WIFI_SSID + .try_into() + .expect("Could not parse the given SSID into WiFi config"), + password: config::WIFI_PASS + .try_into() + .expect("Could not parse the given password into WiFi config"), + channel, + auth_method, + ..Default::default() + }))?; + + debug!("Connecting wifi..."); + wifi.connect()?; + debug!("Waiting for DHCP lease..."); + wifi.wait_netif_up()?; + + let ip_info = wifi.wifi().sta_netif().get_ip_info()?; + info!("Wifi DHCP info: {:?}", ip_info); + + Ok(Box::new(esp_wifi)) +}