pub(crate) mod battery; pub mod esp; mod initial_hal; mod little_fs2storage_adapter; mod rtc; //mod water; use crate::alloc::string::ToString; use crate::hal::rtc::RTCModuleInteraction; use esp_hal::peripherals::Peripherals; use esp_hal::peripherals::GPIO23; use esp_hal::peripherals::GPIO6; //use crate::hal::water::TankSensor; use crate::{ config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, hal::{ battery::{BatteryInteraction, NoBatteryMonitor}, esp::Esp, }, log::{LogMessage}, }; use alloc::boxed::Box; use alloc::format; use alloc::sync::Arc; use core::cell::OnceCell; use anyhow::{bail, Ok, Result}; use async_trait::async_trait; use chrono::{DateTime, FixedOffset, Utc}; use embassy_executor::Spawner; //use battery::BQ34Z100G1; //use bq34z100::Bq34z100g1Driver; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use esp_bootloader_esp_idf::partitions::{ AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry, }; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Pull}; use measurements::{Current, Voltage}; use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use esp_alloc as _; use esp_backtrace as _; use esp_bootloader_esp_idf::ota::Slot; use esp_hal::rng::Rng; use esp_hal::rtc_cntl::{Rtc, SocResetReason}; use esp_hal::system::reset_reason; use esp_hal::timer::timg::TimerGroup; use esp_storage::FlashStorage; use esp_wifi::{init, EspWifiController}; use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem}; use littlefs2::object_safe::DynStorage; use log::{info, warn}; use crate::log::{LogArray, LOG_ACCESS}; pub static TIME_ACCESS: OnceLock = OnceLock::new(); //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; const TANK_MULTI_SAMPLE: usize = 11; //pub static I2C_DRIVER: LazyLock>> = LazyLock::new(PlantHal::create_i2c); #[derive(Debug, PartialEq)] pub enum Sensor { A, B, } pub struct PlantHal {} pub struct HAL<'a> { pub board_hal: Box + Send>, } #[async_trait] pub trait BoardInteraction<'a> { //fn get_tank_sensor(&mut self) -> Option<&mut TankSensor>; fn get_esp(&mut self) -> &mut Esp<'a>; fn get_config(&mut self) -> &PlantControllerConfig; 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<()>; async fn deep_sleep(&mut self, duration_in_ms: u64) -> !; fn is_day(&self) -> bool; //should be multsampled fn light(&mut self, enable: bool) -> Result<()>; async fn pump(&mut self, plant: usize, enable: bool) -> Result<()>; async fn pump_current(&mut self, plant: usize) -> Result; async fn fault(&mut self, plant: usize, enable: bool) -> Result<()>; async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result; async fn general_fault(&mut self, enable: bool); async fn test(&mut self) -> Result<()>; fn set_config(&mut self, config: PlantControllerConfig); async fn get_mptt_voltage(&mut self) -> anyhow::Result; 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) { let even = counter % 2 == 0; let current = counter / (PLANT_COUNT as u32); for led in 0..PLANT_COUNT { match self.fault(led, current == led as u32).await { Result::Ok(_) => {} Err(err) => { warn!("Fault on plant {}: {:?}", led, err); } } } let _ = self.general_fault(even.into()); } } #[allow(dead_code)] pub struct FreePeripherals<'a> { // pub gpio0: Gpio0, // pub gpio1: Gpio1, // pub gpio2: Gpio2, // pub gpio3: Gpio3, // pub gpio4: Gpio4, // pub gpio5: Gpio5, pub gpio6: GPIO6<'a>, // pub gpio7: Gpio7, // pub gpio8: Gpio8, // //config button here // pub gpio10: Gpio10, // pub gpio11: Gpio11, // pub gpio12: Gpio12, // pub gpio13: Gpio13, // pub gpio14: Gpio14, // pub gpio15: Gpio15, // pub gpio16: Gpio16, // pub gpio17: Gpio17, // pub gpio18: Gpio18, // //i2c here // pub gpio21: Gpio21, // pub gpio22: Gpio22, pub gpio23: GPIO23<'a>, // pub gpio24: Gpio24, // pub gpio25: Gpio25, // pub gpio26: Gpio26, // pub gpio27: Gpio27, // pub gpio28: Gpio28, // pub gpio29: Gpio29, // pub gpio30: Gpio30, // pub pcnt0: PCNT0, // pub pcnt1: PCNT1, // pub adc1: ADC1, // pub can: CAN, } 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 }}; } const GW_IP_ADDR_ENV: Option<&'static str> = option_env!("GATEWAY_IP"); impl PlantHal { // fn create_i2c() -> Mutex> { // let peripherals = unsafe { Peripherals::new() }; // // let config = I2cConfig::new() // .scl_enable_pullup(true) // .sda_enable_pullup(true) // .baudrate(100_u32.kHz().into()) // .timeout(APBTickType::from(Duration::from_millis(100))); // // let i2c = peripherals.i2c0; // let scl = peripherals.pins.gpio19.downgrade(); // let sda = peripherals.pins.gpio20.downgrade(); // // Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) // } pub async fn create(spawner: Spawner) -> Result>> { let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals: Peripherals = esp_hal::init(config); esp_alloc::heap_allocator!(size: 64 * 1024); esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000); let rtc: Rtc = Rtc::new(peripherals.LPWR); match(TIME_ACCESS.init(rtc)){ Result::Ok(_) => {} Err(_) => {} } let systimer = SystemTimer::new(peripherals.SYSTIMER); let boot_button = Input::new( peripherals.GPIO9, InputConfig::default().with_pull(Pull::None), ); 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()).expect("Could not init wifi controller") ); let (controller, interfaces) = esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).expect("Could not init wifi"); use esp_hal::timer::systimer::SystemTimer; esp_hal_embassy::init(systimer.alarm0); //let mut adc1 = Adc::new(peripherals.ADC1, adc1_config); // let free_pins = FreePeripherals { // can: peripherals.can, // adc1: peripherals.adc1, // pcnt0: peripherals.pcnt0, // pcnt1: peripherals.pcnt1, // gpio0: peripherals.pins.gpio0, // gpio1: peripherals.pins.gpio1, // gpio2: peripherals.pins.gpio2, // gpio3: peripherals.pins.gpio3, // gpio4: peripherals.pins.gpio4, // gpio5: peripherals.pins.gpio5, gpio6: peripherals.GPIO6, // gpio7: peripherals.pins.gpio7, // gpio8: peripherals.pins.gpio8, // gpio10: peripherals.pins.gpio10, // gpio11: peripherals.pins.gpio11, // gpio12: peripherals.pins.gpio12, // gpio13: peripherals.pins.gpio13, // gpio14: peripherals.pins.gpio14, // gpio15: peripherals.pins.gpio15, // gpio16: peripherals.pins.gpio16, // gpio17: peripherals.pins.gpio17, // gpio18: peripherals.pins.gpio18, // gpio21: peripherals.pins.gpio21, // gpio22: peripherals.pins.gpio22, gpio23: peripherals.GPIO23, // gpio24: peripherals.pins.gpio24, // gpio25: peripherals.pins.gpio25, // gpio26: peripherals.pins.gpio26, // gpio27: peripherals.pins.gpio27, // gpio28: peripherals.pins.gpio28, // gpio29: peripherals.pins.gpio29, // gpio30: peripherals.pins.gpio30, }; // let tablebuffer = mk_static!( [u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN], [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN] ); let storage_ota = mk_static!(FlashStorage, FlashStorage::new()); let pt = esp_bootloader_esp_idf::partitions::read_partition_table(storage_ota, tablebuffer)?; // List all partitions - this is just FYI for i in 0..pt.len() { info!("{:?}", pt.get_partition(i)); } let ota_data = mk_static!( PartitionEntry, pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data( DataPartitionSubType::Ota, ))? .expect("No OTA data partition found") ); let ota_data = mk_static!( FlashRegion, ota_data.as_embedded_storage(storage_ota) ); 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 active?"); } Slot::Slot0 => pt .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( AppPartitionSubType::Ota0, ))? .expect("No OTA slot0 found"), Slot::Slot1 => pt .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( AppPartitionSubType::Ota1, ))? .expect("No OTA slot1 found"), }; let ota_next = mk_static!(PartitionEntry, ota_partition); let storage_ota = mk_static!(FlashStorage, FlashStorage::new()); let ota_next = mk_static!( FlashRegion, ota_next.as_embedded_storage(storage_ota) ); let data_partition = pt .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data( DataPartitionSubType::LittleFs, ))? .expect("Data partition with littlefs not found"); let data_partition = mk_static!(PartitionEntry, data_partition); let storage_data = mk_static!(FlashStorage, FlashStorage::new()); let data = mk_static!( FlashRegion, data_partition.as_embedded_storage(storage_data) ); let lfs2filesystem = mk_static!(LittleFs2Filesystem, LittleFs2Filesystem { storage: data }); let alloc = mk_static!(Allocation, lfs2Filesystem::allocate()); if lfs2filesystem.is_mountable() { log::info!("Littlefs2 filesystem is mountable"); } else { match lfs2filesystem.format() { Result::Ok(..) => { log::info!("Littlefs2 filesystem is formatted"); } Err(err) => { bail!("Littlefs2 filesystem could not be formatted: {:?}", err); } } } let fs = Arc::new(Mutex::new( lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"), )); let mut esp = Esp { fs, rng, controller: Arc::new(Mutex::new(controller)), interfaces: Some(interfaces), boot_button, mqtt_client: None, ota, ota_next, wall_clock_offset: 0, }; //init,reset rtc memory depending on cause let mut init_rtc_store: bool = false; let mut to_config_mode: bool = false; let reasons = match reset_reason() { None => { "unknown" } Some(reason) => { match reason { SocResetReason::ChipPowerOn => { "power on" } SocResetReason::CoreSw => { "software reset" } SocResetReason::CoreDeepSleep => { "deep sleep" } SocResetReason::CoreSDIO => { "sdio reset" } SocResetReason::CoreMwdt0 => { "Watchdog Main" } SocResetReason::CoreMwdt1 => { "Watchdog 1" } SocResetReason::CoreRtcWdt => { "Watchdog RTC" } SocResetReason::Cpu0Mwdt0 => { "Watchdog MCpu0" } SocResetReason::Cpu0Sw => { "software reset cpu0" } SocResetReason::Cpu0RtcWdt => { init_rtc_store = true; "Watchdog RTC cpu0" } SocResetReason::SysBrownOut => { "sys brown out" } SocResetReason::SysRtcWdt => { "Watchdog Sys rtc" } SocResetReason::Cpu0Mwdt1 => { "cpu0 mwdt1" } SocResetReason::SysSuperWdt => { "Watchdog Super" } SocResetReason::CoreEfuseCrc => { "core efuse crc" } SocResetReason::CoreUsbUart => { to_config_mode = true; "core usb uart" } SocResetReason::CoreUsbJtag => { "core usb jtag" } SocResetReason::Cpu0JtagCpu => { "cpu0 jtag cpu" } } } }; LOG_ACCESS.lock().await.log( LogMessage::ResetReason, init_rtc_store as u32, to_config_mode as u32, "", &format!("{reasons:?}"), ).await; esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode) .await; let config = esp.load_config().await; log::info!("Init rtc driver"); // let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); // // log::info!("Init rtc eeprom driver"); // let eeprom = { // Eeprom24x::new_24x32( // MutexDevice::new(&I2C_DRIVER), // SlaveAddr::Alternative(true, true, true), // ) // }; // let rtc_time = rtc.datetime(); // match rtc_time { // OkStd(tt) => { // log::info!("Rtc Module reports time at UTC {}", tt); // } // Err(err) => { // log::info!("Rtc Module could not be read {:?}", err); // } // } // let storage = Storage::new(eeprom, Delay::new(1000)); //let rtc_module: Box = // Box::new(DS3231Module { rtc, storage }) as Box; let hal = match config { Result::Ok(config) => { let battery_interaction: Box = match config.hardware.battery { BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}), // BatteryBoardVersion::BQ34Z100G1 => { // let mut battery_driver = Bq34z100g1Driver { // i2c: MutexDevice::new(&I2C_DRIVER), // delay: Delay::new(0), // flash_block_data: [0; 32], // }; // let status = print_battery_bq34z100(&mut battery_driver); // match status { // Ok(_) => {} // Err(err) => { // log( // LogMessage::BatteryCommunicationError, // 0u32, // 0, // "", // &format!("{err:?})"), // ); // } // } // Box::new(BQ34Z100G1 { battery_driver }) // } BatteryBoardVersion::WchI2cSlave => { // TODO use correct implementation once availible Box::new(NoBatteryMonitor {}) } _ => { todo!() } }; let board_hal: Box = //match config.hardware.board { //BoardVersion::INITIAL => { initial_hal::create_initial_board(free_pins, config, esp)? ; //} // BoardVersion::V3 => { // v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)? // } //BoardVersion::V4 => { // v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)? //} //_ => { // todo!() //} //}; HAL { board_hal } } Err(err) => { LOG_ACCESS.lock().await.log( LogMessage::ConfigModeMissingConfig, 0, 0, "", &err.to_string(), ).await; HAL { board_hal: initial_hal::create_initial_board( free_pins, PlantControllerConfig::default(), esp, )?, } } }; Ok(Mutex::new(hal)) } } pub async fn esp_time() -> DateTime { DateTime::from_timestamp_micros(TIME_ACCESS.get().await.current_time_us() as i64).unwrap() } pub async fn esp_set_time(time: DateTime) { TIME_ACCESS.get().await.set_current_time_us(time.timestamp_micros() as u64); }