enable deepsleep and gpio1 + timer wake

This commit is contained in:
2025-09-26 22:07:48 +02:00
parent 7fc8d0c882
commit 76f59b093d
7 changed files with 79 additions and 82 deletions

View File

@@ -21,14 +21,14 @@ use embassy_executor::Spawner;
use embassy_net::udp::UdpSocket; use embassy_net::udp::UdpSocket;
use embassy_net::{DhcpConfig, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; use embassy_net::{DhcpConfig, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::{Mutex, MutexGuard};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use embedded_storage::nor_flash::ReadNorFlash; use embedded_storage::nor_flash::ReadNorFlash;
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState}; use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
use esp_bootloader_esp_idf::partitions::FlashRegion; use esp_bootloader_esp_idf::partitions::FlashRegion;
use esp_hal::gpio::Input; use esp_hal::gpio::{Input, InputConfig, Pull, RtcPinWithResistors};
use esp_hal::rng::Rng; use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::sleep::RtcSleepConfig; use esp_hal::rtc_cntl::{sleep::{TimerWakeupSource, WakeupLevel}, Rtc};
use esp_hal::system::software_reset; use esp_hal::system::software_reset;
use esp_println::println; use esp_println::println;
use esp_storage::FlashStorage; use esp_storage::FlashStorage;
@@ -113,6 +113,9 @@ pub struct Esp<'a> {
pub boot_button: Input<'a>, pub boot_button: Input<'a>,
// RTC-capable GPIO used as external wake source (store the raw peripheral)
pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>,
pub ota: Ota<'static, FlashStorage>, pub ota: Ota<'static, FlashStorage>,
pub ota_next: &'static mut FlashRegion<'static, FlashStorage>, pub ota_next: &'static mut FlashRegion<'static, FlashStorage>,
} }
@@ -409,14 +412,12 @@ impl Esp<'_> {
spawner.spawn(run_dhcp(stack.clone(), gw_ip_addr_str)).ok(); spawner.spawn(run_dhcp(stack.clone(), gw_ip_addr_str)).ok();
loop { loop {
println!("waiting for wifi ap link up");
if stack.is_link_up() { if stack.is_link_up() {
break; break;
} }
Timer::after(Duration::from_millis(500)).await; Timer::after(Duration::from_millis(500)).await;
} }
while !stack.is_config_up() { while !stack.is_config_up() {
println!("waiting for wifi ap config up");
Timer::after(Duration::from_millis(100)).await Timer::after(Duration::from_millis(100)).await
} }
println!("Connect to the AP `${ssid}` and point your browser to http://{gw_ip_addr_str}/"); println!("Connect to the AP `${ssid}` and point your browser to http://{gw_ip_addr_str}/");
@@ -481,92 +482,79 @@ impl Esp<'_> {
spawner.spawn(net_task(runner)).ok(); spawner.spawn(net_task(runner)).ok();
self.controller.lock().await.start_async().await?; self.controller.lock().await.start_async().await?;
let timeout = TIME_ACCESS.get().await.current_time_us() + max_wait as u64 * 1000; let timeout = {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} + max_wait as u64 * 1000;
loop { loop {
let state = esp_wifi::wifi::sta_state(); let state = esp_wifi::wifi::sta_state();
println!("waiting wifi sta ready {:?}", state);
match state { match state {
WifiState::StaStarted => { WifiState::StaStarted => {
self.controller.lock().await.connect()?; self.controller.lock().await.connect()?;
break; break;
} }
WifiState::StaConnected => {} _ => {}
WifiState::StaDisconnected => {}
WifiState::StaStopped => {}
WifiState::ApStarted => {}
WifiState::ApStopped => {}
WifiState::Invalid => {}
} }
if TIME_ACCESS.get().await.current_time_us() > timeout { if { let guard = TIME_ACCESS.get().await.lock().await; guard.current_time_us() } > timeout {
bail!("Timeout waiting for wifi sta ready") bail!("Timeout waiting for wifi sta ready")
} }
Timer::after(Duration::from_millis(500)).await; Timer::after(Duration::from_millis(500)).await;
} }
let timeout = TIME_ACCESS.get().await.current_time_us() + max_wait as u64 * 1000; let timeout = { let guard = TIME_ACCESS.get().await.lock().await; guard.current_time_us() } + max_wait as u64 * 1000;
loop { loop {
let state = esp_wifi::wifi::sta_state(); let state = esp_wifi::wifi::sta_state();
println!("waiting wifi sta connected {:?}", state);
match state { match state {
WifiState::StaStarted => {}
WifiState::StaConnected => { WifiState::StaConnected => {
break; break;
} }
WifiState::StaDisconnected => {} _ => {}
WifiState::StaStopped => {}
WifiState::ApStarted => {}
WifiState::ApStopped => {}
WifiState::Invalid => {}
} }
if TIME_ACCESS.get().await.current_time_us() > timeout { if { let guard = TIME_ACCESS.get().await.lock().await; guard.current_time_us() } > timeout {
bail!("Timeout waiting for wifi sta connected") bail!("Timeout waiting for wifi sta connected")
} }
Timer::after(Duration::from_millis(500)).await; Timer::after(Duration::from_millis(500)).await;
} }
let timeout = TIME_ACCESS.get().await.current_time_us() + max_wait as u64 * 1000; let timeout = { let guard = TIME_ACCESS.get().await.lock().await; guard.current_time_us() } + max_wait as u64 * 1000;
while !stack.is_link_up() { while !stack.is_link_up() {
if TIME_ACCESS.get().await.current_time_us() > timeout { if { let guard = TIME_ACCESS.get().await.lock().await; guard.current_time_us() } > timeout {
bail!("Timeout waiting for wifi link up") bail!("Timeout waiting for wifi link up")
} }
println!("waiting for wifi link up");
Timer::after(Duration::from_millis(500)).await; Timer::after(Duration::from_millis(500)).await;
} }
let timeout = TIME_ACCESS.get().await.current_time_us() + max_wait as u64 * 1000; let timeout = { let guard = TIME_ACCESS.get().await.lock().await; guard.current_time_us() } + max_wait as u64 * 1000;
while !stack.is_config_up() { while !stack.is_config_up() {
if TIME_ACCESS.get().await.current_time_us() > timeout { if { let guard = TIME_ACCESS.get().await.lock().await; guard.current_time_us() } > timeout {
bail!("Timeout waiting for wifi config up") bail!("Timeout waiting for wifi config up")
} }
println!("waiting for wifi config up");
Timer::after(Duration::from_millis(100)).await Timer::after(Duration::from_millis(100)).await
} }
Ok(stack.clone()) Ok(stack.clone())
} }
pub async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { pub fn deep_sleep(&mut self, duration_in_ms: u64, mut rtc: MutexGuard<CriticalSectionRawMutex, Rtc>) -> ! {
RtcSleepConfig::deep(); // Configure and enter deep sleep using esp-hal. Also keep prior behavior where
// duration_in_ms == 0 triggers an immediate reset.
let cur = self.ota.current_ota_state().unwrap(); // Mark the current OTA image as valid if we reached here while in pending verify.
//we made it till here, so fine if let Ok(cur) = self.ota.current_ota_state() {
if cur == OtaImageState::PendingVerify { if cur == OtaImageState::PendingVerify {
self.ota self.ota
.set_current_ota_state(OtaImageState::Valid) .set_current_ota_state(OtaImageState::Valid)
.expect("Could not set image to valid"); .expect("Could not set image to valid");
}
} }
//unsafe {
// //allow early wakeup by pressing the boot button
if duration_in_ms == 0 { if duration_in_ms == 0 {
software_reset(); software_reset();
} else { } else {
loop { let timer = TimerWakeupSource::new(core::time::Duration::from_millis(duration_in_ms));
info!("todo deepsleep") let mut wake_pins: [(&mut dyn RtcPinWithResistors, WakeupLevel); 1] = [(&mut self.wake_gpio1, WakeupLevel::Low)];
} let ext1 = esp_hal::rtc_cntl::sleep::Ext1WakeupSource::new(&mut wake_pins);
// //configure gpio 1 to wakeup on low, reused boot button for this rtc.sleep_deep(&[&timer, &ext1]);
// esp_sleep_enable_ext1_wakeup(
// 0b10u64,
// esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW,
// );
// esp_deep_sleep(duration_in_ms);
} }
//};
// We should never reach here because sleep_deep never returns, but just in case, reset.
software_reset();
} }
pub(crate) async fn load_config(&mut self) -> FatResult<PlantControllerConfig> { pub(crate) async fn load_config(&mut self) -> FatResult<PlantControllerConfig> {

View File

@@ -2,7 +2,7 @@ use crate::alloc::boxed::Box;
use crate::hal::esp::Esp; use crate::hal::esp::Esp;
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction}; use crate::hal::rtc::{BackupHeader, RTCModuleInteraction};
use crate::hal::water::TankSensor; use crate::hal::water::TankSensor;
use crate::hal::{BoardInteraction, FreePeripherals, Sensor}; use crate::hal::{BoardInteraction, FreePeripherals, Sensor, TIME_ACCESS};
use crate::fat_error::{FatError, FatResult}; use crate::fat_error::{FatError, FatResult};
use crate::{ use crate::{
bail, bail,
@@ -95,7 +95,8 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
} }
async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
self.esp.deep_sleep(duration_in_ms).await; let rtc = TIME_ACCESS.get().await.lock().await;
self.esp.deep_sleep(duration_in_ms, rtc);
} }
fn is_day(&self) -> bool { fn is_day(&self) -> bool {
false false

View File

@@ -13,7 +13,6 @@ use esp_hal::peripherals::Peripherals;
use esp_hal::peripherals::ADC1; use esp_hal::peripherals::ADC1;
use esp_hal::peripherals::APB_SARADC; use esp_hal::peripherals::APB_SARADC;
use esp_hal::peripherals::GPIO0; use esp_hal::peripherals::GPIO0;
use esp_hal::peripherals::GPIO1;
use esp_hal::peripherals::GPIO10; use esp_hal::peripherals::GPIO10;
use esp_hal::peripherals::GPIO11; use esp_hal::peripherals::GPIO11;
use esp_hal::peripherals::GPIO12; use esp_hal::peripherals::GPIO12;
@@ -106,7 +105,7 @@ use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
use littlefs2::object_safe::DynStorage; use littlefs2::object_safe::DynStorage;
use log::{info, warn}; use log::{info, warn};
pub static TIME_ACCESS: OnceLock<Rtc> = OnceLock::new(); pub static TIME_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, Rtc>> = OnceLock::new();
//Only support for 8 right now! //Only support for 8 right now!
pub const PLANT_COUNT: usize = 8; pub const PLANT_COUNT: usize = 8;
@@ -175,7 +174,6 @@ pub trait BoardInteraction<'a> {
#[allow(dead_code)] #[allow(dead_code)]
pub struct FreePeripherals<'a> { pub struct FreePeripherals<'a> {
pub gpio0: GPIO0<'a>, pub gpio0: GPIO0<'a>,
pub gpio1: GPIO1<'a>,
pub gpio2: GPIO2<'a>, pub gpio2: GPIO2<'a>,
pub gpio3: GPIO3<'a>, pub gpio3: GPIO3<'a>,
pub gpio4: GPIO4<'a>, pub gpio4: GPIO4<'a>,
@@ -232,7 +230,7 @@ impl PlantHal {
esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000); esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000);
let rtc: Rtc = Rtc::new(peripherals.LPWR); let rtc: Rtc = Rtc::new(peripherals.LPWR);
TIME_ACCESS.init(rtc).map_err(|_| FatError::String { TIME_ACCESS.init(Mutex::new(rtc)).map_err(|_| FatError::String {
error: "Init error rct".to_string(), error: "Init error rct".to_string(),
})?; })?;
@@ -243,6 +241,9 @@ impl PlantHal {
InputConfig::default().with_pull(Pull::None), InputConfig::default().with_pull(Pull::None),
); );
// Reserve GPIO1 for deep sleep wake (configured just before entering sleep)
let wake_gpio1 = peripherals.GPIO1;
let rng = Rng::new(peripherals.RNG); let rng = Rng::new(peripherals.RNG);
let timg0 = TimerGroup::new(peripherals.TIMG0); let timg0 = TimerGroup::new(peripherals.TIMG0);
let esp_wifi_ctrl = &*mk_static!( let esp_wifi_ctrl = &*mk_static!(
@@ -267,7 +268,6 @@ impl PlantHal {
// pcnt0: peripherals.pcnt0, // pcnt0: peripherals.pcnt0,
// pcnt1: peripherals.pcnt1, // pcnt1: peripherals.pcnt1,
gpio0: peripherals.GPIO0, gpio0: peripherals.GPIO0,
gpio1: peripherals.GPIO1,
gpio2: peripherals.GPIO2, gpio2: peripherals.GPIO2,
gpio3: peripherals.GPIO3, gpio3: peripherals.GPIO3,
gpio4: peripherals.GPIO4, gpio4: peripherals.GPIO4,
@@ -390,6 +390,7 @@ impl PlantHal {
interface_sta : Some(sta), interface_sta : Some(sta),
interface_ap : Some(ap), interface_ap : Some(ap),
boot_button, boot_button,
wake_gpio1,
mqtt_client: None, mqtt_client: None,
ota, ota,
ota_next, ota_next,
@@ -402,25 +403,25 @@ impl PlantHal {
None => "unknown", None => "unknown",
Some(reason) => match reason { Some(reason) => match reason {
SocResetReason::ChipPowerOn => "power on", SocResetReason::ChipPowerOn => "power on",
SocResetReason::CoreSw => "software reset",
SocResetReason::CoreDeepSleep => "deep sleep",
SocResetReason::CoreSDIO => "sdio reset", SocResetReason::CoreSDIO => "sdio reset",
SocResetReason::CoreMwdt0 => "Watchdog Main", SocResetReason::CoreMwdt0 => "Watchdog Main",
SocResetReason::CoreMwdt1 => "Watchdog 1", SocResetReason::CoreMwdt1 => "Watchdog 1",
SocResetReason::CoreRtcWdt => "Watchdog RTC", SocResetReason::CoreRtcWdt => "Watchdog RTC",
SocResetReason::Cpu0Mwdt0 => "Watchdog MCpu0", SocResetReason::Cpu0Mwdt0 => "Watchdog MCpu0",
SocResetReason::Cpu0Sw => "software reset cpu0", SocResetReason::Cpu0Sw => "software reset cpu0",
SocResetReason::SysRtcWdt => "Watchdog Sys rtc",
SocResetReason::Cpu0Mwdt1 => "cpu0 mwdt1",
SocResetReason::SysSuperWdt => "Watchdog Super",
SocResetReason::Cpu0RtcWdt => { SocResetReason::Cpu0RtcWdt => {
init_rtc_store = true; init_rtc_store = true;
"Watchdog RTC cpu0" "Watchdog RTC cpu0"
} }
SocResetReason::CoreSw => "software reset",
SocResetReason::CoreDeepSleep => "deep sleep",
SocResetReason::SysBrownOut => "sys brown out", SocResetReason::SysBrownOut => "sys brown out",
SocResetReason::SysRtcWdt => "Watchdog Sys rtc",
SocResetReason::Cpu0Mwdt1 => "cpu0 mwdt1",
SocResetReason::SysSuperWdt => "Watchdog Super",
SocResetReason::CoreEfuseCrc => "core efuse crc", SocResetReason::CoreEfuseCrc => "core efuse crc",
SocResetReason::CoreUsbUart => { SocResetReason::CoreUsbUart => {
to_config_mode = true; //TODO still required? or via button ignore? to_config_mode = true;
"core usb uart" "core usb uart"
} }
SocResetReason::CoreUsbJtag => "core usb jtag", SocResetReason::CoreUsbJtag => "core usb jtag",
@@ -577,14 +578,15 @@ impl PlantHal {
} }
pub async fn esp_time() -> DateTime<Utc> { pub async fn esp_time() -> DateTime<Utc> {
DateTime::from_timestamp_micros(TIME_ACCESS.get().await.current_time_us() as i64).unwrap() let guard = TIME_ACCESS.get().await.lock().await;
DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap()
} }
pub async fn esp_set_time(time: DateTime<FixedOffset>) { pub async fn esp_set_time(time: DateTime<FixedOffset>) {
TIME_ACCESS {
.get() let mut guard = TIME_ACCESS.get().await.lock().await;
.await guard.set_current_time_us(time.timestamp_micros() as u64);
.set_current_time_us(time.timestamp_micros() as u64); }
BOARD_ACCESS BOARD_ACCESS
.get() .get()
.await .await

View File

@@ -3,7 +3,7 @@ use crate::hal::battery::BatteryInteraction;
use crate::hal::esp::Esp; use crate::hal::esp::Esp;
use crate::hal::rtc::RTCModuleInteraction; use crate::hal::rtc::RTCModuleInteraction;
use crate::hal::water::TankSensor; use crate::hal::water::TankSensor;
use crate::hal::{BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT}; use crate::hal::{BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, TIME_ACCESS};
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::string::ToString; use alloc::string::ToString;
use async_trait::async_trait; use async_trait::async_trait;
@@ -347,7 +347,8 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
self.awake.set_low(); self.awake.set_low();
//self.charger.power_save(); //self.charger.power_save();
self.esp.deep_sleep(duration_in_ms).await; let rtc = TIME_ACCESS.get().await.lock().await;
self.esp.deep_sleep(duration_in_ms, rtc);
} }
fn is_day(&self) -> bool { fn is_day(&self) -> bool {

View File

@@ -111,7 +111,7 @@ impl LogArray {
limit_length(txt_short, &mut txt_short_stack); limit_length(txt_short, &mut txt_short_stack);
limit_length(txt_long, &mut txt_long_stack); limit_length(txt_long, &mut txt_long_stack);
let time = TIME_ACCESS.get().await.current_time_us() / 1000; let time = { let guard = TIME_ACCESS.get().await.lock().await; guard.current_time_us() } / 1000;
let ordinal = message_key.ordinal() as u16; let ordinal = message_key.ordinal() as u16;
let template: &str = message_key.into(); let template: &str = message_key.into();

View File

@@ -166,10 +166,10 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
board.board_hal.general_fault(false).await; board.board_hal.general_fault(false).await;
let cur = match board.board_hal.get_rtc_module().get_rtc_time().await { let cur = match board.board_hal.get_rtc_module().get_rtc_time().await {
Ok(value) => { Ok(value) => {
TIME_ACCESS {
.get() let mut guard = TIME_ACCESS.get().await.lock().await;
.await guard.set_current_time_us(value.timestamp_micros() as u64);
.set_current_time_us(value.timestamp_micros() as u64); }
value value
} }
Err(err) => { Err(err) => {
@@ -396,10 +396,10 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
_water_frozen = true; _water_frozen = true;
} }
} }
info!("Water temp is {}", water_temp.unwrap_or(0.));
//publish_tank_state(&tank_state, &water_temp).await; //publish_tank_state(&tank_state, &water_temp).await;
board = BOARD_ACCESS.get().await.lock().await;
let _plantstate: [PlantState; PLANT_COUNT] = [ let _plantstate: [PlantState; PLANT_COUNT] = [
PlantState::read_hardware_state(0, &mut board).await, PlantState::read_hardware_state(0, &mut board).await,
PlantState::read_hardware_state(1, &mut board).await, PlantState::read_hardware_state(1, &mut board).await,
@@ -410,6 +410,8 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
PlantState::read_hardware_state(6, &mut board).await, PlantState::read_hardware_state(6, &mut board).await,
PlantState::read_hardware_state(7, &mut board).await, PlantState::read_hardware_state(7, &mut board).await,
]; ];
//publish_plant_states(&timezone_time.clone(), &plantstate).await; //publish_plant_states(&timezone_time.clone(), &plantstate).await;
// let pump_required = plantstate // let pump_required = plantstate
@@ -481,6 +483,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
// } // }
// } // }
info!("state of charg");
let is_day = board.board_hal.is_day(); let is_day = board.board_hal.is_day();
let state_of_charge = board let state_of_charge = board
.board_hal .board_hal
@@ -497,10 +500,12 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
.await .await
.unwrap_or(BatteryState::Unknown); .unwrap_or(BatteryState::Unknown);
info!("Battery state is {:?}", battery_state);
let mut light_state = LightState { let mut light_state = LightState {
enabled: board.board_hal.get_config().night_lamp.enabled, enabled: board.board_hal.get_config().night_lamp.enabled,
..Default::default() ..Default::default()
}; };
info!("Light state is {:?}", light_state);
if light_state.enabled { if light_state.enabled {
light_state.is_day = is_day; light_state.is_day = is_day;
light_state.out_of_work_hour = !in_time_range( light_state.out_of_work_hour = !in_time_range(
@@ -603,6 +608,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
.mqtt_publish("/state", "sleep".as_bytes()) .mqtt_publish("/state", "sleep".as_bytes())
.await; .await;
info!("Go to sleep for {} minutes", deep_sleep_duration_minutes);
//determine next event //determine next event
//is light out of work trigger soon? //is light out of work trigger soon?
//is battery low ?? //is battery low ??

View File

@@ -374,6 +374,12 @@ impl Handler for HttpHandler {
self.reboot_now.store(true, Ordering::Relaxed); self.reboot_now.store(true, Ordering::Relaxed);
Some(Ok(None)) Some(Ok(None))
} }
"/exit" => {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.get_esp().set_restart_to_conf(false);
self.reboot_now.store(true, Ordering::Relaxed);
Some(Ok(None))
}
_ => None, _ => None,
}; };
match json { match json {
@@ -850,13 +856,6 @@ pub async fn httpd(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
// //
// unsafe { vTaskDelay(1) }; // unsafe { vTaskDelay(1) };
// //
// let reboot_now_for_exit = reboot_now.clone();
// server
// .fn_handler("/exit", Method::Post, move |_| {
// reboot_now_for_exit.store(true, std::sync::atomic::Ordering::Relaxed);
// anyhow::Ok(())
// })
// .unwrap();
// server // server
} }