195 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
use crate::alloc::string::{String, ToString};
 | 
						|
use crate::config::TankConfig;
 | 
						|
use crate::fat_error::FatResult;
 | 
						|
use crate::hal::HAL;
 | 
						|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 | 
						|
use embassy_sync::mutex::MutexGuard;
 | 
						|
use serde::Serialize;
 | 
						|
 | 
						|
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 {
 | 
						|
    Present(f32),
 | 
						|
    Error(TankError),
 | 
						|
    Disabled,
 | 
						|
}
 | 
						|
 | 
						|
fn raw_voltage_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_voltage_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::Disabled => Err(TankError::SensorDisabled),
 | 
						|
            TankState::Error(err) => Err(err.clone()),
 | 
						|
            TankState::Present(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::Disabled => Err(TankError::SensorDisabled),
 | 
						|
            TankState::Error(err) => Err(err.clone()),
 | 
						|
            TankState::Present(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::Disabled)
 | 
						|
    }
 | 
						|
 | 
						|
    pub fn warn_level(&self, config: &TankConfig) -> Result<bool, TankError> {
 | 
						|
        match self {
 | 
						|
            TankState::Disabled => Err(TankError::SensorDisabled),
 | 
						|
            TankState::Error(err) => Err(err.clone()),
 | 
						|
            TankState::Present(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::Present(raw_value_mv) => {
 | 
						|
                raw_voltage_to_tank_fill_percent(*raw_value_mv, config).err()
 | 
						|
            }
 | 
						|
            TankState::Error(err) => Some(err.clone()),
 | 
						|
            TankState::Disabled => Some(TankError::SensorDisabled),
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    pub fn as_mqtt_info(&self, config: &TankConfig, water_temp: &FatResult<f32>) -> 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 warn level be triggered if there is an error?
 | 
						|
        let raw = match self {
 | 
						|
            TankState::Disabled | TankState::Error(_) => None,
 | 
						|
            TankState::Present(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.as_ref().err().map(|err| err.to_string()),
 | 
						|
            percent,
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
pub async fn determine_tank_state(
 | 
						|
    board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
 | 
						|
) -> TankState {
 | 
						|
    if board.board_hal.get_config().tank.tank_sensor_enabled {
 | 
						|
        match board
 | 
						|
            .board_hal
 | 
						|
            .get_tank_sensor()
 | 
						|
            .map(|f| f.tank_sensor_voltage())
 | 
						|
        {
 | 
						|
            Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv.await.unwrap()),
 | 
						|
            Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        TankState::Disabled
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Debug, Serialize)]
 | 
						|
/// Information structure send to mqtt for monitoring purposes
 | 
						|
pub struct TankInfo {
 | 
						|
    /// there is enough water in the tank
 | 
						|
    pub(crate) enough_water: bool,
 | 
						|
    /// warning that water needs to be refilled soon
 | 
						|
    pub(crate) warn_level: bool,
 | 
						|
    /// estimation how many ml are still in the tank
 | 
						|
    pub(crate) left_ml: Option<f32>,
 | 
						|
    /// if there is an issue with the water level sensor
 | 
						|
    pub(crate) sensor_error: Option<TankError>,
 | 
						|
    /// raw water sensor value
 | 
						|
    pub(crate) raw: Option<f32>,
 | 
						|
    /// percent value
 | 
						|
    pub(crate) percent: Option<f32>,
 | 
						|
    /// water in the tank might be frozen
 | 
						|
    pub(crate) water_frozen: bool,
 | 
						|
    /// water temperature
 | 
						|
    pub(crate) water_temp: Option<f32>,
 | 
						|
    pub(crate) temp_sensor_error: Option<String>,
 | 
						|
}
 |