diff --git a/rust/src/config.rs b/rust/src/config.rs index f8768b4..8ba6148 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -118,6 +118,9 @@ pub struct PlantConfig { pub max_consecutive_pump_count: u8, pub moisture_sensor_min_frequency: Option, // Optional min frequency pub moisture_sensor_max_frequency: Option, // Optional max frequency + pub min_pump_current_ma: u16, + pub max_pump_current_ma: u16, + pub ignore_current_error: bool, } impl Default for PlantConfig { @@ -134,6 +137,9 @@ impl Default for PlantConfig { max_consecutive_pump_count: 10, moisture_sensor_min_frequency: None, // No override by default moisture_sensor_max_frequency: None, // No override by default + min_pump_current_ma: 10, + max_pump_current_ma: 3000, + ignore_current_error: true, } } } diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs index 7a25adb..06e757e 100644 --- a/rust/src/hal/initial_hal.rs +++ b/rust/src/hal/initial_hal.rs @@ -107,6 +107,10 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { bail!("Please configure board revision") } + fn pump_current(&mut self, _plant: usize) -> Result { + bail!("Please configure board revision") + } + fn fault(&mut self, _plant: usize, _enable: bool) -> Result<()> { bail!("Please configure board revision") } @@ -119,10 +123,6 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { let _ = self.general_fault.set_state(enable.into()); } - fn test_pump(&mut self, _plant: usize) -> Result<()> { - bail!("Please configure board revision") - } - fn test(&mut self) -> Result<()> { bail!("Please configure board revision") } diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index b121b8b..4837099 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -99,11 +99,10 @@ pub trait BoardInteraction<'a> { //should be multsampled fn light(&mut self, enable: bool) -> Result<()>; fn pump(&mut self, plant: usize, enable: bool) -> Result<()>; + fn pump_current(&mut self, plant: usize) -> Result; fn fault(&mut self, plant: usize, enable: bool) -> Result<()>; fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result; fn general_fault(&mut self, enable: bool); - - fn test_pump(&mut self, plant: usize) -> Result<()>; fn test(&mut self) -> Result<()>; fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>; fn get_mptt_voltage(&mut self) -> anyhow::Result; diff --git a/rust/src/hal/v3_hal.rs b/rust/src/hal/v3_hal.rs index c795515..0a9772b 100644 --- a/rust/src/hal/v3_hal.rs +++ b/rust/src/hal/v3_hal.rs @@ -14,7 +14,7 @@ use esp_idf_hal::{ gpio::{AnyInputPin, IOPin, InputOutput, PinDriver, Pull}, pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex}, }; -use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay}; +use esp_idf_sys::{gpio_hold_dis, gpio_hold_en}; use measurements::{Current, Voltage}; use plant_ctrl2::sipo::ShiftRegister40; use std::result::Result::Ok as OkStd; @@ -252,6 +252,10 @@ impl<'a> BoardInteraction<'a> for V3<'a> { Ok(()) } + fn pump_current(&mut self, _plant: usize) -> Result { + bail!("Not implemented in v3") + } + fn fault(&mut self, plant: usize, enable: bool) -> Result<()> { let index = match plant { 0 => FAULT_1, @@ -365,13 +369,6 @@ impl<'a> BoardInteraction<'a> for V3<'a> { unsafe { gpio_hold_en(self.general_fault.pin()) }; } - fn test_pump(&mut self, plant: usize) -> Result<()> { - self.pump(plant, true)?; - unsafe { vTaskDelay(30000) }; - self.pump(plant, false)?; - Ok(()) - } - fn test(&mut self) -> Result<()> { self.general_fault(true); self.esp.delay.delay_ms(100); diff --git a/rust/src/hal/v4_hal.rs b/rust/src/hal/v4_hal.rs index fc859bd..0ee598a 100644 --- a/rust/src/hal/v4_hal.rs +++ b/rust/src/hal/v4_hal.rs @@ -13,7 +13,7 @@ use embedded_hal::digital::OutputPin; use embedded_hal_bus::i2c::MutexDevice; use esp_idf_hal::delay::Delay; use esp_idf_hal::gpio::{AnyInputPin, IOPin, InputOutput, Output, PinDriver, Pull}; -use esp_idf_hal::i2c::{I2cDriver, I2cError}; +use esp_idf_hal::i2c::I2cDriver; use esp_idf_hal::pcnt::{ PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, }; @@ -21,7 +21,6 @@ use esp_idf_sys::{gpio_hold_dis, gpio_hold_en}; use ina219::address::{Address, Pin}; use ina219::calibration::UnCalibrated; use ina219::configuration::{Configuration, OperatingMode}; -use ina219::errors::InitializationError; use ina219::SyncIna219; use measurements::{Current, Resistance, Voltage}; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; @@ -125,6 +124,7 @@ pub struct V4<'a> { light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, pump_expander: Pca9535Immediate>>, + pump_ina: Option>, UnCalibrated>>, sensor_expander: Pca9535Immediate>>, extra1: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, extra2: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, @@ -208,7 +208,7 @@ pub(crate) fn create_v4( let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin); } - let mut mppt_ina = SyncIna219::new( + let mppt_ina = SyncIna219::new( MutexDevice::new(&I2C_DRIVER), Address::from_pins(Pin::Vcc, Pin::Gnd), ); @@ -224,15 +224,6 @@ pub(crate) fn create_v4( operating_mode: Default::default(), })?; - //TODO this is probably already done until we are ready first time?, maybe add startup time comparison on access? - esp.delay.delay_ms( - mppt_ina - .configuration()? - .conversion_time() - .unwrap() - .as_millis() as u32, - ); - Charger::SolarMpptV1 { mppt_ina, solar_is_day, @@ -242,6 +233,17 @@ pub(crate) fn create_v4( Err(_) => Charger::ErrorInit {}, }; + let pump_ina = match SyncIna219::new( + MutexDevice::new(&I2C_DRIVER), + Address::from_pins(Pin::Gnd, Pin::Sda), + ) { + Ok(pump_ina) => Some(pump_ina), + Err(err) => { + println!("Error creating pump ina: {:?}", err); + None + } + }; + let v = V4 { rtc_module, esp, @@ -250,6 +252,7 @@ pub(crate) fn create_v4( signal_counter, light, general_fault, + pump_ina, pump_expander, sensor_expander, config, @@ -314,6 +317,24 @@ impl<'a> BoardInteraction<'a> for V4<'a> { anyhow::Ok(()) } + fn pump_current(&mut self, _plant: usize) -> anyhow::Result { + //sensore is shared for all pumps, ignore plant id + match self.pump_ina.as_mut() { + None => { + bail!("pump current sensor not available"); + } + Some(pump_ina) => { + let v = pump_ina.shunt_voltage().map(|v| { + let shunt_voltage = Voltage::from_microvolts(v.shunt_voltage_uv().abs() as f64); + let shut_value = Resistance::from_ohms(0.05_f64); + let current = shunt_voltage.as_volts() / shut_value.as_ohms(); + Current::from_amperes(current) + })?; + Ok(v) + } + } + } + fn fault(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> { if enable { self.pump_expander @@ -406,13 +427,6 @@ impl<'a> BoardInteraction<'a> for V4<'a> { unsafe { gpio_hold_en(self.general_fault.pin()) }; } - fn test_pump(&mut self, plant: usize) -> anyhow::Result<()> { - self.pump(plant, true)?; - self.esp.delay.delay_ms(30000); - self.pump(plant, false)?; - anyhow::Ok(()) - } - fn test(&mut self) -> anyhow::Result<()> { self.general_fault(true); self.esp.delay.delay_ms(100); diff --git a/rust/src/log/mod.rs b/rust/src/log/mod.rs index 3315333..ef8a555 100644 --- a/rust/src/log/mod.rs +++ b/rust/src/log/mod.rs @@ -93,6 +93,7 @@ pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &st let serial_entry = template.fill_in(&values); println!("{serial_entry}"); + //TODO push to mqtt? let entry = LogEntry { timestamp: time, @@ -193,6 +194,16 @@ pub enum LogMessage { serialize = "Pumped multiple times, but plant is still to try attempt: ${number_a} limit :: ${number_b} plant: ${txt_short}" )] ConsecutivePumpCountLimit, + #[strum( + serialize = "Pump Overcurrent error, pump: ${number_a} tripped overcurrent ${number_b} limit was ${txt_short} @s ${txt_long}" + )] + PumpOverCurrent, + #[strum( + serialize = "Pump Open loop error, pump: ${number_a} is low, ${number_b} limit was ${txt_short} @s ${txt_long}" + )] + PumpOpenLoopCurrent, + #[strum(serialize = "Pump Open current sensor required but did not work: ${number_a}")] + PumpMissingSensorCurrent, } #[derive(Serialize)] diff --git a/rust/src/main.rs b/rust/src/main.rs index 89338fe..a0a664b 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,3 +1,4 @@ +use crate::config::PlantConfig; use crate::{ config::BoardVersion::INITIAL, hal::{PlantHal, HAL, PLANT_COUNT}, @@ -76,6 +77,17 @@ struct LightState { struct PumpInfo { enabled: bool, pump_ineffective: bool, + median_current_ma: u16, + max_current_ma: u16, + min_current_ma: u16, +} + +#[derive(Serialize)] +pub struct PumpResult { + median_current_ma: u16, + max_current_ma: u16, + min_current_ma: u16, + error: bool, } #[derive(Serialize, Deserialize, Debug, PartialEq)] @@ -225,6 +237,8 @@ fn safe_main() -> anyhow::Result<()> { try_connect_wifi_sntp_mqtt(&mut board) } else { println!("No wifi configured"); + //the current sensors require this amount to stabilize, in case of wifi this is already handles for sure; + board.board_hal.get_esp().delay.delay_ms(100); NetworkMode::OFFLINE }; @@ -395,14 +409,20 @@ fn safe_main() -> anyhow::Result<()> { board.board_hal.get_esp().last_pump_time(plant_id); //state.active = true; - pump_info(&mut board, plant_id, true, pump_ineffective); + pump_info(&mut board, plant_id, true, pump_ineffective, 0, 0, 0, false); - if !dry_run { - board.board_hal.pump(plant_id, true)?; - Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32); - board.board_hal.pump(plant_id, false)?; - } - pump_info(&mut board, plant_id, false, pump_ineffective); + let result = do_secure_pump(&mut board, plant_id, plant_config, dry_run)?; + board.board_hal.pump(plant_id, false)?; + pump_info( + &mut board, + plant_id, + false, + pump_ineffective, + result.median_current_ma, + result.max_current_ma, + result.min_current_ma, + result.error, + ); } 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 @@ -556,6 +576,92 @@ fn safe_main() -> anyhow::Result<()> { .deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64); } +pub fn do_secure_pump( + board: &mut MutexGuard, + plant_id: usize, + plant_config: &PlantConfig, + dry_run: bool, +) -> anyhow::Result { + let mut current_collector = vec![0_u16; plant_config.pump_time_s.into()]; + let mut error = false; + let mut first_error = true; + if !dry_run { + board.board_hal.pump(plant_id, true)?; + Delay::new_default().delay_ms(2); + for step in 0..plant_config.pump_time_s as usize { + let current = board.board_hal.pump_current(plant_id); + match current { + Ok(current) => { + let current_ma = current.as_milliamperes() as u16; + current_collector[step] = current_ma; + let high_current = current_ma > plant_config.max_pump_current_ma; + if high_current { + if first_error { + log( + LogMessage::PumpOverCurrent, + plant_id as u32 + 1, + current_ma as u32, + plant_config.max_pump_current_ma.to_string().as_str(), + step.to_string().as_str(), + ); + board.board_hal.general_fault(true); + board.board_hal.fault(plant_id, true)?; + if !plant_config.ignore_current_error { + error = true; + break; + } + first_error = false; + } + } + let low_current = current_ma < plant_config.min_pump_current_ma; + if low_current { + if first_error { + log( + LogMessage::PumpOpenLoopCurrent, + plant_id as u32 + 1, + current_ma as u32, + plant_config.min_pump_current_ma.to_string().as_str(), + step.to_string().as_str(), + ); + board.board_hal.general_fault(true); + board.board_hal.fault(plant_id, true)?; + if !plant_config.ignore_current_error { + error = true; + break; + } + first_error = false; + } + } + } + Err(err) => { + if !plant_config.ignore_current_error { + println!("Error getting pump current: {}", err); + log( + LogMessage::PumpMissingSensorCurrent, + plant_id as u32, + 0, + "", + "", + ); + error = true; + break; + } else { + //eg v3 without a sensor ends here, do not spam + } + } + } + Delay::new_default().delay_ms(1000); + } + } + current_collector.sort(); + Ok(PumpResult { + median_current_ma: current_collector[current_collector.len() / 2], + max_current_ma: current_collector[current_collector.len() - 1], + min_current_ma: current_collector[0], + error, + }) +} + fn update_charge_indicator(board: &mut MutexGuard) { //we have mppt controller, ask it for charging current if let Ok(current) = board.board_hal.get_mptt_current() { @@ -711,10 +817,17 @@ fn pump_info( plant_id: usize, pump_active: bool, pump_ineffective: bool, + median_current_ma: u16, + max_current_ma: u16, + min_current_ma: u16, + error: bool, ) { let pump_info = PumpInfo { enabled: pump_active, pump_ineffective, + median_current_ma: median_current_ma, + max_current_ma: max_current_ma, + min_current_ma: min_current_ma, }; let pump_topic = format!("/pump{}", plant_id + 1); match serde_json::to_string(&pump_info) { diff --git a/rust/src/webserver/mod.rs b/rust/src/webserver/mod.rs index b3c6ff1..88d5814 100644 --- a/rust/src/webserver/mod.rs +++ b/rust/src/webserver/mod.rs @@ -2,7 +2,7 @@ use crate::{ config::PlantControllerConfig, - determine_tank_state, get_version, + determine_tank_state, do_secure_pump, get_version, hal::PLANT_COUNT, log::LogMessage, plant_state::{MoistureSensorState, PlantState}, @@ -222,7 +222,7 @@ fn backup_info( fn set_config( request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let all = read_up_to_bytes_from_request(request, Some(3072))?; + let all = read_up_to_bytes_from_request(request, Some(4096))?; let config: PlantControllerConfig = serde_json::from_slice(&all)?; let mut board = BOARD_ACCESS.lock().expect("board access"); @@ -275,8 +275,11 @@ fn pump_test( let actual_data = read_up_to_bytes_from_request(request, None)?; let pump_test: TestPump = serde_json::from_slice(&actual_data)?; let mut board = BOARD_ACCESS.lock().unwrap(); - board.board_hal.test_pump(pump_test.pump)?; - anyhow::Ok(None) + + let config = &board.board_hal.get_config().plants[pump_test.pump].clone(); + let pump_result = do_secure_pump(&mut board, pump_test.pump, config, false)?; + board.board_hal.pump(pump_test.pump, false)?; + anyhow::Ok(Some(serde_json::to_string(&pump_result)?)) } fn tank_info( diff --git a/rust/src_webpack/src/api.ts b/rust/src_webpack/src/api.ts index 712d639..738cff5 100644 --- a/rust/src_webpack/src/api.ts +++ b/rust/src_webpack/src/api.ts @@ -1,174 +1,185 @@ -export interface LogArray extends Array{} - -export interface LogEntry { - timestamp: string, - message_id: number, - a: number, - b: number, - txt_short: string, - txt_long: string +export interface LogArray extends Array { } -export interface LogLocalisation extends Array{} +export interface LogEntry { + timestamp: string, + message_id: number, + a: number, + b: number, + txt_short: string, + txt_long: string +} + +export interface LogLocalisation extends Array { +} export interface LogLocalisationEntry { - msg_type: string, - message: string + msg_type: string, + message: string } export interface BackupHeader { - timestamp: string, - size: number + timestamp: string, + size: number } export interface NetworkConfig { - ap_ssid: string, - ssid: string, - password: string, - mqtt_url: string, - base_topic: string, - max_wait: number + ap_ssid: string, + ssid: string, + password: string, + mqtt_url: string, + base_topic: string, + max_wait: number } export interface FileList { - total: number, - used: number, - files: FileInfo[], - file_system_corrupt: string, - iter_error: string, + total: number, + used: number, + files: FileInfo[], + file_system_corrupt: string, + iter_error: string, } -export interface SolarState{ - mppt_voltage: number, - mppt_current: number, - is_day: boolean +export interface SolarState { + mppt_voltage: number, + mppt_current: number, + is_day: boolean } -export interface FileInfo{ - filename: string, - size: number, +export interface FileInfo { + filename: string, + size: number, } export interface NightLampConfig { - enabled: boolean, - night_lamp_hour_start: number, - night_lamp_hour_end: number, - night_lamp_only_when_dark: boolean, - low_soc_cutoff: number, - low_soc_restore: number + enabled: boolean, + night_lamp_hour_start: number, + night_lamp_hour_end: number, + night_lamp_only_when_dark: boolean, + low_soc_cutoff: number, + low_soc_restore: number } export interface NightLampCommand { - active: boolean + active: boolean } export interface TankConfig { - tank_sensor_enabled: boolean, - tank_allow_pumping_if_sensor_error: boolean, - tank_useable_ml: number, - tank_warn_percent: number, - tank_empty_percent: number, - tank_full_percent: number, + tank_sensor_enabled: boolean, + tank_allow_pumping_if_sensor_error: boolean, + tank_useable_ml: number, + tank_warn_percent: number, + tank_empty_percent: number, + tank_full_percent: number, } export enum BatteryBoardVersion { - Disabled = "Disabled", - BQ34Z100G1 = "BQ34Z100G1", - WchI2cSlave = "WchI2cSlave" + Disabled = "Disabled", + BQ34Z100G1 = "BQ34Z100G1", + WchI2cSlave = "WchI2cSlave" } -export enum BoardVersion{ + +export enum BoardVersion { INITIAL = "INITIAL", V3 = "V3", V4 = "V4" } export interface BoardHardware { - board: BoardVersion, - battery: BatteryBoardVersion, + board: BoardVersion, + battery: BatteryBoardVersion, } export interface PlantControllerConfig { - hardware: BoardHardware, + hardware: BoardHardware, - network: NetworkConfig, - tank: TankConfig, - night_lamp: NightLampConfig, - plants: PlantConfig[] - timezone?: string, + network: NetworkConfig, + tank: TankConfig, + night_lamp: NightLampConfig, + plants: PlantConfig[] + timezone?: string, } export interface PlantConfig { - mode: string, - target_moisture: number, - pump_time_s: number, - pump_cooldown_min: number, - pump_hour_start: number, - pump_hour_end: number, - sensor_a: boolean, - sensor_b: boolean, - max_consecutive_pump_count: number, - moisture_sensor_min_frequency: number | null; - moisture_sensor_max_frequency: number | null; - + mode: string, + target_moisture: number, + pump_time_s: number, + pump_cooldown_min: number, + pump_hour_start: number, + pump_hour_end: number, + sensor_a: boolean, + sensor_b: boolean, + max_consecutive_pump_count: number, + moisture_sensor_min_frequency: number | null; + moisture_sensor_max_frequency: number | null; + min_pump_current_ma: number, + max_pump_current_ma: number, + ignore_current_error: boolean, } +export interface PumpTestResult { + median_current_ma: number, + max_current_ma: number, + min_current_ma: number, + error: boolean, +} export interface SSIDList { - ssids: [string] + ssids: [string] } export interface TestPump { - pump: number + pump: number } export interface SetTime { - time: string + time: string } export interface GetTime { - rtc: string, - native: string + rtc: string, + native: string } export interface Moistures { - moisture_a: [string], - moisture_b: [string], + moisture_a: [string], + moisture_b: [string], } export interface VersionInfo { - git_hash: string, - build_time: string, - partition: string + git_hash: string, + build_time: string, + partition: string } export interface BatteryState { - temperature: string - voltage_milli_volt: string, - current_milli_ampere: string, - cycle_count: string, - design_milli_ampere: string, - remaining_milli_ampere: string, - state_of_charge: string, - state_of_health: string + temperature: string + voltage_milli_volt: string, + current_milli_ampere: string, + cycle_count: string, + design_milli_ampere: string, + remaining_milli_ampere: string, + state_of_charge: string, + state_of_health: string } export interface TankInfo { - /// is there enough water in the tank - enough_water: boolean, - /// warning that water needs to be refilled soon - warn_level: boolean, - /// estimation how many ml are still in tank - left_ml: number | null, - /// if there is was an issue with the water level sensor - sensor_error: string | null, - /// raw water sensor value - raw: number | null, - /// percent value - percent: number | null, - /// water in tank might be frozen - water_frozen: boolean, - /// water temperature - water_temp: number | null, - temp_sensor_error: string | null + /// is there enough water in the tank + enough_water: boolean, + /// warning that water needs to be refilled soon + warn_level: boolean, + /// estimation how many ml are still in tank + left_ml: number | null, + /// if there is was an issue with the water level sensor + sensor_error: string | null, + /// raw water sensor value + raw: number | null, + /// percent value + percent: number | null, + /// water in tank might be frozen + water_frozen: boolean, + /// water temperature + water_temp: number | null, + temp_sensor_error: string | null } diff --git a/rust/src_webpack/src/main.ts b/rust/src_webpack/src/main.ts index f930b5d..3487f2d 100644 --- a/rust/src_webpack/src/main.ts +++ b/rust/src_webpack/src/main.ts @@ -28,7 +28,7 @@ import { SetTime, SSIDList, TankInfo, TestPump, VersionInfo, - FileList, SolarState + FileList, SolarState, PumpTestResult } from "./api"; import {SolarView} from "./solarview"; @@ -345,9 +345,10 @@ export class Controller { method: "POST", body: pretty }) - .then(response => response.text()) + .then(response => response.json() as Promise) .then( - _ => { + response => { + controller.plantViews.setPumpTestCurrent(plantId, response); clearTimeout(timerId); controller.progressview.removeProgress("test_pump"); } diff --git a/rust/src_webpack/src/plant.html b/rust/src_webpack/src/plant.html index 94d9f24..14e7f61 100644 --- a/rust/src_webpack/src/plant.html +++ b/rust/src_webpack/src/plant.html @@ -1,27 +1,30 @@
+ id="plant_${plantId}_header"> Plant ${plantId}
@@ -47,7 +50,8 @@
Pump Cooldown (m):
- +
"Pump Hour Start":
@@ -59,8 +63,8 @@
Warn Pump Count:
- +
Min Frequency Override
@@ -68,7 +72,7 @@
Max Frequency Override
- +
@@ -79,6 +83,22 @@
Sensor B installed:
+
+

Current config:

+
+
+
Min current
+ +
+
+
Max current
+ +
+
+
Ignore current sensor error
+ +
+
@@ -89,11 +109,15 @@
Sensor A: - loading + not measured
Sensor B:
- loading + not measured +
+
+
Test Current
+ not_tested
diff --git a/rust/src_webpack/src/plant.ts b/rust/src_webpack/src/plant.ts index cf58210..4705cd8 100644 --- a/rust/src_webpack/src/plant.ts +++ b/rust/src_webpack/src/plant.ts @@ -1,4 +1,4 @@ -import {PlantConfig} from "./api"; +import {PlantConfig, PumpTestResult} from "./api"; const PLANT_COUNT = 8; @@ -9,39 +9,45 @@ export class PlantViews { private readonly measure_moisture: HTMLButtonElement; private readonly plants: PlantView[] = [] private readonly plantsDiv: HTMLDivElement - - constructor(syncConfig:Controller) { - this.measure_moisture = document.getElementById("measure_moisture") as HTMLButtonElement - this.measure_moisture.onclick = syncConfig.measure_moisture - this.plantsDiv = document.getElementById("plants") as HTMLDivElement; - for (let plantId = 0; plantId < PLANT_COUNT; plantId++) { - this.plants[plantId] = new PlantView(plantId, this.plantsDiv, syncConfig); - } + + constructor(syncConfig: Controller) { + this.measure_moisture = document.getElementById("measure_moisture") as HTMLButtonElement + this.measure_moisture.onclick = syncConfig.measure_moisture + this.plantsDiv = document.getElementById("plants") as HTMLDivElement; + for (let plantId = 0; plantId < PLANT_COUNT; plantId++) { + this.plants[plantId] = new PlantView(plantId, this.plantsDiv, syncConfig); + } } getConfig(): PlantConfig[] { - const rv: PlantConfig[] = []; - for (let i = 0; i < PLANT_COUNT; i++) { - rv[i] = this.plants[i].getConfig(); - } - return rv + const rv: PlantConfig[] = []; + for (let i = 0; i < PLANT_COUNT; i++) { + rv[i] = this.plants[i].getConfig(); + } + return rv } + update(moisture_a: [string], moisture_b: [string]) { - for (let plantId = 0; plantId < PLANT_COUNT; plantId++) { - const a = moisture_a[plantId] - const b = moisture_b[plantId] - this.plants[plantId].update(a,b) - } + for (let plantId = 0; plantId < PLANT_COUNT; plantId++) { + const a = moisture_a[plantId] + const b = moisture_b[plantId] + this.plants[plantId].setMeasurementResult(a, b) + } } setConfig(plants: PlantConfig[]) { - for (let plantId = 0; plantId < PLANT_COUNT; plantId++) { - const plantConfig = plants[plantId]; - const plantView = this.plants[plantId]; - plantView.setConfig(plantConfig) - } + for (let plantId = 0; plantId < PLANT_COUNT; plantId++) { + const plantConfig = plants[plantId]; + const plantView = this.plants[plantId]; + plantView.setConfig(plantConfig) + } } - } + + setPumpTestCurrent(plantId: number, response: PumpTestResult) { + const plantView = this.plants[plantId]; + plantView.setTestResult(response) + } +} export class PlantView { private readonly moistureSensorMinFrequency: HTMLInputElement; @@ -60,109 +66,135 @@ export class PlantView { private readonly mode: HTMLSelectElement; private readonly moistureA: HTMLElement; private readonly moistureB: HTMLElement; + private readonly pump_current_result: HTMLElement private readonly maxConsecutivePumpCount: HTMLInputElement; - - - constructor(plantId: number, parent:HTMLDivElement, controller:Controller) { + private readonly minPumpCurrentMa: HTMLInputElement; + private readonly maxPumpCurrentMa: HTMLInputElement; + private readonly ignoreCurrentError: HTMLInputElement; + + + constructor(plantId: number, parent: HTMLDivElement, controller: Controller) { this.plantId = plantId; - this.plantDiv = document.createElement("div")! as HTMLDivElement - const template = require('./plant.html') as string; + this.plantDiv = document.createElement("div")! as HTMLDivElement + const template = require('./plant.html') as string; this.plantDiv.innerHTML = template.replaceAll("${plantId}", String(plantId)) - - this.plantDiv.classList.add("plantcontainer") - parent.appendChild(this.plantDiv) - - this.header = document.getElementById("plant_"+plantId+"_header")! - this.header.innerText = "Plant "+ (this.plantId+1) - - this.moistureA = document.getElementById("plant_"+plantId+"_moisture_a")! as HTMLElement; - this.moistureB = document.getElementById("plant_"+plantId+"_moisture_b")! as HTMLElement; - this.testButton = document.getElementById("plant_"+plantId+"_test")! as HTMLButtonElement; - this.testButton.onclick = function(){ - controller.testPlant(plantId) - } + this.plantDiv.classList.add("plantcontainer") + parent.appendChild(this.plantDiv) - this.mode = document.getElementById("plant_"+plantId+"_mode") as HTMLSelectElement - this.mode.onchange = function(){ - controller.configChanged() - } + this.header = document.getElementById("plant_" + plantId + "_header")! + this.header.innerText = "Plant " + (this.plantId + 1) - this.targetMoisture = document.getElementById("plant_"+plantId+"_target_moisture")! as HTMLInputElement; - this.targetMoisture.onchange = function(){ - controller.configChanged() - } + this.moistureA = document.getElementById("plant_" + plantId + "_moisture_a")! as HTMLElement; + this.moistureB = document.getElementById("plant_" + plantId + "_moisture_b")! as HTMLElement; + this.pump_current_result = document.getElementById("plant_" + plantId + "_pump_current_result")! as HTMLElement; - this.pumpTimeS = document.getElementById("plant_"+plantId+"_pump_time_s") as HTMLInputElement; - this.pumpTimeS.onchange = function(){ - controller.configChanged() - } - this.pumpCooldown = document.getElementById("plant_"+plantId+"_pump_cooldown_min") as HTMLInputElement; - this.pumpCooldown.onchange = function(){ - controller.configChanged() - } - - this.pumpHourStart = document.getElementById("plant_"+plantId+"_pump_hour_start") as HTMLSelectElement; - this.pumpHourStart.onchange = function(){ - controller.configChanged() - } - for (let i = 0; i < 24; i++) { - let option = document.createElement("option"); - if (i == 10){ - option.selected = true + this.testButton = document.getElementById("plant_" + plantId + "_test")! as HTMLButtonElement; + this.testButton.onclick = function () { + controller.testPlant(plantId) } - option.innerText = i.toString(); - this.pumpHourStart.appendChild(option); - } - this.pumpHourEnd = document.getElementById("plant_"+plantId+"_pump_hour_end") as HTMLSelectElement; - this.pumpHourEnd.onchange = function(){ - controller.configChanged() - } - for (let i = 0; i < 24; i++) { - let option = document.createElement("option"); - if (i == 19){ - option.selected = true + this.mode = document.getElementById("plant_" + plantId + "_mode") as HTMLSelectElement + this.mode.onchange = function () { + controller.configChanged() } - option.innerText = i.toString(); - this.pumpHourEnd.appendChild(option); - } - this.sensorAInstalled = document.getElementById("plant_"+plantId+"_sensor_a") as HTMLInputElement; - this.sensorAInstalled.onchange = function(){ - controller.configChanged() - } + this.targetMoisture = document.getElementById("plant_" + plantId + "_target_moisture")! as HTMLInputElement; + this.targetMoisture.onchange = function () { + controller.configChanged() + } - this.sensorBInstalled = document.getElementById("plant_"+plantId+"_sensor_b") as HTMLInputElement; - this.sensorBInstalled.onchange = function(){ - controller.configChanged() - } + this.pumpTimeS = document.getElementById("plant_" + plantId + "_pump_time_s") as HTMLInputElement; + this.pumpTimeS.onchange = function () { + controller.configChanged() + } - this.maxConsecutivePumpCount = document.getElementById("plant_"+plantId+"_max_consecutive_pump_count") as HTMLInputElement; - this.maxConsecutivePumpCount.onchange = function(){ - controller.configChanged() - } + this.pumpCooldown = document.getElementById("plant_" + plantId + "_pump_cooldown_min") as HTMLInputElement; + this.pumpCooldown.onchange = function () { + controller.configChanged() + } - this.moistureSensorMinFrequency = document.getElementById("plant_"+plantId+"_min_frequency") as HTMLInputElement; - this.moistureSensorMinFrequency.onchange = function(){ + this.pumpHourStart = document.getElementById("plant_" + plantId + "_pump_hour_start") as HTMLSelectElement; + this.pumpHourStart.onchange = function () { + controller.configChanged() + } + for (let i = 0; i < 24; i++) { + let option = document.createElement("option"); + if (i == 10) { + option.selected = true + } + option.innerText = i.toString(); + this.pumpHourStart.appendChild(option); + } + + this.pumpHourEnd = document.getElementById("plant_" + plantId + "_pump_hour_end") as HTMLSelectElement; + this.pumpHourEnd.onchange = function () { + controller.configChanged() + } + for (let i = 0; i < 24; i++) { + let option = document.createElement("option"); + if (i == 19) { + option.selected = true + } + option.innerText = i.toString(); + this.pumpHourEnd.appendChild(option); + } + + this.sensorAInstalled = document.getElementById("plant_" + plantId + "_sensor_a") as HTMLInputElement; + this.sensorAInstalled.onchange = function () { + controller.configChanged() + } + + this.sensorBInstalled = document.getElementById("plant_" + plantId + "_sensor_b") as HTMLInputElement; + this.sensorBInstalled.onchange = function () { + controller.configChanged() + } + + this.minPumpCurrentMa = document.getElementById("plant_" + plantId + "_min_pump_current_ma") as HTMLInputElement; + this.minPumpCurrentMa.onchange = function () { + controller.configChanged() + } + + this.maxPumpCurrentMa = document.getElementById("plant_" + plantId + "_max_pump_current_ma") as HTMLInputElement; + this.maxPumpCurrentMa.onchange = function () { + controller.configChanged() + } + + this.ignoreCurrentError = document.getElementById("plant_" + plantId + "_ignore_current_error") as HTMLInputElement; + this.ignoreCurrentError.onchange = function () { + controller.configChanged() + } + + + this.maxConsecutivePumpCount = document.getElementById("plant_" + plantId + "_max_consecutive_pump_count") as HTMLInputElement; + this.maxConsecutivePumpCount.onchange = function () { + controller.configChanged() + } + + this.moistureSensorMinFrequency = document.getElementById("plant_" + plantId + "_min_frequency") as HTMLInputElement; + this.moistureSensorMinFrequency.onchange = function () { controller.configChanged() } this.moistureSensorMinFrequency.onchange = () => { controller.configChanged(); }; - this.moistureSensorMaxFrequency = document.getElementById("plant_"+plantId+"_max_frequency") as HTMLInputElement; + this.moistureSensorMaxFrequency = document.getElementById("plant_" + plantId + "_max_frequency") as HTMLInputElement; this.moistureSensorMaxFrequency.onchange = () => { controller.configChanged(); }; } - update(a: string, b: string) { + setTestResult(result: PumpTestResult) { + this.pump_current_result.innerText = "Did abort " + result.error + " median current " + result.median_current_ma + " max current " + result.max_current_ma + " min current " + result.min_current_ma + } + + setMeasurementResult(a: string, b: string) { this.moistureA.innerText = a this.moistureB.innerText = b } - + setConfig(plantConfig: PlantConfig) { this.mode.value = plantConfig.mode; this.targetMoisture.value = plantConfig.target_moisture.toString(); @@ -173,6 +205,9 @@ export class PlantView { this.sensorBInstalled.checked = plantConfig.sensor_b; this.sensorAInstalled.checked = plantConfig.sensor_a; this.maxConsecutivePumpCount.value = plantConfig.max_consecutive_pump_count.toString(); + this.minPumpCurrentMa.value = plantConfig.min_pump_current_ma.toString(); + this.maxPumpCurrentMa.value = plantConfig.max_pump_current_ma.toString(); + this.ignoreCurrentError.checked = plantConfig.ignore_current_error; // Set new fields this.moistureSensorMinFrequency.value = @@ -193,7 +228,10 @@ export class PlantView { sensor_a: this.sensorAInstalled.checked, max_consecutive_pump_count: this.maxConsecutivePumpCount.valueAsNumber, moisture_sensor_min_frequency: this.moistureSensorMinFrequency.valueAsNumber || null, - moisture_sensor_max_frequency: this.moistureSensorMaxFrequency.valueAsNumber || null + moisture_sensor_max_frequency: this.moistureSensorMaxFrequency.valueAsNumber || null, + min_pump_current_ma: this.minPumpCurrentMa.valueAsNumber, + max_pump_current_ma: this.maxPumpCurrentMa.valueAsNumber, + ignore_current_error: this.ignoreCurrentError.checked, }; } - } \ No newline at end of file +} \ No newline at end of file