From ab4305a2d3f18b14e0cc82efba124ecee600946d Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 12 May 2024 22:11:32 +0200 Subject: [PATCH] feat(power): enable wifi on demand --- Cargo.lock | 2 + Cargo.toml | 4 ++ src/main.rs | 173 +++++++++++++++++++++++++++++++++++++++++----------- src/mqtt.rs | 19 ++++-- 4 files changed, 158 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1c0778..9e47bec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -815,11 +815,13 @@ version = "0.1.0" dependencies = [ "const-decoder", "embassy-executor", + "embassy-futures", "embassy-net", "embassy-sync 0.5.0 (git+https://github.com/embassy-rs/embassy.git?rev=4b4777)", "embassy-time", "embedded-io-async", "embedded-tls", + "enumset", "esp-alloc", "esp-backtrace", "esp-hal", diff --git a/Cargo.toml b/Cargo.toml index 6d62421..1367d87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,13 @@ bench = false [dependencies] const-decoder = "0.3.0" embassy-executor = { version = "0.5.0", features = ["nightly", "integrated-timers", "arch-riscv32", "executor-thread"] } +embassy-futures = "0.1.1" embassy-net = { version = "0.4.0", features = ["dhcpv4", "dhcpv4-hostname", "dns", "medium-ip", "proto-ipv4", "proto-ipv6", "tcp", "udp"] } embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "4b4777" } embassy-time = { version = "0.3.0" } embedded-io-async = "0.6.1" embedded-tls = { git = "https://github.com/drogue-iot/embedded-tls.git", rev = "f788e02", default-features = false, features = ["embedded-io-adapters"] } +enumset = "1.1.3" esp-alloc = "0.3.0" esp-backtrace = { version = "0.11.0", features = ["esp32c3", "exception-handler", "panic-handler", "println"] } esp-hal = { version = "0.16.1", features = ["embassy", "embassy-time-timg0", "esp32c3"] } @@ -40,6 +42,8 @@ opt-level = 3 [features] tls = [] +tls-sni = ["tls"] +real-world = [] [profile.release] opt-level = "s" diff --git a/src/main.rs b/src/main.rs index 6a38527..14c87d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,11 +16,14 @@ use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use embassy_executor::Executor; +use embassy_futures::block_on; +use embassy_futures::select::{self, select, select3, Either, Either3}; use embassy_net::{Config, DhcpConfig, Stack, StackResources}; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_time::{Duration, Timer}; +use enumset::enum_set; use esp_alloc::EspHeap; use esp_backtrace as _; use esp_hal::adc::{AdcPin, Attenuation, ADC}; @@ -50,6 +53,8 @@ static ALLOCATOR: EspHeap = EspHeap::empty(); const SSID: Option<&str> = option_env!("WIFI_SSID"); const PASSWORD: Option<&str> = option_env!("WIFI_PASSWORD"); +pub const WAKEUP_SCALER: u32 = if cfg!(feature = "real-word") { 20 } else { 1 }; + pub type NetworkStack = &'static Stack>; fn init_heap() { @@ -164,7 +169,7 @@ async fn moisture( .await .insert(Cow::from("moisture"), moisture.to_string()); } - Timer::after(Duration::from_secs(10) - warmup).await; + Timer::after((Duration::from_secs(10) - warmup) * WAKEUP_SCALER).await; } } @@ -190,14 +195,14 @@ async fn battery_monitor( .await .insert(Cow::from("vbat"), v_bat.to_string()); } - Timer::after(Duration::from_secs(30)).await; + Timer::after(Duration::from_secs(30) * WAKEUP_SCALER).await; } } static EXECUTOR: StaticCell = StaticCell::new(); #[entry] fn main() -> ! { - init_logger(log::LevelFilter::Info); + init_logger(log::LevelFilter::Debug); init_heap(); let peripherals = Peripherals::take(); @@ -272,67 +277,167 @@ fn main() -> ! { make_static!(Mutex::new(ADC::::new(peripherals.ADC1, adc1_config))); let executor = EXECUTOR.init(Executor::new()); + + let wifi_reqs: &'static _ = + make_static!(embassy_sync::channel::Channel::::new()); + let wifi_state: &'static _ = make_static!(embassy_sync::channel::Channel::< + NoopRawMutex, + WifiRequestState, + 3, + >::new()); + let wifi_handle = make_static!(WifiHandle { + wifi_state: wifi_state.receiver(), + wifi_request: wifi_reqs.sender(), + }); executor.run(move |spawner| { + spawner.spawn(publish_data( + &stack, + seed, + Duration::from_secs(60) * WAKEUP_SCALER, + wifi_handle.clone(), + )); if let (Some(ssid), Some(psk)) = (SSID.as_ref(), PASSWORD.as_ref()) { + spawner.spawn(ip_task(&stack, wifi_state.sender())).unwrap(); spawner - .spawn(wifi_connection(controller, ssid, psk)) + .spawn(wifi_connection( + controller, + ssid, + psk, + wifi_reqs.receiver(), + wifi_state.sender(), + )) .unwrap(); spawner.spawn(net_task(&stack)).unwrap(); - spawner.spawn(ip_task(&stack)).unwrap(); } // spawner.spawn(blink(led)).unwrap(); spawner .spawn(moisture(pin, adc1, moisture_sensor_suppy_pin.into())) .unwrap(); spawner.spawn(battery_monitor(vbat_in, adc1)).unwrap(); - spawner.spawn(publish_data(&stack, seed, Duration::from_secs(60))); }) } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WifiRequestState { + Connected, + Configured, + Disconnected, +} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WifiRequest { + Connect, + Disconnect, +} + +#[derive(Clone)] +pub struct WifiHandle<'a> { + wifi_state: embassy_sync::channel::Receiver<'a, NoopRawMutex, WifiRequestState, 3>, + wifi_request: embassy_sync::channel::Sender<'a, NoopRawMutex, WifiRequest, 3>, +} + +#[derive(Clone)] +pub struct WifiScope<'a, 'b> { + handle: &'a WifiHandle<'b>, +} + +impl Drop for WifiScope<'_, '_> { + fn drop(&mut self) { + block_on(self.handle.wifi_request.send(WifiRequest::Disconnect)); + } +} + +impl<'a> WifiHandle<'a> { + async fn request(&self, request: WifiRequest, desired_state: WifiRequestState) { + self.wifi_request.send(request).await; + while self.wifi_state.receive().await != desired_state {} + } + + pub async fn configured<'b>(&'b self) -> WifiScope<'b, 'a> + where + 'a: 'b, + { + self.request(WifiRequest::Connect, WifiRequestState::Configured) + .await; + WifiScope { handle: &self } + } +} + #[embassy_executor::task] async fn wifi_connection( mut controller: WifiController<'static>, ssid: &'static str, psk: &'static str, + wifi_requests: embassy_sync::channel::Receiver<'static, NoopRawMutex, WifiRequest, 3>, + wifi_state: embassy_sync::channel::Sender<'static, NoopRawMutex, WifiRequestState, 3>, ) { trace!("start wifi connection task"); + let mut need_connection: u16 = 0; debug!("Device capabilities: {:?}", controller.get_capabilities()); loop { - match esp_wifi::wifi::get_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 + debug!("wifi need: {need_connection}"); + match select( + controller.wait_for_all_events( + enum_set!(WifiEvent::StaDisconnected | WifiEvent::StaConnected), + true, + ), + wifi_requests.receive(), + ) + .await + { + Either::First(_) => { + if controller.is_connected().unwrap() { + wifi_state.send(WifiRequestState::Connected).await; + } + if !controller.is_connected().unwrap() { + wifi_state.send(WifiRequestState::Disconnected).await; + } + Timer::after(Duration::from_millis(5000)).await; } - _ => {} - } - if !matches!(controller.is_started(), Ok(true)) { - let client_config = Configuration::Client(ClientConfiguration { - ssid: ssid.try_into().unwrap(), - password: psk.try_into().unwrap(), - ..Default::default() - }); - controller.set_configuration(&client_config).unwrap(); - trace!("starting wifi"); - controller.start().await.unwrap(); - trace!("Wifi started!"); - } - trace!("About to connect..."); + Either::Second(req) => match req { + WifiRequest::Connect => need_connection += 1, + WifiRequest::Disconnect => { + need_connection -= 1; + if need_connection == 0 { + controller.disconnect().await; + wifi_state.send(WifiRequestState::Disconnected).await; + } + } + }, + }; + debug!("wifi need: {need_connection}"); + if need_connection > 0 { + if !matches!(controller.is_started(), Ok(true)) { + let client_config = Configuration::Client(ClientConfiguration { + ssid: ssid.try_into().unwrap(), + password: psk.try_into().unwrap(), + ..Default::default() + }); + controller.set_configuration(&client_config).unwrap(); + trace!("starting wifi"); + controller.start().await.unwrap(); + trace!("Wifi started!"); + } + trace!("About to connect..."); - match controller.connect().await { - Ok(_) => info!("Wifi connected!"), - Err(e) => { - error!("Failed to connect to wifi: {e:?}"); - Timer::after(Duration::from_millis(5000)).await + match controller.connect().await { + Ok(_) => { + info!("Wifi connected!"); + wifi_state.send(WifiRequestState::Connected).await; + } + Err(e) => { + error!("Failed to connect to wifi: {e:?}"); + Timer::after(Duration::from_millis(5000)).await + } } } } } -pub static HAS_IP_ADDRESS: OnceLock<()> = OnceLock::new(); - #[embassy_executor::task] -async fn ip_task(stack: NetworkStack) { +async fn ip_task( + stack: NetworkStack, + wifi_state: embassy_sync::channel::Sender<'static, NoopRawMutex, WifiRequestState, 3>, +) { loop { if stack.is_link_up() { break; @@ -342,8 +447,8 @@ async fn ip_task(stack: NetworkStack) { debug!("Waiting to get IP address..."); loop { if let Some(config) = stack.config_v4() { + wifi_state.send(WifiRequestState::Configured).await; info!("Got IP: {}", config.address); - HAS_IP_ADDRESS.get_or_init(|| ()); break; } Timer::after(Duration::from_millis(500)).await; diff --git a/src/mqtt.rs b/src/mqtt.rs index 3df090b..b275717 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -4,7 +4,8 @@ use embassy_net::tcp::TcpSocket; use embassy_net::{dns::Error as DnsError, tcp::ConnectError}; use embassy_time::{with_timeout, Duration, Instant, TimeoutError, Timer}; use embedded_tls::{ - Aes128GcmSha256, NoVerify, TlsConfig, TlsConnection, TlsContext, TlsError, UnsecureProvider, TLS_RECORD_OVERHEAD, + Aes128GcmSha256, NoVerify, TlsConfig, TlsConnection, TlsContext, TlsError, UnsecureProvider, + TLS_RECORD_OVERHEAD, }; use esp_backtrace as _; use log::{debug, error, info}; @@ -15,7 +16,7 @@ use rust_mqtt::client::client_config::ClientConfig; use rust_mqtt::packet::v5::reason_codes::ReasonCode; use rust_mqtt::utils::rng_generator::CountingRng; -use crate::{NetworkStack, DATA, HAS_IP_ADDRESS}; +use crate::{NetworkStack, WifiHandle, DATA}; #[derive(Debug)] pub enum SendError { @@ -87,8 +88,7 @@ pub async fn send_message( let begin = Instant::now(); let dns_resp = stack .dns_query(MQTT_SERVER_HOSTNAME, embassy_net::dns::DnsQueryType::A) - .await - .map_err(SendError::Dns)?; + .await?; let after_dns = Instant::now(); const TCP_BUFLEN: usize = 2048 << 2; let mut rx_buffer = [0; TCP_BUFLEN]; @@ -201,11 +201,17 @@ pub async fn send_message( } #[embassy_executor::task] -pub async fn publish_data(stack: NetworkStack, rng_seed: [u8; 32], interval: Duration) { +pub async fn publish_data( + stack: NetworkStack, + rng_seed: [u8; 32], + interval: Duration, + wifi: WifiHandle<'static>, +) { let mut rng = StdRng::from_seed(rng_seed); - HAS_IP_ADDRESS.get().await; loop { { + let wifi_guard = wifi.configured().await; + Timer::after(Duration::from_millis(10)).await; let mut data = DATA.lock().await; let res = send_message( stack, @@ -217,6 +223,7 @@ pub async fn publish_data(stack: NetworkStack, rng_seed: [u8; 32], interval: Dur if let Err(e) = res { error!("Oh no: {e:?}"); }; + drop(wifi_guard); data.clear(); } Timer::after(interval).await;