use crate::config::PlantControllerConfig; use crate::hal::battery::BatteryInteraction; use crate::hal::esp::Esp; use crate::hal::rtc::RTCModuleInteraction; use crate::hal::water::TankSensor; use crate::hal::{ deep_sleep, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, REPEAT_MOIST_MEASURE, }; use crate::log::{log, LogMessage}; use anyhow::bail; 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; use esp_idf_hal::pcnt::{ PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, }; 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::SyncIna219; use measurements::{Current, Resistance, Voltage}; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; use std::result::Result::Ok as OkStd; const MS0: u8 = 1_u8; const MS1: u8 = 0_u8; const MS2: u8 = 3_u8; const MS3: u8 = 4_u8; const MS4: u8 = 2_u8; const SENSOR_ON: u8 = 5_u8; pub enum Charger<'a> { SolarMpptV1 { mppt_ina: SyncIna219>, UnCalibrated>, solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, }, ErrorInit {}, } impl Charger<'_> { pub(crate) fn power_save(&mut self) { match self { Charger::SolarMpptV1 { mppt_ina, .. } => { let _ = mppt_ina .set_configuration(Configuration { reset: Default::default(), bus_voltage_range: Default::default(), shunt_voltage_range: Default::default(), bus_resolution: Default::default(), shunt_resolution: Default::default(), operating_mode: OperatingMode::PowerDown, }) .map_err(|e| { println!( "Error setting ina mppt configuration during deep sleep preparation{:?}", e ); }); } _ => {} } } fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> { match self { Self::SolarMpptV1 { charge_indicator, .. } => { charge_indicator.set_state(charging.into())?; } _ => {} } Ok(()) } fn is_day(&self) -> bool { match self { Charger::SolarMpptV1 { solar_is_day, .. } => solar_is_day.get_level().into(), _ => true, } } fn get_mptt_voltage(&mut self) -> anyhow::Result { let voltage = match self { Charger::SolarMpptV1 { mppt_ina, .. } => mppt_ina .bus_voltage() .map(|v| Voltage::from_millivolts(v.voltage_mv() as f64))?, _ => { bail!("hardware error during init") } }; Ok(voltage) } fn get_mptt_current(&mut self) -> anyhow::Result { let current = match self { Charger::SolarMpptV1 { mppt_ina, .. } => mppt_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) })?, _ => { bail!("hardware error during init") } }; Ok(current) } } pub struct V4<'a> { esp: Esp<'a>, tank_sensor: TankSensor<'a>, charger: Charger<'a>, rtc_module: Box, battery_monitor: Box, config: PlantControllerConfig, signal_counter: PcntDriver<'a>, awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, 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>, } pub(crate) fn create_v4( peripherals: FreePeripherals, esp: Esp<'static>, config: PlantControllerConfig, battery_monitor: Box, rtc_module: Box, ) -> anyhow::Result + Send + 'static>> { let mut awake = PinDriver::output(peripherals.gpio21.downgrade())?; awake.set_high()?; let mut general_fault = PinDriver::input_output(peripherals.gpio23.downgrade())?; general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; let mut extra1 = PinDriver::output(peripherals.gpio6.downgrade())?; extra1.set_low()?; let mut extra2 = PinDriver::output(peripherals.gpio15.downgrade())?; extra2.set_low()?; 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 charge_indicator = PinDriver::input_output(peripherals.gpio3.downgrade())?; charge_indicator.set_pull(Pull::Floating)?; charge_indicator.set_low()?; let mut pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32); for pin in 0..8 { let _ = pump_expander.pin_into_output(GPIOBank::Bank0, pin); let _ = pump_expander.pin_into_output(GPIOBank::Bank1, pin); let _ = pump_expander.pin_set_low(GPIOBank::Bank0, pin); let _ = pump_expander.pin_set_low(GPIOBank::Bank1, pin); } let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34); for pin in 0..8 { let _ = sensor_expander.pin_into_output(GPIOBank::Bank0, pin); let _ = sensor_expander.pin_into_output(GPIOBank::Bank1, pin); let _ = sensor_expander.pin_set_low(GPIOBank::Bank0, pin); let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin); } let mppt_ina = SyncIna219::new( MutexDevice::new(&I2C_DRIVER), Address::from_pins(Pin::Vcc, Pin::Gnd), ); let charger = match mppt_ina { Ok(mut mppt_ina) => { mppt_ina.set_configuration(Configuration { reset: Default::default(), bus_voltage_range: Default::default(), shunt_voltage_range: Default::default(), bus_resolution: Default::default(), shunt_resolution: ina219::configuration::Resolution::Avg128, operating_mode: Default::default(), })?; Charger::SolarMpptV1 { mppt_ina, solar_is_day, charge_indicator, } } 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, awake, tank_sensor, signal_counter, light, general_fault, pump_ina, pump_expander, sensor_expander, config, battery_monitor, charger, extra1, extra2, }; Ok(Box::new(v)) } impl<'a> BoardInteraction<'a> for V4<'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) -> anyhow::Result<()> { self.charger.set_charge_indicator(charging) } fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { self.awake.set_low().unwrap(); self.charger.power_save(); deep_sleep(duration_in_ms); } fn is_day(&self) -> bool { self.charger.is_day() } fn light(&mut self, enable: bool) -> anyhow::Result<()> { unsafe { gpio_hold_dis(self.light.pin()) }; self.light.set_state(enable.into())?; unsafe { gpio_hold_en(self.light.pin()) }; anyhow::Ok(()) } fn pump(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> { if enable { self.pump_expander .pin_set_high(GPIOBank::Bank0, plant.try_into()?)?; } else { self.pump_expander .pin_set_low(GPIOBank::Bank0, plant.try_into()?)?; } 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 .pin_set_high(GPIOBank::Bank1, plant.try_into()?)? } else { self.pump_expander .pin_set_low(GPIOBank::Bank1, plant.try_into()?)? } anyhow::Ok(()) } fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::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.sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; let sensor_channel = match sensor { Sensor::A => plant as u32, Sensor::B => (15 - plant) as u32, }; let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 }; if is_bit_set(0) { self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS0)?; } else { self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?; } if is_bit_set(1) { self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS1)?; } else { self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?; } if is_bit_set(2) { self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS2)?; } else { self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?; } if is_bit_set(3) { self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS3)?; } else { self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?; } self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?; self.sensor_expander .pin_set_high(GPIOBank::Bank0, SENSOR_ON)?; let delay = Delay::new_default(); let measurement = 100; // TODO what is this scaling factor? what is its purpose? let factor = 1000f32 / measurement as f32; //give some time to stabilize delay.delay_ms(10); self.signal_counter.counter_resume()?; delay.delay_ms(measurement); self.signal_counter.counter_pause()?; self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; self.sensor_expander .pin_set_low(GPIOBank::Bank0, SENSOR_ON)?; self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?; self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?; self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?; self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?; 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]; anyhow::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) -> anyhow::Result<()> { self.general_fault(true); self.esp.delay.delay_ms(100); self.general_fault(false); self.esp.delay.delay_ms(500); 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); anyhow::Ok(()) } fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { self.config = config; self.esp.save_config(&self.config)?; anyhow::Ok(()) } fn get_mptt_voltage(&mut self) -> anyhow::Result { self.charger.get_mptt_voltage() } fn get_mptt_current(&mut self) -> anyhow::Result { self.charger.get_mptt_current() } }