use serde::Serialize; use crate::{ config::{PlantControllerConfig, TankConfig}, plant_hal::PlantCtrlBoard, }; const OPEN_TANK_VOLTAGE: f32 = 3.0; pub const WATER_FROZEN_THRESH: f32 = 4.0; #[derive(Debug, Clone, Serialize)] pub enum TankError { SensorDisabled, SensorMissing(f32), SensorValueError { value: f32, min: f32, max: f32 }, BoardError(String), } pub enum TankState { TankSensorPresent(f32), TankSensorError(TankError), TankSensorDisabled, } fn raw_volatge_to_divider_percent(raw_value_mv: f32) -> Result { if raw_value_mv > OPEN_TANK_VOLTAGE { return Err(TankError::SensorMissing(raw_value_mv)); } let r2 = raw_value_mv * 50.0 / (3.3 - raw_value_mv); let mut percent = r2 / 190_f32 * 100_f32; percent = percent.clamp(0.0, 100.0); Ok(percent) } fn raw_voltage_to_tank_fill_percent( raw_value_mv: f32, config: &TankConfig, ) -> Result { let divider_percent = raw_volatge_to_divider_percent(raw_value_mv)?; if divider_percent < config.tank_empty_percent.into() || divider_percent > config.tank_full_percent.into() { return Err(TankError::SensorValueError { value: divider_percent, min: config.tank_empty_percent.into(), max: config.tank_full_percent.into(), }); } Ok( (divider_percent - f32::from(config.tank_empty_percent)) * 100. / f32::from(config.tank_full_percent - config.tank_empty_percent), ) } impl TankState { pub fn left_ml(&self, config: &TankConfig) -> Result { match self { TankState::TankSensorDisabled => Err(TankError::SensorDisabled), TankState::TankSensorError(err) => Err(err.clone()), TankState::TankSensorPresent(raw_value_mv) => { let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?; Ok(config.tank_useable_ml as f32 * tank_fill_percent / 100.) } } } pub fn enough_water(&self, config: &TankConfig) -> Result { match self { TankState::TankSensorDisabled => Err(TankError::SensorDisabled), TankState::TankSensorError(err) => Err(err.clone()), TankState::TankSensorPresent(raw_value_mv) => { let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?; if tank_fill_percent > config.tank_empty_percent.into() { Ok(true) } else { Ok(false) } } } } pub fn is_enabled(&self) -> bool { matches!(self, TankState::TankSensorDisabled) } pub fn warn_level(&self, config: &TankConfig) -> Result { match self { TankState::TankSensorDisabled => Err(TankError::SensorDisabled), TankState::TankSensorError(err) => Err(err.clone()), TankState::TankSensorPresent(raw_value_mv) => { let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config); match tank_fill_percent { Ok(value) => { if value < config.tank_warn_percent.into() { Ok(true) } else { Ok(false) } } Err(err) => match err { TankError::SensorValueError { value, min, max: _ } => Ok(value < min), _ => Err(err), }, } } } } pub fn got_error(&self, config: &TankConfig) -> Option { match self { TankState::TankSensorPresent(raw_value_mv) => { raw_voltage_to_tank_fill_percent(*raw_value_mv, config).err() } TankState::TankSensorError(err) => Some(err.clone()), TankState::TankSensorDisabled => Some(TankError::SensorDisabled), } } pub fn as_mqtt_info( &self, config: &TankConfig, water_temp: Result, ) -> TankInfo { let mut tank_err: Option = None; let left_ml = match self.left_ml(config) { Err(err) => { tank_err = Some(err); None } Ok(left_ml) => Some(left_ml), }; let enough_water = self.enough_water(config).unwrap_or(false); //NOTE: is this correct if there is an error assume not enough water? let warn_level = self.warn_level(config).unwrap_or(false); //NOTE: should no warn level be triggered if there is an error? let raw = match self { TankState::TankSensorDisabled | TankState::TankSensorError(_) => None, TankState::TankSensorPresent(raw_value_mv) => Some(*raw_value_mv), }; let percent = match raw { Some(r) => raw_voltage_to_tank_fill_percent(r, config).ok(), None => None, }; TankInfo { enough_water, warn_level, left_ml, sensor_error: tank_err, raw, water_frozen: water_temp .as_ref() .is_ok_and(|temp| *temp < WATER_FROZEN_THRESH), water_temp: water_temp.as_ref().copied().ok(), temp_sensor_error: water_temp.err().map(|err| err.to_string()), percent, } } } pub fn determine_tank_state( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, config: &PlantControllerConfig, ) -> TankState { if config.tank.tank_sensor_enabled { match board.tank_sensor_voltage() { Ok(raw_sensor_value_mv) => TankState::TankSensorPresent(raw_sensor_value_mv), Err(err) => TankState::TankSensorError(TankError::BoardError(err.to_string())), } } else { TankState::TankSensorDisabled } } #[derive(Debug, Serialize)] /// Information structure send to mqtt for monitoring purposes pub struct TankInfo { /// is there enough water in the tank enough_water: bool, /// warning that water needs to be refilled soon warn_level: bool, /// estimation how many ml are still in tank left_ml: Option, /// if there is was an issue with the water level sensor sensor_error: Option, /// raw water sensor value raw: Option, /// percent value percent: Option, /// water in tank might be frozen water_frozen: bool, /// water temperature water_temp: Option, temp_sensor_error: Option, }