use crate::bail; use crate::fat_error::{FatError, FatResult}; use crate::hal::esp::{hold_disable, hold_enable}; use crate::hal::rtc::RTCModuleInteraction; use crate::hal::v3_shift_register::ShiftRegister40; use crate::hal::water::TankSensor; use crate::hal::{BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT}; use crate::log::{log, LogMessage, LOG_ACCESS}; use crate::{ config::PlantControllerConfig, hal::{battery::BatteryInteraction, esp::Esp}, }; use alloc::boxed::Box; use alloc::format; use alloc::string::ToString; use async_trait::async_trait; use chrono::{DateTime, FixedOffset, Utc}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::Timer; use embedded_hal::digital::OutputPin as _; use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull}; use esp_hal::pcnt::channel::CtrlMode::Keep; use esp_hal::pcnt::channel::EdgeMode; use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment}; use esp_hal::pcnt::unit::Unit; use measurements::{Current, Voltage}; const PUMP8_BIT: usize = 0; const PUMP1_BIT: usize = 1; const PUMP2_BIT: usize = 2; const PUMP3_BIT: usize = 3; const PUMP4_BIT: usize = 4; const PUMP5_BIT: usize = 5; const PUMP6_BIT: usize = 6; const PUMP7_BIT: usize = 7; const MS_0: usize = 8; const MS_4: usize = 9; const MS_2: usize = 10; const MS_3: usize = 11; const MS_1: usize = 13; const SENSOR_ON: usize = 12; const SENSOR_A_1: u8 = 7; const SENSOR_A_2: u8 = 6; const SENSOR_A_3: u8 = 5; const SENSOR_A_4: u8 = 4; const SENSOR_A_5: u8 = 3; const SENSOR_A_6: u8 = 2; const SENSOR_A_7: u8 = 1; const SENSOR_A_8: u8 = 0; const SENSOR_B_1: u8 = 8; const SENSOR_B_2: u8 = 9; const SENSOR_B_3: u8 = 10; const SENSOR_B_4: u8 = 11; const SENSOR_B_5: u8 = 12; const SENSOR_B_6: u8 = 13; const SENSOR_B_7: u8 = 14; const SENSOR_B_8: u8 = 15; const CHARGING: usize = 14; const AWAKE: usize = 15; const FAULT_3: usize = 16; const FAULT_8: usize = 17; const FAULT_7: usize = 18; const FAULT_6: usize = 19; const FAULT_5: usize = 20; const FAULT_4: usize = 21; const FAULT_1: usize = 22; const FAULT_2: usize = 23; const REPEAT_MOIST_MEASURE: usize = 1; pub struct V3<'a> { config: PlantControllerConfig, battery_monitor: Box, rtc_module: Box, esp: Esp<'a>, shift_register: Mutex, Output<'a>, Output<'a>>>, _shift_register_enable_invert: Output<'a>, tank_sensor: TankSensor<'a>, solar_is_day: Input<'a>, light: Output<'a>, main_pump: Output<'a>, general_fault: Output<'a>, pub signal_counter: Unit<'static, 0>, } pub(crate) fn create_v3( peripherals: FreePeripherals<'static>, esp: Esp<'static>, config: PlantControllerConfig, battery_monitor: Box, rtc_module: Box, ) -> Result + Send + 'static>, FatError> { log::info!("Start v3"); let clock = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default()); let latch = Output::new(peripherals.gpio3, Level::Low, OutputConfig::default()); let data = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default()); let shift_register = ShiftRegister40::new(clock, latch, data); //disable all for mut pin in shift_register.decompose() { let _ = pin.set_low(); } // Set always-on status bits let _ = shift_register.decompose()[AWAKE].set_high(); let _ = shift_register.decompose()[CHARGING].set_high(); // Multiplexer defaults: ms0..ms3 low, ms4 high (disabled) let _ = shift_register.decompose()[MS_0].set_low(); let _ = shift_register.decompose()[MS_1].set_low(); let _ = shift_register.decompose()[MS_2].set_low(); let _ = shift_register.decompose()[MS_3].set_low(); let _ = shift_register.decompose()[MS_4].set_high(); let one_wire_pin = Flex::new(peripherals.gpio18); let tank_power_pin = Output::new(peripherals.gpio11, Level::Low, OutputConfig::default()); let flow_sensor_pin = Input::new( peripherals.gpio4, InputConfig::default().with_pull(Pull::Up), ); let tank_sensor = TankSensor::create( one_wire_pin, peripherals.adc1, peripherals.gpio5, tank_power_pin, flow_sensor_pin, peripherals.pcnt1, )?; let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default()); let light = Output::new(peripherals.gpio10, Level::Low, OutputConfig::default()); let mut main_pump = Output::new(peripherals.gpio2, Level::Low, OutputConfig::default()); main_pump.set_low(); let mut general_fault = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default()); general_fault.set_low(); hold_disable(21); let mut shift_register_enable_invert = Output::new(peripherals.gpio21, Level::Low, OutputConfig::default()); shift_register_enable_invert.set_low(); hold_enable(21); let signal_counter = peripherals.pcnt0; signal_counter.set_low_limit(None)?; signal_counter.set_high_limit(Some(i16::MAX))?; let ch0 = &signal_counter.channel0; let edge_pin = Input::new(peripherals.gpio22, InputConfig::default()); ch0.set_edge_signal(edge_pin.peripheral_input()); ch0.set_input_mode(Hold, Increment); ch0.set_ctrl_mode(Keep, Keep); signal_counter.listen(); Ok(Box::new(V3 { config, battery_monitor, rtc_module, esp, shift_register: Mutex::new(shift_register), _shift_register_enable_invert: shift_register_enable_invert, tank_sensor, solar_is_day, light, main_pump, general_fault, signal_counter, })) } #[async_trait] impl<'a> BoardInteraction<'a> for V3<'a> { fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> { Ok(&mut self.tank_sensor) } fn get_esp(&mut self) -> &mut Esp<'a> { &mut self.esp } fn get_config(&mut self) -> &PlantControllerConfig { &self.config } fn get_battery_monitor(&mut self) -> &mut Box { &mut self.battery_monitor } fn get_rtc_module(&mut self) -> &mut Box { &mut self.rtc_module } async fn get_time(&mut self) -> DateTime { self.esp.get_time() } async fn set_time(&mut self, time: &DateTime) -> FatResult<()> { self.rtc_module.set_rtc_time(&time.to_utc()).await?; self.esp.set_time(time.to_utc()); Ok(()) } async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError> { let shift_register = self.shift_register.lock().await; if charging { let _ = shift_register.decompose()[CHARGING].set_high(); } else { let _ = shift_register.decompose()[CHARGING].set_low(); } Ok(()) } async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { let _ = self.shift_register.lock().await.decompose()[AWAKE].set_low(); self.esp.deep_sleep(duration_in_ms) } fn is_day(&self) -> bool { self.solar_is_day.is_high() } async fn light(&mut self, enable: bool) -> Result<(), FatError> { hold_disable(10); if enable { self.light.set_high(); } else { self.light.set_low(); } hold_enable(10); Ok(()) } async fn pump(&mut self, plant: usize, enable: bool) -> Result<(), FatError> { if enable { self.main_pump.set_high(); } let index = match plant { 0 => PUMP1_BIT, 1 => PUMP2_BIT, 2 => PUMP3_BIT, 3 => PUMP4_BIT, 4 => PUMP5_BIT, 5 => PUMP6_BIT, 6 => PUMP7_BIT, 7 => PUMP8_BIT, _ => bail!("Invalid pump {plant}"), }; let shift_register = self.shift_register.lock().await; if enable { let _ = shift_register.decompose()[index].set_high(); } else { let _ = shift_register.decompose()[index].set_low(); } if !enable { self.main_pump.set_low(); } Ok(()) } async fn pump_current(&mut self, _plant: usize) -> Result { bail!("Not implemented in v3") } async fn fault(&mut self, plant: usize, enable: bool) -> Result<(), FatError> { let index = match plant { 0 => FAULT_1, 1 => FAULT_2, 2 => FAULT_3, 3 => FAULT_4, 4 => FAULT_5, 5 => FAULT_6, 6 => FAULT_7, 7 => FAULT_8, _ => panic!("Invalid plant id {}", plant), }; let shift_register = self.shift_register.lock().await; if enable { let _ = shift_register.decompose()[index].set_high(); } else { let _ = shift_register.decompose()[index].set_low(); } Ok(()) } async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { let mut results = [0_f32; REPEAT_MOIST_MEASURE]; for repeat in 0..REPEAT_MOIST_MEASURE { self.signal_counter.pause(); self.signal_counter.clear(); //Disable all { let shift_register = self.shift_register.lock().await; shift_register.decompose()[MS_4].set_high()?; } let sensor_channel = match sensor { Sensor::A => match plant { 0 => SENSOR_A_1, 1 => SENSOR_A_2, 2 => SENSOR_A_3, 3 => SENSOR_A_4, 4 => SENSOR_A_5, 5 => SENSOR_A_6, 6 => SENSOR_A_7, 7 => SENSOR_A_8, _ => bail!("Invalid plant id {}", plant), }, Sensor::B => match plant { 0 => SENSOR_B_1, 1 => SENSOR_B_2, 2 => SENSOR_B_3, 3 => SENSOR_B_4, 4 => SENSOR_B_5, 5 => SENSOR_B_6, 6 => SENSOR_B_7, 7 => SENSOR_B_8, _ => bail!("Invalid plant id {}", plant), }, }; let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; { let shift_register = self.shift_register.lock().await; let pin_0 = &mut shift_register.decompose()[MS_0]; let pin_1 = &mut shift_register.decompose()[MS_1]; let pin_2 = &mut shift_register.decompose()[MS_2]; let pin_3 = &mut shift_register.decompose()[MS_3]; if is_bit_set(0) { pin_0.set_high()?; } else { pin_0.set_low()?; } if is_bit_set(1) { pin_1.set_high()?; } else { pin_1.set_low()?; } if is_bit_set(2) { pin_2.set_high()?; } else { pin_2.set_low()?; } if is_bit_set(3) { pin_3.set_high()?; } else { pin_3.set_low()?; } shift_register.decompose()[MS_4].set_low()?; shift_register.decompose()[SENSOR_ON].set_high()?; } let measurement = 100; //how long to measure and then extrapolate to hz let factor = 1000f32 / measurement as f32; //scale raw cound by this number to get hz //give some time to stabilize Timer::after_millis(10).await; self.signal_counter.resume(); Timer::after_millis(measurement).await; self.signal_counter.pause(); { let shift_register = self.shift_register.lock().await; shift_register.decompose()[MS_4].set_high()?; shift_register.decompose()[SENSOR_ON].set_low()?; } Timer::after_millis(10).await; let unscaled = self.signal_counter.value(); let hz = unscaled as f32 * factor; log( LogMessage::RawMeasure, unscaled as u32, hz as u32, &plant.to_string(), &format!("{sensor:?}"), ); results[repeat] = hz; } results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord let mid = results.len() / 2; let median = results[mid]; Ok(median) } async fn general_fault(&mut self, enable: bool) { hold_disable(6); if enable { self.general_fault.set_high(); } else { self.general_fault.set_low(); } hold_enable(6); } async fn test(&mut self) -> Result<(), FatError> { self.general_fault(true).await; Timer::after_millis(100).await; self.general_fault(false).await; Timer::after_millis(100).await; self.light(true).await?; Timer::after_millis(500).await; self.light(false).await?; Timer::after_millis(500).await; for i in 0..PLANT_COUNT { self.fault(i, true).await?; Timer::after_millis(500).await; self.fault(i, false).await?; Timer::after_millis(500).await; } for i in 0..PLANT_COUNT { self.pump(i, true).await?; Timer::after_millis(100).await; self.pump(i, false).await?; Timer::after_millis(100).await; } for plant in 0..PLANT_COUNT { let a = self.measure_moisture_hz(plant, Sensor::A).await; let b = self.measure_moisture_hz(plant, Sensor::B).await; let aa = match a { Ok(a) => a as u32, Err(_) => u32::MAX, }; let bb = match b { Ok(b) => b as u32, Err(_) => u32::MAX, }; log(LogMessage::TestSensor, aa, bb, &plant.to_string(), ""); } Timer::after_millis(10).await; Ok(()) } fn set_config(&mut self, config: PlantControllerConfig) { self.config = config; } async fn get_mptt_voltage(&mut self) -> Result { bail!("Not implemented in v3") } async fn get_mptt_current(&mut self) -> Result { bail!("Not implemented in v3") } }