refactor/plant-state-handling #11
| @@ -7,7 +7,6 @@ use anyhow::{bail, Result}; | |||||||
| use chrono::{DateTime, Datelike, TimeDelta, Timelike, Utc}; | use chrono::{DateTime, Datelike, TimeDelta, Timelike, Utc}; | ||||||
| use chrono_tz::{Europe::Berlin, Tz}; | use chrono_tz::{Europe::Berlin, Tz}; | ||||||
|  |  | ||||||
| use config::PlantWateringMode; |  | ||||||
| use esp_idf_hal::delay::Delay; | use esp_idf_hal::delay::Delay; | ||||||
| use esp_idf_sys::{ | use esp_idf_sys::{ | ||||||
|     esp_ota_get_app_partition_count, esp_ota_get_running_partition, esp_ota_get_state_partition, |     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] = |     let mut 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])); | ||||||
|  |  | ||||||
|     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 { |     if pump_required { | ||||||
|         log(log::LogMessage::EnableMain, dry_run as u32, 0, "", ""); |         log(log::LogMessage::EnableMain, dry_run as u32, 0, "", ""); | ||||||
|         if !dry_run { |         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 { |         for (plant_id, (state, plant_config)) in plantstate.iter().zip(&config.plants).enumerate() { | ||||||
|             let state = &mut plantstate[plant]; |             if state.needs_to_be_watered(&plant_config, &timezone_time) { | ||||||
|             if state.do_water { |                 let pump_count = board.consecutive_pump_count(plant_id) + 1; | ||||||
|                 let plant_config = &config.plants[plant]; |                 board.store_consecutive_pump_count(plant_id, pump_count); | ||||||
|                 state.consecutive_pump_count = board.consecutive_pump_count(plant) + 1; |                 //TODO(judge) where to put this? | ||||||
|                 board.store_consecutive_pump_count(plant, state.consecutive_pump_count); |                 //if state.consecutive_pump_count > plant_config.max_consecutive_pump_count as u32 { | ||||||
|                 if state.consecutive_pump_count > plant_config.max_consecutive_pump_count as u32 { |                 //    log( | ||||||
|                     log( |                 //        log::LogMessage::ConsecutivePumpCountLimit, | ||||||
|                         log::LogMessage::ConsecutivePumpCountLimit, |                 //        state.consecutive_pump_count as u32, | ||||||
|                         state.consecutive_pump_count as u32, |                 //        plant_config.max_consecutive_pump_count as u32, | ||||||
|                         plant_config.max_consecutive_pump_count as u32, |                 //        &plant.to_string(), | ||||||
|                         &plant.to_string(), |                 //        "", | ||||||
|                         "", |                 //    ); | ||||||
|                     ); |                 //    state.not_effective = true; | ||||||
|                     state.not_effective = true; |                 //    board.fault(plant, true); | ||||||
|                     board.fault(plant, true); |                 //} | ||||||
|                 } |  | ||||||
|                 log( |                 log( | ||||||
|                     log::LogMessage::PumpPlant, |                     log::LogMessage::PumpPlant, | ||||||
|                     (plant + 1) as u32, |                     (plant_id + 1) as u32, | ||||||
|                     plant_config.pump_time_s as u32, |                     plant_config.pump_time_s as u32, | ||||||
|                     &dry_run.to_string(), |                     &dry_run.to_string(), | ||||||
|                     "", |                     "", | ||||||
|                 ); |                 ); | ||||||
|                 board.store_last_pump_time(plant, cur); |                 board.store_last_pump_time(plant_id, cur); | ||||||
|                 board.last_pump_time(plant); |                 board.last_pump_time(plant_id); | ||||||
|                 state.active = true; |                 //state.active = true; | ||||||
|                 if !dry_run { |                 if !dry_run { | ||||||
|                     board.pump(plant, true)?; |                     board.pump(plant_id, true)?; | ||||||
|                     for _ in 0..plant_config.pump_time_s { |                     Delay::new_default().delay_ms(1000*plant_config.pump_time_s as u32); | ||||||
|                         Delay::new_default().delay_ms(1000); |                     board.pump(plant_id, false)?; | ||||||
|                     } |  | ||||||
|                     board.pump(plant, 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 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); | ||||||
|   | |||||||
| @@ -603,7 +603,7 @@ impl PlantCtrlBoard<'_> { | |||||||
|         self.time() |         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 { |         let sensor_channel = match sensor { | ||||||
|             Sensor::A => match plant { |             Sensor::A => match plant { | ||||||
|                 0 => SENSOR_A_1, |                 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 { |         for repeat in 0..REPEAT_MOIST_MEASURE { | ||||||
|             self.signal_counter.counter_pause()?; |             self.signal_counter.counter_pause()?; | ||||||
|             self.signal_counter.counter_clear()?; |             self.signal_counter.counter_clear()?; | ||||||
| @@ -644,7 +644,7 @@ impl PlantCtrlBoard<'_> { | |||||||
|                 .unwrap(); |                 .unwrap(); | ||||||
|  |  | ||||||
|             let delay = Delay::new_default(); |             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; |             let factor = 1000 as f32 / measurement as f32; | ||||||
|  |  | ||||||
|             //give some time to stabilize |             //give some time to stabilize | ||||||
| @@ -658,7 +658,7 @@ impl PlantCtrlBoard<'_> { | |||||||
|                 .unwrap(); |                 .unwrap(); | ||||||
|             delay.delay_ms(10); |             delay.delay_ms(10); | ||||||
|             let unscaled = self.signal_counter.get_counter_value()? as i32; |             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( |             log( | ||||||
|                 LogMessage::RawMeasure, |                 LogMessage::RawMeasure, | ||||||
|                 unscaled as u32, |                 unscaled as u32, | ||||||
| @@ -668,7 +668,7 @@ impl PlantCtrlBoard<'_> { | |||||||
|             ); |             ); | ||||||
|             results[repeat] = hz; |             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; |         let mid = results.len() / 2; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,18 +8,19 @@ use crate::{ | |||||||
|     plant_hal::{self, PLANT_COUNT}, |     plant_hal::{self, PLANT_COUNT}, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const MOIST_SENSOR_MAX_FREQUENCY: u32 = 5500; // 60kHz (500Hz margin) | const MOIST_SENSOR_MAX_FREQUENCY: f32 = 5500.; // 60kHz (500Hz margin) | ||||||
| const MOIST_SENSOR_MIN_FREQUENCY: u32 = 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)] | ||||||
| pub enum HumiditySensorError { | pub enum HumiditySensorError { | ||||||
|     ShortCircuit { hz: f32, max: f32 }, |     ShortCircuit { hz: f32, max: f32 }, | ||||||
|     OpenLoop { hz: f32, min: f32 }, |     OpenLoop { hz: f32, min: f32 }, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, PartialEq)] | #[derive(Debug, PartialEq, Serialize)] | ||||||
| pub enum HumiditySensorState { | pub enum HumiditySensorState { | ||||||
|     Disabled, |     Disabled, | ||||||
|     HumidityValue { raw_hz: u32, moisture_percent: f32 }, |     HumidityValue { raw_hz: f32, moisture_percent: f32 }, | ||||||
|     SensorError(HumiditySensorError), |     SensorError(HumiditySensorError), | ||||||
|     BoardError(String), |     BoardError(String), | ||||||
| } | } | ||||||
| @@ -35,7 +36,7 @@ impl HumiditySensorState { | |||||||
|             moisture_percent, |             moisture_percent, | ||||||
|         } = self |         } = self | ||||||
|         { |         { | ||||||
|             Some(moisture_percent) |             Some(*moisture_percent) | ||||||
|         } else { |         } else { | ||||||
|             None |             None | ||||||
|         } |         } | ||||||
| @@ -44,7 +45,7 @@ impl HumiditySensorState { | |||||||
|  |  | ||||||
| impl HumiditySensorState {} | impl HumiditySensorState {} | ||||||
|  |  | ||||||
| #[derive(Debug, PartialEq)] | #[derive(Debug, PartialEq, Serialize)] | ||||||
| pub enum PumpError { | pub enum PumpError { | ||||||
|     PumpNotWorking { |     PumpNotWorking { | ||||||
|         failed_attempts: usize, |         failed_attempts: usize, | ||||||
| @@ -52,6 +53,7 @@ pub enum PumpError { | |||||||
|     }, |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Serialize)] | ||||||
| pub struct PumpState { | pub struct PumpState { | ||||||
|     consecutive_pump_count: u32, |     consecutive_pump_count: u32, | ||||||
|     previous_pump: Option<DateTime<Utc>>, |     previous_pump: Option<DateTime<Utc>>, | ||||||
| @@ -74,7 +76,7 @@ pub struct PlantState { | |||||||
|  |  | ||||||
| fn map_range_moisture(s: f32) -> Result<f32, HumiditySensorError> { | fn map_range_moisture(s: f32) -> Result<f32, HumiditySensorError> { | ||||||
|     if s < MOIST_SENSOR_MIN_FREQUENCY { |     if s < MOIST_SENSOR_MIN_FREQUENCY { | ||||||
|         return Err(HumiditySensorError::OpenCircuit { |         return Err(HumiditySensorError::OpenLoop { | ||||||
|             hz: s, |             hz: s, | ||||||
|             min: MOIST_SENSOR_MIN_FREQUENCY, |             min: MOIST_SENSOR_MIN_FREQUENCY, | ||||||
|         }); |         }); | ||||||
| @@ -85,7 +87,7 @@ fn map_range_moisture(s: f32) -> Result<f32, HumiditySensorError> { | |||||||
|             max: MOIST_SENSOR_MAX_FREQUENCY, |             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); |         / (MOIST_SENSOR_MAX_FREQUENCY - MOIST_SENSOR_MIN_FREQUENCY); | ||||||
|  |  | ||||||
|     return Ok(moisture_percent); |     return Ok(moisture_percent); | ||||||
| @@ -165,7 +167,7 @@ impl PlantState { | |||||||
|             PlantWateringMode::TargetMoisture => { |             PlantWateringMode::TargetMoisture => { | ||||||
|                 let moisture_percent = match ( |                 let moisture_percent = match ( | ||||||
|                     self.sensor_a.moisture_percent(), |                     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_a), Some(moisture_b)) => (moisture_a + moisture_b) / 2., | ||||||
|                     (Some(moisture_percent), _) => moisture_percent, |                     (Some(moisture_percent), _) => moisture_percent, | ||||||
| @@ -196,51 +198,51 @@ impl PlantState { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fn update_plant_state( | //fn update_plant_state( | ||||||
|     plantstate: &mut [PlantInfo; PLANT_COUNT], | //    plantstate: &mut [PlantInfo; PLANT_COUNT], | ||||||
|     board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, | //    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, | ||||||
|     config: &PlantControllerConfig, | //    config: &PlantControllerConfig, | ||||||
| ) { | //) { | ||||||
|     for plant in 0..PLANT_COUNT { | //    for plant in 0..PLANT_COUNT { | ||||||
|         let state = &plantstate[plant]; | //        let state = &plantstate[plant]; | ||||||
|         let plant_config = &config.plants[plant]; | //        let plant_config = &config.plants[plant]; | ||||||
|  | // | ||||||
|         let mode = format!("{:?}", plant_config.mode); | //        let mode = format!("{:?}", plant_config.mode); | ||||||
|  | // | ||||||
|         let plant_dto = PlantStateMQTT { | //        let plant_dto = PlantStateMQTT { | ||||||
|             a: &sensor_to_string( | //            a: &sensor_to_string( | ||||||
|                 &state.a, | //                &state.a, | ||||||
|                 &state.sensor_error_a, | //                &state.sensor_error_a, | ||||||
|                 plant_config.mode != PlantWateringMode::OFF, | //                plant_config.mode != PlantWateringMode::OFF, | ||||||
|             ), | //            ), | ||||||
|             a_raw: &state.a_raw.unwrap_or(0).to_string(), | //            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: &sensor_to_string(&state.b, &state.sensor_error_b, plant_config.sensor_b), | ||||||
|             b_raw: &state.b_raw.unwrap_or(0).to_string(), | //            b_raw: &state.b_raw.unwrap_or(0).to_string(), | ||||||
|             active: state.active, | //            active: state.active, | ||||||
|             mode: &mode, | //            mode: &mode, | ||||||
|             last_pump: &time_to_string_utc(board.last_pump_time(plant)), | //            last_pump: &time_to_string_utc(board.last_pump_time(plant)), | ||||||
|             next_pump: &time_to_string(state.next_pump), | //            next_pump: &time_to_string(state.next_pump), | ||||||
|             consecutive_pump_count: state.consecutive_pump_count, | //            consecutive_pump_count: state.consecutive_pump_count, | ||||||
|             cooldown: state.cooldown, | //            cooldown: state.cooldown, | ||||||
|             dry: state.dry, | //            dry: state.dry, | ||||||
|             not_effective: state.not_effective, | //            not_effective: state.not_effective, | ||||||
|             out_of_work_hour: state.out_of_work_hour, | //            out_of_work_hour: state.out_of_work_hour, | ||||||
|             pump_error: state.pump_error, | //            pump_error: state.pump_error, | ||||||
|         }; | //        }; | ||||||
|  | // | ||||||
|         match serde_json::to_string(&plant_dto) { | //        match serde_json::to_string(&plant_dto) { | ||||||
|             Ok(state) => { | //            Ok(state) => { | ||||||
|                 let plant_topic = format!("/plant{}", plant + 1); | //                let plant_topic = format!("/plant{}", plant + 1); | ||||||
|                 let _ = board.mqtt_publish(&config, &plant_topic, state.as_bytes()); | //                let _ = board.mqtt_publish(&config, &plant_topic, state.as_bytes()); | ||||||
|                 //reduce speed as else messages will be dropped | //                //reduce speed as else messages will be dropped | ||||||
|                 Delay::new_default().delay_ms(200); | //                Delay::new_default().delay_ms(200); | ||||||
|             } | //            } | ||||||
|             Err(err) => { | //            Err(err) => { | ||||||
|                 println!("Error publishing lightstate {}", 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 | ||||||
| @@ -250,7 +252,7 @@ pub struct PlantInfo { | |||||||
|     /// state of humidity sensor on bank b |     /// state of humidity sensor on bank b | ||||||
|     sensor_b: HumiditySensorState, |     sensor_b: HumiditySensorState, | ||||||
|     /// configured plant watering mode |     /// configured plant watering mode | ||||||
|     mode: config::PlantWateringMode, |     mode: PlantWateringMode, | ||||||
|     /// plant needs to be watered |     /// plant needs to be watered | ||||||
|     do_water: bool, |     do_water: bool, | ||||||
|     /// is plant considerd to be dry according to settings |     /// is plant considerd to be dry according to settings | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
|  |  | ||||||
| pub trait LimitPrecision { | pub trait LimitPrecision { | ||||||
|     fn to_precision(self, presision: i32) -> self; |     fn to_precision(self, presision: i32) -> Self; | ||||||
| } | } | ||||||
|  |  | ||||||
| impl LimitPrecision for f32 { | 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) |         (self * (10_f32).powi(precision)).round() / (10_f32).powi(precision) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user