diff --git a/Software/MainBoard/rust/src/config.rs b/Software/MainBoard/rust/src/config.rs index 06c9f55..2f1b66c 100644 --- a/Software/MainBoard/rust/src/config.rs +++ b/Software/MainBoard/rust/src/config.rs @@ -96,6 +96,8 @@ pub enum BoardVersion { pub struct BoardHardware { pub board: BoardVersion, pub battery: BatteryBoardVersion, + #[serde(default)] + pub pump_corrosion_protection: bool, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)] diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs index 72ac67f..3210642 100644 --- a/Software/MainBoard/rust/src/hal/esp.rs +++ b/Software/MainBoard/rust/src/hal/esp.rs @@ -55,6 +55,8 @@ static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT]; static mut LOW_VOLTAGE_DETECTED: i8 = 0; #[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] static mut RESTART_TO_CONF: i8 = 0; +#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] +static mut LAST_CORROSION_PROTECTION_CHECK_DAY: i8 = -1; const NTP_SERVER: &str = "pool.ntp.org"; @@ -341,6 +343,14 @@ impl Esp<'_> { } } } + pub(crate) fn get_last_corrosion_protection_check_day(&self) -> i8 { + unsafe { LAST_CORROSION_PROTECTION_CHECK_DAY } + } + pub(crate) fn set_last_corrosion_protection_check_day(&mut self, day: i8) { + unsafe { + LAST_CORROSION_PROTECTION_CHECK_DAY = day; + } + } pub(crate) async fn wifi_ap(&mut self, spawner: Spawner) -> FatResult> { let ssid = match self.load_config().await { @@ -595,6 +605,7 @@ impl Esp<'_> { } else { RESTART_TO_CONF = 0; } + LAST_CORROSION_PROTECTION_CHECK_DAY = -1; }; } else { unsafe { diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index 2d63e90..7c6627f 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -122,6 +122,7 @@ struct PumpInfo { median_current_ma: u16, max_current_ma: u16, min_current_ma: u16, + error: String, } #[derive(Serialize)] @@ -129,7 +130,7 @@ pub struct PumpResult { median_current_ma: u16, max_current_ma: u16, min_current_ma: u16, - error: bool, + error: String, flow_value_ml: f32, flow_value_count: i16, pump_time_s: u16, @@ -444,21 +445,40 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { board.board_hal.get_esp().last_pump_time(plant_id); //state.active = true; - pump_info(&mut board, plant_id, true, pump_ineffective, 0, 0, 0).await; + pump_info(&mut board, plant_id, true, pump_ineffective, 0, 0, 0, String::new()).await; - let result = do_secure_pump(&mut board, plant_id, plant_config, dry_run).await?; + let result = do_secure_pump(&mut board, plant_id, plant_config, dry_run).await; + match result { + Ok(state) => { + pump_info( + &mut board, + plant_id, + false, + pump_ineffective, + state.median_current_ma, + state.max_current_ma, + state.min_current_ma, + state.error, + ) + .await; + } + Err(err) => { + pump_info( + &mut board, + plant_id, + false, + pump_ineffective, + 0, + 0, + 0, + format!("{err:?}"), + ) + .await; + } + } //stop pump regardless of prior result//todo refactor to inner? board.board_hal.pump(plant_id, false).await?; - pump_info( - &mut board, - plant_id, - false, - pump_ineffective, - result.median_current_ma, - result.max_current_ma, - result.min_current_ma, - ) - .await; + } 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 @@ -468,6 +488,33 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { .store_consecutive_pump_count(plant_id, 0); } } + } else { + // Pump corrosion protection: pulses each pump once a week for 2s around midday. + let last_check_day = board.board_hal.get_esp().get_last_corrosion_protection_check_day(); + if board.board_hal.get_config().hardware.pump_corrosion_protection { + let current_day = timezone_time.weekday().number_from_monday() as i8; + let current_hour = timezone_time.hour(); + + // Monday (1) and around midday (11-13) + if current_day == 1 && (11..14).contains(¤t_hour) { + if last_check_day != current_day { + info!("Running pump corrosion protection"); + for plant_id in 0..PLANT_COUNT { + let mut plant_config = board.board_hal.get_config().plants[plant_id].clone(); + plant_config.pump_time_s = 2; + plant_config.pump_limit_ml = 1000; // high limit to ensure it runs for 2s + + log(LogMessage::PumpPlant, (plant_id + 1) as u32, 2, "corrosion_prot", ""); + let _ = do_secure_pump(&mut board, plant_id, &plant_config, dry_run).await; + let _ = board.board_hal.pump(plant_id, false).await; + } + board.board_hal.get_esp().set_last_corrosion_protection_check_day(current_day); + } + } else if last_check_day != current_day && current_day != 1 { + // Reset check day if it's a different day (and not Monday), so it can trigger again next week + board.board_hal.get_esp().set_last_corrosion_protection_check_day(-1); + } + } } info!("state of charg"); @@ -626,7 +673,7 @@ pub async fn do_secure_pump( let mut current_collector = vec![0_u16; steps_in_50ms]; let mut flow_collector = vec![0_i16; steps_in_50ms]; - let mut error = false; + let mut error = String::new(); let mut first_error = true; let mut pump_time_ms: u32 = 0; @@ -660,6 +707,7 @@ pub async fn do_secure_pump( && high_current && current_ma > STARTUP_ABORT_CURRENT_MA { + let err_msg = format!("OverCurrent startup: {}mA", current_ma); log( LogMessage::PumpOverCurrent, plant_id as u32 + 1, @@ -667,8 +715,9 @@ pub async fn do_secure_pump( plant_config.max_pump_current_ma.to_string().as_str(), step.to_string().as_str(), ); - error = true; + error = err_msg; } else if high_current && first_error { + let err_msg = format!("OverCurrent: {}mA", current_ma); log( LogMessage::PumpOverCurrent, plant_id as u32 + 1, @@ -679,13 +728,14 @@ pub async fn do_secure_pump( board.board_hal.general_fault(true).await; board.board_hal.fault(plant_id, true).await?; if !plant_config.ignore_current_error { - error = true; + error = err_msg; break; } first_error = false; } let low_current = current_ma < plant_config.min_pump_current_ma; if low_current && first_error { + let err_msg = format!("OpenLoop: {}mA", current_ma); log( LogMessage::PumpOpenLoopCurrent, plant_id as u32 + 1, @@ -696,7 +746,7 @@ pub async fn do_secure_pump( board.board_hal.general_fault(true).await; board.board_hal.fault(plant_id, true).await?; if !plant_config.ignore_current_error { - error = true; + error = err_msg; break; } first_error = false; @@ -705,6 +755,7 @@ pub async fn do_secure_pump( Err(err) => { if !plant_config.ignore_current_error { info!("Error getting pump current: {err}"); + let err_msg = format!("MissingSensor: {err:?}"); log( LogMessage::PumpMissingSensorCurrent, plant_id as u32, @@ -712,7 +763,7 @@ pub async fn do_secure_pump( "", "", ); - error = true; + error = err_msg; break; } else { error!("Error getting pump current: {err}"); @@ -908,6 +959,7 @@ async fn pump_info( median_current_ma: u16, max_current_ma: u16, min_current_ma: u16, + error: String, ) { let pump_info = PumpInfo { enabled: pump_active, @@ -915,6 +967,7 @@ async fn pump_info( median_current_ma, max_current_ma, min_current_ma, + error, }; let pump_topic = format!("/pump{}", plant_id + 1); diff --git a/Software/MainBoard/rust/src_webpack/src/api.ts b/Software/MainBoard/rust/src_webpack/src/api.ts index e5aa625..37e61fb 100644 --- a/Software/MainBoard/rust/src_webpack/src/api.ts +++ b/Software/MainBoard/rust/src_webpack/src/api.ts @@ -98,6 +98,7 @@ export enum BoardVersion { export interface BoardHardware { board: BoardVersion, battery: BatteryBoardVersion, + pump_corrosion_protection: boolean, } export interface PlantControllerConfig { diff --git a/Software/MainBoard/rust/src_webpack/src/hardware.html b/Software/MainBoard/rust/src_webpack/src/hardware.html index 0b2a24b..25e0e50 100644 --- a/Software/MainBoard/rust/src_webpack/src/hardware.html +++ b/Software/MainBoard/rust/src_webpack/src/hardware.html @@ -18,3 +18,7 @@ +
+
Pump corrosion protection (weekly)
+ +
diff --git a/Software/MainBoard/rust/src_webpack/src/hardware.ts b/Software/MainBoard/rust/src_webpack/src/hardware.ts index 86758f2..9123b76 100644 --- a/Software/MainBoard/rust/src_webpack/src/hardware.ts +++ b/Software/MainBoard/rust/src_webpack/src/hardware.ts @@ -4,6 +4,7 @@ import {BatteryBoardVersion, BoardHardware, BoardVersion} from "./api"; export class HardwareConfigView { private readonly hardware_board_value: HTMLSelectElement; private readonly hardware_battery_value: HTMLSelectElement; + private readonly hardware_pump_corrosion_protection: HTMLInputElement; constructor(controller:Controller){ (document.getElementById("hardwareview") as HTMLElement).innerHTML = require('./hardware.html') as string; @@ -29,17 +30,22 @@ export class HardwareConfigView { option.innerText = version.toString(); this.hardware_battery_value.appendChild(option); }) + + this.hardware_pump_corrosion_protection = document.getElementById("hardware_pump_corrosion_protection") as HTMLInputElement; + this.hardware_pump_corrosion_protection.onchange = controller.configChanged } setConfig(hardware: BoardHardware) { this.hardware_board_value.value = hardware.board.toString() this.hardware_battery_value.value = hardware.battery.toString() + this.hardware_pump_corrosion_protection.checked = hardware.pump_corrosion_protection } getConfig(): BoardHardware { return { board : BoardVersion[this.hardware_board_value.value as keyof typeof BoardVersion], battery : BatteryBoardVersion[this.hardware_battery_value.value as keyof typeof BatteryBoardVersion], + pump_corrosion_protection : this.hardware_pump_corrosion_protection.checked, } } }