finalize tank state logging

This commit is contained in:
ju6ge 2025-03-20 22:06:20 +01:00
parent fbf1a84e7d
commit 038f74bca1
Signed by: judge
GPG Key ID: 6512C30DD8E017B5
3 changed files with 91 additions and 62 deletions

View File

@ -137,8 +137,16 @@ pub enum LogMessage {
LowVoltage, LowVoltage,
#[strum(serialize = "Error communicating with battery!! ${txt_long}")] #[strum(serialize = "Error communicating with battery!! ${txt_long}")]
BatteryCommunicationError, BatteryCommunicationError,
#[strum(serialize = "Tank sensor raw ${number_a} percent ${number_b}")] #[strum(serialize = "Tank water level cricial! Refill tank!")]
SensorTankRaw, TankWaterLevelLow,
#[strum(serialize = "Tank sensor hardware error: ${txt_long}")]
TankSensorBoardError,
#[strum(serialize = "Tank sensor not present, raw voltage measured = ${number_a} mV")]
TankSensorMissing,
#[strum(
serialize = "Tank sensor value out of range, min = ${number_a}%, max = ${number_b}%, value = ${text_short}%"
)]
TankSensorValueRangeError,
#[strum( #[strum(
serialize = "raw measure unscaled ${number_a} hz ${number_b}, plant ${txt_short} sensor ${txt_long}" serialize = "raw measure unscaled ${number_a} hz ${number_b}, plant ${txt_short} sensor ${txt_long}"
)] )]

View File

@ -17,7 +17,7 @@ use esp_idf_sys::{
esp_ota_img_states_t_ESP_OTA_IMG_VALID, vTaskDelay, esp_ota_img_states_t_ESP_OTA_IMG_VALID, vTaskDelay,
}; };
use esp_ota::{mark_app_valid, rollback_and_reboot}; use esp_ota::{mark_app_valid, rollback_and_reboot};
use log::log; use log::{log, LogMessage};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use plant_hal::{PlantCtrlBoard, PlantHal, PLANT_COUNT}; use plant_hal::{PlantCtrlBoard, PlantHal, PLANT_COUNT};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -401,37 +401,41 @@ fn safe_main() -> anyhow::Result<()> {
let dry_run = false; let dry_run = false;
let tank_state = determine_tank_state(&mut board, &config); let tank_state = determine_tank_state(&mut board, &config);
if let Some(_err) = tank_state.got_error(&config.tank) {
//TODO log error state to serial if tank_state.is_enabled() {
//log( if let Some(err) = tank_state.got_error(&config.tank) {
//LogMessage::SensorTankRaw, match err {
//raw_value_mv as u32, TankError::SensorDisabled => { /* unreachable */ }
//percent as u32, TankError::SensorMissing(raw_value_mv) => log(
//"", LogMessage::TankSensorMissing,
//"", raw_value_mv as u32,
//); 0,
} else if tank_state.warn_level(&config.tank).is_ok_and(|warn| warn) { "",
//TODO(judge) enable logging again, might require returning level as well "",
//println!( ),
//"Low water, current percent is {}, minimum warn level is {}", TankError::SensorValueError { value, min, max } => log(
//percent as u8, config.tank.tank_warn_percent LogMessage::TankSensorValueRangeError,
//); min as u32,
board.general_fault(true); max as u32,
} else if tank_state.enough_water(&config.tank).is_ok_and(|enough| enough) { &format!("{}", value),
//TODO(judge) enable logging again, might require returning level as well "",
//println!( ),
//"Enough water, current percent is {}, minimum empty level is {}", TankError::BoardError(err) => log(
//percent as u8, config.tank.tank_empty_percent LogMessage::TankSensorBoardError,
//); 0,
} else { 0,
if let Ok(_left_ml) = tank_state.left_ml(&config.tank) { "",
//TODO(judge) enable logging again, might require returning level as well &format!("{}", &err.to_string()),
//println!( ),
//"Tank sensor returned mv {} as {}% leaving {} ml useable", }
//rv.raw, percent as u8, rv.left_ml // disabled can not 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) {
log(LogMessage::TankWaterLevelLow, 0, 0, "", "");
board.general_fault(true);
} }
} }
let mut water_frozen = false; let mut water_frozen = false;
let mut attempt = 1; let mut attempt = 1;
@ -440,14 +444,14 @@ fn safe_main() -> anyhow::Result<()> {
match &temp { match &temp {
Ok(res) => { Ok(res) => {
println!("Water temp is {}", res); println!("Water temp is {}", res);
break temp break temp;
} }
Err(err) => { Err(err) => {
println!("Could not get water temp {} attempt {}", err, attempt) println!("Could not get water temp {} attempt {}", err, attempt)
} }
} }
if attempt == 5 { if attempt == 5 {
break temp break temp;
} }
attempt += 1; attempt += 1;
}; };
@ -682,14 +686,12 @@ fn determine_state_target_moisture_for_plant(
match tank_state.enough_water(&config.tank) { match tank_state.enough_water(&config.tank) {
Err(_tank_err) => { Err(_tank_err) => {
if !config.tank.tank_allow_pumping_if_sensor_error { if !config.tank.tank_allow_pumping_if_sensor_error {
// ignore is ok state.no_water = true;
// wtf does this meen, shouldn't something happen if the configuration specifies
// that no water should flow if there was an error?
} }
}, }
Ok(enough_water) => { // when no tank error, if plant should be watered depends on if enough water is in tank
state.no_water = !enough_water // no_water behaves inversly to enough_water
}, Ok(enough_water) => state.no_water = !enough_water,
} }
} }
let duration = TimeDelta::try_minutes(plant_config.pump_cooldown_min as i64).unwrap(); let duration = TimeDelta::try_minutes(plant_config.pump_cooldown_min as i64).unwrap();
@ -750,10 +752,10 @@ fn determine_state_timer_only_for_plant(
if !config.tank.tank_allow_pumping_if_sensor_error { if !config.tank.tank_allow_pumping_if_sensor_error {
state.do_water = true; state.do_water = true;
} }
}, }
Ok(enough_water) => { Ok(enough_water) => {
state.no_water = !enough_water; state.no_water = !enough_water;
}, }
} }
} }
} }
@ -801,10 +803,10 @@ fn determine_state_timer_and_deadzone_for_plant(
if !config.tank.tank_allow_pumping_if_sensor_error { if !config.tank.tank_allow_pumping_if_sensor_error {
state.do_water = true; state.do_water = true;
} }
}, }
Ok(enough_water) => { Ok(enough_water) => {
state.no_water = !enough_water; state.no_water = !enough_water;
}, }
} }
} }
} }

View File

@ -1,6 +1,9 @@
use serde::Serialize; use serde::Serialize;
use crate::{config::{PlantControllerConfig, TankConfig}, plant_hal::PlantCtrlBoard}; use crate::{
config::{PlantControllerConfig, TankConfig},
plant_hal::PlantCtrlBoard,
};
const OPEN_TANK_VOLTAGE: f32 = 3.0; const OPEN_TANK_VOLTAGE: f32 = 3.0;
pub const WATER_FROZEN_THRESH: f32 = 4.0; pub const WATER_FROZEN_THRESH: f32 = 4.0;
@ -10,7 +13,7 @@ pub enum TankError {
SensorDisabled, SensorDisabled,
SensorMissing(f32), SensorMissing(f32),
SensorValueError { value: f32, min: f32, max: f32 }, SensorValueError { value: f32, min: f32, max: f32 },
BoardError(String) BoardError(String),
} }
pub enum TankState { pub enum TankState {
@ -35,18 +38,21 @@ fn raw_voltage_to_tank_fill_percent(
config: &TankConfig, config: &TankConfig,
) -> Result<f32, TankError> { ) -> Result<f32, TankError> {
let divider_percent = raw_volatge_to_divider_percent(raw_value_mv)?; 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() { if divider_percent < config.tank_empty_percent.into()
|| divider_percent > config.tank_full_percent.into()
{
return Err(TankError::SensorValueError { return Err(TankError::SensorValueError {
value: divider_percent, value: divider_percent,
min: config.tank_empty_percent.into(), min: config.tank_empty_percent.into(),
max: config.tank_full_percent.into(), max: config.tank_full_percent.into(),
}); });
} }
Ok((divider_percent - f32::from(config.tank_empty_percent)) * 100. Ok(
/ f32::from(config.tank_full_percent - config.tank_empty_percent)) (divider_percent - f32::from(config.tank_empty_percent)) * 100.
/ f32::from(config.tank_full_percent - config.tank_empty_percent),
)
} }
impl TankState { impl TankState {
pub fn left_ml(&self, config: &TankConfig) -> Result<f32, TankError> { pub fn left_ml(&self, config: &TankConfig) -> Result<f32, TankError> {
match self { match self {
@ -69,10 +75,14 @@ impl TankState {
} else { } else {
Ok(false) Ok(false)
} }
}, }
} }
} }
pub fn is_enabled(&self) -> bool {
matches!(self, TankState::TankSensorDisabled)
}
pub fn warn_level(&self, config: &TankConfig) -> Result<bool, TankError> { pub fn warn_level(&self, config: &TankConfig) -> Result<bool, TankError> {
match self { match self {
TankState::TankSensorDisabled => Err(TankError::SensorDisabled), TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
@ -84,29 +94,37 @@ impl TankState {
} else { } else {
Ok(false) Ok(false)
} }
}, }
} }
} }
pub fn got_error(&self, config: &TankConfig) -> Option<TankError> { pub fn got_error(&self, config: &TankConfig) -> Option<TankError> {
match self { match self {
TankState::TankSensorPresent(raw_value_mv) => raw_voltage_to_tank_fill_percent(*raw_value_mv, config).err(), TankState::TankSensorPresent(raw_value_mv) => {
raw_voltage_to_tank_fill_percent(*raw_value_mv, config).err()
}
TankState::TankSensorError(err) => Some(err.clone()), TankState::TankSensorError(err) => Some(err.clone()),
TankState::TankSensorDisabled => Some(TankError::SensorDisabled), TankState::TankSensorDisabled => Some(TankError::SensorDisabled),
} }
} }
pub fn as_mqtt_info(&self, config: &TankConfig, water_temp: Result<f32, anyhow::Error>) -> TankInfo { pub fn as_mqtt_info(
&self,
config: &TankConfig,
water_temp: Result<f32, anyhow::Error>,
) -> TankInfo {
let mut tank_err: Option<TankError> = None; let mut tank_err: Option<TankError> = None;
let left_ml = match self.left_ml(config) { let left_ml = match self.left_ml(config) {
Err(err) => { tank_err = Some(err); None }, Err(err) => {
tank_err = Some(err);
None
}
Ok(left_ml) => Some(left_ml), 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 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 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 { let raw = match self {
TankState::TankSensorDisabled TankState::TankSensorDisabled | TankState::TankSensorError(_) => None,
| TankState::TankSensorError(_) => None,
TankState::TankSensorPresent(raw_value_mv) => Some(*raw_value_mv), TankState::TankSensorPresent(raw_value_mv) => Some(*raw_value_mv),
}; };
@ -116,9 +134,11 @@ impl TankState {
left_ml, left_ml,
sensor_error: tank_err, sensor_error: tank_err,
raw, raw,
water_frozen: water_temp.as_ref().is_ok_and(|temp| *temp < WATER_FROZEN_THRESH), water_frozen: water_temp
.as_ref()
.is_ok_and(|temp| *temp < WATER_FROZEN_THRESH),
water_temp: water_temp.as_ref().copied().ok(), water_temp: water_temp.as_ref().copied().ok(),
temp_sensor_error: water_temp.err().map(|err| err.to_string()) temp_sensor_error: water_temp.err().map(|err| err.to_string()),
} }
} }
} }
@ -130,9 +150,8 @@ pub fn determine_tank_state(
if config.tank.tank_sensor_enabled { if config.tank.tank_sensor_enabled {
match board.tank_sensor_voltage() { match board.tank_sensor_voltage() {
Ok(raw_sensor_value_mv) => TankState::TankSensorPresent(raw_sensor_value_mv), Ok(raw_sensor_value_mv) => TankState::TankSensorPresent(raw_sensor_value_mv),
Err(err) => TankState::TankSensorError(TankError::BoardError(err.to_string())) Err(err) => TankState::TankSensorError(TankError::BoardError(err.to_string())),
} }
} else { } else {
TankState::TankSensorDisabled TankState::TankSensorDisabled
} }