refactor/plant-state-handling #11
| @@ -4,7 +4,7 @@ use std::{ | |||||||
| }; | }; | ||||||
|  |  | ||||||
| use anyhow::{bail, Result}; | use anyhow::{bail, Result}; | ||||||
| use chrono::{DateTime, Datelike, TimeDelta, Timelike, Utc}; | use chrono::{DateTime, Datelike, Timelike}; | ||||||
| use chrono_tz::{Europe::Berlin, Tz}; | use chrono_tz::{Europe::Berlin, Tz}; | ||||||
|  |  | ||||||
| use esp_idf_hal::delay::Delay; | use esp_idf_hal::delay::Delay; | ||||||
| @@ -29,7 +29,7 @@ mod plant_state; | |||||||
| mod tank; | mod tank; | ||||||
| pub mod util; | pub mod util; | ||||||
|  |  | ||||||
| use plant_state::{PlantInfo, PlantState}; | use plant_state::PlantState; | ||||||
| use tank::*; | use tank::*; | ||||||
|  |  | ||||||
| const TIME_ZONE: Tz = Berlin; | const TIME_ZONE: Tz = Berlin; | ||||||
| @@ -406,8 +406,21 @@ fn safe_main() -> anyhow::Result<()> { | |||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let mut plantstate: [PlantState; PLANT_COUNT] = |     let plantstate: [PlantState; PLANT_COUNT] = | ||||||
|         core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i])); |         core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i])); | ||||||
|  |     for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(&config.plants).enumerate() { | ||||||
|  |         match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, &timezone_time)) { | ||||||
|  |             Ok(state) => { | ||||||
|  |                 let plant_topic = format!("/plant{}", plant_id + 1); | ||||||
|  |                 let _ = board.mqtt_publish(&config, &plant_topic, state.as_bytes()); | ||||||
|  |                 //reduce speed as else messages will be dropped | ||||||
|  |                 Delay::new_default().delay_ms(200); | ||||||
|  |             } | ||||||
|  |             Err(err) => { | ||||||
|  |                 println!("Error publishing plant state {}", err); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     let pump_required = plantstate |     let pump_required = plantstate | ||||||
|         .iter() |         .iter() | ||||||
| @@ -457,7 +470,6 @@ fn safe_main() -> anyhow::Result<()> { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     //update_plant_state(&mut plantstate, &mut board, &config); |  | ||||||
|  |  | ||||||
|     let is_day = board.is_day(); |     let is_day = board.is_day(); | ||||||
|     let state_of_charge = board.state_charge_percent().unwrap_or(0); |     let state_of_charge = board.state_charge_percent().unwrap_or(0); | ||||||
| @@ -622,40 +634,6 @@ fn main() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fn time_to_string_utc(value_option: Option<DateTime<Utc>>) -> String { |  | ||||||
|     let converted = value_option.and_then(|utc| Some(utc.with_timezone(&TIME_ZONE))); |  | ||||||
|     return time_to_string(converted); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn time_to_string(value_option: Option<DateTime<Tz>>) -> String { |  | ||||||
|     match value_option { |  | ||||||
|         Some(value) => { |  | ||||||
|             let europe_time = value.with_timezone(&TIME_ZONE); |  | ||||||
|             if europe_time.year() > 2023 { |  | ||||||
|                 return europe_time.to_rfc3339(); |  | ||||||
|             } else { |  | ||||||
|                 //initial value of 0 in rtc memory |  | ||||||
|                 return "N/A".to_owned(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         None => return "N/A".to_owned(), |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn sensor_to_string(value: &Option<u8>, error: &Option<SensorError>, enabled: bool) -> String { |  | ||||||
|     if enabled { |  | ||||||
|         match error { |  | ||||||
|             Some(error) => return format!("{:?}", error), |  | ||||||
|             None => match value { |  | ||||||
|                 Some(v) => return v.to_string(), |  | ||||||
|                 None => return "Error".to_owned(), |  | ||||||
|             }, |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         return "disabled".to_owned(); |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn to_string<T: Display>(value: Result<T>) -> String { | fn to_string<T: Display>(value: Result<T>) -> String { | ||||||
|     return match value { |     return match value { | ||||||
|         Ok(v) => v.to_string(), |         Ok(v) => v.to_string(), | ||||||
| @@ -665,7 +643,7 @@ fn to_string<T: Display>(value: Result<T>) -> String { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| fn in_time_range(cur: &DateTime<Tz>, start: u8, end: u8) -> bool { | pub fn in_time_range(cur: &DateTime<Tz>, start: u8, end: u8) -> bool { | ||||||
|     let curhour = cur.hour() as u8; |     let curhour = cur.hour() as u8; | ||||||
|     //eg 10-14 |     //eg 10-14 | ||||||
|     if start < end { |     if start < end { | ||||||
|   | |||||||
| @@ -1,38 +1,40 @@ | |||||||
| use chrono::{DateTime, TimeDelta, Utc}; | use chrono::{DateTime, TimeDelta, Utc}; | ||||||
| use chrono_tz::Tz; | use chrono_tz::Tz; | ||||||
| use measurements::humidity; |  | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     config::{self, PlantConfig}, |     config::{self, PlantConfig}, | ||||||
|     plant_hal::{self, PLANT_COUNT}, |     in_time_range, plant_hal, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const MOIST_SENSOR_MAX_FREQUENCY: f32 = 5500.; // 60kHz (500Hz margin) | const MOIST_SENSOR_MAX_FREQUENCY: f32 = 5500.; // 60kHz (500Hz margin) | ||||||
| const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really really dry, think like cactus levels | const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really really dry, think like cactus levels | ||||||
|  |  | ||||||
| #[derive(Debug, PartialEq, Serialize)] | #[derive(Debug, PartialEq, Serialize)] | ||||||
| pub enum HumiditySensorError { | pub enum MoistureSensorError { | ||||||
|     ShortCircuit { hz: f32, max: f32 }, |     ShortCircuit { hz: f32, max: f32 }, | ||||||
|     OpenLoop { hz: f32, min: f32 }, |     OpenLoop { hz: f32, min: f32 }, | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, PartialEq, Serialize)] |  | ||||||
| pub enum HumiditySensorState { |  | ||||||
|     Disabled, |  | ||||||
|     HumidityValue { raw_hz: f32, moisture_percent: f32 }, |  | ||||||
|     SensorError(HumiditySensorError), |  | ||||||
|     BoardError(String), |     BoardError(String), | ||||||
| } | } | ||||||
|  |  | ||||||
| impl HumiditySensorState { | #[derive(Debug, PartialEq, Serialize)] | ||||||
|     pub fn is_err(&self) -> bool { | pub enum MoistureSensorState { | ||||||
|         matches!(self, Self::SensorError(_)) || matches!(self, Self::BoardError(_)) |     Disabled, | ||||||
|  |     MoistureValue { raw_hz: f32, moisture_percent: f32 }, | ||||||
|  |     SensorError(MoistureSensorError), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl MoistureSensorState { | ||||||
|  |     pub fn is_err(&self) -> Option<&MoistureSensorError> { | ||||||
|  |         match self { | ||||||
|  |             MoistureSensorState::SensorError(moisture_sensor_error) => Some(moisture_sensor_error), | ||||||
|  |             _ => None, | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn moisture_percent(&self) -> Option<f32> { |     pub fn moisture_percent(&self) -> Option<f32> { | ||||||
|         if let HumiditySensorState::HumidityValue { |         if let MoistureSensorState::MoistureValue { | ||||||
|             raw_hz, |             raw_hz: _, | ||||||
|             moisture_percent, |             moisture_percent, | ||||||
|         } = self |         } = self | ||||||
|         { |         { | ||||||
| @@ -43,7 +45,7 @@ impl HumiditySensorState { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl HumiditySensorState {} | impl MoistureSensorState {} | ||||||
|  |  | ||||||
| #[derive(Debug, PartialEq, Serialize)] | #[derive(Debug, PartialEq, Serialize)] | ||||||
| pub enum PumpError { | pub enum PumpError { | ||||||
| @@ -59,30 +61,41 @@ pub struct PumpState { | |||||||
|     previous_pump: Option<DateTime<Utc>>, |     previous_pump: Option<DateTime<Utc>>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] | impl PumpState { | ||||||
|  |     fn is_err(&self, plant_config: &PlantConfig) -> Option<PumpError> { | ||||||
|  |         if self.consecutive_pump_count > plant_config.max_consecutive_pump_count as u32 { | ||||||
|  |             Some(PumpError::PumpNotWorking { | ||||||
|  |                 failed_attempts: self.consecutive_pump_count as usize, | ||||||
|  |                 max_allowed_failures: plant_config.max_consecutive_pump_count as usize, | ||||||
|  |             }) | ||||||
|  |         } else { | ||||||
|  |             None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] | ||||||
| pub enum PlantWateringMode { | pub enum PlantWateringMode { | ||||||
|     OFF, |     OFF, | ||||||
|     TargetMoisture, |     TargetMoisture, | ||||||
|     TimerOnly, |     TimerOnly, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub enum PlantError {} |  | ||||||
|  |  | ||||||
| pub struct PlantState { | pub struct PlantState { | ||||||
|     pub sensor_a: HumiditySensorState, |     pub sensor_a: MoistureSensorState, | ||||||
|     pub sensor_b: HumiditySensorState, |     pub sensor_b: MoistureSensorState, | ||||||
|     pub pump: PumpState, |     pub pump: PumpState, | ||||||
| } | } | ||||||
|  |  | ||||||
| fn map_range_moisture(s: f32) -> Result<f32, HumiditySensorError> { | fn map_range_moisture(s: f32) -> Result<f32, MoistureSensorError> { | ||||||
|     if s < MOIST_SENSOR_MIN_FREQUENCY { |     if s < MOIST_SENSOR_MIN_FREQUENCY { | ||||||
|         return Err(HumiditySensorError::OpenLoop { |         return Err(MoistureSensorError::OpenLoop { | ||||||
|             hz: s, |             hz: s, | ||||||
|             min: MOIST_SENSOR_MIN_FREQUENCY, |             min: MOIST_SENSOR_MIN_FREQUENCY, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     if s > MOIST_SENSOR_MAX_FREQUENCY { |     if s > MOIST_SENSOR_MAX_FREQUENCY { | ||||||
|         return Err(HumiditySensorError::ShortCircuit { |         return Err(MoistureSensorError::ShortCircuit { | ||||||
|             hz: s, |             hz: s, | ||||||
|             max: MOIST_SENSOR_MAX_FREQUENCY, |             max: MOIST_SENSOR_MAX_FREQUENCY, | ||||||
|         }); |         }); | ||||||
| @@ -102,30 +115,34 @@ impl PlantState { | |||||||
|         let sensor_a = if config.sensor_a { |         let sensor_a = if config.sensor_a { | ||||||
|             match board.measure_moisture_hz(plant_id, plant_hal::Sensor::A) { |             match board.measure_moisture_hz(plant_id, plant_hal::Sensor::A) { | ||||||
|                 Ok(raw) => match map_range_moisture(raw) { |                 Ok(raw) => match map_range_moisture(raw) { | ||||||
|                     Ok(moisture_percent) => HumiditySensorState::HumidityValue { |                     Ok(moisture_percent) => MoistureSensorState::MoistureValue { | ||||||
|                         raw_hz: raw, |                         raw_hz: raw, | ||||||
|                         moisture_percent, |                         moisture_percent, | ||||||
|                     }, |                     }, | ||||||
|                     Err(err) => HumiditySensorState::SensorError(err), |                     Err(err) => MoistureSensorState::SensorError(err), | ||||||
|                 }, |                 }, | ||||||
|                 Err(err) => HumiditySensorState::BoardError(err.to_string()), |                 Err(err) => MoistureSensorState::SensorError(MoistureSensorError::BoardError( | ||||||
|  |                     err.to_string(), | ||||||
|  |                 )), | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             HumiditySensorState::Disabled |             MoistureSensorState::Disabled | ||||||
|         }; |         }; | ||||||
|         let sensor_b = if config.sensor_b { |         let sensor_b = if config.sensor_b { | ||||||
|             match board.measure_moisture_hz(plant_id, plant_hal::Sensor::B) { |             match board.measure_moisture_hz(plant_id, plant_hal::Sensor::B) { | ||||||
|                 Ok(raw) => match map_range_moisture(raw) { |                 Ok(raw) => match map_range_moisture(raw) { | ||||||
|                     Ok(moisture_percent) => HumiditySensorState::HumidityValue { |                     Ok(moisture_percent) => MoistureSensorState::MoistureValue { | ||||||
|                         raw_hz: raw, |                         raw_hz: raw, | ||||||
|                         moisture_percent, |                         moisture_percent, | ||||||
|                     }, |                     }, | ||||||
|                     Err(err) => HumiditySensorState::SensorError(err), |                     Err(err) => MoistureSensorState::SensorError(err), | ||||||
|                 }, |                 }, | ||||||
|                 Err(err) => HumiditySensorState::BoardError(err.to_string()), |                 Err(err) => MoistureSensorState::SensorError(MoistureSensorError::BoardError( | ||||||
|  |                     err.to_string(), | ||||||
|  |                 )), | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             HumiditySensorState::Disabled |             MoistureSensorState::Disabled | ||||||
|         }; |         }; | ||||||
|         let previous_pump = board.last_pump_time(plant_id); |         let previous_pump = board.last_pump_time(plant_id); | ||||||
|         let consecutive_pump_count = board.consecutive_pump_count(plant_id); |         let consecutive_pump_count = board.consecutive_pump_count(plant_id); | ||||||
| @@ -144,6 +161,9 @@ impl PlantState { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn pump_in_timeout(&self, plant_conf: &PlantConfig, current_time: &DateTime<Tz>) -> bool { |     pub fn pump_in_timeout(&self, plant_conf: &PlantConfig, current_time: &DateTime<Tz>) -> bool { | ||||||
|  |         if matches!(plant_conf.mode, PlantWateringMode::OFF) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|         self.pump.previous_pump.is_some_and(|last_pump| { |         self.pump.previous_pump.is_some_and(|last_pump| { | ||||||
|             last_pump |             last_pump | ||||||
|                 .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into())) |                 .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into())) | ||||||
| @@ -154,7 +174,26 @@ impl PlantState { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn is_err(&self) -> bool { |     pub fn is_err(&self) -> bool { | ||||||
|         self.sensor_a.is_err() || self.sensor_b.is_err() |         self.sensor_a.is_err().is_some() || self.sensor_b.is_err().is_some() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn plant_moisture( | ||||||
|  |         &self, | ||||||
|  |     ) -> ( | ||||||
|  |         Option<f32>, | ||||||
|  |         (Option<&MoistureSensorError>, Option<&MoistureSensorError>), | ||||||
|  |     ) { | ||||||
|  |         match ( | ||||||
|  |             self.sensor_a.moisture_percent(), | ||||||
|  |             self.sensor_b.moisture_percent(), | ||||||
|  |         ) { | ||||||
|  |             (Some(moisture_a), Some(moisture_b)) => { | ||||||
|  |                 (Some((moisture_a + moisture_b) / 2.), (None, None)) | ||||||
|  |             } | ||||||
|  |             (Some(moisture_percent), _) => (Some(moisture_percent), (None, self.sensor_b.is_err())), | ||||||
|  |             (_, Some(moisture_percent)) => (Some(moisture_percent), (self.sensor_a.is_err(), None)), | ||||||
|  |             _ => (None, (self.sensor_a.is_err(), self.sensor_b.is_err())), | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn needs_to_be_watered( |     pub fn needs_to_be_watered( | ||||||
| @@ -165,18 +204,8 @@ impl PlantState { | |||||||
|         match plant_conf.mode { |         match plant_conf.mode { | ||||||
|             PlantWateringMode::OFF => false, |             PlantWateringMode::OFF => false, | ||||||
|             PlantWateringMode::TargetMoisture => { |             PlantWateringMode::TargetMoisture => { | ||||||
|                 let moisture_percent = match ( |                 let (moisture_percent, _) = self.plant_moisture(); | ||||||
|                     self.sensor_a.moisture_percent(), |                 if let Some(moisture_percent) = moisture_percent { | ||||||
|                     self.sensor_b.moisture_percent(), |  | ||||||
|                 ) { |  | ||||||
|                     (Some(moisture_a), Some(moisture_b)) => (moisture_a + moisture_b) / 2., |  | ||||||
|                     (Some(moisture_percent), _) => moisture_percent, |  | ||||||
|                     (_, Some(moisture_percent)) => moisture_percent, |  | ||||||
|                     _ => { |  | ||||||
|                         // Case for both sensors hitting an error do not water plant in this case |  | ||||||
|                         return false; |  | ||||||
|                     } |  | ||||||
|                 }; |  | ||||||
|                     if self.pump_in_timeout(plant_conf, current_time) { |                     if self.pump_in_timeout(plant_conf, current_time) { | ||||||
|                         false |                         false | ||||||
|                     } else { |                     } else { | ||||||
| @@ -186,6 +215,10 @@ impl PlantState { | |||||||
|                             false |                             false | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } else { | ||||||
|  |                     // in case no moisture can be determined do not water plant | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             PlantWateringMode::TimerOnly => { |             PlantWateringMode::TimerOnly => { | ||||||
|                 if self.pump_in_timeout(plant_conf, current_time) { |                 if self.pump_in_timeout(plant_conf, current_time) { | ||||||
| @@ -196,61 +229,53 @@ impl PlantState { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } |  | ||||||
|  |  | ||||||
| //fn update_plant_state( |     pub fn to_mqtt_info(&self, plant_conf: &PlantConfig, current_time: &DateTime<Tz>) -> PlantInfo { | ||||||
| //    plantstate: &mut [PlantInfo; PLANT_COUNT], |         PlantInfo { | ||||||
| //    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, |             sensor_a: &self.sensor_a, | ||||||
| //    config: &PlantControllerConfig, |             sensor_b: &self.sensor_b, | ||||||
| //) { |             mode: plant_conf.mode, | ||||||
| //    for plant in 0..PLANT_COUNT { |             do_water: self.needs_to_be_watered(plant_conf, current_time), | ||||||
| //        let state = &plantstate[plant]; |             dry: if let Some(moisture_percent) = self.plant_moisture().0 { | ||||||
| //        let plant_config = &config.plants[plant]; |                 moisture_percent < plant_conf.target_moisture | ||||||
| // |             } else { | ||||||
| //        let mode = format!("{:?}", plant_config.mode); |                 false | ||||||
| // |             }, | ||||||
| //        let plant_dto = PlantStateMQTT { |             cooldown: self.pump_in_timeout(plant_conf, current_time), | ||||||
| //            a: &sensor_to_string( |             out_of_work_hour: in_time_range( | ||||||
| //                &state.a, |                 current_time, | ||||||
| //                &state.sensor_error_a, |                 plant_conf.pump_hour_start, | ||||||
| //                plant_config.mode != PlantWateringMode::OFF, |                 plant_conf.pump_hour_end, | ||||||
| //            ), |             ), | ||||||
| //            a_raw: &state.a_raw.unwrap_or(0).to_string(), |             consecutive_pump_count: self.pump.consecutive_pump_count, | ||||||
| //            b: &sensor_to_string(&state.b, &state.sensor_error_b, plant_config.sensor_b), |             pump_error: self.pump.is_err(plant_conf), | ||||||
| //            b_raw: &state.b_raw.unwrap_or(0).to_string(), |             last_pump: self | ||||||
| //            active: state.active, |                 .pump | ||||||
| //            mode: &mode, |                 .previous_pump | ||||||
| //            last_pump: &time_to_string_utc(board.last_pump_time(plant)), |                 .map(|t| t.with_timezone(¤t_time.timezone())), | ||||||
| //            next_pump: &time_to_string(state.next_pump), |             next_pump: if matches!( | ||||||
| //            consecutive_pump_count: state.consecutive_pump_count, |                 plant_conf.mode, | ||||||
| //            cooldown: state.cooldown, |                 PlantWateringMode::TimerOnly | PlantWateringMode::TargetMoisture | ||||||
| //            dry: state.dry, |             ) { | ||||||
| //            not_effective: state.not_effective, |                 self.pump.previous_pump.and_then(|last_pump| { | ||||||
| //            out_of_work_hour: state.out_of_work_hour, |                     last_pump | ||||||
| //            pump_error: state.pump_error, |                         .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into())) | ||||||
| //        }; |                         .map(|t| t.with_timezone(¤t_time.timezone())) | ||||||
| // |                 }) | ||||||
| //        match serde_json::to_string(&plant_dto) { |             } else { | ||||||
| //            Ok(state) => { |                 None | ||||||
| //                let plant_topic = format!("/plant{}", plant + 1); |             }, | ||||||
| //                let _ = board.mqtt_publish(&config, &plant_topic, state.as_bytes()); |         } | ||||||
| //                //reduce speed as else messages will be dropped |     } | ||||||
| //                Delay::new_default().delay_ms(200); | } | ||||||
| //            } |  | ||||||
| //            Err(err) => { |  | ||||||
| //                println!("Error publishing lightstate {}", err); |  | ||||||
| //            } |  | ||||||
| //        }; |  | ||||||
| //    } |  | ||||||
| //} |  | ||||||
|  |  | ||||||
| #[derive(Debug, PartialEq, Serialize)] | #[derive(Debug, PartialEq, Serialize)] | ||||||
| /// State of a single plant to be tracked | /// State of a single plant to be tracked | ||||||
| pub struct PlantInfo { | pub struct PlantInfo<'a> { | ||||||
|     /// state of humidity sensor on bank a |     /// state of humidity sensor on bank a | ||||||
|     sensor_a: HumiditySensorState, |     sensor_a: &'a MoistureSensorState, | ||||||
|     /// state of humidity sensor on bank b |     /// state of humidity sensor on bank b | ||||||
|     sensor_b: HumiditySensorState, |     sensor_b: &'a MoistureSensorState, | ||||||
|     /// configured plant watering mode |     /// configured plant watering mode | ||||||
|     mode: PlantWateringMode, |     mode: PlantWateringMode, | ||||||
|     /// plant needs to be watered |     /// plant needs to be watered | ||||||
| @@ -259,13 +284,9 @@ pub struct PlantInfo { | |||||||
|     dry: bool, |     dry: bool, | ||||||
|     /// plant irrigation cooldown is active |     /// plant irrigation cooldown is active | ||||||
|     cooldown: bool, |     cooldown: bool, | ||||||
|     /// we want to irrigate but tank is empty |  | ||||||
|     no_water: bool, |  | ||||||
|     /// plant should not be watered at this time of day TODO: does this really belong here? Isn't this a global setting? |     /// plant should not be watered at this time of day TODO: does this really belong here? Isn't this a global setting? | ||||||
|     out_of_work_hour: bool, |     out_of_work_hour: bool, | ||||||
|     /// is pump currently running |     /// how often has the pump been watered without reaching target moisture | ||||||
|     active: bool, |  | ||||||
|     /// how often has the logic determined that plant should have been irrigated but wasn't |  | ||||||
|     consecutive_pump_count: u32, |     consecutive_pump_count: u32, | ||||||
|     pump_error: Option<PumpError>, |     pump_error: Option<PumpError>, | ||||||
|     /// last time when pump was active |     /// last time when pump was active | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
|  |  | ||||||
| pub trait LimitPrecision { | pub trait LimitPrecision { | ||||||
|     fn to_precision(self, presision: i32) -> Self; |     fn to_precision(self, presision: i32) -> Self; | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user