feat(power): enable wifi on demand

This commit is contained in:
shimun 2024-05-12 22:11:32 +02:00
parent fec9d8b00a
commit ab4305a2d3
Signed by: shimun
GPG Key ID: E0420647856EA39E
4 changed files with 158 additions and 40 deletions

2
Cargo.lock generated
View File

@ -815,11 +815,13 @@ version = "0.1.0"
dependencies = [ dependencies = [
"const-decoder", "const-decoder",
"embassy-executor", "embassy-executor",
"embassy-futures",
"embassy-net", "embassy-net",
"embassy-sync 0.5.0 (git+https://github.com/embassy-rs/embassy.git?rev=4b4777)", "embassy-sync 0.5.0 (git+https://github.com/embassy-rs/embassy.git?rev=4b4777)",
"embassy-time", "embassy-time",
"embedded-io-async", "embedded-io-async",
"embedded-tls", "embedded-tls",
"enumset",
"esp-alloc", "esp-alloc",
"esp-backtrace", "esp-backtrace",
"esp-hal", "esp-hal",

View File

@ -13,11 +13,13 @@ bench = false
[dependencies] [dependencies]
const-decoder = "0.3.0" const-decoder = "0.3.0"
embassy-executor = { version = "0.5.0", features = ["nightly", "integrated-timers", "arch-riscv32", "executor-thread"] } 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-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-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "4b4777" }
embassy-time = { version = "0.3.0" } embassy-time = { version = "0.3.0" }
embedded-io-async = "0.6.1" 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"] } 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-alloc = "0.3.0"
esp-backtrace = { version = "0.11.0", features = ["esp32c3", "exception-handler", "panic-handler", "println"] } 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"] } esp-hal = { version = "0.16.1", features = ["embassy", "embassy-time-timg0", "esp32c3"] }
@ -40,6 +42,8 @@ opt-level = 3
[features] [features]
tls = [] tls = []
tls-sni = ["tls"]
real-world = []
[profile.release] [profile.release]
opt-level = "s" opt-level = "s"

View File

@ -16,11 +16,14 @@ use alloc::boxed::Box;
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use embassy_executor::Executor; 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_net::{Config, DhcpConfig, Stack, StackResources};
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_sync::once_lock::OnceLock; use embassy_sync::once_lock::OnceLock;
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use enumset::enum_set;
use esp_alloc::EspHeap; use esp_alloc::EspHeap;
use esp_backtrace as _; use esp_backtrace as _;
use esp_hal::adc::{AdcPin, Attenuation, ADC}; 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 SSID: Option<&str> = option_env!("WIFI_SSID");
const PASSWORD: Option<&str> = option_env!("WIFI_PASSWORD"); 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<WifiDevice<'static, WifiStaDevice>>; pub type NetworkStack = &'static Stack<WifiDevice<'static, WifiStaDevice>>;
fn init_heap() { fn init_heap() {
@ -164,7 +169,7 @@ async fn moisture(
.await .await
.insert(Cow::from("moisture"), moisture.to_string()); .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 .await
.insert(Cow::from("vbat"), v_bat.to_string()); .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<Executor> = StaticCell::new(); static EXECUTOR: StaticCell<Executor> = StaticCell::new();
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
init_logger(log::LevelFilter::Info); init_logger(log::LevelFilter::Debug);
init_heap(); init_heap();
let peripherals = Peripherals::take(); let peripherals = Peripherals::take();
@ -272,67 +277,167 @@ fn main() -> ! {
make_static!(Mutex::new(ADC::<ADC1>::new(peripherals.ADC1, adc1_config))); make_static!(Mutex::new(ADC::<ADC1>::new(peripherals.ADC1, adc1_config)));
let executor = EXECUTOR.init(Executor::new()); let executor = EXECUTOR.init(Executor::new());
let wifi_reqs: &'static _ =
make_static!(embassy_sync::channel::Channel::<NoopRawMutex, WifiRequest, 3>::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| { 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()) { if let (Some(ssid), Some(psk)) = (SSID.as_ref(), PASSWORD.as_ref()) {
spawner.spawn(ip_task(&stack, wifi_state.sender())).unwrap();
spawner spawner
.spawn(wifi_connection(controller, ssid, psk)) .spawn(wifi_connection(
controller,
ssid,
psk,
wifi_reqs.receiver(),
wifi_state.sender(),
))
.unwrap(); .unwrap();
spawner.spawn(net_task(&stack)).unwrap(); spawner.spawn(net_task(&stack)).unwrap();
spawner.spawn(ip_task(&stack)).unwrap();
} }
// spawner.spawn(blink(led)).unwrap(); // spawner.spawn(blink(led)).unwrap();
spawner spawner
.spawn(moisture(pin, adc1, moisture_sensor_suppy_pin.into())) .spawn(moisture(pin, adc1, moisture_sensor_suppy_pin.into()))
.unwrap(); .unwrap();
spawner.spawn(battery_monitor(vbat_in, adc1)).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] #[embassy_executor::task]
async fn wifi_connection( async fn wifi_connection(
mut controller: WifiController<'static>, mut controller: WifiController<'static>,
ssid: &'static str, ssid: &'static str,
psk: &'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"); trace!("start wifi connection task");
let mut need_connection: u16 = 0;
debug!("Device capabilities: {:?}", controller.get_capabilities()); debug!("Device capabilities: {:?}", controller.get_capabilities());
loop { loop {
match esp_wifi::wifi::get_wifi_state() { debug!("wifi need: {need_connection}");
WifiState::StaConnected => { match select(
// wait until we're no longer connected controller.wait_for_all_events(
controller.wait_for_event(WifiEvent::StaDisconnected).await; enum_set!(WifiEvent::StaDisconnected | WifiEvent::StaConnected),
Timer::after(Duration::from_millis(5000)).await 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;
} }
_ => {} Either::Second(req) => match req {
} WifiRequest::Connect => need_connection += 1,
if !matches!(controller.is_started(), Ok(true)) { WifiRequest::Disconnect => {
let client_config = Configuration::Client(ClientConfiguration { need_connection -= 1;
ssid: ssid.try_into().unwrap(), if need_connection == 0 {
password: psk.try_into().unwrap(), controller.disconnect().await;
..Default::default() wifi_state.send(WifiRequestState::Disconnected).await;
}); }
controller.set_configuration(&client_config).unwrap(); }
trace!("starting wifi"); },
controller.start().await.unwrap(); };
trace!("Wifi started!"); debug!("wifi need: {need_connection}");
} if need_connection > 0 {
trace!("About to connect..."); 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 { match controller.connect().await {
Ok(_) => info!("Wifi connected!"), Ok(_) => {
Err(e) => { info!("Wifi connected!");
error!("Failed to connect to wifi: {e:?}"); wifi_state.send(WifiRequestState::Connected).await;
Timer::after(Duration::from_millis(5000)).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] #[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 { loop {
if stack.is_link_up() { if stack.is_link_up() {
break; break;
@ -342,8 +447,8 @@ async fn ip_task(stack: NetworkStack) {
debug!("Waiting to get IP address..."); debug!("Waiting to get IP address...");
loop { loop {
if let Some(config) = stack.config_v4() { if let Some(config) = stack.config_v4() {
wifi_state.send(WifiRequestState::Configured).await;
info!("Got IP: {}", config.address); info!("Got IP: {}", config.address);
HAS_IP_ADDRESS.get_or_init(|| ());
break; break;
} }
Timer::after(Duration::from_millis(500)).await; Timer::after(Duration::from_millis(500)).await;

View File

@ -4,7 +4,8 @@ use embassy_net::tcp::TcpSocket;
use embassy_net::{dns::Error as DnsError, tcp::ConnectError}; use embassy_net::{dns::Error as DnsError, tcp::ConnectError};
use embassy_time::{with_timeout, Duration, Instant, TimeoutError, Timer}; use embassy_time::{with_timeout, Duration, Instant, TimeoutError, Timer};
use embedded_tls::{ 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 esp_backtrace as _;
use log::{debug, error, info}; 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::packet::v5::reason_codes::ReasonCode;
use rust_mqtt::utils::rng_generator::CountingRng; use rust_mqtt::utils::rng_generator::CountingRng;
use crate::{NetworkStack, DATA, HAS_IP_ADDRESS}; use crate::{NetworkStack, WifiHandle, DATA};
#[derive(Debug)] #[derive(Debug)]
pub enum SendError { pub enum SendError {
@ -87,8 +88,7 @@ pub async fn send_message(
let begin = Instant::now(); let begin = Instant::now();
let dns_resp = stack let dns_resp = stack
.dns_query(MQTT_SERVER_HOSTNAME, embassy_net::dns::DnsQueryType::A) .dns_query(MQTT_SERVER_HOSTNAME, embassy_net::dns::DnsQueryType::A)
.await .await?;
.map_err(SendError::Dns)?;
let after_dns = Instant::now(); let after_dns = Instant::now();
const TCP_BUFLEN: usize = 2048 << 2; const TCP_BUFLEN: usize = 2048 << 2;
let mut rx_buffer = [0; TCP_BUFLEN]; let mut rx_buffer = [0; TCP_BUFLEN];
@ -201,11 +201,17 @@ pub async fn send_message(
} }
#[embassy_executor::task] #[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); let mut rng = StdRng::from_seed(rng_seed);
HAS_IP_ADDRESS.get().await;
loop { loop {
{ {
let wifi_guard = wifi.configured().await;
Timer::after(Duration::from_millis(10)).await;
let mut data = DATA.lock().await; let mut data = DATA.lock().await;
let res = send_message( let res = send_message(
stack, stack,
@ -217,6 +223,7 @@ pub async fn publish_data(stack: NetworkStack, rng_seed: [u8; 32], interval: Dur
if let Err(e) = res { if let Err(e) = res {
error!("Oh no: {e:?}"); error!("Oh no: {e:?}");
}; };
drop(wifi_guard);
data.clear(); data.clear();
} }
Timer::after(interval).await; Timer::after(interval).await;