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::{BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, TIME_ACCESS}; use alloc::boxed::Box; use alloc::string::ToString; use async_trait::async_trait; use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_time::Timer; use esp_hal::analog::adc::{Adc, AdcConfig, Attenuation}; use esp_hal::{twai, Blocking}; //use embedded_hal_bus::i2c::MutexDevice; use crate::bail; use crate::hal::v4_sensor::{SensorImpl, SensorInteraction}; use crate::fat_error::{FatError, FatResult}; use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull}; use esp_hal::i2c::master::I2c; use esp_hal::pcnt::Pcnt; use esp_hal::twai::{EspTwaiFrame, StandardId, TwaiMode}; use esp_println::println; use ina219::address::{Address, Pin}; use ina219::calibration::UnCalibrated; use ina219::configuration::{Configuration, OperatingMode, Resolution}; use ina219::SyncIna219; use measurements::Resistance; use measurements::{Current, Voltage}; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; use crate::log::{LogMessage, LOG_ACCESS}; // Minimal esp-idf equivalent for gpio_hold on esp32c6 via ROM functions extern "C" { fn gpio_pad_hold(gpio_num: u32); fn gpio_pad_unhold(gpio_num: u32); } #[inline(always)] fn hold_enable(gpio_num: u8) { unsafe { gpio_pad_hold(gpio_num as u32) } } #[inline(always)] fn hold_disable(gpio_num: u8) { unsafe { gpio_pad_unhold(gpio_num as u32) } } const MPPT_CURRENT_SHUNT_OHMS: f64 = 0.05_f64; const TWAI_BAUDRATE: twai::BaudRate = twai::BaudRate::B125K; pub enum Charger<'a> { SolarMpptV1 { mppt_ina: SyncIna219< I2cDevice<'a, CriticalSectionRawMutex, I2c<'static, Blocking>>, UnCalibrated, >, solar_is_day: Input<'a>, charge_indicator: Output<'a>, }, ErrorInit {}, } impl<'a> Charger<'a> { pub(crate) fn get_mppt_current(&mut self) -> FatResult { match self { Charger::SolarMpptV1 { mppt_ina, .. } => { let v = mppt_ina.shunt_voltage()?; let shunt_voltage = Voltage::from_microvolts(v.shunt_voltage_uv().abs() as f64); let shut_value = Resistance::from_ohms(MPPT_CURRENT_SHUNT_OHMS); let current = shunt_voltage.as_volts() / shut_value.as_ohms(); Ok(Current::from_amperes(current)) } Charger::ErrorInit { .. } => { bail!("hardware error during init"); } } } pub(crate) fn get_mptt_voltage(&mut self) -> FatResult { match self { Charger::SolarMpptV1 { mppt_ina, .. } => { let v = mppt_ina.bus_voltage()?; Ok(Voltage::from_millivolts(v.voltage_mv() as f64)) } Charger::ErrorInit { .. } => { bail!("hardware error during init"); } } } } 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| { log::info!( "Error setting ina mppt configuration during deep sleep preparation{:?}", e ); }); } _ => {} } } fn set_charge_indicator(&mut self, charging: bool) -> FatResult<()> { match self { Self::SolarMpptV1 { charge_indicator, .. } => { charge_indicator.set_level(charging.into()); } _ => {} } Ok(()) } fn is_day(&self) -> bool { match self { Charger::SolarMpptV1 { solar_is_day, .. } => solar_is_day.is_high(), _ => true, } } } pub struct V4<'a> { esp: Esp<'a>, tank_sensor: TankSensor<'a>, charger: Charger<'a>, rtc_module: Box, battery_monitor: Box, config: PlantControllerConfig, awake: Output<'a>, light: Output<'a>, general_fault: Output<'a>, pump_expander: Pca9535Immediate>>, pump_ina: Option< SyncIna219>, UnCalibrated>, >, sensor: SensorImpl, extra1: Output<'a>, extra2: Output<'a>, } struct InputOutput<'a> { pin: Flex<'a>, } pub(crate) async fn create_v4( peripherals: FreePeripherals<'static>, esp: Esp<'static>, config: PlantControllerConfig, battery_monitor: Box, rtc_module: Box, ) -> Result + Send + 'static>, FatError> { log::info!("Start v4"); let mut awake = Output::new(peripherals.gpio21, Level::High, OutputConfig::default()); awake.set_high(); let mut general_fault = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default()); general_fault.set_low(); let extra1 = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default()); let extra2 = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default()); 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 sensor_expander_device = I2cDevice::new(I2C_DRIVER.get().await); let mut sensor_expander = Pca9535Immediate::new(sensor_expander_device, 34); let sensor = match sensor_expander.pin_into_output(GPIOBank::Bank0, 0) { Ok(_) => { log::info!("SensorExpander answered"); //pulse counter version // 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, // }, // )?; 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); } SensorImpl::PulseCounter { // signal_counter, sensor_expander, } } Err(_) => { log::info!("Can bus mode "); let twai_config = twai::TwaiConfiguration::new( peripherals.twai, peripherals.gpio0, peripherals.gpio2, TWAI_BAUDRATE, TwaiMode::Normal, ); let mut twai = twai_config.start(); let frame = EspTwaiFrame::new(StandardId::ZERO, &[1, 2, 3]).unwrap(); twai.transmit(&frame).unwrap(); // let frame = twai.receive().unwrap(); println!("Received a frame: {frame:?}"); //can bus version SensorImpl::CanBus { twai } } }; let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default()); let light = Output::new(peripherals.gpio10, Level::Low, Default::default()); let charge_indicator = Output::new(peripherals.gpio3, Level::Low, Default::default()); let pump_device = I2cDevice::new(I2C_DRIVER.get().await); let mut pump_expander = Pca9535Immediate::new(pump_device, 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 mppt_current = I2cDevice::new(I2C_DRIVER.get().await); let mppt_ina = match SyncIna219::new(mppt_current, Address::from_pins(Pin::Vcc, Pin::Gnd)) { Ok(mut ina) => { // Prefer higher averaging for more stable readings let _ = ina.set_configuration(Configuration { reset: Default::default(), bus_voltage_range: Default::default(), shunt_voltage_range: Default::default(), bus_resolution: Default::default(), shunt_resolution: Resolution::Avg128, operating_mode: Default::default(), }); Some(ina) } Err(err) => { log::info!("Error creating mppt ina: {:?}", err); None } }; let pump_current_dev = I2cDevice::new(I2C_DRIVER.get().await); let pump_ina = match SyncIna219::new(pump_current_dev, Address::from_pins(Pin::Gnd, Pin::Sda)) { Ok(ina) => Some(ina), Err(err) => { log::info!("Error creating pump ina: {:?}", err); None } }; let charger = match mppt_ina { Some(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, } } None => Charger::ErrorInit {}, }; let v = V4 { rtc_module, esp, awake, tank_sensor, light, general_fault, //pump_ina, pump_expander, config, battery_monitor, pump_ina, charger, extra1, extra2, sensor, }; Ok(Box::new(v)) } #[async_trait] impl<'a> BoardInteraction<'a> for V4<'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 } fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError> { self.charger.set_charge_indicator(charging) } async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { self.awake.set_low(); //self.charger.power_save(); let rtc = TIME_ACCESS.get().await.lock().await; self.esp.deep_sleep(duration_in_ms, rtc); } fn is_day(&self) -> bool { self.charger.is_day() } async fn light(&mut self, enable: bool) -> Result<(), FatError> { hold_disable(10); self.light.set_level(enable.into()); hold_enable(10); Ok(()) } async fn pump(&mut self, plant: usize, enable: bool) -> FatResult<()> { if enable { self.pump_expander .pin_set_high(GPIOBank::Bank0, plant as u8)?; } else { self.pump_expander .pin_set_low(GPIOBank::Bank0, plant as u8)?; } Ok(()) } async fn pump_current(&mut self, _plant: usize) -> Result { // sensor 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_err(|e| FatError::String { error: alloc::format!("{:?}", e), }) .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) } } } async fn fault(&mut self, plant: usize, enable: bool) -> FatResult<()> { if enable { self.pump_expander .pin_set_high(GPIOBank::Bank1, plant as u8)?; } else { self.pump_expander .pin_set_low(GPIOBank::Bank1, plant as u8)?; } Ok(()) } async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { self.sensor.measure_moisture_hz(plant, sensor).await } async fn general_fault(&mut self, enable: bool) { hold_disable(23); self.general_fault.set_level(enable.into()); hold_enable(23); } 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(500).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_ACCESS.lock().await.log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "").await; } Timer::after_millis(10).await; Ok(()) } fn set_config(&mut self, config: PlantControllerConfig) { self.config = config; } async fn get_mptt_voltage(&mut self) -> Result { self.charger.get_mptt_voltage() } async fn get_mptt_current(&mut self) -> Result { self.charger.get_mppt_current() } }