refactor/plant-state-handling #11
| @@ -7,7 +7,6 @@ use anyhow::{bail, Result}; | ||||
| use chrono::{DateTime, Datelike, TimeDelta, Timelike, Utc}; | ||||
| use chrono_tz::{Europe::Berlin, Tz}; | ||||
|  | ||||
| use config::PlantWateringMode; | ||||
| use esp_idf_hal::delay::Delay; | ||||
| use esp_idf_sys::{ | ||||
|     esp_ota_get_app_partition_count, esp_ota_get_running_partition, esp_ota_get_state_partition, | ||||
| @@ -410,50 +409,55 @@ fn safe_main() -> anyhow::Result<()> { | ||||
|     let mut plantstate: [PlantState; PLANT_COUNT] = | ||||
|         core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i])); | ||||
|  | ||||
|     let pump_required = plantstate.iter().any(|it| it.needs_to_be_watered(&config.plants[i], &timezone_time)) && !water_frozen; | ||||
|     let pump_required = plantstate | ||||
|         .iter() | ||||
|         .zip(&config.plants) | ||||
|         .any(|(it, conf)| it.needs_to_be_watered(&conf, &timezone_time)) | ||||
|         && !water_frozen; | ||||
|     if pump_required { | ||||
|         log(log::LogMessage::EnableMain, dry_run as u32, 0, "", ""); | ||||
|         if !dry_run { | ||||
|             board.any_pump(true)?; | ||||
|             board.any_pump(true)?; // what does this do? Does it need to be reset? | ||||
|         } | ||||
|         for plant in 0..PLANT_COUNT { | ||||
|             let state = &mut plantstate[plant]; | ||||
|             if state.do_water { | ||||
|                 let plant_config = &config.plants[plant]; | ||||
|                 state.consecutive_pump_count = board.consecutive_pump_count(plant) + 1; | ||||
|                 board.store_consecutive_pump_count(plant, state.consecutive_pump_count); | ||||
|                 if state.consecutive_pump_count > plant_config.max_consecutive_pump_count as u32 { | ||||
|                     log( | ||||
|                         log::LogMessage::ConsecutivePumpCountLimit, | ||||
|                         state.consecutive_pump_count as u32, | ||||
|                         plant_config.max_consecutive_pump_count as u32, | ||||
|                         &plant.to_string(), | ||||
|                         "", | ||||
|                     ); | ||||
|                     state.not_effective = true; | ||||
|                     board.fault(plant, true); | ||||
|                 } | ||||
|         for (plant_id, (state, plant_config)) in plantstate.iter().zip(&config.plants).enumerate() { | ||||
|             if state.needs_to_be_watered(&plant_config, &timezone_time) { | ||||
|                 let pump_count = board.consecutive_pump_count(plant_id) + 1; | ||||
|                 board.store_consecutive_pump_count(plant_id, pump_count); | ||||
|                 //TODO(judge) where to put this? | ||||
|                 //if state.consecutive_pump_count > plant_config.max_consecutive_pump_count as u32 { | ||||
|                 //    log( | ||||
|                 //        log::LogMessage::ConsecutivePumpCountLimit, | ||||
|                 //        state.consecutive_pump_count as u32, | ||||
|                 //        plant_config.max_consecutive_pump_count as u32, | ||||
|                 //        &plant.to_string(), | ||||
|                 //        "", | ||||
|                 //    ); | ||||
|                 //    state.not_effective = true; | ||||
|                 //    board.fault(plant, true); | ||||
|                 //} | ||||
|                 log( | ||||
|                     log::LogMessage::PumpPlant, | ||||
|                     (plant + 1) as u32, | ||||
|                     (plant_id + 1) as u32, | ||||
|                     plant_config.pump_time_s as u32, | ||||
|                     &dry_run.to_string(), | ||||
|                     "", | ||||
|                 ); | ||||
|                 board.store_last_pump_time(plant, cur); | ||||
|                 board.last_pump_time(plant); | ||||
|                 state.active = true; | ||||
|                 board.store_last_pump_time(plant_id, cur); | ||||
|                 board.last_pump_time(plant_id); | ||||
|                 //state.active = true; | ||||
|                 if !dry_run { | ||||
|                     board.pump(plant, true)?; | ||||
|                     for _ in 0..plant_config.pump_time_s { | ||||
|                         Delay::new_default().delay_ms(1000); | ||||
|                     } | ||||
|                     board.pump(plant, false)?; | ||||
|                     board.pump(plant_id, true)?; | ||||
|                     Delay::new_default().delay_ms(1000*plant_config.pump_time_s as u32); | ||||
|                     board.pump(plant_id, false)?; | ||||
|                 } | ||||
|             } else if !state.pump_in_timeout(&plant_config, &timezone_time){ | ||||
|                 // plant does not need to be watered and is not in timeout | ||||
|                 // -> reset consecutive pump count | ||||
|                 board.store_consecutive_pump_count(plant_id, 0); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     update_plant_state(&mut plantstate, &mut board, &config); | ||||
|     //update_plant_state(&mut plantstate, &mut board, &config); | ||||
|  | ||||
|     let is_day = board.is_day(); | ||||
|     let state_of_charge = board.state_charge_percent().unwrap_or(0); | ||||
|   | ||||
| @@ -603,7 +603,7 @@ impl PlantCtrlBoard<'_> { | ||||
|         self.time() | ||||
|     } | ||||
|  | ||||
|     pub fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<u32> { | ||||
|     pub fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32> { | ||||
|         let sensor_channel = match sensor { | ||||
|             Sensor::A => match plant { | ||||
|                 0 => SENSOR_A_1, | ||||
| @@ -629,7 +629,7 @@ impl PlantCtrlBoard<'_> { | ||||
|             }, | ||||
|         }; | ||||
|  | ||||
|         let mut results = [0_u32; REPEAT_MOIST_MEASURE]; | ||||
|         let mut results = [0_f32; REPEAT_MOIST_MEASURE]; | ||||
|         for repeat in 0..REPEAT_MOIST_MEASURE { | ||||
|             self.signal_counter.counter_pause()?; | ||||
|             self.signal_counter.counter_clear()?; | ||||
| @@ -644,7 +644,7 @@ impl PlantCtrlBoard<'_> { | ||||
|                 .unwrap(); | ||||
|  | ||||
|             let delay = Delay::new_default(); | ||||
|             let measurement = 100; | ||||
|             let measurement = 100; // TODO what is this scaling factor? what is its purpose? | ||||
|             let factor = 1000 as f32 / measurement as f32; | ||||
|  | ||||
|             //give some time to stabilize | ||||
| @@ -658,7 +658,7 @@ impl PlantCtrlBoard<'_> { | ||||
|                 .unwrap(); | ||||
|             delay.delay_ms(10); | ||||
|             let unscaled = self.signal_counter.get_counter_value()? as i32; | ||||
|             let hz = (unscaled as f32 * factor) as u32; | ||||
|             let hz = unscaled as f32 * factor; | ||||
|             log( | ||||
|                 LogMessage::RawMeasure, | ||||
|                 unscaled as u32, | ||||
| @@ -668,7 +668,7 @@ impl PlantCtrlBoard<'_> { | ||||
|             ); | ||||
|             results[repeat] = hz; | ||||
|         } | ||||
|         results.sort(); | ||||
|         results.sort_by(|a,b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord | ||||
|  | ||||
|         let mid = results.len() / 2; | ||||
|  | ||||
|   | ||||
| @@ -8,18 +8,19 @@ use crate::{ | ||||
|     plant_hal::{self, PLANT_COUNT}, | ||||
| }; | ||||
|  | ||||
| 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 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 | ||||
|  | ||||
| #[derive(Debug, PartialEq, Serialize)] | ||||
| pub enum HumiditySensorError { | ||||
|     ShortCircuit { hz: f32, max: f32 }, | ||||
|     OpenLoop { hz: f32, min: f32 }, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, PartialEq)] | ||||
| #[derive(Debug, PartialEq, Serialize)] | ||||
| pub enum HumiditySensorState { | ||||
|     Disabled, | ||||
|     HumidityValue { raw_hz: u32, moisture_percent: f32 }, | ||||
|     HumidityValue { raw_hz: f32, moisture_percent: f32 }, | ||||
|     SensorError(HumiditySensorError), | ||||
|     BoardError(String), | ||||
| } | ||||
| @@ -35,7 +36,7 @@ impl HumiditySensorState { | ||||
|             moisture_percent, | ||||
|         } = self | ||||
|         { | ||||
|             Some(moisture_percent) | ||||
|             Some(*moisture_percent) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
| @@ -44,7 +45,7 @@ impl HumiditySensorState { | ||||
|  | ||||
| impl HumiditySensorState {} | ||||
|  | ||||
| #[derive(Debug, PartialEq)] | ||||
| #[derive(Debug, PartialEq, Serialize)] | ||||
| pub enum PumpError { | ||||
|     PumpNotWorking { | ||||
|         failed_attempts: usize, | ||||
| @@ -52,6 +53,7 @@ pub enum PumpError { | ||||
|     }, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| pub struct PumpState { | ||||
|     consecutive_pump_count: u32, | ||||
|     previous_pump: Option<DateTime<Utc>>, | ||||
| @@ -74,7 +76,7 @@ pub struct PlantState { | ||||
|  | ||||
| fn map_range_moisture(s: f32) -> Result<f32, HumiditySensorError> { | ||||
|     if s < MOIST_SENSOR_MIN_FREQUENCY { | ||||
|         return Err(HumiditySensorError::OpenCircuit { | ||||
|         return Err(HumiditySensorError::OpenLoop { | ||||
|             hz: s, | ||||
|             min: MOIST_SENSOR_MIN_FREQUENCY, | ||||
|         }); | ||||
| @@ -85,7 +87,7 @@ fn map_range_moisture(s: f32) -> Result<f32, HumiditySensorError> { | ||||
|             max: MOIST_SENSOR_MAX_FREQUENCY, | ||||
|         }); | ||||
|     } | ||||
|     let moisture_percent = (s - MOIST_SENSOR_MIN_FREQUENCY) * 100 | ||||
|     let moisture_percent = (s - MOIST_SENSOR_MIN_FREQUENCY) * 100.0 | ||||
|         / (MOIST_SENSOR_MAX_FREQUENCY - MOIST_SENSOR_MIN_FREQUENCY); | ||||
|  | ||||
|     return Ok(moisture_percent); | ||||
| @@ -165,7 +167,7 @@ impl PlantState { | ||||
|             PlantWateringMode::TargetMoisture => { | ||||
|                 let moisture_percent = match ( | ||||
|                     self.sensor_a.moisture_percent(), | ||||
|                     &self.sensor_b.moisture_percent(), | ||||
|                     self.sensor_b.moisture_percent(), | ||||
|                 ) { | ||||
|                     (Some(moisture_a), Some(moisture_b)) => (moisture_a + moisture_b) / 2., | ||||
|                     (Some(moisture_percent), _) => moisture_percent, | ||||
| @@ -196,51 +198,51 @@ impl PlantState { | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn update_plant_state( | ||||
|     plantstate: &mut [PlantInfo; PLANT_COUNT], | ||||
|     board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, | ||||
|     config: &PlantControllerConfig, | ||||
| ) { | ||||
|     for plant in 0..PLANT_COUNT { | ||||
|         let state = &plantstate[plant]; | ||||
|         let plant_config = &config.plants[plant]; | ||||
|  | ||||
|         let mode = format!("{:?}", plant_config.mode); | ||||
|  | ||||
|         let plant_dto = PlantStateMQTT { | ||||
|             a: &sensor_to_string( | ||||
|                 &state.a, | ||||
|                 &state.sensor_error_a, | ||||
|                 plant_config.mode != PlantWateringMode::OFF, | ||||
|             ), | ||||
|             a_raw: &state.a_raw.unwrap_or(0).to_string(), | ||||
|             b: &sensor_to_string(&state.b, &state.sensor_error_b, plant_config.sensor_b), | ||||
|             b_raw: &state.b_raw.unwrap_or(0).to_string(), | ||||
|             active: state.active, | ||||
|             mode: &mode, | ||||
|             last_pump: &time_to_string_utc(board.last_pump_time(plant)), | ||||
|             next_pump: &time_to_string(state.next_pump), | ||||
|             consecutive_pump_count: state.consecutive_pump_count, | ||||
|             cooldown: state.cooldown, | ||||
|             dry: state.dry, | ||||
|             not_effective: state.not_effective, | ||||
|             out_of_work_hour: state.out_of_work_hour, | ||||
|             pump_error: state.pump_error, | ||||
|         }; | ||||
|  | ||||
|         match serde_json::to_string(&plant_dto) { | ||||
|             Ok(state) => { | ||||
|                 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); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| //fn update_plant_state( | ||||
| //    plantstate: &mut [PlantInfo; PLANT_COUNT], | ||||
| //    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, | ||||
| //    config: &PlantControllerConfig, | ||||
| //) { | ||||
| //    for plant in 0..PLANT_COUNT { | ||||
| //        let state = &plantstate[plant]; | ||||
| //        let plant_config = &config.plants[plant]; | ||||
| // | ||||
| //        let mode = format!("{:?}", plant_config.mode); | ||||
| // | ||||
| //        let plant_dto = PlantStateMQTT { | ||||
| //            a: &sensor_to_string( | ||||
| //                &state.a, | ||||
| //                &state.sensor_error_a, | ||||
| //                plant_config.mode != PlantWateringMode::OFF, | ||||
| //            ), | ||||
| //            a_raw: &state.a_raw.unwrap_or(0).to_string(), | ||||
| //            b: &sensor_to_string(&state.b, &state.sensor_error_b, plant_config.sensor_b), | ||||
| //            b_raw: &state.b_raw.unwrap_or(0).to_string(), | ||||
| //            active: state.active, | ||||
| //            mode: &mode, | ||||
| //            last_pump: &time_to_string_utc(board.last_pump_time(plant)), | ||||
| //            next_pump: &time_to_string(state.next_pump), | ||||
| //            consecutive_pump_count: state.consecutive_pump_count, | ||||
| //            cooldown: state.cooldown, | ||||
| //            dry: state.dry, | ||||
| //            not_effective: state.not_effective, | ||||
| //            out_of_work_hour: state.out_of_work_hour, | ||||
| //            pump_error: state.pump_error, | ||||
| //        }; | ||||
| // | ||||
| //        match serde_json::to_string(&plant_dto) { | ||||
| //            Ok(state) => { | ||||
| //                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)] | ||||
| /// State of a single plant to be tracked | ||||
| @@ -250,7 +252,7 @@ pub struct PlantInfo { | ||||
|     /// state of humidity sensor on bank b | ||||
|     sensor_b: HumiditySensorState, | ||||
|     /// configured plant watering mode | ||||
|     mode: config::PlantWateringMode, | ||||
|     mode: PlantWateringMode, | ||||
|     /// plant needs to be watered | ||||
|     do_water: bool, | ||||
|     /// is plant considerd to be dry according to settings | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
|  | ||||
| pub trait LimitPrecision { | ||||
|     fn to_precision(self, presision: i32) -> self; | ||||
|     fn to_precision(self, presision: i32) -> Self; | ||||
| } | ||||
|  | ||||
| impl LimitPrecision for f32 { | ||||
|     fn to_precision(self, precision: i32) -> self { | ||||
|     fn to_precision(self, precision: i32) -> Self { | ||||
|         (self * (10_f32).powi(precision)).round() / (10_f32).powi(precision) | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user