diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9deaa83..b389b79 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -83,7 +83,7 @@ esp-println = { version = "0.15.0", features = ["esp32c6", "log-04"] } # for more networking protocol support see https://crates.io/crates/edge-net embassy-executor = { version = "0.7.0", features = [ "log", - "task-arena-size-262144" + "task-arena-size-131072" ] } embassy-time = { version = "0.5.0", features = ["log"] } esp-hal-embassy = { version = "0.9.0", features = ["esp32c6", "log-04"] } diff --git a/rust/src/hal/esp.rs b/rust/src/hal/esp.rs index 10e9bd7..86b6676 100644 --- a/rust/src/hal/esp.rs +++ b/rust/src/hal/esp.rs @@ -7,7 +7,7 @@ use chrono::{DateTime, Utc}; use serde::Serialize; use alloc::string::ToString; -use alloc::{string::String, vec::Vec}; +use alloc::{format, string::String, vec::Vec}; use core::marker::PhantomData; use core::net::{IpAddr, Ipv4Addr}; use core::str::FromStr; @@ -15,15 +15,18 @@ use embassy_executor::{SendSpawner, Spawner}; use embassy_net::tcp::TcpSocket; use embassy_net::{IpListenEndpoint, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; use embassy_time::{Duration, Instant, Timer}; -use esp_bootloader_esp_idf::ota::OtaImageState; +use esp_bootloader_esp_idf::ota::{Ota, OtaImageState, Slot}; +use esp_bootloader_esp_idf::partitions::{Error, FlashRegion, PartitionEntry, PartitionTable}; use esp_hal::gpio::Input; use esp_hal::rng::Rng; +use esp_hal::rtc_cntl::sleep::RtcSleepConfig; use esp_println::{print, println}; use esp_storage::FlashStorage; use esp_wifi::wifi::{ AccessPointConfiguration, Configuration, Interfaces, WifiController, WifiDevice, WifiEvent, WifiState, }; +use log::{info, warn}; #[link_section = ".rtc.data"] static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; @@ -72,11 +75,8 @@ pub struct Esp<'a> { pub boot_button: Input<'a>, pub(crate) wall_clock_offset: u64, - pub storage: FlashStorage, - pub slot: usize, - pub next_slot: usize, - pub ota_state: OtaImageState, - pub slot_addres: u32, + pub ota: Ota<'static, FlashStorage>, + pub ota_next: &'static PartitionEntry<'static>, } pub struct IpInfo { @@ -103,6 +103,36 @@ impl Esp<'_> { const CONFIG_FILE: &'static str = "/spiffs/config.cfg"; const BASE_PATH: &'static str = "/spiffs"; + pub(crate) fn get_ota_slot(&mut self) -> String { + match self.ota.current_slot() { + Ok(slot) => { + format!("{:?}", slot) + } + Err(err) => { + format!("{:?}", err) + } + } + } + + pub(crate) fn get_ota_state(&mut self) -> String { + match self.ota.current_ota_state() { + Ok(state) => { + format!("{:?}", state) + } + Err(err) => { + format!("{:?}", err) + } + } + } + + // let current = ota.current_slot()?; + // println!( + // "current image state {:?} (only relevant if the bootloader was built with auto-rollback support)", + // ota.current_ota_state() + // ); + // println!("current {:?} - next {:?}", current, current.next()); + // let ota_state = ota.current_ota_state()?; + pub(crate) fn mode_override_pressed(&mut self) -> bool { self.boot_button.is_low() } @@ -119,6 +149,14 @@ impl Esp<'_> { //self.time() todo!(); } + + pub async fn flash_ota(&mut self) -> anyhow::Result<()> { + let mut storage_ota_next = FlashStorage::new(); + let ota_next = self.ota_next.as_embedded_storage(&mut storage_ota_next); + + bail!("not implemented") + } + pub(crate) fn time(&mut self) -> DateTime { let wall_clock = Instant::now().as_millis() + self.wall_clock_offset; DateTime::from_timestamp_millis(wall_clock as i64).unwrap() @@ -231,6 +269,36 @@ impl Esp<'_> { anyhow::Ok(stack.clone()) } + pub async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { + let mut cfg = RtcSleepConfig::deep(); + + let cur = self.ota.current_ota_state().unwrap(); + //we made it till here, so fine + if cur == OtaImageState::PendingVerify { + self.ota + .set_current_ota_state(OtaImageState::Valid) + .expect("Could not set image to valid"); + } + //unsafe { + // //allow early wakeup by pressing the boot button + if duration_in_ms == 0 { + loop { + info!("todo reboot") + } + } else { + loop { + info!("todo deepsleep") + } + // //configure gpio 1 to wakeup on low, reused boot button for this + // esp_sleep_enable_ext1_wakeup( + // 0b10u64, + // esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, + // ); + // esp_deep_sleep(duration_in_ms); + } + //}; + } + pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> anyhow::Result { let _ssid = network_config .ssid diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs index 736f1d8..5df79a9 100644 --- a/rust/src/hal/initial_hal.rs +++ b/rust/src/hal/initial_hal.rs @@ -3,7 +3,7 @@ use crate::hal::rtc::{BackupHeader, RTCModuleInteraction}; use alloc::vec::Vec; //use crate::hal::water::TankSensor; use crate::alloc::boxed::Box; -use crate::hal::{deep_sleep, BoardInteraction, FreePeripherals, Sensor}; +use crate::hal::{BoardInteraction, FreePeripherals, Sensor}; use crate::{ config::PlantControllerConfig, hal::battery::{BatteryInteraction, NoBatteryMonitor}, @@ -91,8 +91,8 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { bail!("Please configure board revision") } - fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { - deep_sleep(duration_in_ms) + async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { + self.esp.deep_sleep(duration_in_ms).await; } fn is_day(&self) -> bool { false diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index b188b75..ea11e11 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -23,36 +23,25 @@ use alloc::boxed::Box; use alloc::format; use anyhow::{Ok, Result}; use async_trait::async_trait; -use core::cell::OnceCell; -use core::marker::PhantomData; -use core::net::Ipv4Addr; -use core::str::FromStr; -use embassy_executor::{SendSpawner, Spawner}; -use embassy_net::tcp::{Error, TcpSocket}; -use embassy_net::{IpListenEndpoint, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; +use embassy_executor::Spawner; //use battery::BQ34Z100G1; //use bq34z100::Bq34z100g1Driver; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::once_lock::OnceLock; -use embassy_time::{Duration, Timer}; use esp_bootloader_esp_idf::partitions::{ - AppPartitionSubType, DataPartitionSubType, PartitionEntry, + AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry, }; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Io, Pull}; -use esp_hal::timer::systimer::SystemTimer; -use esp_println::{print, println}; +use esp_println::println; use measurements::{Current, Voltage}; -use embassy_sync::mutex::{Mutex, MutexGuard}; +use embassy_sync::mutex::Mutex; use esp_alloc as _; use esp_backtrace as _; -use esp_hal::analog::adc::Adc; +use esp_bootloader_esp_idf::ota::Slot; use esp_hal::rng::Rng; use esp_hal::timer::timg::TimerGroup; -use esp_wifi::wifi::{ - AccessPointConfiguration, Configuration, WifiController, WifiDevice, WifiEvent, WifiState, -}; +use esp_storage::FlashStorage; use esp_wifi::{init, EspWifiController}; //Only support for 8 right now! @@ -62,27 +51,6 @@ const TANK_MULTI_SAMPLE: usize = 11; //pub static I2C_DRIVER: LazyLock>> = LazyLock::new(PlantHal::create_i2c); -fn deep_sleep(_duration_in_ms: u64) -> ! { - //unsafe { - // //if we don't do this here, we might just revert newly flashed firmware - // mark_app_valid(); - // //allow early wakeup by pressing the boot button - // if duration_in_ms == 0 { - // esp_restart(); - // } else { - // //configure gpio 1 to wakeup on low, reused boot button for this - // esp_sleep_enable_ext1_wakeup( - // 0b10u64, - // esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, - // ); - // esp_deep_sleep(duration_in_ms); - // } - loop { - todo!() - } - //}; -} - #[derive(Debug, PartialEq)] pub enum Sensor { A, @@ -103,7 +71,7 @@ pub trait BoardInteraction<'a> { fn get_battery_monitor(&mut self) -> &mut Box; fn get_rtc_module(&mut self) -> &mut Box; fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; - fn deep_sleep(&mut self, duration_in_ms: u64) -> !; + async fn deep_sleep(&mut self, duration_in_ms: u64) -> !; fn is_day(&self) -> bool; //should be multsampled @@ -209,14 +177,14 @@ impl PlantHal { InputConfig::default().with_pull(Pull::None), ); - let mut rng = Rng::new(peripherals.RNG); + let rng = Rng::new(peripherals.RNG); let timg0 = TimerGroup::new(peripherals.TIMG0); let esp_wifi_ctrl = &*mk_static!( EspWifiController<'static>, init(timg0.timer0, rng.clone()).unwrap() ); - let (mut controller, interfaces) = + let (controller, interfaces) = esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).unwrap(); use esp_hal::timer::systimer::SystemTimer; @@ -260,62 +228,60 @@ impl PlantHal { }; // - let mut storage = esp_storage::FlashStorage::new(); - let mut buffer = [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN]; + let tablebuffer: [u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN] = + [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN]; + let tablebuffer = mk_static!( + [u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN], + tablebuffer + ); + let storage_ota = mk_static!(FlashStorage, FlashStorage::new()); let pt = - esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buffer)?; + esp_bootloader_esp_idf::partitions::read_partition_table(storage_ota, tablebuffer)?; // List all partitions - this is just FYI for i in 0..pt.len() { println!("{:?}", pt.get_partition(i)); } - // Find the OTA-data partition and show the currently active partition - let ota_part = pt + let ota_data = pt .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data( DataPartitionSubType::Ota, ))? .unwrap(); - let mut ota_part = ota_part.as_embedded_storage(&mut storage); - println!("Found ota data"); - let mut ota = esp_bootloader_esp_idf::ota::Ota::new(&mut ota_part)?; - let current = ota.current_slot()?; - println!( - "current image state {:?} (only relevant if the bootloader was built with auto-rollback support)", - ota.current_ota_state() - ); - println!("current {:?} - next {:?}", current, current.next()); - let ota_state = ota.current_ota_state()?; + let ota_data = mk_static!(PartitionEntry, ota_data); - let current_app = if current.number() == 0 { - pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( - AppPartitionSubType::Ota0, - )) - } else { - pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( - AppPartitionSubType::Ota1, - )) - }; - let app_address = match current_app { - Result::Ok(part) => match part { - None => 0, - Some(entry) => entry.offset(), - }, - Err(_) => 0, + let ota_data = ota_data.as_embedded_storage(storage_ota); + let ota_data = mk_static!(FlashRegion, ota_data); + + let mut ota = esp_bootloader_esp_idf::ota::Ota::new(ota_data)?; + + let ota_partition = match ota.current_slot()? { + Slot::None => { + panic!("No OTA slot found"); + } + Slot::Slot0 => pt + .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( + AppPartitionSubType::Ota0, + ))? + .unwrap(), + Slot::Slot1 => pt + .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( + AppPartitionSubType::Ota1, + ))? + .unwrap(), }; + let ota_next = mk_static!(PartitionEntry, ota_partition); + let mut esp = Esp { rng, controller: Some(controller), interfaces: Some(interfaces), boot_button, mqtt_client: None, - storage, - slot: current.number(), - slot_addres: app_address, - next_slot: current.next().number(), - ota_state, + ota, + ota_next, wall_clock_offset: 0, }; diff --git a/rust/src/main.rs b/rust/src/main.rs index 38ab0d8..7423a4f 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![feature(never_type)] #![deny( clippy::mem_forget, reason = "mem::forget is generally not safe to do with esp_hal types, especially those \ @@ -30,6 +31,7 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; +use esp_alloc::heap_allocator; use esp_bootloader_esp_idf::ota::OtaImageState; use esp_hal::rom::ets_delay_us; use esp_hal::system::software_reset; @@ -601,7 +603,8 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> { board.board_hal.get_esp().set_restart_to_conf(false); board .board_hal - .deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64); + .deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64) + .await; } } @@ -851,13 +854,11 @@ async fn publish_firmware_info( .mqtt_publish("/firmware/buildtime", version.build_time.as_bytes()) .await; let _ = esp.mqtt_publish("/firmware/last_online", timezone_time.as_bytes()); + let state = esp.get_ota_state(); let _ = esp - .mqtt_publish( - "/firmware/ota_state", - state_to_string(esp.ota_state).as_bytes(), - ) + .mqtt_publish("/firmware/ota_state", state.as_bytes()) .await; - let slot = esp.slot; + let slot = esp.get_ota_slot(); let _ = esp .mqtt_publish("/firmware/ota_slot", format!("slot{slot}").as_bytes()) .await; @@ -1123,17 +1124,11 @@ async fn get_version( let hash = &env!("VERGEN_GIT_SHA")[0..8]; let board = board.board_hal.get_esp(); - let ota_slot = board.slot; - let address = board.slot_addres; - let partition = if ota_slot == 0 { - "ota_1 @ " - } else { - "ota_0 @ " - }; + let ota_slot = board.get_ota_slot(); VersionInfo { git_hash: branch + "@" + hash, build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(), - partition: partition.to_owned() + &address.to_string(), + partition: ota_slot, } } diff --git a/rust/src/webserver/mod.rs b/rust/src/webserver/mod.rs index 6a3eac5..8d30871 100644 --- a/rust/src/webserver/mod.rs +++ b/rust/src/webserver/mod.rs @@ -1,36 +1,22 @@ //offer ota and config mode -use crate::{ - config::PlantControllerConfig, - do_secure_pump, get_version, - hal::PLANT_COUNT, - log::LogMessage, - plant_state::{MoistureSensorState, PlantState}, - VersionInfo, BOARD_ACCESS, -}; -use alloc::boxed::Box; +use crate::{get_version, log::LogMessage, BOARD_ACCESS}; use alloc::string::{String, ToString}; use alloc::sync::Arc; use alloc::vec::Vec; -use anyhow::{bail, Context}; -use chrono::DateTime; -use core::fmt::{Debug, Display, Pointer}; -use core::future::Future; +use core::fmt::{Debug, Display}; use core::net::{IpAddr, Ipv4Addr, SocketAddr}; use core::result::Result::Ok; -use core::sync::atomic::AtomicBool; -use edge_http::io::server::{Connection, DefaultServer, Handler, Server}; +use core::sync::atomic::{AtomicBool, Ordering}; +use edge_http::io::server::{Connection, Handler, Server}; use edge_http::io::Error; use edge_http::Method; use edge_nal::TcpBind; -use edge_nal_embassy::{Tcp, TcpAccept, TcpBuffers}; -use embassy_executor::Spawner; -use embassy_net::tcp::TcpSocket; -use embassy_net::{IpListenEndpoint, Stack}; -use embassy_time::{Duration, Instant, Timer}; +use edge_nal_embassy::{Tcp, TcpBuffers}; +use embassy_net::Stack; +use embassy_time::Instant; use embedded_io_async::{Read, Write}; -use esp_println::{print, println}; -use esp_wifi::wifi::WifiController; +use esp_println::println; use log::info; use serde::{Deserialize, Serialize}; @@ -148,13 +134,6 @@ pub struct NightLampCommand { // anyhow::Ok(Some(json)) // } // -// fn get_config( -// _request: &mut Request<&mut EspHttpConnection>, -// ) -> Result, anyhow::Error> { -// let mut board = BOARD_ACCESS.lock().expect("Should never fail"); -// let json = serde_json::to_string(&board.board_hal.get_config())?; -// anyhow::Ok(Some(json)) -// } // // fn backup_config( // request: &mut Request<&mut EspHttpConnection>, @@ -218,23 +197,6 @@ pub struct NightLampCommand { // } // -// - -// -// fn get_log( -// _request: &mut Request<&mut EspHttpConnection>, -// ) -> Result, anyhow::Error> { -// let output = crate::log::get_log(); -// anyhow::Ok(Some(serde_json::to_string(&output)?)) -// } -// -// fn get_log_localization_config() -> Result { -// anyhow::Ok(serde_json::to_string( -// &LogMessage::to_log_localisation_config(), -// )?) -// } -// - // // fn pump_test( // request: &mut Request<&mut EspHttpConnection>, @@ -347,7 +309,9 @@ pub struct NightLampCommand { // } // } -struct HttpHandler; +struct HttpHandler { + reboot_now: Arc, +} impl Handler for HttpHandler { type Error @@ -389,6 +353,12 @@ impl Handler for HttpHandler { conn.write_all(include_bytes!("bundle.js")).await?; Some(200) } + "/reboot" => { + let mut board = BOARD_ACCESS.get().await.lock().await; + board.board_hal.get_esp().set_restart_to_conf(true); + self.reboot_now.store(true, Ordering::Relaxed); + Some(200) + } &_ => { let json = match path { "/version" => Some(get_version_web(conn).await), @@ -398,6 +368,7 @@ impl Handler for HttpHandler { "/get_config" => Some(get_config(conn).await), "/files" => Some(list_files(conn).await), "/log_localization" => Some(get_log_localization_config(conn).await), + "/log" => Some(get_log(conn).await), _ => None, }; match json { @@ -425,6 +396,23 @@ impl Handler for HttpHandler { } } +// .fn_handler("/reboot", Method::Post, move |_| { +// BOARD_ACCESS +// .lock() +// .unwrap() +// .board_hal +// .get_esp() +// .set_restart_to_conf(true); +// reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed); +// anyhow::Ok(()) + +async fn get_log( + _request: &mut Connection<'_, T, N>, +) -> Result, anyhow::Error> { + let output = crate::log::get_log().await; + anyhow::Ok(Some(serde_json::to_string(&output)?)) +} + async fn get_log_localization_config( _request: &mut Connection<'_, T, N>, ) -> Result, anyhow::Error> { @@ -512,7 +500,7 @@ pub async fn httpd(reboot_now: Arc, stack: Stack<'static>) { let mut server: Server<2, 512, 10> = Server::new(); server - .run(Some(5000), acceptor, HttpHandler) + .run(Some(5000), acceptor, HttpHandler { reboot_now }) .await .expect("TODO: panic message"); println!("Wait for connection..."); @@ -790,47 +778,6 @@ pub async fn httpd(reboot_now: Arc, stack: Stack<'static>) { //server } // -// fn cors_response( -// request: Request<&mut EspHttpConnection>, -// status: u16, -// body: &str, -// ) -> Result<(), anyhow::Error> { -// let headers = [ -// ("Access-Control-Allow-Origin", "*"), -// ("Access-Control-Allow-Headers", "*"), -// ("Access-Control-Allow-Methods", "*"), -// ]; -// let mut response = request.into_response(status, None, &headers)?; -// response.write(body.as_bytes())?; -// response.flush()?; -// anyhow::Ok(()) -// } -// -// type AnyhowHandler = -// fn(&mut Request<&mut EspHttpConnection>) -> Result, anyhow::Error>; -// fn handle_error_to500( -// mut request: Request<&mut EspHttpConnection>, -// chain: AnyhowHandler, -// ) -> Result<(), anyhow::Error> { -// let error = chain(&mut request); -// match error { -// Ok(answer) => match answer { -// Some(json) => { -// cors_response(request, 200, &json)?; -// } -// None => { -// cors_response(request, 200, "")?; -// } -// }, -// Err(err) => { -// let error_text = err.to_string(); -// log::info!("error handling process {}", error_text); -// cors_response(request, 500, &error_text)?; -// } -// } -// anyhow::Ok(()) -// } -// // fn read_up_to_bytes_from_request( // request: &mut Request<&mut EspHttpConnection<'_>>, // limit: Option,