diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 593cc2c..f272ed8 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -88,6 +88,10 @@ text-template = "0.1.0" strum_macros = "0.27.0" esp-ota = { version = "0.2.2", features = ["log"] } unit-enum = "1.4.1" +ambassador = "0.4.1" +tca9535 = "0.1.0" +tca9539 = "0.2.1" +pca9535 = "2.0.0" [patch.crates-io] diff --git a/rust/src/main.rs b/rust/src/main.rs index 8a0a643..fe0d2ea 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,8 +1,4 @@ -use std::{ - fmt::Display, - sync::{atomic::AtomicBool, Arc, Mutex}, -}; -use std::sync::MutexGuard; +use crate::{config::PlantControllerConfig, webserver::webserver::httpd}; use anyhow::bail; use chrono::{DateTime, Datelike, Timelike, Utc}; use chrono_tz::Tz; @@ -18,19 +14,24 @@ use esp_idf_sys::{ use esp_ota::{mark_app_valid, rollback_and_reboot}; use log::{log, LogMessage}; use once_cell::sync::Lazy; -use plant_hal::{PlantCtrlBoard, PlantHal, PLANT_COUNT}; +use plant_hal::{EspHal, PlantHalFactory, PLANT_COUNT}; use serde::{Deserialize, Serialize}; -use crate::{config::PlantControllerConfig, webserver::webserver::httpd}; +use std::sync::MutexGuard; +use std::{ + fmt::Display, + sync::{atomic::AtomicBool, Arc, Mutex}, +}; mod config; mod log; pub mod plant_hal; mod plant_state; mod tank; +use crate::plant_hal::{BoardV3X, SpecificBoard}; use plant_state::PlantState; use tank::*; -pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create().unwrap()); +pub static BOARD_ACCESS: Lazy>> = Lazy::new(|| PlantHalFactory::create_v3().unwrap()); pub static STAY_ALIVE: Lazy = Lazy::new(|| AtomicBool::new(false)); mod webserver { @@ -150,7 +151,7 @@ fn safe_main() -> anyhow::Result<()> { }; log(LogMessage::PartitionState, 0, 0, "", ota_state_string); - let mut board: std::sync::MutexGuard<'_, PlantCtrlBoard<'_>> = BOARD_ACCESS.lock().unwrap(); + let mut board: std::sync::MutexGuard<'_, BoardV3X> = BOARD_ACCESS.lock().unwrap(); board.general_fault(false); log(LogMessage::MountingFilesystem, 0, 0, "", ""); @@ -484,7 +485,7 @@ fn safe_main() -> anyhow::Result<()> { board.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64); } -fn obtain_tank_temperature(board: &mut MutexGuard) -> anyhow::Result { +fn obtain_tank_temperature(board: &mut MutexGuard>) -> anyhow::Result { //multisample should be moved to water_temperature_c let mut attempt = 1; let water_temp: Result = loop { @@ -506,7 +507,7 @@ fn obtain_tank_temperature(board: &mut MutexGuard) -> anyhow::Re water_temp } -fn publish_tank_state(board: &mut MutexGuard, config: &PlantControllerConfig, tank_state: &TankState, water_temp: &anyhow::Result) { +fn publish_tank_state(board: &mut MutexGuard, config: &PlantControllerConfig, tank_state: &TankState, water_temp: &anyhow::Result) { match serde_json::to_string(&tank_state.as_mqtt_info(&config.tank, water_temp)) { Ok(state) => { let _ = board.mqtt_publish(&config, "/water", state.as_bytes()); @@ -517,7 +518,7 @@ fn publish_tank_state(board: &mut MutexGuard, config: &PlantCont }; } -fn publish_plant_states(board: &mut MutexGuard, config: &PlantControllerConfig, timezone_time: &DateTime, plantstate: &[PlantState; 8]) { +fn publish_plant_states(board: &mut MutexGuard>, config: &PlantControllerConfig, timezone_time: &DateTime, plantstate: &[PlantState; 8]) { for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(&config.plants).enumerate() { match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, &timezone_time)) { Ok(state) => { @@ -533,7 +534,7 @@ fn publish_plant_states(board: &mut MutexGuard, config: &PlantCo } } -fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard, config: &PlantControllerConfig, ip_address: &String, timezone_time: DateTime) { +fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard>, config: &PlantControllerConfig, ip_address: &String, timezone_time: DateTime) { let _ = board.mqtt_publish(&config, "/firmware/address", ip_address.as_bytes()); let _ = board.mqtt_publish(&config, "/firmware/githash", version.git_hash.as_bytes()); let _ = board.mqtt_publish( @@ -555,7 +556,7 @@ fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: & let _ = board.mqtt_publish(&config, "/state", "online".as_bytes()); } -fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &PlantControllerConfig) -> NetworkMode{ +fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard>, config: &PlantControllerConfig) -> NetworkMode{ match board.wifi( config.network.ssid.clone().unwrap(), config.network.password.clone(), @@ -603,7 +604,7 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &P } //TODO clean this up? better state -fn pump_info(board: &mut MutexGuard, config: &PlantControllerConfig, plant_id: usize, pump_active: bool, pump_ineffective: bool) { +fn pump_info(board: &mut MutexGuard, config: &PlantControllerConfig, plant_id: usize, pump_active: bool, pump_ineffective: bool) { let pump_info = PumpInfo { enabled: pump_active, pump_ineffective @@ -622,7 +623,7 @@ fn pump_info(board: &mut MutexGuard, config: &PlantControllerCon } fn publish_battery_state( - board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, + board: &mut std::sync::MutexGuard<'_, EspHal<'_>>, config: &PlantControllerConfig, ) { let bat = board.get_battery_state(); diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 96fef55..2898bd3 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -1,3 +1,7 @@ +use ambassador::delegatable_trait; +use ambassador::Delegate; + + use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver}; use crate::log::LogMessage; @@ -47,7 +51,7 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use std::time::Duration; -use embedded_hal::digital::OutputPin; +use embedded_hal::digital::{OutputPin, PinState}; use esp_idf_hal::delay::Delay; use esp_idf_hal::gpio::{AnyInputPin, Gpio18, Gpio5, IOPin, InputOutput, Level, PinDriver, Pull}; use esp_idf_hal::pcnt::{ @@ -59,11 +63,12 @@ use esp_idf_svc::sntp::{self, SyncStatus}; use esp_idf_svc::systime::EspSystemTime; use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; use one_wire_bus::OneWire; - +use pca9535::{ExpanderInputPin, ExpanderOutputPin, GPIOBank, IoExpander, StandardExpanderInterface}; use crate::config::PlantControllerConfig; use crate::log::log; use crate::{to_string, STAY_ALIVE}; + //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; const REPEAT_MOIST_MEASURE: usize = 1; @@ -149,16 +154,43 @@ pub enum Sensor { B, } -pub struct PlantHal {} +pub struct PlantHalFactory {} -pub struct PlantCtrlBoard<'a> { - shift_register: ShiftRegister40< - PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - >, - shift_register_enable_invert: - PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Output>, + +pub struct EspHal<'a> { + wifi_driver: EspWifi<'a>, + mqtt_client: Option>, +} + +pub struct BoardV4X<'a> { + esp: EspHal<'a>, + tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>, + solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, + boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, + signal_counter: PcntDriver<'a>, + light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + one_wire_bus: OneWire>, + battery_driver: Bq34z100g1Driver>, Delay>, + rtc: + Ds323x>>, ds323x::ic::DS3231>, + eeprom: Eeprom24x< + MutexDevice<'a, I2cDriver<'a>>, + eeprom24x::page_size::B32, + eeprom24x::addr_size::TwoBytes, + eeprom24x::unique_serial::No, + > +} + +pub struct BoardV3X<'a> { + esp: EspHal<'a>, + shift_register: ShiftRegister40< + PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + >, tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>, solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, @@ -167,9 +199,7 @@ pub struct PlantCtrlBoard<'a> { main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - wifi_driver: EspWifi<'a>, one_wire_bus: OneWire>, - mqtt_client: Option>, battery_driver: Bq34z100g1Driver>, Delay>, rtc: Ds323x>>, ds323x::ic::DS3231>, @@ -215,158 +245,7 @@ pub struct BackupHeader { pub size: usize, } -impl PlantCtrlBoard<'_> { - pub fn update_charge_indicator(&mut self) { - let is_charging = match self.battery_driver.average_current() { - OkStd(current) => current < 20, - Err(_) => false, - }; - self.shift_register.decompose()[CHARGING] - .set_state(is_charging.into()) - .unwrap(); - } - - pub fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { - self.shift_register.decompose()[AWAKE].set_low().unwrap(); - 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); - } - }; - } - - pub fn get_backup_info(&mut self) -> Result { - let dummy = BackupHeader { - timestamp: 0, - crc16: 0, - size: 0, - }; - let store = bincode::serialize(&dummy)?.len(); - let mut header_page_buffer = vec![0_u8; store]; - - match self.eeprom.read_data(0, &mut header_page_buffer) { - OkStd(_) => {} - Err(err) => bail!("Error reading eeprom header {:?}", err), - }; - println!("Raw header is {:?} with size {}", header_page_buffer, store); - let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; - Ok(header) - } - - pub fn get_backup_config(&mut self) -> Result> { - let dummy = BackupHeader { - timestamp: 0, - crc16: 0, - size: 0, - }; - let store = bincode::serialize(&dummy)?.len(); - let mut header_page_buffer = vec![0_u8; store]; - - match self.eeprom.read_data(0, &mut header_page_buffer) { - OkStd(_) => {} - Err(err) => bail!("Error reading eeprom header {:?}", err), - }; - let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; - - //skip page 0, used by the header - let data_start_address = self.eeprom.page_size() as u32; - let mut data_buffer = vec![0_u8; header.size]; - match self.eeprom.read_data(data_start_address, &mut data_buffer) { - OkStd(_) => {} - Err(err) => bail!("Error reading eeprom data {:?}", err), - }; - - let checksum = X25.checksum(&data_buffer); - if checksum != header.crc16 { - bail!( - "Invalid checksum, got {} but expected {}", - checksum, - header.crc16 - ); - } - - Ok(data_buffer) - } - - pub fn backup_config(&mut self, bytes: &[u8]) -> Result<()> { - let delay = Delay::new_default(); - - let checksum = X25.checksum(bytes); - let page_size = self.eeprom.page_size(); - - let time = self.get_rtc_time()?.timestamp_millis(); - - let header = BackupHeader { - crc16: checksum, - timestamp: time, - size: bytes.len(), - }; - - let encoded = bincode::serialize(&header)?; - if encoded.len() > page_size { - bail!( - "Size limit reached header is {}, but firest page is only {}", - encoded.len(), - page_size - ) - } - let as_u8: &[u8] = &encoded; - - match self.eeprom.write_page(0, as_u8) { - OkStd(_) => {} - Err(err) => bail!("Error writing eeprom {:?}", err), - }; - delay.delay_ms(5); - - let to_write = bytes.chunks(page_size); - - let mut lastiter = 0; - let mut current_page = 1; - for chunk in to_write { - let address = current_page * page_size as u32; - match self.eeprom.write_page(address, chunk) { - OkStd(_) => {} - Err(err) => bail!("Error writing eeprom {:?}", err), - }; - current_page += 1; - - let iter = (current_page % 8) as usize; - if iter != lastiter { - for i in 0..PLANT_COUNT { - self.fault(i, iter == i); - } - lastiter = iter; - } - - //update led here? - delay.delay_ms(5); - } - Ok(()) - } - - pub fn get_battery_state(&mut self) -> BatteryState { - BatteryState { - voltage_milli_volt: to_string(self.voltage_milli_volt()), - current_milli_ampere: to_string(self.average_current_milli_ampere()), - cycle_count: to_string(self.cycle_count()), - design_milli_ampere: to_string(self.design_milli_ampere_hour()), - remaining_milli_ampere: to_string(self.remaining_milli_ampere_hour()), - state_of_charge: to_string(self.state_charge_percent()), - state_of_health: to_string(self.state_health_percent()), - temperature: to_string(self.bat_temperature()), - } - } - +impl EspHal<'_> { pub fn list_files(&self) -> FileList { let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap(); @@ -437,60 +316,6 @@ impl PlantCtrlBoard<'_> { }) } - pub fn is_day(&self) -> bool { - self.solar_is_day.get_level().into() - } - - //should be multsampled - pub fn water_temperature_c(&mut self) -> Result { - let mut delay = Delay::new_default(); - - self.one_wire_bus - .reset(&mut delay) - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - let first = self.one_wire_bus.devices(false, &mut delay).next(); - if first.is_none() { - bail!("Not found any one wire Ds18b20"); - } - let device_address = first - .unwrap() - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - - let water_temp_sensor = Ds18b20::new::(device_address) - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - - water_temp_sensor - .start_temp_measurement(&mut self.one_wire_bus, &mut delay) - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut delay); - let sensor_data = water_temp_sensor - .read_data(&mut self.one_wire_bus, &mut delay) - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - if sensor_data.temperature == 85_f32 { - bail!("Ds18b20 dummy temperature returned"); - } - Ok(sensor_data.temperature / 10_f32) - } - - /// return median tank sensor value in milli volt - pub fn tank_sensor_voltage(&mut self) -> Result { - let delay = Delay::new_default(); - self.tank_power.set_high()?; - //let stabilize - delay.delay_ms(100); - - let mut store = [0_u16; TANK_MULTI_SAMPLE]; - for multisample in 0..TANK_MULTI_SAMPLE { - let value = self.tank_channel.read()?; - store[multisample] = value; - } - self.tank_power.set_low()?; - - store.sort(); - let median_mv = store[6] as f32 / 1000_f32; - Ok(median_mv) - } - pub fn set_low_voltage_in_cycle(&mut self) { unsafe { LOW_VOLTAGE_DETECTED = true; @@ -503,30 +328,6 @@ impl PlantCtrlBoard<'_> { } } - pub fn light(&mut self, enable: bool) -> Result<()> { - unsafe { gpio_hold_dis(self.light.pin()) }; - self.light.set_state(enable.into())?; - unsafe { gpio_hold_en(self.light.pin()) }; - Ok(()) - } - - pub fn pump(&self, plant: usize, enable: bool) -> Result<()> { - let index = match plant { - 0 => PUMP1_BIT, - 1 => PUMP2_BIT, - 2 => PUMP3_BIT, - 3 => PUMP4_BIT, - 4 => PUMP5_BIT, - 5 => PUMP6_BIT, - 6 => PUMP7_BIT, - 7 => PUMP8_BIT, - _ => bail!("Invalid pump {plant}",), - }; - //currently infallible error, keep for future as result anyway - self.shift_register.decompose()[index].set_state(enable.into())?; - Ok(()) - } - pub fn last_pump_time(&self, plant: usize) -> Option> { let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant]; DateTime::from_timestamp_millis(ts) @@ -548,34 +349,6 @@ impl PlantCtrlBoard<'_> { unsafe { CONSECUTIVE_WATERING_PLANT[plant] } } - pub fn fault(&self, plant: usize, enable: bool) { - let index = match plant { - 0 => FAULT_1, - 1 => FAULT_2, - 2 => FAULT_3, - 3 => FAULT_4, - 4 => FAULT_5, - 5 => FAULT_6, - 6 => FAULT_7, - 7 => FAULT_8, - _ => panic!("Invalid plant id {}", plant), - }; - self.shift_register.decompose()[index] - .set_state(enable.into()) - .unwrap() - } - - pub fn low_voltage_in_cycle(&mut self) -> bool { - unsafe { LOW_VOLTAGE_DETECTED } - } - - pub fn any_pump(&mut self, enable: bool) -> Result<()> { - { - self.main_pump.set_state(enable.into())?; - Ok(()) - } - } - pub fn time(&mut self) -> Result> { let time = EspSystemTime {}.now().as_millis(); let smaller_time = time as i64; @@ -599,79 +372,6 @@ impl PlantCtrlBoard<'_> { self.time() } - pub fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { - let sensor_channel = match sensor { - Sensor::A => match plant { - 0 => SENSOR_A_1, - 1 => SENSOR_A_2, - 2 => SENSOR_A_3, - 3 => SENSOR_A_4, - 4 => SENSOR_A_5, - 5 => SENSOR_A_6, - 6 => SENSOR_A_7, - 7 => SENSOR_A_8, - _ => bail!("Invalid plant id {}", plant), - }, - Sensor::B => match plant { - 0 => SENSOR_B_1, - 1 => SENSOR_B_2, - 2 => SENSOR_B_3, - 3 => SENSOR_B_4, - 4 => SENSOR_B_5, - 5 => SENSOR_B_6, - 6 => SENSOR_B_7, - 7 => SENSOR_B_8, - _ => bail!("Invalid plant id {}", plant), - }, - }; - - let mut results = [0_f32; REPEAT_MOIST_MEASURE]; - for repeat in 0..REPEAT_MOIST_MEASURE { - self.signal_counter.counter_pause()?; - self.signal_counter.counter_clear()?; - //Disable all - self.shift_register.decompose()[MS_4].set_high()?; - - self.sensor_multiplexer(sensor_channel)?; - - self.shift_register.decompose()[MS_4].set_low()?; - self.shift_register.decompose()[SENSOR_ON].set_high()?; - - let delay = Delay::new_default(); - let measurement = 100; // TODO what is this scaling factor? what is its purpose? - let factor = 1000f32 / measurement as f32; - - //give some time to stabilize - delay.delay_ms(10); - self.signal_counter.counter_resume()?; - delay.delay_ms(measurement); - self.signal_counter.counter_pause()?; - self.shift_register.decompose()[MS_4].set_high()?; - self.shift_register.decompose()[SENSOR_ON].set_low()?; - delay.delay_ms(10); - let unscaled = self.signal_counter.get_counter_value()? as i32; - let hz = unscaled as f32 * factor; - log( - LogMessage::RawMeasure, - unscaled as u32, - hz as u32, - &plant.to_string(), - &format!("{sensor:?}"), - ); - results[repeat] = hz; - } - results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord - - let mid = results.len() / 2; - - Ok(results[mid]) - } - - pub fn general_fault(&mut self, enable: bool) { - unsafe { gpio_hold_dis(self.general_fault.pin()) }; - self.general_fault.set_state(enable.into()).unwrap(); - unsafe { gpio_hold_en(self.general_fault.pin()) }; - } pub fn wifi_ap(&mut self, ap_ssid: Option>) -> Result<()> { let ssid = @@ -749,6 +449,7 @@ impl PlantCtrlBoard<'_> { Ok(address) } + pub fn mount_file_system(&mut self) -> Result<()> { let base_path = CString::new("/spiffs")?; let storage = CString::new(SPIFFS_PARTITION_NAME)?; @@ -785,44 +486,6 @@ impl PlantCtrlBoard<'_> { }) } - pub fn mode_override_pressed(&mut self) -> bool { - self.boot_button.get_level() == Level::Low - } - - pub fn factory_reset(&mut self) -> Result<()> { - println!("factory resetting"); - let config = Path::new(CONFIG_FILE); - if config.exists() { - println!("Removing config"); - fs::remove_file(config)?; - } - - //destroy backup header - let dummy: [u8; 0] = []; - self.backup_config(&dummy)?; - - Ok(()) - } - - pub fn get_rtc_time(&mut self) -> Result> { - match self.rtc.datetime() { - OkStd(rtc_time) => Ok(rtc_time.and_utc()), - Err(err) => { - bail!("Error getting rtc time {:?}", err) - } - } - } - - pub fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { - let naive_time = time.naive_utc(); - match self.rtc.set_datetime(&naive_time) { - OkStd(_) => Ok(()), - Err(err) => { - bail!("Error getting rtc time {:?}", err) - } - } - } - pub fn get_config(&mut self) -> Result { let cfg = File::open(CONFIG_FILE)?; let config: PlantControllerConfig = serde_json::from_reader(cfg)?; @@ -848,56 +511,6 @@ impl PlantCtrlBoard<'_> { Ok(self.wifi_driver.get_scan_result()?) } - pub fn test_pump(&mut self, plant: usize) -> Result<()> { - self.any_pump(true)?; - self.pump(plant, true)?; - unsafe { vTaskDelay(30000) }; - self.pump(plant, false)?; - self.any_pump(false)?; - Ok(()) - } - - pub fn test(&mut self) -> Result<()> { - self.general_fault(true); - unsafe { vTaskDelay(100) }; - self.general_fault(false); - unsafe { vTaskDelay(100) }; - self.any_pump(true)?; - unsafe { vTaskDelay(500) }; - self.any_pump(false)?; - unsafe { vTaskDelay(500) }; - self.light(true)?; - unsafe { vTaskDelay(500) }; - self.light(false)?; - unsafe { vTaskDelay(500) }; - for i in 0..PLANT_COUNT { - self.fault(i, true); - unsafe { vTaskDelay(500) }; - self.fault(i, false); - unsafe { vTaskDelay(500) }; - } - for i in 0..PLANT_COUNT { - self.pump(i, true)?; - unsafe { vTaskDelay(100) }; - self.pump(i, false)?; - unsafe { vTaskDelay(100) }; - } - for plant in 0..PLANT_COUNT { - let a = self.measure_moisture_hz(plant, Sensor::A); - let b = self.measure_moisture_hz(plant, Sensor::B); - let aa = match a { - OkStd(a) => a as u32, - Err(_) => u32::MAX, - }; - let bb = match b { - OkStd(b) => b as u32, - Err(_) => u32::MAX, - }; - log(LogMessage::TestSensor, aa, bb, &plant.to_string(), ""); - } - Delay::new_default().delay_ms(10); - Ok(()) - } pub fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> { let base_topic = config @@ -1109,88 +722,476 @@ impl PlantCtrlBoard<'_> { } } } +} - pub fn get_restart_to_conf(&mut self) -> bool { +impl SpecificBoard for BoardV3X<'_> { + fn update_charge_indicator(&mut self) { + let is_charging = match self.battery_driver.average_current() { + OkStd(current) => current < 20, + Err(_) => false, + }; + self.shift_register.decompose()[CHARGING] + .set_state(is_charging.into()) + .unwrap(); + } + fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { + self.shift_register.decompose()[AWAKE].set_low().unwrap(); + 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); + } + }; + } + fn get_backup_info(&mut self) -> Result { + let dummy = BackupHeader { + timestamp: 0, + crc16: 0, + size: 0, + }; + let store = bincode::serialize(&dummy)?.len(); + let mut header_page_buffer = vec![0_u8; store]; + + match self.eeprom.read_data(0, &mut header_page_buffer) { + OkStd(_) => {} + Err(err) => bail!("Error reading eeprom header {:?}", err), + }; + println!("Raw header is {:?} with size {}", header_page_buffer, store); + let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; + Ok(header) + } + fn get_backup_config(&mut self) -> Result> { + let dummy = BackupHeader { + timestamp: 0, + crc16: 0, + size: 0, + }; + let store = bincode::serialize(&dummy)?.len(); + let mut header_page_buffer = vec![0_u8; store]; + + match self.eeprom.read_data(0, &mut header_page_buffer) { + OkStd(_) => {} + Err(err) => bail!("Error reading eeprom header {:?}", err), + }; + let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; + + //skip page 0, used by the header + let data_start_address = self.eeprom.page_size() as u32; + let mut data_buffer = vec![0_u8; header.size]; + match self.eeprom.read_data(data_start_address, &mut data_buffer) { + OkStd(_) => {} + Err(err) => bail!("Error reading eeprom data {:?}", err), + }; + + let checksum = X25.checksum(&data_buffer); + if checksum != header.crc16 { + bail!( + "Invalid checksum, got {} but expected {}", + checksum, + header.crc16 + ); + } + + Ok(data_buffer) + } + fn backup_config(&mut self, bytes: &[u8]) -> Result<()> { + let delay = Delay::new_default(); + + let checksum = X25.checksum(bytes); + let page_size = self.eeprom.page_size(); + + let time = self.get_rtc_time()?.timestamp_millis(); + + let header = BackupHeader { + crc16: checksum, + timestamp: time, + size: bytes.len(), + }; + + let encoded = bincode::serialize(&header)?; + if encoded.len() > page_size { + bail!( + "Size limit reached header is {}, but firest page is only {}", + encoded.len(), + page_size + ) + } + let as_u8: &[u8] = &encoded; + + match self.eeprom.write_page(0, as_u8) { + OkStd(_) => {} + Err(err) => bail!("Error writing eeprom {:?}", err), + }; + delay.delay_ms(5); + + let to_write = bytes.chunks(page_size); + + let mut lastiter = 0; + let mut current_page = 1; + for chunk in to_write { + let address = current_page * page_size as u32; + match self.eeprom.write_page(address, chunk) { + OkStd(_) => {} + Err(err) => bail!("Error writing eeprom {:?}", err), + }; + current_page += 1; + + let iter = (current_page % 8) as usize; + if iter != lastiter { + for i in 0..PLANT_COUNT { + self.fault(i, iter == i); + } + lastiter = iter; + } + + //update led here? + delay.delay_ms(5); + } + Ok(()) + } + fn get_battery_state(&mut self) -> BatteryState { + BatteryState { + voltage_milli_volt: to_string(self.voltage_milli_volt()), + current_milli_ampere: to_string(self.average_current_milli_ampere()), + cycle_count: to_string(self.cycle_count()), + design_milli_ampere: to_string(self.design_milli_ampere_hour()), + remaining_milli_ampere: to_string(self.remaining_milli_ampere_hour()), + state_of_charge: to_string(self.state_charge_percent()), + state_of_health: to_string(self.state_health_percent()), + temperature: to_string(self.bat_temperature()), + } + } + fn is_day(&self) -> bool { + self.solar_is_day.get_level().into() + } + //should be multsampled + fn water_temperature_c(&mut self) -> Result { + let mut delay = Delay::new_default(); + + self.one_wire_bus + .reset(&mut delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + let first = self.one_wire_bus.devices(false, &mut delay).next(); + if first.is_none() { + bail!("Not found any one wire Ds18b20"); + } + let device_address = first + .unwrap() + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + + let water_temp_sensor = Ds18b20::new::(device_address) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + + water_temp_sensor + .start_temp_measurement(&mut self.one_wire_bus, &mut delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut delay); + let sensor_data = water_temp_sensor + .read_data(&mut self.one_wire_bus, &mut delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + if sensor_data.temperature == 85_f32 { + bail!("Ds18b20 dummy temperature returned"); + } + Ok(sensor_data.temperature / 10_f32) + } + /// return median tank sensor value in milli volt + fn tank_sensor_voltage(&mut self) -> Result { + let delay = Delay::new_default(); + self.tank_power.set_high()?; + //let stabilize + delay.delay_ms(100); + + let mut store = [0_u16; TANK_MULTI_SAMPLE]; + for multisample in 0..TANK_MULTI_SAMPLE { + let value = self.tank_channel.read()?; + store[multisample] = value; + } + self.tank_power.set_low()?; + + store.sort(); + let median_mv = store[6] as f32 / 1000_f32; + Ok(median_mv) + } + fn light(&mut self, enable: bool) -> Result<()> { + unsafe { gpio_hold_dis(self.light.pin()) }; + self.light.set_state(enable.into())?; + unsafe { gpio_hold_en(self.light.pin()) }; + Ok(()) + } + fn pump(&self, plant: usize, enable: bool) -> Result<()> { + let index = match plant { + 0 => PUMP1_BIT, + 1 => PUMP2_BIT, + 2 => PUMP3_BIT, + 3 => PUMP4_BIT, + 4 => PUMP5_BIT, + 5 => PUMP6_BIT, + 6 => PUMP7_BIT, + 7 => PUMP8_BIT, + _ => bail!("Invalid pump {plant}",), + }; + //currently infallible error, keep for future as result anyway + self.shift_register.decompose()[index].set_state(enable.into())?; + Ok(()) + } + fn fault(&self, plant: usize, enable: bool) { + let index = match plant { + 0 => FAULT_1, + 1 => FAULT_2, + 2 => FAULT_3, + 3 => FAULT_4, + 4 => FAULT_5, + 5 => FAULT_6, + 6 => FAULT_7, + 7 => FAULT_8, + _ => panic!("Invalid plant id {}", plant), + }; + self.shift_register.decompose()[index] + .set_state(enable.into()) + .unwrap() + } + fn low_voltage_in_cycle(&mut self) -> bool { + unsafe { LOW_VOLTAGE_DETECTED } + } + fn any_pump(&mut self, enable: bool) -> Result<()> { + { + self.main_pump.set_state(enable.into())?; + Ok(()) + } + } + fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { + let sensor_channel = match sensor { + Sensor::A => match plant { + 0 => SENSOR_A_1, + 1 => SENSOR_A_2, + 2 => SENSOR_A_3, + 3 => SENSOR_A_4, + 4 => SENSOR_A_5, + 5 => SENSOR_A_6, + 6 => SENSOR_A_7, + 7 => SENSOR_A_8, + _ => bail!("Invalid plant id {}", plant), + }, + Sensor::B => match plant { + 0 => SENSOR_B_1, + 1 => SENSOR_B_2, + 2 => SENSOR_B_3, + 3 => SENSOR_B_4, + 4 => SENSOR_B_5, + 5 => SENSOR_B_6, + 6 => SENSOR_B_7, + 7 => SENSOR_B_8, + _ => bail!("Invalid plant id {}", plant), + }, + }; + + let mut results = [0_f32; REPEAT_MOIST_MEASURE]; + for repeat in 0..REPEAT_MOIST_MEASURE { + self.signal_counter.counter_pause()?; + self.signal_counter.counter_clear()?; + //Disable all + self.shift_register.decompose()[MS_4].set_high()?; + + self.sensor_multiplexer(sensor_channel)?; + + self.shift_register.decompose()[MS_4].set_low()?; + self.shift_register.decompose()[SENSOR_ON].set_high()?; + + let delay = Delay::new_default(); + let measurement = 100; // TODO what is this scaling factor? what is its purpose? + let factor = 1000f32 / measurement as f32; + + //give some time to stabilize + delay.delay_ms(10); + self.signal_counter.counter_resume()?; + delay.delay_ms(measurement); + self.signal_counter.counter_pause()?; + self.shift_register.decompose()[MS_4].set_high()?; + self.shift_register.decompose()[SENSOR_ON].set_low()?; + delay.delay_ms(10); + let unscaled = self.signal_counter.get_counter_value()? as i32; + let hz = unscaled as f32 * factor; + log( + LogMessage::RawMeasure, + unscaled as u32, + hz as u32, + &plant.to_string(), + &format!("{sensor:?}"), + ); + results[repeat] = hz; + } + results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord + + let mid = results.len() / 2; + + Ok(results[mid]) + } + fn general_fault(&mut self, enable: bool) { + unsafe { gpio_hold_dis(self.general_fault.pin()) }; + self.general_fault.set_state(enable.into()).unwrap(); + unsafe { gpio_hold_en(self.general_fault.pin()) }; + } + fn mode_override_pressed(&mut self) -> bool { + self.boot_button.get_level() == Level::Low + } + fn factory_reset(&mut self) -> Result<()> { + println!("factory resetting"); + let config = Path::new(CONFIG_FILE); + if config.exists() { + println!("Removing config"); + fs::remove_file(config)?; + } + + //destroy backup header + let dummy: [u8; 0] = []; + self.backup_config(&dummy)?; + + Ok(()) + } + fn get_rtc_time(&mut self) -> Result> { + match self.rtc.datetime() { + OkStd(rtc_time) => Ok(rtc_time.and_utc()), + Err(err) => { + bail!("Error getting rtc time {:?}", err) + } + } + } + fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { + let naive_time = time.naive_utc(); + match self.rtc.set_datetime(&naive_time) { + OkStd(_) => Ok(()), + Err(err) => { + bail!("Error getting rtc time {:?}", err) + } + } + } + fn test_pump(&mut self, plant: usize) -> Result<()> { + self.any_pump(true)?; + self.pump(plant, true)?; + unsafe { vTaskDelay(30000) }; + self.pump(plant, false)?; + self.any_pump(false)?; + Ok(()) + } + fn test(&mut self) -> Result<()> { + self.general_fault(true); + unsafe { vTaskDelay(100) }; + self.general_fault(false); + unsafe { vTaskDelay(100) }; + self.any_pump(true)?; + unsafe { vTaskDelay(500) }; + self.any_pump(false)?; + unsafe { vTaskDelay(500) }; + self.light(true)?; + unsafe { vTaskDelay(500) }; + self.light(false)?; + unsafe { vTaskDelay(500) }; + for i in 0..PLANT_COUNT { + self.fault(i, true); + unsafe { vTaskDelay(500) }; + self.fault(i, false); + unsafe { vTaskDelay(500) }; + } + for i in 0..PLANT_COUNT { + self.pump(i, true)?; + unsafe { vTaskDelay(100) }; + self.pump(i, false)?; + unsafe { vTaskDelay(100) }; + } + for plant in 0..PLANT_COUNT { + let a = self.measure_moisture_hz(plant, Sensor::A); + let b = self.measure_moisture_hz(plant, Sensor::B); + let aa = match a { + OkStd(a) => a as u32, + Err(_) => u32::MAX, + }; + let bb = match b { + OkStd(b) => b as u32, + Err(_) => u32::MAX, + }; + log(LogMessage::TestSensor, aa, bb, &plant.to_string(), ""); + } + Delay::new_default().delay_ms(10); + Ok(()) + } + fn get_restart_to_conf(&mut self) -> bool { unsafe { RESTART_TO_CONF } } - - pub fn set_restart_to_conf(&mut self, to_conf: bool) { + fn set_restart_to_conf(&mut self, to_conf: bool) { unsafe { RESTART_TO_CONF = to_conf; } } - - pub fn state_charge_percent(&mut self) -> Result { + fn state_charge_percent(&mut self) -> Result { match self.battery_driver.state_of_charge() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading SoC {:?}", err), } } - - pub fn remaining_milli_ampere_hour(&mut self) -> Result { + fn remaining_milli_ampere_hour(&mut self) -> Result { match self.battery_driver.remaining_capacity() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Remaining Capacity {:?}", err), } } - - pub fn max_milli_ampere_hour(&mut self) -> Result { + fn max_milli_ampere_hour(&mut self) -> Result { match self.battery_driver.full_charge_capacity() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Full Charge Capacity {:?}", err), } } - - pub fn design_milli_ampere_hour(&mut self) -> Result { + fn design_milli_ampere_hour(&mut self) -> Result { match self.battery_driver.design_capacity() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Design Capacity {:?}", err), } } - - pub fn voltage_milli_volt(&mut self) -> Result { + fn voltage_milli_volt(&mut self) -> Result { match self.battery_driver.voltage() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading voltage {:?}", err), } } - - pub fn average_current_milli_ampere(&mut self) -> Result { + fn average_current_milli_ampere(&mut self) -> Result { match self.battery_driver.average_current() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Average Current {:?}", err), } } - - pub fn cycle_count(&mut self) -> Result { + fn cycle_count(&mut self) -> Result { match self.battery_driver.cycle_count() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Cycle Count {:?}", err), } } - - pub fn state_health_percent(&mut self) -> Result { + fn state_health_percent(&mut self) -> Result { match self.battery_driver.state_of_health() { OkStd(r) => Ok(r as u8), Err(err) => bail!("Error reading State of Health {:?}", err), } } - - pub fn bat_temperature(&mut self) -> Result { + fn bat_temperature(&mut self) -> Result { match self.battery_driver.temperature() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Temperature {:?}", err), } } - - pub fn flash_bq34_z100(&mut self, line: &str, dryrun: bool) -> Result<()> { + fn flash_bq34_z100(&mut self, line: &str, dryrun: bool) -> Result<()> { match self.battery_driver.write_flash_stream_i2c(line, dryrun) { OkStd(r) => Ok(r), Err(err) => bail!("Error reading SoC {:?}", err), } } - - pub fn sensor_multiplexer(&mut self, n: u8) -> Result<()> { + fn sensor_multiplexer(&mut self, n: u8) -> Result<()> { assert!(n < 16); let is_bit_set = |b: u8| -> bool { n & (1 << b) != 0 }; @@ -1222,6 +1223,47 @@ impl PlantCtrlBoard<'_> { } } +#[delegatable_trait] +pub trait SpecificBoard { + fn update_charge_indicator(&mut self); + 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<()>; + fn get_battery_state(&mut self) -> BatteryState; + fn is_day(&self) -> bool; + //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(&self, plant: usize, enable: bool) -> Result<()>; + fn fault(&self, plant: usize, enable: bool); + fn low_voltage_in_cycle(&mut self) -> bool; + fn any_pump(&mut self, enable: bool) -> Result<()>; + fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result; + fn general_fault(&mut self, enable: bool); + fn mode_override_pressed(&mut self) -> 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 get_restart_to_conf(&mut self) -> bool; + fn set_restart_to_conf(&mut self, to_conf: bool); + fn state_charge_percent(&mut self) -> Result; + fn remaining_milli_ampere_hour(&mut self) -> Result; + fn max_milli_ampere_hour(&mut self) -> Result; + fn design_milli_ampere_hour(&mut self) -> Result; + fn voltage_milli_volt(&mut self) -> Result; + fn average_current_milli_ampere(&mut self) -> Result; + fn cycle_count(&mut self) -> Result; + fn state_health_percent(&mut self) -> Result; + fn bat_temperature(&mut self) -> Result; + fn flash_bq34_z100(&mut self, line: &str, dryrun: bool) -> Result<()>; + fn sensor_multiplexer(&mut self, n: u8) -> Result<()>; +} + fn print_battery( battery_driver: &mut Bq34z100g1Driver>, Delay>, ) -> Result<(), Bq34Z100Error> { @@ -1280,8 +1322,8 @@ fn print_battery( Result::Ok(()) } -pub static I2C_DRIVER: Lazy>> = Lazy::new(PlantHal::create_i2c); -impl PlantHal { +pub static I2C_DRIVER: Lazy>> = Lazy::new(PlantHalFactory::create_i2c); +impl PlantHalFactory { fn create_i2c() -> Mutex> { let peripherals = unsafe { Peripherals::new() }; @@ -1297,7 +1339,261 @@ impl PlantHal { Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) } - pub fn create() -> Result>> { + + pub fn create_v4() -> Result>> { + let peripherals = Peripherals::take()?; + + + + println!("Init battery driver"); + let mut battery_driver = Bq34z100g1Driver { + i2c: MutexDevice::new(&I2C_DRIVER), + delay: Delay::new(0), + flash_block_data: [0; 32], + }; + + println!("Init rtc driver"); + let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); + + println!("Init rtc eeprom driver"); + let mut eeprom = { + Eeprom24x::new_24x32( + MutexDevice::new(&I2C_DRIVER), + SlaveAddr::Alternative(true, true, true), + ) + }; + + println!("Init pump module"); + + let mut pump_multiplexer = pca9535::Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32); + + let mut sensor_multiplexer = pca9535::Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34); + + + for pin in 0..7 { + match sensor_multiplexer.pin_set_low(GPIOBank::Bank0, pin) { + OkStd(_) => (), + Err(err) => { + println!("Error setting sensor multiplexer Bank0 pin {:?} {:?}", pin, err) + } + } + match sensor_multiplexer.pin_set_low(GPIOBank::Bank1, pin) { + OkStd(_) => (), + Err(err) => { + println!("Error setting sensor multiplexer Bank1 pin {:?} {:?}", pin, err) + } + } + } + + //let pump_expander = IoExpander::new(pump_multiplexer); + + //let sensor_expander: IoExpander, _, Mutex<_>> = IoExpander::new(sensor_multiplexer); + + + let mut awake = PinDriver::input_output_od(peripherals.pins.gpio15.downgrade())?; + awake.set_high()?; + + let mut awake = PinDriver::input_output_od(peripherals.pins.gpio3.downgrade())?; + awake.set_high()?; + + //TODO unwraps not ok + + + //let ms0 = ExpanderOutputPin::new(&sensor_expander, GPIOBank::Bank0, 1, PinState::Low).unwrap(); + //let ms1 = ExpanderOutputPin::new(&sensor_expander, GPIOBank::Bank0, 0, PinState::Low).unwrap(); + //let ms2 = ExpanderOutputPin::new(&sensor_expander, GPIOBank::Bank0, 3, PinState::Low).unwrap(); + //let ms3 = ExpanderOutputPin::new(&sensor_expander, GPIOBank::Bank0, 4, PinState::Low).unwrap(); + //let ms4 = ExpanderOutputPin::new(&sensor_expander, GPIOBank::Bank0, 2, PinState::High).unwrap(); + //let sensor_active = ExpanderOutputPin::new(&sensor_expander, GPIOBank::Bank0, 5, PinState::Low).unwrap(); + + let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18)?; + one_wire_pin.set_pull(Pull::Floating)?; + + 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); + } + } + match eeprom.read_byte(0) { + OkStd(byte) => { + println!("Read first byte with status {}", byte); + } + Err(err) => { + println!("Eeprom could not read first byte {:?}", err); + } + } + + //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:?}"), + ); + if init_rtc_store { + unsafe { + LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT]; + CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT]; + LOW_VOLTAGE_DETECTED = false; + crate::log::init(); + RESTART_TO_CONF = to_config_mode; + }; + } else { + unsafe { + if to_config_mode { + RESTART_TO_CONF = true; + } + log( + LogMessage::RestartToConfig, + RESTART_TO_CONF as u32, + 0, + "", + "", + ); + log( + LogMessage::LowVoltage, + LOW_VOLTAGE_DETECTED as u32, + 0, + "", + "", + ); + for i in 0..PLANT_COUNT { + println!( + "LAST_WATERING_TIMESTAMP[{}] = UTC {}", + i, LAST_WATERING_TIMESTAMP[i] + ); + } + for i in 0..PLANT_COUNT { + println!( + "CONSECUTIVE_WATERING_PLANT[{}] = {}", + i, CONSECUTIVE_WATERING_PLANT[i] + ); + } + } + } + + let mut counter_unit1 = PcntDriver::new( + peripherals.pcnt0, + Some(peripherals.pins.gpio22), + Option::::None, + Option::::None, + Option::::None, + )?; + + counter_unit1.channel_config( + PcntChannel::Channel0, + PinIndex::Pin0, + PinIndex::Pin1, + &PcntChannelConfig { + lctrl_mode: PcntControlMode::Keep, + hctrl_mode: PcntControlMode::Keep, + pos_mode: PcntCountMode::Increment, + neg_mode: PcntCountMode::Hold, + counter_h_lim: i16::MAX, + counter_l_lim: 0, + }, + )?; + + let adc_config = AdcChannelConfig { + attenuation: attenuation::DB_11, + resolution: Resolution::Resolution12Bit, + calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve, + }; + let tank_driver = AdcDriver::new(peripherals.adc1)?; + let tank_channel: AdcChannelDriver> = + AdcChannelDriver::new(tank_driver, peripherals.pins.gpio5, &adc_config)?; + + let mut solar_is_day = PinDriver::input(peripherals.pins.gpio7.downgrade())?; + solar_is_day.set_pull(Pull::Floating)?; + + let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; + boot_button.set_pull(Pull::Floating)?; + + let mut light = PinDriver::input_output(peripherals.pins.gpio10.downgrade())?; + light.set_pull(Pull::Floating)?; + + let mut main_pump = PinDriver::input_output(peripherals.pins.gpio2.downgrade())?; + main_pump.set_pull(Pull::Floating)?; + main_pump.set_low()?; + let mut tank_power = PinDriver::input_output(peripherals.pins.gpio11.downgrade())?; + tank_power.set_pull(Pull::Floating)?; + let mut general_fault = PinDriver::input_output(peripherals.pins.gpio6.downgrade())?; + general_fault.set_pull(Pull::Floating)?; + general_fault.set_low()?; + + let one_wire_bus = OneWire::new(one_wire_pin) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + + let status = print_battery(&mut battery_driver); + match status { + OkStd(_) => {} + Err(err) => { + log( + LogMessage::BatteryCommunicationError, + 0u32, + 0, + "", + &format!("{err:?})"), + ); + } + } + + let peripherals = unsafe { Peripherals::new() }; + let sys_loop = EspSystemEventLoop::take()?; + let nvs = EspDefaultNvsPartition::take()?; + let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; + + let rv = Mutex::new( + BoardV4X { + esp: EspHal { + wifi_driver, + mqtt_client: None, + }, + tank_channel, + solar_is_day, + boot_button, + light, + main_pump, + tank_power, + general_fault, + one_wire_bus, + signal_counter: counter_unit1, + battery_driver, + rtc, + eeprom, + }); + Ok(rv) + } + + pub fn create_v3() -> Result>> { let peripherals = Peripherals::take()?; let mut clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?; @@ -1464,10 +1760,6 @@ impl PlantHal { }, )?; - let sys_loop = EspSystemEventLoop::take()?; - let nvs = EspDefaultNvsPartition::take()?; - let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; - let adc_config = AdcChannelConfig { attenuation: attenuation::DB_11, resolution: Resolution::Resolution12Bit, @@ -1511,11 +1803,23 @@ impl PlantHal { ); } } - let shift_register_enable_invert = PinDriver::output(peripherals.pins.gpio21.downgrade())?; + let mut shift_register_enable_invert = PinDriver::output(peripherals.pins.gpio21.downgrade())?; - let rv = Mutex::new(PlantCtrlBoard { + let peripherals = unsafe { Peripherals::new() }; + let sys_loop = EspSystemEventLoop::take()?; + let nvs = EspDefaultNvsPartition::take()?; + let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; + + unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) }; + shift_register_enable_invert.set_low()?; + unsafe { gpio_hold_en(shift_register_enable_invert.pin()) }; + + let board = BoardV3X { + esp: EspHal{ + wifi_driver, + mqtt_client: None + }, shift_register, - shift_register_enable_invert, tank_channel, solar_is_day, boot_button, @@ -1525,19 +1829,18 @@ impl PlantHal { general_fault, one_wire_bus, signal_counter: counter_unit1, - wifi_driver, - mqtt_client: None, battery_driver, rtc, eeprom, - }); + }; + let ref_dyn_foo: Box = Box::new(board); + + let rv = Mutex::new( + ref_dyn_foo + ); + + - let _ = rv.lock().is_ok_and(|mut board| { - unsafe { gpio_hold_dis(board.shift_register_enable_invert.pin()) }; - board.shift_register_enable_invert.set_low().unwrap(); - unsafe { gpio_hold_en(board.shift_register_enable_invert.pin()) }; - true - }); Ok(rv) } diff --git a/rust/src/plant_state.rs b/rust/src/plant_state.rs index 6bae845..1c57bcd 100644 --- a/rust/src/plant_state.rs +++ b/rust/src/plant_state.rs @@ -3,6 +3,7 @@ use chrono_tz::Tz; use serde::{Deserialize, Serialize}; use crate::{config::PlantConfig, in_time_range, plant_hal}; +use crate::plant_hal::{BoardV3X, SpecificBoard}; const MOIST_SENSOR_MAX_FREQUENCY: f32 = 7500.; // 60kHz (500Hz margin) const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really, really dry, think like cactus levels @@ -113,7 +114,7 @@ fn map_range_moisture( impl PlantState { pub fn read_hardware_state( plant_id: usize, - board: &mut plant_hal::PlantCtrlBoard, + board: &mut plant_hal::EspHal, config: &PlantConfig, ) -> Self { let sensor_a = if config.sensor_a { diff --git a/rust/src/tank.rs b/rust/src/tank.rs index 7c426ef..1a46a92 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -1,9 +1,7 @@ use serde::Serialize; -use crate::{ - config::{PlantControllerConfig, TankConfig}, - plant_hal::PlantCtrlBoard, -}; +use crate::plant_hal::{EspHal, SpecificBoard}; +use crate::config::{PlantControllerConfig, TankConfig}; const OPEN_TANK_VOLTAGE: f32 = 3.0; pub const WATER_FROZEN_THRESH: f32 = 4.0; @@ -158,7 +156,7 @@ impl TankState { } pub fn determine_tank_state( - board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, + board: &mut std::sync::MutexGuard<'_, EspHal<'_>>, config: &PlantControllerConfig, ) -> TankState { if config.tank.tank_sensor_enabled { diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index 5244a47..eb72835 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -21,6 +21,7 @@ use std::{ use url::Url; use crate::config::PlantControllerConfig; +use crate::plant_hal::SpecificBoard; use crate::plant_state::MoistureSensorState; #[derive(Serialize, Debug)] diff --git a/website/themes/blowfish b/website/themes/blowfish index 26d1205..1d21656 160000 --- a/website/themes/blowfish +++ b/website/themes/blowfish @@ -1 +1 @@ -Subproject commit 26d1205439b460bee960fd4c29f3c5c20948875f +Subproject commit 1d21656d5efcf6a6b247245d057bf553f3209f39