diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 73e096d..593cc2c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -77,7 +77,7 @@ serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" #timezone -chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone" , "alloc"] } +chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone" , "alloc", "serde"] } chrono-tz = {version="0.8.0", default-features = false , features = [ "filter-by-regex" ]} eeprom24x = "0.7.2" url = "2.5.3" diff --git a/rust/build.rs b/rust/build.rs index b0169e5..1499088 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -3,39 +3,39 @@ use std::process::Command; use vergen::EmitBuilder; fn main() { println!("cargo:rerun-if-changed=./src/src_webpack"); - Command::new("rm") - .arg("./src/webserver/bundle.js") - .output() - .unwrap(); - - match Command::new("cmd").spawn() { - Ok(_) => { - println!("Assuming build on windows"); - let output = Command::new("cmd") - .arg("/K") - .arg("npx") - .arg("webpack") - .current_dir("./src_webpack") - .output() - .unwrap(); - println!("status: {}", output.status); - println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - assert!(output.status.success()); + Command::new("rm") + .arg("./src/webserver/bundle.js") + .output() + .unwrap(); + + match Command::new("cmd").spawn() { + Ok(_) => { + println!("Assuming build on windows"); + let output = Command::new("cmd") + .arg("/K") + .arg("npx") + .arg("webpack") + .current_dir("./src_webpack") + .output() + .unwrap(); + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + assert!(output.status.success()); + } + Err(_) => { + println!("Assuming build on linux"); + let output = Command::new("npx") + .arg("webpack") + .current_dir("./src_webpack") + .output() + .unwrap(); + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + assert!(output.status.success()); + } } - Err(_) => { - println!("Assuming build on linux"); - let output = Command::new("npx") - .arg("webpack") - .current_dir("./src_webpack") - .output() - .unwrap(); - println!("status: {}", output.status); - println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - assert!(output.status.success()); - } - } embuild::espidf::sysenv::output(); let _ = EmitBuilder::builder().all_git().all_build().emit(); diff --git a/rust/src/config.rs b/rust/src/config.rs index 3190a5c..f716189 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; +use crate::plant_state::PlantWateringMode; use crate::PLANT_COUNT; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] @@ -83,8 +84,8 @@ pub struct PlantControllerConfig { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(default)] pub struct PlantConfig { - pub mode: Mode, - pub target_moisture: u8, + pub mode: PlantWateringMode, + pub target_moisture: f32, pub pump_time_s: u16, pub pump_cooldown_min: u16, pub pump_hour_start: u8, @@ -96,8 +97,8 @@ pub struct PlantConfig { impl Default for PlantConfig { fn default() -> Self { Self { - mode: Mode::OFF, - target_moisture: 40, + mode: PlantWateringMode::OFF, + target_moisture: 40., pump_time_s: 30, pump_cooldown_min: 60, pump_hour_start: 9, @@ -108,11 +109,3 @@ impl Default for PlantConfig { } } } - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub enum Mode { - OFF, - TargetMoisture, - TimerOnly, - TimerAndDeadzone, -} diff --git a/rust/src/main.rs b/rust/src/main.rs index 0e26c6d..ae54c25 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -7,7 +7,7 @@ use anyhow::{bail, Result}; use chrono::{DateTime, Datelike, TimeDelta, Timelike, Utc}; use chrono_tz::{Europe::Berlin, Tz}; -use config::Mode; +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, @@ -28,8 +28,9 @@ mod log; pub mod plant_hal; mod plant_state; mod tank; +pub mod util; -use plant_state::{PlantInfo, PlantStateMQTT}; +use plant_state::{PlantInfo, PlantState}; use tank::*; const TIME_ZONE: Tz = Berlin; @@ -406,18 +407,10 @@ fn safe_main() -> anyhow::Result<()> { } }; - let mut plantstate: [PlantInfo; PLANT_COUNT] = core::array::from_fn(|_| PlantInfo { - ..Default::default() - }); - determine_plant_state( - &mut plantstate, - timezone_time, - &tank_state, - &config, - &mut board, - ); + 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.do_water) && !water_frozen; + let pump_required = plantstate.iter().any(|it| it.needs_to_be_watered(&config.plants[i], &timezone_time)) && !water_frozen; if pump_required { log(log::LogMessage::EnableMain, dry_run as u32, 0, "", ""); if !dry_run { @@ -568,279 +561,6 @@ fn publish_battery_state( }; } -fn determine_state_target_moisture_for_plant( - board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, - plant: usize, - state: &mut PlantInfo, - config: &PlantControllerConfig, - tank_state: &TankState, - cur: DateTime, -) { - let plant_config = &config.plants[plant]; - if plant_config.mode == Mode::OFF { - return; - } - match board.measure_moisture_hz(plant, plant_hal::Sensor::A) { - Ok(a) => { - state.a_raw = Some(a); - let mapped = map_range_moisture(a as f32); - match mapped { - Ok(result) => state.a = Some(result), - Err(err) => { - state.sensor_error_a = Some(err); - } - } - } - Err(_) => { - state.sensor_error_a = Some(SensorError::Unknown); - } - } - if plant_config.sensor_b { - match board.measure_moisture_hz(plant, plant_hal::Sensor::B) { - Ok(b) => { - state.b_raw = Some(b); - let mapped = map_range_moisture(b as f32); - match mapped { - Ok(result) => state.b = Some(result), - Err(err) => { - state.sensor_error_b = Some(err); - } - } - } - Err(_) => { - state.sensor_error_b = Some(SensorError::Unknown); - } - } - } - - //FIXME how to average analyze whatever? - let a_low = state.a.is_some() && state.a.unwrap() < plant_config.target_moisture; - let b_low = state.b.is_some() && state.b.unwrap() < plant_config.target_moisture; - - if a_low || b_low { - state.dry = true; - match tank_state.enough_water(&config.tank) { - Err(_tank_err) => { - if !config.tank.tank_allow_pumping_if_sensor_error { - state.no_water = true; - } - } - // when no tank error, if plant should be watered depends on if enough water is in tank - // no_water behaves inversly to enough_water - Ok(enough_water) => state.no_water = !enough_water, - } - } - let duration = TimeDelta::try_minutes(plant_config.pump_cooldown_min as i64).unwrap(); - let last_pump = board.last_pump_time(plant); - match last_pump { - Some(last_pump) => { - let next_pump = last_pump + duration; - if next_pump > cur { - let local_time = next_pump.with_timezone(&TIME_ZONE); - state.next_pump = Some(local_time); - state.cooldown = true; - } - } - None => { - println!( - "Could not restore last pump for plant {}, restoring", - plant + 1 - ); - board.store_last_pump_time(plant, DateTime::from_timestamp_millis(0).unwrap()); - state.pump_error = true; - } - } - - if !in_time_range( - &cur, - plant_config.pump_hour_start, - plant_config.pump_hour_end, - ) { - state.out_of_work_hour = true; - } - if state.dry && !state.no_water && !state.cooldown && !state.out_of_work_hour { - state.do_water = true; - } -} - -fn determine_state_timer_only_for_plant( - board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, - plant: usize, - state: &mut PlantInfo, - config: &PlantControllerConfig, - tank_state: &TankState, - cur: DateTime, -) { - let plant_config = &config.plants[plant]; - let duration = TimeDelta::try_minutes(plant_config.pump_cooldown_min as i64).unwrap(); - - let last_pump = board.last_pump_time(plant); - match last_pump { - Some(last_pump) => { - let next_pump = last_pump + duration; - if next_pump > cur { - let europe_time = next_pump.with_timezone(&TIME_ZONE); - state.next_pump = Some(europe_time); - state.cooldown = true; - } else { - match tank_state.enough_water(&config.tank) { - Err(_tank_err) => { - if !config.tank.tank_allow_pumping_if_sensor_error { - state.do_water = true; - } - } - Ok(enough_water) => { - state.no_water = !enough_water; - } - } - } - } - None => { - println!( - "Could not restore last pump for plant {}, restoring", - plant + 1 - ); - board.store_last_pump_time(plant, DateTime::from_timestamp_millis(0).unwrap()); - state.pump_error = true; - } - } -} - -fn determine_state_timer_and_deadzone_for_plant( - board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, - plant: usize, - state: &mut PlantInfo, - config: &PlantControllerConfig, - tank_state: &TankState, - cur: DateTime, -) { - let plant_config = &config.plants[plant]; - let duration = TimeDelta::try_minutes(plant_config.pump_cooldown_min as i64).unwrap(); - - let last_pump = board.last_pump_time(plant); - match last_pump { - Some(last_pump) => { - let next_pump = last_pump + duration; - if next_pump > cur { - let europe_time = next_pump.with_timezone(&TIME_ZONE); - state.next_pump = Some(europe_time); - state.cooldown = true; - } - if !in_time_range( - &cur, - plant_config.pump_hour_start, - plant_config.pump_hour_end, - ) { - state.out_of_work_hour = true; - } - if !state.cooldown && !state.out_of_work_hour { - match tank_state.enough_water(&config.tank) { - Err(_tank_err) => { - if !config.tank.tank_allow_pumping_if_sensor_error { - state.do_water = true; - } - } - Ok(enough_water) => { - state.no_water = !enough_water; - } - } - } - } - None => { - println!( - "Could not restore last pump for plant {}, restoring", - plant + 1 - ); - board.store_last_pump_time(plant, DateTime::from_timestamp_millis(0).unwrap()); - state.pump_error = true; - } - } -} - -fn determine_plant_state( - plantstate: &mut [PlantInfo; PLANT_COUNT], - cur: DateTime, - tank_state: &TankState, - config: &PlantControllerConfig, - board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, -) { - for plant in 0..PLANT_COUNT { - let state = &mut plantstate[plant]; - let plant_config = &config.plants[plant]; - match plant_config.mode { - config::Mode::OFF => {} - config::Mode::TargetMoisture => { - determine_state_target_moisture_for_plant( - board, plant, state, config, tank_state, cur, - ); - } - config::Mode::TimerOnly => { - determine_state_timer_only_for_plant(board, plant, state, config, tank_state, cur); - } - config::Mode::TimerAndDeadzone => { - determine_state_timer_and_deadzone_for_plant( - board, plant, state, config, tank_state, cur, - ); - } - } - - if state.sensor_error_a.is_some() || state.sensor_error_b.is_some() { - board.fault(plant, true); - } - if !state.dry { - state.consecutive_pump_count = 0; - board.store_consecutive_pump_count(plant, 0); - } - println!("Plant {} state is {:?}", plant, state); - } -} - -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 != Mode::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 wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { let delay = wait_type.blink_pattern(); diff --git a/rust/src/plant_state.rs b/rust/src/plant_state.rs index 69d92b6..80bcf1a 100644 --- a/rust/src/plant_state.rs +++ b/rust/src/plant_state.rs @@ -1,68 +1,110 @@ -use chrono::{DateTime, Utc}; +use chrono::{DateTime, TimeDelta, Utc}; use chrono_tz::Tz; -use serde::Serialize; +use measurements::humidity; +use serde::{Deserialize, Serialize}; -use crate::{config, plant_hal}; +use crate::{ + config::{self, PlantConfig}, + 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 -pub enum HumiditySensorError{ - ShortCircuit{hz: f32, max: f32}, - OpenLoop{hz: f32, min: f32} +pub enum HumiditySensorError { + ShortCircuit { hz: f32, max: f32 }, + OpenLoop { hz: f32, min: f32 }, } +#[derive(Debug, PartialEq)] pub enum HumiditySensorState { Disabled, - HumidityValue{raw_hz: u32, moisture_percent: f32}, + HumidityValue { raw_hz: u32, moisture_percent: f32 }, SensorError(HumiditySensorError), - BoardError(String) + BoardError(String), } impl HumiditySensorState { + pub fn is_err(&self) -> bool { + matches!(self, Self::SensorError(_)) || matches!(self, Self::BoardError(_)) + } + + pub fn moisture_percent(&self) -> Option { + if let HumiditySensorState::HumidityValue { + raw_hz, + moisture_percent, + } = self + { + Some(moisture_percent) + } else { + None + } + } } -pub enum PumpError {} +impl HumiditySensorState {} + +#[derive(Debug, PartialEq)] +pub enum PumpError { + PumpNotWorking { + failed_attempts: usize, + max_allowed_failures: usize, + }, +} pub struct PumpState { consecutive_pump_count: u32, - previous_pump: Option> + previous_pump: Option>, } -pub enum PlantError{} +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub enum PlantWateringMode { + OFF, + TargetMoisture, + TimerOnly, +} + +pub enum PlantError {} pub struct PlantState { - sensor_a: HumiditySensorState, - sensor_b: HumiditySensorState, - pump: PumpState, + pub sensor_a: HumiditySensorState, + pub sensor_b: HumiditySensorState, + pub pump: PumpState, } fn map_range_moisture(s: f32) -> Result { if s < MOIST_SENSOR_MIN_FREQUENCY { - return Err(HumiditySensorError::OpenCircuit { hz: s, min: FROM.0 }); + return Err(HumiditySensorError::OpenCircuit { + hz: s, + min: MOIST_SENSOR_MIN_FREQUENCY, + }); } if s > MOIST_SENSOR_MAX_FREQUENCY { - return Err(HumiditySensorError::ShortCircuit { hz: s, max: FROM.1 }); + return Err(HumiditySensorError::ShortCircuit { + hz: s, + max: MOIST_SENSOR_MAX_FREQUENCY, + }); } - let moisture_percent = (s - MOIST_SENSOR_MIN_FREQUENCY) * 100 / (MOIST_SENSOR_MAX_FREQUENCY - MOIST_SENSOR_MIN_FREQUENCY); + 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 + 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), - } + 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()), } @@ -71,11 +113,12 @@ impl PlantState { }; 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), - } + 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()), } @@ -84,45 +127,145 @@ impl PlantState { }; let previous_pump = board.last_pump_time(plant_id); let consecutive_pump_count = board.consecutive_pump_count(plant_id); - Self { + let state = Self { sensor_a, sensor_b, - pump: PumpState { consecutive_pump_count , previous_pump} + pump: PumpState { + consecutive_pump_count, + previous_pump, + }, + }; + if state.is_err() { + board.fault(plant_id, true); + } + state + } + + pub fn pump_in_timeout(&self, plant_conf: &PlantConfig, current_time: &DateTime) -> bool { + self.pump.previous_pump.is_some_and(|last_pump| { + last_pump + .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into())) + .is_some_and(|earliest_next_allowed_pump| { + earliest_next_allowed_pump > *current_time + }) + }) + } + + pub fn is_err(&self) -> bool { + self.sensor_a.is_err() || self.sensor_b.is_err() + } + + pub fn needs_to_be_watered( + &self, + plant_conf: &PlantConfig, + current_time: &DateTime, + ) -> bool { + 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 { + false + } + } + } + PlantWateringMode::TimerOnly => { + if self.pump_in_timeout(plant_conf, current_time) { + false + } else { + true + } + } } } } -#[derive(Debug, PartialEq, Default, Serialize)] +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 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, + sensor_a: HumiditySensorState, /// state of humidity sensor on bank b - b: HumiditySensorState, - /// raw measured frequency value for sensor on bank b in hertz - b_raw: Option, + sensor_b: HumiditySensorState, /// 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, + mode: config::PlantWateringMode, /// 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 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 + /// 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 + consecutive_pump_count: u32, + pump_error: Option, /// last time when pump was active last_pump: Option>, /// next time when pump should activate diff --git a/rust/src/util.rs b/rust/src/util.rs new file mode 100644 index 0000000..a26fee4 --- /dev/null +++ b/rust/src/util.rs @@ -0,0 +1,10 @@ + +pub trait LimitPrecision { + fn to_precision(self, presision: i32) -> self; +} + +impl LimitPrecision for f32 { + fn to_precision(self, precision: i32) -> self { + (self * (10_f32).powi(precision)).round() / (10_f32).powi(precision) + } +} diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index 0fb1f6d..f47a0fa 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -1,8 +1,8 @@ //offer ota and config mode use crate::{ - determine_tank_state, get_version, log::LogMessage, map_range_moisture, plant_hal::PLANT_COUNT, - BOARD_ACCESS, + determine_tank_state, get_version, log::LogMessage, plant_hal::PLANT_COUNT, + plant_state::PlantState, util::LimitPrecision, BOARD_ACCESS, }; use anyhow::bail; use chrono::DateTime; @@ -35,8 +35,8 @@ struct LoadData<'a> { #[derive(Serialize, Debug)] struct Moistures { - moisture_a: Vec, - moisture_b: Vec, + moisture_a: Vec>, + moisture_b: Vec>, } #[derive(Deserialize, Debug)] @@ -81,33 +81,21 @@ fn get_live_moisture( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let mut board = BOARD_ACCESS.lock().unwrap(); + let config = board.get_config().unwrap(); - let mut a: Vec = Vec::new(); - let mut b: Vec = Vec::new(); - for plant in 0..8 { - let a_hz = board.measure_moisture_hz(plant, crate::plant_hal::Sensor::A)?; - let b_hz = board.measure_moisture_hz(plant, crate::plant_hal::Sensor::B)?; - let a_pct = map_range_moisture(a_hz as f32); - - match a_pct { - Ok(result) => { - a.push(result); - } - Err(_) => { - a.push(200); - } - } - - let b_pct = map_range_moisture(b_hz as f32); - match b_pct { - Ok(result) => { - b.push(result); - } - Err(_) => { - b.push(200); - } - } - } + let plant_state = Vec::from_iter( + (0..PLANT_COUNT).map(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i])), + ); + let a = Vec::from_iter( + plant_state + .iter() + .map(|s| s.sensor_a.moisture_percent().map(|f| f.to_precision(2))), + ); + let b = Vec::from_iter( + plant_state + .iter() + .map(|s| s.sensor_b.moisture_percent().map(|f| f.to_precision(2))), + ); let data = Moistures { moisture_a: a,