diff --git a/src/main.rs b/src/main.rs
index 34f3896..5cd19af 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,26 +5,32 @@ 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::*, 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>,
+ 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: mut_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 +38,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 +58,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 +69,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(app_state.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,
+ }))
}
diff --git a/src/ntp.rs b/src/ntp.rs
new file mode 100644
index 0000000..33d4eea
--- /dev/null
+++ b/src/ntp.rs
@@ -0,0 +1,17 @@
+use esp_idf_svc::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 ntp.get_sync_status() != SyncStatus::Completed {
+ info!("Waiting for SNTP to sync...");
+ FreeRtos::delay_ms(SNTP_STATUS_POLL_INTVL_MS);
+ }
+}
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))
+}