195 lines
6.6 KiB
Rust
195 lines
6.6 KiB
Rust
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<f32, TankError> {
|
|
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<f32, TankError> {
|
|
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<f32, TankError> {
|
|
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<bool, TankError> {
|
|
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<bool, TankError> {
|
|
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<TankError> {
|
|
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<f32, anyhow::Error>,
|
|
) -> TankInfo {
|
|
let mut tank_err: Option<TankError> = 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<f32>,
|
|
/// if there is was an issue with the water level sensor
|
|
sensor_error: Option<TankError>,
|
|
/// raw water sensor value
|
|
raw: Option<f32>,
|
|
/// percent value
|
|
percent: Option<f32>,
|
|
/// water in tank might be frozen
|
|
water_frozen: bool,
|
|
/// water temperature
|
|
water_temp: Option<f32>,
|
|
temp_sensor_error: Option<String>,
|
|
}
|