pub(crate) mod battery; mod esp; mod initial_hal; mod v3_hal; mod v4_hal; use battery::BQ34Z100G1; use bq34z100::Bq34z100g1Driver; use crate::log::LogMessage; use ds323x::DateTimeAccess; use esp_ota::mark_app_valid; use eeprom24x::Eeprom24xTrait; use embedded_hal_bus::i2c::MutexDevice; use esp_idf_hal::adc::ADC1; use esp_idf_hal::i2c::{APBTickType, I2cConfig, I2cDriver}; use esp_idf_hal::units::FromValueType; use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::nvs::EspDefaultNvsPartition; use esp_idf_svc::wifi::EspWifi; use esp_idf_sys::esp_restart; use esp_idf_sys::{ esp_deep_sleep, esp_sleep_enable_ext1_wakeup, esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, }; use once_cell::sync::Lazy; use anyhow::{Ok, Result}; use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; use std::result::Result::Ok as OkStd; use std::sync::Mutex; use std::time::Duration; use crate::config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}; use crate::hal::battery::{print_battery_bq34z100, BatteryInteraction, NoBatteryMonitor}; use crate::hal::esp::ESP; use crate::hal::initial_hal::Initial; use crate::log::log; use embedded_hal::digital::OutputPin; use esp_idf_hal::delay::Delay; use esp_idf_hal::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, }; use esp_idf_hal::pcnt::PCNT0; use esp_idf_hal::prelude::Peripherals; use esp_idf_hal::reset::ResetReason; use measurements::{Current, Voltage}; use pca9535::StandardExpanderInterface; //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); #[non_exhaustive] struct V3Constants; impl V3Constants {} const X25: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); 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>, } #[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct BackupHeader { pub timestamp: i64, crc16: u16, pub size: usize, } impl Default for BackupHeader { fn default() -> Self { Self { timestamp: Default::default(), crc16: Default::default(), size: Default::default(), } } } pub trait BoardInteraction<'a> { fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; fn is_day(&self) -> bool; fn get_mptt_voltage(&mut self) -> Result; fn get_mptt_current(&mut self) -> Result; fn get_esp(&mut self) -> &mut ESP<'a>; fn get_config(&mut self) -> &PlantControllerConfig; fn get_battery_monitor(&mut self) -> &mut Box; fn deep_sleep(&mut self, duration_in_ms: u64) -> !; fn get_backup_info(&mut self) -> Result; fn get_backup_config(&mut self) -> Result>; fn backup_config(&mut self, bytes: &[u8]) -> Result<()>; //should be multsampled fn water_temperature_c(&mut self) -> Result; /// return median tank sensor value in milli volt fn tank_sensor_voltage(&mut self) -> Result; 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 factory_reset(&mut self) -> Result<()>; fn get_rtc_time(&mut self) -> Result>; fn set_rtc_time(&mut self, time: &DateTime) -> Result<()>; fn test_pump(&mut self, plant: usize) -> Result<()>; fn test(&mut self) -> Result<()>; fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>; } 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(); 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)? } BoardVersion::V4 => { v4_hal::create_v4(free_pins, esp, config, battery_interaction)? } }; 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)) } }