pub(crate) mod battery; mod esp; mod initial_hal; mod rtc; mod v3_hal; mod v4_hal; mod water; use crate::hal::rtc::{DS3231Module, RTCModuleInteraction}; use crate::hal::water::TankSensor; use crate::{ config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, hal::{ battery::{print_battery_bq34z100, BatteryInteraction, NoBatteryMonitor}, esp::Esp, }, log::{log, LogMessage}, }; use anyhow::{Ok, Result}; use battery::BQ34Z100G1; use bq34z100::Bq34z100g1Driver; use ds323x::{DateTimeAccess, Ds323x}; use eeprom24x::{Eeprom24x, SlaveAddr, Storage}; use embedded_hal_bus::i2c::MutexDevice; use esp_idf_hal::{ adc::ADC1, delay::Delay, gpio::{ Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, Gpio18, Gpio2, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, Gpio29, Gpio3, Gpio30, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, IOPin, PinDriver, Pull, }, i2c::{APBTickType, I2cConfig, I2cDriver}, pcnt::PCNT0, prelude::Peripherals, reset::ResetReason, units::FromValueType, }; use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition, wifi::EspWifi}; use esp_idf_sys::{ esp_deep_sleep, esp_restart, esp_sleep_enable_ext1_wakeup, esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, }; use esp_ota::mark_app_valid; use measurements::{Current, Voltage}; use once_cell::sync::Lazy; use std::result::Result::Ok as OkStd; use std::sync::Mutex; use std::time::Duration; //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; const REPEAT_MOIST_MEASURE: usize = 1; const TANK_MULTI_SAMPLE: usize = 11; pub static I2C_DRIVER: Lazy>> = Lazy::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); } }; } #[derive(Debug, PartialEq)] pub enum Sensor { A, B, } pub struct PlantHal {} pub struct HAL<'a> { pub board_hal: Box + Send>, } pub trait BoardInteraction<'a> { fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>>; 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<()>; fn deep_sleep(&mut self, duration_in_ms: u64) -> !; fn is_day(&self) -> bool; //should be multsampled fn light(&mut self, enable: bool) -> Result<()>; fn pump(&mut self, plant: usize, enable: bool) -> Result<()>; fn fault(&mut self, plant: usize, enable: bool) -> Result<()>; fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result; fn general_fault(&mut self, enable: bool); fn test_pump(&mut self, plant: usize) -> Result<()>; fn test(&mut self) -> Result<()>; fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>; fn get_mptt_voltage(&mut self) -> anyhow::Result; 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 fn progress(&mut self, counter: u32) { let even = counter % 2 == 0; let current = counter / (PLANT_COUNT as u32); for led in 0..PLANT_COUNT { self.fault(led, current == led as u32).unwrap(); } let _ = self.general_fault(even.into()); } } #[allow(dead_code)] pub struct FreePeripherals { pub gpio0: Gpio0, pub gpio1: Gpio1, pub gpio2: Gpio2, pub gpio3: Gpio3, pub gpio4: Gpio4, pub gpio5: Gpio5, pub gpio6: Gpio6, 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, 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 adc1: ADC1, } 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 fn create() -> Result>> { let peripherals = Peripherals::take()?; let sys_loop = EspSystemEventLoop::take()?; let nvs = EspDefaultNvsPartition::take()?; let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; boot_button.set_pull(Pull::Floating)?; let free_pins = FreePeripherals { adc1: peripherals.adc1, pcnt0: peripherals.pcnt0, 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.pins.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.pins.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 mut esp = Esp { mqtt_client: None, wifi_driver, boot_button, delay: Delay::new(1000), }; //init,reset rtc memory depending on cause let mut init_rtc_store: bool = false; let mut to_config_mode: bool = false; let reasons = ResetReason::get(); match reasons { ResetReason::Software => {} ResetReason::ExternalPin => {} ResetReason::Watchdog => { init_rtc_store = true; } ResetReason::Sdio => init_rtc_store = true, ResetReason::Panic => init_rtc_store = true, ResetReason::InterruptWatchdog => init_rtc_store = true, ResetReason::PowerOn => init_rtc_store = true, ResetReason::Unknown => init_rtc_store = true, ResetReason::Brownout => init_rtc_store = true, ResetReason::TaskWatchdog => init_rtc_store = true, ResetReason::DeepSleep => {} ResetReason::USBPeripheral => { init_rtc_store = true; to_config_mode = true; } ResetReason::JTAG => init_rtc_store = true, }; log( LogMessage::ResetReason, init_rtc_store as u32, to_config_mode as u32, "", &format!("{reasons:?}"), ); esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode); let fs_mount_error = esp.mount_file_system().is_err(); let config = esp.load_config(); println!("Init rtc driver"); let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); println!("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) => { println!("Rtc Module reports time at UTC {}", tt); } Err(err) => { println!("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 { OkStd(_) => {} 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 {}) } }; let board_hal: Box = match config.hardware.board { BoardVersion::INITIAL => { initial_hal::create_initial_board(free_pins, fs_mount_error, 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)? } }; HAL { board_hal } } Err(err) => { log( LogMessage::ConfigModeMissingConfig, 0, 0, "", &err.to_string(), ); HAL { board_hal: initial_hal::create_initial_board( free_pins, fs_mount_error, PlantControllerConfig::default(), esp, )?, } } }; Ok(Mutex::new(hal)) } }