Compare commits
2 commits
23fd3babfc
...
5b58040975
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5b58040975 | ||
![]() |
1970d37d51 |
4 changed files with 146 additions and 92 deletions
|
@ -9,6 +9,9 @@ pub(crate) const T_ON: &'static str = "17:00";
|
|||
/// End of "on" cycle for switch, in 24 hour "HH:MM" format.
|
||||
pub(crate) const T_OFF: &'static str = "09:00";
|
||||
|
||||
/// Time zone for T_ON and T_OFF.
|
||||
pub(crate) const TZ: chrono_tz::Tz = chrono_tz::US::Pacific;
|
||||
|
||||
/// Access point SSID.
|
||||
pub(crate) const WIFI_SSID: &'static str = "Example";
|
||||
|
||||
|
|
153
src/main.rs
153
src/main.rs
|
@ -2,29 +2,38 @@ use std::default::Default;
|
|||
|
||||
use anyhow::{bail, Result};
|
||||
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<P: OutputPin> {
|
||||
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,40 +41,14 @@ 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);
|
||||
info!("Current time: {}", now);
|
||||
let dt = Utc::now().with_timezone(&config::TZ);
|
||||
info!("Current time: {}", dt);
|
||||
|
||||
let t = now.time();
|
||||
let t = dt.time();
|
||||
|
||||
let active = match (t_on < t_off, t > t_on, t > t_off) {
|
||||
let switch_active = match (t_on < t_off, t > t_on, t > t_off) {
|
||||
// Active period falls within single day, and t falls between t_on
|
||||
// and t_off.
|
||||
(true, true, false) => true,
|
||||
|
@ -77,10 +60,10 @@ fn main() -> Result<()> {
|
|||
_ => false,
|
||||
};
|
||||
|
||||
if active {
|
||||
switch.set_high()?;
|
||||
if switch_active {
|
||||
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 +72,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<P = Modem> + 'static,
|
||||
sysloop: EspSystemEventLoop,
|
||||
) -> Result<Box<EspWifi<'static>>> {
|
||||
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<Box<AppState<Gpio7>>> {
|
||||
// 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,
|
||||
}))
|
||||
}
|
||||
|
|
20
src/ntp.rs
Normal file
20
src/ntp.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
62
src/wifi.rs
Normal file
62
src/wifi.rs
Normal file
|
@ -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<P = Modem> + 'static,
|
||||
sysloop: EspSystemEventLoop,
|
||||
) -> Result<Box<EspWifi<'static>>> {
|
||||
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))
|
||||
}
|
Loading…
Add table
Reference in a new issue