diff --git a/rust/src/main.rs b/rust/src/main.rs index 8a0a643..8f5a8d0 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,8 +1,4 @@ -use std::{ - fmt::Display, - sync::{atomic::AtomicBool, Arc, Mutex}, -}; -use std::sync::MutexGuard; +use crate::{config::PlantControllerConfig, webserver::webserver::httpd}; use anyhow::bail; use chrono::{DateTime, Datelike, Timelike, Utc}; use chrono_tz::Tz; @@ -18,19 +14,24 @@ use esp_idf_sys::{ use esp_ota::{mark_app_valid, rollback_and_reboot}; use log::{log, LogMessage}; use once_cell::sync::Lazy; -use plant_hal::{PlantCtrlBoard, PlantHal, PLANT_COUNT}; +use plant_hal::{PlantHal, PLANT_COUNT}; use serde::{Deserialize, Serialize}; -use crate::{config::PlantControllerConfig, webserver::webserver::httpd}; +use std::sync::MutexGuard; +use std::{ + fmt::Display, + sync::{atomic::AtomicBool, Arc, Mutex}, +}; mod config; mod log; pub mod plant_hal; mod plant_state; mod tank; +use crate::plant_hal::{BatteryInteraction, BoardInteraction, HAL}; use plant_state::PlantState; use tank::*; -pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create().unwrap()); +pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create_v3().unwrap()); pub static STAY_ALIVE: Lazy = Lazy::new(|| AtomicBool::new(false)); mod webserver { @@ -150,7 +151,7 @@ fn safe_main() -> anyhow::Result<()> { }; log(LogMessage::PartitionState, 0, 0, "", ota_state_string); - let mut board: std::sync::MutexGuard<'_, PlantCtrlBoard<'_>> = BOARD_ACCESS.lock().unwrap(); + let mut board = BOARD_ACCESS.lock().unwrap(); board.general_fault(false); log(LogMessage::MountingFilesystem, 0, 0, "", ""); @@ -400,7 +401,7 @@ fn safe_main() -> anyhow::Result<()> { } let is_day = board.is_day(); - let state_of_charge = board.state_charge_percent().unwrap_or(0); + let state_of_charge = board.battery_monitor.state_charge_percent().unwrap_or(0); let mut light_state = LightState { enabled: config.night_lamp.enabled, @@ -484,7 +485,7 @@ fn safe_main() -> anyhow::Result<()> { board.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64); } -fn obtain_tank_temperature(board: &mut MutexGuard) -> anyhow::Result { +fn obtain_tank_temperature(board: &mut MutexGuard) -> anyhow::Result { //multisample should be moved to water_temperature_c let mut attempt = 1; let water_temp: Result = loop { @@ -506,7 +507,7 @@ fn obtain_tank_temperature(board: &mut MutexGuard) -> anyhow::Re water_temp } -fn publish_tank_state(board: &mut MutexGuard, config: &PlantControllerConfig, tank_state: &TankState, water_temp: &anyhow::Result) { +fn publish_tank_state(board: &mut MutexGuard, config: &PlantControllerConfig, tank_state: &TankState, water_temp: &anyhow::Result) { match serde_json::to_string(&tank_state.as_mqtt_info(&config.tank, water_temp)) { Ok(state) => { let _ = board.mqtt_publish(&config, "/water", state.as_bytes()); @@ -517,7 +518,7 @@ fn publish_tank_state(board: &mut MutexGuard, config: &PlantCont }; } -fn publish_plant_states(board: &mut MutexGuard, config: &PlantControllerConfig, timezone_time: &DateTime, plantstate: &[PlantState; 8]) { +fn publish_plant_states(board: &mut MutexGuard, config: &PlantControllerConfig, timezone_time: &DateTime, plantstate: &[PlantState; 8]) { for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(&config.plants).enumerate() { match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, &timezone_time)) { Ok(state) => { @@ -533,7 +534,7 @@ fn publish_plant_states(board: &mut MutexGuard, config: &PlantCo } } -fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard, config: &PlantControllerConfig, ip_address: &String, timezone_time: DateTime) { +fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard, config: &PlantControllerConfig, ip_address: &String, timezone_time: DateTime) { let _ = board.mqtt_publish(&config, "/firmware/address", ip_address.as_bytes()); let _ = board.mqtt_publish(&config, "/firmware/githash", version.git_hash.as_bytes()); let _ = board.mqtt_publish( @@ -555,7 +556,7 @@ fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: & let _ = board.mqtt_publish(&config, "/state", "online".as_bytes()); } -fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &PlantControllerConfig) -> NetworkMode{ +fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &PlantControllerConfig) -> NetworkMode{ match board.wifi( config.network.ssid.clone().unwrap(), config.network.password.clone(), @@ -603,7 +604,7 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &P } //TODO clean this up? better state -fn pump_info(board: &mut MutexGuard, config: &PlantControllerConfig, plant_id: usize, pump_active: bool, pump_ineffective: bool) { +fn pump_info(board: &mut MutexGuard, config: &PlantControllerConfig, plant_id: usize, pump_active: bool, pump_ineffective: bool) { let pump_info = PumpInfo { enabled: pump_active, pump_ineffective @@ -622,18 +623,11 @@ fn pump_info(board: &mut MutexGuard, config: &PlantControllerCon } fn publish_battery_state( - board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, + board: &mut MutexGuard<'_, HAL<'_>>, config: &PlantControllerConfig, ) { - let bat = board.get_battery_state(); - match serde_json::to_string(&bat) { - Ok(state) => { - let _ = board.mqtt_publish(config, "/battery", state.as_bytes()); - } - Err(err) => { - println!("Error publishing battery_state {}", err); - } - }; + let state = board.get_battery_state(); + let _ = board.mqtt_publish(config, "/battery", state.as_bytes()); } fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 96fef55..8200b0e 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -62,6 +62,7 @@ use one_wire_bus::OneWire; use crate::config::PlantControllerConfig; use crate::log::log; +use crate::plant_hal::BoardHal::V3; use crate::{to_string, STAY_ALIVE}; //Only support for 8 right now! @@ -151,34 +152,75 @@ pub enum Sensor { pub struct PlantHal {} -pub struct PlantCtrlBoard<'a> { - shift_register: ShiftRegister40< - PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - >, - shift_register_enable_invert: - PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Output>, - tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>, - solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, - boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, - signal_counter: PcntDriver<'a>, - light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - wifi_driver: EspWifi<'a>, - one_wire_bus: OneWire>, +pub struct ESP<'a> { mqtt_client: Option>, - battery_driver: Bq34z100g1Driver>, Delay>, - rtc: - Ds323x>>, ds323x::ic::DS3231>, - eeprom: Eeprom24x< - MutexDevice<'a, I2cDriver<'a>>, - eeprom24x::page_size::B32, - eeprom24x::addr_size::TwoBytes, - eeprom24x::unique_serial::No, - >, + signal_counter: PcntDriver<'a>, + wifi_driver: EspWifi<'a>, + boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, + one_wire_bus: OneWire>, +} + +pub struct HAL<'a>{ + pub board_hal: BoardHal<'a>, + pub esp: ESP<'a>, + pub battery_monitor: BatteryMonitor<'a> +} + +pub enum BatteryMonitor<'a> { + BQ34Z100G1 { + battery_driver: Bq34z100g1Driver>, Delay> + }, + WchI2cSlave { + + } +} + +pub enum BoardHal<'a>{ + 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>, + + 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>, + boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, + signal_counter: PcntDriver<'a>, + light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + wifi_driver: EspWifi<'a>, + one_wire_bus: OneWire>, + battery_driver: Bq34z100g1Driver>, Delay>, + rtc: + Ds323x>>, ds323x::ic::DS3231>, + eeprom: Eeprom24x< + MutexDevice<'a, I2cDriver<'a>>, + eeprom24x::page_size::B32, + eeprom24x::addr_size::TwoBytes, + eeprom24x::unique_serial::No, + >, + } } #[derive(Serialize, Debug)] @@ -215,19 +257,156 @@ pub struct BackupHeader { pub size: usize, } -impl PlantCtrlBoard<'_> { - pub fn update_charge_indicator(&mut self) { - let is_charging = match self.battery_driver.average_current() { - OkStd(current) => current < 20, - Err(_) => false, - }; - self.shift_register.decompose()[CHARGING] - .set_state(is_charging.into()) - .unwrap(); +impl BatteryInteraction for BatteryMonitor<'_> { + fn state_charge_percent(&mut self) -> Result { + match self { + BatteryMonitor::BQ34Z100G1 { battery_driver} => { + match battery_driver.state_of_charge() { + OkStd(r) => Ok(r), + Err(err) => bail!("Error reading SoC {:?}", err), + } + }, + BatteryMonitor::WchI2cSlave { .. } => { + bail!("Not implemented") + } + } } - pub fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { - self.shift_register.decompose()[AWAKE].set_low().unwrap(); + fn remaining_milli_ampere_hour(&mut self) -> Result { + match self { + BatteryMonitor::BQ34Z100G1 { battery_driver} => { + match battery_driver.remaining_capacity(){ + OkStd(r) => Ok(r), + Err(err) => bail!("Error reading remaining_milli_ampere_hour {:?}", err), + } + }, + BatteryMonitor::WchI2cSlave { .. } => { + bail!("Not implemented") + } + } + } + fn max_milli_ampere_hour(&mut self) -> Result { + match self { + BatteryMonitor::BQ34Z100G1 { battery_driver} => { + match battery_driver.full_charge_capacity() { + OkStd(r) => Ok(r), + Err(err) => bail!("Error reading max_milli_ampere_hour {:?}", err), + } + }, + BatteryMonitor::WchI2cSlave { .. } => { + bail!("Not implemented") + } + } + } + fn design_milli_ampere_hour(&mut self) -> Result { + match self { + BatteryMonitor::BQ34Z100G1 { battery_driver} => { + match battery_driver.design_capacity() { + OkStd(r) => Ok(r), + Err(err) => bail!("Error reading design_milli_ampere_hour {:?}", err), + } + }, + BatteryMonitor::WchI2cSlave { .. } => { + bail!("Not implemented") + } + } + } + fn voltage_milli_volt(&mut self) -> Result { + match self { + BatteryMonitor::BQ34Z100G1 { battery_driver} => { + match battery_driver.voltage() { + OkStd(r) => Ok(r), + Err(err) => bail!("Error reading voltage_milli_volt {:?}", err), + } + }, + BatteryMonitor::WchI2cSlave { .. } => { + bail!("Not implemented") + } + } + } + fn average_current_milli_ampere(&mut self) -> Result { + match self { + BatteryMonitor::BQ34Z100G1 { battery_driver} => { + match battery_driver.average_current() { + OkStd(r) => Ok(r), + Err(err) => bail!("Error reading average_current_milli_ampere {:?}", err), + } + }, + BatteryMonitor::WchI2cSlave { .. } => { + bail!("Not implemented") + } + } + } + fn cycle_count(&mut self) -> Result { + match self { + BatteryMonitor::BQ34Z100G1 { battery_driver} => { + match battery_driver.cycle_count() { + OkStd(r) => Ok(r), + Err(err) => bail!("Error reading cycle_count {:?}", err), + } + }, + BatteryMonitor::WchI2cSlave { .. } => { + bail!("Not implemented") + } + } + } + fn state_health_percent(&mut self) -> Result { + match self { + BatteryMonitor::BQ34Z100G1 { battery_driver} => { + match battery_driver.state_of_health() { + OkStd(r) => Ok(r as u8), + Err(err) => bail!("Error reading state_health_percent {:?}", err), + } + }, + BatteryMonitor::WchI2cSlave { .. } => { + bail!("Not implemented") + } + } + } + fn bat_temperature(&mut self) -> Result { + match self { + BatteryMonitor::BQ34Z100G1 { battery_driver} => { + match battery_driver.temperature() { + OkStd(r) => Ok(r), + Err(err) => bail!("Error reading bat_temperature {:?}", err), + } + }, + BatteryMonitor::WchI2cSlave { .. } => { + bail!("Not implemented") + } + } + } +} + +impl BoardInteraction for HAL<'_> { + fn update_charge_indicator(&mut self) { + let is_charging = match &mut self.battery_monitor { + BatteryMonitor::BQ34Z100G1 { battery_driver, .. } => { + match battery_driver.average_current() { + OkStd(current) => current < 20, + Err(_) => false, + } + } + _ => false + }; + + match &mut self.board_hal { + BoardHal::V3 { shift_register, .. } => { + shift_register.decompose()[CHARGING] + .set_state(is_charging.into()) + .unwrap(); + } + BoardHal::V4 { .. } => {} + } + } + fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { + match &mut self.board_hal { + BoardHal::V3 { shift_register, .. } => { + shift_register.decompose()[AWAKE].set_low().unwrap(); + } + BoardHal::V4 { .. } => {} + } + unsafe { //if we don't do this here, we might just revert newly flashed firmware mark_app_valid(); @@ -244,8 +423,11 @@ impl PlantCtrlBoard<'_> { } }; } - - pub fn get_backup_info(&mut self) -> Result { + fn get_backup_info(&mut self) -> Result { + let eeprom = match &mut self.board_hal { + BoardHal::V3 { eeprom, .. } => {eeprom} + BoardHal::V4 { eeprom, .. } => {eeprom } + }; let dummy = BackupHeader { timestamp: 0, crc16: 0, @@ -254,7 +436,7 @@ impl PlantCtrlBoard<'_> { let store = bincode::serialize(&dummy)?.len(); let mut header_page_buffer = vec![0_u8; store]; - match self.eeprom.read_data(0, &mut header_page_buffer) { + match eeprom.read_data(0, &mut header_page_buffer) { OkStd(_) => {} Err(err) => bail!("Error reading eeprom header {:?}", err), }; @@ -262,8 +444,12 @@ impl PlantCtrlBoard<'_> { let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; Ok(header) } + fn get_backup_config(&mut self) -> Result> { + let eeprom = match &mut self.board_hal { + BoardHal::V3 { eeprom, .. } => {eeprom} + BoardHal::V4 { eeprom, .. } => {eeprom } + }; - pub fn get_backup_config(&mut self) -> Result> { let dummy = BackupHeader { timestamp: 0, crc16: 0, @@ -272,16 +458,16 @@ impl PlantCtrlBoard<'_> { let store = bincode::serialize(&dummy)?.len(); let mut header_page_buffer = vec![0_u8; store]; - match self.eeprom.read_data(0, &mut header_page_buffer) { + 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 = self.eeprom.page_size() as u32; + let data_start_address = eeprom.page_size() as u32; let mut data_buffer = vec![0_u8; header.size]; - match self.eeprom.read_data(data_start_address, &mut data_buffer) { + match eeprom.read_data(data_start_address, &mut data_buffer) { OkStd(_) => {} Err(err) => bail!("Error reading eeprom data {:?}", err), }; @@ -297,14 +483,19 @@ impl PlantCtrlBoard<'_> { 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 + } + BoardHal::V4 { eeprom, .. } => { eeprom } + }; - pub fn backup_config(&mut self, bytes: &[u8]) -> Result<()> { let delay = Delay::new_default(); let checksum = X25.checksum(bytes); - let page_size = self.eeprom.page_size(); - - let time = self.get_rtc_time()?.timestamp_millis(); + let page_size = eeprom.page_size(); let header = BackupHeader { crc16: checksum, @@ -322,7 +513,7 @@ impl PlantCtrlBoard<'_> { } let as_u8: &[u8] = &encoded; - match self.eeprom.write_page(0, as_u8) { + match eeprom.write_page(0, as_u8) { OkStd(_) => {} Err(err) => bail!("Error writing eeprom {:?}", err), }; @@ -334,7 +525,13 @@ impl PlantCtrlBoard<'_> { let mut current_page = 1; for chunk in to_write { let address = current_page * page_size as u32; - match self.eeprom.write_page(address, chunk) { + let eeprom = match &mut self.board_hal { + V3 { eeprom, .. } => { + eeprom + } + BoardHal::V4 { eeprom, .. } => { eeprom } + }; + match eeprom.write_page(address, chunk) { OkStd(_) => {} Err(err) => bail!("Error writing eeprom {:?}", err), }; @@ -353,21 +550,28 @@ impl PlantCtrlBoard<'_> { } Ok(()) } + fn get_battery_state(&mut self) -> String { + let bat = BatteryState { + voltage_milli_volt: to_string(self.battery_monitor.voltage_milli_volt()), + current_milli_ampere: to_string(self.battery_monitor.average_current_milli_ampere()), + cycle_count: to_string(self.battery_monitor.cycle_count()), + design_milli_ampere: to_string(self.battery_monitor.design_milli_ampere_hour()), + remaining_milli_ampere: to_string(self.battery_monitor.remaining_milli_ampere_hour()), + state_of_charge: to_string(self.battery_monitor.state_charge_percent()), + state_of_health: to_string(self.battery_monitor.state_health_percent()), + temperature: to_string(self.battery_monitor.bat_temperature()), + }; - pub fn get_battery_state(&mut self) -> BatteryState { - BatteryState { - voltage_milli_volt: to_string(self.voltage_milli_volt()), - current_milli_ampere: to_string(self.average_current_milli_ampere()), - cycle_count: to_string(self.cycle_count()), - design_milli_ampere: to_string(self.design_milli_ampere_hour()), - remaining_milli_ampere: to_string(self.remaining_milli_ampere_hour()), - state_of_charge: to_string(self.state_charge_percent()), - state_of_health: to_string(self.state_health_percent()), - temperature: to_string(self.bat_temperature()), + match serde_json::to_string(&bat) { + std::prelude::rust_2015::Ok(state) => { + state + } + Err(err) => { + format!("{:?}", err).to_owned() + } } } - - pub fn list_files(&self) -> FileList { + fn list_files(&self) -> FileList { let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap(); let mut file_system_corrupt = None; @@ -417,8 +621,7 @@ impl PlantCtrlBoard<'_> { iter_error, } } - - pub fn delete_file(&self, filename: &str) -> Result<()> { + fn delete_file(&self, filename: &str) -> Result<()> { let filepath = Path::new(BASE_PATH).join(Path::new(filename)); match fs::remove_file(filepath) { OkStd(_) => Ok(()), @@ -427,8 +630,7 @@ impl PlantCtrlBoard<'_> { } } } - - pub fn get_file_handle(&self, filename: &str, write: bool) -> Result { + fn get_file_handle(&self, filename: &str, write: bool) -> Result { let filepath = Path::new(BASE_PATH).join(Path::new(filename)); Ok(if write { File::create(filepath)? @@ -436,19 +638,20 @@ impl PlantCtrlBoard<'_> { File::open(filepath)? }) } - - pub fn is_day(&self) -> bool { - self.solar_is_day.get_level().into() + fn is_day(& self) -> bool { + match & self.board_hal { + BoardHal::V3 { solar_is_day, .. } => {solar_is_day.get_level().into()} + BoardHal::V4 { solar_is_day, .. } => {solar_is_day.get_level().into() } + } } - //should be multsampled - pub fn water_temperature_c(&mut self) -> Result { + fn water_temperature_c(&mut self) -> Result { let mut delay = Delay::new_default(); - self.one_wire_bus + self.esp.one_wire_bus .reset(&mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - let first = self.one_wire_bus.devices(false, &mut delay).next(); + let first = self.esp.one_wire_bus.devices(false, &mut delay).next(); if first.is_none() { bail!("Not found any one wire Ds18b20"); } @@ -460,131 +663,149 @@ impl PlantCtrlBoard<'_> { .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; water_temp_sensor - .start_temp_measurement(&mut self.one_wire_bus, &mut delay) + .start_temp_measurement(&mut self.esp.one_wire_bus, &mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut delay); let sensor_data = water_temp_sensor - .read_data(&mut self.one_wire_bus, &mut delay) + .read_data(&mut self.esp.one_wire_bus, &mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; if sensor_data.temperature == 85_f32 { bail!("Ds18b20 dummy temperature returned"); } Ok(sensor_data.temperature / 10_f32) } - /// return median tank sensor value in milli volt - pub fn tank_sensor_voltage(&mut self) -> Result { + fn tank_sensor_voltage(&mut self) -> Result { + let (tank_power, tank_channel) = match &mut self.board_hal { + BoardHal::V3 { tank_power, tank_channel, .. } => {(tank_power,tank_channel)} + BoardHal::V4 { tank_power, tank_channel, .. } => {(tank_power,tank_channel) } + }; + + let delay = Delay::new_default(); - self.tank_power.set_high()?; + tank_power.set_high()?; //let stabilize delay.delay_ms(100); let mut store = [0_u16; TANK_MULTI_SAMPLE]; for multisample in 0..TANK_MULTI_SAMPLE { - let value = self.tank_channel.read()?; + let value = tank_channel.read()?; store[multisample] = value; } - self.tank_power.set_low()?; + tank_power.set_low()?; store.sort(); let median_mv = store[6] as f32 / 1000_f32; Ok(median_mv) } - - pub fn set_low_voltage_in_cycle(&mut self) { + fn set_low_voltage_in_cycle(&mut self) { unsafe { LOW_VOLTAGE_DETECTED = true; } } - - pub fn clear_low_voltage_in_cycle(&mut self) { + fn clear_low_voltage_in_cycle(&mut self) { unsafe { LOW_VOLTAGE_DETECTED = false; } } - - pub fn light(&mut self, enable: bool) -> Result<()> { - unsafe { gpio_hold_dis(self.light.pin()) }; - self.light.set_state(enable.into())?; - unsafe { gpio_hold_en(self.light.pin()) }; - Ok(()) - } - - pub fn pump(&self, plant: usize, enable: bool) -> Result<()> { - let index = match plant { - 0 => PUMP1_BIT, - 1 => PUMP2_BIT, - 2 => PUMP3_BIT, - 3 => PUMP4_BIT, - 4 => PUMP5_BIT, - 5 => PUMP6_BIT, - 6 => PUMP7_BIT, - 7 => PUMP8_BIT, - _ => bail!("Invalid pump {plant}",), + fn light(&mut self, enable: bool) -> Result<()> { + let light = match &mut self.board_hal { + BoardHal::V3 { light, .. } => light, + BoardHal::V4 { light, .. } => light }; - //currently infallible error, keep for future as result anyway - self.shift_register.decompose()[index].set_state(enable.into())?; + + unsafe { gpio_hold_dis(light.pin()) }; + light.set_state(enable.into())?; + unsafe { gpio_hold_en(light.pin()) }; Ok(()) } + fn pump(& self, plant: usize, enable: bool) -> Result<()> { + match & self.board_hal { + BoardHal::V3 { shift_register, .. } => { + 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 + shift_register.decompose()[index].set_state(enable.into())?; + } + BoardHal::V4 { .. } => {} + } - pub fn last_pump_time(&self, plant: usize) -> Option> { + Ok(()) + } + fn last_pump_time(&self, plant: usize) -> Option> { let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant]; DateTime::from_timestamp_millis(ts) } - - pub fn store_last_pump_time(&mut self, plant: usize, time: DateTime) { + fn store_last_pump_time(&mut self, plant: usize, time: DateTime) { unsafe { LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis(); } } - - pub fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { + fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { unsafe { CONSECUTIVE_WATERING_PLANT[plant] = count; } } - - pub fn consecutive_pump_count(&mut self, plant: usize) -> u32 { + fn consecutive_pump_count(&mut self, plant: usize) -> u32 { unsafe { CONSECUTIVE_WATERING_PLANT[plant] } } + fn fault(& self, plant: usize, enable: bool) { + match & 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()) + .unwrap() + } + BoardHal::V4 { .. } => { + + } + } - pub fn fault(&self, plant: usize, enable: bool) { - let index = match plant { - 0 => FAULT_1, - 1 => FAULT_2, - 2 => FAULT_3, - 3 => FAULT_4, - 4 => FAULT_5, - 5 => FAULT_6, - 6 => FAULT_7, - 7 => FAULT_8, - _ => panic!("Invalid plant id {}", plant), - }; - self.shift_register.decompose()[index] - .set_state(enable.into()) - .unwrap() } - - pub fn low_voltage_in_cycle(&mut self) -> bool { + fn low_voltage_in_cycle(&mut self) -> bool { unsafe { LOW_VOLTAGE_DETECTED } } + fn any_pump(&mut self, enable: bool) -> Result<()> { - pub fn any_pump(&mut self, enable: bool) -> Result<()> { - { - self.main_pump.set_state(enable.into())?; - Ok(()) + match &mut self.board_hal { + BoardHal::V3 { main_pump, .. } => { + main_pump.set_state(enable.into())?; + } + BoardHal::V4 { main_pump, .. } => { + main_pump.set_state(enable.into())?; + } } - } + Ok(()) - pub fn time(&mut self) -> Result> { + } + 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) } - - pub fn sntp(&mut self, max_wait_ms: u32) -> Result> { + 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 { @@ -598,82 +819,116 @@ impl PlantCtrlBoard<'_> { self.time() } + fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { + match &mut self.board_hal { + V3{ shift_register, .. } => { + let mut results = [0_f32; REPEAT_MOIST_MEASURE]; + for repeat in 0..REPEAT_MOIST_MEASURE { + self.esp.signal_counter.counter_pause()?; + self.esp.signal_counter.counter_clear()?; + //Disable all + shift_register.decompose()[MS_4].set_high()?; - pub fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { - let sensor_channel = match sensor { - Sensor::A => match plant { - 0 => SENSOR_A_1, - 1 => SENSOR_A_2, - 2 => SENSOR_A_3, - 3 => SENSOR_A_4, - 4 => SENSOR_A_5, - 5 => SENSOR_A_6, - 6 => SENSOR_A_7, - 7 => SENSOR_A_8, - _ => bail!("Invalid plant id {}", plant), + 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 shift_register.decompose()[MS_0]; + let pin_1 = &mut shift_register.decompose()[MS_1]; + let pin_2 = &mut shift_register.decompose()[MS_2]; + let pin_3 = &mut 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()?; + } + + shift_register.decompose()[MS_4].set_low()?; + shift_register.decompose()[SENSOR_ON].set_high()?; + + let delay = Delay::new_default(); + let measurement = 100; // TODO what is this scaling factor? what is its purpose? + let factor = 1000f32 / measurement as f32; + + //give some time to stabilize + delay.delay_ms(10); + self.esp.signal_counter.counter_resume()?; + delay.delay_ms(measurement); + self.esp.signal_counter.counter_pause()?; + shift_register.decompose()[MS_4].set_high()?; + shift_register.decompose()[SENSOR_ON].set_low()?; + delay.delay_ms(10); + let unscaled = self.esp.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) }, - Sensor::B => match plant { - 0 => SENSOR_B_1, - 1 => SENSOR_B_2, - 2 => SENSOR_B_3, - 3 => SENSOR_B_4, - 4 => SENSOR_B_5, - 5 => SENSOR_B_6, - 6 => SENSOR_B_7, - 7 => SENSOR_B_8, - _ => bail!("Invalid plant id {}", plant), - }, - }; - - let mut results = [0_f32; REPEAT_MOIST_MEASURE]; - for repeat in 0..REPEAT_MOIST_MEASURE { - self.signal_counter.counter_pause()?; - self.signal_counter.counter_clear()?; - //Disable all - self.shift_register.decompose()[MS_4].set_high()?; - - self.sensor_multiplexer(sensor_channel)?; - - self.shift_register.decompose()[MS_4].set_low()?; - self.shift_register.decompose()[SENSOR_ON].set_high()?; - - let delay = Delay::new_default(); - let measurement = 100; // TODO what is this scaling factor? what is its purpose? - let factor = 1000f32 / measurement as f32; - - //give some time to stabilize - delay.delay_ms(10); - self.signal_counter.counter_resume()?; - delay.delay_ms(measurement); - self.signal_counter.counter_pause()?; - self.shift_register.decompose()[MS_4].set_high()?; - self.shift_register.decompose()[SENSOR_ON].set_low()?; - delay.delay_ms(10); - let unscaled = self.signal_counter.get_counter_value()? as i32; - let hz = unscaled as f32 * factor; - log( - LogMessage::RawMeasure, - unscaled as u32, - hz as u32, - &plant.to_string(), - &format!("{sensor:?}"), - ); - results[repeat] = hz; + _ => { + bail!("Not implemented for this board") + } } - results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord - let mid = results.len() / 2; - - Ok(results[mid]) } - - pub fn general_fault(&mut self, enable: bool) { - unsafe { gpio_hold_dis(self.general_fault.pin()) }; - self.general_fault.set_state(enable.into()).unwrap(); - unsafe { gpio_hold_en(self.general_fault.pin()) }; + fn general_fault(&mut self, enable: bool) { + let general_fault = match &mut self.board_hal { + BoardHal::V3 { general_fault, .. } => {general_fault} + BoardHal::V4 { 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()) }; } - - pub fn wifi_ap(&mut self, ap_ssid: Option>) -> Result<()> { + fn wifi_ap(&mut self, ap_ssid: Option>) -> Result<()> { let ssid = ap_ssid.unwrap_or(heapless::String::from_str("PlantCtrl Emergency Mode").unwrap()); let apconfig = AccessPointConfiguration { @@ -682,13 +937,12 @@ impl PlantCtrlBoard<'_> { ssid_hidden: false, ..Default::default() }; - self.wifi_driver + self.esp.wifi_driver .set_configuration(&Configuration::AccessPoint(apconfig))?; - self.wifi_driver.start()?; + self.esp.wifi_driver.start()?; Ok(()) } - - pub fn wifi( + fn wifi( &mut self, ssid: heapless::String<32>, password: Option>, @@ -697,7 +951,7 @@ impl PlantCtrlBoard<'_> { match password { Some(pw) => { //TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not - self.wifi_driver.set_configuration(&Configuration::Client( + self.esp.wifi_driver.set_configuration(&Configuration::Client( ClientConfiguration { ssid, password: pw, @@ -706,7 +960,7 @@ impl PlantCtrlBoard<'_> { ))?; } None => { - self.wifi_driver.set_configuration(&Configuration::Client( + self.esp.wifi_driver.set_configuration(&Configuration::Client( ClientConfiguration { ssid, auth_method: AuthMethod::None, @@ -716,40 +970,39 @@ impl PlantCtrlBoard<'_> { } } - self.wifi_driver.start()?; - self.wifi_driver.connect()?; + self.esp.wifi_driver.start()?; + self.esp.wifi_driver.connect()?; let delay = Delay::new_default(); let mut counter = 0_u32; - while !self.wifi_driver.is_connected()? { + while !self.esp.wifi_driver.is_connected()? { delay.delay_ms(250); counter += 250; if counter > max_wait { //ignore these errors, Wi-Fi will not be used this - self.wifi_driver.disconnect().unwrap_or(()); - self.wifi_driver.stop().unwrap_or(()); + self.esp.wifi_driver.disconnect().unwrap_or(()); + self.esp.wifi_driver.stop().unwrap_or(()); bail!("Did not manage wifi connection within timeout"); } } println!("Should be connected now"); - while !self.wifi_driver.is_up()? { + while !self.esp.wifi_driver.is_up()? { delay.delay_ms(250); counter += 250; if counter > max_wait { //ignore these errors, Wi-Fi will not be used this - self.wifi_driver.disconnect().unwrap_or(()); - self.wifi_driver.stop().unwrap_or(()); + self.esp.wifi_driver.disconnect().unwrap_or(()); + self.esp.wifi_driver.stop().unwrap_or(()); bail!("Did not manage wifi connection within timeout"); } } //update freertos registers ;) - let address = self.wifi_driver.sta_netif().get_ip_info()?; + let address = self.esp.wifi_driver.sta_netif().get_ip_info()?; log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}")); Ok(address) } - - pub fn mount_file_system(&mut self) -> Result<()> { + fn mount_file_system(&mut self) -> Result<()> { let base_path = CString::new("/spiffs")?; let storage = CString::new(SPIFFS_PARTITION_NAME)?; let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { @@ -766,8 +1019,7 @@ impl PlantCtrlBoard<'_> { Ok(()) } } - - pub fn file_system_size(&mut self) -> Result { + fn file_system_size(&mut self) -> Result { let storage = CString::new(SPIFFS_PARTITION_NAME)?; let mut total_size = 0; let mut used_size = 0; @@ -784,12 +1036,10 @@ impl PlantCtrlBoard<'_> { free_size: total_size - used_size, }) } - - pub fn mode_override_pressed(&mut self) -> bool { - self.boot_button.get_level() == Level::Low + fn mode_override_pressed(&mut self) -> bool { + self.esp.boot_button.get_level() == Level::Low } - - pub fn factory_reset(&mut self) -> Result<()> { + fn factory_reset(&mut self) -> Result<()> { println!("factory resetting"); let config = Path::new(CONFIG_FILE); if config.exists() { @@ -803,41 +1053,44 @@ impl PlantCtrlBoard<'_> { Ok(()) } - - pub fn get_rtc_time(&mut self) -> Result> { - match self.rtc.datetime() { + fn get_rtc_time(&mut self) -> Result> { + let rtc = match &mut self.board_hal { + BoardHal::V3 { rtc, .. } => {rtc} + BoardHal::V4 { rtc, .. } => {rtc} + }; + match rtc.datetime() { OkStd(rtc_time) => Ok(rtc_time.and_utc()), Err(err) => { bail!("Error getting rtc time {:?}", err) } } } - - pub fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { + fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { + let rtc = match &mut self.board_hal { + BoardHal::V3 { rtc, .. } => {rtc} + BoardHal::V4 { rtc, .. } => {rtc} + }; let naive_time = time.naive_utc(); - match self.rtc.set_datetime(&naive_time) { + match rtc.set_datetime(&naive_time) { OkStd(_) => Ok(()), Err(err) => { bail!("Error getting rtc time {:?}", err) } } } - - pub fn get_config(&mut self) -> Result { + fn get_config(&mut self) -> Result { let cfg = File::open(CONFIG_FILE)?; let config: PlantControllerConfig = serde_json::from_reader(cfg)?; Ok(config) } - - pub fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()> { + fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()> { let mut cfg = File::create(CONFIG_FILE)?; serde_json::to_writer(&mut cfg, &config)?; println!("Wrote config config {:?}", config); Ok(()) } - - pub fn wifi_scan(&mut self) -> Result> { - self.wifi_driver.start_scan( + fn wifi_scan(&mut self) -> Result> { + self.esp.wifi_driver.start_scan( &ScanConfig { scan_type: ScanType::Passive(Duration::from_secs(5)), show_hidden: false, @@ -845,10 +1098,9 @@ impl PlantCtrlBoard<'_> { }, true, )?; - Ok(self.wifi_driver.get_scan_result()?) + Ok(self.esp.wifi_driver.get_scan_result()?) } - - pub fn test_pump(&mut self, plant: usize) -> Result<()> { + fn test_pump(&mut self, plant: usize) -> Result<()> { self.any_pump(true)?; self.pump(plant, true)?; unsafe { vTaskDelay(30000) }; @@ -856,8 +1108,7 @@ impl PlantCtrlBoard<'_> { self.any_pump(false)?; Ok(()) } - - pub fn test(&mut self) -> Result<()> { + fn test(&mut self) -> Result<()> { self.general_fault(true); unsafe { vTaskDelay(100) }; self.general_fault(false); @@ -898,8 +1149,7 @@ impl PlantCtrlBoard<'_> { Delay::new_default().delay_ms(10); Ok(()) } - - pub fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> { + fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> { let base_topic = config .network .base_topic @@ -1033,7 +1283,7 @@ impl PlantCtrlBoard<'_> { match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) { true => { println!("Round trip registered, proceeding"); - self.mqtt_client = Some(client); + self.esp.mqtt_client = Some(client); return Ok(()); } false => { @@ -1055,14 +1305,13 @@ impl PlantCtrlBoard<'_> { } bail!("Mqtt did not fire connection callback in time"); } - - pub fn mqtt_publish( + fn mqtt_publish( &mut self, config: &PlantControllerConfig, subtopic: &str, message: &[u8], ) -> Result<()> { - if self.mqtt_client.is_none() { + if self.esp.mqtt_client.is_none() { return Ok(()); } if !subtopic.starts_with("/") { @@ -1073,7 +1322,7 @@ impl PlantCtrlBoard<'_> { println!("Subtopic exceeds 192 chars {}", subtopic); bail!("Subtopic exceeds 192 chars {}", subtopic); } - let client = self.mqtt_client.as_mut().unwrap(); + let client = self.esp.mqtt_client.as_mut().unwrap(); let mut full_topic: heapless::String<256> = heapless::String::new(); if full_topic .push_str(config.network.base_topic.as_ref().unwrap()) @@ -1109,117 +1358,86 @@ impl PlantCtrlBoard<'_> { } } } - - pub fn get_restart_to_conf(&mut self) -> bool { + fn get_restart_to_conf(&mut self) -> bool { unsafe { RESTART_TO_CONF } } - - pub fn set_restart_to_conf(&mut self, to_conf: bool) { + fn set_restart_to_conf(&mut self, to_conf: bool) { unsafe { RESTART_TO_CONF = to_conf; } } +} - pub fn state_charge_percent(&mut self) -> Result { - match self.battery_driver.state_of_charge() { - OkStd(r) => Ok(r), - Err(err) => bail!("Error reading SoC {:?}", err), - } - } +pub trait BatteryInteraction { + fn state_charge_percent(&mut self) -> Result; + fn remaining_milli_ampere_hour(&mut self) -> Result; + fn max_milli_ampere_hour(&mut self) -> Result; + fn design_milli_ampere_hour(&mut self) -> Result; + fn voltage_milli_volt(&mut self) -> Result; + fn average_current_milli_ampere(&mut self) -> Result; + fn cycle_count(&mut self) -> Result; + fn state_health_percent(&mut self) -> Result; + fn bat_temperature(&mut self) -> Result; +} - pub fn remaining_milli_ampere_hour(&mut self) -> Result { - match self.battery_driver.remaining_capacity() { - OkStd(r) => Ok(r), - Err(err) => bail!("Error reading Remaining Capacity {:?}", err), - } - } +pub trait BoardInteraction { + fn update_charge_indicator(&mut self); + fn deep_sleep(&mut self, duration_in_ms: u64) -> !; + fn get_backup_info(&mut self) -> Result; + fn get_backup_config(&mut self) -> Result>; + fn backup_config(&mut self, bytes: &[u8]) -> Result<()>; + fn get_battery_state(&mut self) -> String; + fn list_files(&self) -> FileList; + fn delete_file(&self, filename: &str) -> Result<()>; + fn get_file_handle(&self, filename: &str, write: bool) -> Result; + fn is_day(&self) -> bool; + //should be multsampled + fn water_temperature_c(&mut self) -> Result; + /// return median tank sensor value in milli volt + fn tank_sensor_voltage(&mut self) -> Result; + fn set_low_voltage_in_cycle(&mut self); + fn clear_low_voltage_in_cycle(&mut self); + fn light(&mut self, enable: bool) -> Result<()>; + fn pump(&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(&self, plant: usize, enable: bool); + 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 wifi_ap(&mut self, ap_ssid: Option>) -> Result<()>; + fn wifi( + &mut self, + ssid: heapless::String<32>, + password: Option>, + max_wait: u32, + ) -> Result; + fn mount_file_system(&mut self) -> Result<()>; + fn file_system_size(&mut self) -> Result; + 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 get_config(&mut self) -> Result; + fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()>; + fn wifi_scan(&mut self) -> Result>; + fn test_pump(&mut self, plant: usize) -> Result<()>; + fn test(&mut self) -> Result<()>; + fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()>; + fn mqtt_publish( + &mut self, + config: &PlantControllerConfig, + subtopic: &str, + message: &[u8], + ) -> Result<()>; + fn get_restart_to_conf(&mut self) -> bool; + fn set_restart_to_conf(&mut self, to_conf: bool); - pub fn max_milli_ampere_hour(&mut self) -> Result { - match self.battery_driver.full_charge_capacity() { - OkStd(r) => Ok(r), - Err(err) => bail!("Error reading Full Charge Capacity {:?}", err), - } - } - - pub fn design_milli_ampere_hour(&mut self) -> Result { - match self.battery_driver.design_capacity() { - OkStd(r) => Ok(r), - Err(err) => bail!("Error reading Design Capacity {:?}", err), - } - } - - pub fn voltage_milli_volt(&mut self) -> Result { - match self.battery_driver.voltage() { - OkStd(r) => Ok(r), - Err(err) => bail!("Error reading voltage {:?}", err), - } - } - - pub fn average_current_milli_ampere(&mut self) -> Result { - match self.battery_driver.average_current() { - OkStd(r) => Ok(r), - Err(err) => bail!("Error reading Average Current {:?}", err), - } - } - - pub fn cycle_count(&mut self) -> Result { - match self.battery_driver.cycle_count() { - OkStd(r) => Ok(r), - Err(err) => bail!("Error reading Cycle Count {:?}", err), - } - } - - pub fn state_health_percent(&mut self) -> Result { - match self.battery_driver.state_of_health() { - OkStd(r) => Ok(r as u8), - Err(err) => bail!("Error reading State of Health {:?}", err), - } - } - - pub fn bat_temperature(&mut self) -> Result { - match self.battery_driver.temperature() { - OkStd(r) => Ok(r), - Err(err) => bail!("Error reading Temperature {:?}", err), - } - } - - pub fn flash_bq34_z100(&mut self, line: &str, dryrun: bool) -> Result<()> { - match self.battery_driver.write_flash_stream_i2c(line, dryrun) { - OkStd(r) => Ok(r), - Err(err) => bail!("Error reading SoC {:?}", err), - } - } - - pub fn sensor_multiplexer(&mut self, n: u8) -> Result<()> { - assert!(n < 16); - let is_bit_set = |b: u8| -> bool { n & (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()?; - } - Ok(()) - } } fn print_battery( @@ -1297,7 +1515,7 @@ impl PlantHal { Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) } - pub fn create() -> Result>> { + pub fn create_v3() -> Result>> { let peripherals = Peripherals::take()?; let mut clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?; @@ -1348,7 +1566,7 @@ impl PlantHal { ) }; - let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18)?; + let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18.downgrade())?; one_wire_pin.set_pull(Pull::Floating)?; let rtc_time = rtc.datetime(); @@ -1511,34 +1729,37 @@ impl PlantHal { ); } } - let shift_register_enable_invert = PinDriver::output(peripherals.pins.gpio21.downgrade())?; + let mut shift_register_enable_invert = PinDriver::output(peripherals.pins.gpio21.downgrade())?; - let rv = Mutex::new(PlantCtrlBoard { - shift_register, - shift_register_enable_invert, - tank_channel, - solar_is_day, - boot_button, - light, - main_pump, - tank_power, - general_fault, - one_wire_bus, - signal_counter: counter_unit1, - wifi_driver, - mqtt_client: None, - battery_driver, - rtc, - eeprom, + + unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) }; + shift_register_enable_invert.set_low()?; + unsafe { gpio_hold_en(shift_register_enable_invert.pin()) }; + + let rv = Mutex::new(HAL { + board_hal: BoardHal::V3 { + shift_register, + shift_register_enable_invert, + tank_channel, + solar_is_day, + light, + main_pump, + tank_power, + general_fault, + rtc, + eeprom, + }, + esp: ESP { + mqtt_client: None, + signal_counter: counter_unit1, + wifi_driver, + boot_button, + one_wire_bus, + }, + battery_monitor: BatteryMonitor::BQ34Z100G1 { + battery_driver + }, }); - - let _ = rv.lock().is_ok_and(|mut board| { - unsafe { gpio_hold_dis(board.shift_register_enable_invert.pin()) }; - board.shift_register_enable_invert.set_low().unwrap(); - unsafe { gpio_hold_en(board.shift_register_enable_invert.pin()) }; - true - }); - Ok(rv) } } diff --git a/rust/src/plant_state.rs b/rust/src/plant_state.rs index 6bae845..38e4240 100644 --- a/rust/src/plant_state.rs +++ b/rust/src/plant_state.rs @@ -3,6 +3,7 @@ use chrono_tz::Tz; use serde::{Deserialize, Serialize}; use crate::{config::PlantConfig, in_time_range, plant_hal}; +use crate::plant_hal::BoardInteraction; const MOIST_SENSOR_MAX_FREQUENCY: f32 = 7500.; // 60kHz (500Hz margin) const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really, really dry, think like cactus levels @@ -113,7 +114,7 @@ fn map_range_moisture( impl PlantState { pub fn read_hardware_state( plant_id: usize, - board: &mut plant_hal::PlantCtrlBoard, + board: &mut plant_hal::HAL, config: &PlantConfig, ) -> Self { let sensor_a = if config.sensor_a { diff --git a/rust/src/tank.rs b/rust/src/tank.rs index 7c426ef..8f0bd9e 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -1,9 +1,7 @@ use serde::Serialize; -use crate::{ - config::{PlantControllerConfig, TankConfig}, - plant_hal::PlantCtrlBoard, -}; +use crate::plant_hal::{BoardInteraction, HAL}; +use crate::config::{PlantControllerConfig, TankConfig}; const OPEN_TANK_VOLTAGE: f32 = 3.0; pub const WATER_FROZEN_THRESH: f32 = 4.0; @@ -158,7 +156,7 @@ impl TankState { } pub fn determine_tank_state( - board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, + board: &mut std::sync::MutexGuard<'_, HAL<'_>>, config: &PlantControllerConfig, ) -> TankState { if config.tank.tank_sensor_enabled { diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index ea11f34..fb91852 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -8,7 +8,6 @@ use anyhow::bail; use chrono::DateTime; use core::result::Result::Ok; use embedded_svc::http::Method; -use esp_idf_hal::delay::Delay; use esp_idf_svc::http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}; use esp_idf_sys::{settimeofday, timeval, vTaskDelay}; use esp_ota::OtaUpdate; @@ -21,6 +20,7 @@ use std::{ use url::Url; use crate::config::PlantControllerConfig; +use crate::plant_hal::BoardInteraction; use crate::plant_state::MoistureSensorState; #[derive(Serialize, Debug)] @@ -351,45 +351,6 @@ fn ota( anyhow::Ok(None) } -fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> { - let mut board = BOARD_ACCESS.lock().unwrap(); - - let mut toggle = true; - let delay = Delay::new(1); - - let file_handle = board.get_file_handle(filename, false)?; - - let mut reader = std::io::BufRead::lines(std::io::BufReader::with_capacity(512, file_handle)); - let mut line = 0; - loop { - board.general_fault(toggle); - toggle = !toggle; - - delay.delay_us(2); - line += 1; - match reader.next() { - Some(next) => { - let input = next?; - println!("flashing bq34z100 dryrun:{dryrun} line {line} payload: {input}"); - match board.flash_bq34_z100(&input, dryrun) { - Ok(_) => { - println!("ok") - } - Err(err) => { - bail!( - "Error flashing bq34z100 in dryrun: {dryrun} line: {line} error: {err}" - ) - } - } - } - None => break, - } - } - println!("Finished flashing file {line} lines processed"); - board.general_fault(false); - anyhow::Ok(()) -} - fn query_param(uri: &str, param_name: &str) -> Option { println!("{uri} get {param_name}"); let parsed = Url::parse(&format!("http://127.0.0.1/{uri}")).unwrap(); @@ -630,35 +591,6 @@ pub fn httpd(reboot_now: Arc) -> Box> { cors_response(request, 200, "") }) .unwrap(); - - server - .fn_handler("/flashbattery", Method::Post, move |request| { - let filename = query_param(request.uri(),"filename").unwrap(); - let dryrun = true; - match flash_bq(&filename, false) { - Ok(_) => { - if !dryrun { - match flash_bq(&filename, true) { - Ok(_) => { - cors_response(request, 200, "Sucessfully flashed bq34z100")?; - }, - Err(err) => { - let info = format!("Could not flash bq34z100, could be bricked now! {filename} {err:?}"); - cors_response(request, 500, &info)?; - }, - } - } else { - cors_response(request, 200, "Sucessfully processed bq34z100")?; - } - }, - Err(err) => { - let info = format!("Could not process firmware file for, bq34z100, refusing to flash! {filename} {err:?}"); - cors_response(request, 500, &info)?; - }, - }; - anyhow::Ok(()) - }) - .unwrap(); unsafe { vTaskDelay(1) }; server .fn_handler("/", Method::Get, move |request| {