use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver}; use ds323x::{DateTimeAccess, Ds323x}; use eeprom24x::{Eeprom24x, SlaveAddr}; use embedded_hal_bus::i2c::MutexDevice; use embedded_svc::wifi::{ AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration, }; use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; use esp_idf_hal::adc::{attenuation, Resolution}; use esp_idf_hal::i2c::{APBTickType, I2cConfig, I2cDriver, I2cError}; use esp_idf_hal::units::FromValueType; use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::ipv4::IpInfo; use esp_idf_svc::mqtt::client::QoS::AtLeastOnce; use esp_idf_svc::mqtt::client::QoS::ExactlyOnce; use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration}; use esp_idf_svc::nvs::EspDefaultNvsPartition; use esp_idf_svc::wifi::config::{ScanConfig, ScanType}; use esp_idf_svc::wifi::EspWifi; use measurements::Temperature; use once_cell::sync::Lazy; use plant_ctrl2::sipo::ShiftRegister40; use anyhow::{anyhow, Context}; use anyhow::{bail, Ok, Result}; use serde::Serialize; use std::ffi::CString; use std::fs::{self, File}; use std::path::Path; use chrono::{DateTime, Utc}; use ds18b20::Ds18b20; use std::result::Result::Ok as OkStd; use std::str::FromStr; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use std::time::Duration; use embedded_hal::digital::OutputPin; use esp_idf_hal::delay::Delay; use esp_idf_hal::gpio::{AnyInputPin, Gpio18, Gpio5, IOPin, InputOutput, Level, PinDriver, Pull}; use esp_idf_hal::pcnt::{ PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, }; use esp_idf_hal::prelude::Peripherals; use esp_idf_hal::reset::ResetReason; use esp_idf_svc::sntp::{self, SyncStatus}; use esp_idf_svc::systime::EspSystemTime; use esp_idf_sys::{esp, esp_spiffs_check, gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; use one_wire_bus::OneWire; use crate::config::{self, PlantControllerConfig}; use crate::{plant_hal, to_string, STAY_ALIVE}; //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; const REPEAT_MOIST_MEASURE: usize = 1; const SPIFFS_PARTITION_NAME: &str = "storage"; const CONFIG_FILE: &str = "/spiffs/config.cfg"; const BASE_PATH: &str = "/spiffs"; const TANK_MULTI_SAMPLE: usize = 11; 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 SENSOR_ON: usize = 12; const MS_1: usize = 13; //unused 14 //unused 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 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; #[link_section = ".rtc.data"] static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; #[link_section = ".rtc.data"] static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT]; #[link_section = ".rtc.data"] static mut LOW_VOLTAGE_DETECTED: bool = false; #[link_section = ".rtc.data"] static mut RESTART_TO_CONF: bool = false; pub struct FileSystemSizeInfo { pub total_size: usize, pub used_size: usize, pub free_size: usize, } #[derive(strum::Display)] pub enum StartMode { AP, Normal, } #[derive(Debug, PartialEq)] pub enum Sensor { A, B, } pub struct PlantHal {} pub struct PlantCtrlBoard<'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_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>, solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, signal_counter: PcntDriver<'a>, light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, pub wifi_driver: EspWifi<'a>, one_wire_bus: OneWire>, mqtt_client: Option>, battery_driver: Bq34z100g1Driver>, Delay>, rtc: Ds323x>>, ds323x::ic::DS3231>, eeprom: Eeprom24x< MutexDevice<'a, I2cDriver<'a>>, eeprom24x::page_size::B32, eeprom24x::addr_size::TwoBytes, eeprom24x::unique_serial::No, >, } #[derive(Serialize, Debug)] pub struct FileInfo { filename: String, size: usize, } #[derive(Serialize, Debug)] pub struct FileList { files: Vec, file_system_corrupt: Option, iter_error: Option, } #[derive(Serialize)] pub struct BatteryState { voltage_milli_volt: String, current_milli_ampere: String, cycle_count: String, design_milli_ampere: String, remaining_milli_ampere: String, state_of_charge: String, state_of_health: String, temperature: String, } impl PlantCtrlBoard<'_> { pub fn get_battery_state(&mut self) -> BatteryState { let bat = BatteryState { voltage_milli_volt: to_string(self.voltage_milli_volt()), current_milli_ampere: to_string(self.average_current_milli_ampere()), cycle_count: to_string(self.cycle_count()), design_milli_ampere: to_string(self.design_milli_ampere_hour()), remaining_milli_ampere: to_string(self.remaining_milli_ampere_hour()), state_of_charge: to_string(self.state_charge_percent()), state_of_health: to_string(self.state_health_percent()), temperature: to_string(self.bat_temperature()), }; return bat; } pub fn list_files(&self, filename: &str) -> FileList { let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap(); let error = unsafe { esp! { esp_spiffs_check(storage.as_ptr()) } }; let mut file_system_corrupt = match error { OkStd(_) => None, Err(err) => { println!("Corrupt spiffs {err:?}"); Some(format!("{err:?}")) } }; let mut iter_error = None; let mut result = Vec::new(); println!("Filename {filename}"); let filepath = Path::new(BASE_PATH); let read_dir = fs::read_dir(filepath); match read_dir { OkStd(read_dir) => { for item in read_dir { println!("start loop"); match item { OkStd(file) => { let f = FileInfo { filename: file.file_name().into_string().unwrap(), size: file .metadata() .and_then(|it| core::result::Result::Ok(it.len())) .unwrap_or_default() as usize, }; println!("fileinfo {f:?}"); result.push(f); } Err(err) => { iter_error = Some(format!("{err:?}")); break; } } } } Err(err) => { file_system_corrupt = Some(format!("{err:?}")); } } return FileList { file_system_corrupt, files: result, iter_error, }; } pub fn delete_file(&self, filename: &str) -> Result<()> { let filepath = Path::new(BASE_PATH).join(Path::new(filename)); match fs::remove_file(filepath) { OkStd(_) => Ok(()), Err(err) => { bail!(format!("{err:?}")) } } } pub fn get_file_handle(&self, filename: &str, write: bool) -> Result { let filepath = Path::new(BASE_PATH).join(Path::new(filename)); return Ok(if write { File::create(filepath)? } else { File::open(filepath)? }); } pub fn is_day(&self) -> bool { self.solar_is_day.get_level().into() } pub fn water_temperature_c(&mut self) -> Result { let mut delay = Delay::new_default(); self.one_wire_bus .reset(&mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; let first = self.one_wire_bus.devices(false, &mut delay).next(); if first.is_none() { bail!("Not found any one wire Ds18b20"); } let device_address = first .unwrap() .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; let water_temp_sensor = Ds18b20::new::(device_address) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; water_temp_sensor .start_temp_measurement(&mut self.one_wire_bus, &mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut delay); let sensor_data = water_temp_sensor .read_data(&mut self.one_wire_bus, &mut delay) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; if sensor_data.temperature == 85_f32 { bail!("Ds18b20 dummy temperature returned"); } Ok(sensor_data.temperature / 10_f32) } pub fn tank_sensor_percent(&mut self) -> Result { let delay = Delay::new_default(); self.tank_power.set_high()?; //let stabilize delay.delay_ms(100); unsafe { vTaskDelay(100); } let mut store = [0_u16; TANK_MULTI_SAMPLE]; for multisample in 0..TANK_MULTI_SAMPLE { let value = self.tank_channel.read()?; store[multisample] = value; } store.sort(); let median = store[6] as f32 / 1000_f32; let config_open_voltage_mv = 3.0; if config_open_voltage_mv < median { self.tank_power.set_low()?; bail!( "Tank sensor missing, open loop voltage {} on tank sensor input {}", config_open_voltage_mv, median ); } let r2 = median * 50.0 / (3.3 - median); let mut percent = r2 / 190_f32 * 100_f32; percent = percent.clamp(0.0, 100.0); println!("Tank sensor raw {} percent {}", median, percent); return Ok(percent as u16); } pub fn set_low_voltage_in_cycle(&mut self) { unsafe { LOW_VOLTAGE_DETECTED = true; } } pub fn clear_low_voltage_in_cycle(&mut self) { unsafe { LOW_VOLTAGE_DETECTED = false; } } pub 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(()) } pub fn pump(&self, plant: usize, enable: bool) -> Result<()> { 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}",), }; //currently infailable error, keep for future as result anyway self.shift_register.decompose()[index].set_state(enable.into())?; Ok(()) } pub fn last_pump_time(&self, plant: usize) -> Option> { let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant]; return Some(DateTime::from_timestamp_millis(ts)?); } pub fn store_last_pump_time(&mut self, plant: usize, time: chrono::DateTime) { unsafe { LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis(); } } pub fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { unsafe { CONSECUTIVE_WATERING_PLANT[plant] = count; } } pub fn consecutive_pump_count(&mut self, plant: usize) -> u32 { unsafe { return CONSECUTIVE_WATERING_PLANT[plant]; } } pub fn fault(&self, plant: usize, enable: bool) { 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()) .unwrap() } pub fn low_voltage_in_cycle(&mut self) -> bool { unsafe { return LOW_VOLTAGE_DETECTED; } } pub fn any_pump(&mut self, enable: bool) -> Result<()> { { self.main_pump.set_state(enable.into()).unwrap(); Ok(()) } } pub fn time(&mut self) -> Result> { let time = EspSystemTime {}.now().as_millis(); let smaller_time = time as i64; let local_time = DateTime::from_timestamp_millis(smaller_time) .ok_or(anyhow!("could not convert timestamp"))?; Ok(local_time) } pub fn sntp(&mut self, max_wait_ms: u32) -> Result> { let sntp = sntp::EspSntp::new_default()?; let mut counter = 0; while sntp.get_sync_status() != SyncStatus::Completed { let delay = Delay::new_default(); delay.delay_ms(100); counter += 100; if counter > max_wait_ms { bail!("Reached sntp timeout, aborting") } } self.time() } pub fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result { 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 mut results = [0; 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().unwrap(); self.sensor_multiplexer(sensor_channel)?; self.shift_register.decompose()[MS_4].set_low().unwrap(); self.shift_register.decompose()[SENSOR_ON] .set_high() .unwrap(); let delay = Delay::new_default(); let measurement = 100; let factor = 1000 as f32 / 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.shift_register.decompose()[MS_4].set_high().unwrap(); self.shift_register.decompose()[SENSOR_ON] .set_low() .unwrap(); delay.delay_ms(10); let unscaled = self.signal_counter.get_counter_value()? as i32; let hz = (unscaled as f32 * factor) as i32; println!( "raw measure unscaled {} hz {}, plant {} sensor {:?}", unscaled, hz, plant, sensor ); results[repeat] = hz; //println!("Measuring {:?} @ {} with {}", sensor, plant, hz); } results.sort(); let mid = results.len() / 2; Ok(results[mid]) } pub 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()) }; } pub fn wifi_ap(&mut self, ap_ssid: Option>) -> Result<()> { let ssid = ap_ssid.unwrap_or(heapless::String::from_str("PlantCtrl Emergency Mode").unwrap()); let apconfig = AccessPointConfiguration { ssid, auth_method: AuthMethod::None, ssid_hidden: false, ..Default::default() }; let clientconfig = ClientConfiguration::default(); self.wifi_driver .set_configuration(&Configuration::Mixed(clientconfig, apconfig))?; self.wifi_driver.start()?; Ok(()) } pub fn wifi( &mut self, ssid: heapless::String<32>, password: Option>, max_wait: u32, ) -> Result { match password { Some(pw) => { //TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not self.wifi_driver.set_configuration(&Configuration::Client( ClientConfiguration { ssid: ssid, password: pw, ..Default::default() }, ))?; } None => { self.wifi_driver.set_configuration(&Configuration::Client( ClientConfiguration { ssid: ssid, auth_method: AuthMethod::None, ..Default::default() }, ))?; } } self.wifi_driver.start()?; self.wifi_driver.connect()?; let delay = Delay::new_default(); let mut counter = 0_u32; while !self.wifi_driver.is_connected()? { println!("Waiting for station connection"); delay.delay_ms(250); counter += 250; if counter > max_wait { //ignore these errors, wifi will not be used this self.wifi_driver.disconnect().unwrap_or(()); self.wifi_driver.stop().unwrap_or(()); bail!("Did not manage wifi connection within timeout"); } } println!("Should be connected now"); while !self.wifi_driver.is_up()? { println!("Waiting for network being up"); delay.delay_ms(250); counter += 250; if counter > max_wait { //ignore these errors, wifi will not be used this self.wifi_driver.disconnect().unwrap_or(()); self.wifi_driver.stop().unwrap_or(()); bail!("Did not manage wifi connection within timeout"); } } //update freertos registers ;) let address = self.wifi_driver.sta_netif().get_ip_info()?; println!("IP info: {:?}", address); Ok(address) } pub fn mount_file_system(&mut self) -> Result<()> { let base_path = CString::new("/spiffs")?; let storage = CString::new(SPIFFS_PARTITION_NAME)?; let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { base_path: base_path.as_ptr(), partition_label: storage.as_ptr(), max_files: 5, format_if_mount_failed: true, }; //TODO check fielsystem esp_spiffs_check unsafe { esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?; Ok(()) } } pub fn file_system_size(&mut self) -> Result { let storage = CString::new(SPIFFS_PARTITION_NAME)?; let mut total_size = 0; let mut used_size = 0; unsafe { esp_idf_sys::esp!(esp_idf_sys::esp_spiffs_info( storage.as_ptr(), &mut total_size, &mut used_size ))?; } Ok(FileSystemSizeInfo { total_size, used_size, free_size: total_size - used_size, }) } pub fn is_mode_override(&mut self) -> bool { self.boot_button.get_level() == Level::Low } pub fn factory_reset(&mut self) -> Result<()> { println!("factory resetting"); let config = Path::new(CONFIG_FILE); if config.exists() { println!("Removing config"); std::fs::remove_file(config)?; } //TODO clear eeprom Ok(()) } pub fn get_rtc_time(&mut self) -> Result> { match self.rtc.datetime() { OkStd(rtc_time) => { return Ok(rtc_time.and_utc()); } Err(err) => { bail!("Error getting rtc time {:?}", err) } } } pub fn set_rtc_time(&mut self, time: &DateTime) -> Result<()> { let naive_time = time.naive_utc(); match self.rtc.set_datetime(&naive_time) { OkStd(_) => Ok(()), Err(err) => { bail!("Error getting rtc time {:?}", err) } } } pub fn get_config(&mut self) -> Result { let cfg = File::open(CONFIG_FILE)?; let config: PlantControllerConfig = serde_json::from_reader(cfg)?; Ok(config) } pub fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()> { let mut cfg = File::create(CONFIG_FILE)?; serde_json::to_writer(&mut cfg, &config)?; println!("Wrote config config {:?}", config); Ok(()) } pub fn wifi_scan(&mut self) -> Result> { self.wifi_driver.start_scan( &ScanConfig { scan_type: ScanType::Passive(Duration::from_secs(5)), show_hidden: false, ..Default::default() }, true, )?; Ok(self.wifi_driver.get_scan_result()?) } pub fn test_pump(&mut self, plant: usize) -> Result<()> { self.any_pump(true)?; self.pump(plant, true)?; unsafe { vTaskDelay(30000) }; self.pump(plant, false)?; self.any_pump(false)?; Ok(()) } pub fn test(&mut self) -> Result<()> { self.general_fault(true); unsafe { vTaskDelay(100) }; self.general_fault(false); unsafe { vTaskDelay(100) }; self.any_pump(true)?; unsafe { vTaskDelay(500) }; self.any_pump(false)?; unsafe { vTaskDelay(500) }; self.light(true)?; unsafe { vTaskDelay(500) }; self.light(false)?; unsafe { vTaskDelay(500) }; for i in 0..PLANT_COUNT { self.fault(i, true); unsafe { vTaskDelay(500) }; self.fault(i, false); unsafe { vTaskDelay(500) }; } for i in 0..PLANT_COUNT { self.pump(i, true)?; unsafe { vTaskDelay(100) }; self.pump(i, false)?; unsafe { vTaskDelay(100) }; } for plant in 0..PLANT_COUNT { let a = self.measure_moisture_hz(plant, plant_hal::Sensor::A); let b = self.measure_moisture_hz(plant, plant_hal::Sensor::B); print!("P:{} a:{:?} b:{:?}", plant, a, b) } println!(); Delay::new_default().delay_ms(10); Ok(()) } pub fn is_wifi_config_file_existant(&mut self) -> bool { let config = Path::new(CONFIG_FILE); config.exists() } pub fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> { let base_topic = config .network .base_topic .as_ref() .context("missing base topic")?; if base_topic.is_empty() { bail!("Mqtt base_topic was empty") } let mqtt_url = config .network .mqtt_url .as_ref() .context("missing mqtt url")?; if mqtt_url.is_empty() { bail!("Mqtt url was empty") } let last_will_topic = format!("{}/state", base_topic); let mqtt_client_config = MqttClientConfiguration { lwt: Some(LwtConfiguration { topic: &last_will_topic, payload: "lost".as_bytes(), qos: AtLeastOnce, retain: true, }), client_id: Some("plantctrl"), keep_alive_interval: Some(Duration::from_secs(60 * 60 * 2)), //room for improvement ..Default::default() }; let mqtt_connected_event_received = Arc::new(AtomicBool::new(false)); let mqtt_connected_event_ok = Arc::new(AtomicBool::new(false)); let round_trip_ok = Arc::new(AtomicBool::new(false)); let round_trip_topic = format!("{}/internal/roundtrip", base_topic); let stay_alive_topic = format!("{}/stay_alive", base_topic); println!("Stay alive topic is {}", stay_alive_topic); let mqtt_connected_event_received_copy = mqtt_connected_event_received.clone(); let mqtt_connected_event_ok_copy = mqtt_connected_event_ok.clone(); let stay_alive_topic_copy = stay_alive_topic.clone(); let round_trip_topic_copy = round_trip_topic.clone(); let round_trip_ok_copy = round_trip_ok.clone(); println!( "Connecting mqtt {} with id {}", mqtt_url, mqtt_client_config.client_id.unwrap_or("not set") ); let mut client = EspMqttClient::new_cb(&mqtt_url, &mqtt_client_config, move |event| { let payload = event.payload(); match payload { embedded_svc::mqtt::client::EventPayload::Received { id: _, topic, data, details: _, } => { let data = String::from_utf8_lossy(data); if let Some(topic) = topic { //todo use enums if topic.eq(round_trip_topic_copy.as_str()) { round_trip_ok_copy.store(true, std::sync::atomic::Ordering::Relaxed); } else if topic.eq(stay_alive_topic_copy.as_str()) { let value = data.eq_ignore_ascii_case("true") || data.eq_ignore_ascii_case("1"); println!("Received stay alive with value {}", value); STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed); } else { println!("Unknown topic recieved {}", topic); } } } esp_idf_svc::mqtt::client::EventPayload::Connected(_) => { mqtt_connected_event_received_copy .store(true, std::sync::atomic::Ordering::Relaxed); mqtt_connected_event_ok_copy.store(true, std::sync::atomic::Ordering::Relaxed); println!("Mqtt connected"); } esp_idf_svc::mqtt::client::EventPayload::Disconnected => { mqtt_connected_event_received_copy .store(true, std::sync::atomic::Ordering::Relaxed); mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed); println!("Mqtt disconnected"); } esp_idf_svc::mqtt::client::EventPayload::Error(esp_error) => { println!("EspMqttError reported {:?}", esp_error); mqtt_connected_event_received_copy .store(true, std::sync::atomic::Ordering::Relaxed); mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed); println!("Mqtt error"); } esp_idf_svc::mqtt::client::EventPayload::BeforeConnect => { println!("Mqtt before connect") } esp_idf_svc::mqtt::client::EventPayload::Subscribed(_) => { println!("Mqtt subscribed") } esp_idf_svc::mqtt::client::EventPayload::Unsubscribed(_) => { println!("Mqtt unsubscribed") } esp_idf_svc::mqtt::client::EventPayload::Published(_) => { println!("Mqtt published") } esp_idf_svc::mqtt::client::EventPayload::Deleted(_) => { println!("Mqtt deleted") } } })?; let wait_for_connections_event = 0; while wait_for_connections_event < 100 { match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) { true => { println!("Mqtt connection callback received, progressing"); match mqtt_connected_event_ok.load(std::sync::atomic::Ordering::Relaxed) { true => { println!("Mqtt did callback as connected, testing with roundtrip now"); //subscribe to roundtrip client.subscribe(round_trip_topic.as_str(), ExactlyOnce)?; client.subscribe(stay_alive_topic.as_str(), ExactlyOnce)?; //publish to roundtrip client.publish( round_trip_topic.as_str(), ExactlyOnce, false, "online_test".as_bytes(), )?; let wait_for_roundtrip = 0; while wait_for_roundtrip < 100 { match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) { true => { println!("Round trip registered, proceeding"); self.mqtt_client = Some(client); return Ok(()); } false => { unsafe { vTaskDelay(10) }; } } } bail!("Mqtt did not complete roundtrip in time"); } false => { bail!("Mqtt did respond but with failure") } } } false => { unsafe { vTaskDelay(10) }; } } } bail!("Mqtt did not fire connection callback in time"); } pub fn mqtt_publish( &mut self, config: &PlantControllerConfig, subtopic: &str, message: &[u8], ) -> Result<()> { if self.mqtt_client.is_none() { return Ok(()); } if !subtopic.starts_with("/") { println!("Subtopic without / at start {}", subtopic); bail!("Subtopic without / at start {}", subtopic); } if subtopic.len() > 192 { println!("Subtopic exceeds 192 chars {}", subtopic); bail!("Subtopic exceeds 192 chars {}", subtopic); } let client = self.mqtt_client.as_mut().unwrap(); let mut full_topic: heapless::String<256> = heapless::String::new(); if full_topic .push_str(&config.network.base_topic.as_ref().unwrap()) .is_err() { println!("Some error assembling full_topic 1"); bail!("Some error assembling full_topic 1") }; if full_topic.push_str(subtopic).is_err() { println!("Some error assembling full_topic 2"); bail!("Some error assembling full_topic 2") }; let publish = client.publish( &full_topic, embedded_svc::mqtt::client::QoS::ExactlyOnce, true, message, ); Delay::new(10).delay_ms(50); match publish { OkStd(message_id) => { println!( "Published mqtt topic {} with message {:#?} msgid is {:?}", full_topic, String::from_utf8_lossy(message), message_id ); return Ok(()); } Err(err) => { println!( "Error during mqtt send on topic {} with message {:#?} error is {:?}", full_topic, String::from_utf8_lossy(message), err ); return Err(err)?; } }; } pub fn get_restart_to_conf(&mut self) -> bool { return unsafe { RESTART_TO_CONF }; } pub fn set_restart_to_conf(&mut self, to_conf: bool) { unsafe { RESTART_TO_CONF = to_conf; } } pub fn state_charge_percent(&mut self) -> Result { match self.battery_driver.state_of_charge() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading SoC {:?}", err), } } pub fn remaining_milli_ampere_hour(&mut self) -> Result { match self.battery_driver.remaining_capacity() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Remaining Capacity {:?}", err), } } pub fn max_milli_ampere_hour(&mut self) -> Result { match self.battery_driver.full_charge_capacity() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Full Charge Capacity {:?}", err), } } pub fn design_milli_ampere_hour(&mut self) -> Result { match self.battery_driver.design_capacity() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Design Capacity {:?}", err), } } pub fn voltage_milli_volt(&mut self) -> Result { match self.battery_driver.voltage() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading voltage {:?}", err), } } pub fn average_current_milli_ampere(&mut self) -> Result { match self.battery_driver.average_current() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Average Current {:?}", err), } } pub fn cycle_count(&mut self) -> Result { match self.battery_driver.cycle_count() { OkStd(r) => Ok(r), Err(err) => bail!("Error reading Cycle Count {:?}", err), } } pub fn state_health_percent(&mut self) -> Result { match self.battery_driver.state_of_health() { OkStd(r) => Ok(r as u8), Err(err) => bail!("Error reading State of Health {:?}", err), } } pub fn bat_temperature(&mut self) -> Result { match self.battery_driver.temperature() { OkStd(r) => Ok(r as u16), Err(err) => bail!("Error reading Temperature {:?}", err), } } pub fn flash_bq34_z100(&mut self, line: &str, dryrun: bool) -> Result<()> { match self.battery_driver.write_flash_stream_i2c(line, dryrun) { OkStd(r) => Ok(r), Err(err) => bail!("Error reading SoC {:?}", err), } } pub fn sensor_multiplexer(&mut self, n: u8) -> Result<()> { assert!(n < 16); let is_bit_set = |b: u8| -> bool { n & (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()?; } Ok(()) } } fn print_battery( battery_driver: &mut Bq34z100g1Driver>, Delay>, ) -> Result<(), Bq34Z100Error> { println!("Try communicating with battery"); let fwversion = battery_driver.fw_version().unwrap_or_else(|e| { println!("Firmeware {:?}", e); 0 }); println!("fw version is {}", fwversion); let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| { println!("Design capacity {:?}", e); 0 }); println!("Design Capacity {}", design_capacity); if design_capacity == 1000 { println!("Still stock configuring battery, readouts are likely to be wrong!"); } let flags = battery_driver.get_flags_decoded()?; println!("Flags {:?}", flags); let chem_id = battery_driver.chem_id().unwrap_or_else(|e| { println!("Chemid {:?}", e); 0 }); let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| { println!("Bat Temp {:?}", e); 0 }); let temp_c = Temperature::from_kelvin(bat_temp as f64 / 10_f64).as_celsius(); let voltage = battery_driver.voltage().unwrap_or_else(|e| { println!("Bat volt {:?}", e); 0 }); let current = battery_driver.current().unwrap_or_else(|e| { println!("Bat current {:?}", e); 0 }); let state = battery_driver.state_of_charge().unwrap_or_else(|e| { println!("Bat Soc {:?}", e); 0 }); let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| { println!("Bat Charge Volt {:?}", e); 0 }); let charge_current = battery_driver.charge_current().unwrap_or_else(|e| { println!("Bat Charge Current {:?}", e); 0 }); println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current); let _ = battery_driver.unsealed(); let _ = battery_driver.it_enable(); return Result::Ok(()); } pub static I2C_DRIVER: Lazy>> = Lazy::new(|| PlantHal::create_i2c()); impl PlantHal { fn create_i2c() -> Mutex> { let peripherals = unsafe { Peripherals::new() }; let config = I2cConfig::new() .scl_enable_pullup(true) .sda_enable_pullup(true) .baudrate(100_u32.kHz().into()) .timeout(APBTickType::from(Duration::from_millis(100))); let i2c = peripherals.i2c0; let scl = peripherals.pins.gpio19.downgrade(); let sda = peripherals.pins.gpio20.downgrade(); Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) } pub fn create() -> Result>> { let peripherals = Peripherals::take()?; println!("Init battery driver"); let mut battery_driver = Bq34z100g1Driver { i2c: MutexDevice::new(&I2C_DRIVER), delay: Delay::new(0), flash_block_data: [0; 32], }; println!("Init rtc driver"); let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); println!("Init rtc eeprom driver"); let mut eeprom = { Eeprom24x::new_24x32( MutexDevice::new(&I2C_DRIVER), SlaveAddr::Alternative(true, true, true), ) }; let rtc_time = rtc.datetime(); match rtc_time { OkStd(tt) => { println!("Rtc Module reports time at UTC {}", tt); } Err(err) => { println!("Rtc Module could not be read {:?}", err); } } match eeprom.read_byte(0) { OkStd(byte) => { println!("Read first byte with status {}", byte); } Err(err) => { println!("Eeprom could not read first byte {:?}", err); } } let mut clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?; clock.set_pull(Pull::Floating).unwrap(); let mut latch = PinDriver::input_output(peripherals.pins.gpio3.downgrade())?; latch.set_pull(Pull::Floating).unwrap(); let mut data = PinDriver::input_output(peripherals.pins.gpio23.downgrade())?; data.set_pull(Pull::Floating).unwrap(); let shift_register = ShiftRegister40::new(clock.into(), latch.into(), data.into()); for mut pin in shift_register.decompose() { pin.set_low().unwrap(); } let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18)?; one_wire_pin.set_pull(Pull::Floating).unwrap(); //disable all 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()?; //init,reset rtc memory depending on cause let mut init_rtc_store: bool = false; let mut to_config_mode: bool = false; let reasons = ResetReason::get(); match reasons { ResetReason::Software => {}, ResetReason::ExternalPin => {}, ResetReason::Watchdog => { init_rtc_store = true; }, ResetReason::Sdio => { init_rtc_store = true }, ResetReason::Panic => { init_rtc_store = true }, ResetReason::InterruptWatchdog => { init_rtc_store = true }, ResetReason::PowerOn => { init_rtc_store = true }, ResetReason::Unknown => { init_rtc_store = true }, ResetReason::Brownout => { init_rtc_store = true }, ResetReason::TaskWatchdog => { init_rtc_store = true }, ResetReason::DeepSleep => {}, ResetReason::USBPeripheral => { init_rtc_store = true; to_config_mode = true; }, ResetReason::JTAG => { init_rtc_store = true }, }; println!("Reset due to {:?} requires rtc clear {} and force config mode {}", reasons, init_rtc_store, to_config_mode); if init_rtc_store { unsafe { LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT]; CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT]; LOW_VOLTAGE_DETECTED = false; RESTART_TO_CONF = to_config_mode; }; } else { unsafe { if to_config_mode{ RESTART_TO_CONF = true; } println!("Current restart to conf mode{:?}", RESTART_TO_CONF); println!( "Current low voltage detection is {:?}", LOW_VOLTAGE_DETECTED ); for i in 0..PLANT_COUNT { println!( "LAST_WATERING_TIMESTAMP[{}] = UTC {}", i, LAST_WATERING_TIMESTAMP[i] ); } for i in 0..PLANT_COUNT { println!( "CONSECUTIVE_WATERING_PLANT[{}] = {}", i, CONSECUTIVE_WATERING_PLANT[i] ); } } } let mut counter_unit1 = PcntDriver::new( peripherals.pcnt0, Some(peripherals.pins.gpio22), Option::::None, Option::::None, Option::::None, )?; println!("Channel config start"); counter_unit1.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, }, )?; println!("Setup filter"); //TODO validate filter value! currently max allowed value counter_unit1.set_filter_value(1023)?; counter_unit1.filter_enable()?; println!("Wifi start"); let sys_loop = EspSystemEventLoop::take()?; let nvs = EspDefaultNvsPartition::take()?; let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; let adc_config = AdcChannelConfig { attenuation: attenuation::DB_11, resolution: Resolution::Resolution12Bit, calibration: true, }; let tank_driver = AdcDriver::new(peripherals.adc1)?; let tank_channel: AdcChannelDriver> = AdcChannelDriver::new(tank_driver, peripherals.pins.gpio5, &adc_config)?; let mut solar_is_day = PinDriver::input(peripherals.pins.gpio8.downgrade())?; solar_is_day.set_pull(Pull::Floating)?; let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; boot_button.set_pull(Pull::Floating)?; let mut light = PinDriver::input_output(peripherals.pins.gpio10.downgrade())?; light.set_pull(Pull::Floating).unwrap(); let mut main_pump = PinDriver::input_output(peripherals.pins.gpio2.downgrade())?; main_pump.set_pull(Pull::Floating)?; main_pump.set_low()?; let mut tank_power = PinDriver::input_output(peripherals.pins.gpio11.downgrade())?; tank_power.set_pull(Pull::Floating)?; let mut general_fault = PinDriver::input_output(peripherals.pins.gpio6.downgrade())?; general_fault.set_pull(Pull::Floating)?; general_fault.set_low()?; let one_wire_bus = OneWire::new(one_wire_pin) .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; println!("After stuff"); let status = print_battery(&mut battery_driver); if status.is_err() { println!("Error communicating with battery!! {:?}", status.err()); } else { println!("Managed to comunnicate with battery"); } let shift_register_enable_invert = PinDriver::output(peripherals.pins.gpio21.downgrade())?; let rv = Mutex::new(PlantCtrlBoard { shift_register, shift_register_enable_invert, tank_channel, solar_is_day, boot_button, light, main_pump, tank_power, general_fault, one_wire_bus, signal_counter: counter_unit1, wifi_driver, mqtt_client: None, battery_driver: battery_driver, rtc: rtc, eeprom: eeprom, }); let _ = rv.lock().is_ok_and(|mut board| { unsafe { gpio_hold_dis(board.shift_register_enable_invert.pin()) }; board.shift_register_enable_invert.set_low().unwrap(); unsafe { gpio_hold_en(board.shift_register_enable_invert.pin()) }; return true; }); Ok(rv) } }