finish refactor of plant state logic
This commit is contained in:
		@@ -4,7 +4,7 @@ use std::{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use anyhow::{bail, Result};
 | 
			
		||||
use chrono::{DateTime, Datelike, TimeDelta, Timelike, Utc};
 | 
			
		||||
use chrono::{DateTime, Datelike, Timelike};
 | 
			
		||||
use chrono_tz::{Europe::Berlin, Tz};
 | 
			
		||||
 | 
			
		||||
use esp_idf_hal::delay::Delay;
 | 
			
		||||
@@ -29,7 +29,7 @@ mod plant_state;
 | 
			
		||||
mod tank;
 | 
			
		||||
pub mod util;
 | 
			
		||||
 | 
			
		||||
use plant_state::{PlantInfo, PlantState};
 | 
			
		||||
use plant_state::PlantState;
 | 
			
		||||
use tank::*;
 | 
			
		||||
 | 
			
		||||
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]));
 | 
			
		||||
    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
 | 
			
		||||
        .iter()
 | 
			
		||||
@@ -447,17 +460,16 @@ fn safe_main() -> anyhow::Result<()> {
 | 
			
		||||
                //state.active = true;
 | 
			
		||||
                if !dry_run {
 | 
			
		||||
                    board.pump(plant_id, true)?;
 | 
			
		||||
                    Delay::new_default().delay_ms(1000*plant_config.pump_time_s as u32);
 | 
			
		||||
                    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){
 | 
			
		||||
            } 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);
 | 
			
		||||
 | 
			
		||||
    let is_day = board.is_day();
 | 
			
		||||
    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 {
 | 
			
		||||
    return match value {
 | 
			
		||||
        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;
 | 
			
		||||
    //eg 10-14
 | 
			
		||||
    if start < end {
 | 
			
		||||
 
 | 
			
		||||
@@ -668,7 +668,7 @@ impl PlantCtrlBoard<'_> {
 | 
			
		||||
            );
 | 
			
		||||
            results[repeat] = hz;
 | 
			
		||||
        }
 | 
			
		||||
        results.sort_by(|a,b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
 | 
			
		||||
        results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
 | 
			
		||||
 | 
			
		||||
        let mid = results.len() / 2;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +1,40 @@
 | 
			
		||||
use chrono::{DateTime, TimeDelta, Utc};
 | 
			
		||||
use chrono_tz::Tz;
 | 
			
		||||
use measurements::humidity;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    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_MIN_FREQUENCY: f32 = 150.; // this is really really dry, think like cactus levels
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Serialize)]
 | 
			
		||||
pub enum HumiditySensorError {
 | 
			
		||||
pub enum MoistureSensorError {
 | 
			
		||||
    ShortCircuit { hz: f32, max: 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),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HumiditySensorState {
 | 
			
		||||
    pub fn is_err(&self) -> bool {
 | 
			
		||||
        matches!(self, Self::SensorError(_)) || matches!(self, Self::BoardError(_))
 | 
			
		||||
#[derive(Debug, PartialEq, Serialize)]
 | 
			
		||||
pub enum MoistureSensorState {
 | 
			
		||||
    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> {
 | 
			
		||||
        if let HumiditySensorState::HumidityValue {
 | 
			
		||||
            raw_hz,
 | 
			
		||||
        if let MoistureSensorState::MoistureValue {
 | 
			
		||||
            raw_hz: _,
 | 
			
		||||
            moisture_percent,
 | 
			
		||||
        } = self
 | 
			
		||||
        {
 | 
			
		||||
@@ -43,7 +45,7 @@ impl HumiditySensorState {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HumiditySensorState {}
 | 
			
		||||
impl MoistureSensorState {}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Serialize)]
 | 
			
		||||
pub enum PumpError {
 | 
			
		||||
@@ -59,30 +61,41 @@ pub struct PumpState {
 | 
			
		||||
    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 {
 | 
			
		||||
    OFF,
 | 
			
		||||
    TargetMoisture,
 | 
			
		||||
    TimerOnly,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum PlantError {}
 | 
			
		||||
 | 
			
		||||
pub struct PlantState {
 | 
			
		||||
    pub sensor_a: HumiditySensorState,
 | 
			
		||||
    pub sensor_b: HumiditySensorState,
 | 
			
		||||
    pub sensor_a: MoistureSensorState,
 | 
			
		||||
    pub sensor_b: MoistureSensorState,
 | 
			
		||||
    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 {
 | 
			
		||||
        return Err(HumiditySensorError::OpenLoop {
 | 
			
		||||
        return Err(MoistureSensorError::OpenLoop {
 | 
			
		||||
            hz: s,
 | 
			
		||||
            min: MOIST_SENSOR_MIN_FREQUENCY,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    if s > MOIST_SENSOR_MAX_FREQUENCY {
 | 
			
		||||
        return Err(HumiditySensorError::ShortCircuit {
 | 
			
		||||
        return Err(MoistureSensorError::ShortCircuit {
 | 
			
		||||
            hz: s,
 | 
			
		||||
            max: MOIST_SENSOR_MAX_FREQUENCY,
 | 
			
		||||
        });
 | 
			
		||||
@@ -102,30 +115,34 @@ impl PlantState {
 | 
			
		||||
        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 {
 | 
			
		||||
                    Ok(moisture_percent) => MoistureSensorState::MoistureValue {
 | 
			
		||||
                        raw_hz: raw,
 | 
			
		||||
                        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 {
 | 
			
		||||
            HumiditySensorState::Disabled
 | 
			
		||||
            MoistureSensorState::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 {
 | 
			
		||||
                    Ok(moisture_percent) => MoistureSensorState::MoistureValue {
 | 
			
		||||
                        raw_hz: raw,
 | 
			
		||||
                        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 {
 | 
			
		||||
            HumiditySensorState::Disabled
 | 
			
		||||
            MoistureSensorState::Disabled
 | 
			
		||||
        };
 | 
			
		||||
        let previous_pump = board.last_pump_time(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 {
 | 
			
		||||
        if matches!(plant_conf.mode, PlantWateringMode::OFF) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        self.pump.previous_pump.is_some_and(|last_pump| {
 | 
			
		||||
            last_pump
 | 
			
		||||
                .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into()))
 | 
			
		||||
@@ -154,7 +174,26 @@ impl PlantState {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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(
 | 
			
		||||
@@ -165,26 +204,20 @@ impl PlantState {
 | 
			
		||||
        match plant_conf.mode {
 | 
			
		||||
            PlantWateringMode::OFF => false,
 | 
			
		||||
            PlantWateringMode::TargetMoisture => {
 | 
			
		||||
                let moisture_percent = match (
 | 
			
		||||
                    self.sensor_a.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) {
 | 
			
		||||
                    false
 | 
			
		||||
                } else {
 | 
			
		||||
                    if moisture_percent < plant_conf.target_moisture {
 | 
			
		||||
                        true
 | 
			
		||||
                    } else {
 | 
			
		||||
                let (moisture_percent, _) = self.plant_moisture();
 | 
			
		||||
                if let Some(moisture_percent) = moisture_percent {
 | 
			
		||||
                    if self.pump_in_timeout(plant_conf, current_time) {
 | 
			
		||||
                        false
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if moisture_percent < plant_conf.target_moisture {
 | 
			
		||||
                            true
 | 
			
		||||
                        } else {
 | 
			
		||||
                            false
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // in case no moisture can be determined do not water plant
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            PlantWateringMode::TimerOnly => {
 | 
			
		||||
@@ -196,61 +229,53 @@ 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);
 | 
			
		||||
//            }
 | 
			
		||||
//        };
 | 
			
		||||
//    }
 | 
			
		||||
//}
 | 
			
		||||
    pub fn to_mqtt_info(&self, plant_conf: &PlantConfig, current_time: &DateTime<Tz>) -> PlantInfo {
 | 
			
		||||
        PlantInfo {
 | 
			
		||||
            sensor_a: &self.sensor_a,
 | 
			
		||||
            sensor_b: &self.sensor_b,
 | 
			
		||||
            mode: plant_conf.mode,
 | 
			
		||||
            do_water: self.needs_to_be_watered(plant_conf, current_time),
 | 
			
		||||
            dry: if let Some(moisture_percent) = self.plant_moisture().0 {
 | 
			
		||||
                moisture_percent < plant_conf.target_moisture
 | 
			
		||||
            } else {
 | 
			
		||||
                false
 | 
			
		||||
            },
 | 
			
		||||
            cooldown: self.pump_in_timeout(plant_conf, current_time),
 | 
			
		||||
            out_of_work_hour: in_time_range(
 | 
			
		||||
                current_time,
 | 
			
		||||
                plant_conf.pump_hour_start,
 | 
			
		||||
                plant_conf.pump_hour_end,
 | 
			
		||||
            ),
 | 
			
		||||
            consecutive_pump_count: self.pump.consecutive_pump_count,
 | 
			
		||||
            pump_error: self.pump.is_err(plant_conf),
 | 
			
		||||
            last_pump: self
 | 
			
		||||
                .pump
 | 
			
		||||
                .previous_pump
 | 
			
		||||
                .map(|t| t.with_timezone(¤t_time.timezone())),
 | 
			
		||||
            next_pump: if matches!(
 | 
			
		||||
                plant_conf.mode,
 | 
			
		||||
                PlantWateringMode::TimerOnly | PlantWateringMode::TargetMoisture
 | 
			
		||||
            ) {
 | 
			
		||||
                self.pump.previous_pump.and_then(|last_pump| {
 | 
			
		||||
                    last_pump
 | 
			
		||||
                        .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into()))
 | 
			
		||||
                        .map(|t| t.with_timezone(¤t_time.timezone()))
 | 
			
		||||
                })
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Serialize)]
 | 
			
		||||
/// State of a single plant to be tracked
 | 
			
		||||
pub struct PlantInfo {
 | 
			
		||||
pub struct PlantInfo<'a> {
 | 
			
		||||
    /// state of humidity sensor on bank a
 | 
			
		||||
    sensor_a: HumiditySensorState,
 | 
			
		||||
    sensor_a: &'a MoistureSensorState,
 | 
			
		||||
    /// state of humidity sensor on bank b
 | 
			
		||||
    sensor_b: HumiditySensorState,
 | 
			
		||||
    sensor_b: &'a MoistureSensorState,
 | 
			
		||||
    /// configured plant watering mode
 | 
			
		||||
    mode: PlantWateringMode,
 | 
			
		||||
    /// plant needs to be watered
 | 
			
		||||
@@ -259,13 +284,9 @@ pub struct PlantInfo {
 | 
			
		||||
    dry: bool,
 | 
			
		||||
    /// plant irrigation cooldown is active
 | 
			
		||||
    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?
 | 
			
		||||
    out_of_work_hour: bool,
 | 
			
		||||
    /// is pump currently running
 | 
			
		||||
    active: bool,
 | 
			
		||||
    /// how often has the logic determined that plant should have been irrigated but wasn't
 | 
			
		||||
    /// how often has the pump been watered without reaching target moisture
 | 
			
		||||
    consecutive_pump_count: u32,
 | 
			
		||||
    pump_error: Option<PumpError>,
 | 
			
		||||
    /// last time when pump was active
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
 | 
			
		||||
pub trait LimitPrecision {
 | 
			
		||||
    fn to_precision(self, presision: i32) -> Self;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user