use crate::hal::rtc::RTCModuleInteraction; use crate::hal::water::TankSensor; use crate::hal::{ deep_sleep, BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT, }; use crate::log::{log, LogMessage}; use crate::{ config::PlantControllerConfig, hal::{battery::BatteryInteraction, esp::Esp}, }; use anyhow::{bail, Ok, Result}; use embedded_hal::digital::OutputPin; use measurements::{Current, Voltage}; use plant_ctrl2::sipo::ShiftRegister40; use core::result::Result::Ok as OkStd; use alloc::string::ToString; use alloc::boxed::Box; use esp_hall::gpio::Pull; 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: ShiftRegister40< PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, >, _shift_register_enable_invert: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Output>, tank_sensor: TankSensor<'a>, solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, signal_counter: PcntDriver<'a>, } pub(crate) fn create_v3( peripherals: FreePeripherals, esp: Esp<'static>, config: PlantControllerConfig, battery_monitor: Box, rtc_module: Box, ) -> Result + Send>> { log::info!("Start v3"); let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?; clock.set_pull(Pull::Floating)?; let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?; latch.set_pull(Pull::Floating)?; let mut data = PinDriver::input_output(peripherals.gpio23.downgrade())?; data.set_pull(Pull::Floating)?; let shift_register = ShiftRegister40::new(clock, latch, data); //disable all for mut pin in shift_register.decompose() { pin.set_low()?; } let awake = &mut shift_register.decompose()[AWAKE]; awake.set_high()?; let charging = &mut shift_register.decompose()[CHARGING]; charging.set_high()?; let ms0 = &mut shift_register.decompose()[MS_0]; ms0.set_low()?; let ms1 = &mut shift_register.decompose()[MS_1]; ms1.set_low()?; let ms2 = &mut shift_register.decompose()[MS_2]; ms2.set_low()?; let ms3 = &mut shift_register.decompose()[MS_3]; ms3.set_low()?; let ms4 = &mut shift_register.decompose()[MS_4]; ms4.set_high()?; let one_wire_pin = peripherals.gpio18.downgrade(); let tank_power_pin = peripherals.gpio11.downgrade(); let flow_sensor_pin = peripherals.gpio4.downgrade(); let tank_sensor = TankSensor::create( one_wire_pin, peripherals.adc1, peripherals.gpio5, tank_power_pin, flow_sensor_pin, peripherals.pcnt1, )?; let mut signal_counter = PcntDriver::new( peripherals.pcnt0, Some(peripherals.gpio22), Option::::None, Option::::None, Option::::None, )?; signal_counter.channel_config( PcntChannel::Channel0, PinIndex::Pin0, PinIndex::Pin1, &PcntChannelConfig { lctrl_mode: PcntControlMode::Keep, hctrl_mode: PcntControlMode::Keep, pos_mode: PcntCountMode::Increment, neg_mode: PcntCountMode::Hold, counter_h_lim: i16::MAX, counter_l_lim: 0, }, )?; let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?; solar_is_day.set_pull(Pull::Floating)?; let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?; light.set_pull(Pull::Floating)?; let mut main_pump = PinDriver::input_output(peripherals.gpio2.downgrade())?; main_pump.set_pull(Pull::Floating)?; main_pump.set_low()?; let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?; general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; let mut shift_register_enable_invert = PinDriver::output(peripherals.gpio21.downgrade())?; unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) }; shift_register_enable_invert.set_low()?; unsafe { gpio_hold_en(shift_register_enable_invert.pin()) }; Ok(Box::new(V3 { config, battery_monitor, rtc_module, esp, shift_register, _shift_register_enable_invert: shift_register_enable_invert, tank_sensor, solar_is_day, light, main_pump, general_fault, signal_counter, })) } impl<'a> BoardInteraction<'a> for V3<'a> { fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> { Some(&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 } fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?) } fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { let _ = self.shift_register.decompose()[AWAKE].set_low(); deep_sleep(duration_in_ms) } fn is_day(&self) -> bool { self.solar_is_day.get_level().into() } fn light(&mut self, enable: bool) -> Result<()> { unsafe { gpio_hold_dis(self.light.pin()) }; self.light.set_state(enable.into())?; unsafe { gpio_hold_en(self.light.pin()) }; Ok(()) } fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { 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}",), }; self.shift_register.decompose()[index].set_state(enable.into())?; if !enable { self.main_pump.set_low()?; } 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, 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), }; self.shift_register.decompose()[index].set_state(enable.into())?; Ok(()) } 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.counter_pause()?; self.signal_counter.counter_clear()?; //Disable all self.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 pin_0 = &mut self.shift_register.decompose()[MS_0]; let pin_1 = &mut self.shift_register.decompose()[MS_1]; let pin_2 = &mut self.shift_register.decompose()[MS_2]; let pin_3 = &mut self.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()?; } self.shift_register.decompose()[MS_4].set_low()?; self.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 self.esp.delay.delay_ms(10); self.signal_counter.counter_resume()?; self.esp.delay.delay_ms(measurement); self.signal_counter.counter_pause()?; self.shift_register.decompose()[MS_4].set_high()?; self.shift_register.decompose()[SENSOR_ON].set_low()?; self.esp.delay.delay_ms(10); let unscaled = self.signal_counter.get_counter_value()? as i32; 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) } fn general_fault(&mut self, enable: bool) { unsafe { gpio_hold_dis(self.general_fault.pin()) }; self.general_fault.set_state(enable.into()).unwrap(); unsafe { gpio_hold_en(self.general_fault.pin()) }; } fn test(&mut self) -> Result<()> { self.general_fault(true); self.esp.delay.delay_ms(100); self.general_fault(false); self.esp.delay.delay_ms(100); self.light(true)?; self.esp.delay.delay_ms(500); self.light(false)?; self.esp.delay.delay_ms(500); for i in 0..PLANT_COUNT { self.fault(i, true)?; self.esp.delay.delay_ms(500); self.fault(i, false)?; self.esp.delay.delay_ms(500); } for i in 0..PLANT_COUNT { self.pump(i, true)?; self.esp.delay.delay_ms(100); self.pump(i, false)?; self.esp.delay.delay_ms(100); } for plant in 0..PLANT_COUNT { let a = self.measure_moisture_hz(plant, Sensor::A); let b = self.measure_moisture_hz(plant, Sensor::B); let aa = match a { OkStd(a) => a as u32, Err(_) => u32::MAX, }; let bb = match b { OkStd(b) => b as u32, Err(_) => u32::MAX, }; log(LogMessage::TestSensor, aa, bb, &plant.to_string(), ""); } self.esp.delay.delay_ms(10); Ok(()) } fn set_config(&mut self, config: PlantControllerConfig) -> Result<()> { self.config = config; self.esp.save_config(&self.config)?; anyhow::Ok(()) } fn get_mptt_voltage(&mut self) -> Result { //assuming module to work, these are the hardware set values if self.is_day() { Ok(Voltage::from_volts(15_f64)) } else { Ok(Voltage::from_volts(0_f64)) } } fn get_mptt_current(&mut self) -> Result { bail!("Board does not have current sensor") } }