From fb180630e45e5475dd820cc1e1ecd4956a15054d Mon Sep 17 00:00:00 2001 From: ju6ge Date: Thu, 13 Mar 2025 22:41:00 +0100 Subject: [PATCH 1/7] WIP refactor tank state code --- rust/src/main.rs | 119 ++------------------------------- rust/src/plant_hal.rs | 33 ++------- rust/src/tank.rs | 152 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 139 deletions(-) create mode 100644 rust/src/tank.rs diff --git a/rust/src/main.rs b/rust/src/main.rs index 8387cb7..7293148 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -26,6 +26,9 @@ use crate::{config::PlantControllerConfig, webserver::webserver::httpd}; mod config; mod log; pub mod plant_hal; +mod tank; + +use tank::*; const TIME_ZONE: Tz = Berlin; @@ -125,34 +128,6 @@ enum SensorError { OpenCircuit { hz: f32, min: f32 }, } -#[derive(Debug, PartialEq, Default)] -/// State data for water tank -/// -/// TODO unify with TankStateMQTT -struct TankState { - /// 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: u32, - /// if there is was an issue with the water level sensor - /// TODO merge with left_ml as Result - sensor_error: bool, - /// raw water sensor value - raw: u16, -} - -#[derive(Serialize)] -struct TankStateMQTT { - enough_water: bool, - warn_level: bool, - left_ml: u32, - sensor_error: bool, - raw: u16, - water_frozen: String, -} - #[derive(Serialize)] struct PlantStateMQTT<'a> { a: &'a str, @@ -631,74 +606,12 @@ fn publish_battery_state( }; } -fn determine_tank_state( - board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, - config: &PlantControllerConfig, -) -> TankState { - if config.tank.tank_sensor_enabled { - let mut rv: TankState = TankState { - ..Default::default() - }; - let success = board - .tank_sensor_percent() - .and_then(|raw| { - rv.raw = raw; - return map_range( - ( - config.tank.tank_empty_percent as f32, - config.tank.tank_full_percent as f32, - ), - raw as f32, - ); - }) - .and_then(|percent| { - rv.left_ml = ((percent * config.tank.tank_useable_ml as f32) / 100_f32) as u32; - println!( - "Tank sensor returned mv {} as {}% leaving {} ml useable", - rv.raw, percent as u8, rv.left_ml - ); - if config.tank.tank_warn_percent > percent as u8 { - board.general_fault(true); - println!( - "Low water, current percent is {}, minimum warn level is {}", - percent as u8, config.tank.tank_warn_percent - ); - rv.warn_level = true; - } - if config.tank.tank_empty_percent < percent as u8 { - println!( - "Enough water, current percent is {}, minimum empty level is {}", - percent as u8, config.tank.tank_empty_percent - ); - rv.enough_water = true; - } - return Ok(()); - }); - match success { - Err(err) => { - println!("Could not determine tank value due to {}", err); - board.general_fault(true); - rv.sensor_error = true; - } - Ok(_) => {} - } - return rv; - } - return TankState { - warn_level: false, - enough_water: true, - left_ml: 1337, - sensor_error: false, - raw: 0, - }; -} - fn determine_state_target_moisture_for_plant( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, plant: usize, state: &mut PlantState, config: &PlantControllerConfig, - tank_state: &TankState, + tank_state: &TankInfo, cur: DateTime, ) { let plant_config = &config.plants[plant]; @@ -788,7 +701,7 @@ fn determine_state_timer_only_for_plant( plant: usize, state: &mut PlantState, config: &PlantControllerConfig, - tank_state: &TankState, + tank_state: &TankInfo, cur: DateTime, ) { let plant_config = &config.plants[plant]; @@ -826,7 +739,7 @@ fn determine_state_timer_and_deadzone_for_plant( plant: usize, state: &mut PlantState, config: &PlantControllerConfig, - tank_state: &TankState, + tank_state: &TankInfo, cur: DateTime, ) { let plant_config = &config.plants[plant]; @@ -870,7 +783,7 @@ fn determine_state_timer_and_deadzone_for_plant( fn determine_plant_state( plantstate: &mut [PlantState; PLANT_COUNT], cur: DateTime, - tank_state: &TankState, + tank_state: &TankInfo, config: &PlantControllerConfig, board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, ) { @@ -1051,24 +964,6 @@ fn to_string(value: Result) -> String { }; } -fn map_range(from_range: (f32, f32), s: f32) -> anyhow::Result { - if s < from_range.0 { - anyhow::bail!( - "Value out of range, min {} but current is {}", - from_range.0, - s - ); - } - if s > from_range.1 { - anyhow::bail!( - "Value out of range, max {} but current is {}", - from_range.1, - s - ); - } - return Ok(TO.0 + (s - from_range.0) * (TO.1 - TO.0) / (from_range.1 - from_range.0)); -} - fn map_range_moisture(s: f32) -> Result { if s < FROM.0 { return Err(SensorError::OpenCircuit { hz: s, min: FROM.0 }); diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index c6dc2b5..a65c91d 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -471,44 +471,23 @@ impl PlantCtrlBoard<'_> { Ok(sensor_data.temperature / 10_f32) } - pub fn tank_sensor_percent(&mut self) -> Result { + /// return median tank sensor value in milli volt + pub fn tank_sensor_voltage(&mut self) -> Result { let delay = Delay::new_default(); self.tank_power.set_high()?; //let stabilize delay.delay_ms(100); - unsafe { - vTaskDelay(100); - } let mut store = [0_u16; TANK_MULTI_SAMPLE]; for multisample in 0..TANK_MULTI_SAMPLE { let value = self.tank_channel.read()?; store[multisample] = value; } + self.tank_power.set_low()?; + store.sort(); - let median = store[6] as f32 / 1000_f32; - let config_open_voltage_mv = 3.0; - if config_open_voltage_mv < median { - self.tank_power.set_low()?; - bail!( - "Tank sensor missing, open loop voltage {} on tank sensor input {}", - config_open_voltage_mv, - median - ); - } - - let r2 = median * 50.0 / (3.3 - median); - let mut percent = r2 / 190_f32 * 100_f32; - percent = percent.clamp(0.0, 100.0); - log( - LogMessage::SensorTankRaw, - median as u32, - percent as u32, - "", - "", - ); - - return Ok(percent as u16); + let median_mv = store[6] as f32 / 1000_f32; + Ok(median_mv) } pub fn set_low_voltage_in_cycle(&mut self) { diff --git a/rust/src/tank.rs b/rust/src/tank.rs new file mode 100644 index 0000000..05f0374 --- /dev/null +++ b/rust/src/tank.rs @@ -0,0 +1,152 @@ +use crate::config::TankConfig; + +const OPEN_TANK_VOLTAGE: f32 = 3.0; + +#[derive(Debug, PartialEq, Default)] +/// State data for water tank +/// +/// TODO unify with TankStateMQTT +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: u32, + /// if there is was an issue with the water level sensor + /// TODO merge with left_ml as Result + sensor_error: bool, + /// raw water sensor value + raw: u16, +} + +pub enum TankError { + SensorDisabled, + SensorMissing(f32), + SensorValueError { value: f32, min: f32, max: f32 }, +} + +pub enum TankState { + TankSensorPresent(u16), + TankSensorDisabled, +} + +fn raw_volatge_to_divider_percent(raw_value_mv: u16) -> 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); + // TODO(judge) move this to a sensible place + //log( + //LogMessage::SensorTankRaw, + //raw_value_mv as u32, + //percent as u32, + //"", + //"", + //); + Ok(percent) +} + +fn raw_voltage_to_tank_fill_percent( + raw_value_mv: u16, + config: &TankConfig, +) -> Result { + let divider_percent = raw_volatge_to_divider_percent(raw_value_mv)?; + if s < config.tank_empty_percent || s > config.tank_full_percent { + return Err(TankError::SensorValueError { + value: divider_percent, + min: config.tank_empty_percent, + max: config.tank_full_percent, + }); + } + Ok((divider_percent - config.tank_empty_percent) * 100 + / (config.tank_full_percent - config.tank_empty_percent)) +} + + +impl TankState { + pub fn left_ml(&self, config: &TankConfig) -> Result { + match self { + TankState::TankSensorDisabled => Err(TankError::SensorMissing), + TankState::TankSensorPresent(raw_value_mv) => { + let tank_fill_percent = raw_voltage_to_tank_fill_percent(raw_value_mv, config); + todo!() + } + } + } +} + +#[derive(Serialize)] +pub struct TankStateMQTT { + enough_water: bool, + warn_level: bool, + left_ml: u32, + sensor_error: bool, + raw: u16, + water_frozen: String, +} + +pub fn determine_tank_state( + board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, + config: &PlantControllerConfig, +) -> TankInfo { + if config.tank.tank_sensor_enabled { + let mut rv: TankInfo = TankInfo { + ..Default::default() + }; + let success = board + .tank_sensor_percent() + .and_then(|raw| { + rv.raw = raw; + return map_range( + ( + config.tank.tank_empty_percent as f32, + config.tank.tank_full_percent as f32, + ), + raw as f32, + ); + }) + .and_then(|percent| { + rv.left_ml = ((percent * config.tank.tank_useable_ml as f32) / 100_f32) as u32; + println!( + "Tank sensor returned mv {} as {}% leaving {} ml useable", + rv.raw, percent as u8, rv.left_ml + ); + if config.tank.tank_warn_percent > percent as u8 { + board.general_fault(true); + println!( + "Low water, current percent is {}, minimum warn level is {}", + percent as u8, config.tank.tank_warn_percent + ); + rv.warn_level = true; + } + if config.tank.tank_empty_percent < percent as u8 { + println!( + "Enough water, current percent is {}, minimum empty level is {}", + percent as u8, config.tank.tank_empty_percent + ); + rv.enough_water = true; + } + return Ok(()); + }); + match success { + Err(err) => { + println!("Could not determine tank value due to {}", err); + board.general_fault(true); + rv.sensor_error = true; + } + Ok(_) => {} + } + return rv; + } + return TankInfo { + warn_level: false, + enough_water: true, + left_ml: 1337, + sensor_error: false, + raw: 0, + }; +} From e878a774ff389e8c49327c68129a3ba42b5761f1 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Fri, 14 Mar 2025 21:59:01 +0100 Subject: [PATCH 2/7] implement more functions for TankState --- rust/src/tank.rs | 114 ++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 60 deletions(-) diff --git a/rust/src/tank.rs b/rust/src/tank.rs index 05f0374..f99b6d0 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -1,4 +1,4 @@ -use crate::config::TankConfig; +use crate::config::{self, PlantControllerConfig, TankConfig}; const OPEN_TANK_VOLTAGE: f32 = 3.0; @@ -68,15 +68,59 @@ fn raw_voltage_to_tank_fill_percent( impl TankState { - pub fn left_ml(&self, config: &TankConfig) -> Result { + pub fn left_ml(&self, config: &TankConfig) -> Result { match self { - TankState::TankSensorDisabled => Err(TankError::SensorMissing), + TankState::TankSensorDisabled => Err(TankError::SensorDisabled), TankState::TankSensorPresent(raw_value_mv) => { - let tank_fill_percent = raw_voltage_to_tank_fill_percent(raw_value_mv, config); - todo!() + let tank_fill_percent = raw_voltage_to_tank_fill_percent(raw_value_mv, config)?; + //TODO(judge) move logging to more sensible place + //println!( + //"Tank sensor returned mv {} as {}% leaving {} ml useable", + //rv.raw, percent as u8, rv.left_ml + //); + 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::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 { + //TODO(judge) move logging to more sensible place + //println!( + //"Enough water, current percent is {}, minimum empty level is {}", + //percent as u8, config.tank.tank_empty_percent + //); + Ok(true) + } else { + Ok(false) + } + }, + } + } + + pub fn warn_level(&self, config: &TankConfig) -> Result { + match self { + TankState::TankSensorDisabled => Err(TankError::SensorDisabled), + 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_warn_percent { + //TODO(judge) move logging to more sensible place + //println!( + //"Low water, current percent is {}, minimum warn level is {}", + //percent as u8, config.tank.tank_warn_percent + //); + // TODO(judge) move board fault setting + // board.general_fault(true); + Ok(true) + } else { + Ok(false) + } + }, + } + } } #[derive(Serialize)] @@ -92,61 +136,11 @@ pub struct TankStateMQTT { pub fn determine_tank_state( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, config: &PlantControllerConfig, -) -> TankInfo { +) -> TankState { if config.tank.tank_sensor_enabled { - let mut rv: TankInfo = TankInfo { - ..Default::default() - }; - let success = board - .tank_sensor_percent() - .and_then(|raw| { - rv.raw = raw; - return map_range( - ( - config.tank.tank_empty_percent as f32, - config.tank.tank_full_percent as f32, - ), - raw as f32, - ); - }) - .and_then(|percent| { - rv.left_ml = ((percent * config.tank.tank_useable_ml as f32) / 100_f32) as u32; - println!( - "Tank sensor returned mv {} as {}% leaving {} ml useable", - rv.raw, percent as u8, rv.left_ml - ); - if config.tank.tank_warn_percent > percent as u8 { - board.general_fault(true); - println!( - "Low water, current percent is {}, minimum warn level is {}", - percent as u8, config.tank.tank_warn_percent - ); - rv.warn_level = true; - } - if config.tank.tank_empty_percent < percent as u8 { - println!( - "Enough water, current percent is {}, minimum empty level is {}", - percent as u8, config.tank.tank_empty_percent - ); - rv.enough_water = true; - } - return Ok(()); - }); - match success { - Err(err) => { - println!("Could not determine tank value due to {}", err); - board.general_fault(true); - rv.sensor_error = true; - } - Ok(_) => {} - } - return rv; + let raw_sensor_value_mv = board.tank_sensor_voltage(); + TankState::TankSensorPresent(raw_sensor_value_mv) + } else { + TankState::TankSensorDisabled } - return TankInfo { - warn_level: false, - enough_water: true, - left_ml: 1337, - sensor_error: false, - raw: 0, - }; } From 48b0777d9460bf66435c457c060fa2f2713a2d0b Mon Sep 17 00:00:00 2001 From: ju6ge Date: Fri, 14 Mar 2025 23:39:46 +0100 Subject: [PATCH 3/7] fix tank.rs errors after intial implementation --- rust/src/plant_hal.rs | 2 +- rust/src/tank.rs | 41 ++++++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index a65c91d..7bf736e 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -472,7 +472,7 @@ impl PlantCtrlBoard<'_> { } /// return median tank sensor value in milli volt - pub fn tank_sensor_voltage(&mut self) -> Result { + pub fn tank_sensor_voltage(&mut self) -> Result { let delay = Delay::new_default(); self.tank_power.set_high()?; //let stabilize diff --git a/rust/src/tank.rs b/rust/src/tank.rs index f99b6d0..c09baa4 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -1,4 +1,4 @@ -use crate::config::{self, PlantControllerConfig, TankConfig}; +use crate::{config::{self, PlantControllerConfig, TankConfig}, plant_hal::PlantCtrlBoard}; const OPEN_TANK_VOLTAGE: f32 = 3.0; @@ -24,14 +24,15 @@ pub enum TankError { SensorDisabled, SensorMissing(f32), SensorValueError { value: f32, min: f32, max: f32 }, + BoardError(anyhow::Error) } pub enum TankState { - TankSensorPresent(u16), + TankSensorPresent(f32), TankSensorDisabled, } -fn raw_volatge_to_divider_percent(raw_value_mv: u16) -> Result { +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)); } @@ -51,19 +52,19 @@ fn raw_volatge_to_divider_percent(raw_value_mv: u16) -> Result { } fn raw_voltage_to_tank_fill_percent( - raw_value_mv: u16, + raw_value_mv: f32, config: &TankConfig, ) -> Result { let divider_percent = raw_volatge_to_divider_percent(raw_value_mv)?; - if s < config.tank_empty_percent || s > config.tank_full_percent { + 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, - max: config.tank_full_percent, + min: config.tank_empty_percent.into(), + max: config.tank_full_percent.into(), }); } - Ok((divider_percent - config.tank_empty_percent) * 100 - / (config.tank_full_percent - config.tank_empty_percent)) + Ok((divider_percent - f32::from(config.tank_empty_percent)) * 100. + / f32::from(config.tank_full_percent - config.tank_empty_percent)) } @@ -72,7 +73,7 @@ impl TankState { match self { TankState::TankSensorDisabled => Err(TankError::SensorDisabled), TankState::TankSensorPresent(raw_value_mv) => { - let tank_fill_percent = raw_voltage_to_tank_fill_percent(raw_value_mv, config)?; + let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?; //TODO(judge) move logging to more sensible place //println!( //"Tank sensor returned mv {} as {}% leaving {} ml useable", @@ -86,8 +87,8 @@ impl TankState { match self { TankState::TankSensorDisabled => Err(TankError::SensorDisabled), 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 { + let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?; + if tank_fill_percent > config.tank_empty_percent.into() { //TODO(judge) move logging to more sensible place //println!( //"Enough water, current percent is {}, minimum empty level is {}", @@ -105,8 +106,8 @@ impl TankState { match self { TankState::TankSensorDisabled => Err(TankError::SensorDisabled), 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_warn_percent { + let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?; + if tank_fill_percent < config.tank_warn_percent.into() { //TODO(judge) move logging to more sensible place //println!( //"Low water, current percent is {}, minimum warn level is {}", @@ -121,9 +122,11 @@ impl TankState { }, } } + + } -#[derive(Serialize)] +#[derive(serde::Serialize)] pub struct TankStateMQTT { enough_water: bool, warn_level: bool, @@ -136,11 +139,11 @@ pub struct TankStateMQTT { pub fn determine_tank_state( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, config: &PlantControllerConfig, -) -> TankState { +) -> Result { if config.tank.tank_sensor_enabled { - let raw_sensor_value_mv = board.tank_sensor_voltage(); - TankState::TankSensorPresent(raw_sensor_value_mv) + let raw_sensor_value_mv = board.tank_sensor_voltage().map_err(|err| TankError::BoardError(err))?; + Ok(TankState::TankSensorPresent(raw_sensor_value_mv)) } else { - TankState::TankSensorDisabled + Ok(TankState::TankSensorDisabled) } } From b993f2b0372235e344dc74cc4abc1a4200517cb0 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sat, 15 Mar 2025 01:13:48 +0100 Subject: [PATCH 4/7] use proper error handling --- rust/src/main.rs | 49 ++++++++++++++++++++++++++++++++---------------- rust/src/tank.rs | 6 ++++++ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/rust/src/main.rs b/rust/src/main.rs index 7293148..db1e76f 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -611,7 +611,7 @@ fn determine_state_target_moisture_for_plant( plant: usize, state: &mut PlantState, config: &PlantControllerConfig, - tank_state: &TankInfo, + tank_state: &TankState, cur: DateTime, ) { let plant_config = &config.plants[plant]; @@ -657,10 +657,17 @@ fn determine_state_target_moisture_for_plant( if a_low || b_low { state.dry = true; - if tank_state.sensor_error && !config.tank.tank_allow_pumping_if_sensor_error { - //ignore is ok - } else if !tank_state.enough_water { - state.no_water = true; + match tank_state.enough_water(&config.tank) { + Err(_tank_err) => { + if !config.tank.tank_allow_pumping_if_sensor_error { + // ignore is ok + // 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) => { + state.no_water = !enough_water + }, } } let duration = TimeDelta::try_minutes(plant_config.pump_cooldown_min as i64).unwrap(); @@ -701,7 +708,7 @@ fn determine_state_timer_only_for_plant( plant: usize, state: &mut PlantState, config: &PlantControllerConfig, - tank_state: &TankInfo, + tank_state: &TankState, cur: DateTime, ) { let plant_config = &config.plants[plant]; @@ -716,10 +723,15 @@ fn determine_state_timer_only_for_plant( state.next_pump = Some(europe_time); state.cooldown = true; } else { - if tank_state.sensor_error && !config.tank.tank_allow_pumping_if_sensor_error { - state.do_water = true; - } else if !tank_state.enough_water { - state.no_water = true; + match tank_state.enough_water(&config.tank) { + Err(_tank_err) => { + if !config.tank.tank_allow_pumping_if_sensor_error { + state.do_water = true; + } + }, + Ok(enough_water) => { + state.no_water = !enough_water; + }, } } } @@ -739,7 +751,7 @@ fn determine_state_timer_and_deadzone_for_plant( plant: usize, state: &mut PlantState, config: &PlantControllerConfig, - tank_state: &TankInfo, + tank_state: &TankState, cur: DateTime, ) { let plant_config = &config.plants[plant]; @@ -762,10 +774,15 @@ fn determine_state_timer_and_deadzone_for_plant( state.out_of_work_hour = true; } if !state.cooldown && !state.out_of_work_hour { - if tank_state.sensor_error && !config.tank.tank_allow_pumping_if_sensor_error { - state.do_water = true; - } else if !tank_state.enough_water { - state.no_water = true; + match tank_state.enough_water(&config.tank) { + Err(_tank_err) => { + if !config.tank.tank_allow_pumping_if_sensor_error { + state.do_water = true; + } + }, + Ok(enough_water) => { + state.no_water = !enough_water; + }, } } } @@ -783,7 +800,7 @@ fn determine_state_timer_and_deadzone_for_plant( fn determine_plant_state( plantstate: &mut [PlantState; PLANT_COUNT], cur: DateTime, - tank_state: &TankInfo, + tank_state: &TankState, config: &PlantControllerConfig, board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, ) { diff --git a/rust/src/tank.rs b/rust/src/tank.rs index c09baa4..3a7e223 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -123,6 +123,12 @@ impl TankState { } } + 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::TankSensorDisabled => Some(TankError::SensorDisabled), + } + } } From 4a334ef2f295821e77b921a0420d72ccba787f33 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sat, 15 Mar 2025 15:16:20 +0100 Subject: [PATCH 5/7] streamline tank state mqtt information structure --- rust/src/main.rs | 41 ++++++++------------- rust/src/tank.rs | 95 +++++++++++++++++++++++++++++++----------------- 2 files changed, 78 insertions(+), 58 deletions(-) diff --git a/rust/src/main.rs b/rust/src/main.rs index db1e76f..3f4902c 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -401,41 +401,32 @@ fn safe_main() -> anyhow::Result<()> { let dry_run = false; let tank_state = determine_tank_state(&mut board, &config); - let mut tank_state_mqtt = TankStateMQTT { - enough_water: tank_state.enough_water, - left_ml: tank_state.left_ml, - warn_level: tank_state.warn_level, - sensor_error: tank_state.sensor_error, - raw: tank_state.raw, - water_frozen: "".to_owned(), - }; let mut water_frozen = false; - let mut temp: Option = None; - for _attempt in 0..5 { - let water_temperature = board.water_temperature_c(); - match water_temperature { + let mut attempt = 1; + let water_temp: Result = loop { + let temp = board.water_temperature_c(); + match &temp { Ok(res) => { - temp = Some(res); - break; + println!("Water temp is {}", res); + break temp } Err(err) => { - println!("Could not get water temp {} attempt {}", err, _attempt) + println!("Could not get water temp {} attempt {}", err, attempt) } } - } - match temp { - Some(res) => { - println!("Water temp is {}", res); - if res < 4_f32 { - water_frozen = true; - } - tank_state_mqtt.water_frozen = water_frozen.to_string(); + if attempt == 5 { + break temp + } + attempt += 1; + }; + if let Ok(res) = water_temp { + if res < WATER_FROZEN_THRESH { + water_frozen = true; } - None => tank_state_mqtt.water_frozen = "tank sensor error".to_owned(), } - match serde_json::to_string(&tank_state_mqtt) { + 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()); } diff --git a/rust/src/tank.rs b/rust/src/tank.rs index 3a7e223..29b9041 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -1,34 +1,21 @@ -use crate::{config::{self, PlantControllerConfig, TankConfig}, plant_hal::PlantCtrlBoard}; +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, PartialEq, Default)] -/// State data for water tank -/// -/// TODO unify with TankStateMQTT -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: u32, - /// if there is was an issue with the water level sensor - /// TODO merge with left_ml as Result - sensor_error: bool, - /// raw water sensor value - raw: u16, -} - +#[derive(Debug, Clone, Serialize)] pub enum TankError { SensorDisabled, SensorMissing(f32), SensorValueError { value: f32, min: f32, max: f32 }, - BoardError(anyhow::Error) + BoardError(String) } pub enum TankState { TankSensorPresent(f32), + TankSensorError(TankError), TankSensorDisabled, } @@ -72,6 +59,7 @@ 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)?; //TODO(judge) move logging to more sensible place @@ -86,6 +74,7 @@ impl TankState { 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() { @@ -105,6 +94,7 @@ impl TankState { 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)?; if tank_fill_percent < config.tank_warn_percent.into() { @@ -126,30 +116,69 @@ impl TankState { 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), + }; -#[derive(serde::Serialize)] -pub struct TankStateMQTT { - enough_water: bool, - warn_level: bool, - left_ml: u32, - sensor_error: bool, - raw: u16, - water_frozen: String, + 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()) + } + } } pub fn determine_tank_state( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, config: &PlantControllerConfig, -) -> Result { +) -> TankState { if config.tank.tank_sensor_enabled { - let raw_sensor_value_mv = board.tank_sensor_voltage().map_err(|err| TankError::BoardError(err))?; - Ok(TankState::TankSensorPresent(raw_sensor_value_mv)) + 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 { - Ok(TankState::TankSensorDisabled) + 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, + /// water in tank might be frozen + water_frozen: bool, + /// water temperature + water_temp: Option, + temp_sensor_error: Option, +} From fbf1a84e7df492b1724c6449f54b614311267e06 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sat, 15 Mar 2025 15:57:15 +0100 Subject: [PATCH 6/7] suggestion for logging location --- rust/src/main.rs | 31 +++++++++++++++++++++++++++++++ rust/src/tank.rs | 25 ------------------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/rust/src/main.rs b/rust/src/main.rs index 3f4902c..cf04194 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -401,6 +401,37 @@ fn safe_main() -> anyhow::Result<()> { let dry_run = false; 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 + //log( + //LogMessage::SensorTankRaw, + //raw_value_mv as u32, + //percent as u32, + //"", + //"", + //); + } 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 {}", + //percent as u8, config.tank.tank_warn_percent + //); + board.general_fault(true); + } else if tank_state.enough_water(&config.tank).is_ok_and(|enough| enough) { + //TODO(judge) enable logging again, might require returning level as well + //println!( + //"Enough water, current percent is {}, minimum empty level is {}", + //percent as u8, config.tank.tank_empty_percent + //); + } else { + if let Ok(_left_ml) = tank_state.left_ml(&config.tank) { + //TODO(judge) enable logging again, might require returning level as well + //println!( + //"Tank sensor returned mv {} as {}% leaving {} ml useable", + //rv.raw, percent as u8, rv.left_ml + //); + } + } let mut water_frozen = false; let mut attempt = 1; diff --git a/rust/src/tank.rs b/rust/src/tank.rs index 29b9041..eb531a0 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -27,14 +27,6 @@ fn raw_volatge_to_divider_percent(raw_value_mv: f32) -> Result { 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); - // TODO(judge) move this to a sensible place - //log( - //LogMessage::SensorTankRaw, - //raw_value_mv as u32, - //percent as u32, - //"", - //"", - //); Ok(percent) } @@ -62,11 +54,6 @@ impl TankState { 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)?; - //TODO(judge) move logging to more sensible place - //println!( - //"Tank sensor returned mv {} as {}% leaving {} ml useable", - //rv.raw, percent as u8, rv.left_ml - //); Ok(config.tank_useable_ml as f32 * tank_fill_percent / 100.) } } @@ -78,11 +65,6 @@ impl TankState { 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() { - //TODO(judge) move logging to more sensible place - //println!( - //"Enough water, current percent is {}, minimum empty level is {}", - //percent as u8, config.tank.tank_empty_percent - //); Ok(true) } else { Ok(false) @@ -98,13 +80,6 @@ impl TankState { 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_warn_percent.into() { - //TODO(judge) move logging to more sensible place - //println!( - //"Low water, current percent is {}, minimum warn level is {}", - //percent as u8, config.tank.tank_warn_percent - //); - // TODO(judge) move board fault setting - // board.general_fault(true); Ok(true) } else { Ok(false) From 038f74bca164557a9554d80bc137b505fed9dda8 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Thu, 20 Mar 2025 22:06:20 +0100 Subject: [PATCH 7/7] finalize tank state logging --- rust/src/log/mod.rs | 12 +++++-- rust/src/main.rs | 88 +++++++++++++++++++++++---------------------- rust/src/tank.rs | 53 ++++++++++++++++++--------- 3 files changed, 91 insertions(+), 62 deletions(-) diff --git a/rust/src/log/mod.rs b/rust/src/log/mod.rs index 9d468bc..0b3384b 100644 --- a/rust/src/log/mod.rs +++ b/rust/src/log/mod.rs @@ -137,8 +137,16 @@ pub enum LogMessage { LowVoltage, #[strum(serialize = "Error communicating with battery!! ${txt_long}")] BatteryCommunicationError, - #[strum(serialize = "Tank sensor raw ${number_a} percent ${number_b}")] - SensorTankRaw, + #[strum(serialize = "Tank water level cricial! Refill tank!")] + 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( serialize = "raw measure unscaled ${number_a} hz ${number_b}, plant ${txt_short} sensor ${txt_long}" )] diff --git a/rust/src/main.rs b/rust/src/main.rs index cf04194..dd08ee5 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -17,7 +17,7 @@ use esp_idf_sys::{ esp_ota_img_states_t_ESP_OTA_IMG_VALID, vTaskDelay, }; use esp_ota::{mark_app_valid, rollback_and_reboot}; -use log::log; +use log::{log, LogMessage}; use once_cell::sync::Lazy; use plant_hal::{PlantCtrlBoard, PlantHal, PLANT_COUNT}; use serde::{Deserialize, Serialize}; @@ -401,37 +401,41 @@ fn safe_main() -> anyhow::Result<()> { let dry_run = false; 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 - //log( - //LogMessage::SensorTankRaw, - //raw_value_mv as u32, - //percent as u32, - //"", - //"", - //); - } 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 {}", - //percent as u8, config.tank.tank_warn_percent - //); - board.general_fault(true); - } else if tank_state.enough_water(&config.tank).is_ok_and(|enough| enough) { - //TODO(judge) enable logging again, might require returning level as well - //println!( - //"Enough water, current percent is {}, minimum empty level is {}", - //percent as u8, config.tank.tank_empty_percent - //); - } else { - if let Ok(_left_ml) = tank_state.left_ml(&config.tank) { - //TODO(judge) enable logging again, might require returning level as well - //println!( - //"Tank sensor returned mv {} as {}% leaving {} ml useable", - //rv.raw, percent as u8, rv.left_ml - //); + + if tank_state.is_enabled() { + if let Some(err) = tank_state.got_error(&config.tank) { + match err { + TankError::SensorDisabled => { /* unreachable */ } + TankError::SensorMissing(raw_value_mv) => log( + LogMessage::TankSensorMissing, + raw_value_mv as u32, + 0, + "", + "", + ), + TankError::SensorValueError { value, min, max } => log( + LogMessage::TankSensorValueRangeError, + min as u32, + max as u32, + &format!("{}", value), + "", + ), + TankError::BoardError(err) => log( + LogMessage::TankSensorBoardError, + 0, + 0, + "", + &format!("{}", &err.to_string()), + ), + } + // 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 attempt = 1; @@ -440,14 +444,14 @@ fn safe_main() -> anyhow::Result<()> { match &temp { Ok(res) => { println!("Water temp is {}", res); - break temp + break temp; } Err(err) => { println!("Could not get water temp {} attempt {}", err, attempt) } } if attempt == 5 { - break temp + break temp; } attempt += 1; }; @@ -682,14 +686,12 @@ fn determine_state_target_moisture_for_plant( match tank_state.enough_water(&config.tank) { Err(_tank_err) => { if !config.tank.tank_allow_pumping_if_sensor_error { - // ignore is ok - // wtf does this meen, shouldn't something happen if the configuration specifies - // that no water should flow if there was an error? + state.no_water = true; } - }, - Ok(enough_water) => { - state.no_water = !enough_water - }, + } + // when no tank error, if plant should be watered depends on if enough water is in tank + // 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(); @@ -750,10 +752,10 @@ fn determine_state_timer_only_for_plant( if !config.tank.tank_allow_pumping_if_sensor_error { state.do_water = true; } - }, + } Ok(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 { state.do_water = true; } - }, + } Ok(enough_water) => { state.no_water = !enough_water; - }, + } } } } diff --git a/rust/src/tank.rs b/rust/src/tank.rs index eb531a0..f0a1de8 100644 --- a/rust/src/tank.rs +++ b/rust/src/tank.rs @@ -1,6 +1,9 @@ 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; pub const WATER_FROZEN_THRESH: f32 = 4.0; @@ -10,7 +13,7 @@ pub enum TankError { SensorDisabled, SensorMissing(f32), SensorValueError { value: f32, min: f32, max: f32 }, - BoardError(String) + BoardError(String), } pub enum TankState { @@ -35,18 +38,21 @@ fn raw_voltage_to_tank_fill_percent( 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() { + 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)) + 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 { @@ -69,10 +75,14 @@ impl TankState { } 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), @@ -84,29 +94,37 @@ impl TankState { } else { Ok(false) } - }, + } } } 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::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 { + 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 }, + 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::TankSensorDisabled | TankState::TankSensorError(_) => None, TankState::TankSensorPresent(raw_value_mv) => Some(*raw_value_mv), }; @@ -116,9 +134,11 @@ impl TankState { left_ml, sensor_error: tank_err, 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(), - 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 { 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())) + Err(err) => TankState::TankSensorError(TankError::BoardError(err.to_string())), } - } else { TankState::TankSensorDisabled }