From 38f4ada433a06985d53fcbe51ef0935def6af642 Mon Sep 17 00:00:00 2001 From: Empire Phoenix Date: Sun, 8 Jun 2025 15:59:24 +0200 Subject: [PATCH 1/4] Part 1 of V3 extraction in preperation of v4 and v3 software merge --- rust/src/main.rs | 46 +- rust/src/plant_hal.rs | 995 +++++++++++++++++++------------- rust/src/plant_state.rs | 3 +- rust/src/tank.rs | 8 +- rust/src/webserver/webserver.rs | 70 +-- 5 files changed, 634 insertions(+), 488 deletions(-) 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| { -- 2.47.1 From a9d7936376c34dfe2d4577102a09bd057d9830f8 Mon Sep 17 00:00:00 2001 From: Empire Phoenix Date: Wed, 11 Jun 2025 17:35:23 +0200 Subject: [PATCH 2/4] add null board --- rust/src/main.rs | 23 ++++-- rust/src/plant_hal.rs | 129 +++++++++++++++++++++++++------- rust/src/plant_state.rs | 2 +- rust/src/webserver/webserver.rs | 4 +- 4 files changed, 118 insertions(+), 40 deletions(-) diff --git a/rust/src/main.rs b/rust/src/main.rs index 8f5a8d0..0d7939b 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -184,7 +184,12 @@ fn safe_main() -> anyhow::Result<()> { } println!("cur is {}", cur); - board.update_charge_indicator(); + match board.battery_monitor.average_current_milli_ampere() { + Ok(charging) => { + board.set_charge_indicator(charging > 20) + } + Err(_) => {} + } if board.get_restart_to_conf() { log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", ""); @@ -362,12 +367,12 @@ fn safe_main() -> anyhow::Result<()> { if pump_ineffective { log( LogMessage::ConsecutivePumpCountLimit, - pump_count as u32, + pump_count, plant_config.max_consecutive_pump_count as u32, &(plant_id+1).to_string(), "", ); - board.fault(plant_id, true); + board.fault(plant_id, true)?; } log( LogMessage::PumpPlant, @@ -638,28 +643,30 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { loop { unsafe { let mut lock = BOARD_ACCESS.lock().unwrap(); - lock.update_charge_indicator(); + if let Ok(charging) = lock.battery_monitor.average_current_milli_ampere() { + lock.set_charge_indicator(charging > 20) + } match wait_type { WaitType::MissingConfig => { // Keep existing behavior: circular filling pattern led_count %= 8; led_count += 1; for i in 0..8 { - lock.fault(i, i < led_count); + let _ = lock.fault(i, i < led_count); } } WaitType::ConfigButton => { // Alternating pattern: 1010 1010 -> 0101 0101 pattern_step = (pattern_step + 1) % 2; for i in 0..8 { - lock.fault(i, (i + pattern_step) % 2 == 0); + let _ = lock.fault(i, (i + pattern_step) % 2 == 0); } } WaitType::MqttConfig => { // Moving dot pattern pattern_step = (pattern_step + 1) % 8; for i in 0..8 { - lock.fault(i, i == pattern_step); + let _ = lock.fault(i, i == pattern_step); } } } @@ -672,7 +679,7 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { // Clear all LEDs for i in 0..8 { - lock.fault(i, false); + let _ = lock.fault(i, false); } drop(lock); vTaskDelay(delay); diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 8200b0e..5d3a061 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -63,7 +63,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}; +use crate::{plant_hal, to_string, STAY_ALIVE}; //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; @@ -167,6 +167,9 @@ pub struct HAL<'a>{ } pub enum BatteryMonitor<'a> { + Disabled { + + }, BQ34Z100G1 { battery_driver: Bq34z100g1Driver>, Delay> }, @@ -176,6 +179,9 @@ pub enum BatteryMonitor<'a> { } pub enum BoardHal<'a>{ + Initial { + + }, V3 { shift_register: ShiftRegister40< PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, @@ -269,6 +275,9 @@ impl BatteryInteraction for BatteryMonitor<'_> { BatteryMonitor::WchI2cSlave { .. } => { bail!("Not implemented") } + BatteryMonitor::Disabled {} => { + bail!("Battery monitor is disabled") + } } } @@ -282,6 +291,9 @@ impl BatteryInteraction for BatteryMonitor<'_> { }, BatteryMonitor::WchI2cSlave { .. } => { bail!("Not implemented") + }, + &mut BatteryMonitor::Disabled { } => { + bail!("Battery monitor is disabled") } } } @@ -295,6 +307,9 @@ impl BatteryInteraction for BatteryMonitor<'_> { }, BatteryMonitor::WchI2cSlave { .. } => { bail!("Not implemented") + }, + &mut BatteryMonitor::Disabled { } => { + bail!("Battery monitor is disabled") } } } @@ -308,6 +323,9 @@ impl BatteryInteraction for BatteryMonitor<'_> { }, BatteryMonitor::WchI2cSlave { .. } => { bail!("Not implemented") + }, + &mut BatteryMonitor::Disabled { } => { + bail!("Battery monitor is disabled") } } } @@ -321,6 +339,9 @@ impl BatteryInteraction for BatteryMonitor<'_> { }, BatteryMonitor::WchI2cSlave { .. } => { bail!("Not implemented") + }, + &mut BatteryMonitor::Disabled { } => { + bail!("Battery monitor is disabled") } } } @@ -334,6 +355,9 @@ impl BatteryInteraction for BatteryMonitor<'_> { }, BatteryMonitor::WchI2cSlave { .. } => { bail!("Not implemented") + }, + &mut BatteryMonitor::Disabled { } => { + bail!("Battery monitor is disabled") } } } @@ -347,6 +371,9 @@ impl BatteryInteraction for BatteryMonitor<'_> { }, BatteryMonitor::WchI2cSlave { .. } => { bail!("Not implemented") + }, + &mut BatteryMonitor::Disabled { } => { + bail!("Battery monitor is disabled") } } } @@ -360,6 +387,9 @@ impl BatteryInteraction for BatteryMonitor<'_> { }, BatteryMonitor::WchI2cSlave { .. } => { bail!("Not implemented") + }, + &mut BatteryMonitor::Disabled { } => { + bail!("Battery monitor is disabled") } } } @@ -373,30 +403,26 @@ impl BatteryInteraction for BatteryMonitor<'_> { }, BatteryMonitor::WchI2cSlave { .. } => { bail!("Not implemented") + }, + &mut BatteryMonitor::Disabled { } => { + bail!("Battery monitor is disabled") } } } } 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 - }; - + fn set_charge_indicator(&mut self, charging: bool) { match &mut self.board_hal { BoardHal::V3 { shift_register, .. } => { shift_register.decompose()[CHARGING] - .set_state(is_charging.into()) + .set_state(charging.into()) .unwrap(); } - BoardHal::V4 { .. } => {} + BoardHal::V4 { .. } => {}, + &mut plant_hal::BoardHal::Initial { } => { + + } } } fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { @@ -404,7 +430,10 @@ impl BoardInteraction for HAL<'_> { BoardHal::V3 { shift_register, .. } => { shift_register.decompose()[AWAKE].set_low().unwrap(); } - BoardHal::V4 { .. } => {} + BoardHal::V4 { .. } => {}, + &mut plant_hal::BoardHal::Initial { } => { + + } } unsafe { @@ -426,7 +455,10 @@ impl BoardInteraction for HAL<'_> { fn get_backup_info(&mut self) -> Result { let eeprom = match &mut self.board_hal { BoardHal::V3 { eeprom, .. } => {eeprom} - BoardHal::V4 { eeprom, .. } => {eeprom } + BoardHal::V4 { eeprom, .. } => {eeprom }, + &mut plant_hal::BoardHal::Initial { } => { + bail!("Board not configured yet") + } }; let dummy = BackupHeader { timestamp: 0, @@ -448,6 +480,9 @@ impl BoardInteraction for HAL<'_> { let eeprom = match &mut self.board_hal { BoardHal::V3 { eeprom, .. } => {eeprom} BoardHal::V4 { eeprom, .. } => {eeprom } + &mut plant_hal::BoardHal::Initial { } => { + bail!("Board not configured yet") + } }; let dummy = BackupHeader { @@ -489,7 +524,10 @@ impl BoardInteraction for HAL<'_> { V3 { eeprom, .. } => { eeprom } - BoardHal::V4 { eeprom, .. } => { eeprom } + BoardHal::V4 { eeprom, .. } => { eeprom }, + &mut plant_hal::BoardHal::Initial { } => { + bail!("Board not configured yet") + } }; let delay = Delay::new_default(); @@ -530,6 +568,9 @@ impl BoardInteraction for HAL<'_> { eeprom } BoardHal::V4 { eeprom, .. } => { eeprom } + &mut plant_hal::BoardHal::Initial { } => { + bail!("Board not configured yet") + } }; match eeprom.write_page(address, chunk) { OkStd(_) => {} @@ -642,6 +683,9 @@ impl BoardInteraction for HAL<'_> { 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() } + plant_hal::BoardHal::Initial {} => { + false + } } } //should be multsampled @@ -679,6 +723,9 @@ impl BoardInteraction for HAL<'_> { 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) } + &mut plant_hal::BoardHal::Initial { } => { + bail!("Board not configured yet") + } }; @@ -711,7 +758,10 @@ impl BoardInteraction for HAL<'_> { fn light(&mut self, enable: bool) -> Result<()> { let light = match &mut self.board_hal { BoardHal::V3 { light, .. } => light, - BoardHal::V4 { light, .. } => light + BoardHal::V4 { light, .. } => light, + &mut plant_hal::BoardHal::Initial { } => { + bail!("Board not configured yet") + } }; unsafe { gpio_hold_dis(light.pin()) }; @@ -736,7 +786,12 @@ impl BoardInteraction for HAL<'_> { //currently infallible error, keep for future as result anyway shift_register.decompose()[index].set_state(enable.into())?; } - BoardHal::V4 { .. } => {} + BoardHal::V4 { .. } => { + bail!("Not yet implemented") + }, + &plant_hal::BoardHal::Initial {} => { + bail!("Board not configured yet") + } } Ok(()) @@ -758,7 +813,7 @@ impl BoardInteraction for HAL<'_> { fn consecutive_pump_count(&mut self, plant: usize) -> u32 { unsafe { CONSECUTIVE_WATERING_PLANT[plant] } } - fn fault(& self, plant: usize, enable: bool) { + fn fault(& self, plant: usize, enable: bool) -> Result<()>{ match & self.board_hal { V3 { shift_register, .. } => { let index = match plant { @@ -773,11 +828,15 @@ impl BoardInteraction for HAL<'_> { _ => panic!("Invalid plant id {}", plant), }; shift_register.decompose()[index] - .set_state(enable.into()) - .unwrap() + .set_state(enable.into())?; + Ok(()) + } BoardHal::V4 { .. } => { - + bail!("Not yet implemented") + } + &plant_hal::BoardHal::Initial {} => { + bail!("Board not configured yet") } } @@ -794,6 +853,9 @@ impl BoardInteraction for HAL<'_> { BoardHal::V4 { main_pump, .. } => { main_pump.set_state(enable.into())?; } + &mut plant_hal::BoardHal::Initial { } => { + bail!("Board not configured yet") + } } Ok(()) @@ -922,7 +984,10 @@ impl BoardInteraction for HAL<'_> { 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} + BoardHal::V4 { general_fault, .. } => {general_fault}, + &mut plant_hal::BoardHal::Initial { } => { + return + } }; unsafe { gpio_hold_dis(general_fault.pin()) }; general_fault.set_state(enable.into()).unwrap(); @@ -1056,7 +1121,10 @@ impl BoardInteraction for HAL<'_> { fn get_rtc_time(&mut self) -> Result> { let rtc = match &mut self.board_hal { BoardHal::V3 { rtc, .. } => {rtc} - BoardHal::V4 { rtc, .. } => {rtc} + BoardHal::V4 { rtc, .. } => {rtc}, + &mut plant_hal::BoardHal::Initial { } => { + bail!("Board not configured yet") + } }; match rtc.datetime() { OkStd(rtc_time) => Ok(rtc_time.and_utc()), @@ -1069,6 +1137,9 @@ impl BoardInteraction for HAL<'_> { let rtc = match &mut self.board_hal { BoardHal::V3 { rtc, .. } => {rtc} BoardHal::V4 { rtc, .. } => {rtc} + &mut plant_hal::BoardHal::Initial { } => { + bail!("Board not configured yet") + } }; let naive_time = time.naive_utc(); match rtc.set_datetime(&naive_time) { @@ -1122,9 +1193,9 @@ impl BoardInteraction for HAL<'_> { self.light(false)?; unsafe { vTaskDelay(500) }; for i in 0..PLANT_COUNT { - self.fault(i, true); + self.fault(i, true)?; unsafe { vTaskDelay(500) }; - self.fault(i, false); + self.fault(i, false)?; unsafe { vTaskDelay(500) }; } for i in 0..PLANT_COUNT { @@ -1381,7 +1452,7 @@ pub trait BatteryInteraction { } pub trait BoardInteraction { - fn update_charge_indicator(&mut self); + fn set_charge_indicator(&mut self, charging: bool); fn deep_sleep(&mut self, duration_in_ms: u64) -> !; fn get_backup_info(&mut self) -> Result; fn get_backup_config(&mut self) -> Result>; @@ -1403,7 +1474,7 @@ pub trait BoardInteraction { 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 fault(&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>; diff --git a/rust/src/plant_state.rs b/rust/src/plant_state.rs index 38e4240..d531995 100644 --- a/rust/src/plant_state.rs +++ b/rust/src/plant_state.rs @@ -170,7 +170,7 @@ impl PlantState { }, }; if state.is_err() { - board.fault(plant_id, true); + let _ = board.fault(plant_id, true); } state } diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index fb91852..5b3d483 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -328,7 +328,7 @@ fn ota( let iter = (total_read / 1024) % 8; if iter != lastiter { for i in 0..PLANT_COUNT { - board.fault(i, iter == i); + let _ = board.fault(i, iter == i); } lastiter = iter; } @@ -541,7 +541,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { let iter = (total_read / 1024) % 8; if iter != lastiter { for i in 0..PLANT_COUNT { - lock.fault(i, iter == i); + let _ = lock.fault(i, iter == i); } lastiter = iter; } -- 2.47.1 From 6499b18adaa963d2a544f0eab90ea424c36bc0d4 Mon Sep 17 00:00:00 2001 From: Empire Date: Wed, 11 Jun 2025 22:08:59 +0200 Subject: [PATCH 3/4] allow startup with initial board mode and nearly no pin configs --- rust/.cargo/config.toml | 4 +- rust/src/config.rs | 22 +- rust/src/main.rs | 140 +++-- rust/src/plant_hal.rs | 873 +++++++++++++++++++------------- rust/src/plant_state.rs | 15 +- rust/src/tank.rs | 7 +- rust/src/webserver/webserver.rs | 33 +- website/themes/blowfish | 2 +- 8 files changed, 627 insertions(+), 469 deletions(-) diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml index d87148b..c7e8e2b 100644 --- a/rust/.cargo/config.toml +++ b/rust/.cargo/config.toml @@ -5,8 +5,8 @@ target = "riscv32imac-esp-espidf" [target.riscv32imac-esp-espidf] linker = "ldproxy" #runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv -b no-reset" # Select this runner in case of usb ttl -#runner = "espflash flash --monitor --baud 921600 --flash-size 16mb --partition-table partitions.csv" -runner = "cargo runner" +runner = "espflash flash --monitor --baud 921600 --flash-size 16mb --partition-table partitions.csv" +#runner = "cargo runner" #runner = "espflash flash --monitor --partition-table partitions.csv -b no-reset" # create upgrade image file for webupload diff --git a/rust/src/config.rs b/rust/src/config.rs index f70fd25..f103e02 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -1,6 +1,5 @@ -use std::str::FromStr; - use serde::{Deserialize, Serialize}; +use std::str::FromStr; use crate::plant_state::PlantWateringMode; use crate::PLANT_COUNT; @@ -13,6 +12,7 @@ pub struct NetworkConfig { pub password: Option>, pub mqtt_url: Option>, pub base_topic: Option>, + pub max_wait: u32 } impl Default for NetworkConfig { fn default() -> Self { @@ -22,6 +22,7 @@ impl Default for NetworkConfig { password: None, mqtt_url: None, base_topic: None, + max_wait: 10000, } } } @@ -72,9 +73,26 @@ impl Default for TankConfig { } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +pub enum BatteryBoardVersion{ + #[default] + Disabled, + BQ34Z100G1, + WchI2cSlave +} +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +pub enum BoardVersion{ + #[default] + INITIAL, + V3, + V4 +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] #[serde(default)] pub struct PlantControllerConfig { + pub board_hardware: BoardVersion, + pub battery_hardware: BatteryBoardVersion, pub network: NetworkConfig, pub tank: TankConfig, pub night_lamp: NightLampConfig, diff --git a/rust/src/main.rs b/rust/src/main.rs index 0d7939b..b7f72f0 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,4 +1,4 @@ -use crate::{config::PlantControllerConfig, webserver::webserver::httpd}; +use crate::webserver::webserver::httpd; use anyhow::bail; use chrono::{DateTime, Datelike, Timelike, Utc}; use chrono_tz::Tz; @@ -27,11 +27,11 @@ pub mod plant_hal; mod plant_state; mod tank; -use crate::plant_hal::{BatteryInteraction, BoardInteraction, HAL}; +use crate::plant_hal::{BatteryInteraction, BoardHal, BoardInteraction, HAL}; use plant_state::PlantState; use tank::*; -pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create_v3().unwrap()); +pub static BOARD_ACCESS: Lazy> = Lazy::new(|| PlantHal::create().unwrap()); pub static STAY_ALIVE: Lazy = Lazy::new(|| AtomicBool::new(false)); mod webserver { @@ -113,7 +113,6 @@ fn safe_main() -> anyhow::Result<()> { esp_idf_sys::CONFIG_MAIN_TASK_STACK_SIZE ) } - println!("Startup Rust"); let mut to_config = false; @@ -151,20 +150,9 @@ fn safe_main() -> anyhow::Result<()> { }; log(LogMessage::PartitionState, 0, 0, "", ota_state_string); - let mut board = BOARD_ACCESS.lock().unwrap(); + 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); - log(LogMessage::MountingFilesystem, 0, 0, "", ""); - board.mount_file_system()?; - let free_space = board.file_system_size()?; - log( - LogMessage::FilesystemMount, - free_space.free_size as u32, - free_space.total_size as u32, - &free_space.used_size.to_string(), - "", - ); - let cur = board .get_rtc_time() .or_else(|err| { @@ -220,28 +208,22 @@ fn safe_main() -> anyhow::Result<()> { } } - let config: PlantControllerConfig = match board.get_config() { - Ok(valid) => valid, - Err(err) => { - log( - LogMessage::ConfigModeMissingConfig, - 0, - 0, - "", - &err.to_string(), - ); - //config upload will trigger reboot! - let _ = board.wifi_ap(None); + match board.board_hal { + BoardHal::Initial { .. } => { + //config upload will trigger reboot and then switch to selected board_hal + let _ = board.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 config.network.ssid.is_some() { - try_connect_wifi_sntp_mqtt(&mut board, &config) + let network_mode = if board.config.network.ssid.is_some() { + try_connect_wifi_sntp_mqtt(&mut board) } else { println!("No wifi configured"); NetworkMode::OFFLINE @@ -249,7 +231,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.wifi_ap(Some(config.network.ap_ssid.clone())) { + match board.wifi_ap() { Ok(_) => { println!("Started ap, continuing") } @@ -257,7 +239,7 @@ fn safe_main() -> anyhow::Result<()> { } } - let timezone = match &config.timezone { + let timezone = match & board.config.timezone { Some(tz_str) => tz_str.parse::().unwrap_or_else(|_| { println!("Invalid timezone '{}', falling back to UTC", tz_str); UTC @@ -274,8 +256,8 @@ fn safe_main() -> anyhow::Result<()> { ); if let NetworkMode::WIFI { ref ip_address, .. } = network_mode { - publish_firmware_info(version, address, ota_state_string, &mut board, &config, &ip_address, timezone_time); - publish_battery_state(&mut board, &config); + publish_firmware_info(version, address, ota_state_string, &mut board, &ip_address, timezone_time); + publish_battery_state(&mut board); } @@ -301,10 +283,10 @@ fn safe_main() -> anyhow::Result<()> { let dry_run = false; - let tank_state = determine_tank_state(&mut board, &config); + let tank_state = determine_tank_state(&mut board); if tank_state.is_enabled() { - if let Some(err) = tank_state.got_error(&config.tank) { + if let Some(err) = tank_state.got_error(&board.config.tank) { match err { TankError::SensorDisabled => { /* unreachable */ } TankError::SensorMissing(raw_value_mv) => log( @@ -327,7 +309,7 @@ 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(&config.tank).is_ok_and(|warn| warn) { + } else if tank_state.warn_level(&board.config.tank).is_ok_and(|warn| warn) { log(LogMessage::TankWaterLevelLow, 0, 0, "", ""); board.general_fault(true); } @@ -342,15 +324,15 @@ fn safe_main() -> anyhow::Result<()> { } } - publish_tank_state(&mut board, &config, &tank_state, &water_temp); + publish_tank_state(&mut board, &tank_state, &water_temp); let plantstate: [PlantState; PLANT_COUNT] = - core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i])); - publish_plant_states(&mut board, &config, &timezone_time, &plantstate); + core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board)); + publish_plant_states(&mut board, &timezone_time, &plantstate); let pump_required = plantstate .iter() - .zip(&config.plants) + .zip(&board.config.plants) .any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time)) && !water_frozen; if pump_required { @@ -358,7 +340,7 @@ fn safe_main() -> anyhow::Result<()> { 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(&config.plants).enumerate() { + for (plant_id, (state, plant_config)) in plantstate.iter().zip(&board.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); @@ -385,14 +367,14 @@ fn safe_main() -> anyhow::Result<()> { board.last_pump_time(plant_id); //state.active = true; - pump_info(&mut board, &config, plant_id, true, pump_ineffective); + pump_info(&mut board, plant_id, true, pump_ineffective); if !dry_run { board.pump(plant_id, true)?; Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32); board.pump(plant_id, false)?; } - pump_info(&mut board, &config, plant_id, false, pump_ineffective); + 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 @@ -409,26 +391,26 @@ fn safe_main() -> anyhow::Result<()> { let state_of_charge = board.battery_monitor.state_charge_percent().unwrap_or(0); let mut light_state = LightState { - enabled: config.night_lamp.enabled, + enabled: board.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, - config.night_lamp.night_lamp_hour_start, - config.night_lamp.night_lamp_hour_end, + board.config.night_lamp.night_lamp_hour_start, + board.config.night_lamp.night_lamp_hour_end, ); - if state_of_charge < config.night_lamp.low_soc_cutoff { + if state_of_charge < board.config.night_lamp.low_soc_cutoff { board.set_low_voltage_in_cycle(); - } else if state_of_charge > config.night_lamp.low_soc_restore { + } else if state_of_charge > board.config.night_lamp.low_soc_restore { board.clear_low_voltage_in_cycle(); } light_state.battery_low = board.low_voltage_in_cycle(); if !light_state.out_of_work_hour { - if config.night_lamp.night_lamp_only_when_dark { + if board.config.night_lamp.night_lamp_only_when_dark { if !light_state.is_day { if light_state.battery_low { board.light(false)?; @@ -453,7 +435,7 @@ fn safe_main() -> anyhow::Result<()> { match serde_json::to_string(&light_state) { Ok(state) => { - let _ = board.mqtt_publish(&config, "/light", state.as_bytes()); + let _ = board.mqtt_publish( "/light", state.as_bytes()); } Err(err) => { println!("Error publishing lightstate {}", err); @@ -461,16 +443,16 @@ fn safe_main() -> anyhow::Result<()> { }; let deep_sleep_duration_minutes: u32 = if state_of_charge < 10 { - let _ = board.mqtt_publish(&config, "/deepsleep", "low Volt 12h".as_bytes()); + let _ = board.mqtt_publish( "/deepsleep", "low Volt 12h".as_bytes()); 12 * 60 } else if is_day { - let _ = board.mqtt_publish(&config, "/deepsleep", "normal 20m".as_bytes()); + let _ = board.mqtt_publish( "/deepsleep", "normal 20m".as_bytes()); 20 } else { - let _ = board.mqtt_publish(&config, "/deepsleep", "night 1h".as_bytes()); + let _ = board.mqtt_publish( "/deepsleep", "night 1h".as_bytes()); 60 }; - let _ = board.mqtt_publish(&config, "/state", "sleep".as_bytes()); + let _ = board.mqtt_publish( "/state", "sleep".as_bytes()); mark_app_valid(); @@ -512,10 +494,10 @@ fn obtain_tank_temperature(board: &mut MutexGuard) -> anyhow::Result { water_temp } -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)) { +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)) { Ok(state) => { - let _ = board.mqtt_publish(&config, "/water", state.as_bytes()); + let _ = board.mqtt_publish("/water", state.as_bytes()); } Err(err) => { println!("Error publishing tankstate {}", err); @@ -523,12 +505,12 @@ fn publish_tank_state(board: &mut MutexGuard, config: &PlantControllerConfi }; } -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() { +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() { 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.mqtt_publish(&config, &plant_topic, state.as_bytes()); + let _ = board.mqtt_publish(&plant_topic, state.as_bytes()); //reduce speed as else messages will be dropped Delay::new_default().delay_ms(200); } @@ -539,34 +521,27 @@ fn publish_plant_states(board: &mut MutexGuard, config: &PlantControllerCon } } -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()); +fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard, ip_address: &String, timezone_time: DateTime) { + let _ = board.mqtt_publish("/firmware/address", ip_address.as_bytes()); + let _ = board.mqtt_publish( "/firmware/githash", version.git_hash.as_bytes()); let _ = board.mqtt_publish( - &config, "/firmware/buildtime", version.build_time.as_bytes(), ); let _ = board.mqtt_publish( - &config, "/firmware/last_online", timezone_time.to_rfc3339().as_bytes(), ); - let _ = board.mqtt_publish(&config, "/firmware/ota_state", ota_state_string.as_bytes()); + let _ = board.mqtt_publish( "/firmware/ota_state", ota_state_string.as_bytes()); let _ = board.mqtt_publish( - &config, "/firmware/partition_address", format!("{:#06x}", address).as_bytes(), ); - let _ = board.mqtt_publish(&config, "/state", "online".as_bytes()); + let _ = board.mqtt_publish( "/state", "online".as_bytes()); } -fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &PlantControllerConfig) -> NetworkMode{ - match board.wifi( - config.network.ssid.clone().unwrap(), - config.network.password.clone(), - 10000, - ) { +fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard) -> NetworkMode{ + match board.wifi() { Ok(ip_info) => { let sntp_mode: SntpMode = match board.sntp(1000 * 10) { Ok(new_time) => { @@ -580,8 +555,8 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &PlantControl SntpMode::OFFLINE } }; - let mqtt_connected = if let Some(_) = config.network.mqtt_url { - match board.mqtt(&config) { + let mqtt_connected = if let Some(_) = board.config.network.mqtt_url { + match board.mqtt() { Ok(_) => { println!("Mqtt connection ready"); true @@ -609,7 +584,7 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard, config: &PlantControl } //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, plant_id: usize, pump_active: bool, pump_ineffective: bool) { let pump_info = PumpInfo { enabled: pump_active, pump_ineffective @@ -617,7 +592,7 @@ fn pump_info(board: &mut MutexGuard, config: &PlantControllerConfig, plant_ let pump_topic = format!("/pump{}", plant_id + 1); match serde_json::to_string(&pump_info) { Ok(state) => { - let _ = board.mqtt_publish(config, &pump_topic, state.as_bytes()); + let _ = board.mqtt_publish(&pump_topic, state.as_bytes()); //reduce speed as else messages will be dropped Delay::new_default().delay_ms(200); } @@ -628,11 +603,10 @@ fn pump_info(board: &mut MutexGuard, config: &PlantControllerConfig, plant_ } fn publish_battery_state( - board: &mut MutexGuard<'_, HAL<'_>>, - config: &PlantControllerConfig, + board: &mut MutexGuard<'_, HAL<'_>> ) { let state = board.get_battery_state(); - let _ = board.mqtt_publish(config, "/battery", state.as_bytes()); + let _ = board.mqtt_publish( "/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 5d3a061..b5a76c4 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -12,7 +12,7 @@ use embedded_svc::wifi::{ 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::adc::{attenuation, Resolution, ADC1}; use esp_idf_hal::i2c::{APBTickType, I2cConfig, I2cDriver, I2cError}; use esp_idf_hal::units::FromValueType; use esp_idf_svc::eventloop::EspSystemEventLoop; @@ -49,10 +49,8 @@ use std::time::Duration; use embedded_hal::digital::OutputPin; use esp_idf_hal::delay::Delay; -use esp_idf_hal::gpio::{AnyInputPin, Gpio18, Gpio5, IOPin, InputOutput, Level, PinDriver, Pull}; -use esp_idf_hal::pcnt::{ - PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, -}; +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, 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}; @@ -60,19 +58,15 @@ use esp_idf_svc::systime::EspSystemTime; use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; use one_wire_bus::OneWire; -use crate::config::PlantControllerConfig; +use crate::config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}; use crate::log::log; -use crate::plant_hal::BoardHal::V3; +use crate::plant_hal::BoardHal::{Initial, V3, V4}; use crate::{plant_hal, to_string, STAY_ALIVE}; //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; const REPEAT_MOIST_MEASURE: usize = 1; -const SPIFFS_PARTITION_NAME: &str = "storage"; -const CONFIG_FILE: &str = "/spiffs/config.cfg"; -const BASE_PATH: &str = "/spiffs"; - const TANK_MULTI_SAMPLE: usize = 11; const PUMP8_BIT: usize = 0; @@ -151,16 +145,194 @@ pub enum Sensor { } pub struct PlantHal {} - pub struct ESP<'a> { mqtt_client: Option>, - 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>, + boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input> +} + +impl ESP<'_> { + const SPIFFS_PARTITION_NAME: &'static str = "storage"; + const CONFIG_FILE: &'static str = "/spiffs/config.cfg"; + const BASE_PATH: &'static str = "/spiffs"; + fn get_config(&mut self) -> Result { + let cfg = File::open(Self::CONFIG_FILE)?; + let config: PlantControllerConfig = serde_json::from_reader(cfg)?; + Ok(config) + } + pub(crate) fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()> { + let mut cfg = File::create(Self::CONFIG_FILE)?; + serde_json::to_writer(&mut cfg, &config)?; + println!("Wrote config config {:?}", config); + Ok(()) + } + fn delete_config(&self) -> Result<()>{ + let config = Path::new(Self::CONFIG_FILE); + if config.exists() { + println!("Removing config"); + fs::remove_file(config)? + } + Ok(()) + } + fn mount_file_system(&mut self) -> Result<()> { + log(LogMessage::MountingFilesystem, 0, 0, "", ""); + let base_path = CString::new("/spiffs")?; + let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?; + let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { + base_path: base_path.as_ptr(), + partition_label: storage.as_ptr(), + max_files: 5, + format_if_mount_failed: true, + }; + + unsafe { + esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?; + } + + let free_space = self.file_system_size()?; + log( + LogMessage::FilesystemMount, + free_space.free_size as u32, + free_space.total_size as u32, + &free_space.used_size.to_string(), + "", + ); + Ok(()) + } + fn file_system_size(&mut self) -> Result { + let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?; + let mut total_size = 0; + let mut used_size = 0; + unsafe { + esp_idf_sys::esp!(esp_spiffs_info( + storage.as_ptr(), + &mut total_size, + &mut used_size + ))?; + } + Ok(FileSystemSizeInfo { + total_size, + used_size, + free_size: total_size - used_size, + }) + } + + + pub(crate) fn list_files(&self) -> FileList { + let storage = CString::new(Self::SPIFFS_PARTITION_NAME).unwrap(); + + let mut file_system_corrupt = None; + + let mut iter_error = None; + let mut result = Vec::new(); + + let filepath = Path::new(Self::BASE_PATH); + let read_dir = fs::read_dir(filepath); + match read_dir { + OkStd(read_dir) => { + for item in read_dir { + match item { + OkStd(file) => { + let f = FileInfo { + filename: file.file_name().into_string().unwrap(), + size: file + .metadata() + .and_then(|it| Result::Ok(it.len())) + .unwrap_or_default() + as usize, + }; + result.push(f); + } + Err(err) => { + iter_error = Some(format!("{err:?}")); + break; + } + } + } + } + Err(err) => { + file_system_corrupt = Some(format!("{err:?}")); + } + } + let mut total: usize = 0; + let mut used: usize = 0; + unsafe { + esp_spiffs_info(storage.as_ptr(), &mut total, &mut used); + } + + FileList { + total, + used, + file_system_corrupt, + files: result, + iter_error, + } + } + pub(crate) fn delete_file(&self, filename: &str) -> Result<()> { + let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); + match fs::remove_file(filepath) { + OkStd(_) => Ok(()), + Err(err) => { + bail!(format!("{err:?}")) + } + } + } + pub(crate) fn get_file_handle(&self, filename: &str, write: bool) -> Result { + let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); + Ok(if write { + File::create(filepath)? + } else { + File::open(filepath)? + }) + } + + fn init_rtc_deepsleep_memory(&self, init_rtc_store: bool, to_config_mode: bool){ + if init_rtc_store { + unsafe { + LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT]; + CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT]; + LOW_VOLTAGE_DETECTED = false; + crate::log::init(); + RESTART_TO_CONF = to_config_mode; + }; + } else { + unsafe { + if to_config_mode { + RESTART_TO_CONF = true; + } + log( + LogMessage::RestartToConfig, + RESTART_TO_CONF as u32, + 0, + "", + "", + ); + log( + LogMessage::LowVoltage, + LOW_VOLTAGE_DETECTED as u32, + 0, + "", + "", + ); + for i in 0..PLANT_COUNT { + println!( + "LAST_WATERING_TIMESTAMP[{}] = UTC {}", + i, LAST_WATERING_TIMESTAMP[i] + ); + } + for i in 0..PLANT_COUNT { + println!( + "CONSECUTIVE_WATERING_PLANT[{}] = {}", + i, CONSECUTIVE_WATERING_PLANT[i] + ); + } + } + } + } } pub struct HAL<'a>{ + pub config: PlantControllerConfig, pub board_hal: BoardHal<'a>, pub esp: ESP<'a>, pub battery_monitor: BatteryMonitor<'a> @@ -180,7 +352,7 @@ pub enum BatteryMonitor<'a> { pub enum BoardHal<'a>{ Initial { - + general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput> }, V3 { shift_register: ShiftRegister40< @@ -196,7 +368,8 @@ pub enum BoardHal<'a>{ main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, - + signal_counter: PcntDriver<'a>, + one_wire_bus: OneWire>, rtc: Ds323x>>, ds323x::ic::DS3231>, eeprom: Eeprom24x< @@ -216,8 +389,7 @@ pub enum BoardHal<'a>{ 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>, + one_wire_bus: OneWire>, rtc: Ds323x>>, ds323x::ic::DS3231>, eeprom: Eeprom24x< @@ -414,24 +586,24 @@ impl BatteryInteraction for BatteryMonitor<'_> { impl BoardInteraction for HAL<'_> { fn set_charge_indicator(&mut self, charging: bool) { match &mut self.board_hal { - BoardHal::V3 { shift_register, .. } => { + V3 { shift_register, .. } => { shift_register.decompose()[CHARGING] .set_state(charging.into()) .unwrap(); } - BoardHal::V4 { .. } => {}, - &mut plant_hal::BoardHal::Initial { } => { + V4 { .. } => {}, + Initial { .. } => { } } } fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { match &mut self.board_hal { - BoardHal::V3 { shift_register, .. } => { + V3 { shift_register, .. } => { shift_register.decompose()[AWAKE].set_low().unwrap(); } - BoardHal::V4 { .. } => {}, - &mut plant_hal::BoardHal::Initial { } => { + V4 { .. } => {}, + Initial { .. } => { } } @@ -456,7 +628,7 @@ impl BoardInteraction for HAL<'_> { let eeprom = match &mut self.board_hal { BoardHal::V3 { eeprom, .. } => {eeprom} BoardHal::V4 { eeprom, .. } => {eeprom }, - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -480,7 +652,7 @@ impl BoardInteraction for HAL<'_> { let eeprom = match &mut self.board_hal { BoardHal::V3 { eeprom, .. } => {eeprom} BoardHal::V4 { eeprom, .. } => {eeprom } - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -525,7 +697,7 @@ impl BoardInteraction for HAL<'_> { eeprom } BoardHal::V4 { eeprom, .. } => { eeprom }, - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -568,7 +740,7 @@ impl BoardInteraction for HAL<'_> { eeprom } BoardHal::V4 { eeprom, .. } => { eeprom } - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -581,7 +753,7 @@ impl BoardInteraction for HAL<'_> { let iter = (current_page % 8) as usize; if iter != lastiter { for i in 0..PLANT_COUNT { - self.fault(i, iter == i); + let _ = self.fault(i, iter == i); } lastiter = iter; } @@ -612,78 +784,11 @@ impl BoardInteraction for HAL<'_> { } } } - fn list_files(&self) -> FileList { - let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap(); - - let mut file_system_corrupt = None; - - let mut iter_error = None; - let mut result = Vec::new(); - - let filepath = Path::new(BASE_PATH); - let read_dir = fs::read_dir(filepath); - match read_dir { - OkStd(read_dir) => { - for item in read_dir { - match item { - OkStd(file) => { - let f = FileInfo { - filename: file.file_name().into_string().unwrap(), - size: file - .metadata() - .and_then(|it| Result::Ok(it.len())) - .unwrap_or_default() - as usize, - }; - result.push(f); - } - Err(err) => { - iter_error = Some(format!("{err:?}")); - break; - } - } - } - } - Err(err) => { - file_system_corrupt = Some(format!("{err:?}")); - } - } - let mut total: usize = 0; - let mut used: usize = 0; - unsafe { - esp_spiffs_info(storage.as_ptr(), &mut total, &mut used); - } - - FileList { - total, - used, - file_system_corrupt, - files: result, - iter_error, - } - } - fn delete_file(&self, filename: &str) -> Result<()> { - let filepath = Path::new(BASE_PATH).join(Path::new(filename)); - match fs::remove_file(filepath) { - OkStd(_) => Ok(()), - Err(err) => { - bail!(format!("{err:?}")) - } - } - } - 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)? - } else { - File::open(filepath)? - }) - } 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() } - plant_hal::BoardHal::Initial {} => { + plant_hal::BoardHal::Initial { .. } => { false } } @@ -692,10 +797,18 @@ impl BoardInteraction for HAL<'_> { fn water_temperature_c(&mut self) -> Result { let mut delay = Delay::new_default(); - self.esp.one_wire_bus + let one_wire_bus = match &mut self.board_hal { + BoardHal::V3 { one_wire_bus, .. } => one_wire_bus, + BoardHal::V4 { one_wire_bus, .. } => one_wire_bus, + &mut plant_hal::BoardHal::Initial { .. } => { + bail!("Board not configured yet") + } + }; + + one_wire_bus .reset(&mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; - let first = self.esp.one_wire_bus.devices(false, &mut delay).next(); + let first = one_wire_bus.devices(false, &mut delay).next(); if first.is_none() { bail!("Not found any one wire Ds18b20"); } @@ -707,11 +820,11 @@ impl BoardInteraction for HAL<'_> { .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; water_temp_sensor - .start_temp_measurement(&mut self.esp.one_wire_bus, &mut delay) + .start_temp_measurement(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.esp.one_wire_bus, &mut delay) + .read_data(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"); @@ -723,7 +836,7 @@ impl BoardInteraction for HAL<'_> { 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) } - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -759,7 +872,7 @@ impl BoardInteraction for HAL<'_> { let light = match &mut self.board_hal { BoardHal::V3 { light, .. } => light, BoardHal::V4 { light, .. } => light, - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } }; @@ -789,7 +902,7 @@ impl BoardInteraction for HAL<'_> { BoardHal::V4 { .. } => { bail!("Not yet implemented") }, - &plant_hal::BoardHal::Initial {} => { + &plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } } @@ -835,7 +948,7 @@ impl BoardInteraction for HAL<'_> { BoardHal::V4 { .. } => { bail!("Not yet implemented") } - &plant_hal::BoardHal::Initial {} => { + &plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } } @@ -853,7 +966,7 @@ impl BoardInteraction for HAL<'_> { BoardHal::V4 { main_pump, .. } => { main_pump.set_state(enable.into())?; } - &mut plant_hal::BoardHal::Initial { } => { + &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") } } @@ -883,11 +996,11 @@ impl BoardInteraction for HAL<'_> { } fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { match &mut self.board_hal { - V3{ shift_register, .. } => { + V3{ signal_counter, 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()?; + signal_counter.counter_pause()?; + signal_counter.counter_clear()?; //Disable all shift_register.decompose()[MS_4].set_high()?; @@ -952,13 +1065,13 @@ impl BoardInteraction for HAL<'_> { //give some time to stabilize delay.delay_ms(10); - self.esp.signal_counter.counter_resume()?; + signal_counter.counter_resume()?; delay.delay_ms(measurement); - self.esp.signal_counter.counter_pause()?; + 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 unscaled = signal_counter.get_counter_value()? as i32; let hz = unscaled as f32 * factor; log( LogMessage::RawMeasure, @@ -983,19 +1096,24 @@ impl BoardInteraction for HAL<'_> { } 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}, - &mut plant_hal::BoardHal::Initial { } => { - return - } + 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 wifi_ap(&mut self, ap_ssid: Option>) -> Result<()> { - let ssid = - ap_ssid.unwrap_or(heapless::String::from_str("PlantCtrl Emergency Mode").unwrap()); + fn wifi_ap(&mut self) -> Result<()> { + let ssid = match self.board_hal { + Initial { .. } => { + //this mode is only used if no config file is found, or it is unparseable + heapless::String::from_str("PlantCtrl Emergency Mode").unwrap() + }, + _ => { + self.config.network.ap_ssid.clone() + } + }; let apconfig = AccessPointConfiguration { ssid, auth_method: AuthMethod::None, @@ -1008,11 +1126,12 @@ impl BoardInteraction for HAL<'_> { Ok(()) } fn wifi( - &mut self, - ssid: heapless::String<32>, - password: Option>, - max_wait: u32, + &mut self ) -> Result { + let ssid = self.config.network.ssid.clone().ok_or(anyhow!("No ssid configured"))?; + let password = self.config.network.password.clone(); + let max_wait = self.config.network.max_wait; + 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 @@ -1050,7 +1169,7 @@ impl BoardInteraction for HAL<'_> { bail!("Did not manage wifi connection within timeout"); } } - println!("Should be connected now"); + println!("Should be connected now, waiting for link to be up"); while !self.esp.wifi_driver.is_up()? { delay.delay_ms(250); @@ -1067,51 +1186,12 @@ impl BoardInteraction for HAL<'_> { log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}")); Ok(address) } - 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 { - base_path: base_path.as_ptr(), - partition_label: storage.as_ptr(), - max_files: 5, - format_if_mount_failed: true, - }; - - //TODO check fielsystem esp_spiffs_check - - unsafe { - esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?; - Ok(()) - } - } - fn file_system_size(&mut self) -> Result { - let storage = CString::new(SPIFFS_PARTITION_NAME)?; - let mut total_size = 0; - let mut used_size = 0; - unsafe { - esp_idf_sys::esp!(esp_spiffs_info( - storage.as_ptr(), - &mut total_size, - &mut used_size - ))?; - } - Ok(FileSystemSizeInfo { - total_size, - used_size, - free_size: total_size - used_size, - }) - } fn mode_override_pressed(&mut self) -> bool { self.esp.boot_button.get_level() == Level::Low } fn factory_reset(&mut self) -> Result<()> { println!("factory resetting"); - let config = Path::new(CONFIG_FILE); - if config.exists() { - println!("Removing config"); - fs::remove_file(config)?; - } - + self.esp.delete_config()?; //destroy backup header let dummy: [u8; 0] = []; self.backup_config(&dummy)?; @@ -1120,9 +1200,9 @@ impl BoardInteraction for HAL<'_> { } fn get_rtc_time(&mut self) -> Result> { let rtc = match &mut self.board_hal { - BoardHal::V3 { rtc, .. } => {rtc} - BoardHal::V4 { rtc, .. } => {rtc}, - &mut plant_hal::BoardHal::Initial { } => { + V3 { rtc, .. } => {rtc} + V4 { rtc, .. } => {rtc}, + Initial { .. } => { bail!("Board not configured yet") } }; @@ -1135,9 +1215,9 @@ impl BoardInteraction for HAL<'_> { } fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { let rtc = match &mut self.board_hal { - BoardHal::V3 { rtc, .. } => {rtc} - BoardHal::V4 { rtc, .. } => {rtc} - &mut plant_hal::BoardHal::Initial { } => { + V3 { rtc, .. } => {rtc} + V4 { rtc, .. } => {rtc} + Initial { .. } => { bail!("Board not configured yet") } }; @@ -1149,17 +1229,6 @@ impl BoardInteraction for HAL<'_> { } } } - fn get_config(&mut self) -> Result { - let cfg = File::open(CONFIG_FILE)?; - let config: PlantControllerConfig = serde_json::from_reader(cfg)?; - Ok(config) - } - 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(()) - } fn wifi_scan(&mut self) -> Result> { self.esp.wifi_driver.start_scan( &ScanConfig { @@ -1220,8 +1289,8 @@ impl BoardInteraction for HAL<'_> { Delay::new_default().delay_ms(10); Ok(()) } - fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> { - let base_topic = config + fn mqtt(&mut self) -> Result<()> { + let base_topic = self.config .network .base_topic .as_ref() @@ -1229,7 +1298,7 @@ impl BoardInteraction for HAL<'_> { if base_topic.is_empty() { bail!("Mqtt base_topic was empty") } - let mqtt_url = config + let mqtt_url = self.config .network .mqtt_url .as_ref() @@ -1378,7 +1447,6 @@ impl BoardInteraction for HAL<'_> { } fn mqtt_publish( &mut self, - config: &PlantControllerConfig, subtopic: &str, message: &[u8], ) -> Result<()> { @@ -1396,7 +1464,7 @@ impl BoardInteraction for HAL<'_> { 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()) + .push_str(self.config.network.base_topic.as_ref().unwrap()) .is_err() { println!("Some error assembling full_topic 1"); @@ -1458,9 +1526,6 @@ pub trait BoardInteraction { 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; @@ -1481,28 +1546,20 @@ pub trait BoardInteraction { 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_ap(&mut self) -> 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(&mut self) -> Result<()>; fn mqtt_publish( &mut self, - config: &PlantControllerConfig, subtopic: &str, message: &[u8], ) -> Result<()>; @@ -1511,7 +1568,7 @@ pub trait BoardInteraction { } -fn print_battery( +fn print_battery_bq34z100( battery_driver: &mut Bq34z100g1Driver>, Delay>, ) -> Result<(), Bq34Z100Error> { println!("Try communicating with battery"); @@ -1569,6 +1626,42 @@ fn print_battery( Result::Ok(()) } + +pub struct FreePeripherals { + pub gpio0: Gpio0, + pub gpio1: Gpio1, + pub gpio2: Gpio2, + pub gpio3: Gpio3, + pub gpio4: Gpio4, + pub gpio5: Gpio5, + pub gpio6: Gpio6, + pub gpio7: Gpio7, + pub gpio8: Gpio8, + pub gpio10: Gpio10, + pub gpio11: Gpio11, + pub gpio12: Gpio12, + pub gpio13: Gpio13, + pub gpio14: Gpio14, + pub gpio15: Gpio15, + pub gpio16: Gpio16, + pub gpio17: Gpio17, + pub gpio18: Gpio18, + pub gpio19: Gpio19, + pub gpio20: Gpio20, + pub gpio21: Gpio21, + pub gpio22: Gpio22, + pub gpio23: Gpio23, + pub gpio24: Gpio24, + pub gpio25: Gpio25, + pub gpio26: Gpio26, + pub gpio27: Gpio27, + pub gpio28: Gpio28, + pub gpio29: Gpio29, + pub gpio30: Gpio30, + pub pcnt0: PCNT0, + pub adc1: ADC1, +} + pub static I2C_DRIVER: Lazy>> = Lazy::new(PlantHal::create_i2c); impl PlantHal { fn create_i2c() -> Mutex> { @@ -1586,77 +1679,59 @@ impl PlantHal { Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) } - pub fn create_v3() -> 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 clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?; - clock.set_pull(Pull::Floating)?; - let mut latch = PinDriver::input_output(peripherals.pins.gpio3.downgrade())?; - latch.set_pull(Pull::Floating)?; - let mut data = PinDriver::input_output(peripherals.pins.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 mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; + boot_button.set_pull(Pull::Floating)?; - 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"); - let mut battery_driver = Bq34z100g1Driver { - i2c: MutexDevice::new(&I2C_DRIVER), - delay: Delay::new(0), - flash_block_data: [0; 32], + let free_pins = FreePeripherals { + adc1: peripherals.adc1, + pcnt0: peripherals.pcnt0, + gpio0: peripherals.pins.gpio0, + gpio1: peripherals.pins.gpio1, + gpio2: peripherals.pins.gpio2, + gpio3: peripherals.pins.gpio3, + gpio4: peripherals.pins.gpio4, + gpio5: peripherals.pins.gpio5, + gpio6: peripherals.pins.gpio6, + gpio7: peripherals.pins.gpio7, + gpio8: peripherals.pins.gpio8, + gpio10: peripherals.pins.gpio10, + gpio11: peripherals.pins.gpio11, + gpio12: peripherals.pins.gpio12, + gpio13: peripherals.pins.gpio13, + gpio14: peripherals.pins.gpio14, + gpio15: peripherals.pins.gpio15, + gpio16: peripherals.pins.gpio16, + gpio17: peripherals.pins.gpio17, + gpio18: peripherals.pins.gpio18, + gpio19: peripherals.pins.gpio19, + gpio20: peripherals.pins.gpio20, + gpio21: peripherals.pins.gpio21, + gpio22: peripherals.pins.gpio22, + gpio23: peripherals.pins.gpio23, + gpio24: peripherals.pins.gpio24, + gpio25: peripherals.pins.gpio25, + gpio26: peripherals.pins.gpio26, + gpio27: peripherals.pins.gpio27, + gpio28: peripherals.pins.gpio28, + gpio29: peripherals.pins.gpio29, + gpio30: peripherals.pins.gpio30, }; - 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 esp = ESP { + mqtt_client: None, + wifi_driver, + boot_button }; - 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(); - match rtc_time { - OkStd(tt) => { - println!("Rtc Module reports time at UTC {}", tt); - } - Err(err) => { - println!("Rtc Module could not be read {:?}", err); - } - } - match eeprom.read_byte(0) { - OkStd(byte) => { - println!("Read first byte with status {}", byte); - } - Err(err) => { - println!("Eeprom could not read first byte {:?}", err); - } - } //init,reset rtc memory depending on cause let mut init_rtc_store: bool = false; @@ -1689,57 +1764,178 @@ impl PlantHal { "", &format!("{reasons:?}"), ); - if init_rtc_store { - unsafe { - LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT]; - CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT]; - LOW_VOLTAGE_DETECTED = false; - crate::log::init(); - RESTART_TO_CONF = to_config_mode; - }; - } else { - unsafe { - if to_config_mode { - RESTART_TO_CONF = true; + + 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 hal = match config { + Result::Ok(config) => { + let board_hal : BoardHal = match config.board_hardware { + + 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.battery_hardware { + BatteryBoardVersion::Disabled => { BatteryMonitor::Disabled {}} + BatteryBoardVersion::BQ34Z100G1 => { + let mut battery_driver = Bq34z100g1Driver { + i2c: MutexDevice::new(&I2C_DRIVER), + delay: Delay::new(0), + flash_block_data: [0; 32], + }; + let status = print_battery_bq34z100(&mut battery_driver); + match status { + OkStd(_) => {} + Err(err) => { + log( + LogMessage::BatteryCommunicationError, + 0u32, + 0, + "", + &format!("{err:?})"), + ); + } + } + BatteryMonitor::BQ34Z100G1 { battery_driver } + } + BatteryBoardVersion::WchI2cSlave => { + BatteryMonitor::WchI2cSlave {} + } + }; + + + HAL{ + config, + board_hal, + esp, + battery_monitor } + } + Err(err) => { log( - LogMessage::RestartToConfig, - RESTART_TO_CONF as u32, + LogMessage::ConfigModeMissingConfig, + 0, 0, "", - "", + &err.to_string(), ); - log( - LogMessage::LowVoltage, - LOW_VOLTAGE_DETECTED as u32, - 0, - "", - "", - ); - for i in 0..PLANT_COUNT { - println!( - "LAST_WATERING_TIMESTAMP[{}] = UTC {}", - i, LAST_WATERING_TIMESTAMP[i] - ); - } - for i in 0..PLANT_COUNT { - println!( - "CONSECUTIVE_WATERING_PLANT[{}] = {}", - i, CONSECUTIVE_WATERING_PLANT[i] - ); + + let mut 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 {}, } } + }; + + Ok(Mutex::new(hal)) + } + + fn create_v4(peripherals: FreePeripherals) -> Result> { + let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?; + general_fault.set_pull(Pull::Floating)?; + general_fault.set_low()?; + + + //temp remove me + general_fault.set_high()?; + + bail!("not implemented"); + } + + 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()[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 counter_unit1 = PcntDriver::new( + + let mut signal_counter = PcntDriver::new( peripherals.pcnt0, - Some(peripherals.pins.gpio22), + Some(peripherals.gpio22), Option::::None, Option::::None, Option::::None, )?; - counter_unit1.channel_config( + signal_counter.channel_config( PcntChannel::Channel0, PinIndex::Pin0, PinIndex::Pin1, @@ -1753,9 +1949,7 @@ impl PlantHal { }, )?; - let sys_loop = EspSystemEventLoop::take()?; - let nvs = EspDefaultNvsPartition::take()?; - let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; + let adc_config = AdcChannelConfig { attenuation: attenuation::DB_11, @@ -1764,73 +1958,50 @@ impl PlantHal { }; let tank_driver = AdcDriver::new(peripherals.adc1)?; let tank_channel: AdcChannelDriver> = - AdcChannelDriver::new(tank_driver, peripherals.pins.gpio5, &adc_config)?; + AdcChannelDriver::new(tank_driver, peripherals.gpio5, &adc_config)?; - let mut solar_is_day = PinDriver::input(peripherals.pins.gpio7.downgrade())?; + let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?; solar_is_day.set_pull(Pull::Floating)?; - let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; - boot_button.set_pull(Pull::Floating)?; - let mut light = PinDriver::input_output(peripherals.pins.gpio10.downgrade())?; + + let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?; light.set_pull(Pull::Floating)?; - let mut main_pump = PinDriver::input_output(peripherals.pins.gpio2.downgrade())?; + 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.pins.gpio11.downgrade())?; + let mut tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?; tank_power.set_pull(Pull::Floating)?; - let mut general_fault = PinDriver::input_output(peripherals.pins.gpio6.downgrade())?; + 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 status = print_battery(&mut battery_driver); - match status { - OkStd(_) => {} - Err(err) => { - log( - LogMessage::BatteryCommunicationError, - 0u32, - 0, - "", - &format!("{err:?})"), - ); - } - } - let mut shift_register_enable_invert = PinDriver::output(peripherals.pins.gpio21.downgrade())?; + + 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()) }; - 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 - }, - }); - Ok(rv) + + 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/plant_state.rs b/rust/src/plant_state.rs index d531995..e0e67ef 100644 --- a/rust/src/plant_state.rs +++ b/rust/src/plant_state.rs @@ -114,15 +114,14 @@ fn map_range_moisture( impl PlantState { pub fn read_hardware_state( plant_id: usize, - board: &mut plant_hal::HAL, - config: &PlantConfig, + board: &mut plant_hal::HAL ) -> Self { - let sensor_a = if config.sensor_a { + let sensor_a = if board.config.plants[plant_id].sensor_a { match board.measure_moisture_hz(plant_id, plant_hal::Sensor::A) { Ok(raw) => match map_range_moisture( raw, - config.moisture_sensor_min_frequency, - config.moisture_sensor_max_frequency, + board.config.plants[plant_id].moisture_sensor_min_frequency, + board.config.plants[plant_id].moisture_sensor_max_frequency, ) { Ok(moisture_percent) => MoistureSensorState::MoistureValue { raw_hz: raw, @@ -138,12 +137,12 @@ impl PlantState { MoistureSensorState::Disabled }; - let sensor_b = if config.sensor_b { + let sensor_b = if board.config.plants[plant_id].sensor_b { match board.measure_moisture_hz(plant_id, plant_hal::Sensor::B) { Ok(raw) => match map_range_moisture( raw, - config.moisture_sensor_min_frequency, - config.moisture_sensor_max_frequency, + board.config.plants[plant_id].moisture_sensor_min_frequency, + board.config.plants[plant_id].moisture_sensor_max_frequency, ) { Ok(moisture_percent) => MoistureSensorState::MoistureValue { raw_hz: raw, diff --git a/rust/src/tank.rs b/rust/src/tank.rs index 8f0bd9e..9217e21 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -1,7 +1,7 @@ use serde::Serialize; +use crate::config::TankConfig; 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; @@ -156,10 +156,9 @@ impl TankState { } pub fn determine_tank_state( - board: &mut std::sync::MutexGuard<'_, HAL<'_>>, - config: &PlantControllerConfig, + board: &mut std::sync::MutexGuard<'_, HAL<'_>> ) -> TankState { - if config.tank.tank_sensor_enabled { + if board.config.tank.tank_sensor_enabled { match board.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 5b3d483..a76f6d2 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -114,11 +114,9 @@ fn get_timezones( fn get_live_moisture( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let config = board.get_config().unwrap(); - + let mut board = BOARD_ACCESS.lock().expect("Should never fail"); let plant_state = Vec::from_iter( - (0..PLANT_COUNT).map(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i])), + (0..PLANT_COUNT).map(|i| PlantState::read_hardware_state(i, &mut board)), ); let a = Vec::from_iter( plant_state @@ -159,11 +157,8 @@ fn get_live_moisture( fn get_config( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let json = match board.get_config() { - Ok(config) => serde_json::to_string(&config)?, - Err(_) => serde_json::to_string(&PlantControllerConfig::default())?, - }; + let board = BOARD_ACCESS.lock().expect("Should never fail"); + let json = serde_json::to_string(&board.config)?; anyhow::Ok(Some(json)) } @@ -193,7 +188,7 @@ fn get_backup_config( fn backup_info( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); + let mut board = BOARD_ACCESS.lock().expect("Should never fail"); let header = board.get_backup_info(); let json = match header { Ok(h) => { @@ -222,7 +217,9 @@ 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.set_config(&config)?; + board.esp.set_config(&config)?; + + board.config = config; anyhow::Ok(Some("saved".to_owned())) } @@ -268,12 +265,11 @@ fn tank_info( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let mut board = BOARD_ACCESS.lock().unwrap(); - let config = board.get_config()?; - let tank_info = determine_tank_state(&mut board, &config); + let tank_info = determine_tank_state(&mut board); //should be multsampled let water_temp = board.water_temperature_c(); Ok(Some(serde_json::to_string( - &tank_info.as_mqtt_info(&config.tank, &water_temp), + &tank_info.as_mqtt_info(&board.config.tank, &water_temp), )?)) } @@ -302,8 +298,8 @@ fn wifi_scan( fn list_files( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let board = BOARD_ACCESS.lock().unwrap(); - let result = board.list_files(); + 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 file_list_json = serde_json::to_string(&result)?; anyhow::Ok(Some(file_list_json)) } @@ -493,6 +489,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { let file_handle = BOARD_ACCESS .lock() .unwrap() + .esp .get_file_handle(&filename, false); match file_handle { Ok(mut file_handle) => { @@ -529,7 +526,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { .fn_handler("/file", Method::Post, move |mut request| { let filename = query_param(request.uri(), "filename").unwrap(); let lock = BOARD_ACCESS.lock().unwrap(); - let file_handle = lock.get_file_handle(&filename, true); + let file_handle = lock.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) => { @@ -573,7 +570,7 @@ pub fn httpd(reboot_now: Arc) -> Box> { let filename = query_param(request.uri(), "filename").unwrap(); let copy = filename.clone(); let board = BOARD_ACCESS.lock().unwrap(); - match board.delete_file(&filename) { + match board.esp.delete_file(&filename) { Ok(_) => { let info = format!("Deleted file {copy}"); cors_response(request, 200, &info)?; diff --git a/website/themes/blowfish b/website/themes/blowfish index 26d1205..1d21656 160000 --- a/website/themes/blowfish +++ b/website/themes/blowfish @@ -1 +1 @@ -Subproject commit 26d1205439b460bee960fd4c29f3c5c20948875f +Subproject commit 1d21656d5efcf6a6b247245d057bf553f3209f39 -- 2.47.1 From 05400c7c4a0ead9cea3e289e676f59b435828db2 Mon Sep 17 00:00:00 2001 From: Empire Date: Sat, 14 Jun 2025 00:40:47 +0200 Subject: [PATCH 4/4] v4 board impl --- rust/Cargo.toml | 1 + rust/src/config.rs | 9 +- rust/src/plant_hal.rs | 375 +++++++++++++++++++------ rust/src/webserver/webserver.rs | 3 +- rust/src_webpack/src/api.ts | 66 +++-- rust/src_webpack/src/batteryview.ts | 1 + rust/src_webpack/src/fileview.ts | 2 +- rust/src_webpack/src/hardware.html | 20 ++ rust/src_webpack/src/hardware.ts | 45 +++ rust/src_webpack/src/log.ts | 1 + rust/src_webpack/src/main.html | 4 + rust/src_webpack/src/main.ts | 32 ++- rust/src_webpack/src/network.html | 5 + rust/src_webpack/src/network.ts | 7 + rust/src_webpack/src/nightlightview.ts | 1 + rust/src_webpack/src/ota.html | 2 +- rust/src_webpack/src/ota.ts | 7 + rust/src_webpack/src/plant.ts | 2 + rust/src_webpack/src/submitView.ts | 1 + rust/src_webpack/src/tankview.ts | 1 + rust/src_webpack/webpack.config.js | 2 +- 21 files changed, 471 insertions(+), 116 deletions(-) create mode 100644 rust/src_webpack/src/hardware.html create mode 100644 rust/src_webpack/src/hardware.ts diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 593cc2c..cbfa9d1 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -88,6 +88,7 @@ text-template = "0.1.0" strum_macros = "0.27.0" esp-ota = { version = "0.2.2", features = ["log"] } unit-enum = "1.4.1" +pca9535 = { version = "2.0.0", features = ["std"] } [patch.crates-io] diff --git a/rust/src/config.rs b/rust/src/config.rs index f103e02..910ce67 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -88,11 +88,16 @@ pub enum BoardVersion{ V4 } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +pub struct BoardHardware { + pub board: BoardVersion, + pub battery: BatteryBoardVersion, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] #[serde(default)] pub struct PlantControllerConfig { - pub board_hardware: BoardVersion, - pub battery_hardware: BatteryBoardVersion, + pub hardware: BoardHardware, pub network: NetworkConfig, pub tank: TankConfig, pub night_lamp: NightLampConfig, diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index b5a76c4..c1201c5 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -57,7 +57,7 @@ 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 crate::config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}; use crate::log::log; use crate::plant_hal::BoardHal::{Initial, V3, V4}; @@ -78,12 +78,39 @@ 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 SENSOR_ON: usize = 12; -const MS_1: usize = 13; +#[non_exhaustive] +struct V3Constants; + +impl V3Constants { + 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; @@ -96,23 +123,7 @@ const FAULT_4: usize = 21; const FAULT_1: usize = 22; const FAULT_2: usize = 23; -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 X25: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); @@ -382,13 +393,9 @@ pub enum BoardHal<'a>{ 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>, rtc: Ds323x>>, ds323x::ic::DS3231>, @@ -398,6 +405,9 @@ pub enum BoardHal<'a>{ eeprom24x::addr_size::TwoBytes, eeprom24x::unique_serial::No, >, + general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, + pump_expander: Pca9535Immediate>>, + sensor_expander: Pca9535Immediate>>, } } @@ -882,9 +892,9 @@ impl BoardInteraction for HAL<'_> { unsafe { gpio_hold_en(light.pin()) }; Ok(()) } - fn pump(& self, plant: usize, enable: bool) -> Result<()> { - match & self.board_hal { - BoardHal::V3 { shift_register, .. } => { + fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { + match &mut self.board_hal { + V3 { shift_register, .. } => { let index = match plant { 0 => PUMP1_BIT, 1 => PUMP2_BIT, @@ -899,10 +909,14 @@ impl BoardInteraction for HAL<'_> { //currently infallible error, keep for future as result anyway shift_register.decompose()[index].set_state(enable.into())?; } - BoardHal::V4 { .. } => { - bail!("Not yet implemented") + 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()?)?; + } }, - &plant_hal::BoardHal::Initial { .. } => { + &mut Initial { .. } => { bail!("Board not configured yet") } } @@ -926,8 +940,8 @@ impl BoardInteraction for HAL<'_> { fn consecutive_pump_count(&mut self, plant: usize) -> u32 { unsafe { CONSECUTIVE_WATERING_PLANT[plant] } } - fn fault(& self, plant: usize, enable: bool) -> Result<()>{ - match & self.board_hal { + fn fault(&mut self, plant: usize, enable: bool) -> Result<()>{ + match &mut self.board_hal { V3 { shift_register, .. } => { let index = match plant { 0 => FAULT_1, @@ -942,29 +956,31 @@ impl BoardInteraction for HAL<'_> { }; shift_register.decompose()[index] .set_state(enable.into())?; - Ok(()) + } + 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()?)?; + } } - BoardHal::V4 { .. } => { - bail!("Not yet implemented") - } - &plant_hal::BoardHal::Initial { .. } => { + &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 { - BoardHal::V3 { main_pump, .. } => { + V3 { main_pump, .. } => { main_pump.set_state(enable.into())?; } - BoardHal::V4 { main_pump, .. } => { - main_pump.set_state(enable.into())?; + V4 { .. } => { + //does not exist in v4, ignore it } &mut plant_hal::BoardHal::Initial { .. } => { bail!("Board not configured yet") @@ -1002,39 +1018,39 @@ impl BoardInteraction for HAL<'_> { signal_counter.counter_pause()?; signal_counter.counter_clear()?; //Disable all - shift_register.decompose()[MS_4].set_high()?; + shift_register.decompose()[V3Constants::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, + 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 => 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, + 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()[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]; + 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 { @@ -1056,8 +1072,8 @@ impl BoardInteraction for HAL<'_> { pin_3.set_low()?; } - shift_register.decompose()[MS_4].set_low()?; - shift_register.decompose()[SENSOR_ON].set_high()?; + 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? @@ -1068,8 +1084,8 @@ impl BoardInteraction for HAL<'_> { signal_counter.counter_resume()?; delay.delay_ms(measurement); signal_counter.counter_pause()?; - shift_register.decompose()[MS_4].set_high()?; - shift_register.decompose()[SENSOR_ON].set_low()?; + 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; @@ -1088,6 +1104,91 @@ impl BoardInteraction for HAL<'_> { 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") } @@ -1534,12 +1635,12 @@ pub trait BoardInteraction { 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 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(&self, plant: usize, enable: bool) -> Result<()>; + 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>; @@ -1771,8 +1872,7 @@ impl PlantHal { let config = esp.get_config(); let hal = match config { Result::Ok(config) => { - let board_hal : BoardHal = match config.board_hardware { - + 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)?; @@ -1787,7 +1887,7 @@ impl PlantHal { BoardVersion::V4 => {PlantHal::create_v4(free_pins)?} }; - let battery_monitor : BatteryMonitor = match config.battery_hardware { + let battery_monitor : BatteryMonitor = match config.hardware.battery { BatteryBoardVersion::Disabled => { BatteryMonitor::Disabled {}} BatteryBoardVersion::BQ34Z100G1 => { let mut battery_driver = Bq34z100g1Driver { @@ -1849,15 +1949,124 @@ impl PlantHal { } 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())?; general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; - //temp remove me - general_fault.set_high()?; - bail!("not implemented"); + 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 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 { + tank_channel, + solar_is_day, + signal_counter, + light, + tank_power, + one_wire_bus, + rtc, + eeprom, + general_fault, + pump_expander, + sensor_expander + }) } fn create_v3(peripherals: FreePeripherals) -> Result> { @@ -1879,16 +2088,16 @@ impl PlantHal { let charging = &mut shift_register.decompose()[CHARGING]; charging.set_high()?; - let ms0 = &mut shift_register.decompose()[MS_0]; + let ms0 = &mut shift_register.decompose()[V3Constants::MS_0]; ms0.set_low()?; - let ms1 = &mut shift_register.decompose()[MS_1]; + let ms1 = &mut shift_register.decompose()[V3Constants::MS_1]; ms1.set_low()?; - let ms2 = &mut shift_register.decompose()[MS_2]; + let ms2 = &mut shift_register.decompose()[V3Constants::MS_2]; ms2.set_low()?; - let ms3 = &mut shift_register.decompose()[MS_3]; + let ms3 = &mut shift_register.decompose()[V3Constants::MS_3]; ms3.set_low()?; - let ms4 = &mut shift_register.decompose()[MS_4]; + let ms4 = &mut shift_register.decompose()[V3Constants::MS_4]; ms4.set_high()?; println!("Init battery driver"); diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index a76f6d2..5d0b8a1 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -216,6 +216,7 @@ fn set_config( ) -> Result, anyhow::Error> { 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)?; @@ -525,7 +526,7 @@ 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 lock = BOARD_ACCESS.lock().unwrap(); + let mut lock = BOARD_ACCESS.lock().unwrap(); let file_handle = lock.esp.get_file_handle(&filename, true); match file_handle { //TODO get free filesystem size, check against during write if not to large diff --git a/rust/src_webpack/src/api.ts b/rust/src_webpack/src/api.ts index 531be75..faff2ca 100644 --- a/rust/src_webpack/src/api.ts +++ b/rust/src_webpack/src/api.ts @@ -1,6 +1,6 @@ -interface LogArray extends Array{} +export interface LogArray extends Array{} -interface LogEntry { +export interface LogEntry { timestamp: string, message_id: number, a: number, @@ -9,27 +9,28 @@ interface LogEntry { txt_long: string } -interface LogLocalisation extends Array{} +export interface LogLocalisation extends Array{} -interface LogLocalisationEntry { +export interface LogLocalisationEntry { msg_type: string, message: string } -interface BackupHeader { +export interface BackupHeader { timestamp: string, size: number } -interface NetworkConfig { +export interface NetworkConfig { ap_ssid: string, ssid: string, password: string, mqtt_url: string, - base_topic: string + base_topic: string, + max_wait: number } -interface FileList { +export interface FileList { total: number, used: number, files: FileInfo[], @@ -37,12 +38,12 @@ interface FileList { iter_error: string, } -interface FileInfo{ +export interface FileInfo{ filename: string, size: number, } -interface NightLampConfig { +export interface NightLampConfig { enabled: boolean, night_lamp_hour_start: number, night_lamp_hour_end: number, @@ -51,11 +52,11 @@ interface NightLampConfig { low_soc_restore: number } -interface NightLampCommand { +export interface NightLampCommand { active: boolean } -interface TankConfig { +export interface TankConfig { tank_sensor_enabled: boolean, tank_allow_pumping_if_sensor_error: boolean, tank_useable_ml: number, @@ -64,7 +65,26 @@ interface TankConfig { tank_full_percent: number, } -interface PlantControllerConfig { + +export enum BatteryBoardVersion { + Disabled = "Disabled", + BQ34Z100G1 = "BQ34Z100G1", + WchI2cSlave = "WchI2cSlave" +} +export enum BoardVersion{ + INITIAL = "INITIAL", + V3 = "V3", + V4 = "V4" +} + +export interface BoardHardware { + board: BoardVersion, + battery: BatteryBoardVersion, +} + +export interface PlantControllerConfig { + hardware: BoardHardware, + network: NetworkConfig, tank: TankConfig, night_lamp: NightLampConfig, @@ -72,7 +92,7 @@ interface PlantControllerConfig { timezone?: string, } -interface PlantConfig { +export interface PlantConfig { mode: string, target_moisture: number, pump_time_s: number, @@ -88,35 +108,35 @@ interface PlantConfig { } -interface SSIDList { +export interface SSIDList { ssids: [string] } -interface TestPump { +export interface TestPump { pump: number } -interface SetTime { +export interface SetTime { time: string } -interface GetTime { +export interface GetTime { rtc: string, native: string } -interface Moistures { +export interface Moistures { moisture_a: [string], moisture_b: [string], } -interface VersionInfo { +export interface VersionInfo { git_hash: string, build_time: string, partition: string } -interface BatteryState { +export interface BatteryState { temperature: string voltage_milli_volt: string, current_milli_ampere: string, @@ -127,7 +147,7 @@ interface BatteryState { state_of_health: string } -interface TankInfo { +export interface TankInfo { /// is there enough water in the tank enough_water: boolean, /// warning that water needs to be refilled soon @@ -145,4 +165,4 @@ interface TankInfo { /// water temperature water_temp: number | null, temp_sensor_error: string | null -} \ No newline at end of file +} diff --git a/rust/src_webpack/src/batteryview.ts b/rust/src_webpack/src/batteryview.ts index 5b0b6cf..1dcbaeb 100644 --- a/rust/src_webpack/src/batteryview.ts +++ b/rust/src_webpack/src/batteryview.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {BatteryState} from "./api"; export class BatteryView{ voltage_milli_volt: HTMLSpanElement; diff --git a/rust/src_webpack/src/fileview.ts b/rust/src_webpack/src/fileview.ts index 2d5e870..4a30f72 100644 --- a/rust/src_webpack/src/fileview.ts +++ b/rust/src_webpack/src/fileview.ts @@ -1,5 +1,5 @@ import {Controller} from "./main"; - +import {FileInfo, FileList} from "./api"; const regex = /[^a-zA-Z0-9_.]/g; function sanitize(str:string){ diff --git a/rust/src_webpack/src/hardware.html b/rust/src_webpack/src/hardware.html new file mode 100644 index 0000000..71a7f32 --- /dev/null +++ b/rust/src_webpack/src/hardware.html @@ -0,0 +1,20 @@ + + +
Hardware:
+
+
BoardRevision
+ +
+
+
BatteryMonitor
+ +
diff --git a/rust/src_webpack/src/hardware.ts b/rust/src_webpack/src/hardware.ts new file mode 100644 index 0000000..ab1819a --- /dev/null +++ b/rust/src_webpack/src/hardware.ts @@ -0,0 +1,45 @@ +import { Controller } from "./main"; +import {BatteryBoardVersion, BoardHardware, BoardVersion} from "./api"; + +export class HardwareConfigView { + private readonly hardware_board_value: HTMLSelectElement; + private readonly hardware_battery_value: HTMLSelectElement; + constructor(controller:Controller){ + (document.getElementById("hardwareview") as HTMLElement).innerHTML = require('./hardware.html') as string; + + this.hardware_board_value = document.getElementById("hardware_board_value") as HTMLSelectElement; + this.hardware_board_value.onchange = controller.configChanged + + Object.keys(BoardVersion).forEach(version => { + let option = document.createElement("option"); + if (version == BoardVersion.INITIAL.toString()){ + option.selected = true + } + option.innerText = version.toString(); + this.hardware_board_value.appendChild(option); + }) + + this.hardware_battery_value = document.getElementById("hardware_battery_value") as HTMLSelectElement; + this.hardware_battery_value.onchange = controller.configChanged + Object.keys(BatteryBoardVersion).forEach(version => { + let option = document.createElement("option"); + if (version == BatteryBoardVersion.Disabled.toString()){ + option.selected = true + } + option.innerText = version.toString(); + this.hardware_battery_value.appendChild(option); + }) + } + + setConfig(hardware: BoardHardware) { + this.hardware_board_value.value = hardware.board.toString() + this.hardware_battery_value.value = hardware.battery.toString() + } + + getConfig(): BoardHardware { + return { + board : BoardVersion[this.hardware_board_value.value as keyof typeof BoardVersion], + battery : BatteryBoardVersion[this.hardware_battery_value.value as keyof typeof BatteryBoardVersion], + } + } + } \ No newline at end of file diff --git a/rust/src_webpack/src/log.ts b/rust/src_webpack/src/log.ts index b8239b6..1e38724 100644 --- a/rust/src_webpack/src/log.ts +++ b/rust/src_webpack/src/log.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {LogArray, LogLocalisation} from "./api"; export class LogView { private readonly logpanel: HTMLElement; diff --git a/rust/src_webpack/src/main.html b/rust/src_webpack/src/main.html index 8716f63..a706228 100644 --- a/rust/src_webpack/src/main.html +++ b/rust/src_webpack/src/main.html @@ -138,6 +138,10 @@
+
+
+
+
diff --git a/rust/src_webpack/src/main.ts b/rust/src_webpack/src/main.ts index ab3fc44..d34145c 100644 --- a/rust/src_webpack/src/main.ts +++ b/rust/src_webpack/src/main.ts @@ -17,6 +17,19 @@ import { OTAView } from "./ota"; import { BatteryView } from "./batteryview"; import { FileView } from './fileview'; import { LogView } from './log'; +import {HardwareConfigView} from "./hardware"; +import { + BackupHeader, + BatteryState, + GetTime, LogArray, LogLocalisation, + Moistures, + NightLampCommand, + PlantControllerConfig, + SetTime, SSIDList, TankInfo, + TestPump, + VersionInfo, + FileList +} from "./api"; export class Controller { loadTankInfo() : Promise { @@ -66,7 +79,7 @@ export class Controller { } populateTimezones(): Promise { - return fetch('/timezones') + return fetch(PUBLIC_URL+'/timezones') .then(response => response.json()) .then(json => json as string[]) .then(timezones => { @@ -268,6 +281,12 @@ export class Controller { } } + selfTest(){ + fetch(PUBLIC_URL + "/boardtest", { + method: "POST" + }) + } + testNightLamp(active: boolean){ var body: NightLampCommand = { active: active @@ -313,6 +332,7 @@ export class Controller { getConfig(): PlantControllerConfig { return { + hardware: controller.hardwareView.getConfig(), network: controller.networkView.getConfig(), tank: controller.tankView.getConfig(), night_lamp: controller.nightLampView.getConfig(), @@ -360,6 +380,7 @@ export class Controller { this.nightLampView.setConfig(current.night_lamp); this.plantViews.setConfig(current.plants); this.timeView.setTimeZone(current.timezone); + this.hardwareView.setConfig(current.hardware); } measure_moisture() { @@ -437,6 +458,7 @@ export class Controller { readonly timeView: TimeView; readonly plantViews: PlantViews; readonly networkView: NetworkConfigView; + readonly hardwareView: HardwareConfigView; readonly tankView: TankConfigView; readonly nightLampView: NightLampView; readonly submitView: SubmitView; @@ -457,6 +479,7 @@ export class Controller { this.progressview = new ProgressView(this) this.fileview = new FileView(this) this.logView = new LogView(this) + this.hardwareView = new HardwareConfigView(this) this.rebootBtn = document.getElementById("reboot") as HTMLButtonElement this.rebootBtn.onclick = () => { controller.reboot(); @@ -466,6 +489,10 @@ export class Controller { controller.exit(); } } + + selftest() { + + } } const controller = new Controller(); controller.progressview.removeProgress("rebooting"); @@ -505,9 +532,6 @@ executeTasksSequentially().then(r => { controller.progressview.removeProgress("initial") }); - - - controller.progressview.removeProgress("rebooting"); window.addEventListener("beforeunload", (event) => { diff --git a/rust/src_webpack/src/network.html b/rust/src_webpack/src/network.html index 521155f..5bdca8d 100644 --- a/rust/src_webpack/src/network.html +++ b/rust/src_webpack/src/network.html @@ -42,6 +42,11 @@
+ +
+ + +
diff --git a/rust/src_webpack/src/network.ts b/rust/src_webpack/src/network.ts index 8f04063..df1ffbd 100644 --- a/rust/src_webpack/src/network.ts +++ b/rust/src_webpack/src/network.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {NetworkConfig, SSIDList} from "./api"; export class NetworkConfigView { setScanResult(ssidList: SSIDList) { @@ -14,6 +15,7 @@ export class NetworkConfigView { private readonly password: HTMLInputElement; private readonly mqtt_url: HTMLInputElement; private readonly base_topic: HTMLInputElement; + private readonly max_wait: HTMLInputElement; private readonly ssidlist: HTMLElement; constructor(controller: Controller, publicIp: string) { @@ -28,6 +30,9 @@ export class NetworkConfigView { this.ssid.onchange = controller.configChanged this.password = (document.getElementById("password") as HTMLInputElement); this.password.onchange = controller.configChanged + this.max_wait = (document.getElementById("max_wait") as HTMLInputElement); + this.max_wait.onchange = controller.configChanged + this.mqtt_url = document.getElementById("mqtt_url") as HTMLInputElement; this.mqtt_url.onchange = controller.configChanged this.base_topic = document.getElementById("base_topic") as HTMLInputElement; @@ -47,10 +52,12 @@ export class NetworkConfigView { this.password.value = network.password; this.mqtt_url.value = network.mqtt_url; this.base_topic.value = network.base_topic; + this.max_wait.value = network.max_wait.toString(); } getConfig(): NetworkConfig { return { + max_wait: +this.max_wait.value, ap_ssid: this.ap_ssid.value, ssid: this.ssid.value ?? null, password: this.password.value ?? null, diff --git a/rust/src_webpack/src/nightlightview.ts b/rust/src_webpack/src/nightlightview.ts index d2d6417..f28d723 100644 --- a/rust/src_webpack/src/nightlightview.ts +++ b/rust/src_webpack/src/nightlightview.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {NightLampConfig} from "./api"; export class NightLampView { private readonly night_lamp_only_when_dark: HTMLInputElement; diff --git a/rust/src_webpack/src/ota.html b/rust/src_webpack/src/ota.html index 32522b9..8584893 100644 --- a/rust/src_webpack/src/ota.html +++ b/rust/src_webpack/src/ota.html @@ -37,5 +37,5 @@
- +
\ No newline at end of file diff --git a/rust/src_webpack/src/ota.ts b/rust/src_webpack/src/ota.ts index 670f9e4..923e5fb 100644 --- a/rust/src_webpack/src/ota.ts +++ b/rust/src_webpack/src/ota.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {VersionInfo} from "./api"; export class OTAView { readonly file1Upload: HTMLInputElement; @@ -9,6 +10,8 @@ export class OTAView { constructor(controller: Controller) { (document.getElementById("firmwareview") as HTMLElement).innerHTML = require("./ota.html") + let test = document.getElementById("test") as HTMLButtonElement; + this.firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement; this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement; this.firmware_partition = document.getElementById("firmware_partition") as HTMLDivElement; @@ -24,6 +27,10 @@ export class OTAView { } controller.uploadNewFirmware(selectedFile); }; + + test.onclick = () => { + controller.selftest(); + } } setVersion(versionInfo: VersionInfo) { diff --git a/rust/src_webpack/src/plant.ts b/rust/src_webpack/src/plant.ts index d71cf89..d80a120 100644 --- a/rust/src_webpack/src/plant.ts +++ b/rust/src_webpack/src/plant.ts @@ -1,3 +1,5 @@ +import {PlantConfig} from "./api"; + const PLANT_COUNT = 8; diff --git a/rust/src_webpack/src/submitView.ts b/rust/src_webpack/src/submitView.ts index d5ddbb8..e84c0d2 100644 --- a/rust/src_webpack/src/submitView.ts +++ b/rust/src_webpack/src/submitView.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {BackupHeader} from "./api"; export class SubmitView { json: HTMLDivElement; diff --git a/rust/src_webpack/src/tankview.ts b/rust/src_webpack/src/tankview.ts index f701557..d1f4de8 100644 --- a/rust/src_webpack/src/tankview.ts +++ b/rust/src_webpack/src/tankview.ts @@ -1,4 +1,5 @@ import { Controller } from "./main"; +import {TankConfig, TankInfo} from "./api"; export class TankConfigView { private readonly tank_useable_ml: HTMLInputElement; diff --git a/rust/src_webpack/webpack.config.js b/rust/src_webpack/webpack.config.js index 6386702..00e4ebc 100644 --- a/rust/src_webpack/webpack.config.js +++ b/rust/src_webpack/webpack.config.js @@ -9,7 +9,7 @@ console.log("Dev server is " + isDevServer); var host; if (isDevServer){ //ensure no trailing / - host = 'http://192.168.251.37'; + host = 'http://192.168.71.1'; } else { host = ''; } -- 2.47.1