feat(power): enable wifi on demand
This commit is contained in:
parent
fec9d8b00a
commit
ab4305a2d3
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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"
|
||||||
|
173
src/main.rs
173
src/main.rs
@ -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;
|
||||||
|
19
src/mqtt.rs
19
src/mqtt.rs
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user