From c429c829b46ac02198a770c958873023efaae20e Mon Sep 17 00:00:00 2001 From: ju6ge Date: Thu, 19 Jun 2025 13:57:36 +0200 Subject: [PATCH 1/5] start splitting board hal enum into structs --- rust/src/hal/mod.rs | 712 +++++++++++++++++++++++++++++++++----------- 1 file changed, 538 insertions(+), 174 deletions(-) diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index d1ec766..8377e06 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -1,7 +1,7 @@ -mod esp; pub(crate) mod battery; +mod esp; -use bq34z100::{Bq34z100g1Driver}; +use bq34z100::Bq34z100g1Driver; use crate::log::LogMessage; use ds323x::{DateTimeAccess, Ds323x}; @@ -28,7 +28,7 @@ use esp_idf_sys::{ use once_cell::sync::Lazy; use plant_ctrl2::sipo::ShiftRegister40; -use anyhow::{anyhow}; +use anyhow::anyhow; use anyhow::{bail, Ok, Result}; use serde::{Deserialize, Serialize}; @@ -45,8 +45,15 @@ use crate::hal::BoardHal::{Initial, V3, V4}; use crate::log::log; use embedded_hal::digital::OutputPin; use esp_idf_hal::delay::Delay; -use esp_idf_hal::gpio::{AnyInputPin, Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, Gpio18, Gpio19, Gpio2, Gpio20, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, Gpio29, Gpio3, Gpio30, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, IOPin, InputOutput, Level, Output, PinDriver, Pull}; -use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT0}; +use esp_idf_hal::gpio::{ + AnyInputPin, Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, + Gpio18, Gpio19, Gpio2, Gpio20, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, + Gpio29, Gpio3, Gpio30, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, IOPin, InputOutput, Level, Output, + PinDriver, Pull, +}; +use esp_idf_hal::pcnt::{ + PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT0, +}; use esp_idf_hal::prelude::Peripherals; use esp_idf_hal::reset::ResetReason; use esp_idf_svc::sntp::{self, SyncStatus}; @@ -67,7 +74,6 @@ pub static I2C_DRIVER: Lazy>> = Lazy::new(PlantHal::cre struct V3Constants; impl V3Constants { - const PUMP8_BIT: usize = 0; const PUMP1_BIT: usize = 1; const PUMP2_BIT: usize = 2; @@ -102,9 +108,6 @@ impl V3Constants { const SENSOR_B_8: u8 = 15; } - - - const CHARGING: usize = 14; const AWAKE: usize = 15; @@ -117,10 +120,35 @@ const FAULT_4: usize = 21; const FAULT_1: usize = 22; const FAULT_2: usize = 23; - - 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); + } + }; +} + +fn get_restart_to_conf() -> bool { + unsafe { RESTART_TO_CONF } +} +fn set_restart_to_conf(to_conf: bool) { + unsafe { + RESTART_TO_CONF = to_conf; + } +} + #[link_section = ".rtc.data"] static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; #[link_section = ".rtc.data"] @@ -139,71 +167,433 @@ pub enum Sensor { pub struct PlantHal {} - - -pub struct HAL<'a>{ +pub struct HAL<'a> { pub config: PlantControllerConfig, - pub board_hal: BoardHal<'a>, + pub board_hal: Box, pub esp: ESP<'a>, - pub battery_monitor: BatteryMonitor<'a> + pub battery_monitor: BatteryMonitor<'a>, } +pub struct Initial { + general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, +} +impl BoardInteraction for Initial { + fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { + bail!("Please configure board revision") + } -pub enum BoardHal<'a>{ - Initial { - general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput> - }, - V3 { - 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>, - 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>, - 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>, - signal_counter: PcntDriver<'a>, - one_wire_bus: OneWire>, - rtc: - Ds323x>>, ds323x::ic::DS3231>, - eeprom: Eeprom24x< - MutexDevice<'a, I2cDriver<'a>>, - eeprom24x::page_size::B32, - eeprom24x::addr_size::TwoBytes, - eeprom24x::unique_serial::No, - >, - }, - V4 { - 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>, - signal_counter: PcntDriver<'a>, - charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, - light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - one_wire_bus: OneWire>, - rtc: - Ds323x>>, ds323x::ic::DS3231>, - eeprom: Eeprom24x< - MutexDevice<'a, I2cDriver<'a>>, - eeprom24x::page_size::B32, - eeprom24x::addr_size::TwoBytes, - eeprom24x::unique_serial::No, - >, - general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - pump_expander: Pca9535Immediate>>, - sensor_expander: Pca9535Immediate>>, + fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { + deep_sleep(duration_in_ms) + } + + fn get_backup_info(&mut self) -> Result { + bail!("Please configure board revision") + } + + fn get_backup_config(&mut self) -> Result> { + bail!("Please configure board revision") + } + + fn backup_config(&mut self, bytes: &[u8]) -> Result<()> { + bail!("Please configure board revision") + } + + fn is_day(&self) -> bool { + bail!("Please configure board revision") + } + + fn water_temperature_c(&mut self) -> Result { + bail!("Please configure board revision") + } + + fn tank_sensor_voltage(&mut self) -> Result { + bail!("Please configure board revision") + } + + fn set_low_voltage_in_cycle(&mut self) { + bail!("Please configure board revision") + } + + fn clear_low_voltage_in_cycle(&mut self) { + bail!("Please configure board revision") + } + + fn light(&mut self, enable: bool) -> Result<()> { + bail!("Please configure board revision") + } + + fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { + bail!("Please configure board revision") + } + + fn last_pump_time(&self, plant: usize) -> Option> { + bail!("Please configure board revision") + } + + fn store_last_pump_time(&mut self, plant: usize, time: DateTime) { + bail!("Please configure board revision") + } + + fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { + bail!("Please configure board revision") + } + + fn consecutive_pump_count(&mut self, plant: usize) -> u32 { + bail!("Please configure board revision") + } + + fn fault(&mut self, plant: usize, enable: bool) -> Result<()> { + bail!("Please configure board revision") + } + + fn low_voltage_in_cycle(&mut self) -> bool { + bail!("Please configure board revision") + } + + fn any_pump(&mut self, enable: bool) -> Result<()> { + bail!("Please configure board revision") + } + + fn time(&mut self) -> Result> { + bail!("Please configure board revision") + } + + fn sntp(&mut self, max_wait_ms: u32) -> Result> { + bail!("Please configure board revision") + } + + fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { + bail!("Please configure board revision") + } + + fn general_fault(&mut self, enable: bool) { + let _ = self.general_fault.set_state(enable.into()); + } + + fn mode_override_pressed(&mut self) -> bool { + bail!("Please configure board revision") + } + + fn factory_reset(&mut self) -> Result<()> { + bail!("Please configure board revision") + } + + fn get_rtc_time(&mut self) -> Result> { + bail!("Please configure board revision") + } + + fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { + bail!("Please configure board revision") + } + + fn wifi_scan(&mut self) -> Result> { + bail!("Please configure board revision") + } + + fn test_pump(&mut self, plant: usize) -> Result<()> { + bail!("Please configure board revision") + } + + fn test(&mut self) -> Result<()> { + bail!("Please configure board revision") + } + + fn get_restart_to_conf(&mut self) -> bool { + get_restart_to_conf() + } + + fn set_restart_to_conf(&mut self, to_conf: bool) { + set_restart_to_conf(to_conf); } } +pub struct V3 { + 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>, + 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>, + 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>, + signal_counter: PcntDriver<'a>, + one_wire_bus: OneWire>, + rtc: + Ds323x>>, ds323x::ic::DS3231>, + eeprom: Eeprom24x< + MutexDevice<'a, I2cDriver<'a>>, + eeprom24x::page_size::B32, + eeprom24x::addr_size::TwoBytes, + eeprom24x::unique_serial::No, + >, +} +impl BoardInteraction for V3 { + fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { + Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?) + } + fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { + let _ = self.shift_register.decompose()[AWAKE].set_low(); + deep_sleep(duration_in_ms) + } + + fn get_backup_info(&mut self) -> Result { + let store = bincode::serialize(&BackupHeader::default())?.len(); + let mut header_page_buffer = vec![0_u8; store]; + + self.eeprom + .read_data(0, &mut header_page_buffer) + .map_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 store = bincode::serialize(&BackupHeader::default())?.len(); + let mut header_page_buffer = vec![0_u8; store]; + + self.eeprom + .read_data(0, &mut header_page_buffer) + .map_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 = eeprom.page_size() as u32; + let mut data_buffer = vec![0_u8; header.size]; + self.eeprom + .read_data(data_start_address, &mut data_buffer) + .map_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 time = self.get_rtc_time()?.timestamp_millis(); + + let delay = Delay::new_default(); + + let checksum = X25.checksum(bytes); + let page_size = eeprom.page_size(); + + 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; + self.eeprom + .write_page(address, chunk) + .map_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 { + let _ = self.fault(i, iter == i); + } + lastiter = iter; + } + + delay.delay_ms(5); + } + Ok(()) + } + + fn is_day(&self) -> bool { + self.solar_is_day.get_level().into() + } + + fn water_temperature_c(&mut self) -> Result { + self.one_wire_bus + .reset(&mut self.esp.delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + let first = one_wire_bus.devices(false, &mut self.esp.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(one_wire_bus, &mut self.esp.delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.esp.delay); + let sensor_data = water_temp_sensor + .read_data(one_wire_bus, &mut self.esp.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) + } + + fn tank_sensor_voltage(&mut self) -> Result { + todo!() + } + + fn set_low_voltage_in_cycle(&mut self) { + todo!() + } + + fn clear_low_voltage_in_cycle(&mut self) { + todo!() + } + + fn light(&mut self, enable: bool) -> Result<()> { + todo!() + } + + fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { + todo!() + } + + fn last_pump_time(&self, plant: usize) -> Option> { + todo!() + } + + fn store_last_pump_time(&mut self, plant: usize, time: DateTime) { + todo!() + } + + fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { + todo!() + } + + fn consecutive_pump_count(&mut self, plant: usize) -> u32 { + todo!() + } + + fn fault(&mut self, plant: usize, enable: bool) -> Result<()> { + todo!() + } + + fn low_voltage_in_cycle(&mut self) -> bool { + todo!() + } + + fn any_pump(&mut self, enable: bool) -> Result<()> { + todo!() + } + + fn time(&mut self) -> Result> { + todo!() + } + + fn sntp(&mut self, max_wait_ms: u32) -> Result> { + todo!() + } + + fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { + todo!() + } + + fn general_fault(&mut self, enable: bool) { + todo!() + } + + fn mode_override_pressed(&mut self) -> bool { + todo!() + } + + fn factory_reset(&mut self) -> Result<()> { + todo!() + } + + fn get_rtc_time(&mut self) -> Result> { + todo!() + } + + fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { + todo!() + } + + fn wifi_scan(&mut self) -> Result> { + todo!() + } + + fn test_pump(&mut self, plant: usize) -> Result<()> { + todo!() + } + + fn test(&mut self) -> Result<()> { + todo!() + } + + fn get_restart_to_conf(&mut self) -> bool { + todo!() + } + + fn set_restart_to_conf(&mut self, to_conf: bool) { + todo!() + } +} + +pub struct V4 { + 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>, + signal_counter: PcntDriver<'a>, + charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, + light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + one_wire_bus: OneWire>, + rtc: + Ds323x>>, ds323x::ic::DS3231>, + eeprom: Eeprom24x< + MutexDevice<'a, I2cDriver<'a>>, + eeprom24x::page_size::B32, + eeprom24x::addr_size::TwoBytes, + eeprom24x::unique_serial::No, + >, + general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + pump_expander: Pca9535Immediate>>, + sensor_expander: Pca9535Immediate>>, +} #[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct BackupHeader { @@ -212,6 +602,16 @@ pub struct BackupHeader { pub size: usize, } +impl Default for BackupHeader { + fn default() -> Self { + Self { + timestamp: Default::default(), + crc16: Default::default(), + size: Default::default(), + } + } +} + impl BoardInteraction for HAL<'_> { fn set_charge_indicator(&mut self, charging: bool) { match &mut self.board_hal { @@ -220,12 +620,12 @@ impl BoardInteraction for HAL<'_> { .set_state(charging.into()) .unwrap(); } - V4 { charge_indicator , ..} => { + V4 { + charge_indicator, .. + } => { charge_indicator.set_state(charging.into()).unwrap(); - }, - Initial { .. } => { - } + Initial { .. } => {} } } fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { @@ -233,12 +633,10 @@ impl BoardInteraction for HAL<'_> { V3 { shift_register, .. } => { shift_register.decompose()[AWAKE].set_low().unwrap(); } - V4 { awake, .. } => { + V4 { awake, .. } => { awake.set_low().unwrap(); - }, - Initial { .. } => { - } + Initial { .. } => {} } unsafe { @@ -259,8 +657,8 @@ impl BoardInteraction for HAL<'_> { } fn get_backup_info(&mut self) -> Result { let eeprom = match &mut self.board_hal { - V3 { eeprom, .. } => {eeprom} - V4 { eeprom, .. } => {eeprom }, + V3 { eeprom, .. } => eeprom, + V4 { eeprom, .. } => eeprom, &mut Initial { .. } => { bail!("Board not configured yet") } @@ -283,8 +681,8 @@ impl BoardInteraction for HAL<'_> { } fn get_backup_config(&mut self) -> Result> { let eeprom = match &mut self.board_hal { - V3 { eeprom, .. } => {eeprom} - V4 { eeprom, .. } => {eeprom } + V3 { eeprom, .. } => eeprom, + V4 { eeprom, .. } => eeprom, &mut Initial { .. } => { bail!("Board not configured yet") } @@ -326,10 +724,8 @@ impl BoardInteraction for HAL<'_> { fn backup_config(&mut self, bytes: &[u8]) -> Result<()> { let time = self.get_rtc_time()?.timestamp_millis(); let eeprom = match &mut self.board_hal { - V3 { eeprom, .. } => { - eeprom - } - V4 { eeprom, .. } => { eeprom }, + V3 { eeprom, .. } => eeprom, + V4 { eeprom, .. } => eeprom, &mut Initial { .. } => { bail!("Board not configured yet") } @@ -369,10 +765,8 @@ impl BoardInteraction for HAL<'_> { for chunk in to_write { let address = current_page * page_size as u32; let eeprom = match &mut self.board_hal { - V3 { eeprom, .. } => { - eeprom - } - V4 { eeprom, .. } => { eeprom } + V3 { eeprom, .. } => eeprom, + V4 { eeprom, .. } => eeprom, &mut Initial { .. } => { bail!("Board not configured yet") } @@ -396,13 +790,11 @@ impl BoardInteraction for HAL<'_> { } Ok(()) } - fn is_day(& self) -> bool { - match & self.board_hal { - V3 { solar_is_day, .. } => {solar_is_day.get_level().into()} - V4 { solar_is_day, .. } => {solar_is_day.get_level().into() } - Initial { .. } => { - false - } + fn is_day(&self) -> bool { + match &self.board_hal { + V3 { solar_is_day, .. } => solar_is_day.get_level().into(), + V4 { solar_is_day, .. } => solar_is_day.get_level().into(), + Initial { .. } => false, } } //should be multsampled @@ -444,14 +836,21 @@ impl BoardInteraction for HAL<'_> { /// return median tank sensor value in milli volt fn tank_sensor_voltage(&mut self) -> Result { let (tank_power, tank_channel) = match &mut self.board_hal { - V3 { tank_power, tank_channel, .. } => {(tank_power,tank_channel)} - V4 { tank_power, tank_channel, .. } => {(tank_power,tank_channel) } + V3 { + tank_power, + tank_channel, + .. + } => (tank_power, tank_channel), + V4 { + tank_power, + tank_channel, + .. + } => (tank_power, tank_channel), &mut Initial { .. } => { bail!("Board not configured yet") } }; - tank_power.set_high()?; //let stabilize self.esp.delay.delay_ms(100); @@ -508,13 +907,13 @@ impl BoardInteraction for HAL<'_> { //currently infallible error, keep for future as result anyway shift_register.decompose()[index].set_state(enable.into())?; } - V4 { pump_expander, .. } => { + V4 { pump_expander, .. } => { if enable { pump_expander.pin_set_high(GPIOBank::Bank0, plant.try_into()?)?; } else { pump_expander.pin_set_low(GPIOBank::Bank0, plant.try_into()?)?; } - }, + } &mut Initial { .. } => { bail!("Board not configured yet") } @@ -539,7 +938,7 @@ impl BoardInteraction for HAL<'_> { fn consecutive_pump_count(&mut self, plant: usize) -> u32 { unsafe { CONSECUTIVE_WATERING_PLANT[plant] } } - fn fault(&mut self, plant: usize, enable: bool) -> Result<()>{ + fn fault(&mut self, plant: usize, enable: bool) -> Result<()> { match &mut self.board_hal { V3 { shift_register, .. } => { let index = match plant { @@ -553,8 +952,7 @@ impl BoardInteraction for HAL<'_> { 7 => FAULT_8, _ => panic!("Invalid plant id {}", plant), }; - shift_register.decompose()[index] - .set_state(enable.into())?; + shift_register.decompose()[index].set_state(enable.into())?; } V4 { pump_expander, .. } => { if enable { @@ -562,7 +960,6 @@ impl BoardInteraction for HAL<'_> { } else { pump_expander.pin_set_low(GPIOBank::Bank1, plant.try_into()?)?; } - } &mut Initial { .. } => { bail!("Board not configured yet") @@ -586,7 +983,6 @@ impl BoardInteraction for HAL<'_> { } } Ok(()) - } fn time(&mut self) -> Result> { let time = EspSystemTime {}.now().as_millis(); @@ -611,7 +1007,11 @@ impl BoardInteraction for HAL<'_> { } fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { match &mut self.board_hal { - V3{ signal_counter, shift_register, .. } => { + V3 { + signal_counter, + shift_register, + .. + } => { let mut results = [0_f32; REPEAT_MOIST_MEASURE]; for repeat in 0..REPEAT_MOIST_MEASURE { signal_counter.counter_pause()?; @@ -644,7 +1044,6 @@ impl BoardInteraction for HAL<'_> { }, }; - let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; let pin_0 = &mut shift_register.decompose()[V3Constants::MS_0]; let pin_1 = &mut shift_register.decompose()[V3Constants::MS_1]; @@ -702,14 +1101,17 @@ impl BoardInteraction for HAL<'_> { let mid = results.len() / 2; let median = results[mid]; Ok(median) - }, - V4 {sensor_expander, signal_counter, ..} => { + } + V4 { + sensor_expander, + signal_counter, + .. + } => { let mut results = [0_f32; REPEAT_MOIST_MEASURE]; for repeat in 0..REPEAT_MOIST_MEASURE { signal_counter.counter_pause()?; signal_counter.counter_clear()?; - const MS0: u8 = 1_u8; const MS1: u8 = 0_u8; const MS2: u8 = 3_u8; @@ -720,16 +1122,10 @@ impl BoardInteraction for HAL<'_> { sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; let sensor_channel = match sensor { - Sensor::A =>{ - plant as u32 - }, - Sensor::B => { - (15 - plant) as u32 - }, + Sensor::A => plant as u32, + Sensor::B => (15 - plant) as u32, }; - - let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; if is_bit_set(0) { sensor_expander.pin_set_high(GPIOBank::Bank0, MS0)?; @@ -792,13 +1188,12 @@ impl BoardInteraction for HAL<'_> { bail!("Not implemented for this board") } } - } fn general_fault(&mut self, enable: bool) { let general_fault = match &mut self.board_hal { - V3 { general_fault, .. } => {general_fault} - V4 { general_fault, .. } => {general_fault}, - Initial { general_fault, .. } => {general_fault} + V3 { general_fault, .. } => general_fault, + V4 { general_fault, .. } => general_fault, + Initial { general_fault, .. } => general_fault, }; unsafe { gpio_hold_dis(general_fault.pin()) }; general_fault.set_state(enable.into()).unwrap(); @@ -819,8 +1214,8 @@ impl BoardInteraction for HAL<'_> { } fn get_rtc_time(&mut self) -> Result> { let rtc = match &mut self.board_hal { - V3 { rtc, .. } => {rtc} - V4 { rtc, .. } => {rtc}, + V3 { rtc, .. } => rtc, + V4 { rtc, .. } => rtc, Initial { .. } => { bail!("Board not configured yet") } @@ -834,8 +1229,8 @@ impl BoardInteraction for HAL<'_> { } fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { let rtc = match &mut self.board_hal { - V3 { rtc, .. } => {rtc} - V4 { rtc, .. } => {rtc} + V3 { rtc, .. } => rtc, + V4 { rtc, .. } => rtc, Initial { .. } => { bail!("Board not configured yet") } @@ -908,21 +1303,10 @@ impl BoardInteraction for HAL<'_> { Delay::new_default().delay_ms(10); Ok(()) } - - fn get_restart_to_conf(&mut self) -> bool { - unsafe { RESTART_TO_CONF } - } - fn set_restart_to_conf(&mut self, to_conf: bool) { - unsafe { - RESTART_TO_CONF = to_conf; - } - } } - - pub trait BoardInteraction { - fn set_charge_indicator(&mut self, charging: bool); + fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; fn deep_sleep(&mut self, duration_in_ms: u64) -> !; fn get_backup_info(&mut self) -> Result; fn get_backup_config(&mut self) -> Result>; @@ -956,11 +1340,8 @@ pub trait BoardInteraction { fn test(&mut self) -> Result<()>; fn get_restart_to_conf(&mut self) -> bool; fn set_restart_to_conf(&mut self, to_conf: bool); - } - - pub struct FreePeripherals { pub gpio0: Gpio0, pub gpio1: Gpio1, @@ -996,7 +1377,6 @@ pub struct FreePeripherals { pub adc1: ADC1, } - impl PlantHal { fn create_i2c() -> Mutex> { let peripherals = unsafe { Peripherals::new() }; @@ -1014,14 +1394,12 @@ impl PlantHal { Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) } - - pub fn create() -> Result>> { + 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)?; @@ -1062,10 +1440,9 @@ impl PlantHal { mqtt_client: None, wifi_driver, boot_button, - delay: Delay::new(1000) + 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; @@ -1104,9 +1481,10 @@ impl PlantHal { let config = esp.get_config(); let hal = match config { Result::Ok(config) => { - let board_hal : BoardHal = match config.hardware.board { + let board_hal: BoardHal = match config.hardware.board { BoardVersion::INITIAL => { - let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; + let mut general_fault = + PinDriver::input_output(free_pins.gpio6.downgrade())?; general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; @@ -1114,13 +1492,14 @@ impl PlantHal { general_fault.set_high()? } - Initial { general_fault}}, - BoardVersion::V3 => {PlantHal::create_v3(free_pins)?}, - BoardVersion::V4 => {PlantHal::create_v4(free_pins)?} + Initial { general_fault } + } + BoardVersion::V3 => PlantHal::create_v3(free_pins)?, + BoardVersion::V4 => PlantHal::create_v4(free_pins)?, }; - let battery_monitor : BatteryMonitor = match config.hardware.battery { - BatteryBoardVersion::Disabled => { BatteryMonitor::Disabled {}} + let battery_monitor: BatteryMonitor = match config.hardware.battery { + BatteryBoardVersion::Disabled => BatteryMonitor::Disabled {}, BatteryBoardVersion::BQ34Z100G1 => { let mut battery_driver = Bq34z100g1Driver { i2c: MutexDevice::new(&I2C_DRIVER), @@ -1142,17 +1521,14 @@ impl PlantHal { } BatteryMonitor::BQ34Z100G1 { battery_driver } } - BatteryBoardVersion::WchI2cSlave => { - BatteryMonitor::WchI2cSlave {} - } + BatteryBoardVersion::WchI2cSlave => BatteryMonitor::WchI2cSlave {}, }; - - HAL{ + HAL { config, board_hal, esp, - battery_monitor + battery_monitor, } } Err(err) => { @@ -1168,9 +1544,9 @@ impl PlantHal { general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; - HAL{ + HAL { config: Default::default(), - board_hal: Initial { general_fault}, + board_hal: Initial { general_fault }, esp, battery_monitor: BatteryMonitor::Disabled {}, } @@ -1205,7 +1581,6 @@ impl PlantHal { let one_wire_bus = OneWire::new(one_wire_pin) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - let rtc_time = rtc.datetime(); match rtc_time { OkStd(tt) => { @@ -1224,7 +1599,6 @@ impl PlantHal { } } - let mut signal_counter = PcntDriver::new( peripherals.pcnt0, Some(peripherals.gpio22), @@ -1269,11 +1643,10 @@ impl PlantHal { charge_indicator.set_pull(Pull::Floating)?; charge_indicator.set_low()?; - let mut pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32); //todo error handing if init error - for pin in 0..8{ + for pin in 0..8 { let _ = pump_expander.pin_into_output(GPIOBank::Bank0, pin); let _ = pump_expander.pin_into_output(GPIOBank::Bank1, pin); let _ = pump_expander.pin_set_low(GPIOBank::Bank0, pin); @@ -1281,7 +1654,7 @@ impl PlantHal { } let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34); - for pin in 0..8{ + for pin in 0..8 { let _ = sensor_expander.pin_into_output(GPIOBank::Bank0, pin); let _ = sensor_expander.pin_into_output(GPIOBank::Bank1, pin); let _ = sensor_expander.pin_set_low(GPIOBank::Bank0, pin); @@ -1301,7 +1674,7 @@ impl PlantHal { general_fault, pump_expander, sensor_expander, - charge_indicator + charge_indicator, }) } @@ -1338,7 +1711,6 @@ impl PlantHal { println!("Init battery driver"); - println!("Init rtc driver"); let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); @@ -1371,7 +1743,6 @@ impl PlantHal { } } - let mut signal_counter = PcntDriver::new( peripherals.pcnt0, Some(peripherals.gpio22), @@ -1394,8 +1765,6 @@ impl PlantHal { }, )?; - - let adc_config = AdcChannelConfig { attenuation: attenuation::DB_11, resolution: Resolution::Resolution12Bit, @@ -1408,8 +1777,6 @@ impl PlantHal { let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?; solar_is_day.set_pull(Pull::Floating)?; - - let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?; light.set_pull(Pull::Floating)?; @@ -1425,15 +1792,12 @@ impl PlantHal { let one_wire_bus = OneWire::new(one_wire_pin) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - let mut shift_register_enable_invert = PinDriver::output(peripherals.gpio21.downgrade())?; - unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) }; shift_register_enable_invert.set_low()?; unsafe { gpio_hold_en(shift_register_enable_invert.pin()) }; - Ok(BoardHal::V3 { shift_register, shift_register_enable_invert, From fc1991523aedf49d9a674ae8ae927f936ae703d9 Mon Sep 17 00:00:00 2001 From: Empire Date: Thu, 19 Jun 2025 16:56:33 +0200 Subject: [PATCH 2/5] splitting wip --- rust/src/hal/esp.rs | 115 ++- rust/src/hal/initial_hal.rs | 99 ++ rust/src/hal/mod.rs | 1543 +------------------------------ rust/src/hal/v3_hal.rs | 621 +++++++++++++ rust/src/hal/v4_hal.rs | 530 +++++++++++ rust/src/main.rs | 222 +++-- rust/src/plant_state.rs | 22 +- rust/src/tank.rs | 4 +- rust/src/webserver/webserver.rs | 72 +- 9 files changed, 1553 insertions(+), 1675 deletions(-) create mode 100644 rust/src/hal/initial_hal.rs create mode 100644 rust/src/hal/v3_hal.rs create mode 100644 rust/src/hal/v4_hal.rs diff --git a/rust/src/hal/esp.rs b/rust/src/hal/esp.rs index e9f14f4..b0811f7 100644 --- a/rust/src/hal/esp.rs +++ b/rust/src/hal/esp.rs @@ -1,27 +1,42 @@ use crate::config::{NetworkConfig, PlantControllerConfig}; -use crate::hal::{CONSECUTIVE_WATERING_PLANT, LAST_WATERING_TIMESTAMP, LOW_VOLTAGE_DETECTED, PLANT_COUNT, RESTART_TO_CONF}; +use crate::hal::PLANT_COUNT; use crate::log::{log, LogMessage}; +use crate::STAY_ALIVE; use anyhow::{anyhow, bail, Context}; +use chrono::{DateTime, Utc}; use embedded_svc::ipv4::IpInfo; -use embedded_svc::wifi::{AccessPointConfiguration, AuthMethod, ClientConfiguration, Configuration}; +use embedded_svc::mqtt::client::QoS::{AtLeastOnce, ExactlyOnce}; +use embedded_svc::wifi::{AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration}; use esp_idf_hal::delay::Delay; -use esp_idf_hal::gpio::PinDriver; +use esp_idf_hal::gpio::{Level, PinDriver}; use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration}; +use esp_idf_svc::wifi::config::{ScanConfig, ScanType}; use esp_idf_svc::wifi::EspWifi; use esp_idf_sys::{esp_spiffs_info, vTaskDelay}; +use serde::Serialize; use std::ffi::CString; use std::fs; use std::fs::File; use std::path::Path; use std::result::Result::Ok as OkStd; use std::str::FromStr; -use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicBool; +use std::sync::Arc; use std::time::Duration; -use embedded_svc::mqtt::client::QoS::{AtLeastOnce, ExactlyOnce}; -use esp_idf_hal::i2c::I2cDriver; -use serde::Serialize; -use crate::STAY_ALIVE; +use esp_idf_svc::sntp; +use esp_idf_svc::sntp::SyncStatus; +use esp_idf_svc::systime::EspSystemTime; + + +#[link_section = ".rtc.data"] +static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; +#[link_section = ".rtc.data"] +static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT]; +#[link_section = ".rtc.data"] +static mut LOW_VOLTAGE_DETECTED: bool = false; +#[link_section = ".rtc.data"] +static mut RESTART_TO_CONF: bool = false; + #[derive(Serialize, Debug)] pub struct FileInfo { @@ -61,8 +76,84 @@ impl ESP<'_> { const CONFIG_FILE: &'static str = "/spiffs/config.cfg"; const BASE_PATH: &'static str = "/spiffs"; + pub(crate) fn mode_override_pressed(&mut self) -> bool { + self.boot_button.get_level() == Level::Low + } + pub(crate) 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 { + self.delay.delay_ms(100); + counter += 100; + if counter > max_wait_ms { + bail!("Reached sntp timeout, aborting") + } + } + self.time() + } + pub(crate) fn time(&mut self) -> anyhow::Result> { + let time = EspSystemTime {}.now().as_millis(); + let smaller_time = time as i64; + let local_time = DateTime::from_timestamp_millis(smaller_time) + .ok_or(anyhow!("could not convert timestamp"))?; + anyhow::Ok(local_time) + } + pub(crate) fn wifi_scan(&mut self) -> anyhow::Result> { + self.wifi_driver.start_scan( + &ScanConfig { + scan_type: ScanType::Passive(Duration::from_secs(5)), + show_hidden: false, + ..Default::default() + }, + true, + )?; + anyhow::Ok(self.wifi_driver.get_scan_result()?) + } + + pub(crate) fn last_pump_time(&self, plant: usize) -> Option> { + let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant]; + DateTime::from_timestamp_millis(ts) + } + pub(crate) fn store_last_pump_time(&mut self, plant: usize, time: DateTime) { + unsafe { + LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis(); + } + } + pub(crate) fn set_low_voltage_in_cycle(&mut self) { + unsafe { + LOW_VOLTAGE_DETECTED = true; + } + } + pub(crate) fn clear_low_voltage_in_cycle(&mut self) { + unsafe { + LOW_VOLTAGE_DETECTED = false; + } + } + + pub(crate) fn low_voltage_in_cycle(&mut self) -> bool { + unsafe { + LOW_VOLTAGE_DETECTED + } + } + pub(crate) fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { + unsafe { + CONSECUTIVE_WATERING_PLANT[plant] = count; + } + } + pub(crate) fn consecutive_pump_count(&mut self, plant: usize) -> u32 { + unsafe { CONSECUTIVE_WATERING_PLANT[plant] } + } + pub(crate) fn get_restart_to_conf(&mut self) -> bool { + unsafe { RESTART_TO_CONF } + } + pub(crate) fn set_restart_to_conf(&mut self, to_conf: bool) { + unsafe { + RESTART_TO_CONF = to_conf; + } + } + pub(crate) fn wifi_ap(&mut self) -> anyhow::Result<()> { - let ssid = match self.get_config(){ + let ssid = match self.load_config(){ Ok(config) => { config.network.ap_ssid.clone() } @@ -145,12 +236,12 @@ impl ESP<'_> { log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}")); anyhow::Ok(address) } - pub(crate) fn get_config(&mut self) -> anyhow::Result { + pub(crate) fn load_config(&mut self) -> anyhow::Result { let cfg = File::open(Self::CONFIG_FILE)?; let config: PlantControllerConfig = serde_json::from_reader(cfg)?; anyhow::Ok(config) } - pub(crate) fn set_config(&mut self, config: &PlantControllerConfig) -> anyhow::Result<()> { + pub(crate) fn save_config(&mut self, config: &PlantControllerConfig) -> anyhow::Result<()> { let mut cfg = File::create(Self::CONFIG_FILE)?; serde_json::to_writer(&mut cfg, &config)?; println!("Wrote config config {:?}", config); @@ -227,7 +318,7 @@ impl ESP<'_> { filename: file.file_name().into_string().unwrap(), size: file .metadata() - .and_then(|it| anyhow::Result::Ok(it.len())) + .and_then(|it| Ok(it.len())) .unwrap_or_default() as usize, }; diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs new file mode 100644 index 0000000..bd7bc6b --- /dev/null +++ b/rust/src/hal/initial_hal.rs @@ -0,0 +1,99 @@ +use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, Sensor}; +use anyhow::{bail, Result}; +use chrono::{DateTime, Utc}; +use embedded_hal::digital::OutputPin; +use esp_idf_hal::gpio::{InputOutput, PinDriver}; +use crate::config::{BoardHardware, PlantControllerConfig}; +use crate::hal::battery::{BatteryInteraction, BatteryMonitor}; +use crate::hal::esp::ESP; + +pub struct Initial<'a> { + pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + pub(crate) esp: ESP<'a>, + pub(crate) config: PlantControllerConfig, +} + +impl BoardInteraction<'_> for Initial<'_> { + fn get_esp<'a>(&mut self) -> &mut ESP<'a> { + &mut self.esp + } + + fn get_config(&mut self) -> &PlantControllerConfig { + &self.config + } + + fn get_battery_monitor(&mut self) -> Box { + let v = BatteryMonitor::Disabled { + + }; + Box::new(v) as Box + } + + fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { + bail!("Please configure board revision") + } + + fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { + deep_sleep(duration_in_ms) + } + + fn get_backup_info(&mut self) -> Result { + bail!("Please configure board revision") + } + + fn get_backup_config(&mut self) -> Result> { + bail!("Please configure board revision") + } + + fn backup_config(&mut self, bytes: &[u8]) -> Result<()> { + bail!("Please configure board revision") + } + + fn is_day(&self) -> bool { + false + } + + fn water_temperature_c(&mut self) -> Result { + bail!("Please configure board revision") + } + + fn tank_sensor_voltage(&mut self) -> Result { + bail!("Please configure board revision") + } + fn light(&mut self, enable: bool) -> Result<()> { + bail!("Please configure board revision") + } + + fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { + bail!("Please configure board revision") + } + fn fault(&mut self, plant: usize, _enable: bool) -> Result<()> { + bail!("Please configure board revision") + } + fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { + bail!("Please configure board revision") + } + + fn general_fault(&mut self, enable: bool) { + let _ = self.general_fault.set_state(enable.into()); + } + + fn factory_reset(&mut self) -> Result<()> { + bail!("Please configure board revision") + } + + fn get_rtc_time(&mut self) -> Result> { + bail!("Please configure board revision") + } + + fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { + bail!("Please configure board revision") + } + fn test_pump(&mut self, plant: usize) -> Result<()> { + bail!("Please configure board revision") + } + + fn test(&mut self) -> Result<()> { + bail!("Please configure board revision") + } +} \ No newline at end of file diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index 8377e06..5f17bb8 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -1,24 +1,23 @@ pub(crate) mod battery; mod esp; +mod initial_hal; +mod v3_hal; +mod v4_hal; use bq34z100::Bq34z100g1Driver; use crate::log::LogMessage; -use ds323x::{DateTimeAccess, Ds323x}; +use ds323x::DateTimeAccess; use esp_ota::mark_app_valid; -use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr}; +use eeprom24x::Eeprom24xTrait; use embedded_hal_bus::i2c::MutexDevice; -use embedded_svc::wifi::AccessPointInfo; -use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; -use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; -use esp_idf_hal::adc::{attenuation, Resolution, ADC1}; +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::config::{ScanConfig, ScanType}; use esp_idf_svc::wifi::EspWifi; use esp_idf_sys::esp_restart; use esp_idf_sys::{ @@ -26,41 +25,32 @@ use esp_idf_sys::{ esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, }; use once_cell::sync::Lazy; -use plant_ctrl2::sipo::ShiftRegister40; -use anyhow::anyhow; -use anyhow::{bail, Ok, Result}; +use anyhow::{Ok, Result}; use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; -use ds18b20::Ds18b20; 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, BatteryMonitor}; +use crate::hal::battery::{print_battery_bq34z100, BatteryInteraction, BatteryMonitor}; use crate::hal::esp::ESP; -use crate::hal::BoardHal::{Initial, V3, V4}; +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::{ - AnyInputPin, Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, - Gpio18, Gpio19, Gpio2, Gpio20, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, - Gpio29, Gpio3, Gpio30, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, IOPin, InputOutput, Level, Output, + 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::{ - PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT0, -}; +use esp_idf_hal::pcnt::PCNT0; use esp_idf_hal::prelude::Peripherals; use esp_idf_hal::reset::ResetReason; -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::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; +use pca9535::StandardExpanderInterface; //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; @@ -74,51 +64,10 @@ pub static I2C_DRIVER: Lazy>> = Lazy::new(PlantHal::cre struct V3Constants; impl V3Constants { - const PUMP8_BIT: usize = 0; - const PUMP1_BIT: usize = 1; - const PUMP2_BIT: usize = 2; - const PUMP3_BIT: usize = 3; - const PUMP4_BIT: usize = 4; - const PUMP5_BIT: usize = 5; - const PUMP6_BIT: usize = 6; - const PUMP7_BIT: usize = 7; - const MS_0: usize = 8; - const MS_4: usize = 9; - const MS_2: usize = 10; - const MS_3: usize = 11; - const MS_1: usize = 13; - const SENSOR_ON: usize = 12; - const SENSOR_A_1: u8 = 7; - const SENSOR_A_2: u8 = 6; - const SENSOR_A_3: u8 = 5; - const SENSOR_A_4: u8 = 4; - const SENSOR_A_5: u8 = 3; - const SENSOR_A_6: u8 = 2; - const SENSOR_A_7: u8 = 1; - const SENSOR_A_8: u8 = 0; - - const SENSOR_B_1: u8 = 8; - const SENSOR_B_2: u8 = 9; - const SENSOR_B_3: u8 = 10; - const SENSOR_B_4: u8 = 11; - const SENSOR_B_5: u8 = 12; - const SENSOR_B_6: u8 = 13; - const SENSOR_B_7: u8 = 14; - const SENSOR_B_8: u8 = 15; } -const CHARGING: usize = 14; -const AWAKE: usize = 15; -const FAULT_3: usize = 16; -const FAULT_8: usize = 17; -const FAULT_7: usize = 18; -const FAULT_6: usize = 19; -const FAULT_5: usize = 20; -const FAULT_4: usize = 21; -const FAULT_1: usize = 22; -const FAULT_2: usize = 23; const X25: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); @@ -140,25 +89,6 @@ fn deep_sleep(duration_in_ms: u64) -> ! { }; } -fn get_restart_to_conf() -> bool { - unsafe { RESTART_TO_CONF } -} -fn set_restart_to_conf(to_conf: bool) { - unsafe { - RESTART_TO_CONF = to_conf; - } -} - -#[link_section = ".rtc.data"] -static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; -#[link_section = ".rtc.data"] -static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT]; -#[link_section = ".rtc.data"] -static mut LOW_VOLTAGE_DETECTED: bool = false; - -#[link_section = ".rtc.data"] -static mut RESTART_TO_CONF: bool = false; - #[derive(Debug, PartialEq)] pub enum Sensor { A, @@ -168,431 +98,7 @@ pub enum Sensor { pub struct PlantHal {} pub struct HAL<'a> { - pub config: PlantControllerConfig, - pub board_hal: Box, - pub esp: ESP<'a>, - pub battery_monitor: BatteryMonitor<'a>, -} - -pub struct Initial { - general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, -} - -impl BoardInteraction for Initial { - fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { - bail!("Please configure board revision") - } - - fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { - deep_sleep(duration_in_ms) - } - - fn get_backup_info(&mut self) -> Result { - bail!("Please configure board revision") - } - - fn get_backup_config(&mut self) -> Result> { - bail!("Please configure board revision") - } - - fn backup_config(&mut self, bytes: &[u8]) -> Result<()> { - bail!("Please configure board revision") - } - - fn is_day(&self) -> bool { - bail!("Please configure board revision") - } - - fn water_temperature_c(&mut self) -> Result { - bail!("Please configure board revision") - } - - fn tank_sensor_voltage(&mut self) -> Result { - bail!("Please configure board revision") - } - - fn set_low_voltage_in_cycle(&mut self) { - bail!("Please configure board revision") - } - - fn clear_low_voltage_in_cycle(&mut self) { - bail!("Please configure board revision") - } - - fn light(&mut self, enable: bool) -> Result<()> { - bail!("Please configure board revision") - } - - fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { - bail!("Please configure board revision") - } - - fn last_pump_time(&self, plant: usize) -> Option> { - bail!("Please configure board revision") - } - - fn store_last_pump_time(&mut self, plant: usize, time: DateTime) { - bail!("Please configure board revision") - } - - fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { - bail!("Please configure board revision") - } - - fn consecutive_pump_count(&mut self, plant: usize) -> u32 { - bail!("Please configure board revision") - } - - fn fault(&mut self, plant: usize, enable: bool) -> Result<()> { - bail!("Please configure board revision") - } - - fn low_voltage_in_cycle(&mut self) -> bool { - bail!("Please configure board revision") - } - - fn any_pump(&mut self, enable: bool) -> Result<()> { - bail!("Please configure board revision") - } - - fn time(&mut self) -> Result> { - bail!("Please configure board revision") - } - - fn sntp(&mut self, max_wait_ms: u32) -> Result> { - bail!("Please configure board revision") - } - - fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { - bail!("Please configure board revision") - } - - fn general_fault(&mut self, enable: bool) { - let _ = self.general_fault.set_state(enable.into()); - } - - fn mode_override_pressed(&mut self) -> bool { - bail!("Please configure board revision") - } - - fn factory_reset(&mut self) -> Result<()> { - bail!("Please configure board revision") - } - - fn get_rtc_time(&mut self) -> Result> { - bail!("Please configure board revision") - } - - fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { - bail!("Please configure board revision") - } - - fn wifi_scan(&mut self) -> Result> { - bail!("Please configure board revision") - } - - fn test_pump(&mut self, plant: usize) -> Result<()> { - bail!("Please configure board revision") - } - - fn test(&mut self) -> Result<()> { - bail!("Please configure board revision") - } - - fn get_restart_to_conf(&mut self) -> bool { - get_restart_to_conf() - } - - fn set_restart_to_conf(&mut self, to_conf: bool) { - set_restart_to_conf(to_conf); - } -} - -pub struct V3 { - 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>, - 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>, - 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>, - signal_counter: PcntDriver<'a>, - one_wire_bus: OneWire>, - rtc: - Ds323x>>, ds323x::ic::DS3231>, - eeprom: Eeprom24x< - MutexDevice<'a, I2cDriver<'a>>, - eeprom24x::page_size::B32, - eeprom24x::addr_size::TwoBytes, - eeprom24x::unique_serial::No, - >, -} - -impl BoardInteraction for V3 { - fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { - Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?) - } - - fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { - let _ = self.shift_register.decompose()[AWAKE].set_low(); - deep_sleep(duration_in_ms) - } - - fn get_backup_info(&mut self) -> Result { - let store = bincode::serialize(&BackupHeader::default())?.len(); - let mut header_page_buffer = vec![0_u8; store]; - - self.eeprom - .read_data(0, &mut header_page_buffer) - .map_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 store = bincode::serialize(&BackupHeader::default())?.len(); - let mut header_page_buffer = vec![0_u8; store]; - - self.eeprom - .read_data(0, &mut header_page_buffer) - .map_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 = eeprom.page_size() as u32; - let mut data_buffer = vec![0_u8; header.size]; - self.eeprom - .read_data(data_start_address, &mut data_buffer) - .map_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 time = self.get_rtc_time()?.timestamp_millis(); - - let delay = Delay::new_default(); - - let checksum = X25.checksum(bytes); - let page_size = eeprom.page_size(); - - 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; - self.eeprom - .write_page(address, chunk) - .map_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 { - let _ = self.fault(i, iter == i); - } - lastiter = iter; - } - - delay.delay_ms(5); - } - Ok(()) - } - - fn is_day(&self) -> bool { - self.solar_is_day.get_level().into() - } - - fn water_temperature_c(&mut self) -> Result { - self.one_wire_bus - .reset(&mut self.esp.delay) - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - let first = one_wire_bus.devices(false, &mut self.esp.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(one_wire_bus, &mut self.esp.delay) - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.esp.delay); - let sensor_data = water_temp_sensor - .read_data(one_wire_bus, &mut self.esp.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) - } - - fn tank_sensor_voltage(&mut self) -> Result { - todo!() - } - - fn set_low_voltage_in_cycle(&mut self) { - todo!() - } - - fn clear_low_voltage_in_cycle(&mut self) { - todo!() - } - - fn light(&mut self, enable: bool) -> Result<()> { - todo!() - } - - fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { - todo!() - } - - fn last_pump_time(&self, plant: usize) -> Option> { - todo!() - } - - fn store_last_pump_time(&mut self, plant: usize, time: DateTime) { - todo!() - } - - fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { - todo!() - } - - fn consecutive_pump_count(&mut self, plant: usize) -> u32 { - todo!() - } - - fn fault(&mut self, plant: usize, enable: bool) -> Result<()> { - todo!() - } - - fn low_voltage_in_cycle(&mut self) -> bool { - todo!() - } - - fn any_pump(&mut self, enable: bool) -> Result<()> { - todo!() - } - - fn time(&mut self) -> Result> { - todo!() - } - - fn sntp(&mut self, max_wait_ms: u32) -> Result> { - todo!() - } - - fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { - todo!() - } - - fn general_fault(&mut self, enable: bool) { - todo!() - } - - fn mode_override_pressed(&mut self) -> bool { - todo!() - } - - fn factory_reset(&mut self) -> Result<()> { - todo!() - } - - fn get_rtc_time(&mut self) -> Result> { - todo!() - } - - fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { - todo!() - } - - fn wifi_scan(&mut self) -> Result> { - todo!() - } - - fn test_pump(&mut self, plant: usize) -> Result<()> { - todo!() - } - - fn test(&mut self) -> Result<()> { - todo!() - } - - fn get_restart_to_conf(&mut self) -> bool { - todo!() - } - - fn set_restart_to_conf(&mut self, to_conf: bool) { - todo!() - } -} - -pub struct V4 { - 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>, - signal_counter: PcntDriver<'a>, - charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, - light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - one_wire_bus: OneWire>, - rtc: - Ds323x>>, ds323x::ic::DS3231>, - eeprom: Eeprom24x< - MutexDevice<'a, I2cDriver<'a>>, - eeprom24x::page_size::B32, - eeprom24x::addr_size::TwoBytes, - eeprom24x::unique_serial::No, - >, - general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - pump_expander: Pca9535Immediate>>, - sensor_expander: Pca9535Immediate>>, + pub board_hal: Box>, } #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -612,700 +118,10 @@ impl Default for BackupHeader { } } -impl BoardInteraction for HAL<'_> { - fn set_charge_indicator(&mut self, charging: bool) { - match &mut self.board_hal { - V3 { shift_register, .. } => { - shift_register.decompose()[CHARGING] - .set_state(charging.into()) - .unwrap(); - } - V4 { - charge_indicator, .. - } => { - charge_indicator.set_state(charging.into()).unwrap(); - } - Initial { .. } => {} - } - } - fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { - match &mut self.board_hal { - V3 { shift_register, .. } => { - shift_register.decompose()[AWAKE].set_low().unwrap(); - } - V4 { awake, .. } => { - awake.set_low().unwrap(); - } - Initial { .. } => {} - } - - 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 eeprom = match &mut self.board_hal { - V3 { eeprom, .. } => eeprom, - V4 { eeprom, .. } => eeprom, - &mut Initial { .. } => { - bail!("Board not configured yet") - } - }; - 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 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 eeprom = match &mut self.board_hal { - V3 { eeprom, .. } => eeprom, - V4 { eeprom, .. } => eeprom, - &mut Initial { .. } => { - bail!("Board not configured yet") - } - }; - - 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 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 = eeprom.page_size() as u32; - let mut data_buffer = vec![0_u8; header.size]; - match 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 time = self.get_rtc_time()?.timestamp_millis(); - let eeprom = match &mut self.board_hal { - V3 { eeprom, .. } => eeprom, - V4 { eeprom, .. } => eeprom, - &mut Initial { .. } => { - bail!("Board not configured yet") - } - }; - - let delay = Delay::new_default(); - - let checksum = X25.checksum(bytes); - let page_size = eeprom.page_size(); - - 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 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; - let eeprom = match &mut self.board_hal { - V3 { eeprom, .. } => eeprom, - V4 { eeprom, .. } => eeprom, - &mut Initial { .. } => { - bail!("Board not configured yet") - } - }; - match 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 { - let _ = self.fault(i, iter == i); - } - lastiter = iter; - } - - //update led here? - delay.delay_ms(5); - } - Ok(()) - } - fn is_day(&self) -> bool { - match &self.board_hal { - V3 { solar_is_day, .. } => solar_is_day.get_level().into(), - V4 { solar_is_day, .. } => solar_is_day.get_level().into(), - Initial { .. } => false, - } - } - //should be multsampled - fn water_temperature_c(&mut self) -> Result { - let one_wire_bus = match &mut self.board_hal { - V3 { one_wire_bus, .. } => one_wire_bus, - V4 { one_wire_bus, .. } => one_wire_bus, - &mut Initial { .. } => { - bail!("Board not configured yet") - } - }; - - one_wire_bus - .reset(&mut self.esp.delay) - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - let first = one_wire_bus.devices(false, &mut self.esp.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(one_wire_bus, &mut self.esp.delay) - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.esp.delay); - let sensor_data = water_temp_sensor - .read_data(one_wire_bus, &mut self.esp.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 (tank_power, tank_channel) = match &mut self.board_hal { - V3 { - tank_power, - tank_channel, - .. - } => (tank_power, tank_channel), - V4 { - tank_power, - tank_channel, - .. - } => (tank_power, tank_channel), - &mut Initial { .. } => { - bail!("Board not configured yet") - } - }; - - tank_power.set_high()?; - //let stabilize - self.esp.delay.delay_ms(100); - - let mut store = [0_u16; TANK_MULTI_SAMPLE]; - for multisample in 0..TANK_MULTI_SAMPLE { - let value = tank_channel.read()?; - store[multisample] = value; - } - tank_power.set_low()?; - - store.sort(); - let median_mv = store[6] as f32 / 1000_f32; - Ok(median_mv) - } - fn set_low_voltage_in_cycle(&mut self) { - unsafe { - LOW_VOLTAGE_DETECTED = true; - } - } - fn clear_low_voltage_in_cycle(&mut self) { - unsafe { - LOW_VOLTAGE_DETECTED = false; - } - } - fn light(&mut self, enable: bool) -> Result<()> { - let light = match &mut self.board_hal { - V3 { light, .. } => light, - V4 { light, .. } => light, - &mut Initial { .. } => { - bail!("Board not configured yet") - } - }; - - unsafe { gpio_hold_dis(light.pin()) }; - light.set_state(enable.into())?; - unsafe { gpio_hold_en(light.pin()) }; - Ok(()) - } - fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { - match &mut self.board_hal { - V3 { shift_register, .. } => { - let index = match plant { - 0 => V3Constants::PUMP1_BIT, - 1 => V3Constants::PUMP2_BIT, - 2 => V3Constants::PUMP3_BIT, - 3 => V3Constants::PUMP4_BIT, - 4 => V3Constants::PUMP5_BIT, - 5 => V3Constants::PUMP6_BIT, - 6 => V3Constants::PUMP7_BIT, - 7 => V3Constants::PUMP8_BIT, - _ => bail!("Invalid pump {plant}",), - }; - //currently infallible error, keep for future as result anyway - shift_register.decompose()[index].set_state(enable.into())?; - } - V4 { pump_expander, .. } => { - if enable { - pump_expander.pin_set_high(GPIOBank::Bank0, plant.try_into()?)?; - } else { - pump_expander.pin_set_low(GPIOBank::Bank0, plant.try_into()?)?; - } - } - &mut Initial { .. } => { - bail!("Board not configured yet") - } - } - - Ok(()) - } - fn last_pump_time(&self, plant: usize) -> Option> { - let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant]; - DateTime::from_timestamp_millis(ts) - } - fn store_last_pump_time(&mut self, plant: usize, time: DateTime) { - unsafe { - LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis(); - } - } - fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { - unsafe { - CONSECUTIVE_WATERING_PLANT[plant] = count; - } - } - fn consecutive_pump_count(&mut self, plant: usize) -> u32 { - unsafe { CONSECUTIVE_WATERING_PLANT[plant] } - } - fn fault(&mut self, plant: usize, enable: bool) -> Result<()> { - match &mut self.board_hal { - V3 { shift_register, .. } => { - 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), - }; - shift_register.decompose()[index].set_state(enable.into())?; - } - V4 { pump_expander, .. } => { - if enable { - pump_expander.pin_set_high(GPIOBank::Bank1, plant.try_into()?)?; - } else { - pump_expander.pin_set_low(GPIOBank::Bank1, plant.try_into()?)?; - } - } - &mut Initial { .. } => { - bail!("Board not configured yet") - } - } - Ok(()) - } - fn low_voltage_in_cycle(&mut self) -> bool { - unsafe { LOW_VOLTAGE_DETECTED } - } - fn any_pump(&mut self, enable: bool) -> Result<()> { - match &mut self.board_hal { - V3 { main_pump, .. } => { - main_pump.set_state(enable.into())?; - } - V4 { .. } => { - //does not exist in v4, ignore it - } - &mut Initial { .. } => { - bail!("Board not configured yet") - } - } - Ok(()) - } - fn time(&mut self) -> Result> { - let time = EspSystemTime {}.now().as_millis(); - let smaller_time = time as i64; - let local_time = DateTime::from_timestamp_millis(smaller_time) - .ok_or(anyhow!("could not convert timestamp"))?; - Ok(local_time) - } - fn sntp(&mut self, max_wait_ms: u32) -> Result> { - let sntp = sntp::EspSntp::new_default()?; - let mut counter = 0; - while sntp.get_sync_status() != SyncStatus::Completed { - let delay = Delay::new_default(); - delay.delay_ms(100); - counter += 100; - if counter > max_wait_ms { - bail!("Reached sntp timeout, aborting") - } - } - - self.time() - } - fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { - match &mut self.board_hal { - V3 { - signal_counter, - shift_register, - .. - } => { - let mut results = [0_f32; REPEAT_MOIST_MEASURE]; - for repeat in 0..REPEAT_MOIST_MEASURE { - signal_counter.counter_pause()?; - signal_counter.counter_clear()?; - //Disable all - shift_register.decompose()[V3Constants::MS_4].set_high()?; - - let sensor_channel = match sensor { - Sensor::A => match plant { - 0 => V3Constants::SENSOR_A_1, - 1 => V3Constants::SENSOR_A_2, - 2 => V3Constants::SENSOR_A_3, - 3 => V3Constants::SENSOR_A_4, - 4 => V3Constants::SENSOR_A_5, - 5 => V3Constants::SENSOR_A_6, - 6 => V3Constants::SENSOR_A_7, - 7 => V3Constants::SENSOR_A_8, - _ => bail!("Invalid plant id {}", plant), - }, - Sensor::B => match plant { - 0 => V3Constants::SENSOR_B_1, - 1 => V3Constants::SENSOR_B_2, - 2 => V3Constants::SENSOR_B_3, - 3 => V3Constants::SENSOR_B_4, - 4 => V3Constants::SENSOR_B_5, - 5 => V3Constants::SENSOR_B_6, - 6 => V3Constants::SENSOR_B_7, - 7 => V3Constants::SENSOR_B_8, - _ => bail!("Invalid plant id {}", plant), - }, - }; - - let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; - let pin_0 = &mut shift_register.decompose()[V3Constants::MS_0]; - let pin_1 = &mut shift_register.decompose()[V3Constants::MS_1]; - let pin_2 = &mut shift_register.decompose()[V3Constants::MS_2]; - let pin_3 = &mut shift_register.decompose()[V3Constants::MS_3]; - if is_bit_set(0) { - pin_0.set_high()?; - } else { - pin_0.set_low()?; - } - if is_bit_set(1) { - pin_1.set_high()?; - } else { - pin_1.set_low()?; - } - if is_bit_set(2) { - pin_2.set_high()?; - } else { - pin_2.set_low()?; - } - if is_bit_set(3) { - pin_3.set_high()?; - } else { - pin_3.set_low()?; - } - - shift_register.decompose()[V3Constants::MS_4].set_low()?; - shift_register.decompose()[V3Constants::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); - signal_counter.counter_resume()?; - delay.delay_ms(measurement); - signal_counter.counter_pause()?; - shift_register.decompose()[V3Constants::MS_4].set_high()?; - shift_register.decompose()[V3Constants::SENSOR_ON].set_low()?; - delay.delay_ms(10); - let unscaled = 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; - let median = results[mid]; - Ok(median) - } - V4 { - sensor_expander, - signal_counter, - .. - } => { - let mut results = [0_f32; REPEAT_MOIST_MEASURE]; - for repeat in 0..REPEAT_MOIST_MEASURE { - signal_counter.counter_pause()?; - signal_counter.counter_clear()?; - - const MS0: u8 = 1_u8; - const MS1: u8 = 0_u8; - const MS2: u8 = 3_u8; - const MS3: u8 = 4_u8; - const MS4: u8 = 2_u8; - const SENSOR_ON: u8 = 5_u8; - //Disable all - sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; - - let sensor_channel = match sensor { - Sensor::A => plant as u32, - Sensor::B => (15 - plant) as u32, - }; - - let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; - if is_bit_set(0) { - sensor_expander.pin_set_high(GPIOBank::Bank0, MS0)?; - } else { - sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?; - } - if is_bit_set(1) { - sensor_expander.pin_set_high(GPIOBank::Bank0, MS1)?; - } else { - sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?; - } - if is_bit_set(2) { - sensor_expander.pin_set_high(GPIOBank::Bank0, MS2)?; - } else { - sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?; - } - if is_bit_set(3) { - sensor_expander.pin_set_high(GPIOBank::Bank0, MS3)?; - } else { - sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?; - } - - sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?; - sensor_expander.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?; - - 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); - signal_counter.counter_resume()?; - delay.delay_ms(measurement); - signal_counter.counter_pause()?; - sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; - sensor_expander.pin_set_low(GPIOBank::Bank0, SENSOR_ON)?; - sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?; - sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?; - sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?; - sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?; - delay.delay_ms(10); - let unscaled = 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; - let median = results[mid]; - Ok(median) - } - _ => { - bail!("Not implemented for this board") - } - } - } - fn general_fault(&mut self, enable: bool) { - let general_fault = match &mut self.board_hal { - V3 { general_fault, .. } => general_fault, - V4 { general_fault, .. } => general_fault, - Initial { general_fault, .. } => general_fault, - }; - unsafe { gpio_hold_dis(general_fault.pin()) }; - general_fault.set_state(enable.into()).unwrap(); - unsafe { gpio_hold_en(general_fault.pin()) }; - } - - fn mode_override_pressed(&mut self) -> bool { - self.esp.boot_button.get_level() == Level::Low - } - fn factory_reset(&mut self) -> Result<()> { - println!("factory resetting"); - self.esp.delete_config()?; - //destroy backup header - let dummy: [u8; 0] = []; - self.backup_config(&dummy)?; - - Ok(()) - } - fn get_rtc_time(&mut self) -> Result> { - let rtc = match &mut self.board_hal { - V3 { rtc, .. } => rtc, - V4 { rtc, .. } => rtc, - Initial { .. } => { - bail!("Board not configured yet") - } - }; - match 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 rtc = match &mut self.board_hal { - V3 { rtc, .. } => rtc, - V4 { rtc, .. } => rtc, - Initial { .. } => { - bail!("Board not configured yet") - } - }; - let naive_time = time.naive_utc(); - match rtc.set_datetime(&naive_time) { - OkStd(_) => Ok(()), - Err(err) => { - bail!("Error getting rtc time {:?}", err) - } - } - } - fn wifi_scan(&mut self) -> Result> { - self.esp.wifi_driver.start_scan( - &ScanConfig { - scan_type: ScanType::Passive(Duration::from_secs(5)), - show_hidden: false, - ..Default::default() - }, - true, - )?; - Ok(self.esp.wifi_driver.get_scan_result()?) - } - 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(()) - } -} - -pub trait BoardInteraction { +pub trait BoardInteraction<'a> { + fn get_esp(&mut self) -> &mut ESP<'a>; + fn get_config(&mut self) -> &PlantControllerConfig; + fn get_battery_monitor(&mut self) -> &mut Box; fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; fn deep_sleep(&mut self, duration_in_ms: u64) -> !; fn get_backup_info(&mut self) -> Result; @@ -1316,30 +132,16 @@ pub trait BoardInteraction { fn water_temperature_c(&mut self) -> Result; /// return median tank sensor value in milli volt fn tank_sensor_voltage(&mut self) -> Result; - fn set_low_voltage_in_cycle(&mut self); - fn clear_low_voltage_in_cycle(&mut self); fn light(&mut self, enable: bool) -> Result<()>; fn pump(&mut self, plant: usize, enable: bool) -> Result<()>; - fn last_pump_time(&self, plant: usize) -> Option>; - fn store_last_pump_time(&mut self, plant: usize, time: DateTime); - fn store_consecutive_pump_count(&mut self, plant: usize, count: u32); - fn consecutive_pump_count(&mut self, plant: usize) -> u32; fn fault(&mut self, plant: usize, enable: bool) -> Result<()>; - fn low_voltage_in_cycle(&mut self) -> bool; - fn any_pump(&mut self, enable: bool) -> Result<()>; - fn time(&mut self) -> Result>; - fn sntp(&mut self, max_wait_ms: u32) -> 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 wifi_scan(&mut self) -> 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); } pub struct FreePeripherals { @@ -1478,26 +280,12 @@ impl PlantHal { esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode); let fs_mount_error = esp.mount_file_system().is_err(); - let config = esp.get_config(); + let config = esp.load_config(); + + + let hal = match config { Result::Ok(config) => { - let board_hal: BoardHal = match config.hardware.board { - BoardVersion::INITIAL => { - let mut general_fault = - PinDriver::input_output(free_pins.gpio6.downgrade())?; - general_fault.set_pull(Pull::Floating)?; - general_fault.set_low()?; - - if fs_mount_error { - general_fault.set_high()? - } - - Initial { general_fault } - } - BoardVersion::V3 => PlantHal::create_v3(free_pins)?, - BoardVersion::V4 => PlantHal::create_v4(free_pins)?, - }; - let battery_monitor: BatteryMonitor = match config.hardware.battery { BatteryBoardVersion::Disabled => BatteryMonitor::Disabled {}, BatteryBoardVersion::BQ34Z100G1 => { @@ -1523,12 +311,20 @@ impl PlantHal { } BatteryBoardVersion::WchI2cSlave => BatteryMonitor::WchI2cSlave {}, }; + let battery_interaction = Box::new(battery_monitor) as Box; + + + let board_hal: Box = match config.hardware.board { + BoardVersion::INITIAL => { + Self::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 { - config, - board_hal, - esp, - battery_monitor, + board_hal } } Err(err) => { @@ -1539,16 +335,8 @@ impl PlantHal { "", &err.to_string(), ); - - let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; - general_fault.set_pull(Pull::Floating)?; - general_fault.set_low()?; - HAL { - config: Default::default(), - board_hal: Initial { general_fault }, - esp, - battery_monitor: BatteryMonitor::Disabled {}, + board_hal:Self::create_initial_board(free_pins, fs_mount_error, PlantControllerConfig::default(), esp )? } } }; @@ -1556,261 +344,22 @@ impl PlantHal { Ok(Mutex::new(hal)) } - fn create_v4(peripherals: FreePeripherals) -> Result> { - let mut awake = PinDriver::output(peripherals.gpio15.downgrade())?; - awake.set_high()?; - - let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?; + fn create_initial_board(free_pins: FreePeripherals, fs_mount_error: bool, config: PlantControllerConfig, esp: ESP) -> Result>> { + let mut general_fault = + PinDriver::input_output(free_pins.gpio6.downgrade())?; general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; - 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), - ) - }; - - let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?; - one_wire_pin.set_pull(Pull::Floating)?; - - let one_wire_bus = OneWire::new(one_wire_pin) - .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - - 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); - } + if fs_mount_error { + general_fault.set_high()? } - match eeprom.read_byte(0) { - OkStd(byte) => { - println!("Read first byte with status {}", byte); - } - Err(err) => { - println!("Eeprom could not read first byte {:?}", err); - } - } - - let mut signal_counter = PcntDriver::new( - peripherals.pcnt0, - Some(peripherals.gpio22), - Option::::None, - Option::::None, - Option::::None, - )?; - - signal_counter.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.gpio5, &adc_config)?; - - let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?; - solar_is_day.set_pull(Pull::Floating)?; - - let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?; - light.set_pull(Pull::Floating)?; - - let mut tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?; - tank_power.set_pull(Pull::Floating)?; - - let mut charge_indicator = PinDriver::input_output(peripherals.gpio3.downgrade())?; - charge_indicator.set_pull(Pull::Floating)?; - charge_indicator.set_low()?; - - let mut pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32); - - //todo error handing if init error - for pin in 0..8 { - let _ = pump_expander.pin_into_output(GPIOBank::Bank0, pin); - let _ = pump_expander.pin_into_output(GPIOBank::Bank1, pin); - let _ = pump_expander.pin_set_low(GPIOBank::Bank0, pin); - let _ = pump_expander.pin_set_low(GPIOBank::Bank1, pin); - } - - let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34); - for pin in 0..8 { - let _ = sensor_expander.pin_into_output(GPIOBank::Bank0, pin); - let _ = sensor_expander.pin_into_output(GPIOBank::Bank1, pin); - let _ = sensor_expander.pin_set_low(GPIOBank::Bank0, pin); - let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin); - } - - Ok(V4 { - awake, - tank_channel, - solar_is_day, - signal_counter, - light, - tank_power, - one_wire_bus, - rtc, - eeprom, + let v = Initial { general_fault, - pump_expander, - sensor_expander, - charge_indicator, - }) + config, + esp + }; + Ok(Box::new(v)) } - fn create_v3(peripherals: FreePeripherals) -> Result> { - let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?; - clock.set_pull(Pull::Floating)?; - let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?; - latch.set_pull(Pull::Floating)?; - let mut data = PinDriver::input_output(peripherals.gpio23.downgrade())?; - data.set_pull(Pull::Floating)?; - let shift_register = ShiftRegister40::new(clock, latch, data); - //disable all - for mut pin in shift_register.decompose() { - pin.set_low()?; - } - let awake = &mut shift_register.decompose()[AWAKE]; - awake.set_high()?; - - let charging = &mut shift_register.decompose()[CHARGING]; - charging.set_high()?; - - let ms0 = &mut shift_register.decompose()[V3Constants::MS_0]; - ms0.set_low()?; - let ms1 = &mut shift_register.decompose()[V3Constants::MS_1]; - ms1.set_low()?; - let ms2 = &mut shift_register.decompose()[V3Constants::MS_2]; - ms2.set_low()?; - let ms3 = &mut shift_register.decompose()[V3Constants::MS_3]; - ms3.set_low()?; - - let ms4 = &mut shift_register.decompose()[V3Constants::MS_4]; - ms4.set_high()?; - - println!("Init battery driver"); - - 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), - ) - }; - - let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?; - 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); - } - } - - let mut signal_counter = PcntDriver::new( - peripherals.pcnt0, - Some(peripherals.gpio22), - Option::::None, - Option::::None, - Option::::None, - )?; - - signal_counter.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.gpio5, &adc_config)?; - - let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?; - solar_is_day.set_pull(Pull::Floating)?; - - let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?; - light.set_pull(Pull::Floating)?; - - let mut main_pump = PinDriver::input_output(peripherals.gpio2.downgrade())?; - main_pump.set_pull(Pull::Floating)?; - main_pump.set_low()?; - let mut tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?; - tank_power.set_pull(Pull::Floating)?; - let mut general_fault = PinDriver::input_output(peripherals.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 mut shift_register_enable_invert = PinDriver::output(peripherals.gpio21.downgrade())?; - - unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) }; - shift_register_enable_invert.set_low()?; - unsafe { gpio_hold_en(shift_register_enable_invert.pin()) }; - - Ok(BoardHal::V3 { - shift_register, - shift_register_enable_invert, - tank_channel, - solar_is_day, - light, - main_pump, - tank_power, - general_fault, - signal_counter, - one_wire_bus, - rtc, - eeprom, - }) - } } diff --git a/rust/src/hal/v3_hal.rs b/rust/src/hal/v3_hal.rs new file mode 100644 index 0000000..f4756c7 --- /dev/null +++ b/rust/src/hal/v3_hal.rs @@ -0,0 +1,621 @@ +use crate::hal::esp::ESP; +use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, V3Constants, I2C_DRIVER, PLANT_COUNT, REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25}; +use crate::log::{log, LogMessage}; +use anyhow::{anyhow, bail, Ok, Result}; +use chrono::{DateTime, Utc}; +use ds18b20::Ds18b20; +use ds323x::{DateTimeAccess, Ds323x}; +use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr}; +use embedded_hal::digital::OutputPin; +use embedded_hal_bus::i2c::MutexDevice; +use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; +use esp_idf_hal::delay::Delay; +use esp_idf_hal::gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, PinDriver, Pull}; +use esp_idf_hal::i2c::I2cDriver; +use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex}; +use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; +use one_wire_bus::OneWire; +use plant_ctrl2::sipo::ShiftRegister40; +use std::result::Result::Ok as OkStd; +use esp_idf_hal::adc::{attenuation, Resolution}; +use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; +use crate::config::PlantControllerConfig; +use crate::hal::battery::{BatteryInteraction, BatteryMonitor}; + +const PUMP8_BIT: usize = 0; +const PUMP1_BIT: usize = 1; +const PUMP2_BIT: usize = 2; +const PUMP3_BIT: usize = 3; +const PUMP4_BIT: usize = 4; +const PUMP5_BIT: usize = 5; +const PUMP6_BIT: usize = 6; +const PUMP7_BIT: usize = 7; +const MS_0: usize = 8; +const MS_4: usize = 9; +const MS_2: usize = 10; +const MS_3: usize = 11; +const MS_1: usize = 13; +const SENSOR_ON: usize = 12; + +const SENSOR_A_1: u8 = 7; +const SENSOR_A_2: u8 = 6; +const SENSOR_A_3: u8 = 5; +const SENSOR_A_4: u8 = 4; +const SENSOR_A_5: u8 = 3; +const SENSOR_A_6: u8 = 2; +const SENSOR_A_7: u8 = 1; +const SENSOR_A_8: u8 = 0; + +const SENSOR_B_1: u8 = 8; +const SENSOR_B_2: u8 = 9; +const SENSOR_B_3: u8 = 10; +const SENSOR_B_4: u8 = 11; +const SENSOR_B_5: u8 = 12; +const SENSOR_B_6: u8 = 13; +const SENSOR_B_7: u8 = 14; +const SENSOR_B_8: u8 = 15; + +const CHARGING: usize = 14; +const AWAKE: usize = 15; + +const FAULT_3: usize = 16; +const FAULT_8: usize = 17; +const FAULT_7: usize = 18; +const FAULT_6: usize = 19; +const FAULT_5: usize = 20; +const FAULT_4: usize = 21; +const FAULT_1: usize = 22; +const FAULT_2: usize = 23; + +pub struct V3<'a> { + config: PlantControllerConfig, + battery_monitor: Box, + esp: ESP<'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>, + 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>, + 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>, + signal_counter: PcntDriver<'a>, + one_wire_bus: OneWire>, + rtc: + Ds323x>>, ds323x::ic::DS3231>, + eeprom: Eeprom24x< + MutexDevice<'a, I2cDriver<'a>>, + eeprom24x::page_size::B32, + eeprom24x::addr_size::TwoBytes, + eeprom24x::unique_serial::No, + >, +} + +pub(crate) fn create_v3(peripherals: FreePeripherals, esp: ESP, config: PlantControllerConfig, battery_monitor: Box) -> Result> { + let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?; + clock.set_pull(Pull::Floating)?; + let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?; + latch.set_pull(Pull::Floating)?; + let mut data = PinDriver::input_output(peripherals.gpio23.downgrade())?; + data.set_pull(Pull::Floating)?; + let shift_register = ShiftRegister40::new(clock, latch, data); + //disable all + for mut pin in shift_register.decompose() { + pin.set_low()?; + } + + let awake = &mut shift_register.decompose()[AWAKE]; + awake.set_high()?; + + let charging = &mut shift_register.decompose()[CHARGING]; + charging.set_high()?; + + let ms0 = &mut shift_register.decompose()[MS_0]; + ms0.set_low()?; + let ms1 = &mut shift_register.decompose()[MS_1]; + ms1.set_low()?; + let ms2 = &mut shift_register.decompose()[MS_2]; + ms2.set_low()?; + let ms3 = &mut shift_register.decompose()[MS_3]; + ms3.set_low()?; + + let ms4 = &mut shift_register.decompose()[MS_4]; + ms4.set_high()?; + + println!("Init battery driver"); + + 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), + ) + }; + + let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?; + 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); + } + } + + let mut signal_counter = PcntDriver::new( + peripherals.pcnt0, + Some(peripherals.gpio22), + Option::::None, + Option::::None, + Option::::None, + )?; + + signal_counter.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.gpio5, &adc_config)?; + + let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?; + solar_is_day.set_pull(Pull::Floating)?; + + let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?; + light.set_pull(Pull::Floating)?; + + let mut main_pump = PinDriver::input_output(peripherals.gpio2.downgrade())?; + main_pump.set_pull(Pull::Floating)?; + main_pump.set_low()?; + let mut tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?; + tank_power.set_pull(Pull::Floating)?; + let mut general_fault = PinDriver::input_output(peripherals.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 mut shift_register_enable_invert = PinDriver::output(peripherals.gpio21.downgrade())?; + + unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) }; + shift_register_enable_invert.set_low()?; + unsafe { gpio_hold_en(shift_register_enable_invert.pin()) }; + + Ok(Box::new(V3 { + config, + battery_monitor, + esp, + shift_register, + shift_register_enable_invert, + tank_channel, + solar_is_day, + light, + main_pump, + tank_power, + general_fault, + signal_counter, + one_wire_bus, + rtc, + eeprom, + })) +} + +impl BoardInteraction<'_> for V3<'_> { + fn get_esp(&mut self) -> &mut ESP<'static> { + &mut self.esp + } + + fn get_config(&mut self) -> &PlantControllerConfig { + &self.config + } + + fn get_battery_monitor(&mut self) -> &mut Box<(dyn BatteryInteraction + 'static)> { + &mut self.battery_monitor + } + + fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { + Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?) + } + + fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { + let _ = self.shift_register.decompose()[AWAKE].set_low(); + deep_sleep(duration_in_ms) + } + + fn get_backup_info(&mut self) -> Result { + let store = bincode::serialize(&BackupHeader::default())?.len(); + let mut header_page_buffer = vec![0_u8; store]; + + self.eeprom + .read_data(0, &mut header_page_buffer) + .map_err(|err| anyhow!("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 store = bincode::serialize(&BackupHeader::default())?.len(); + let mut header_page_buffer = vec![0_u8; store]; + + self.eeprom + .read_data(0, &mut header_page_buffer) + .map_err(|err| anyhow!("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]; + self.eeprom + .read_data(data_start_address, &mut data_buffer) + .map_err(|err| anyhow!("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 time = self.get_rtc_time()?.timestamp_millis(); + + let delay = Delay::new_default(); + + let checksum = X25.checksum(bytes); + let page_size = self.eeprom.page_size(); + + 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; + self.eeprom + .write_page(address, chunk) + .map_err(|err| anyhow!("Error writing eeprom {:?}", err))?; + current_page += 1; + + let iter = (current_page % 8) as usize; + if iter != lastiter { + for i in 0..PLANT_COUNT { + let _ = self.fault(i, iter == i); + } + lastiter = iter; + } + + delay.delay_ms(5); + } + Ok(()) + } + + fn is_day(&self) -> bool { + self.solar_is_day.get_level().into() + } + + fn water_temperature_c(&mut self) -> Result { + self.one_wire_bus + .reset(&mut self.esp.delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + let first = self.one_wire_bus.devices(false, &mut self.esp.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 self.esp.delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.esp.delay); + let sensor_data = water_temp_sensor + .read_data(&mut self.one_wire_bus, &mut self.esp.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) + } + + fn tank_sensor_voltage(&mut self) -> Result { + self.tank_power.set_high()?; + //let stabilize + self.esp.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(&mut self, plant: usize, enable: bool) -> Result<()> { + if enable { + self.main_pump.set_high()?; + } + + 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())?; + + if !enable { + self.main_pump.set_low()?; + } + Ok(()) + } + + fn fault(&mut self, plant: usize, enable: bool) -> Result<()> { + 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())?; + Ok(()) + } + + fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { + 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()?; + + 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 is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; + let pin_0 = &mut self.shift_register.decompose()[MS_0]; + let pin_1 = &mut self.shift_register.decompose()[MS_1]; + let pin_2 = &mut self.shift_register.decompose()[MS_2]; + let pin_3 = &mut self.shift_register.decompose()[MS_3]; + if is_bit_set(0) { + pin_0.set_high()?; + } else { + pin_0.set_low()?; + } + if is_bit_set(1) { + pin_1.set_high()?; + } else { + pin_1.set_low()?; + } + if is_bit_set(2) { + pin_2.set_high()?; + } else { + pin_2.set_low()?; + } + if is_bit_set(3) { + pin_3.set_high()?; + } else { + pin_3.set_low()?; + } + + self.shift_register.decompose()[MS_4].set_low()?; + self.shift_register.decompose()[SENSOR_ON].set_high()?; + + let measurement = 100; // TODO what is this scaling factor? what is its purpose? + let factor = 1000f32 / measurement as f32; + + //give some time to stabilize + self.esp.delay.delay_ms(10); + self.signal_counter.counter_resume()?; + self.esp.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()?; + self.esp.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; + let median = results[mid]; + Ok(median) + } + + 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 factory_reset(&mut self) -> Result<()> { + println!("factory resetting"); + self.esp.delete_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.pump(plant, true)?; + unsafe { vTaskDelay(30000) }; + self.pump(plant, false)?; + Ok(()) + } + + fn test(&mut self) -> Result<()> { + self.general_fault(true); + self.esp.delay.delay_ms(100); + self.general_fault(false); + self.esp.delay.delay_ms(100); + self.light(true)?; + self.esp.delay.delay_ms(500); + self.light(false)?; + self.esp.delay.delay_ms(500); + for i in 0..PLANT_COUNT { + self.fault(i, true)?; + self.esp.delay.delay_ms(500); + self.fault(i, false)?; + self.esp.delay.delay_ms(500); + } + for i in 0..PLANT_COUNT { + self.pump(i, true)?; + self.esp.delay.delay_ms(100); + self.pump(i, false)?; + self.esp.delay.delay_ms(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(), ""); + } + self.esp.delay.delay_ms(10); + Ok(()) + } +} \ No newline at end of file diff --git a/rust/src/hal/v4_hal.rs b/rust/src/hal/v4_hal.rs new file mode 100644 index 0000000..8c3bac2 --- /dev/null +++ b/rust/src/hal/v4_hal.rs @@ -0,0 +1,530 @@ +use anyhow::{anyhow, bail}; +use chrono::{DateTime, Utc}; +use ds18b20::Ds18b20; +use ds323x::{DateTimeAccess, Ds323x}; +use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr}; +use embedded_hal::digital::OutputPin; +use embedded_hal_bus::i2c::MutexDevice; +use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; +use esp_idf_hal::delay::Delay; +use esp_idf_hal::gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, Output, PinDriver, Pull}; +use esp_idf_hal::i2c::I2cDriver; +use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex}; +use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; +use one_wire_bus::OneWire; +use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; +use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25}; +use crate::hal::esp::ESP; +use std::result::Result::Ok as OkStd; +use esp_idf_hal::adc::{attenuation, Resolution}; +use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; +use crate::config::PlantControllerConfig; +use crate::hal::battery::{BatteryInteraction, BatteryMonitor}; +use crate::log::{log, LogMessage}; + +const MS0: u8 = 1_u8; +const MS1: u8 = 0_u8; +const MS2: u8 = 3_u8; +const MS3: u8 = 4_u8; +const MS4: u8 = 2_u8; +const SENSOR_ON: u8 = 5_u8; + +pub struct V4<'a> { + esp: ESP<'a>, + battery_monitor: Box, + config: PlantControllerConfig, + 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>, + signal_counter: PcntDriver<'a>, + charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, + light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + one_wire_bus: OneWire>, + rtc: + Ds323x>>, ds323x::ic::DS3231>, + eeprom: Eeprom24x< + MutexDevice<'a, I2cDriver<'a>>, + eeprom24x::page_size::B32, + eeprom24x::addr_size::TwoBytes, + eeprom24x::unique_serial::No, + >, + general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + pump_expander: Pca9535Immediate>>, + sensor_expander: Pca9535Immediate>>, +} + + +pub(crate) fn create_v4(peripherals: FreePeripherals, esp:ESP, config:PlantControllerConfig, battery_monitor: Box) -> anyhow::Result> { + let mut awake = PinDriver::output(peripherals.gpio15.downgrade())?; + awake.set_high()?; + + let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?; + general_fault.set_pull(Pull::Floating)?; + general_fault.set_low()?; + + 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), + ) + }; + + let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?; + one_wire_pin.set_pull(Pull::Floating)?; + + let one_wire_bus = OneWire::new(one_wire_pin) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + + 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); + } + } + + let mut signal_counter = PcntDriver::new( + peripherals.pcnt0, + Some(peripherals.gpio22), + Option::::None, + Option::::None, + Option::::None, + )?; + + signal_counter.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.gpio5, &adc_config)?; + + let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?; + solar_is_day.set_pull(Pull::Floating)?; + + let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?; + light.set_pull(Pull::Floating)?; + + let mut tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?; + tank_power.set_pull(Pull::Floating)?; + + let mut charge_indicator = PinDriver::input_output(peripherals.gpio3.downgrade())?; + charge_indicator.set_pull(Pull::Floating)?; + charge_indicator.set_low()?; + + let mut pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32); + + //todo error handing if init error + for pin in 0..8 { + let _ = pump_expander.pin_into_output(GPIOBank::Bank0, pin); + let _ = pump_expander.pin_into_output(GPIOBank::Bank1, pin); + let _ = pump_expander.pin_set_low(GPIOBank::Bank0, pin); + let _ = pump_expander.pin_set_low(GPIOBank::Bank1, pin); + } + + let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34); + for pin in 0..8 { + let _ = sensor_expander.pin_into_output(GPIOBank::Bank0, pin); + let _ = sensor_expander.pin_into_output(GPIOBank::Bank1, pin); + let _ = sensor_expander.pin_set_low(GPIOBank::Bank0, pin); + let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin); + } + + let v = V4 { + esp, + awake, + tank_channel, + solar_is_day, + signal_counter, + light, + tank_power, + one_wire_bus, + rtc, + eeprom, + general_fault, + pump_expander, + sensor_expander, + charge_indicator, + config, + battery_monitor, + }; + Ok(Box::new(v)) +} + +impl BoardInteraction<'_> for V4<'_> { + fn get_esp(&mut self) -> &mut ESP<'_> { + &mut self.esp + } + + fn get_config(&mut self) -> &PlantControllerConfig { + &self.config + } + + fn get_battery_monitor(&mut self) -> &mut Box { + &mut self.battery_monitor + } + + fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> { + self.charge_indicator.set_state(charging.into()).expect("cannot fail"); + Ok(()) + } + + fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { + self.awake.set_low().unwrap(); + deep_sleep(duration_in_ms); + } + + fn get_backup_info(&mut self) -> anyhow::Result { + let store = bincode::serialize(&BackupHeader::default())?.len(); + let mut header_page_buffer = vec![0_u8; store]; + + self.eeprom + .read_data(0, &mut header_page_buffer) + .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; + + println!("Raw header is {:?} with size {}", header_page_buffer, store); + let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; + anyhow::Ok(header) + } + + fn get_backup_config(&mut self) -> anyhow::Result> { + let store = bincode::serialize(&BackupHeader::default())?.len(); + let mut header_page_buffer = vec![0_u8; store]; + + self.eeprom + .read_data(0, &mut header_page_buffer) + .map_err(|err| anyhow!("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]; + self.eeprom + .read_data(data_start_address, &mut data_buffer) + .map_err(|err| anyhow!("Error reading eeprom data {:?}", err))?; + + let checksum = X25.checksum(&data_buffer); + if checksum != header.crc16 { + bail!( + "Invalid checksum, got {} but expected {}", + checksum, + header.crc16 + ); + } + + anyhow::Ok(data_buffer) + } + + fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()> { + let time = self.get_rtc_time()?.timestamp_millis(); + + let delay = Delay::new_default(); + + let checksum = X25.checksum(bytes); + let page_size = self.eeprom.page_size(); + + 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; + self.eeprom + .write_page(address, chunk) + .map_err(|err| anyhow!("Error writing eeprom {:?}", err))?; + current_page += 1; + + let iter = (current_page % 8) as usize; + if iter != lastiter { + for i in 0..PLANT_COUNT { + let _ = self.fault(i, iter == i); + } + lastiter = iter; + } + + delay.delay_ms(5); + } + anyhow::Ok(()) + } + + fn is_day(&self) -> bool { + self.solar_is_day.get_level().into() + } + + fn water_temperature_c(&mut self) -> anyhow::Result { + self.one_wire_bus + .reset(&mut self.esp.delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + let first = self.one_wire_bus.devices(false, &mut self.esp.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 self.esp.delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.esp.delay); + let sensor_data = water_temp_sensor + .read_data(&mut self.one_wire_bus, &mut self.esp.delay) + .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; + if sensor_data.temperature == 85_f32 { + bail!("Ds18b20 dummy temperature returned"); + } + anyhow::Ok(sensor_data.temperature / 10_f32) + } + + fn tank_sensor_voltage(&mut self) -> anyhow::Result { + self.tank_power.set_high()?; + //let stabilize + self.esp.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; + anyhow::Ok(median_mv) + } + + fn light(&mut self, enable: bool) -> anyhow::Result<()> { + unsafe { gpio_hold_dis(self.light.pin()) }; + self.light.set_state(enable.into())?; + unsafe { gpio_hold_en(self.light.pin()) }; + anyhow::Ok(()) + } + + fn pump(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> { + if enable { + self.pump_expander.pin_set_high(GPIOBank::Bank0, plant.try_into()?)?; + } else { + self.pump_expander.pin_set_low(GPIOBank::Bank0, plant.try_into()?)?; + } + anyhow::Ok(()) + } + + fn fault(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> { + if enable { + self.pump_expander.pin_set_high(GPIOBank::Bank1, plant.try_into()?)? + } else { + self.pump_expander.pin_set_low(GPIOBank::Bank1, plant.try_into()?)? + } + anyhow::Ok(()) + } + + fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result { + 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.sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; + + let sensor_channel = match sensor { + Sensor::A => plant as u32, + Sensor::B => (15 - plant) as u32, + }; + + let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; + if is_bit_set(0) { + self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS0)?; + } else { + self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?; + } + if is_bit_set(1) { + self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS1)?; + } else { + self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?; + } + if is_bit_set(2) { + self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS2)?; + } else { + self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?; + } + if is_bit_set(3) { + self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS3)?; + } else { + self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?; + } + + self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?; + self.sensor_expander.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?; + + 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.sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; + self.sensor_expander.pin_set_low(GPIOBank::Bank0, SENSOR_ON)?; + self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?; + self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?; + self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?; + self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?; + 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; + let median = results[mid]; + anyhow::Ok(median) + } + + 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 factory_reset(&mut self) -> anyhow::Result<()> { + println!("factory resetting"); + self.esp.delete_config()?; + //destroy backup header + let dummy: [u8; 0] = []; + self.backup_config(&dummy)?; + + anyhow::Ok(()) + } + + fn get_rtc_time(&mut self) -> anyhow::Result> { + match self.rtc.datetime() { + OkStd(rtc_time) => anyhow::Ok(rtc_time.and_utc()), + Err(err) => { + bail!("Error getting rtc time {:?}", err) + } + } + } + + fn set_rtc_time(&mut self, time: &DateTime) -> anyhow::Result<()> { + let naive_time = time.naive_utc(); + match self.rtc.set_datetime(&naive_time) { + OkStd(_) => anyhow::Ok(()), + Err(err) => { + bail!("Error getting rtc time {:?}", err) + } + } + } + + fn test_pump(&mut self, plant: usize) -> anyhow::Result<()> { + self.pump(plant, true)?; + self.esp.delay.delay_ms(30000); + self.pump(plant, false)?; + anyhow::Ok(()) + } + + fn test(&mut self) -> anyhow::Result<()> { + self.general_fault(true); + self.esp.delay.delay_ms(100); + self.general_fault(false); + self.esp.delay.delay_ms(500); + self.light(true)?; + self.esp.delay.delay_ms(500); + self.light(false)?; + self.esp.delay.delay_ms(500); + for i in 0..PLANT_COUNT { + self.fault(i, true)?; + self.esp.delay.delay_ms(500); + self.fault(i, false)?; + self.esp.delay.delay_ms(500); + } + for i in 0..PLANT_COUNT { + self.pump(i, true)?; + self.esp.delay.delay_ms(100); + self.pump(i, false)?; + self.esp.delay.delay_ms(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(), ""); + } + self.esp.delay.delay_ms(10); + anyhow::Ok(()) + } +} diff --git a/rust/src/main.rs b/rust/src/main.rs index b739965..8012341 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -28,9 +28,9 @@ mod tank; use plant_state::PlantState; use tank::*; +use crate::config::BoardVersion::INITIAL; use crate::hal::{BoardInteraction, PlantHal, HAL, PLANT_COUNT}; use crate::hal::battery::BatteryInteraction; -use crate::hal::BoardHal::Initial; pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create().unwrap()); pub static STAY_ALIVE: Lazy = Lazy::new(|| AtomicBool::new(false)); @@ -151,14 +151,14 @@ fn safe_main() -> anyhow::Result<()> { log(LogMessage::PartitionState, 0, 0, "", ota_state_string); let mut board = BOARD_ACCESS.lock().expect("Could not lock board no other lock should be able to exist during startup!"); - board.general_fault(false); + board.board_hal.general_fault(false); - let cur = board + let cur = board.board_hal .get_rtc_time() .or_else(|err| { println!("rtc module error: {:?}", err); - board.general_fault(true); - board.time() + board.board_hal.general_fault(true); + board.board_hal.get_esp().time() }) .map_err(|err| -> Result<(), _> { bail!("time error {}", err); @@ -172,57 +172,52 @@ fn safe_main() -> anyhow::Result<()> { } println!("cur is {}", cur); - match board.battery_monitor.average_current_milli_ampere() { + match board.board_hal.get_battery_monitor().average_current_milli_ampere() { Ok(charging) => { - board.set_charge_indicator(charging > 20) + let _ = board.board_hal.set_charge_indicator(charging > 20); } Err(_) => {} } - if board.get_restart_to_conf() { + if board.board_hal.get_esp().get_restart_to_conf() { log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", ""); for _i in 0..2 { - board.general_fault(true); + board.board_hal.general_fault(true); Delay::new_default().delay_ms(100); - board.general_fault(false); + board.board_hal.general_fault(false); Delay::new_default().delay_ms(100); } to_config = true; - board.general_fault(true); - board.set_restart_to_conf(false); - } else if board.mode_override_pressed() { - board.general_fault(true); + board.board_hal.general_fault(true); + board.board_hal.get_esp().set_restart_to_conf(false); + } else if board.board_hal.get_esp().mode_override_pressed() { + board.board_hal.general_fault(true); log(LogMessage::ConfigModeButtonOverride, 0, 0, "", ""); for _i in 0..5 { - board.general_fault(true); + board.board_hal.general_fault(true); Delay::new_default().delay_ms(100); - board.general_fault(false); + board.board_hal.general_fault(false); Delay::new_default().delay_ms(100); } - if board.mode_override_pressed() { - board.general_fault(true); + if board.board_hal.get_esp().mode_override_pressed() { + board.board_hal.general_fault(true); to_config = true; } else { - board.general_fault(false); + board.board_hal.general_fault(false); } } - match board.board_hal { - Initial { .. } => { - //config upload will trigger reboot and then switch to selected board_hal - let _ = board.esp.wifi_ap(); - drop(board); - let reboot_now = Arc::new(AtomicBool::new(false)); - let _webserver = httpd(reboot_now.clone()); - wait_infinity(WaitType::MissingConfig, reboot_now.clone()); - } - _ => {} + if board.board_hal.get_config().hardware.board == INITIAL { + let _ = board.board_hal.get_esp().wifi_ap(); + drop(board); + let reboot_now = Arc::new(AtomicBool::new(false)); + let _webserver = httpd(reboot_now.clone()); + wait_infinity(WaitType::MissingConfig, reboot_now.clone()); } - println!("attempting to connect wifi"); - let network_mode = if board.config.network.ssid.is_some() { + let network_mode = if board.board_hal.get_config().network.ssid.is_some() { try_connect_wifi_sntp_mqtt(&mut board) } else { println!("No wifi configured"); @@ -231,7 +226,7 @@ fn safe_main() -> anyhow::Result<()> { if matches!(network_mode, NetworkMode::OFFLINE) && to_config { println!("Could not connect to station and config mode forced, switching to ap mode!"); - match board.esp.wifi_ap() { + match board.board_hal.get_esp().wifi_ap() { Ok(_) => { println!("Started ap, continuing") } @@ -239,7 +234,7 @@ fn safe_main() -> anyhow::Result<()> { } } - let timezone = match & board.config.timezone { + let timezone = match & board.board_hal.get_config().timezone { Some(tz_str) => tz_str.parse::().unwrap_or_else(|_| { println!("Invalid timezone '{}', falling back to UTC", tz_str); UTC @@ -286,7 +281,7 @@ fn safe_main() -> anyhow::Result<()> { let tank_state = determine_tank_state(&mut board); if tank_state.is_enabled() { - if let Some(err) = tank_state.got_error(&board.config.tank) { + if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) { match err { TankError::SensorDisabled => { /* unreachable */ } TankError::SensorMissing(raw_value_mv) => log( @@ -308,10 +303,10 @@ fn safe_main() -> anyhow::Result<()> { } } // disabled cannot trigger this because of wrapping if is_enabled - board.general_fault(true); - } else if tank_state.warn_level(&board.config.tank).is_ok_and(|warn| warn) { + board.board_hal.general_fault(true); + } else if tank_state.warn_level(&board.board_hal.get_config().tank).is_ok_and(|warn| warn) { log(LogMessage::TankWaterLevelLow, 0, 0, "", ""); - board.general_fault(true); + board.board_hal.general_fault(true); } } @@ -332,18 +327,15 @@ fn safe_main() -> anyhow::Result<()> { let pump_required = plantstate .iter() - .zip(&board.config.plants) + .zip(&board.board_hal.get_config().plants) .any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time)) && !water_frozen; if pump_required { log(LogMessage::EnableMain, dry_run as u32, 0, "", ""); - if !dry_run { - board.any_pump(true)?; // enables main power output, eg for a central pump with valve setup or a main water valve for the risk affine - } - for (plant_id, (state, plant_config)) in plantstate.iter().zip(&board.config.plants.clone()).enumerate() { + for (plant_id, (state, plant_config)) in plantstate.iter().zip(&board.board_hal.get_config().plants.clone()).enumerate() { if state.needs_to_be_watered(plant_config, &timezone_time) { - let pump_count = board.consecutive_pump_count(plant_id) + 1; - board.store_consecutive_pump_count(plant_id, pump_count); + let pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id) + 1; + board.board_hal.get_esp().store_consecutive_pump_count(plant_id, pump_count); let pump_ineffective = pump_count > plant_config.max_consecutive_pump_count as u32; if pump_ineffective { @@ -354,7 +346,7 @@ fn safe_main() -> anyhow::Result<()> { &(plant_id+1).to_string(), "", ); - board.fault(plant_id, true)?; + board.board_hal.fault(plant_id, true)?; } log( LogMessage::PumpPlant, @@ -363,71 +355,68 @@ fn safe_main() -> anyhow::Result<()> { &dry_run.to_string(), "", ); - board.store_last_pump_time(plant_id, cur); - board.last_pump_time(plant_id); + board.board_hal.get_esp().store_last_pump_time(plant_id, cur); + board.board_hal.get_esp().last_pump_time(plant_id); //state.active = true; pump_info(&mut board, plant_id, true, pump_ineffective); if !dry_run { - board.pump(plant_id, true)?; + board.board_hal.pump(plant_id, true)?; Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32); - board.pump(plant_id, false)?; + board.board_hal.pump(plant_id, false)?; } pump_info(&mut board, plant_id, false, pump_ineffective); } else if !state.pump_in_timeout(plant_config, &timezone_time) { // plant does not need to be watered and is not in timeout // -> reset consecutive pump count - board.store_consecutive_pump_count(plant_id, 0); + board.board_hal.get_esp().store_consecutive_pump_count(plant_id, 0); } } - if !dry_run { - board.any_pump(false)?; // disable main power output, eg for a central pump with valve setup or a main water valve for the risk affine - } } - let is_day = board.is_day(); - let state_of_charge = board.battery_monitor.state_charge_percent().unwrap_or(0); + let is_day = board.board_hal.is_day(); + let state_of_charge = board.board_hal.get_battery_monitor().state_charge_percent().unwrap_or(0); let mut light_state = LightState { - enabled: board.config.night_lamp.enabled, + enabled: board.board_hal.get_config().night_lamp.enabled, ..Default::default() }; if light_state.enabled { light_state.is_day = is_day; light_state.out_of_work_hour = !in_time_range( &timezone_time, - board.config.night_lamp.night_lamp_hour_start, - board.config.night_lamp.night_lamp_hour_end, + board.board_hal.get_config().night_lamp.night_lamp_hour_start, + board.board_hal.get_config().night_lamp.night_lamp_hour_end, ); - if state_of_charge < board.config.night_lamp.low_soc_cutoff { - board.set_low_voltage_in_cycle(); - } else if state_of_charge > board.config.night_lamp.low_soc_restore { - board.clear_low_voltage_in_cycle(); + if state_of_charge < board.board_hal.get_config().night_lamp.low_soc_cutoff { + board.board_hal.get_esp().set_low_voltage_in_cycle(); + } else if state_of_charge > board.board_hal.get_config().night_lamp.low_soc_restore { + board.board_hal.get_esp().clear_low_voltage_in_cycle(); } - light_state.battery_low = board.low_voltage_in_cycle(); + light_state.battery_low = board.board_hal.get_esp().low_voltage_in_cycle(); if !light_state.out_of_work_hour { - if board.config.night_lamp.night_lamp_only_when_dark { + if board.board_hal.get_config().night_lamp.night_lamp_only_when_dark { if !light_state.is_day { if light_state.battery_low { - board.light(false)?; + board.board_hal.light(false)?; } else { light_state.active = true; - board.light(true)?; + board.board_hal.light(true)?; } } } else if light_state.battery_low { - board.light(false)?; + board.board_hal.light(false)?; } else { light_state.active = true; - board.light(true)?; + board.board_hal.light(true)?; } } else { light_state.active = false; - board.light(false)?; + board.board_hal.light(false)?; } println!("Lightstate is {:?}", light_state); @@ -435,7 +424,7 @@ fn safe_main() -> anyhow::Result<()> { match serde_json::to_string(&light_state) { Ok(state) => { - let _ = board.esp.mqtt_publish( "/light", state.as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish( "/light", state.as_bytes()); } Err(err) => { println!("Error publishing lightstate {}", err); @@ -443,16 +432,16 @@ fn safe_main() -> anyhow::Result<()> { }; let deep_sleep_duration_minutes: u32 = if state_of_charge < 10 { - let _ = board.esp.mqtt_publish( "/deepsleep", "low Volt 12h".as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish( "/deepsleep", "low Volt 12h".as_bytes()); 12 * 60 } else if is_day { - let _ = board.esp.mqtt_publish( "/deepsleep", "normal 20m".as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish( "/deepsleep", "normal 20m".as_bytes()); 20 } else { - let _ = board.esp.mqtt_publish( "/deepsleep", "night 1h".as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish( "/deepsleep", "night 1h".as_bytes()); 60 }; - let _ = board.esp.mqtt_publish( "/state", "sleep".as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish( "/state", "sleep".as_bytes()); //determine next event //is light out of work trigger soon? @@ -472,15 +461,15 @@ fn safe_main() -> anyhow::Result<()> { let _webserver = httpd(reboot_now.clone()); wait_infinity(WaitType::MqttConfig, reboot_now.clone()); } - board.set_restart_to_conf(false); - board.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64); + board.board_hal.get_esp().set_restart_to_conf(false); + board.board_hal.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64); } 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 { - let temp = board.water_temperature_c(); + let temp = board.board_hal.water_temperature_c(); match &temp { Ok(res) => { println!("Water temp is {}", res); @@ -499,9 +488,9 @@ fn obtain_tank_temperature(board: &mut MutexGuard) -> anyhow::Result { } fn publish_tank_state(board: &mut MutexGuard, tank_state: &TankState, water_temp: &anyhow::Result) { - match serde_json::to_string(&tank_state.as_mqtt_info(&board.config.tank, water_temp)) { + match serde_json::to_string(&tank_state.as_mqtt_info(&board.board_hal.get_config().tank, water_temp)) { Ok(state) => { - let _ = board.esp.mqtt_publish("/water", state.as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish("/water", state.as_bytes()); } Err(err) => { println!("Error publishing tankstate {}", err); @@ -510,13 +499,13 @@ fn publish_tank_state(board: &mut MutexGuard, tank_state: &TankState, water } fn publish_plant_states(board: &mut MutexGuard, timezone_time: &DateTime, plantstate: &[PlantState; 8]) { - for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(& board.config.plants.clone()).enumerate() { + for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(& board.board_hal.get_config().plants.clone()).enumerate() { match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, &timezone_time)) { Ok(state) => { let plant_topic = format!("/plant{}", plant_id + 1); - let _ = board.esp.mqtt_publish(&plant_topic, state.as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish(&plant_topic, state.as_bytes()); //reduce speed as else messages will be dropped - board.esp.delay.delay_ms(200); + board.board_hal.get_esp().delay.delay_ms(200); } Err(err) => { println!("Error publishing plant state {}", err); @@ -526,43 +515,43 @@ fn publish_plant_states(board: &mut MutexGuard, timezone_time: &DateTime, ip_address: &String, timezone_time: DateTime) { - let _ = board.esp.mqtt_publish("/firmware/address", ip_address.as_bytes()); - let _ = board.esp.mqtt_publish( "/firmware/githash", version.git_hash.as_bytes()); - let _ = board.esp.mqtt_publish( + let _ = board.board_hal.get_esp().mqtt_publish("/firmware/address", ip_address.as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish( "/firmware/githash", version.git_hash.as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish( "/firmware/buildtime", version.build_time.as_bytes(), ); - let _ = board.esp.mqtt_publish( + let _ = board.board_hal.get_esp().mqtt_publish( "/firmware/last_online", timezone_time.to_rfc3339().as_bytes(), ); - let _ = board.esp.mqtt_publish( "/firmware/ota_state", ota_state_string.as_bytes()); - let _ = board.esp.mqtt_publish( + let _ = board.board_hal.get_esp().mqtt_publish( "/firmware/ota_state", ota_state_string.as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish( "/firmware/partition_address", format!("{:#06x}", address).as_bytes(), ); - let _ = board.esp.mqtt_publish( "/state", "online".as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish( "/state", "online".as_bytes()); } fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard) -> NetworkMode{ - let nw_conf = &board.config.network.clone(); - match board.esp.wifi(nw_conf) { + let nw_conf = &board.board_hal.get_config().network.clone(); + match board.board_hal.get_esp().wifi(nw_conf) { Ok(ip_info) => { - let sntp_mode: SntpMode = match board.sntp(1000 * 10) { + let sntp_mode: SntpMode = match board.board_hal.get_esp().sntp(1000 * 10) { Ok(new_time) => { println!("Using time from sntp"); - let _ = board.set_rtc_time(&new_time); + let _ = board.board_hal.set_rtc_time(&new_time); SntpMode::SYNC {current: new_time} } Err(err) => { println!("sntp error: {}", err); - board.general_fault(true); + board.board_hal.general_fault(true); SntpMode::OFFLINE } }; - let mqtt_connected = if let Some(_) = board.config.network.mqtt_url { - let nw_config = &board.config.network.clone(); - match board.esp.mqtt(nw_config) { + let mqtt_connected = if let Some(_) = board.board_hal.get_config().network.mqtt_url { + let nw_config = &board.board_hal.get_config().network.clone(); + match board.board_hal.get_esp().mqtt(nw_config) { Ok(_) => { println!("Mqtt connection ready"); true @@ -583,13 +572,12 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard) -> NetworkMode{ } Err(_) => { println!("Offline mode"); - board.general_fault(true); + board.board_hal.general_fault(true); NetworkMode::OFFLINE } } } -//TODO clean this up? better state fn pump_info(board: &mut MutexGuard, plant_id: usize, pump_active: bool, pump_ineffective: bool) { let pump_info = PumpInfo { enabled: pump_active, @@ -598,7 +586,7 @@ fn pump_info(board: &mut MutexGuard, plant_id: usize, pump_active: bool, pu let pump_topic = format!("/pump{}", plant_id + 1); match serde_json::to_string(&pump_info) { Ok(state) => { - let _ = board.esp.mqtt_publish(&pump_topic, state.as_bytes()); + let _ = board.board_hal.get_esp().mqtt_publish(&pump_topic, state.as_bytes()); //reduce speed as else messages will be dropped Delay::new_default().delay_ms(200); } @@ -611,8 +599,8 @@ fn pump_info(board: &mut MutexGuard, plant_id: usize, pump_active: bool, pu fn publish_battery_state( board: &mut MutexGuard<'_, HAL<'_>> ) { - let state = board.battery_monitor.get_battery_state(); - let _ = board.esp.mqtt_publish( "/battery", state.as_bytes()); + let state = board.board_hal.get_battery_monitor().get_battery_state(); + let _ = board.board_hal.get_esp().mqtt_publish( "/battery", state.as_bytes()); } fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { @@ -622,9 +610,9 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { loop { unsafe { - let mut lock = BOARD_ACCESS.lock().unwrap(); - if let Ok(charging) = lock.battery_monitor.average_current_milli_ampere() { - lock.set_charge_indicator(charging > 20) + let mut board = BOARD_ACCESS.lock().unwrap(); + if let Ok(charging) = board.board_hal.get_battery_monitor().average_current_milli_ampere() { + let _ = board.board_hal.set_charge_indicator(charging > 20); } match wait_type { WaitType::MissingConfig => { @@ -632,36 +620,36 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { led_count %= 8; led_count += 1; for i in 0..8 { - let _ = lock.fault(i, i < led_count); + let _ = board.board_hal.fault(i, i < led_count); } } WaitType::ConfigButton => { // Alternating pattern: 1010 1010 -> 0101 0101 pattern_step = (pattern_step + 1) % 2; for i in 0..8 { - let _ = lock.fault(i, (i + pattern_step) % 2 == 0); + let _ = board.board_hal.fault(i, (i + pattern_step) % 2 == 0); } } WaitType::MqttConfig => { // Moving dot pattern pattern_step = (pattern_step + 1) % 8; for i in 0..8 { - let _ = lock.fault(i, i == pattern_step); + let _ = board.board_hal.fault(i, i == pattern_step); } } } - lock.general_fault(true); - drop(lock); + board.board_hal.general_fault(true); + drop(board); vTaskDelay(delay); - let mut lock = BOARD_ACCESS.lock().unwrap(); - lock.general_fault(false); + let mut board = BOARD_ACCESS.lock().unwrap(); + board.board_hal.general_fault(false); // Clear all LEDs for i in 0..8 { - let _ = lock.fault(i, false); + let _ = board.board_hal.fault(i, false); } - drop(lock); + drop(board); vTaskDelay(delay); if wait_type == WaitType::MqttConfig @@ -672,7 +660,7 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { if reboot_now.load(std::sync::atomic::Ordering::Relaxed) { //ensure clean http answer Delay::new_default().delay_ms(500); - BOARD_ACCESS.lock().unwrap().deep_sleep(1); + BOARD_ACCESS.lock().unwrap().board_hal.deep_sleep(1); } } } @@ -685,8 +673,8 @@ fn main() { // timeout, this is just a fallback Ok(_) => { println!("Main app finished, restarting"); - BOARD_ACCESS.lock().unwrap().set_restart_to_conf(false); - BOARD_ACCESS.lock().unwrap().deep_sleep(1); + BOARD_ACCESS.lock().unwrap().board_hal.get_esp().set_restart_to_conf(false); + BOARD_ACCESS.lock().unwrap().board_hal.deep_sleep(1); } // if safe_main exists with an error, rollback to a known good ota version Err(err) => { diff --git a/rust/src/plant_state.rs b/rust/src/plant_state.rs index 3e6f569..2495fb9 100644 --- a/rust/src/plant_state.rs +++ b/rust/src/plant_state.rs @@ -116,12 +116,12 @@ impl PlantState { plant_id: usize, board: &mut HAL ) -> Self { - let sensor_a = if board.config.plants[plant_id].sensor_a { - match board.measure_moisture_hz(plant_id, Sensor::A) { + let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a { + match board.board_hal.measure_moisture_hz(plant_id, Sensor::A) { Ok(raw) => match map_range_moisture( raw, - board.config.plants[plant_id].moisture_sensor_min_frequency, - board.config.plants[plant_id].moisture_sensor_max_frequency, + board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency, + board.board_hal.get_config().plants[plant_id].moisture_sensor_max_frequency, ) { Ok(moisture_percent) => MoistureSensorState::MoistureValue { raw_hz: raw, @@ -137,12 +137,12 @@ impl PlantState { MoistureSensorState::Disabled }; - let sensor_b = if board.config.plants[plant_id].sensor_b { - match board.measure_moisture_hz(plant_id, Sensor::B) { + let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b { + match board.board_hal.measure_moisture_hz(plant_id, Sensor::B) { Ok(raw) => match map_range_moisture( raw, - board.config.plants[plant_id].moisture_sensor_min_frequency, - board.config.plants[plant_id].moisture_sensor_max_frequency, + board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency, + board.board_hal.get_config().plants[plant_id].moisture_sensor_max_frequency, ) { Ok(moisture_percent) => MoistureSensorState::MoistureValue { raw_hz: raw, @@ -158,8 +158,8 @@ impl PlantState { MoistureSensorState::Disabled }; - let previous_pump = board.last_pump_time(plant_id); - let consecutive_pump_count = board.consecutive_pump_count(plant_id); + let previous_pump = board.board_hal.get_esp().last_pump_time(plant_id); + let consecutive_pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id); let state = Self { sensor_a, sensor_b, @@ -169,7 +169,7 @@ impl PlantState { }, }; if state.is_err() { - let _ = board.fault(plant_id, true); + let _ = board.board_hal.fault(plant_id, true); } state } diff --git a/rust/src/tank.rs b/rust/src/tank.rs index c6a2e60..9f3aec9 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -158,8 +158,8 @@ impl TankState { pub fn determine_tank_state( board: &mut std::sync::MutexGuard<'_, HAL<'_>> ) -> TankState { - if board.config.tank.tank_sensor_enabled { - match board.tank_sensor_voltage() { + if board.board_hal.get_config().tank.tank_sensor_enabled { + match board.board_hal.tank_sensor_voltage() { Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv), Err(err) => TankState::Error(TankError::BoardError(err.to_string())), } diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index f89fe61..f4bb27c 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -68,26 +68,26 @@ fn write_time( let actual_data = read_up_to_bytes_from_request(request, None)?; let time: SetTime = serde_json::from_slice(&actual_data)?; let parsed = DateTime::parse_from_rfc3339(time.time).map_err(|err| anyhow::anyhow!(err))?; - let mut board = BOARD_ACCESS.lock().unwrap(); + let mut board = BOARD_ACCESS.lock().expect("board access"); let now = timeval { tv_sec: parsed.to_utc().timestamp(), tv_usec: 0, }; unsafe { settimeofday(&now, core::ptr::null_mut()) }; - board.set_rtc_time(&parsed.to_utc())?; + board.board_hal.set_rtc_time(&parsed.to_utc())?; anyhow::Ok(None) } fn get_time( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let native = board + let mut board = BOARD_ACCESS.lock().expect("board access"); + let native = board.board_hal.get_esp() .time() .map(|t| t.to_rfc3339()) .unwrap_or("error".to_string()); - let rtc = board + let rtc = board.board_hal .get_rtc_time() .map(|t| t.to_rfc3339()) .unwrap_or("error".to_string()); @@ -158,8 +158,8 @@ fn get_live_moisture( fn get_config( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let board = BOARD_ACCESS.lock().expect("Should never fail"); - let json = serde_json::to_string(&board.config)?; + let mut board = BOARD_ACCESS.lock().expect("Should never fail"); + let json = serde_json::to_string(&board.board_hal.get_config())?; anyhow::Ok(Some(json)) } @@ -167,16 +167,16 @@ fn backup_config( request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let all = read_up_to_bytes_from_request(request, Some(3072))?; - let mut board = BOARD_ACCESS.lock().unwrap(); - board.backup_config(&all)?; + let mut board = BOARD_ACCESS.lock().expect("board access"); + board.board_hal.backup_config(&all)?; anyhow::Ok(Some("saved".to_owned())) } fn get_backup_config( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let json = match board.get_backup_config() { + let mut board = BOARD_ACCESS.lock().expect("board access"); + let json = match board.board_hal.get_backup_config() { Ok(config) => from_utf8(&config)?.to_owned(), Err(err) => { println!("Error get backup config {:?}", err); @@ -190,7 +190,7 @@ fn backup_info( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let mut board = BOARD_ACCESS.lock().expect("Should never fail"); - let header = board.get_backup_info(); + let header = board.board_hal.get_backup_info(); let json = match header { Ok(h) => { let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap(); @@ -218,18 +218,18 @@ fn set_config( let all = read_up_to_bytes_from_request(request, Some(3072))?; let config: PlantControllerConfig = serde_json::from_slice(&all)?; - let mut board = BOARD_ACCESS.lock().unwrap(); - board.esp.set_config(&config)?; + let mut board = BOARD_ACCESS.lock().expect("board access"); + board.board_hal.get_esp().save_config(&config)?; - board.config = config; +//TODO fixme board.config = config; anyhow::Ok(Some("saved".to_owned())) } fn get_battery_state( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let battery_state = board.battery_monitor.get_battery_state(); + let mut board = BOARD_ACCESS.lock().expect("board access"); + let battery_state = board.board_hal.get_battery_monitor().get_battery_state(); let battery_json = serde_json::to_string(&battery_state)?; anyhow::Ok(Some(battery_json)) } @@ -259,7 +259,7 @@ fn pump_test( let actual_data = read_up_to_bytes_from_request(request, None)?; let pump_test: TestPump = serde_json::from_slice(&actual_data)?; let mut board = BOARD_ACCESS.lock().unwrap(); - board.test_pump(pump_test.pump)?; + board.board_hal.test_pump(pump_test.pump)?; anyhow::Ok(None) } @@ -269,9 +269,9 @@ fn tank_info( let mut board = BOARD_ACCESS.lock().unwrap(); let tank_info = determine_tank_state(&mut board); //should be multsampled - let water_temp = board.water_temperature_c(); + let water_temp = board.board_hal.water_temperature_c(); Ok(Some(serde_json::to_string( - &tank_info.as_mqtt_info(&board.config.tank, &water_temp), + &tank_info.as_mqtt_info(&board.board_hal.get_config().tank, &water_temp), )?)) } @@ -281,7 +281,7 @@ fn night_lamp_test( let actual_data = read_up_to_bytes_from_request(request, None)?; let light_command: NightLampCommand = serde_json::from_slice(&actual_data)?; let mut board = BOARD_ACCESS.lock().unwrap(); - board.light(light_command.active)?; + board.board_hal.light(light_command.active)?; anyhow::Ok(None) } @@ -289,7 +289,7 @@ fn wifi_scan( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let mut board = BOARD_ACCESS.lock().unwrap(); - let scan_result = board.wifi_scan()?; + let scan_result = board.board_hal.get_esp().wifi_scan()?; let mut ssids: Vec<&String<32>> = Vec::new(); scan_result.iter().for_each(|s| ssids.push(&s.ssid)); let ssid_json = serde_json::to_string(&SSIDList { ssids })?; @@ -300,8 +300,8 @@ fn wifi_scan( fn list_files( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let board = BOARD_ACCESS.lock().expect("It should be possible to lock the board for exclusive fs access"); - let result = board.esp.list_files(); + let mut board = BOARD_ACCESS.lock().expect("It should be possible to lock the board for exclusive fs access"); + let result = board.board_hal.get_esp().list_files(); let file_list_json = serde_json::to_string(&result)?; anyhow::Ok(Some(file_list_json)) } @@ -323,12 +323,12 @@ fn ota( total_read += read; let to_write = &buffer[0..read]; //delay for watchdog and wifi stuff - board.esp.delay.delay_ms(1); + board.board_hal.get_esp().delay.delay_ms(1); let iter = (total_read / 1024) % 8; if iter != lastiter { for i in 0..PLANT_COUNT { - let _ = board.fault(i, iter == i); + let _ = board.board_hal.fault(i, iter == i); } lastiter = iter; } @@ -345,7 +345,7 @@ fn ota( let mut finalizer = ota.finalize()?; println!("changing boot partition"); - board.set_restart_to_conf(true); + board.board_hal.get_esp().set_restart_to_conf(true); drop(board); finalizer.set_as_boot_partition()?; anyhow::Ok(None) @@ -420,7 +420,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { .unwrap(); server .fn_handler("/boardtest", Method::Post, move |_| { - BOARD_ACCESS.lock().unwrap().test() + BOARD_ACCESS.lock().unwrap().board_hal.test() }) .unwrap(); server @@ -472,7 +472,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { let reboot_now_for_reboot = reboot_now.clone(); server .fn_handler("/reboot", Method::Post, move |_| { - BOARD_ACCESS.lock().unwrap().set_restart_to_conf(true); + BOARD_ACCESS.lock().unwrap().board_hal.get_esp().set_restart_to_conf(true); reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed); anyhow::Ok(()) }) @@ -493,7 +493,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { let file_handle = BOARD_ACCESS .lock() .unwrap() - .esp + .board_hal.get_esp() .get_file_handle(&filename, false); match file_handle { Ok(mut file_handle) => { @@ -529,8 +529,8 @@ pub fn httpd(reboot_now: Arc) -> Box> { server .fn_handler("/file", Method::Post, move |mut request| { let filename = query_param(request.uri(), "filename").unwrap(); - let mut lock = BOARD_ACCESS.lock().unwrap(); - let file_handle = lock.esp.get_file_handle(&filename, true); + let mut board = BOARD_ACCESS.lock().unwrap(); + let file_handle = board.board_hal.get_esp().get_file_handle(&filename, true); match file_handle { //TODO get free filesystem size, check against during write if not to large Ok(mut file_handle) => { @@ -542,7 +542,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { let iter = (total_read / 1024) % 8; if iter != lastiter { for i in 0..PLANT_COUNT { - let _ = lock.fault(i, iter == i); + let _ = board.board_hal.fault(i, iter == i); } lastiter = iter; } @@ -564,7 +564,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { cors_response(request, 500, &error_text)?; } } - drop(lock); + drop(board); anyhow::Ok(()) }) .unwrap(); @@ -573,8 +573,8 @@ pub fn httpd(reboot_now: Arc) -> Box> { .fn_handler("/file", Method::Delete, move |request| { let filename = query_param(request.uri(), "filename").unwrap(); let copy = filename.clone(); - let board = BOARD_ACCESS.lock().unwrap(); - match board.esp.delete_file(&filename) { + let mut board = BOARD_ACCESS.lock().unwrap(); + match board.board_hal.get_esp().delete_file(&filename) { Ok(_) => { let info = format!("Deleted file {copy}"); cors_response(request, 200, &info)?; From e2cbf9618e18e7e3b9fc6e8b4c34bf349f133a03 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Thu, 19 Jun 2025 18:17:29 +0200 Subject: [PATCH 3/5] fixed lifetime annotations + thread safty still needs work --- rust/src/hal/initial_hal.rs | 45 ++++++++++++++++++++-------- rust/src/hal/mod.rs | 59 ++++++++++++------------------------- rust/src/hal/v3_hal.rs | 31 ++++++++++++------- rust/src/hal/v4_hal.rs | 56 ++++++++++++++++++++++------------- rust/src/main.rs | 2 +- 5 files changed, 110 insertions(+), 83 deletions(-) diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs index bd7bc6b..d054f10 100644 --- a/rust/src/hal/initial_hal.rs +++ b/rust/src/hal/initial_hal.rs @@ -1,20 +1,44 @@ -use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, Sensor}; +use crate::config::{BoardHardware, PlantControllerConfig}; +use crate::hal::battery::{BatteryInteraction, BatteryMonitor}; +use crate::hal::esp::ESP; +use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, Sensor, FreePeripherals}; +use esp_idf_hal::gpio::{IOPin, Pull}; use anyhow::{bail, Result}; use chrono::{DateTime, Utc}; use embedded_hal::digital::OutputPin; use esp_idf_hal::gpio::{InputOutput, PinDriver}; -use crate::config::{BoardHardware, PlantControllerConfig}; -use crate::hal::battery::{BatteryInteraction, BatteryMonitor}; -use crate::hal::esp::ESP; pub struct Initial<'a> { pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, pub(crate) esp: ESP<'a>, pub(crate) config: PlantControllerConfig, + pub(crate) battery: Box, } -impl BoardInteraction<'_> for Initial<'_> { - fn get_esp<'a>(&mut self) -> &mut ESP<'a> { +pub(crate) fn create_initial_board( + free_pins: FreePeripherals, + fs_mount_error: bool, + config: PlantControllerConfig, + esp: ESP<'static>, +) -> Result> + Send + Sync> { + let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; + general_fault.set_pull(Pull::Floating)?; + general_fault.set_low()?; + + if fs_mount_error { + general_fault.set_high()? + } + let v = Initial { + general_fault, + config, + esp, + battery: Box::new(BatteryMonitor::Disabled {}), + }; + Ok(Box::new(v)) +} + +impl<'a> BoardInteraction<'a> for Initial<'a> { + fn get_esp(&mut self) -> &mut ESP<'a> { &mut self.esp } @@ -22,11 +46,8 @@ impl BoardInteraction<'_> for Initial<'_> { &self.config } - fn get_battery_monitor(&mut self) -> Box { - let v = BatteryMonitor::Disabled { - - }; - Box::new(v) as Box + fn get_battery_monitor(&mut self) -> &mut Box { + &mut self.battery } fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { @@ -96,4 +117,4 @@ impl BoardInteraction<'_> for Initial<'_> { fn test(&mut self) -> Result<()> { bail!("Please configure board revision") } -} \ No newline at end of file +} diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index 5f17bb8..79ca9e0 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -42,10 +42,9 @@ 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, + 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; @@ -63,11 +62,7 @@ pub static I2C_DRIVER: Lazy>> = Lazy::new(PlantHal::cre #[non_exhaustive] struct V3Constants; -impl V3Constants { - -} - - +impl V3Constants {} const X25: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); @@ -98,7 +93,7 @@ pub enum Sensor { pub struct PlantHal {} pub struct HAL<'a> { - pub board_hal: Box>, + pub board_hal: Box + Sync + Send>, } #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -282,8 +277,6 @@ impl PlantHal { let config = esp.load_config(); - - let hal = match config { Result::Ok(config) => { let battery_monitor: BatteryMonitor = match config.hardware.battery { @@ -313,19 +306,19 @@ impl PlantHal { }; let battery_interaction = Box::new(battery_monitor) as Box; - let board_hal: Box = match config.hardware.board { BoardVersion::INITIAL => { - Self::create_initial_board(free_pins, fs_mount_error, config, esp)? + 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)? } - 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 - } + HAL { board_hal } } Err(err) => { log( @@ -336,30 +329,16 @@ impl PlantHal { &err.to_string(), ); HAL { - board_hal:Self::create_initial_board(free_pins, fs_mount_error, PlantControllerConfig::default(), esp )? + board_hal: initial_hal::create_initial_board( + free_pins, + fs_mount_error, + PlantControllerConfig::default(), + esp, + )?, } } }; Ok(Mutex::new(hal)) } - - fn create_initial_board(free_pins: FreePeripherals, fs_mount_error: bool, config: PlantControllerConfig, esp: ESP) -> Result>> { - let mut general_fault = - PinDriver::input_output(free_pins.gpio6.downgrade())?; - general_fault.set_pull(Pull::Floating)?; - general_fault.set_low()?; - - if fs_mount_error { - general_fault.set_high()? - } - let v = Initial { - general_fault, - config, - esp - }; - Ok(Box::new(v)) - } - - } diff --git a/rust/src/hal/v3_hal.rs b/rust/src/hal/v3_hal.rs index f4756c7..e8ee742 100644 --- a/rust/src/hal/v3_hal.rs +++ b/rust/src/hal/v3_hal.rs @@ -1,5 +1,10 @@ +use crate::config::PlantControllerConfig; +use crate::hal::battery::{BatteryInteraction, BatteryMonitor}; use crate::hal::esp::ESP; -use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, V3Constants, I2C_DRIVER, PLANT_COUNT, REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25}; +use crate::hal::{ + deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, V3Constants, I2C_DRIVER, + PLANT_COUNT, REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25, +}; use crate::log::{log, LogMessage}; use anyhow::{anyhow, bail, Ok, Result}; use chrono::{DateTime, Utc}; @@ -8,19 +13,20 @@ use ds323x::{DateTimeAccess, Ds323x}; use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr}; use embedded_hal::digital::OutputPin; use embedded_hal_bus::i2c::MutexDevice; +use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; +use esp_idf_hal::adc::{attenuation, Resolution}; use esp_idf_hal::delay::Delay; use esp_idf_hal::gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, PinDriver, Pull}; use esp_idf_hal::i2c::I2cDriver; -use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex}; +use esp_idf_hal::pcnt::{ + PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, +}; use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; use one_wire_bus::OneWire; use plant_ctrl2::sipo::ShiftRegister40; use std::result::Result::Ok as OkStd; -use esp_idf_hal::adc::{attenuation, Resolution}; -use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; -use crate::config::PlantControllerConfig; -use crate::hal::battery::{BatteryInteraction, BatteryMonitor}; +use std::sync::Arc; const PUMP8_BIT: usize = 0; const PUMP1_BIT: usize = 1; @@ -96,7 +102,12 @@ pub struct V3<'a> { >, } -pub(crate) fn create_v3(peripherals: FreePeripherals, esp: ESP, config: PlantControllerConfig, battery_monitor: Box) -> Result> { +pub(crate) fn create_v3( + peripherals: FreePeripherals, + esp: ESP<'static>, + config: PlantControllerConfig, + battery_monitor: Box, +) -> Result> { let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?; clock.set_pull(Pull::Floating)?; let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?; @@ -235,8 +246,8 @@ pub(crate) fn create_v3(peripherals: FreePeripherals, esp: ESP, config: PlantCon })) } -impl BoardInteraction<'_> for V3<'_> { - fn get_esp(&mut self) -> &mut ESP<'static> { +impl<'a> BoardInteraction<'a> for V3<'a> { + fn get_esp(&mut self) -> &mut ESP<'a> { &mut self.esp } @@ -618,4 +629,4 @@ impl BoardInteraction<'_> for V3<'_> { self.esp.delay.delay_ms(10); Ok(()) } -} \ No newline at end of file +} diff --git a/rust/src/hal/v4_hal.rs b/rust/src/hal/v4_hal.rs index 8c3bac2..ddb1419 100644 --- a/rust/src/hal/v4_hal.rs +++ b/rust/src/hal/v4_hal.rs @@ -1,3 +1,11 @@ +use crate::config::PlantControllerConfig; +use crate::hal::battery::{BatteryInteraction, BatteryMonitor}; +use crate::hal::esp::ESP; +use crate::hal::{ + deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, + REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25, +}; +use crate::log::{log, LogMessage}; use anyhow::{anyhow, bail}; use chrono::{DateTime, Utc}; use ds18b20::Ds18b20; @@ -5,22 +13,19 @@ use ds323x::{DateTimeAccess, Ds323x}; use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr}; use embedded_hal::digital::OutputPin; use embedded_hal_bus::i2c::MutexDevice; +use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; +use esp_idf_hal::adc::{attenuation, Resolution}; use esp_idf_hal::delay::Delay; use esp_idf_hal::gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, Output, PinDriver, Pull}; use esp_idf_hal::i2c::I2cDriver; -use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex}; +use esp_idf_hal::pcnt::{ + PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, +}; use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; use one_wire_bus::OneWire; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; -use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25}; -use crate::hal::esp::ESP; use std::result::Result::Ok as OkStd; -use esp_idf_hal::adc::{attenuation, Resolution}; -use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; -use crate::config::PlantControllerConfig; -use crate::hal::battery::{BatteryInteraction, BatteryMonitor}; -use crate::log::{log, LogMessage}; const MS0: u8 = 1_u8; const MS1: u8 = 0_u8; @@ -54,8 +59,12 @@ pub struct V4<'a> { sensor_expander: Pca9535Immediate>>, } - -pub(crate) fn create_v4(peripherals: FreePeripherals, esp:ESP, config:PlantControllerConfig, battery_monitor: Box) -> anyhow::Result> { +pub(crate) fn create_v4( + peripherals: FreePeripherals, + esp: ESP<'static>, + config: PlantControllerConfig, + battery_monitor: Box, +) -> anyhow::Result> { let mut awake = PinDriver::output(peripherals.gpio15.downgrade())?; awake.set_high()?; @@ -181,8 +190,8 @@ pub(crate) fn create_v4(peripherals: FreePeripherals, esp:ESP, config:PlantContr Ok(Box::new(v)) } -impl BoardInteraction<'_> for V4<'_> { - fn get_esp(&mut self) -> &mut ESP<'_> { +impl<'a> BoardInteraction<'a> for V4<'a> { + fn get_esp(&mut self) -> &mut ESP<'a> { &mut self.esp } @@ -195,7 +204,9 @@ impl BoardInteraction<'_> for V4<'_> { } fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> { - self.charge_indicator.set_state(charging.into()).expect("cannot fail"); + self.charge_indicator + .set_state(charging.into()) + .expect("cannot fail"); Ok(()) } @@ -358,18 +369,22 @@ impl BoardInteraction<'_> for V4<'_> { fn pump(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> { if enable { - self.pump_expander.pin_set_high(GPIOBank::Bank0, plant.try_into()?)?; + self.pump_expander + .pin_set_high(GPIOBank::Bank0, plant.try_into()?)?; } else { - self.pump_expander.pin_set_low(GPIOBank::Bank0, plant.try_into()?)?; + self.pump_expander + .pin_set_low(GPIOBank::Bank0, plant.try_into()?)?; } anyhow::Ok(()) } fn fault(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> { if enable { - self.pump_expander.pin_set_high(GPIOBank::Bank1, plant.try_into()?)? + self.pump_expander + .pin_set_high(GPIOBank::Bank1, plant.try_into()?)? } else { - self.pump_expander.pin_set_low(GPIOBank::Bank1, plant.try_into()?)? + self.pump_expander + .pin_set_low(GPIOBank::Bank1, plant.try_into()?)? } anyhow::Ok(()) } @@ -380,7 +395,6 @@ impl BoardInteraction<'_> for V4<'_> { self.signal_counter.counter_pause()?; self.signal_counter.counter_clear()?; - //Disable all self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; @@ -412,7 +426,8 @@ impl BoardInteraction<'_> for V4<'_> { } self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?; - self.sensor_expander.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?; + self.sensor_expander + .pin_set_high(GPIOBank::Bank0, SENSOR_ON)?; let delay = Delay::new_default(); let measurement = 100; // TODO what is this scaling factor? what is its purpose? @@ -424,7 +439,8 @@ impl BoardInteraction<'_> for V4<'_> { delay.delay_ms(measurement); self.signal_counter.counter_pause()?; self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; - self.sensor_expander.pin_set_low(GPIOBank::Bank0, SENSOR_ON)?; + self.sensor_expander + .pin_set_low(GPIOBank::Bank0, SENSOR_ON)?; self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?; self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?; self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?; diff --git a/rust/src/main.rs b/rust/src/main.rs index 8012341..89ad933 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -31,7 +31,7 @@ use tank::*; use crate::config::BoardVersion::INITIAL; use crate::hal::{BoardInteraction, PlantHal, HAL, PLANT_COUNT}; use crate::hal::battery::BatteryInteraction; -pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create().unwrap()); +pub static BOARD_ACCESS: Lazy>> = Lazy::new(|| Arc::new(PlantHal::create().unwrap())); pub static STAY_ALIVE: Lazy = Lazy::new(|| AtomicBool::new(false)); mod webserver { From 69077239a538365661f4f8405378ce8a3b72ffab Mon Sep 17 00:00:00 2001 From: ju6ge Date: Thu, 19 Jun 2025 18:36:48 +0200 Subject: [PATCH 4/5] fix tread safty issues by annotating Box Traits correctly --- rust/src/hal/initial_hal.rs | 6 +++--- rust/src/hal/mod.rs | 8 ++++---- rust/src/hal/v3_hal.rs | 8 ++++---- rust/src/hal/v4_hal.rs | 8 ++++---- rust/src/main.rs | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs index d054f10..3a944b2 100644 --- a/rust/src/hal/initial_hal.rs +++ b/rust/src/hal/initial_hal.rs @@ -12,7 +12,7 @@ pub struct Initial<'a> { pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, pub(crate) esp: ESP<'a>, pub(crate) config: PlantControllerConfig, - pub(crate) battery: Box, + pub(crate) battery: Box, } pub(crate) fn create_initial_board( @@ -20,7 +20,7 @@ pub(crate) fn create_initial_board( fs_mount_error: bool, config: PlantControllerConfig, esp: ESP<'static>, -) -> Result> + Send + Sync> { +) -> Result + Send>> { let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; @@ -46,7 +46,7 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { &self.config } - fn get_battery_monitor(&mut self) -> &mut Box { + fn get_battery_monitor(&mut self) -> &mut Box { &mut self.battery } diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index 79ca9e0..c05b9e4 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -93,7 +93,7 @@ pub enum Sensor { pub struct PlantHal {} pub struct HAL<'a> { - pub board_hal: Box + Sync + Send>, + pub board_hal: Box + Send>, } #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -116,7 +116,7 @@ impl Default for BackupHeader { pub trait BoardInteraction<'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_battery_monitor(&mut self) -> &mut Box; fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; fn deep_sleep(&mut self, duration_in_ms: u64) -> !; fn get_backup_info(&mut self) -> Result; @@ -304,9 +304,9 @@ impl PlantHal { } BatteryBoardVersion::WchI2cSlave => BatteryMonitor::WchI2cSlave {}, }; - let battery_interaction = Box::new(battery_monitor) as Box; + let battery_interaction = Box::new(battery_monitor) as Box; - let board_hal: Box = match config.hardware.board { + let board_hal: Box = match config.hardware.board { BoardVersion::INITIAL => { initial_hal::create_initial_board(free_pins, fs_mount_error, config, esp)? } diff --git a/rust/src/hal/v3_hal.rs b/rust/src/hal/v3_hal.rs index e8ee742..968a066 100644 --- a/rust/src/hal/v3_hal.rs +++ b/rust/src/hal/v3_hal.rs @@ -75,7 +75,7 @@ const FAULT_2: usize = 23; pub struct V3<'a> { config: PlantControllerConfig, - battery_monitor: Box, + battery_monitor: Box, esp: ESP<'a>, shift_register: ShiftRegister40< PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, @@ -106,8 +106,8 @@ pub(crate) fn create_v3( peripherals: FreePeripherals, esp: ESP<'static>, config: PlantControllerConfig, - battery_monitor: Box, -) -> Result> { + battery_monitor: Box, +) -> Result> { let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?; clock.set_pull(Pull::Floating)?; let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?; @@ -255,7 +255,7 @@ impl<'a> BoardInteraction<'a> for V3<'a> { &self.config } - fn get_battery_monitor(&mut self) -> &mut Box<(dyn BatteryInteraction + 'static)> { + fn get_battery_monitor(&mut self) -> &mut Box<(dyn BatteryInteraction + Send + 'static)> { &mut self.battery_monitor } diff --git a/rust/src/hal/v4_hal.rs b/rust/src/hal/v4_hal.rs index ddb1419..8fcf1bf 100644 --- a/rust/src/hal/v4_hal.rs +++ b/rust/src/hal/v4_hal.rs @@ -36,7 +36,7 @@ const SENSOR_ON: u8 = 5_u8; pub struct V4<'a> { esp: ESP<'a>, - battery_monitor: Box, + battery_monitor: Box, config: PlantControllerConfig, 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>, @@ -63,8 +63,8 @@ pub(crate) fn create_v4( peripherals: FreePeripherals, esp: ESP<'static>, config: PlantControllerConfig, - battery_monitor: Box, -) -> anyhow::Result> { + battery_monitor: Box, +) -> anyhow::Result> { let mut awake = PinDriver::output(peripherals.gpio15.downgrade())?; awake.set_high()?; @@ -199,7 +199,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { &self.config } - fn get_battery_monitor(&mut self) -> &mut Box { + fn get_battery_monitor(&mut self) -> &mut Box { &mut self.battery_monitor } diff --git a/rust/src/main.rs b/rust/src/main.rs index 89ad933..8012341 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -31,7 +31,7 @@ use tank::*; use crate::config::BoardVersion::INITIAL; use crate::hal::{BoardInteraction, PlantHal, HAL, PLANT_COUNT}; use crate::hal::battery::BatteryInteraction; -pub static BOARD_ACCESS: Lazy>> = Lazy::new(|| Arc::new(PlantHal::create().unwrap())); +pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create().unwrap()); pub static STAY_ALIVE: Lazy = Lazy::new(|| AtomicBool::new(false)); mod webserver { From 3a2e59874ebfba51f6e490dd4658a93161ef0459 Mon Sep 17 00:00:00 2001 From: Empire Date: Thu, 19 Jun 2025 20:06:43 +0200 Subject: [PATCH 5/5] fix config on submit not updated, fix startup initial always in ap mode --- rust/src/hal/initial_hal.rs | 6 ++++++ rust/src/hal/mod.rs | 1 + rust/src/hal/v3_hal.rs | 6 ++++++ rust/src/hal/v4_hal.rs | 6 ++++++ rust/src/main.rs | 2 +- rust/src/webserver/webserver.rs | 4 +--- 6 files changed, 21 insertions(+), 4 deletions(-) diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs index 3a944b2..2325e5e 100644 --- a/rust/src/hal/initial_hal.rs +++ b/rust/src/hal/initial_hal.rs @@ -117,4 +117,10 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { fn test(&mut self) -> Result<()> { bail!("Please configure board revision") } + + fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { + self.config = config; + self.esp.save_config(&self.config)?; + anyhow::Ok(()) + } } diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index c05b9e4..a70ba24 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -137,6 +137,7 @@ pub trait BoardInteraction<'a> { 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 { diff --git a/rust/src/hal/v3_hal.rs b/rust/src/hal/v3_hal.rs index 968a066..1a32f24 100644 --- a/rust/src/hal/v3_hal.rs +++ b/rust/src/hal/v3_hal.rs @@ -629,4 +629,10 @@ impl<'a> BoardInteraction<'a> for V3<'a> { self.esp.delay.delay_ms(10); Ok(()) } + + fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { + self.config = config; + self.esp.save_config(&self.config)?; + anyhow::Ok(()) + } } diff --git a/rust/src/hal/v4_hal.rs b/rust/src/hal/v4_hal.rs index 8fcf1bf..11b81da 100644 --- a/rust/src/hal/v4_hal.rs +++ b/rust/src/hal/v4_hal.rs @@ -543,4 +543,10 @@ impl<'a> BoardInteraction<'a> for V4<'a> { self.esp.delay.delay_ms(10); anyhow::Ok(()) } + + fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { + self.config = config; + self.esp.save_config(&self.config)?; + anyhow::Ok(()) + } } diff --git a/rust/src/main.rs b/rust/src/main.rs index 8012341..8a0be13 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -208,7 +208,7 @@ fn safe_main() -> anyhow::Result<()> { } } - if board.board_hal.get_config().hardware.board == INITIAL { + 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(); drop(board); let reboot_now = Arc::new(AtomicBool::new(false)); diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index f4bb27c..1bf0a45 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -219,9 +219,7 @@ fn set_config( let config: PlantControllerConfig = serde_json::from_slice(&all)?; let mut board = BOARD_ACCESS.lock().expect("board access"); - board.board_hal.get_esp().save_config(&config)?; - -//TODO fixme board.config = config; + board.board_hal.set_config(config); anyhow::Ok(Some("saved".to_owned())) }