From be3c4a50953ed708a05c946814891dbf39a5f7f0 Mon Sep 17 00:00:00 2001 From: Empire Phoenix Date: Sat, 13 Sep 2025 20:56:11 +0200 Subject: [PATCH] startup and ota state detection working, now wifi_ap next --- rust/Cargo.toml | 34 ++++-- rust/build.rs | 8 +- rust/src/hal/battery.rs | 41 ++++--- rust/src/hal/esp.rs | 51 ++++----- rust/src/hal/initial_hal.rs | 11 +- rust/src/hal/mod.rs | 118 ++++++++++++++------ rust/src/log/mod.rs | 44 ++++---- rust/src/main.rs | 209 +++++++++++++++++++----------------- rust/src/tank.rs | 3 +- 9 files changed, 294 insertions(+), 225 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5993509..e142c6b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -4,6 +4,15 @@ name = "plant-ctrl2" rust-version = "1.86" version = "0.1.0" +# Explicitly configure the binary target and disable building it as a test/bench. +[[bin]] +name = "plant-ctrl2" +path = "src/main.rs" +# Prevent IDEs/Cargo from trying to compile a test harness for this no_std binary. +test = false +bench = false +doc = false + [package.metadata.cargo_runner] # The string `$TARGET_FILE` will be replaced with the path from cargo. command = [ @@ -66,6 +75,8 @@ esp-backtrace = { version = "0.17.0", features = [ "exception-handler", "panic-handler", "println", + "colors", + "custom-halt" ] } esp-println = { version = "0.15.0", features = ["esp32c6", "log-04"] } # for more networking protocol support see https://crates.io/crates/edge-net @@ -75,14 +86,16 @@ embassy-executor = { version = "0.7.0", features = [ ] } embassy-time = { version = "0.4.0", features = ["log"] } esp-hal-embassy = { version = "0.9.0", features = ["esp32c6", "log-04"] } -#esp-wifi = { version = "0.15.0", features = [ -# "builtin-scheduler", -# "esp-alloc", -# "esp32c6", -# "log-04", -# "smoltcp", -# "wifi", -#] } +esp-storage = { version = "0.7.0", features = ["esp32c6"] } + +esp-wifi = { version = "0.15.0", features = [ + "builtin-scheduler", + "esp-alloc", + "esp32c6", + "log-04", + "smoltcp", + "wifi", +] } #smoltcp = { version = "0.12.0", default-features = false, features = [ # "log", # "medium-ethernet", @@ -126,7 +139,6 @@ eeprom24x = "0.7.2" crc = "3.2.1" bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"] } ringbuffer = "0.15.0" -#text-template = "0.1.0" strum_macros = "0.27.0" #esp-ota = { version = "0.2.2", features = ["log"] } unit-enum = "1.4.1" @@ -139,6 +151,10 @@ portable-atomic = "1.11.1" embassy-sync = { version = "0.7.2", features = ["log"] } async-trait = "0.1.89" bq34z100 = { version = "0.4.0", default-features = false } +edge-dhcp = "0.6.0" +edge-nal = "0.5.0" +edge-nal-embassy = "0.6.0" +static_cell = "2.1.1" [patch.crates-io] diff --git a/rust/build.rs b/rust/build.rs index 33f236a..de1608d 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -50,13 +50,13 @@ fn linker_be_nice() { } fn main() { - //webpack(); - //linker_be_nice(); + webpack(); + linker_be_nice(); let _ = EmitBuilder::builder().all_git().all_build().emit(); } fn webpack() { - println!("cargo:rerun-if-changed=./src/src_webpack"); + //println!("cargo:rerun-if-changed=./src/src_webpack"); Command::new("rm") .arg("./src/webserver/bundle.js") .output() @@ -118,4 +118,4 @@ fn webpack() { .unwrap(); } } -} \ No newline at end of file +} diff --git a/rust/src/hal/battery.rs b/rust/src/hal/battery.rs index 5ada3c3..44e2cbf 100644 --- a/rust/src/hal/battery.rs +++ b/rust/src/hal/battery.rs @@ -1,23 +1,20 @@ -use alloc::string::String; use crate::hal::Box; -use anyhow::anyhow; +use alloc::string::String; use async_trait::async_trait; -use bq34z100::{Bq34Z100Error, Bq34z100g1Driver}; -use measurements::Temperature; use serde::Serialize; #[async_trait] pub trait BatteryInteraction { - async fn state_charge_percent(& mut self) -> Result; - async fn remaining_milli_ampere_hour(& mut self) -> Result; - async fn max_milli_ampere_hour(& mut self) -> Result; - async fn design_milli_ampere_hour(& mut self) -> Result; - async fn voltage_milli_volt(& mut self) -> Result; - async fn average_current_milli_ampere(& mut self) -> Result; - async fn cycle_count(& mut self) -> Result; - async fn state_health_percent(& mut self) -> Result; - async fn bat_temperature(& mut self) -> Result; - async fn get_battery_state(& mut self) -> Result; + async fn state_charge_percent(&mut self) -> Result; + async fn remaining_milli_ampere_hour(&mut self) -> Result; + async fn max_milli_ampere_hour(&mut self) -> Result; + async fn design_milli_ampere_hour(&mut self) -> Result; + async fn voltage_milli_volt(&mut self) -> Result; + async fn average_current_milli_ampere(&mut self) -> Result; + async fn cycle_count(&mut self) -> Result; + async fn state_health_percent(&mut self) -> Result; + async fn bat_temperature(&mut self) -> Result; + async fn get_battery_state(&mut self) -> Result; } #[derive(Debug, Serialize)] @@ -56,31 +53,31 @@ pub enum BatteryState { pub struct NoBatteryMonitor {} #[async_trait] impl BatteryInteraction for NoBatteryMonitor { - async fn state_charge_percent(& mut self) -> Result { + async fn state_charge_percent(&mut self) -> Result { Err(BatteryError::NoBatteryMonitor) } - async fn remaining_milli_ampere_hour(& mut self) -> Result { + async fn remaining_milli_ampere_hour(&mut self) -> Result { Err(BatteryError::NoBatteryMonitor) } - async fn max_milli_ampere_hour(& mut self) -> Result { + async fn max_milli_ampere_hour(&mut self) -> Result { Err(BatteryError::NoBatteryMonitor) } - async fn design_milli_ampere_hour(& mut self) -> Result { + async fn design_milli_ampere_hour(&mut self) -> Result { Err(BatteryError::NoBatteryMonitor) } - async fn voltage_milli_volt(& mut self) -> Result { + async fn voltage_milli_volt(&mut self) -> Result { Err(BatteryError::NoBatteryMonitor) } - async fn average_current_milli_ampere(& mut self) -> Result { + async fn average_current_milli_ampere(&mut self) -> Result { Err(BatteryError::NoBatteryMonitor) } - async fn cycle_count(& mut self) -> Result { + async fn cycle_count(&mut self) -> Result { Err(BatteryError::NoBatteryMonitor) } @@ -92,7 +89,7 @@ impl BatteryInteraction for NoBatteryMonitor { Err(BatteryError::NoBatteryMonitor) } - async fn get_battery_state(& mut self) -> Result { + async fn get_battery_state(&mut self) -> Result { Ok(BatteryState::Unknown) } } diff --git a/rust/src/hal/esp.rs b/rust/src/hal/esp.rs index 6d239de..d61866e 100644 --- a/rust/src/hal/esp.rs +++ b/rust/src/hal/esp.rs @@ -6,14 +6,14 @@ use anyhow::{anyhow, bail, Context}; use chrono::{DateTime, Utc}; use serde::Serialize; -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; +use alloc::{string::String, vec::Vec}; use core::marker::PhantomData; use core::net::IpAddr; use core::str::FromStr; -use embassy_time::{Instant, Timer}; +use embassy_time::Instant; +use esp_bootloader_esp_idf::ota::OtaImageState; +use esp_hal::gpio::Input; +use esp_storage::FlashStorage; #[link_section = ".rtc.data"] static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; @@ -51,11 +51,16 @@ pub struct MqttClient<'a> { base_topic: heapless::String<64>, } pub struct Esp<'a> { + pub boot_button: Input<'a>, pub(crate) mqtt_client: Option>, pub(crate) dummy: PhantomData<&'a ()>, - pub(crate) wall_clock_offset: u64 + pub(crate) wall_clock_offset: u64, //pub(crate) wifi_driver: EspWifi<'a>, //pub(crate) boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, + pub storage: FlashStorage, + pub slot: usize, + pub next_slot: usize, + pub ota_state: OtaImageState, } pub struct IpInfo { @@ -72,10 +77,9 @@ impl Esp<'_> { const BASE_PATH: &'static str = "/spiffs"; pub(crate) fn mode_override_pressed(&mut self) -> bool { - todo!(); - //self.boot_button.get_level() == Level::Low + self.boot_button.is_low() } - pub(crate) async fn sntp(&mut self, max_wait_ms: u32) -> anyhow::Result> { + pub(crate) async fn sntp(&mut self, _max_wait_ms: u32) -> anyhow::Result> { //let sntp = sntp::EspSntp::new_default()?; //let mut counter = 0; //while sntp.get_sync_status() != SyncStatus::Completed { @@ -147,10 +151,11 @@ impl Esp<'_> { } pub(crate) async fn wifi_ap(&mut self) -> anyhow::Result<()> { - let ssid = match self.load_config() { + let _ssid = match self.load_config() { Ok(config) => config.network.ap_ssid.clone(), Err(_) => heapless::String::from_str("PlantCtrl Emergency Mode").unwrap(), }; + todo!("todo"); // // let apconfig = AccessPointConfiguration { @@ -165,15 +170,13 @@ impl Esp<'_> { // anyhow::Ok(()) } - - pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> anyhow::Result { - let ssid = network_config + let _ssid = network_config .ssid .clone() .ok_or(anyhow!("No ssid configured"))?; - let password = network_config.password.clone(); - let max_wait = network_config.max_wait; + let _password = network_config.password.clone(); + let _max_wait = network_config.max_wait; bail!("todo") // match password { // Some(pw) => { @@ -237,7 +240,7 @@ impl Esp<'_> { } pub(crate) async fn save_config( &mut self, - config: &PlantControllerConfig, + _config: &PlantControllerConfig, ) -> anyhow::Result<()> { bail!("todo"); // let mut cfg = File::create(Self::CONFIG_FILE)?; @@ -247,9 +250,9 @@ impl Esp<'_> { } pub(crate) fn mount_file_system(&mut self) -> anyhow::Result<()> { bail!("fail"); - log(LogMessage::MountingFilesystem, 0, 0, "", ""); - let base_path = String::try_from("/spiffs")?; - let storage = String::try_from(Self::SPIFFS_PARTITION_NAME)?; + // log(LogMessage::MountingFilesystem, 0, 0, "", ""); + // let base_path = String::try_from("/spiffs")?; + // let storage = String::try_from(Self::SPIFFS_PARTITION_NAME)?; //let conf = todo!(); //let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { @@ -272,7 +275,7 @@ impl Esp<'_> { // &free_space.used_size.to_string(), // "", // ); - anyhow::Ok(()) + // anyhow::Ok(()) } async fn file_system_size(&mut self) -> anyhow::Result { bail!("fail"); @@ -348,7 +351,7 @@ impl Esp<'_> { // iter_error, // } } - pub(crate) async fn delete_file(&self, filename: &str) -> anyhow::Result<()> { + pub(crate) async fn delete_file(&self, _filename: &str) -> anyhow::Result<()> { bail!("todo"); // let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); // match fs::remove_file(filepath) { @@ -425,7 +428,7 @@ impl Esp<'_> { if base_topic.is_empty() { bail!("Mqtt base_topic was empty") } - let base_topic_copy = base_topic.clone(); + let _base_topic_copy = base_topic.clone(); let mqtt_url = network_config .mqtt_url .as_ref() @@ -583,8 +586,8 @@ impl Esp<'_> { } pub(crate) async fn mqtt_publish( &mut self, - subtopic: &str, - message: &[u8], + _subtopic: &str, + _message: &[u8], ) -> anyhow::Result<()> { bail!("todo"); // diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs index a378e78..89ecf45 100644 --- a/rust/src/hal/initial_hal.rs +++ b/rust/src/hal/initial_hal.rs @@ -1,7 +1,8 @@ -use alloc::vec::Vec; use crate::hal::esp::Esp; 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::{ config::PlantControllerConfig, @@ -10,9 +11,7 @@ use crate::{ use anyhow::{bail, Result}; use async_trait::async_trait; use chrono::{DateTime, Utc}; -use embedded_hal::digital::OutputPin; use measurements::{Current, Voltage}; -use crate::alloc::boxed::Box; pub struct Initial<'a> { //pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, @@ -49,7 +48,7 @@ impl RTCModuleInteraction for NoRTC { pub(crate) fn create_initial_board( //free_pins: FreePeripherals, - fs_mount_error: bool, + _fs_mount_error: bool, config: PlantControllerConfig, esp: Esp<'static>, ) -> Result + Send>> { @@ -123,7 +122,7 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { bail!("Please configure board revision") } - async fn general_fault(&mut self, enable: bool) { + async fn general_fault(&mut self, _enable: bool) { //let _ = self.general_fault.set_state(enable.into()); } @@ -134,7 +133,7 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { async fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { self.config = config; //TODO -// self.esp.save_config(&self.config)?; + // self.esp.save_config(&self.config)?; anyhow::Ok(()) } diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index 370f8bd..2eaecee 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -5,7 +5,7 @@ mod rtc; //mod water; use crate::alloc::string::ToString; -use crate::hal::rtc::{RTCModuleInteraction}; +use crate::hal::rtc::RTCModuleInteraction; //use crate::hal::water::TankSensor; use crate::{ config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, @@ -17,20 +17,24 @@ use crate::{ }; use alloc::boxed::Box; use alloc::format; -use core::marker::PhantomData; use anyhow::{Ok, Result}; use async_trait::async_trait; +use core::marker::PhantomData; +use embassy_executor::Spawner; //use battery::BQ34Z100G1; -use bq34z100::Bq34z100g1Driver; -use ds323x::{DateTimeAccess, Ds323x}; -use eeprom24x::{Eeprom24x, SlaveAddr, Storage}; -use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex}; +//use bq34z100::Bq34z100g1Driver; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::Mutex; -use embassy_sync::lazy_lock::LazyLock; +use esp_bootloader_esp_idf::partitions::DataPartitionSubType; use esp_hal::clock::CpuClock; +use esp_hal::gpio::{Input, InputConfig, Pull}; use esp_hal::timer::systimer::SystemTimer; +use esp_println::println; use measurements::{Current, Voltage}; +use esp_alloc as _; +use esp_backtrace as _; + //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; @@ -38,24 +42,34 @@ const TANK_MULTI_SAMPLE: usize = 11; //pub static I2C_DRIVER: LazyLock>> = LazyLock::new(PlantHal::create_i2c); -fn deep_sleep(duration_in_ms: u64) -> ! { +// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html +macro_rules! mk_static { + ($t:ty,$val:expr) => {{ + static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); + #[deny(unused_attributes)] + let x = STATIC_CELL.uninit().write(($val)); + x + }}; +} + +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!() - } + // //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!() + } //}; } @@ -95,7 +109,6 @@ pub trait BoardInteraction<'a> { async fn get_mptt_current(&mut self) -> anyhow::Result; } - impl dyn BoardInteraction<'_> { //the counter is just some arbitrary number that increases whenever some progress was made, try to keep the updates < 10 per second for ux reasons async fn _progress(&mut self, counter: u32) { @@ -163,17 +176,19 @@ impl PlantHal { // Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) // } - pub fn create() -> Result>> { + pub fn create(spawner: Spawner) -> Result>> { let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); esp_alloc::heap_allocator!(size: 64 * 1024); - let timer0 = SystemTimer::new(peripherals.SYSTIMER); - esp_hal_embassy::init(timer0.alarm0); + let systimer = SystemTimer::new(peripherals.SYSTIMER); + esp_hal_embassy::init(systimer.alarm0); + let boot_button = Input::new( + peripherals.GPIO9, + InputConfig::default().with_pull(Pull::None), + ); - // let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; - // boot_button.set_pull(Pull::Floating)?; // // let free_pins = FreePeripherals { // can: peripherals.can, @@ -210,13 +225,46 @@ impl PlantHal { // gpio30: peripherals.pins.gpio30, // }; // + + let mut storage = esp_storage::FlashStorage::new(); + let mut buffer = [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN]; + let pt = + esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buffer)?; + + // 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 + .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 mut esp = Esp { - mqtt_client: None, + boot_button, + mqtt_client: None, + storage, + slot: current.number(), + next_slot: current.next().number(), + ota_state, dummy: PhantomData::default(), - wall_clock_offset : 0 - // wifi_driver, - // boot_button - }; + wall_clock_offset: 0, // wifi_driver, + // boot_button + }; //init,reset rtc memory depending on cause let mut init_rtc_store: bool = false; diff --git a/rust/src/log/mod.rs b/rust/src/log/mod.rs index d20027d..338ac4c 100644 --- a/rust/src/log/mod.rs +++ b/rust/src/log/mod.rs @@ -4,8 +4,10 @@ use alloc::vec::Vec; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::lazy_lock::LazyLock; use embassy_sync::mutex::Mutex; +use embassy_time::Instant; +use log::info; use serde::Serialize; -use strum_macros::{EnumIter, IntoStaticStr}; +use strum_macros::IntoStaticStr; use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; use unit_enum::UnitEnum; @@ -19,8 +21,9 @@ const BUFFER_SIZE: usize = 220; static mut BUFFER: ConstGenericRingBuffer = ConstGenericRingBuffer::::new(); #[allow(static_mut_refs)] -static BUFFER_ACCESS: LazyLock>> = - LazyLock::new(|| unsafe { Mutex::new(&mut BUFFER) }); +static BUFFER_ACCESS: LazyLock< + Mutex>, +> = LazyLock::new(|| unsafe { Mutex::new(&mut BUFFER) }); #[derive(Serialize, Debug, Clone)] pub struct LogEntry { @@ -69,7 +72,13 @@ pub async fn get_log() -> Vec { read_copy } -pub async fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &str, txt_long: &str) { +pub async fn log( + message_key: LogMessage, + number_a: u32, + number_b: u32, + txt_short: &str, + txt_long: &str, +) { let mut txt_short_stack: heapless::String = heapless::String::new(); let mut txt_long_stack: heapless::String = heapless::String::new(); @@ -77,27 +86,18 @@ pub async fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_shor limit_length(txt_long, &mut txt_long_stack); //TODO - let time = 0; - + let time = Instant::now().as_secs(); // let time = EspSystemTime {}.now().as_millis() as u64; // let ordinal = message_key.ordinal() as u16; - // let template_string: &str = message_key.into(); - // - // let mut values: HashMap<&str, &str> = HashMap::new(); - // let number_a_str = number_a.to_string(); - // let number_b_str = number_b.to_string(); - // - // values.insert("number_a", &number_a_str); - // values.insert("number_b", &number_b_str); - // values.insert("txt_short", txt_short); - // values.insert("txt_long", txt_long); - // - // let template = Template::from(template_string); - // let serial_entry = template.fill_in(&values); - // - // log::info!("{serial_entry}"); - // //TODO push to mqtt? + let template: &str = message_key.into(); + let mut template_string = template.to_string(); + template_string = template_string.replace("${number_a}", number_a.to_string().as_str()); + template_string = template_string.replace("${number_b}", number_b.to_string().as_str()); + template_string = template_string.replace("${txt_long}", txt_long); + template_string = template_string.replace("${txt_short}", txt_short); + + info!("LOG: {} : {}", time, template_string); let entry = LogEntry { timestamp: time, diff --git a/rust/src/main.rs b/rust/src/main.rs index 1b83dc1..a20b637 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -10,6 +10,7 @@ esp_bootloader_esp_idf::esp_app_desc!(); use esp_backtrace as _; use crate::config::PlantConfig; +use crate::tank::WATER_FROZEN_THRESH; use crate::{ config::BoardVersion::INITIAL, hal::{PlantHal, HAL, PLANT_COUNT}, @@ -20,23 +21,36 @@ use alloc::borrow::ToOwned; use alloc::string::{String, ToString}; use alloc::sync::Arc; use alloc::{format, vec}; -use core::any::Any; -use anyhow::{bail, Context}; use chrono::{DateTime, Datelike, Timelike, Utc}; use chrono_tz::Tz::{self}; use core::sync::atomic::Ordering; use embassy_executor::Spawner; -use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; -use embassy_sync::lazy_lock::LazyLock; -use embassy_sync::mutex::Mutex; +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_bootloader_esp_idf::ota::OtaImageState; +use esp_hal::rom::ets_delay_us; +use esp_hal::system::software_reset; use esp_println::{logger, println}; use hal::battery::BatteryState; use log::{log, LogMessage}; use plant_state::PlantState; use portable_atomic::AtomicBool; use serde::{Deserialize, Serialize}; -use crate::tank::{TankError, WATER_FROZEN_THRESH}; + +#[no_mangle] +extern "C" fn custom_halt() -> ! { + println!("Fatal error occurred. Restarting in 10 seconds..."); + + for _delay in 0..30 { + ets_delay_us(1_000_000); + } + println!("resetting"); + //give serial transmit time to finish + ets_delay_us(500_000); + software_reset() +} //use tank::*; mod config; @@ -48,10 +62,8 @@ mod tank; extern crate alloc; //mod webserver; -pub static BOARD_ACCESS: LazyLock> = - LazyLock::new(|| { - PlantHal::create().unwrap() - }); +pub static BOARD_ACCESS: OnceLock> = OnceLock::new(); + pub static STAY_ALIVE: AtomicBool = AtomicBool::new(false); #[derive(Serialize, Deserialize, Debug, PartialEq)] @@ -140,14 +152,9 @@ async fn safe_main() -> anyhow::Result<()> { version.git_hash, version.build_time ); + let _esp = BOARD_ACCESS.get().await.lock().await.board_hal.get_esp(); + //TODO - //let count = unsafe { esp_ota_get_app_partition_count() }; - //log::info!("Partition count is {}", count); - //let mut ota_state: esp_ota_img_states_t = 0; - //let running_partition = unsafe { esp_ota_get_running_partition() }; - //let partition_address = unsafe { (*running_partition).address }; - //log::info!("Partition address is {}", address); - let partition_address = 0x1337; // TODO //let ota_state_string = unsafe { @@ -169,9 +176,9 @@ async fn safe_main() -> anyhow::Result<()> { //} //}; //log(LogMessage::PartitionState, 0, 0, "", ota_state_string); - let ota_state_string = "unknown"; + let _ota_state_string = "unknown"; println!("faul led"); - let mut board = BOARD_ACCESS.get().lock().await; + let mut board = BOARD_ACCESS.get().await.lock().await; board.board_hal.general_fault(false).await; println!("faul led2"); let cur = board @@ -184,7 +191,6 @@ async fn safe_main() -> anyhow::Result<()> { board.board_hal.general_fault(true); anyhow::Ok(board.board_hal.get_esp().time()) })?; - //check if we know the time current > 2020 (plausibility checks, this code is newer than 2020) if cur.year() < 2020 { to_config = true; @@ -192,10 +198,10 @@ async fn safe_main() -> anyhow::Result<()> { } info!("cur is {}", cur); - update_charge_indicator().await; - + update_charge_indicator(&mut board).await; + println!("faul led3"); if board.board_hal.get_esp().get_restart_to_conf() { - log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", ""); + log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "").await; for _i in 0..2 { board.board_hal.general_fault(true).await; Timer::after_millis(100).await; @@ -221,12 +227,15 @@ async fn safe_main() -> anyhow::Result<()> { } else { board.board_hal.general_fault(false).await; } + } else { + info!("no mode override"); } if board.board_hal.get_config().hardware.board == INITIAL && board.board_hal.get_config().network.ssid.is_none() { - let _ = board.board_hal.get_esp().wifi_ap(); + info!("No wifi configured, starting initial config mode"); + board.board_hal.get_esp().wifi_ap().await.unwrap(); drop(board); let reboot_now = Arc::new(AtomicBool::new(false)); //TODO @@ -254,7 +263,6 @@ async fn safe_main() -> anyhow::Result<()> { } } - // let timezone = match &board.board_hal.get_config().timezone { // Some(tz_str) => tz_str.parse::().unwrap_or_else(|_| { // info!("Invalid timezone '{}', falling back to UTC", tz_str); @@ -262,25 +270,18 @@ async fn safe_main() -> anyhow::Result<()> { // }), // None => UTC, // Fallback to UTC if no timezone is set // }; - let timezone = Tz::UTC; + let _timezone = Tz::UTC; - let timezone_time = cur;//TODO.with_timezone(&timezone); + drop(board); + + let timezone_time = cur; //TODO.with_timezone(&timezone); info!( "Running logic at utc {} and {} {}", - cur, - "todo timezone.name()", - timezone_time + cur, "todo timezone.name()", timezone_time ); if let NetworkMode::WIFI { ref ip_address, .. } = network_mode { - publish_firmware_info( - version, - partition_address, - ota_state_string, - ip_address, - &timezone_time.to_rfc3339(), - ) - .await; + publish_firmware_info(version, ip_address, &timezone_time.to_rfc3339()).await; publish_battery_state().await; let _ = publish_mppt_state().await; } @@ -299,10 +300,8 @@ async fn safe_main() -> anyhow::Result<()> { .to_string() .as_str(), "", - ).await; - - //TODO must drop board here? - //drop(board); + ) + .await; if to_config { //check if client or ap mode and init Wi-Fi @@ -316,7 +315,7 @@ async fn safe_main() -> anyhow::Result<()> { log(LogMessage::NormalRun, 0, 0, "", "").await; } - let dry_run = false; + let _dry_run = false; // // let tank_state = determine_tank_state(&mut board); // @@ -353,7 +352,7 @@ async fn safe_main() -> anyhow::Result<()> { // } // } - let mut water_frozen = false; + let mut _water_frozen = false; //TODO let water_temp = anyhow::Ok(12_f32); // board @@ -364,14 +363,14 @@ async fn safe_main() -> anyhow::Result<()> { if let Ok(res) = water_temp { if res < WATER_FROZEN_THRESH { - water_frozen = true; + _water_frozen = true; } } //publish_tank_state(&tank_state, &water_temp).await; - - let plantstate: [PlantState; PLANT_COUNT] = [ + board = BOARD_ACCESS.get().await.lock().await; + let _plantstate: [PlantState; PLANT_COUNT] = [ PlantState::read_hardware_state(0, &mut board).await, PlantState::read_hardware_state(1, &mut board).await, PlantState::read_hardware_state(2, &mut board).await, @@ -468,7 +467,7 @@ async fn safe_main() -> anyhow::Result<()> { .await .unwrap_or(BatteryState::Unknown); - let mut light_state = LightState { + let light_state = LightState { enabled: board.board_hal.get_config().night_lamp.enabled, ..Default::default() }; @@ -611,7 +610,7 @@ pub async fn do_secure_pump( let mut error = false; let mut first_error = true; let mut pump_time_s = 0; - let board = &mut BOARD_ACCESS.get().lock().await; + let board = &mut BOARD_ACCESS.get().await.lock().await; if !dry_run { // board // .board_hal @@ -733,8 +732,7 @@ pub async fn do_secure_pump( }) } -async fn update_charge_indicator() { - let board = &mut BOARD_ACCESS.get().lock().await; +async fn update_charge_indicator(board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'_>>) { //we have mppt controller, ask it for charging current // let tank_state = determine_tank_state(&mut board); // @@ -779,7 +777,8 @@ async fn update_charge_indicator() { else if let Ok(charging) = board .board_hal .get_battery_monitor() - .average_current_milli_ampere().await + .average_current_milli_ampere() + .await { let _ = board.board_hal.set_charge_indicator(charging > 20); } else { @@ -830,51 +829,45 @@ async fn update_charge_indicator() { // } // } -async fn publish_firmware_info( - version: VersionInfo, - address: u32, - ota_state_string: &str, - ip_address: &String, - timezone_time: &String, -) { - let board = &mut BOARD_ACCESS.get().lock().await; - let _ = board - .board_hal - .get_esp() +async fn publish_firmware_info(version: VersionInfo, ip_address: &String, timezone_time: &String) { + let board = &mut BOARD_ACCESS.get().await.lock().await; + let esp = board.board_hal.get_esp(); + let _ = esp .mqtt_publish("/firmware/address", ip_address.as_bytes()) .await; - let _ = board - .board_hal - .get_esp() + let _ = esp .mqtt_publish("/firmware/githash", version.git_hash.as_bytes()) .await; - let _ = board - .board_hal - .get_esp() + let _ = esp .mqtt_publish("/firmware/buildtime", version.build_time.as_bytes()) .await; - let _ = board.board_hal.get_esp().mqtt_publish( - "/firmware/last_online", - timezone_time.as_bytes(), - ); - let _ = board - .board_hal - .get_esp() - .mqtt_publish("/firmware/ota_state", ota_state_string.as_bytes()) + let _ = esp.mqtt_publish("/firmware/last_online", timezone_time.as_bytes()); + let _ = esp + .mqtt_publish( + "/firmware/ota_state", + state_to_string(esp.ota_state).as_bytes(), + ) .await; - let _ = board.board_hal.get_esp().mqtt_publish( - "/firmware/partition_address", - format!("{:#06x}", address).as_bytes(), - ); - let _ = board - .board_hal - .get_esp() - .mqtt_publish("/state", "online".as_bytes()) + let slot = esp.slot; + let _ = esp + .mqtt_publish("/firmware/ota_slot", format!("slot{slot}").as_bytes()) .await; + let _ = esp.mqtt_publish("/state", "online".as_bytes()).await; +} + +fn state_to_string(state: OtaImageState) -> &'static str { + match state { + OtaImageState::New => "New", + OtaImageState::PendingVerify => "PendingVerify", + OtaImageState::Valid => "Valid", + OtaImageState::Invalid => "Invalid", + OtaImageState::Aborted => "Aborted", + OtaImageState::Undefined => "Undefined", + } } async fn try_connect_wifi_sntp_mqtt() -> NetworkMode { - let board = &mut BOARD_ACCESS.get().lock().await; + let board = &mut BOARD_ACCESS.get().await.lock().await; let nw_conf = &board.board_hal.get_config().network.clone(); match board.board_hal.get_esp().wifi(nw_conf).await { Ok(ip_info) => { @@ -941,6 +934,7 @@ async fn pump_info( Ok(state) => { let _ = BOARD_ACCESS .get() + .await .lock() .await .board_hal @@ -957,7 +951,7 @@ async fn pump_info( } async fn publish_mppt_state() -> anyhow::Result<()> { - let board_hal = &mut BOARD_ACCESS.get().lock().await.board_hal; + let board_hal = &mut BOARD_ACCESS.get().await.lock().await.board_hal; let current = board_hal.get_mptt_current().await?; let voltage = board_hal.get_mptt_voltage().await?; let solar_state = Solar { @@ -975,8 +969,12 @@ async fn publish_mppt_state() -> anyhow::Result<()> { } async fn publish_battery_state() -> () { - let board = &mut BOARD_ACCESS.get().lock().await; - let state = board.board_hal.get_battery_monitor().get_battery_state().await; + let board = &mut BOARD_ACCESS.get().await.lock().await; + let state = board + .board_hal + .get_battery_monitor() + .get_battery_state() + .await; if let Ok(serialized_battery_state_bytes) = serde_json::to_string(&state).map(|s| s.into_bytes()) { @@ -992,8 +990,9 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { let mut led_count = 8; let mut pattern_step = 0; loop { - update_charge_indicator().await; - let mut board = BOARD_ACCESS.get().lock().await; + let mut board = BOARD_ACCESS.get().await.lock().await; + update_charge_indicator(&mut board).await; + match wait_type { WaitType::MissingConfig => { // Keep existing behavior: circular filling pattern @@ -1023,7 +1022,7 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { drop(board); Timer::after_millis(delay).await; - let mut board = BOARD_ACCESS.get().lock().await; + let mut board = BOARD_ACCESS.get().await.lock().await; board.board_hal.general_fault(false); // Clear all LEDs @@ -1039,7 +1038,13 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { if reboot_now.load(Ordering::Relaxed) { //ensure clean http answer Timer::after_millis(500).await; - BOARD_ACCESS.get().lock().await.board_hal.deep_sleep(1); + BOARD_ACCESS + .get() + .await + .lock() + .await + .board_hal + .deep_sleep(1); } } } @@ -1049,19 +1054,21 @@ async fn main(spawner: Spawner) { // intialize embassy logger::init_logger_from_env(); //force init here! - let board = BOARD_ACCESS.get().lock().await; - drop(board); - println!("test"); + println!("Hal init"); + match BOARD_ACCESS.init(PlantHal::create(spawner).unwrap()) { + Ok(_) => {} + Err(_) => { + panic!("Could not set hal to static") + } + } + println!("Hal init done, starting logic"); - info!("Embassy initialized!"); - - let result = safe_main().await; - match result { + match safe_main().await { // this should not get triggered, safe_main should not return but go into deep sleep with sensible // timeout, this is just a fallback Ok(_) => { warn!("Main app finished, but should never do, restarting"); - let board = &mut BOARD_ACCESS.get().lock().await.board_hal; + let board = &mut BOARD_ACCESS.get().await.lock().await.board_hal; board.get_esp().set_restart_to_conf(false); board.deep_sleep(1); diff --git a/rust/src/tank.rs b/rust/src/tank.rs index 7f53694..0416302 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -1,6 +1,5 @@ -use crate::{config::TankConfig, hal::HAL}; -use anyhow::Context; use crate::alloc::string::{String, ToString}; +use crate::config::TankConfig; use serde::Serialize; const OPEN_TANK_VOLTAGE: f32 = 3.0;