WIP introduce plant_state module
This commit is contained in:
		| @@ -26,21 +26,14 @@ use crate::{config::PlantControllerConfig, webserver::webserver::httpd}; | ||||
| mod config; | ||||
| mod log; | ||||
| pub mod plant_hal; | ||||
| mod plant_state; | ||||
| mod tank; | ||||
|  | ||||
| use plant_state::{PlantInfo, PlantStateMQTT}; | ||||
| use tank::*; | ||||
|  | ||||
| const TIME_ZONE: Tz = Berlin; | ||||
|  | ||||
| const MOIST_SENSOR_MAX_FREQUENCY: u32 = 5500; // 60kHz (500Hz margin) | ||||
| const MOIST_SENSOR_MIN_FREQUENCY: u32 = 150; // this is really really dry, think like cactus levels | ||||
|  | ||||
| const FROM: (f32, f32) = ( | ||||
|     MOIST_SENSOR_MIN_FREQUENCY as f32, | ||||
|     MOIST_SENSOR_MAX_FREQUENCY as f32, | ||||
| ); | ||||
| const TO: (f32, f32) = (0_f32, 100_f32); | ||||
|  | ||||
| pub static BOARD_ACCESS: Lazy<Mutex<PlantCtrlBoard>> = Lazy::new(|| PlantHal::create().unwrap()); | ||||
| pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false)); | ||||
|  | ||||
| @@ -80,46 +73,6 @@ struct LightState { | ||||
|     is_day: bool, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, PartialEq, Default)] | ||||
| /// State of a single plant to be tracked | ||||
| /// | ||||
| /// TODO can some state be replaced with functions | ||||
| /// TODO unify with PlantStateMQTT | ||||
| struct PlantState { | ||||
|     /// state of humidity sensor on bank a | ||||
|     a: Option<u8>, | ||||
|     /// raw measured frequency value for sensor on bank a in hertz | ||||
|     a_raw: Option<u32>, | ||||
|     /// state of humidity sensor on bank b | ||||
|     b: Option<u8>, | ||||
|     /// raw measured frequency value for sensor on bank b in hertz | ||||
|     b_raw: Option<u32>, | ||||
|     /// how often has the logic determined that plant should have been irrigated but wasn't | ||||
|     consecutive_pump_count: u32, | ||||
|     /// plant needs to be watered | ||||
|     do_water: bool, | ||||
|     /// is plant considerd to be dry according to settings | ||||
|     dry: bool, | ||||
|     /// is pump currently running | ||||
|     active: bool, | ||||
|     /// TODO: convert this to an Option<PumpErorr> enum for every case that can happen | ||||
|     pump_error: bool, | ||||
|     /// if pump count has increased higher than configured limit | ||||
|     not_effective: bool, | ||||
|     /// plant irrigation cooldown is active | ||||
|     cooldown: bool, | ||||
|     /// we want to irrigate but tank is empty | ||||
|     no_water: bool, | ||||
|     ///TODO: combine with field a using Result | ||||
|     sensor_error_a: Option<SensorError>, | ||||
|     ///TODO: combine with field b using Result | ||||
|     sensor_error_b: Option<SensorError>, | ||||
|     /// pump should not be watered at this time of day | ||||
|     out_of_work_hour: bool, | ||||
|     /// next time when pump should activate | ||||
|     next_pump: Option<DateTime<Tz>>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug, PartialEq)] | ||||
| /// humidity sensor error | ||||
| enum SensorError { | ||||
| @@ -128,24 +81,6 @@ enum SensorError { | ||||
|     OpenCircuit { hz: f32, min: f32 }, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| struct PlantStateMQTT<'a> { | ||||
|     a: &'a str, | ||||
|     a_raw: &'a str, | ||||
|     b: &'a str, | ||||
|     b_raw: &'a str, | ||||
|     mode: &'a str, | ||||
|     consecutive_pump_count: u32, | ||||
|     dry: bool, | ||||
|     active: bool, | ||||
|     pump_error: bool, | ||||
|     not_effective: bool, | ||||
|     cooldown: bool, | ||||
|     out_of_work_hour: bool, | ||||
|     last_pump: &'a str, | ||||
|     next_pump: &'a str, | ||||
| } | ||||
|  | ||||
| fn safe_main() -> anyhow::Result<()> { | ||||
|     // It is necessary to call this function once. Otherwise some patches to the runtime | ||||
|     // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 | ||||
| @@ -471,7 +406,7 @@ fn safe_main() -> anyhow::Result<()> { | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     let mut plantstate: [PlantState; PLANT_COUNT] = core::array::from_fn(|_| PlantState { | ||||
|     let mut plantstate: [PlantInfo; PLANT_COUNT] = core::array::from_fn(|_| PlantInfo { | ||||
|         ..Default::default() | ||||
|     }); | ||||
|     determine_plant_state( | ||||
| @@ -636,7 +571,7 @@ fn publish_battery_state( | ||||
| fn determine_state_target_moisture_for_plant( | ||||
|     board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, | ||||
|     plant: usize, | ||||
|     state: &mut PlantState, | ||||
|     state: &mut PlantInfo, | ||||
|     config: &PlantControllerConfig, | ||||
|     tank_state: &TankState, | ||||
|     cur: DateTime<Tz>, | ||||
| @@ -731,7 +666,7 @@ fn determine_state_target_moisture_for_plant( | ||||
| fn determine_state_timer_only_for_plant( | ||||
|     board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, | ||||
|     plant: usize, | ||||
|     state: &mut PlantState, | ||||
|     state: &mut PlantInfo, | ||||
|     config: &PlantControllerConfig, | ||||
|     tank_state: &TankState, | ||||
|     cur: DateTime<Tz>, | ||||
| @@ -774,7 +709,7 @@ fn determine_state_timer_only_for_plant( | ||||
| fn determine_state_timer_and_deadzone_for_plant( | ||||
|     board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, | ||||
|     plant: usize, | ||||
|     state: &mut PlantState, | ||||
|     state: &mut PlantInfo, | ||||
|     config: &PlantControllerConfig, | ||||
|     tank_state: &TankState, | ||||
|     cur: DateTime<Tz>, | ||||
| @@ -823,7 +758,7 @@ fn determine_state_timer_and_deadzone_for_plant( | ||||
| } | ||||
|  | ||||
| fn determine_plant_state( | ||||
|     plantstate: &mut [PlantState; PLANT_COUNT], | ||||
|     plantstate: &mut [PlantInfo; PLANT_COUNT], | ||||
|     cur: DateTime<Tz>, | ||||
|     tank_state: &TankState, | ||||
|     config: &PlantControllerConfig, | ||||
| @@ -861,7 +796,7 @@ fn determine_plant_state( | ||||
| } | ||||
|  | ||||
| fn update_plant_state( | ||||
|     plantstate: &mut [PlantState; PLANT_COUNT], | ||||
|     plantstate: &mut [PlantInfo; PLANT_COUNT], | ||||
|     board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, | ||||
|     config: &PlantControllerConfig, | ||||
| ) { | ||||
| @@ -1006,18 +941,6 @@ fn to_string<T: Display>(value: Result<T>) -> String { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| fn map_range_moisture(s: f32) -> Result<u8, SensorError> { | ||||
|     if s < FROM.0 { | ||||
|         return Err(SensorError::OpenCircuit { hz: s, min: FROM.0 }); | ||||
|     } | ||||
|     if s > FROM.1 { | ||||
|         return Err(SensorError::ShortCircuit { hz: s, max: FROM.1 }); | ||||
|     } | ||||
|     let tmp = TO.0 + (s - FROM.0) * (TO.1 - TO.0) / (FROM.1 - FROM.0); | ||||
|  | ||||
|     return Ok(tmp as u8); | ||||
| } | ||||
|  | ||||
| fn in_time_range(cur: &DateTime<Tz>, start: u8, end: u8) -> bool { | ||||
|     let curhour = cur.hour() as u8; | ||||
|     //eg 10-14 | ||||
|   | ||||
							
								
								
									
										130
									
								
								rust/src/plant_state.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								rust/src/plant_state.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| use chrono::{DateTime, Utc}; | ||||
| use chrono_tz::Tz; | ||||
| use serde::Serialize; | ||||
|  | ||||
| use crate::{config, plant_hal}; | ||||
|  | ||||
| const MOIST_SENSOR_MAX_FREQUENCY: u32 = 5500; // 60kHz (500Hz margin) | ||||
| const MOIST_SENSOR_MIN_FREQUENCY: u32 = 150; // this is really really dry, think like cactus levels | ||||
|  | ||||
| pub enum HumiditySensorError{ | ||||
|     ShortCircuit{hz: f32, max: f32}, | ||||
|     OpenLoop{hz: f32, min: f32} | ||||
| } | ||||
|  | ||||
| pub enum HumiditySensorState { | ||||
|     Disabled, | ||||
|     HumidityValue{raw_hz: u32, moisture_percent: f32}, | ||||
|     SensorError(HumiditySensorError), | ||||
|     BoardError(String) | ||||
| } | ||||
|  | ||||
| impl HumiditySensorState { | ||||
| } | ||||
|  | ||||
| pub enum PumpError {} | ||||
|  | ||||
| pub struct PumpState { | ||||
|     consecutive_pump_count: u32, | ||||
|     previous_pump: Option<DateTime<Utc>> | ||||
| } | ||||
|  | ||||
| pub enum PlantError{} | ||||
|  | ||||
| pub struct PlantState { | ||||
|     sensor_a: HumiditySensorState, | ||||
|     sensor_b: HumiditySensorState, | ||||
|     pump: PumpState, | ||||
| } | ||||
|  | ||||
| fn map_range_moisture(s: f32) -> Result<f32, HumiditySensorError> { | ||||
|     if s < MOIST_SENSOR_MIN_FREQUENCY { | ||||
|         return Err(HumiditySensorError::OpenCircuit { hz: s, min: FROM.0 }); | ||||
|     } | ||||
|     if s > MOIST_SENSOR_MAX_FREQUENCY { | ||||
|         return Err(HumiditySensorError::ShortCircuit { hz: s, max: FROM.1 }); | ||||
|     } | ||||
|     let moisture_percent = (s - MOIST_SENSOR_MIN_FREQUENCY) * 100 / (MOIST_SENSOR_MAX_FREQUENCY - MOIST_SENSOR_MIN_FREQUENCY); | ||||
|  | ||||
|     return Ok(moisture_percent); | ||||
| } | ||||
|  | ||||
|  | ||||
| impl PlantState { | ||||
|     pub fn read_hardware_state( | ||||
|         plant_id: usize, | ||||
|         board: &mut plant_hal::PlantCtrlBoard, | ||||
|         config: &config::PlantConfig | ||||
|     ) -> Self { | ||||
|         let sensor_a = if config.sensor_a { | ||||
|             match board.measure_moisture_hz(plant_id, plant_hal::Sensor::A) { | ||||
|                 Ok(raw) => { | ||||
|                     match map_range_moisture(raw) { | ||||
|                         Ok(moisture_percent) => HumiditySensorState::HumidityValue { raw_hz: raw, moisture_percent }, | ||||
|                         Err(err) => HumiditySensorState::SensorError(err), | ||||
|                     } | ||||
|                 }, | ||||
|                 Err(err) => HumiditySensorState::BoardError(err.to_string()), | ||||
|             } | ||||
|         } else { | ||||
|             HumiditySensorState::Disabled | ||||
|         }; | ||||
|         let sensor_b = if config.sensor_b { | ||||
|             match board.measure_moisture_hz(plant_id, plant_hal::Sensor::B) { | ||||
|                 Ok(raw) => { | ||||
|                     match map_range_moisture(raw) { | ||||
|                         Ok(moisture_percent) => HumiditySensorState::HumidityValue { raw_hz: raw, moisture_percent }, | ||||
|                         Err(err) => HumiditySensorState::SensorError(err), | ||||
|                     } | ||||
|                 }, | ||||
|                 Err(err) => HumiditySensorState::BoardError(err.to_string()), | ||||
|             } | ||||
|         } else { | ||||
|             HumiditySensorState::Disabled | ||||
|         }; | ||||
|         let previous_pump = board.last_pump_time(plant_id); | ||||
|         let consecutive_pump_count = board.consecutive_pump_count(plant_id); | ||||
|         Self { | ||||
|             sensor_a, | ||||
|             sensor_b, | ||||
|             pump: PumpState { consecutive_pump_count , previous_pump} | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, PartialEq, Default, Serialize)] | ||||
| /// State of a single plant to be tracked | ||||
| pub struct PlantInfo { | ||||
|     /// state of humidity sensor on bank a | ||||
|     a: HumiditySensorState, | ||||
|     /// raw measured frequency value for sensor on bank a in hertz | ||||
|     a_raw: Option<u32>, | ||||
|     /// state of humidity sensor on bank b | ||||
|     b: HumiditySensorState, | ||||
|     /// raw measured frequency value for sensor on bank b in hertz | ||||
|     b_raw: Option<u32>, | ||||
|     /// configured plant watering mode | ||||
|     mode: config::Mode, | ||||
|     /// how often has the logic determined that plant should have been irrigated but wasn't | ||||
|     consecutive_pump_count: u32, | ||||
|     /// plant needs to be watered | ||||
|     do_water: bool, | ||||
|     /// is plant considerd to be dry according to settings | ||||
|     dry: bool, | ||||
|     /// is pump currently running | ||||
|     active: bool, | ||||
|     /// TODO: convert this to an Option<PumpErorr> enum for every case that can happen | ||||
|     pump_error: bool, | ||||
|     /// if pump count has increased higher than configured limit | ||||
|     not_effective: bool, | ||||
|     /// plant irrigation cooldown is active | ||||
|     cooldown: bool, | ||||
|     /// we want to irrigate but tank is empty | ||||
|     no_water: bool, | ||||
|     /// pump should not be watered at this time of day | ||||
|     out_of_work_hour: bool, | ||||
|     /// last time when pump was active | ||||
|     last_pump: Option<DateTime<Tz>>, | ||||
|     /// next time when pump should activate | ||||
|     next_pump: Option<DateTime<Tz>>, | ||||
| } | ||||
		Reference in New Issue
	
	Block a user