it's alive
This commit is contained in:
		| @@ -460,6 +460,7 @@ | ||||
|       "single_global_label": "ignore", | ||||
|       "unannotated": "error", | ||||
|       "unconnected_wire_endpoint": "warning", | ||||
|       "undefined_netclass": "error", | ||||
|       "unit_value_mismatch": "error", | ||||
|       "unresolved_variable": "error", | ||||
|       "wire_dangling": "error" | ||||
|   | ||||
| @@ -4,6 +4,7 @@ rustflags = [ | ||||
|   # NOTE: May negatively impact performance of produced code | ||||
|   "-C", "force-frame-pointers", | ||||
|   "-Z", "stack-protector=all", | ||||
|   "-C", "link-arg=-Tlinkall.x", | ||||
| ] | ||||
|  | ||||
| target = "riscv32imac-unknown-none-elf" | ||||
|   | ||||
							
								
								
									
										104
									
								
								rust/Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								rust/Cargo.toml
									
									
									
									
									
								
							| @@ -1,31 +1,8 @@ | ||||
| [package] | ||||
| name = "plant-ctrl2" | ||||
| version = "0.1.0" | ||||
| authors = ["Empire Phoenix"] | ||||
| edition = "2021" | ||||
| resolver = "2" | ||||
| #rust-version = "1.71" | ||||
|  | ||||
| [profile.dev] | ||||
| # Explicitly disable LTO which the Xtensa codegen backend has issues | ||||
| lto = false | ||||
| strip = false | ||||
| debug = true | ||||
| overflow-checks = true | ||||
| panic = "abort" | ||||
| incremental = true | ||||
| opt-level = 2 | ||||
|  | ||||
|  | ||||
| [profile.release] | ||||
| # Explicitly disable LTO which the Xtensa codegen backend has issues | ||||
| lto = false | ||||
| strip = true | ||||
| debug = false | ||||
| overflow-checks = false | ||||
| panic = "abort" | ||||
| incremental = true | ||||
| opt-level = "z" | ||||
| name = "plant-ctrl2" | ||||
| rust-version = "1.86" | ||||
| version = "0.1.0" | ||||
|  | ||||
| [package.metadata.cargo_runner] | ||||
| # The string `$TARGET_FILE` will be replaced with the path from cargo. | ||||
| @@ -40,6 +17,26 @@ command = [ | ||||
|     "partitions.csv" | ||||
| ] | ||||
|  | ||||
| [profile.dev] | ||||
| lto = true | ||||
| strip = false | ||||
| debug = false | ||||
| overflow-checks = true | ||||
| panic = "abort" | ||||
| incremental = true | ||||
| opt-level = "z" | ||||
|  | ||||
|  | ||||
| [profile.release] | ||||
| # Explicitly disable LTO which the Xtensa codegen backend has issues | ||||
| lto = true | ||||
| strip = true | ||||
| debug = false | ||||
| overflow-checks = true | ||||
| panic = "abort" | ||||
| incremental = true | ||||
| opt-level = "z" | ||||
|  | ||||
|  | ||||
| [package.metadata.espflash] | ||||
| partition_table = "partitions.csv" | ||||
| @@ -78,28 +75,28 @@ embassy-executor = { version = "0.7.0", features = [ | ||||
| ] } | ||||
| embassy-time = { version = "0.4.0", features = ["log"] } | ||||
| esp-hal-embassy = { version = "0.9.0", features = ["esp32c6", "log-04"] } | ||||
| esp-wifi = { version = "0.15.0", features = [ | ||||
|     "builtin-scheduler", | ||||
|     "esp-alloc", | ||||
|     "esp32c6", | ||||
|     "log-04", | ||||
|     "smoltcp", | ||||
|     "wifi", | ||||
| ] } | ||||
| smoltcp = { version = "0.12.0", default-features = false, features = [ | ||||
|     "log", | ||||
|     "medium-ethernet", | ||||
|     "multicast", | ||||
|     "proto-dhcpv4", | ||||
|     "proto-dns", | ||||
|     "proto-ipv4", | ||||
|     "socket-dns", | ||||
|     "socket-icmp", | ||||
|     "socket-raw", | ||||
|     "socket-tcp", | ||||
|     "socket-udp", | ||||
| ] } | ||||
| static_cell = "2.1.1" | ||||
| #esp-wifi = { version = "0.15.0", features = [ | ||||
| #    "builtin-scheduler", | ||||
| #    "esp-alloc", | ||||
| #    "esp32c6", | ||||
| #    "log-04", | ||||
| #    "smoltcp", | ||||
| #    "wifi", | ||||
| #] } | ||||
| #smoltcp = { version = "0.12.0", default-features = false, features = [ | ||||
| #    "log", | ||||
| #    "medium-ethernet", | ||||
| #    "multicast", | ||||
| #    "proto-dhcpv4", | ||||
| #    "proto-dns", | ||||
| #    "proto-ipv4", | ||||
| #    "socket-dns", | ||||
| #    "socket-icmp", | ||||
| #    "socket-raw", | ||||
| #    "socket-tcp", | ||||
| #    "socket-udp", | ||||
| #] } | ||||
| #static_cell = "2.1.1" | ||||
| embedded-hal = "1.0.0" | ||||
| heapless = { version = "0.8", features = ["serde"] } | ||||
| embedded-hal-bus = { version = "0.3.0" } | ||||
| @@ -122,8 +119,8 @@ serde_json = { version = "1.0.143", default-features = false, features = ["alloc | ||||
|  | ||||
| #timezone | ||||
|  | ||||
| chrono = { version = "0.4.23", default-features = false, features = ["iana-time-zone", "alloc", "serde"] } | ||||
| chrono-tz = { version = "0.10.3", default-features = false, features = ["filter-by-regex"] } | ||||
| chrono = { version = "0.4.42", default-features = false, features = ["iana-time-zone", "alloc", "serde"] } | ||||
| chrono-tz = { version = "0.10.4", default-features = false, features = ["filter-by-regex"] } | ||||
| eeprom24x = "0.7.2" | ||||
| #url = "2.5.3" | ||||
| crc = "3.2.1" | ||||
| @@ -138,18 +135,13 @@ ina219 = { version = "0.2.0" } | ||||
| embedded-storage = "=0.3.1" | ||||
| ekv = "1.0.0" | ||||
| embedded-can = "0.4.1" | ||||
| critical-section = "1.2.0" | ||||
| portable-atomic = "1.11.1" | ||||
| embassy-sync = { version = "0.7.2", features = ["log"] } | ||||
| async-trait = "0.1.89" | ||||
| bq34z100 = { version = "0.4.0", default-features = false } | ||||
|  | ||||
|  | ||||
| [patch.crates-io] | ||||
| #esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal.git" } | ||||
| #esp-idf-hal = { git = "https://github.com/empirephoenix/esp-idf-hal.git" } | ||||
| #esp-idf-sys = { git = "https://github.com/empirephoenix/esp-idf-sys.git" } | ||||
| #esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys.git" } | ||||
| #esp-idf-svc = { git = "https://github.com/esp-rs/esp-idf-svc.git" } | ||||
| #bq34z100 = { path = "../../bq34z100_rust" } | ||||
|  | ||||
| [build-dependencies] | ||||
|   | ||||
| @@ -50,6 +50,12 @@ fn linker_be_nice() { | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
|     //webpack(); | ||||
|     //linker_be_nice(); | ||||
|     let _ = EmitBuilder::builder().all_git().all_build().emit(); | ||||
| } | ||||
|  | ||||
| fn webpack() { | ||||
|     println!("cargo:rerun-if-changed=./src/src_webpack"); | ||||
|     Command::new("rm") | ||||
|         .arg("./src/webserver/bundle.js") | ||||
| @@ -112,6 +118,4 @@ fn main() { | ||||
|                 .unwrap(); | ||||
|         } | ||||
|     } | ||||
|     linker_be_nice(); | ||||
|     let _ = EmitBuilder::builder().all_git().all_build().emit(); | ||||
| } | ||||
| } | ||||
							
								
								
									
										176
									
								
								rust/scratch/water.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								rust/scratch/water.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,176 @@ | ||||
| use crate::hal::TANK_MULTI_SAMPLE; | ||||
| use anyhow::{anyhow, bail}; | ||||
| use ds18b20::Ds18b20; | ||||
| use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; | ||||
| use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; | ||||
| use esp_idf_hal::adc::{attenuation, Resolution, ADC1}; | ||||
| use esp_idf_hal::delay::Delay; | ||||
| use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull}; | ||||
| use esp_idf_hal::pcnt::{ | ||||
|     PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1, | ||||
| }; | ||||
| use esp_idf_sys::EspError; | ||||
| use one_wire_bus::OneWire; | ||||
|  | ||||
| pub struct TankSensor<'a> { | ||||
|     // one_wire_bus: OneWire<PinDriver<'a, AnyIOPin, InputOutput>>, | ||||
|     // tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, ADC1>>, | ||||
|     // tank_power: PinDriver<'a, AnyIOPin, InputOutput>, | ||||
|     // flow_counter: PcntDriver<'a>, | ||||
|     // delay: Delay, | ||||
| } | ||||
|  | ||||
| impl<'a> TankSensor<'a> { | ||||
|     pub(crate) fn create( | ||||
|         // one_wire_pin: AnyIOPin, | ||||
|         // adc1: ADC1, | ||||
|         // gpio5: Gpio5, | ||||
|         // tank_power_pin: AnyIOPin, | ||||
|         // flow_sensor_pin: AnyIOPin, | ||||
|         // pcnt1: PCNT1, | ||||
|     ) -> anyhow::Result<TankSensor<'a>> { | ||||
|         // let mut one_wire_pin = | ||||
|         //     PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin"); | ||||
|         // one_wire_pin | ||||
|         //     .set_pull(Pull::Floating) | ||||
|         //     .expect("Failed to set pull"); | ||||
|         // | ||||
|         // let adc_config = AdcChannelConfig { | ||||
|         //     attenuation: attenuation::DB_11, | ||||
|         //     resolution: Resolution::Resolution12Bit, | ||||
|         //     calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve, | ||||
|         // }; | ||||
|         // let tank_driver = AdcDriver::new(adc1).expect("Failed to configure ADC"); | ||||
|         // let tank_channel = AdcChannelDriver::new(tank_driver, gpio5, &adc_config) | ||||
|         //     .expect("Failed to configure ADC channel"); | ||||
|         // | ||||
|         // let mut tank_power = | ||||
|         //     PinDriver::input_output(tank_power_pin).expect("Failed to configure pin"); | ||||
|         // tank_power | ||||
|         //     .set_pull(Pull::Floating) | ||||
|         //     .expect("Failed to set pull"); | ||||
|         // | ||||
|         // let one_wire_bus = | ||||
|         //     OneWire::new(one_wire_pin).expect("OneWire bus did not pull up after release"); | ||||
|         // | ||||
|         // let mut flow_counter = PcntDriver::new( | ||||
|         //     pcnt1, | ||||
|         //     Some(flow_sensor_pin), | ||||
|         //     Option::<AnyInputPin>::None, | ||||
|         //     Option::<AnyInputPin>::None, | ||||
|         //     Option::<AnyInputPin>::None, | ||||
|         // )?; | ||||
|         // | ||||
|         // flow_counter.channel_config( | ||||
|         //     PcntChannel::Channel1, | ||||
|         //     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, | ||||
|         //     }, | ||||
|         // )?; | ||||
|         // | ||||
|         // Ok(TankSensor { | ||||
|         //     one_wire_bus, | ||||
|         //     tank_channel, | ||||
|         //     tank_power, | ||||
|         //     flow_counter, | ||||
|         //     delay: Default::default(), | ||||
|         // }) | ||||
|         bail!("Tank sensor not implemented"); | ||||
|     } | ||||
|  | ||||
|     pub fn reset_flow_meter(&mut self) { | ||||
|         // self.flow_counter.counter_pause().unwrap(); | ||||
|         // self.flow_counter.counter_clear().unwrap(); | ||||
|     } | ||||
|  | ||||
|     pub fn start_flow_meter(&mut self) { | ||||
|         //self.flow_counter.counter_resume().unwrap(); | ||||
|     } | ||||
|  | ||||
|     pub fn get_flow_meter_value(&mut self) -> i16 { | ||||
|         //self.flow_counter.get_counter_value().unwrap() | ||||
|         5_i16 | ||||
|     } | ||||
|  | ||||
|     pub fn stop_flow_meter(&mut self) -> i16 { | ||||
|         //self.flow_counter.counter_pause().unwrap(); | ||||
|         self.get_flow_meter_value() | ||||
|     } | ||||
|  | ||||
|     pub async fn water_temperature_c(&mut self) -> anyhow::Result<f32> { | ||||
|         //multisample should be moved to water_temperature_c | ||||
|         let mut attempt = 1; | ||||
|         let water_temp: Result<f32, anyhow::Error> = loop { | ||||
|             let temp = self.single_temperature_c().await; | ||||
|             match &temp { | ||||
|                 Ok(res) => { | ||||
|                     log::info!("Water temp is {}", res); | ||||
|                     break temp; | ||||
|                 } | ||||
|                 Err(err) => { | ||||
|                     log::info!("Could not get water temp {} attempt {}", err, attempt) | ||||
|                 } | ||||
|             } | ||||
|             if attempt == 5 { | ||||
|                 break temp; | ||||
|             } | ||||
|             attempt += 1; | ||||
|         }; | ||||
|         water_temp | ||||
|     } | ||||
|  | ||||
|     async fn single_temperature_c(&mut self) -> anyhow::Result<f32> { | ||||
|         bail!("err"); | ||||
|         // self.one_wire_bus | ||||
|         //     .reset(&mut self.delay) | ||||
|         //     .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; | ||||
|         // let first = self.one_wire_bus.devices(false, &mut self.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::<EspError>(device_address) | ||||
|         //     .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; | ||||
|         // | ||||
|         // water_temp_sensor | ||||
|         //     .start_temp_measurement(&mut self.one_wire_bus, &mut self.delay) | ||||
|         //     .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; | ||||
|         // ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.delay); | ||||
|         // let sensor_data = water_temp_sensor | ||||
|         //     .read_data(&mut self.one_wire_bus, &mut self.delay) | ||||
|         //     .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; | ||||
|         // if sensor_data.temperature == 85_f32 { | ||||
|         //     bail!("Ds18b20 dummy temperature returned"); | ||||
|         // } | ||||
|         //anyhow::Ok(sensor_data.temperature / 10_f32) | ||||
|         Ok(13_f32) | ||||
|     } | ||||
|  | ||||
|     pub async fn tank_sensor_voltage(&mut self) -> anyhow::Result<f32> { | ||||
|         // self.tank_power.set_high()?; | ||||
|         // //let stabilize | ||||
|         // self.delay.delay_ms(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; | ||||
|         // } | ||||
|         // self.tank_power.set_low()?; | ||||
|         // | ||||
|         // store.sort(); | ||||
|         // let median_mv = store[6] as f32 / 1000_f32; | ||||
|         let median_mv = 10_f32; | ||||
|         anyhow::Ok(median_mv) | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,8 @@ | ||||
| use alloc::string::String; | ||||
| use core::str::FromStr; | ||||
| use crate::hal::PLANT_COUNT; | ||||
| use crate::plant_state::PlantWateringMode; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::str::FromStr; | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] | ||||
| #[serde(default)] | ||||
|   | ||||
| @@ -1,20 +1,23 @@ | ||||
| use alloc::string::String; | ||||
| use crate::hal::Box; | ||||
| use anyhow::anyhow; | ||||
| use async_trait::async_trait; | ||||
| use bq34z100::{Bq34Z100Error, Bq34z100g1Driver}; | ||||
| use measurements::Temperature; | ||||
| use serde::Serialize; | ||||
|  | ||||
| #[async_trait] | ||||
| pub trait BatteryInteraction { | ||||
|     async fn state_charge_percent(&mut self) -> Result<f32, BatteryError>; | ||||
|     async fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>; | ||||
|     async fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>; | ||||
|     async fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>; | ||||
|     async fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError>; | ||||
|     async fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError>; | ||||
|     async fn cycle_count(&mut self) -> Result<u16, BatteryError>; | ||||
|     async fn state_health_percent(&mut self) -> Result<u16, BatteryError>; | ||||
|     async fn bat_temperature(&mut self) -> Result<u16, BatteryError>; | ||||
|     async fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError>; | ||||
|     async fn state_charge_percent(& mut self) -> Result<f32, BatteryError>; | ||||
|     async fn remaining_milli_ampere_hour(& mut self) -> Result<u16, BatteryError>; | ||||
|     async fn max_milli_ampere_hour(& mut self) -> Result<u16, BatteryError>; | ||||
|     async fn design_milli_ampere_hour(& mut self) -> Result<u16, BatteryError>; | ||||
|     async fn voltage_milli_volt(& mut self) -> Result<u16, BatteryError>; | ||||
|     async fn average_current_milli_ampere(& mut self) -> Result<i16, BatteryError>; | ||||
|     async fn cycle_count(& mut self) -> Result<u16, BatteryError>; | ||||
|     async fn state_health_percent(& mut self) -> Result<u16, BatteryError>; | ||||
|     async fn bat_temperature(& mut self) -> Result<u16, BatteryError>; | ||||
|     async fn get_battery_state(& mut self) -> Result<BatteryState, BatteryError>; | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| @@ -35,13 +38,13 @@ pub enum BatteryError { | ||||
|     CommunicationError(String), | ||||
| } | ||||
|  | ||||
| impl From<Bq34Z100Error<esp_idf_hal::i2c::I2cError>> for BatteryError { | ||||
|     fn from(err: Bq34Z100Error<esp_idf_hal::i2c::I2cError>) -> Self { | ||||
|         BatteryError::CommunicationError( | ||||
|             anyhow!("failed to communicate with battery monitor: {:?}", err).to_string(), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| // impl From<Bq34Z100Error<esp_idf_hal::i2c::I2cError>> for BatteryError { | ||||
| //     fn from(err: Bq34Z100Error<esp_idf_hal::i2c::I2cError>) -> Self { | ||||
| //         BatteryError::CommunicationError( | ||||
| //             anyhow!("failed to communicate with battery monitor: {:?}", err).to_string(), | ||||
| //         ) | ||||
| //     } | ||||
| // } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| pub enum BatteryState { | ||||
| @@ -51,45 +54,45 @@ pub enum BatteryState { | ||||
|  | ||||
| /// If no battery monitor is installed this implementation will be used | ||||
| pub struct NoBatteryMonitor {} | ||||
|  | ||||
| #[async_trait] | ||||
| impl BatteryInteraction for NoBatteryMonitor { | ||||
|     fn state_charge_percent(&mut self) -> Result<f32, BatteryError> { | ||||
|     async fn state_charge_percent(& mut self) -> Result<f32, BatteryError> { | ||||
|         Err(BatteryError::NoBatteryMonitor) | ||||
|     } | ||||
|  | ||||
|     fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { | ||||
|     async fn remaining_milli_ampere_hour(& mut self) -> Result<u16, BatteryError> { | ||||
|         Err(BatteryError::NoBatteryMonitor) | ||||
|     } | ||||
|  | ||||
|     fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { | ||||
|     async fn max_milli_ampere_hour(& mut self) -> Result<u16, BatteryError> { | ||||
|         Err(BatteryError::NoBatteryMonitor) | ||||
|     } | ||||
|  | ||||
|     fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { | ||||
|     async fn design_milli_ampere_hour(& mut self) -> Result<u16, BatteryError> { | ||||
|         Err(BatteryError::NoBatteryMonitor) | ||||
|     } | ||||
|  | ||||
|     fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> { | ||||
|     async fn voltage_milli_volt(& mut self) -> Result<u16, BatteryError> { | ||||
|         Err(BatteryError::NoBatteryMonitor) | ||||
|     } | ||||
|  | ||||
|     fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> { | ||||
|     async fn average_current_milli_ampere(& mut self) -> Result<i16, BatteryError> { | ||||
|         Err(BatteryError::NoBatteryMonitor) | ||||
|     } | ||||
|  | ||||
|     fn cycle_count(&mut self) -> Result<u16, BatteryError> { | ||||
|     async fn cycle_count(& mut self) -> Result<u16, BatteryError> { | ||||
|         Err(BatteryError::NoBatteryMonitor) | ||||
|     } | ||||
|  | ||||
|     fn state_health_percent(&mut self) -> Result<u16, BatteryError> { | ||||
|     async fn state_health_percent(&mut self) -> Result<u16, BatteryError> { | ||||
|         Err(BatteryError::NoBatteryMonitor) | ||||
|     } | ||||
|  | ||||
|     fn bat_temperature(&mut self) -> Result<u16, BatteryError> { | ||||
|     async fn bat_temperature(&mut self) -> Result<u16, BatteryError> { | ||||
|         Err(BatteryError::NoBatteryMonitor) | ||||
|     } | ||||
|  | ||||
|     fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> { | ||||
|     async fn get_battery_state(& mut self) -> Result<BatteryState, BatteryError> { | ||||
|         Ok(BatteryState::Unknown) | ||||
|     } | ||||
| } | ||||
| @@ -98,115 +101,115 @@ impl BatteryInteraction for NoBatteryMonitor { | ||||
| #[allow(dead_code)] | ||||
| pub struct WchI2cSlave {} | ||||
|  | ||||
| pub struct BQ34Z100G1<'a> { | ||||
|     pub battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>, | ||||
| } | ||||
|  | ||||
| impl BatteryInteraction for BQ34Z100G1<'_> { | ||||
|     fn state_charge_percent(&mut self) -> Result<f32, BatteryError> { | ||||
|         Ok(self.battery_driver.state_of_charge().map(f32::from)?) | ||||
|     } | ||||
|  | ||||
|     fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { | ||||
|         Ok(self.battery_driver.remaining_capacity()?) | ||||
|     } | ||||
|  | ||||
|     fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { | ||||
|         Ok(self.battery_driver.full_charge_capacity()?) | ||||
|     } | ||||
|  | ||||
|     fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { | ||||
|         Ok(self.battery_driver.design_capacity()?) | ||||
|     } | ||||
|  | ||||
|     fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> { | ||||
|         Ok(self.battery_driver.voltage()?) | ||||
|     } | ||||
|  | ||||
|     fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> { | ||||
|         Ok(self.battery_driver.average_current()?) | ||||
|     } | ||||
|  | ||||
|     fn cycle_count(&mut self) -> Result<u16, BatteryError> { | ||||
|         Ok(self.battery_driver.cycle_count()?) | ||||
|     } | ||||
|  | ||||
|     fn state_health_percent(&mut self) -> Result<u16, BatteryError> { | ||||
|         Ok(self.battery_driver.state_of_health()?) | ||||
|     } | ||||
|  | ||||
|     fn bat_temperature(&mut self) -> Result<u16, BatteryError> { | ||||
|         Ok(self.battery_driver.temperature()?) | ||||
|     } | ||||
|  | ||||
|     fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> { | ||||
|         Ok(BatteryState::Info(BatteryInfo { | ||||
|             voltage_milli_volt: self.voltage_milli_volt()?, | ||||
|             average_current_milli_ampere: self.average_current_milli_ampere()?, | ||||
|             cycle_count: self.cycle_count()?, | ||||
|             design_milli_ampere_hour: self.design_milli_ampere_hour()?, | ||||
|             remaining_milli_ampere_hour: self.remaining_milli_ampere_hour()?, | ||||
|             state_of_charge: self.state_charge_percent()?, | ||||
|             state_of_health: self.state_health_percent()?, | ||||
|             temperature: self.bat_temperature()?, | ||||
|         })) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn print_battery_bq34z100( | ||||
|     battery_driver: &mut Bq34z100g1Driver<MutexDevice<I2cDriver<'_>>, Delay>, | ||||
| ) -> anyhow::Result<(), Bq34Z100Error<I2cError>> { | ||||
|     log::info!("Try communicating with battery"); | ||||
|     let fwversion = battery_driver.fw_version().unwrap_or_else(|e| { | ||||
|         log::info!("Firmware {:?}", e); | ||||
|         0 | ||||
|     }); | ||||
|     log::info!("fw version is {}", fwversion); | ||||
|  | ||||
|     let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| { | ||||
|         log::info!("Design capacity {:?}", e); | ||||
|         0 | ||||
|     }); | ||||
|     log::info!("Design Capacity {}", design_capacity); | ||||
|     if design_capacity == 1000 { | ||||
|         log::info!("Still stock configuring battery, readouts are likely to be wrong!"); | ||||
|     } | ||||
|  | ||||
|     let flags = battery_driver.get_flags_decoded()?; | ||||
|     log::info!("Flags {:?}", flags); | ||||
|  | ||||
|     let chem_id = battery_driver.chem_id().unwrap_or_else(|e| { | ||||
|         log::info!("Chemid {:?}", e); | ||||
|         0 | ||||
|     }); | ||||
|  | ||||
|     let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| { | ||||
|         log::info!("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| { | ||||
|         log::info!("Bat volt {:?}", e); | ||||
|         0 | ||||
|     }); | ||||
|     let current = battery_driver.current().unwrap_or_else(|e| { | ||||
|         log::info!("Bat current {:?}", e); | ||||
|         0 | ||||
|     }); | ||||
|     let state = battery_driver.state_of_charge().unwrap_or_else(|e| { | ||||
|         log::info!("Bat Soc {:?}", e); | ||||
|         0 | ||||
|     }); | ||||
|     let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| { | ||||
|         log::info!("Bat Charge Volt {:?}", e); | ||||
|         0 | ||||
|     }); | ||||
|     let charge_current = battery_driver.charge_current().unwrap_or_else(|e| { | ||||
|         log::info!("Bat Charge Current {:?}", e); | ||||
|         0 | ||||
|     }); | ||||
|     log::info!("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(); | ||||
|     anyhow::Result::Ok(()) | ||||
| } | ||||
| // pub struct BQ34Z100G1<'a> { | ||||
| //     pub battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>, | ||||
| // } | ||||
| // | ||||
| // impl BatteryInteraction for BQ34Z100G1<'_> { | ||||
| //     fn state_charge_percent(&mut self) -> Result<f32, BatteryError> { | ||||
| //         Ok(self.battery_driver.state_of_charge().map(f32::from)?) | ||||
| //     } | ||||
| // | ||||
| //     fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { | ||||
| //         Ok(self.battery_driver.remaining_capacity()?) | ||||
| //     } | ||||
| // | ||||
| //     fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { | ||||
| //         Ok(self.battery_driver.full_charge_capacity()?) | ||||
| //     } | ||||
| // | ||||
| //     fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { | ||||
| //         Ok(self.battery_driver.design_capacity()?) | ||||
| //     } | ||||
| // | ||||
| //     fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> { | ||||
| //         Ok(self.battery_driver.voltage()?) | ||||
| //     } | ||||
| // | ||||
| //     fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> { | ||||
| //         Ok(self.battery_driver.average_current()?) | ||||
| //     } | ||||
| // | ||||
| //     fn cycle_count(&mut self) -> Result<u16, BatteryError> { | ||||
| //         Ok(self.battery_driver.cycle_count()?) | ||||
| //     } | ||||
| // | ||||
| //     fn state_health_percent(&mut self) -> Result<u16, BatteryError> { | ||||
| //         Ok(self.battery_driver.state_of_health()?) | ||||
| //     } | ||||
| // | ||||
| //     fn bat_temperature(&mut self) -> Result<u16, BatteryError> { | ||||
| //         Ok(self.battery_driver.temperature()?) | ||||
| //     } | ||||
| // | ||||
| //     fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> { | ||||
| //         Ok(BatteryState::Info(BatteryInfo { | ||||
| //             voltage_milli_volt: self.voltage_milli_volt()?, | ||||
| //             average_current_milli_ampere: self.average_current_milli_ampere()?, | ||||
| //             cycle_count: self.cycle_count()?, | ||||
| //             design_milli_ampere_hour: self.design_milli_ampere_hour()?, | ||||
| //             remaining_milli_ampere_hour: self.remaining_milli_ampere_hour()?, | ||||
| //             state_of_charge: self.state_charge_percent()?, | ||||
| //             state_of_health: self.state_health_percent()?, | ||||
| //             temperature: self.bat_temperature()?, | ||||
| //         })) | ||||
| //     } | ||||
| // } | ||||
| // | ||||
| // pub fn print_battery_bq34z100( | ||||
| //     battery_driver: &mut Bq34z100g1Driver<MutexDevice<I2cDriver<'_>>, Delay>, | ||||
| // ) -> anyhow::Result<(), Bq34Z100Error<I2cError>> { | ||||
| //     log::info!("Try communicating with battery"); | ||||
| //     let fwversion = battery_driver.fw_version().unwrap_or_else(|e| { | ||||
| //         log::info!("Firmware {:?}", e); | ||||
| //         0 | ||||
| //     }); | ||||
| //     log::info!("fw version is {}", fwversion); | ||||
| // | ||||
| //     let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| { | ||||
| //         log::info!("Design capacity {:?}", e); | ||||
| //         0 | ||||
| //     }); | ||||
| //     log::info!("Design Capacity {}", design_capacity); | ||||
| //     if design_capacity == 1000 { | ||||
| //         log::info!("Still stock configuring battery, readouts are likely to be wrong!"); | ||||
| //     } | ||||
| // | ||||
| //     let flags = battery_driver.get_flags_decoded()?; | ||||
| //     log::info!("Flags {:?}", flags); | ||||
| // | ||||
| //     let chem_id = battery_driver.chem_id().unwrap_or_else(|e| { | ||||
| //         log::info!("Chemid {:?}", e); | ||||
| //         0 | ||||
| //     }); | ||||
| // | ||||
| //     let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| { | ||||
| //         log::info!("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| { | ||||
| //         log::info!("Bat volt {:?}", e); | ||||
| //         0 | ||||
| //     }); | ||||
| //     let current = battery_driver.current().unwrap_or_else(|e| { | ||||
| //         log::info!("Bat current {:?}", e); | ||||
| //         0 | ||||
| //     }); | ||||
| //     let state = battery_driver.state_of_charge().unwrap_or_else(|e| { | ||||
| //         log::info!("Bat Soc {:?}", e); | ||||
| //         0 | ||||
| //     }); | ||||
| //     let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| { | ||||
| //         log::info!("Bat Charge Volt {:?}", e); | ||||
| //         0 | ||||
| //     }); | ||||
| //     let charge_current = battery_driver.charge_current().unwrap_or_else(|e| { | ||||
| //         log::info!("Bat Charge Current {:?}", e); | ||||
| //         0 | ||||
| //     }); | ||||
| //     log::info!("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(); | ||||
| //     anyhow::Result::Ok(()) | ||||
| // } | ||||
|   | ||||
| @@ -10,6 +10,10 @@ use alloc::{ | ||||
|     string::{String, ToString}, | ||||
|     vec::Vec, | ||||
| }; | ||||
| use core::marker::PhantomData; | ||||
| use core::net::IpAddr; | ||||
| use core::str::FromStr; | ||||
| use embassy_time::Timer; | ||||
|  | ||||
| #[link_section = ".rtc.data"] | ||||
| static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; | ||||
| @@ -42,15 +46,23 @@ pub struct FileSystemSizeInfo { | ||||
| } | ||||
|  | ||||
| pub struct MqttClient<'a> { | ||||
|     dummy: PhantomData<&'a ()>, | ||||
|     //mqtt_client: EspMqttClient<'a>, | ||||
|     base_topic: heapless::String<64>, | ||||
| } | ||||
| pub struct Esp<'a> { | ||||
|     pub(crate) mqtt_client: Option<MqttClient<'a>>, | ||||
|     pub(crate) dummy: PhantomData<&'a ()>, | ||||
|     //pub(crate) wifi_driver: EspWifi<'a>, | ||||
|     //pub(crate) boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, | ||||
| } | ||||
|  | ||||
| pub struct IpInfo { | ||||
|     pub(crate) ip: IpAddr, | ||||
|     netmask: IpAddr, | ||||
|     gateway: IpAddr, | ||||
| } | ||||
|  | ||||
| struct AccessPointInfo {} | ||||
|  | ||||
| impl Esp<'_> { | ||||
| @@ -76,23 +88,25 @@ impl Esp<'_> { | ||||
|         todo!(); | ||||
|     } | ||||
|     pub(crate) fn time(&mut self) -> anyhow::Result<DateTime<Utc>> { | ||||
|         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"))?; | ||||
|         anyhow::Ok(local_time) | ||||
|         bail!("todo"); | ||||
|         // 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"))?; | ||||
|         // anyhow::Ok(local_time) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn wifi_scan(&mut self) -> anyhow::Result<Vec<AccessPointInfo>> { | ||||
|         self.wifi_driver.start_scan( | ||||
|             &ScanConfig { | ||||
|                 scan_type: ScanType::Passive(Duration::from_secs(5)), | ||||
|                 show_hidden: false, | ||||
|                 ..Default::default() | ||||
|             }, | ||||
|             true, | ||||
|         )?; | ||||
|         anyhow::Ok(self.wifi_driver.get_scan_result()?) | ||||
|         bail!("todo"); | ||||
|         // self.wifi_driver.start_scan( | ||||
|         //     &ScanConfig { | ||||
|         //         scan_type: ScanType::Passive(Duration::from_secs(5)), | ||||
|         //         show_hidden: false, | ||||
|         //         ..Default::default() | ||||
|         //     }, | ||||
|         //     true, | ||||
|         // )?; | ||||
|         // anyhow::Ok(self.wifi_driver.get_scan_result()?) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>> { | ||||
| @@ -140,19 +154,22 @@ impl Esp<'_> { | ||||
|             Ok(config) => config.network.ap_ssid.clone(), | ||||
|             Err(_) => heapless::String::from_str("PlantCtrl Emergency Mode").unwrap(), | ||||
|         }; | ||||
|  | ||||
|         let apconfig = AccessPointConfiguration { | ||||
|             ssid, | ||||
|             auth_method: AuthMethod::None, | ||||
|             ssid_hidden: false, | ||||
|             ..Default::default() | ||||
|         }; | ||||
|         self.wifi_driver | ||||
|             .set_configuration(&Configuration::AccessPoint(apconfig))?; | ||||
|         self.wifi_driver.start()?; | ||||
|         anyhow::Ok(()) | ||||
|         todo!("todo"); | ||||
|         // | ||||
|         // let apconfig = AccessPointConfiguration { | ||||
|         //     ssid, | ||||
|         //     auth_method: AuthMethod::None, | ||||
|         //     ssid_hidden: false, | ||||
|         //     ..Default::default() | ||||
|         // }; | ||||
|         // self.wifi_driver | ||||
|         //     .set_configuration(&Configuration::AccessPoint(apconfig))?; | ||||
|         // self.wifi_driver.start()?; | ||||
|         // anyhow::Ok(()) | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> anyhow::Result<IpInfo> { | ||||
|         let ssid = network_config | ||||
|             .ssid | ||||
| @@ -160,80 +177,83 @@ impl Esp<'_> { | ||||
|             .ok_or(anyhow!("No ssid configured"))?; | ||||
|         let password = network_config.password.clone(); | ||||
|         let max_wait = network_config.max_wait; | ||||
|  | ||||
|         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, | ||||
|                         password: pw, | ||||
|                         ..Default::default() | ||||
|                     }, | ||||
|                 ))?; | ||||
|             } | ||||
|             None => { | ||||
|                 self.wifi_driver.set_configuration(&Configuration::Client( | ||||
|                     ClientConfiguration { | ||||
|                         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()? { | ||||
|             delay.delay_ms(250); | ||||
|             counter += 250; | ||||
|             if counter > max_wait { | ||||
|                 //ignore these errors, Wi-Fi 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"); | ||||
|             } | ||||
|         } | ||||
|         log::info!("Should be connected now, waiting for link to be up"); | ||||
|  | ||||
|         while !self.wifi_driver.is_up()? { | ||||
|             delay.delay_ms(250); | ||||
|             counter += 250; | ||||
|             if counter > max_wait { | ||||
|                 //ignore these errors, Wi-Fi 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()?; | ||||
|         log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}")); | ||||
|         anyhow::Ok(address) | ||||
|         bail!("todo") | ||||
|         // 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, | ||||
|         //                 password: pw, | ||||
|         //                 ..Default::default() | ||||
|         //             }, | ||||
|         //         ))?; | ||||
|         //     } | ||||
|         //     None => { | ||||
|         //         self.wifi_driver.set_configuration(&Configuration::Client( | ||||
|         //             ClientConfiguration { | ||||
|         //                 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()? { | ||||
|         //     delay.delay_ms(250); | ||||
|         //     counter += 250; | ||||
|         //     if counter > max_wait { | ||||
|         //         //ignore these errors, Wi-Fi 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"); | ||||
|         //     } | ||||
|         // } | ||||
|         // log::info!("Should be connected now, waiting for link to be up"); | ||||
|         // | ||||
|         // while !self.wifi_driver.is_up()? { | ||||
|         //     delay.delay_ms(250); | ||||
|         //     counter += 250; | ||||
|         //     if counter > max_wait { | ||||
|         //         //ignore these errors, Wi-Fi 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()?; | ||||
|         // log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}")); | ||||
|         // anyhow::Ok(address) | ||||
|     } | ||||
|     pub(crate) async fn load_config(&mut self) -> anyhow::Result<PlantControllerConfig> { | ||||
|         let cfg = File::open(Self::CONFIG_FILE)?; | ||||
|         let config: PlantControllerConfig = serde_json::from_reader(cfg)?; | ||||
|         anyhow::Ok(config) | ||||
|     pub(crate) fn load_config(&mut self) -> anyhow::Result<PlantControllerConfig> { | ||||
|         bail!("todo"); | ||||
|         // let cfg = File::open(Self::CONFIG_FILE)?; | ||||
|         // let config: PlantControllerConfig = serde_json::from_reader(cfg)?; | ||||
|         // anyhow::Ok(config) | ||||
|     } | ||||
|     pub(crate) async fn save_config( | ||||
|         &mut self, | ||||
|         config: &PlantControllerConfig, | ||||
|     ) -> anyhow::Result<()> { | ||||
|         let mut cfg = File::create(Self::CONFIG_FILE)?; | ||||
|         serde_json::to_writer(&mut cfg, &config)?; | ||||
|         log::info!("Wrote config config {:?}", config); | ||||
|         anyhow::Ok(()) | ||||
|         bail!("todo"); | ||||
|         // let mut cfg = File::create(Self::CONFIG_FILE)?; | ||||
|         // serde_json::to_writer(&mut cfg, &config)?; | ||||
|         // log::info!("Wrote config config {:?}", config); | ||||
|         // anyhow::Ok(()) | ||||
|     } | ||||
|     pub(crate) async fn mount_file_system(&mut self) -> anyhow::Result<()> { | ||||
|     pub(crate) fn mount_file_system(&mut self) -> anyhow::Result<()> { | ||||
|         bail!("fail"); | ||||
|         log(LogMessage::MountingFilesystem, 0, 0, "", ""); | ||||
|         let base_path = String::try_from("/spiffs")?; | ||||
|         let storage = String::try_from(Self::SPIFFS_PARTITION_NAME)?; | ||||
|         let conf = todo!(); | ||||
|         //let conf = todo!(); | ||||
|  | ||||
|         //let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { | ||||
|         //base_path: base_path.as_ptr(), | ||||
| @@ -247,102 +267,112 @@ impl Esp<'_> { | ||||
|         //esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?; | ||||
|         //} | ||||
|  | ||||
|         let free_space = self.file_system_size()?; | ||||
|         log( | ||||
|             LogMessage::FilesystemMount, | ||||
|             free_space.free_size as u32, | ||||
|             free_space.total_size as u32, | ||||
|             &free_space.used_size.to_string(), | ||||
|             "", | ||||
|         ); | ||||
|         // let free_space = self.file_system_size()?; | ||||
|         // log( | ||||
|         //     LogMessage::FilesystemMount, | ||||
|         //     free_space.free_size as u32, | ||||
|         //     free_space.total_size as u32, | ||||
|         //     &free_space.used_size.to_string(), | ||||
|         //     "", | ||||
|         // ); | ||||
|         anyhow::Ok(()) | ||||
|     } | ||||
|     async fn file_system_size(&mut self) -> anyhow::Result<FileSystemSizeInfo> { | ||||
|         let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?; | ||||
|         let mut total_size = 0; | ||||
|         let mut used_size = 0; | ||||
|         unsafe { | ||||
|             esp_idf_sys::esp!(esp_spiffs_info( | ||||
|                 storage.as_ptr(), | ||||
|                 &mut total_size, | ||||
|                 &mut used_size | ||||
|             ))?; | ||||
|         } | ||||
|         anyhow::Ok(FileSystemSizeInfo { | ||||
|             total_size, | ||||
|             used_size, | ||||
|             free_size: total_size - used_size, | ||||
|         }) | ||||
|         bail!("fail"); | ||||
|         // let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?; | ||||
|         // let mut total_size = 0; | ||||
|         // let mut used_size = 0; | ||||
|         // unsafe { | ||||
|         //     esp_idf_sys::esp!(esp_spiffs_info( | ||||
|         //         storage.as_ptr(), | ||||
|         //         &mut total_size, | ||||
|         //         &mut used_size | ||||
|         //     ))?; | ||||
|         // } | ||||
|         // anyhow::Ok(FileSystemSizeInfo { | ||||
|         //     total_size, | ||||
|         //     used_size, | ||||
|         //     free_size: total_size - used_size, | ||||
|         // }) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn list_files(&self) -> FileList { | ||||
|         let storage = CString::new(Self::SPIFFS_PARTITION_NAME).unwrap(); | ||||
|  | ||||
|         let mut file_system_corrupt = None; | ||||
|  | ||||
|         let mut iter_error = None; | ||||
|         let mut result = Vec::new(); | ||||
|  | ||||
|         let filepath = Path::new(Self::BASE_PATH); | ||||
|         let read_dir = fs::read_dir(filepath); | ||||
|         match read_dir { | ||||
|             OkStd(read_dir) => { | ||||
|                 for item in read_dir { | ||||
|                     match item { | ||||
|                         OkStd(file) => { | ||||
|                             let f = FileInfo { | ||||
|                                 filename: file.file_name().into_string().unwrap(), | ||||
|                                 size: file.metadata().map(|it| it.len()).unwrap_or_default() | ||||
|                                     as usize, | ||||
|                             }; | ||||
|                             result.push(f); | ||||
|                         } | ||||
|                         Err(err) => { | ||||
|                             iter_error = Some(format!("{err:?}")); | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 file_system_corrupt = Some(format!("{err:?}")); | ||||
|             } | ||||
|         } | ||||
|         let mut total: usize = 0; | ||||
|         let mut used: usize = 0; | ||||
|         unsafe { | ||||
|             esp_spiffs_info(storage.as_ptr(), &mut total, &mut used); | ||||
|         } | ||||
|  | ||||
|         FileList { | ||||
|             total, | ||||
|             used, | ||||
|             file_system_corrupt, | ||||
|             files: result, | ||||
|             iter_error, | ||||
|         } | ||||
|         return FileList { | ||||
|             total: 0, | ||||
|             used: 0, | ||||
|             file_system_corrupt: None, | ||||
|             files: Vec::new(), | ||||
|             iter_error: None, | ||||
|         }; | ||||
|         // | ||||
|         // let storage = CString::new(Self::SPIFFS_PARTITION_NAME).unwrap(); | ||||
|         // | ||||
|         // let mut file_system_corrupt = None; | ||||
|         // | ||||
|         // let mut iter_error = None; | ||||
|         // let mut result = Vec::new(); | ||||
|         // | ||||
|         // let filepath = Path::new(Self::BASE_PATH); | ||||
|         // let read_dir = fs::read_dir(filepath); | ||||
|         // match read_dir { | ||||
|         //     OkStd(read_dir) => { | ||||
|         //         for item in read_dir { | ||||
|         //             match item { | ||||
|         //                 OkStd(file) => { | ||||
|         //                     let f = FileInfo { | ||||
|         //                         filename: file.file_name().into_string().unwrap(), | ||||
|         //                         size: file.metadata().map(|it| it.len()).unwrap_or_default() | ||||
|         //                             as usize, | ||||
|         //                     }; | ||||
|         //                     result.push(f); | ||||
|         //                 } | ||||
|         //                 Err(err) => { | ||||
|         //                     iter_error = Some(format!("{err:?}")); | ||||
|         //                     break; | ||||
|         //                 } | ||||
|         //             } | ||||
|         //         } | ||||
|         //     } | ||||
|         //     Err(err) => { | ||||
|         //         file_system_corrupt = Some(format!("{err:?}")); | ||||
|         //     } | ||||
|         // } | ||||
|         // let mut total: usize = 0; | ||||
|         // let mut used: usize = 0; | ||||
|         // unsafe { | ||||
|         //     esp_spiffs_info(storage.as_ptr(), &mut total, &mut used); | ||||
|         // } | ||||
|         // | ||||
|         // FileList { | ||||
|         //     total, | ||||
|         //     used, | ||||
|         //     file_system_corrupt, | ||||
|         //     files: result, | ||||
|         //     iter_error, | ||||
|         // } | ||||
|     } | ||||
|     pub(crate) async fn delete_file(&self, filename: &str) -> anyhow::Result<()> { | ||||
|         let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); | ||||
|         match fs::remove_file(filepath) { | ||||
|             OkStd(_) => anyhow::Ok(()), | ||||
|             Err(err) => { | ||||
|                 bail!(format!("{err:?}")) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     pub(crate) async fn get_file_handle( | ||||
|         &self, | ||||
|         filename: &str, | ||||
|         write: bool, | ||||
|     ) -> anyhow::Result<File> { | ||||
|         let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); | ||||
|         anyhow::Ok(if write { | ||||
|             File::create(filepath)? | ||||
|         } else { | ||||
|             File::open(filepath)? | ||||
|         }) | ||||
|         bail!("todo"); | ||||
|         // let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); | ||||
|         // match fs::remove_file(filepath) { | ||||
|         //     OkStd(_) => anyhow::Ok(()), | ||||
|         //     Err(err) => { | ||||
|         //         bail!(format!("{err:?}")) | ||||
|         //     } | ||||
|         // } | ||||
|     } | ||||
|     // pub(crate) async fn get_file_handle( | ||||
|     //     &self, | ||||
|     //     filename: &str, | ||||
|     //     write: bool, | ||||
|     // ) -> anyhow::Result<File> { | ||||
|     //     let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename)); | ||||
|     //     anyhow::Ok(if write { | ||||
|     //         File::create(filepath)? | ||||
|     //     } else { | ||||
|     //         File::open(filepath)? | ||||
|     //     }) | ||||
|     // } | ||||
|  | ||||
|     pub(crate) fn init_rtc_deepsleep_memory(&self, init_rtc_store: bool, to_config_mode: bool) { | ||||
|         if init_rtc_store { | ||||
| @@ -407,200 +437,204 @@ impl Esp<'_> { | ||||
|             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)), | ||||
|             username: network_config.mqtt_user.as_ref().map(|v| &**v), | ||||
|             password: network_config.mqtt_password.as_ref().map(|v| &**v), | ||||
|             //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); | ||||
|         log(LogMessage::StayAlive, 0, 0, "", &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(); | ||||
|         let client_id = mqtt_client_config.client_id.unwrap_or("not set"); | ||||
|         log(LogMessage::MqttInfo, 0, 0, client_id, mqtt_url); | ||||
|         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"); | ||||
|                             log(LogMessage::MqttStayAliveRec, 0, 0, &data, ""); | ||||
|                             STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed); | ||||
|                         } else { | ||||
|                             log(LogMessage::UnknownTopic, 0, 0, "", 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); | ||||
|                     log::info!("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); | ||||
|                     log::info!("Mqtt disconnected"); | ||||
|                 } | ||||
|                 esp_idf_svc::mqtt::client::EventPayload::Error(esp_error) => { | ||||
|                     log::info!("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); | ||||
|                     log::info!("Mqtt error"); | ||||
|                 } | ||||
|                 esp_idf_svc::mqtt::client::EventPayload::BeforeConnect => { | ||||
|                     log::info!("Mqtt before connect") | ||||
|                 } | ||||
|                 esp_idf_svc::mqtt::client::EventPayload::Subscribed(_) => { | ||||
|                     log::info!("Mqtt subscribed") | ||||
|                 } | ||||
|                 esp_idf_svc::mqtt::client::EventPayload::Unsubscribed(_) => { | ||||
|                     log::info!("Mqtt unsubscribed") | ||||
|                 } | ||||
|                 esp_idf_svc::mqtt::client::EventPayload::Published(_) => { | ||||
|                     log::info!("Mqtt published") | ||||
|                 } | ||||
|                 esp_idf_svc::mqtt::client::EventPayload::Deleted(_) => { | ||||
|                     log::info!("Mqtt deleted") | ||||
|                 } | ||||
|             } | ||||
|         })?; | ||||
|  | ||||
|         let mut wait_for_connections_event = 0; | ||||
|         while wait_for_connections_event < 100 { | ||||
|             wait_for_connections_event += 1; | ||||
|             match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) { | ||||
|                 true => { | ||||
|                     log::info!("Mqtt connection callback received, progressing"); | ||||
|                     match mqtt_connected_event_ok.load(std::sync::atomic::Ordering::Relaxed) { | ||||
|                         true => { | ||||
|                             log::info!( | ||||
|                                 "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 mut wait_for_roundtrip = 0; | ||||
|                             while wait_for_roundtrip < 100 { | ||||
|                                 wait_for_roundtrip += 1; | ||||
|                                 match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) { | ||||
|                                     true => { | ||||
|                                         log::info!("Round trip registered, proceeding"); | ||||
|                                         self.mqtt_client = Some(MqttClient { | ||||
|                                             mqtt_client: client, | ||||
|                                             base_topic: base_topic_copy, | ||||
|                                         }); | ||||
|                                         return anyhow::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"); | ||||
|         bail!("todo"); | ||||
|         // | ||||
|         // 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)), | ||||
|         //     username: network_config.mqtt_user.as_ref().map(|v| &**v), | ||||
|         //     password: network_config.mqtt_password.as_ref().map(|v| &**v), | ||||
|         //     //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); | ||||
|         // log(LogMessage::StayAlive, 0, 0, "", &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(); | ||||
|         // let client_id = mqtt_client_config.client_id.unwrap_or("not set"); | ||||
|         // log(LogMessage::MqttInfo, 0, 0, client_id, mqtt_url); | ||||
|         // 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"); | ||||
|         //                     log(LogMessage::MqttStayAliveRec, 0, 0, &data, ""); | ||||
|         //                     STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed); | ||||
|         //                 } else { | ||||
|         //                     log(LogMessage::UnknownTopic, 0, 0, "", 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); | ||||
|         //             log::info!("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); | ||||
|         //             log::info!("Mqtt disconnected"); | ||||
|         //         } | ||||
|         //         esp_idf_svc::mqtt::client::EventPayload::Error(esp_error) => { | ||||
|         //             log::info!("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); | ||||
|         //             log::info!("Mqtt error"); | ||||
|         //         } | ||||
|         //         esp_idf_svc::mqtt::client::EventPayload::BeforeConnect => { | ||||
|         //             log::info!("Mqtt before connect") | ||||
|         //         } | ||||
|         //         esp_idf_svc::mqtt::client::EventPayload::Subscribed(_) => { | ||||
|         //             log::info!("Mqtt subscribed") | ||||
|         //         } | ||||
|         //         esp_idf_svc::mqtt::client::EventPayload::Unsubscribed(_) => { | ||||
|         //             log::info!("Mqtt unsubscribed") | ||||
|         //         } | ||||
|         //         esp_idf_svc::mqtt::client::EventPayload::Published(_) => { | ||||
|         //             log::info!("Mqtt published") | ||||
|         //         } | ||||
|         //         esp_idf_svc::mqtt::client::EventPayload::Deleted(_) => { | ||||
|         //             log::info!("Mqtt deleted") | ||||
|         //         } | ||||
|         //     } | ||||
|         // })?; | ||||
|         // | ||||
|         // let mut wait_for_connections_event = 0; | ||||
|         // while wait_for_connections_event < 100 { | ||||
|         //     wait_for_connections_event += 1; | ||||
|         //     match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) { | ||||
|         //         true => { | ||||
|         //             log::info!("Mqtt connection callback received, progressing"); | ||||
|         //             match mqtt_connected_event_ok.load(std::sync::atomic::Ordering::Relaxed) { | ||||
|         //                 true => { | ||||
|         //                     log::info!( | ||||
|         //                         "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 mut wait_for_roundtrip = 0; | ||||
|         //                     while wait_for_roundtrip < 100 { | ||||
|         //                         wait_for_roundtrip += 1; | ||||
|         //                         match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) { | ||||
|         //                             true => { | ||||
|         //                                 log::info!("Round trip registered, proceeding"); | ||||
|         //                                 self.mqtt_client = Some(MqttClient { | ||||
|         //                                     mqtt_client: client, | ||||
|         //                                     base_topic: base_topic_copy, | ||||
|         //                                 }); | ||||
|         //                                 return anyhow::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(crate) async fn mqtt_publish( | ||||
|         &mut self, | ||||
|         subtopic: &str, | ||||
|         message: &[u8], | ||||
|     ) -> anyhow::Result<()> { | ||||
|         if self.mqtt_client.is_none() { | ||||
|             return anyhow::Ok(()); | ||||
|         } | ||||
|         if !subtopic.starts_with("/") { | ||||
|             log::info!("Subtopic without / at start {}", subtopic); | ||||
|             bail!("Subtopic without / at start {}", subtopic); | ||||
|         } | ||||
|         if subtopic.len() > 192 { | ||||
|             log::info!("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(client.base_topic.as_str()).is_err() { | ||||
|             log::info!("Some error assembling full_topic 1"); | ||||
|             bail!("Some error assembling full_topic 1") | ||||
|         }; | ||||
|         if full_topic.push_str(subtopic).is_err() { | ||||
|             log::info!("Some error assembling full_topic 2"); | ||||
|             bail!("Some error assembling full_topic 2") | ||||
|         }; | ||||
|         let publish = client | ||||
|             .mqtt_client | ||||
|             .publish(&full_topic, ExactlyOnce, true, message); | ||||
|         Delay::new(10).delay_ms(50); | ||||
|         match publish { | ||||
|             OkStd(message_id) => { | ||||
|                 log::info!( | ||||
|                     "Published mqtt topic {} with message {:#?} msgid is {:?}", | ||||
|                     full_topic, | ||||
|                     String::from_utf8_lossy(message), | ||||
|                     message_id | ||||
|                 ); | ||||
|                 anyhow::Ok(()) | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 log::info!( | ||||
|                     "Error during mqtt send on topic {} with message {:#?} error is {:?}", | ||||
|                     full_topic, | ||||
|                     String::from_utf8_lossy(message), | ||||
|                     err | ||||
|                 ); | ||||
|                 Err(err)? | ||||
|             } | ||||
|         } | ||||
|         bail!("todo"); | ||||
|         // | ||||
|         // if self.mqtt_client.is_none() { | ||||
|         //     return anyhow::Ok(()); | ||||
|         // } | ||||
|         // if !subtopic.starts_with("/") { | ||||
|         //     log::info!("Subtopic without / at start {}", subtopic); | ||||
|         //     bail!("Subtopic without / at start {}", subtopic); | ||||
|         // } | ||||
|         // if subtopic.len() > 192 { | ||||
|         //     log::info!("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(client.base_topic.as_str()).is_err() { | ||||
|         //     log::info!("Some error assembling full_topic 1"); | ||||
|         //     bail!("Some error assembling full_topic 1") | ||||
|         // }; | ||||
|         // if full_topic.push_str(subtopic).is_err() { | ||||
|         //     log::info!("Some error assembling full_topic 2"); | ||||
|         //     bail!("Some error assembling full_topic 2") | ||||
|         // }; | ||||
|         // let publish = client | ||||
|         //     .mqtt_client | ||||
|         //     .publish(&full_topic, ExactlyOnce, true, message); | ||||
|         // Timer::after_millis(10).await; | ||||
|         // match publish { | ||||
|         //     OkStd(message_id) => { | ||||
|         //         log::info!( | ||||
|         //             "Published mqtt topic {} with message {:#?} msgid is {:?}", | ||||
|         //             full_topic, | ||||
|         //             String::from_utf8_lossy(message), | ||||
|         //             message_id | ||||
|         //         ); | ||||
|         //         anyhow::Ok(()) | ||||
|         //     } | ||||
|         //     Err(err) => { | ||||
|         //         log::info!( | ||||
|         //             "Error during mqtt send on topic {} with message {:#?} error is {:?}", | ||||
|         //             full_topic, | ||||
|         //             String::from_utf8_lossy(message), | ||||
|         //             err | ||||
|         //         ); | ||||
|         //         Err(err)? | ||||
|         //     } | ||||
|         // } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,19 +1,21 @@ | ||||
| use alloc::vec::Vec; | ||||
| use crate::hal::esp::Esp; | ||||
| use crate::hal::rtc::{BackupHeader, RTCModuleInteraction}; | ||||
| use crate::hal::water::TankSensor; | ||||
| //use crate::hal::water::TankSensor; | ||||
| use crate::hal::{deep_sleep, BoardInteraction, FreePeripherals, Sensor}; | ||||
| use crate::{ | ||||
|     config::PlantControllerConfig, | ||||
|     hal::battery::{BatteryInteraction, NoBatteryMonitor}, | ||||
| }; | ||||
| use anyhow::{bail, Result}; | ||||
| use async_trait::async_trait; | ||||
| use chrono::{DateTime, Utc}; | ||||
| use embedded_hal::digital::OutputPin; | ||||
| use measurements::{Current, Voltage}; | ||||
| use crate::alloc::boxed::Box; | ||||
|  | ||||
| pub struct Initial<'a> { | ||||
|     pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
|     //pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
|     pub(crate) esp: Esp<'a>, | ||||
|     pub(crate) config: PlantControllerConfig, | ||||
|     pub(crate) battery: Box<dyn BatteryInteraction + Send>, | ||||
| @@ -22,44 +24,45 @@ pub struct Initial<'a> { | ||||
|  | ||||
| struct NoRTC {} | ||||
|  | ||||
| #[async_trait] | ||||
| impl RTCModuleInteraction for NoRTC { | ||||
|     fn get_backup_info(&mut self) -> Result<BackupHeader> { | ||||
|     async fn get_backup_info(&mut self) -> Result<BackupHeader> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn get_backup_config(&mut self) -> Result<Vec<u8>> { | ||||
|     async fn get_backup_config(&mut self) -> Result<Vec<u8>> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn backup_config(&mut self, _bytes: &[u8]) -> Result<()> { | ||||
|     async fn backup_config(&mut self, _bytes: &[u8]) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> { | ||||
|     async fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<()> { | ||||
|     async fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(crate) fn create_initial_board( | ||||
|     free_pins: FreePeripherals, | ||||
|     //free_pins: FreePeripherals, | ||||
|     fs_mount_error: bool, | ||||
|     config: PlantControllerConfig, | ||||
|     esp: Esp<'static>, | ||||
| ) -> Result<Box<dyn BoardInteraction<'static> + Send>> { | ||||
|     log::info!("Start initial"); | ||||
|     let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; | ||||
|     general_fault.set_pull(Pull::Floating)?; | ||||
|     general_fault.set_low()?; | ||||
|  | ||||
|     if fs_mount_error { | ||||
|         general_fault.set_high()? | ||||
|     } | ||||
|     // let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; | ||||
|     // general_fault.set_pull(Pull::Floating)?; | ||||
|     // general_fault.set_low()?; | ||||
|     // | ||||
|     // if fs_mount_error { | ||||
|     //     general_fault.set_high()? | ||||
|     // } | ||||
|     let v = Initial { | ||||
|         general_fault, | ||||
|         //general_fault, | ||||
|         config, | ||||
|         esp, | ||||
|         battery: Box::new(NoBatteryMonitor {}), | ||||
| @@ -68,10 +71,11 @@ pub(crate) fn create_initial_board( | ||||
|     Ok(Box::new(v)) | ||||
| } | ||||
|  | ||||
| #[async_trait] | ||||
| impl<'a> BoardInteraction<'a> for Initial<'a> { | ||||
|     fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> { | ||||
|         None | ||||
|     } | ||||
|     // fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> { | ||||
|     //     None | ||||
|     // } | ||||
|  | ||||
|     fn get_esp(&mut self) -> &mut Esp<'a> { | ||||
|         &mut self.esp | ||||
| @@ -103,41 +107,42 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn pump(&mut self, _plant: usize, _enable: bool) -> Result<()> { | ||||
|     async fn pump(&mut self, _plant: usize, _enable: bool) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn pump_current(&mut self, _plant: usize) -> Result<Current> { | ||||
|     async fn pump_current(&mut self, _plant: usize) -> Result<Current> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn fault(&mut self, _plant: usize, _enable: bool) -> Result<()> { | ||||
|     async fn fault(&mut self, _plant: usize, _enable: bool) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn measure_moisture_hz(&mut self, _plant: usize, _sensor: Sensor) -> Result<f32> { | ||||
|     async fn measure_moisture_hz(&mut self, _plant: usize, _sensor: Sensor) -> Result<f32> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn general_fault(&mut self, enable: bool) { | ||||
|         let _ = self.general_fault.set_state(enable.into()); | ||||
|     async fn general_fault(&mut self, enable: bool) { | ||||
|         //let _ = self.general_fault.set_state(enable.into()); | ||||
|     } | ||||
|  | ||||
|     fn test(&mut self) -> Result<()> { | ||||
|     async fn test(&mut self) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { | ||||
|     async fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { | ||||
|         self.config = config; | ||||
|         self.esp.save_config(&self.config)?; | ||||
|         //TODO | ||||
| //        self.esp.save_config(&self.config)?; | ||||
|         anyhow::Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_voltage(&mut self) -> Result<Voltage> { | ||||
|     async fn get_mptt_voltage(&mut self) -> Result<Voltage> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_current(&mut self) -> Result<Current> { | ||||
|     async fn get_mptt_current(&mut self) -> Result<Current> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,55 +2,33 @@ pub(crate) mod battery; | ||||
| mod esp; | ||||
| mod initial_hal; | ||||
| mod rtc; | ||||
| mod v3_hal; | ||||
| mod v4_hal; | ||||
| mod v4_sensor; | ||||
| mod water; | ||||
| //mod water; | ||||
|  | ||||
| use crate::alloc::string::ToString; | ||||
| use crate::hal::rtc::{DS3231Module, RTCModuleInteraction}; | ||||
| use crate::hal::water::TankSensor; | ||||
| use crate::hal::rtc::{RTCModuleInteraction}; | ||||
| //use crate::hal::water::TankSensor; | ||||
| use crate::{ | ||||
|     config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, | ||||
|     hal::{ | ||||
|         battery::{print_battery_bq34z100, BatteryInteraction, NoBatteryMonitor}, | ||||
|         battery::{BatteryInteraction, NoBatteryMonitor}, | ||||
|         esp::Esp, | ||||
|     }, | ||||
|     log::{log, LogMessage}, | ||||
| }; | ||||
| use alloc::boxed::Box; | ||||
| use alloc::format; | ||||
| use core::marker::PhantomData; | ||||
| use anyhow::{Ok, Result}; | ||||
| use async_trait::async_trait; | ||||
| use battery::BQ34Z100G1; | ||||
| //use battery::BQ34Z100G1; | ||||
| use bq34z100::Bq34z100g1Driver; | ||||
| use ds323x::{DateTimeAccess, Ds323x}; | ||||
| use eeprom24x::{Eeprom24x, SlaveAddr, Storage}; | ||||
| use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; | ||||
| use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex}; | ||||
| use embassy_sync::mutex::Mutex; | ||||
| use embassy_sync::{LazyLock, Mutex}; | ||||
| use embedded_hal_bus::i2c::MutexDevice; | ||||
| use esp_idf_hal::can::CAN; | ||||
| use esp_idf_hal::pcnt::PCNT1; | ||||
| use esp_idf_hal::{ | ||||
|     adc::ADC1, | ||||
|     delay::Delay, | ||||
|     gpio::{ | ||||
|         Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, Gpio18, | ||||
|         Gpio2, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, Gpio29, Gpio3, | ||||
|         Gpio30, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, IOPin, PinDriver, Pull, | ||||
|     }, | ||||
|     i2c::{APBTickType, I2cConfig, I2cDriver}, | ||||
|     pcnt::PCNT0, | ||||
|     prelude::Peripherals, | ||||
|     reset::ResetReason, | ||||
|     units::FromValueType, | ||||
| }; | ||||
| use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition, wifi::EspWifi}; | ||||
| use esp_idf_sys::{ | ||||
|     esp_deep_sleep, esp_restart, esp_sleep_enable_ext1_wakeup, | ||||
|     esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, | ||||
| }; | ||||
| use esp_ota::mark_app_valid; | ||||
| use embassy_sync::lazy_lock::LazyLock; | ||||
| use esp_hal::clock::CpuClock; | ||||
| use esp_hal::timer::systimer::SystemTimer; | ||||
| use measurements::{Current, Voltage}; | ||||
|  | ||||
| //Only support for 8 right now! | ||||
| @@ -58,24 +36,27 @@ pub const PLANT_COUNT: usize = 8; | ||||
|  | ||||
| const TANK_MULTI_SAMPLE: usize = 11; | ||||
|  | ||||
| pub static I2C_DRIVER: LazyLock<Mutex<I2cDriver<'static>>> = LazyLock::new(PlantHal::create_i2c); | ||||
| //pub static I2C_DRIVER: LazyLock<Mutex<CriticalSectionRawMutex,I2cDriver<'static>>> = LazyLock::new(PlantHal::create_i2c); | ||||
|  | ||||
| fn deep_sleep(duration_in_ms: u64) -> ! { | ||||
|     unsafe { | ||||
|         //if we don't do this here, we might just revert newly flashed firmware | ||||
|         mark_app_valid(); | ||||
|         //allow early wakeup by pressing the boot button | ||||
|         if duration_in_ms == 0 { | ||||
|             esp_restart(); | ||||
|         } else { | ||||
|             //configure gpio 1 to wakeup on low, reused boot button for this | ||||
|             esp_sleep_enable_ext1_wakeup( | ||||
|                 0b10u64, | ||||
|                 esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, | ||||
|             ); | ||||
|             esp_deep_sleep(duration_in_ms); | ||||
|     //unsafe { | ||||
|         // //if we don't do this here, we might just revert newly flashed firmware | ||||
|         // mark_app_valid(); | ||||
|         // //allow early wakeup by pressing the boot button | ||||
|         // if duration_in_ms == 0 { | ||||
|         //     esp_restart(); | ||||
|         // } else { | ||||
|         //     //configure gpio 1 to wakeup on low, reused boot button for this | ||||
|         //     esp_sleep_enable_ext1_wakeup( | ||||
|         //         0b10u64, | ||||
|         //         esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, | ||||
|         //     ); | ||||
|         //     esp_deep_sleep(duration_in_ms); | ||||
|         // } | ||||
|         loop { | ||||
|             todo!() | ||||
|         } | ||||
|     }; | ||||
|     //}; | ||||
| } | ||||
|  | ||||
| #[derive(Debug, PartialEq)] | ||||
| @@ -92,7 +73,7 @@ pub struct HAL<'a> { | ||||
|  | ||||
| #[async_trait] | ||||
| pub trait BoardInteraction<'a> { | ||||
|     fn get_tank_sensor(&mut self) -> Option<&mut TankSensor>; | ||||
|     //fn get_tank_sensor(&mut self) -> Option<&mut TankSensor>; | ||||
|     fn get_esp(&mut self) -> &mut Esp<'a>; | ||||
|     fn get_config(&mut self) -> &PlantControllerConfig; | ||||
|     fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send>; | ||||
| @@ -114,13 +95,14 @@ pub trait BoardInteraction<'a> { | ||||
|     async fn get_mptt_current(&mut self) -> anyhow::Result<Current>; | ||||
| } | ||||
|  | ||||
|  | ||||
| impl dyn BoardInteraction<'_> { | ||||
|     //the counter is just some arbitrary number that increases whenever some progress was made, try to keep the updates < 10 per second for ux reasons | ||||
|     async fn _progress(&mut self, counter: u32) { | ||||
|         let even = counter % 2 == 0; | ||||
|         let current = counter / (PLANT_COUNT as u32); | ||||
|         for led in 0..PLANT_COUNT { | ||||
|             self.fault(led, current == led as u32).unwrap(); | ||||
|             self.fault(led, current == led as u32).await.unwrap(); | ||||
|         } | ||||
|         let _ = self.general_fault(even.into()); | ||||
|     } | ||||
| @@ -128,134 +110,138 @@ impl dyn BoardInteraction<'_> { | ||||
|  | ||||
| #[allow(dead_code)] | ||||
| pub struct FreePeripherals { | ||||
|     pub gpio0: Gpio0, | ||||
|     pub gpio1: Gpio1, | ||||
|     pub gpio2: Gpio2, | ||||
|     pub gpio3: Gpio3, | ||||
|     pub gpio4: Gpio4, | ||||
|     pub gpio5: Gpio5, | ||||
|     pub gpio6: Gpio6, | ||||
|     pub gpio7: Gpio7, | ||||
|     pub gpio8: Gpio8, | ||||
|     //config button here | ||||
|     pub gpio10: Gpio10, | ||||
|     pub gpio11: Gpio11, | ||||
|     pub gpio12: Gpio12, | ||||
|     pub gpio13: Gpio13, | ||||
|     pub gpio14: Gpio14, | ||||
|     pub gpio15: Gpio15, | ||||
|     pub gpio16: Gpio16, | ||||
|     pub gpio17: Gpio17, | ||||
|     pub gpio18: Gpio18, | ||||
|     //i2c here | ||||
|     pub gpio21: Gpio21, | ||||
|     pub gpio22: Gpio22, | ||||
|     pub gpio23: Gpio23, | ||||
|     pub gpio24: Gpio24, | ||||
|     pub gpio25: Gpio25, | ||||
|     pub gpio26: Gpio26, | ||||
|     pub gpio27: Gpio27, | ||||
|     pub gpio28: Gpio28, | ||||
|     pub gpio29: Gpio29, | ||||
|     pub gpio30: Gpio30, | ||||
|     pub pcnt0: PCNT0, | ||||
|     pub pcnt1: PCNT1, | ||||
|     pub adc1: ADC1, | ||||
|     pub can: CAN, | ||||
|     // pub gpio0: Gpio0, | ||||
|     // pub gpio1: Gpio1, | ||||
|     // pub gpio2: Gpio2, | ||||
|     // pub gpio3: Gpio3, | ||||
|     // pub gpio4: Gpio4, | ||||
|     // pub gpio5: Gpio5, | ||||
|     // pub gpio6: Gpio6, | ||||
|     // pub gpio7: Gpio7, | ||||
|     // pub gpio8: Gpio8, | ||||
|     // //config button here | ||||
|     // pub gpio10: Gpio10, | ||||
|     // pub gpio11: Gpio11, | ||||
|     // pub gpio12: Gpio12, | ||||
|     // pub gpio13: Gpio13, | ||||
|     // pub gpio14: Gpio14, | ||||
|     // pub gpio15: Gpio15, | ||||
|     // pub gpio16: Gpio16, | ||||
|     // pub gpio17: Gpio17, | ||||
|     // pub gpio18: Gpio18, | ||||
|     // //i2c here | ||||
|     // pub gpio21: Gpio21, | ||||
|     // pub gpio22: Gpio22, | ||||
|     // pub gpio23: Gpio23, | ||||
|     // pub gpio24: Gpio24, | ||||
|     // pub gpio25: Gpio25, | ||||
|     // pub gpio26: Gpio26, | ||||
|     // pub gpio27: Gpio27, | ||||
|     // pub gpio28: Gpio28, | ||||
|     // pub gpio29: Gpio29, | ||||
|     // pub gpio30: Gpio30, | ||||
|     // pub pcnt0: PCNT0, | ||||
|     // pub pcnt1: PCNT1, | ||||
|     // pub adc1: ADC1, | ||||
|     // pub can: CAN, | ||||
| } | ||||
|  | ||||
| impl PlantHal { | ||||
|     fn create_i2c() -> Mutex<I2cDriver<'static>> { | ||||
|         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()) | ||||
|     } | ||||
|     // fn create_i2c() -> Mutex<CriticalSectionRawMutex, I2cDriver<'static>> { | ||||
|     //     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<Mutex<CriticalSectionRawMutex, HAL<'static>>> { | ||||
|         let peripherals = Peripherals::take()?; | ||||
|         let sys_loop = EspSystemEventLoop::take()?; | ||||
|         let nvs = EspDefaultNvsPartition::take()?; | ||||
|         let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; | ||||
|         let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); | ||||
|         let peripherals = esp_hal::init(config); | ||||
|  | ||||
|         let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; | ||||
|         boot_button.set_pull(Pull::Floating)?; | ||||
|         esp_alloc::heap_allocator!(size: 64 * 1024); | ||||
|         let timer0 = SystemTimer::new(peripherals.SYSTIMER); | ||||
|         esp_hal_embassy::init(timer0.alarm0); | ||||
|  | ||||
|         let free_pins = FreePeripherals { | ||||
|             can: peripherals.can, | ||||
|             adc1: peripherals.adc1, | ||||
|             pcnt0: peripherals.pcnt0, | ||||
|             pcnt1: peripherals.pcnt1, | ||||
|             gpio0: peripherals.pins.gpio0, | ||||
|             gpio1: peripherals.pins.gpio1, | ||||
|             gpio2: peripherals.pins.gpio2, | ||||
|             gpio3: peripherals.pins.gpio3, | ||||
|             gpio4: peripherals.pins.gpio4, | ||||
|             gpio5: peripherals.pins.gpio5, | ||||
|             gpio6: peripherals.pins.gpio6, | ||||
|             gpio7: peripherals.pins.gpio7, | ||||
|             gpio8: peripherals.pins.gpio8, | ||||
|             gpio10: peripherals.pins.gpio10, | ||||
|             gpio11: peripherals.pins.gpio11, | ||||
|             gpio12: peripherals.pins.gpio12, | ||||
|             gpio13: peripherals.pins.gpio13, | ||||
|             gpio14: peripherals.pins.gpio14, | ||||
|             gpio15: peripherals.pins.gpio15, | ||||
|             gpio16: peripherals.pins.gpio16, | ||||
|             gpio17: peripherals.pins.gpio17, | ||||
|             gpio18: peripherals.pins.gpio18, | ||||
|             gpio21: peripherals.pins.gpio21, | ||||
|             gpio22: peripherals.pins.gpio22, | ||||
|             gpio23: peripherals.pins.gpio23, | ||||
|             gpio24: peripherals.pins.gpio24, | ||||
|             gpio25: peripherals.pins.gpio25, | ||||
|             gpio26: peripherals.pins.gpio26, | ||||
|             gpio27: peripherals.pins.gpio27, | ||||
|             gpio28: peripherals.pins.gpio28, | ||||
|             gpio29: peripherals.pins.gpio29, | ||||
|             gpio30: peripherals.pins.gpio30, | ||||
|         }; | ||||
|  | ||||
|         // let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; | ||||
|         // boot_button.set_pull(Pull::Floating)?; | ||||
|         // | ||||
|         // let free_pins = FreePeripherals { | ||||
|         //     can: peripherals.can, | ||||
|         //     adc1: peripherals.adc1, | ||||
|         //     pcnt0: peripherals.pcnt0, | ||||
|         //     pcnt1: peripherals.pcnt1, | ||||
|         //     gpio0: peripherals.pins.gpio0, | ||||
|         //     gpio1: peripherals.pins.gpio1, | ||||
|         //     gpio2: peripherals.pins.gpio2, | ||||
|         //     gpio3: peripherals.pins.gpio3, | ||||
|         //     gpio4: peripherals.pins.gpio4, | ||||
|         //     gpio5: peripherals.pins.gpio5, | ||||
|         //     gpio6: peripherals.pins.gpio6, | ||||
|         //     gpio7: peripherals.pins.gpio7, | ||||
|         //     gpio8: peripherals.pins.gpio8, | ||||
|         //     gpio10: peripherals.pins.gpio10, | ||||
|         //     gpio11: peripherals.pins.gpio11, | ||||
|         //     gpio12: peripherals.pins.gpio12, | ||||
|         //     gpio13: peripherals.pins.gpio13, | ||||
|         //     gpio14: peripherals.pins.gpio14, | ||||
|         //     gpio15: peripherals.pins.gpio15, | ||||
|         //     gpio16: peripherals.pins.gpio16, | ||||
|         //     gpio17: peripherals.pins.gpio17, | ||||
|         //     gpio18: peripherals.pins.gpio18, | ||||
|         //     gpio21: peripherals.pins.gpio21, | ||||
|         //     gpio22: peripherals.pins.gpio22, | ||||
|         //     gpio23: peripherals.pins.gpio23, | ||||
|         //     gpio24: peripherals.pins.gpio24, | ||||
|         //     gpio25: peripherals.pins.gpio25, | ||||
|         //     gpio26: peripherals.pins.gpio26, | ||||
|         //     gpio27: peripherals.pins.gpio27, | ||||
|         //     gpio28: peripherals.pins.gpio28, | ||||
|         //     gpio29: peripherals.pins.gpio29, | ||||
|         //     gpio30: peripherals.pins.gpio30, | ||||
|         // }; | ||||
|         // | ||||
|         let mut esp = Esp { | ||||
|             mqtt_client: None, | ||||
|             wifi_driver, | ||||
|             boot_button, | ||||
|             delay: Delay::new(1000), | ||||
|         }; | ||||
|              mqtt_client: None, | ||||
|             dummy: PhantomData::default() | ||||
|         //     wifi_driver, | ||||
|         //     boot_button | ||||
|          }; | ||||
|  | ||||
|         //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, | ||||
|         }; | ||||
|         let reasons = ""; | ||||
|         // 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, | ||||
|         // }; | ||||
|         log( | ||||
|             LogMessage::ResetReason, | ||||
|             init_rtc_store as u32, | ||||
| @@ -270,70 +256,76 @@ impl PlantHal { | ||||
|         let config = esp.load_config(); | ||||
|  | ||||
|         log::info!("Init rtc driver"); | ||||
|         let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); | ||||
|         // let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); | ||||
|         // | ||||
|         // log::info!("Init rtc eeprom driver"); | ||||
|         // let eeprom = { | ||||
|         //     Eeprom24x::new_24x32( | ||||
|         //         MutexDevice::new(&I2C_DRIVER), | ||||
|         //         SlaveAddr::Alternative(true, true, true), | ||||
|         //     ) | ||||
|         // }; | ||||
|         // let rtc_time = rtc.datetime(); | ||||
|         // match rtc_time { | ||||
|         //     OkStd(tt) => { | ||||
|         //         log::info!("Rtc Module reports time at UTC {}", tt); | ||||
|         //     } | ||||
|         //     Err(err) => { | ||||
|         //         log::info!("Rtc Module could not be read {:?}", err); | ||||
|         //     } | ||||
|         // } | ||||
|  | ||||
|         log::info!("Init rtc eeprom driver"); | ||||
|         let eeprom = { | ||||
|             Eeprom24x::new_24x32( | ||||
|                 MutexDevice::new(&I2C_DRIVER), | ||||
|                 SlaveAddr::Alternative(true, true, true), | ||||
|             ) | ||||
|         }; | ||||
|         let rtc_time = rtc.datetime(); | ||||
|         match rtc_time { | ||||
|             OkStd(tt) => { | ||||
|                 log::info!("Rtc Module reports time at UTC {}", tt); | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 log::info!("Rtc Module could not be read {:?}", err); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let storage = Storage::new(eeprom, Delay::new(1000)); | ||||
|         let rtc_module: Box<dyn RTCModuleInteraction + Send> = | ||||
|             Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>; | ||||
|         // let storage = Storage::new(eeprom, Delay::new(1000)); | ||||
|         //let rtc_module: Box<dyn RTCModuleInteraction + Send> = | ||||
|         //    Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>; | ||||
|  | ||||
|         let hal = match config { | ||||
|             Result::Ok(config) => { | ||||
|                 let battery_interaction: Box<dyn BatteryInteraction + Send> = | ||||
|                     match config.hardware.battery { | ||||
|                         BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}), | ||||
|                         BatteryBoardVersion::BQ34Z100G1 => { | ||||
|                             let mut battery_driver = Bq34z100g1Driver { | ||||
|                                 i2c: MutexDevice::new(&I2C_DRIVER), | ||||
|                                 delay: Delay::new(0), | ||||
|                                 flash_block_data: [0; 32], | ||||
|                             }; | ||||
|                             let status = print_battery_bq34z100(&mut battery_driver); | ||||
|                             match status { | ||||
|                                 OkStd(_) => {} | ||||
|                                 Err(err) => { | ||||
|                                     log( | ||||
|                                         LogMessage::BatteryCommunicationError, | ||||
|                                         0u32, | ||||
|                                         0, | ||||
|                                         "", | ||||
|                                         &format!("{err:?})"), | ||||
|                                     ); | ||||
|                                 } | ||||
|                             } | ||||
|                             Box::new(BQ34Z100G1 { battery_driver }) | ||||
|                         } | ||||
|                         // BatteryBoardVersion::BQ34Z100G1 => { | ||||
|                         //     let mut battery_driver = Bq34z100g1Driver { | ||||
|                         //         i2c: MutexDevice::new(&I2C_DRIVER), | ||||
|                         //         delay: Delay::new(0), | ||||
|                         //         flash_block_data: [0; 32], | ||||
|                         //     }; | ||||
|                         //     let status = print_battery_bq34z100(&mut battery_driver); | ||||
|                         //     match status { | ||||
|                         //         Ok(_) => {} | ||||
|                         //         Err(err) => { | ||||
|                         //             log( | ||||
|                         //                 LogMessage::BatteryCommunicationError, | ||||
|                         //                 0u32, | ||||
|                         //                 0, | ||||
|                         //                 "", | ||||
|                         //                 &format!("{err:?})"), | ||||
|                         //             ); | ||||
|                         //         } | ||||
|                         //     } | ||||
|                         //     Box::new(BQ34Z100G1 { battery_driver }) | ||||
|                         // } | ||||
|                         BatteryBoardVersion::WchI2cSlave => { | ||||
|                             // TODO use correct implementation once availible | ||||
|                             Box::new(NoBatteryMonitor {}) | ||||
|                         } | ||||
|                         _ => { | ||||
|                             todo!() | ||||
|                         } | ||||
|                     }; | ||||
|  | ||||
|                 let board_hal: Box<dyn BoardInteraction + Send> = match config.hardware.board { | ||||
|                     BoardVersion::INITIAL => { | ||||
|                         initial_hal::create_initial_board(free_pins, fs_mount_error, config, esp)? | ||||
|                         initial_hal::create_initial_board(fs_mount_error, config, esp)? | ||||
|                     } | ||||
|                     BoardVersion::V3 => { | ||||
|                         v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)? | ||||
|                     } | ||||
|                     BoardVersion::V4 => { | ||||
|                         v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)? | ||||
|                     // BoardVersion::V3 => { | ||||
|                     //     v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)? | ||||
|                     // } | ||||
|                     // BoardVersion::V4 => { | ||||
|                     //     v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)? | ||||
|                     // } | ||||
|                     _ => { | ||||
|                         todo!() | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
| @@ -349,7 +341,6 @@ impl PlantHal { | ||||
|                 ); | ||||
|                 HAL { | ||||
|                     board_hal: initial_hal::create_initial_board( | ||||
|                         free_pins, | ||||
|                         fs_mount_error, | ||||
|                         PlantControllerConfig::default(), | ||||
|                         esp, | ||||
|   | ||||
| @@ -1,133 +1,138 @@ | ||||
| use crate::hal::Box; | ||||
| use alloc::vec::Vec; | ||||
| use anyhow::{anyhow, bail}; | ||||
| use async_trait::async_trait; | ||||
| use bincode::config::Configuration; | ||||
| use bincode::{config, Decode, Encode}; | ||||
| use chrono::{DateTime, Utc}; | ||||
| use ds323x::{DateTimeAccess, Ds323x}; | ||||
| use eeprom24x::addr_size::TwoBytes; | ||||
| use eeprom24x::page_size::B32; | ||||
| use eeprom24x::unique_serial::No; | ||||
| use eeprom24x::Storage; | ||||
| use embedded_storage::ReadStorage as embedded_storage_ReadStorage; | ||||
| use embedded_storage::Storage as embedded_storage_Storage; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC); | ||||
| const CONFIG: Configuration = config::standard(); | ||||
|  | ||||
| // use crate::hal::Box; | ||||
| // use alloc::vec::Vec; | ||||
| // use anyhow::{anyhow, bail}; | ||||
| // use async_trait::async_trait; | ||||
| // use bincode::config::Configuration; | ||||
| // use bincode::{config, Decode, Encode}; | ||||
| // use chrono::{DateTime, Utc}; | ||||
| // use ds323x::{DateTimeAccess, Ds323x}; | ||||
| // use eeprom24x::addr_size::TwoBytes; | ||||
| // use eeprom24x::page_size::B32; | ||||
| // use eeprom24x::unique_serial::No; | ||||
| // use eeprom24x::Storage; | ||||
| // use embedded_storage::ReadStorage as embedded_storage_ReadStorage; | ||||
| // use embedded_storage::Storage as embedded_storage_Storage; | ||||
| // use serde::{Deserialize, Serialize}; | ||||
| // | ||||
| // const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC); | ||||
| // const CONFIG: Configuration = config::standard(); | ||||
| // | ||||
| #[async_trait] | ||||
| pub trait RTCModuleInteraction { | ||||
|     async fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader>; | ||||
|     async fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>>; | ||||
|     async fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()>; | ||||
|     async fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>>; | ||||
|     async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()>; | ||||
|      async fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader>; | ||||
|      async fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>>; | ||||
|      async fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()>; | ||||
|      async fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>>; | ||||
|      async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()>; | ||||
| } | ||||
|  | ||||
| const BACKUP_HEADER_MAX_SIZE: usize = 64; | ||||
| #[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)] | ||||
| // | ||||
| // const BACKUP_HEADER_MAX_SIZE: usize = 64; | ||||
| // #[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)] | ||||
| pub struct BackupHeader { | ||||
|     pub timestamp: i64, | ||||
|     crc16: u16, | ||||
|     pub size: u16, | ||||
| } | ||||
|  | ||||
| pub struct DS3231Module<'a> { | ||||
|     pub(crate) rtc: | ||||
|         Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>, | ||||
|  | ||||
|     pub(crate) storage: Storage<MutexDevice<'a, I2cDriver<'a>>, B32, TwoBytes, No, Delay>, | ||||
| } | ||||
|  | ||||
| impl RTCModuleInteraction for DS3231Module<'_> { | ||||
|     fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader> { | ||||
|         let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; | ||||
|  | ||||
|         self.storage | ||||
|             .read(0, &mut header_page_buffer) | ||||
|             .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; | ||||
|  | ||||
|         let (header, len): (BackupHeader, usize) = | ||||
|             bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?; | ||||
|  | ||||
|         log::info!("Raw header is {:?} with size {}", header_page_buffer, len); | ||||
|         anyhow::Ok(header) | ||||
|     } | ||||
|  | ||||
|     fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>> { | ||||
|         let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; | ||||
|  | ||||
|         self.storage | ||||
|             .read(0, &mut header_page_buffer) | ||||
|             .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; | ||||
|         let (header, _header_size): (BackupHeader, usize) = | ||||
|             bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?; | ||||
|  | ||||
|         let mut data_buffer = vec![0_u8; header.size as usize]; | ||||
|         //read the specified number of bytes after the header | ||||
|         self.storage | ||||
|             .read(BACKUP_HEADER_MAX_SIZE as u32, &mut data_buffer) | ||||
|             .map_err(|err| anyhow!("Error reading eeprom data {:?}", err))?; | ||||
|  | ||||
|         let checksum = X25.checksum(&data_buffer); | ||||
|         if checksum != header.crc16 { | ||||
|             bail!( | ||||
|                 "Invalid checksum, got {} but expected {}", | ||||
|                 checksum, | ||||
|                 header.crc16 | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         anyhow::Ok(data_buffer) | ||||
|     } | ||||
|     fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()> { | ||||
|         let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; | ||||
|  | ||||
|         let time = self.get_rtc_time()?.timestamp_millis(); | ||||
|         let checksum = X25.checksum(bytes); | ||||
|  | ||||
|         let header = BackupHeader { | ||||
|             crc16: checksum, | ||||
|             timestamp: time, | ||||
|             size: bytes.len() as u16, | ||||
|         }; | ||||
|         let config = config::standard(); | ||||
|         let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?; | ||||
|         log::info!( | ||||
|             "Raw header is {:?} with size {}", | ||||
|             header_page_buffer, | ||||
|             encoded | ||||
|         ); | ||||
|         self.storage | ||||
|             .write(0, &header_page_buffer) | ||||
|             .map_err(|err| anyhow!("Error writing header {:?}", err))?; | ||||
|  | ||||
|         //write rest after the header | ||||
|         self.storage | ||||
|             .write(BACKUP_HEADER_MAX_SIZE as u32, &bytes) | ||||
|             .map_err(|err| anyhow!("Error writing body {:?}", err))?; | ||||
|  | ||||
|         anyhow::Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>> { | ||||
|         match self.rtc.datetime() { | ||||
|             OkStd(rtc_time) => anyhow::Ok(rtc_time.and_utc()), | ||||
|             Err(err) => { | ||||
|                 bail!("Error getting rtc time {:?}", err) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()> { | ||||
|         let naive_time = time.naive_utc(); | ||||
|         match self.rtc.set_datetime(&naive_time) { | ||||
|             OkStd(_) => anyhow::Ok(()), | ||||
|             Err(err) => { | ||||
|                 bail!("Error getting rtc time {:?}", err) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| // | ||||
| // pub struct DS3231Module<'a> { | ||||
| //     pub(crate) rtc: | ||||
| //         Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>, | ||||
| // | ||||
| //     pub(crate) storage: Storage<MutexDevice<'a, I2cDriver<'a>>, B32, TwoBytes, No, Delay>, | ||||
| // } | ||||
| // | ||||
| // impl RTCModuleInteraction for DS3231Module<'_> { | ||||
| //     fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader> { | ||||
| //         let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; | ||||
| // | ||||
| //         self.storage | ||||
| //             .read(0, &mut header_page_buffer) | ||||
| //             .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; | ||||
| // | ||||
| //         let (header, len): (BackupHeader, usize) = | ||||
| //             bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?; | ||||
| // | ||||
| //         log::info!("Raw header is {:?} with size {}", header_page_buffer, len); | ||||
| //         anyhow::Ok(header) | ||||
| //     } | ||||
| // | ||||
| //     fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>> { | ||||
| //         let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; | ||||
| // | ||||
| //         self.storage | ||||
| //             .read(0, &mut header_page_buffer) | ||||
| //             .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; | ||||
| //         let (header, _header_size): (BackupHeader, usize) = | ||||
| //             bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?; | ||||
| // | ||||
| //         let mut data_buffer = vec![0_u8; header.size as usize]; | ||||
| //         //read the specified number of bytes after the header | ||||
| //         self.storage | ||||
| //             .read(BACKUP_HEADER_MAX_SIZE as u32, &mut data_buffer) | ||||
| //             .map_err(|err| anyhow!("Error reading eeprom data {:?}", err))?; | ||||
| // | ||||
| //         let checksum = X25.checksum(&data_buffer); | ||||
| //         if checksum != header.crc16 { | ||||
| //             bail!( | ||||
| //                 "Invalid checksum, got {} but expected {}", | ||||
| //                 checksum, | ||||
| //                 header.crc16 | ||||
| //             ); | ||||
| //         } | ||||
| // | ||||
| //         anyhow::Ok(data_buffer) | ||||
| //     } | ||||
| //     fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()> { | ||||
| //         let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; | ||||
| // | ||||
| //         let time = self.get_rtc_time()?.timestamp_millis(); | ||||
| //         let checksum = X25.checksum(bytes); | ||||
| // | ||||
| //         let header = BackupHeader { | ||||
| //             crc16: checksum, | ||||
| //             timestamp: time, | ||||
| //             size: bytes.len() as u16, | ||||
| //         }; | ||||
| //         let config = config::standard(); | ||||
| //         let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?; | ||||
| //         log::info!( | ||||
| //             "Raw header is {:?} with size {}", | ||||
| //             header_page_buffer, | ||||
| //             encoded | ||||
| //         ); | ||||
| //         self.storage | ||||
| //             .write(0, &header_page_buffer) | ||||
| //             .map_err(|err| anyhow!("Error writing header {:?}", err))?; | ||||
| // | ||||
| //         //write rest after the header | ||||
| //         self.storage | ||||
| //             .write(BACKUP_HEADER_MAX_SIZE as u32, &bytes) | ||||
| //             .map_err(|err| anyhow!("Error writing body {:?}", err))?; | ||||
| // | ||||
| //         anyhow::Ok(()) | ||||
| //     } | ||||
| // | ||||
| //     fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>> { | ||||
| //         match self.rtc.datetime() { | ||||
| //             OkStd(rtc_time) => anyhow::Ok(rtc_time.and_utc()), | ||||
| //             Err(err) => { | ||||
| //                 bail!("Error getting rtc time {:?}", err) | ||||
| //             } | ||||
| //         } | ||||
| //     } | ||||
| // | ||||
| //     fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()> { | ||||
| //         let naive_time = time.naive_utc(); | ||||
| //         match self.rtc.set_datetime(&naive_time) { | ||||
| //             OkStd(_) => anyhow::Ok(()), | ||||
| //             Err(err) => { | ||||
| //                 bail!("Error getting rtc time {:?}", err) | ||||
| //             } | ||||
| //         } | ||||
| //     } | ||||
| // } | ||||
|   | ||||
| @@ -1,171 +0,0 @@ | ||||
| use crate::hal::TANK_MULTI_SAMPLE; | ||||
| use anyhow::{anyhow, bail}; | ||||
| use ds18b20::Ds18b20; | ||||
| use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; | ||||
| use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; | ||||
| use esp_idf_hal::adc::{attenuation, Resolution, ADC1}; | ||||
| use esp_idf_hal::delay::Delay; | ||||
| use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull}; | ||||
| use esp_idf_hal::pcnt::{ | ||||
|     PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1, | ||||
| }; | ||||
| use esp_idf_sys::EspError; | ||||
| use one_wire_bus::OneWire; | ||||
|  | ||||
| pub struct TankSensor<'a> { | ||||
|     one_wire_bus: OneWire<PinDriver<'a, AnyIOPin, InputOutput>>, | ||||
|     tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, ADC1>>, | ||||
|     tank_power: PinDriver<'a, AnyIOPin, InputOutput>, | ||||
|     flow_counter: PcntDriver<'a>, | ||||
|     delay: Delay, | ||||
| } | ||||
|  | ||||
| impl<'a> TankSensor<'a> { | ||||
|     pub(crate) fn create( | ||||
|         one_wire_pin: AnyIOPin, | ||||
|         adc1: ADC1, | ||||
|         gpio5: Gpio5, | ||||
|         tank_power_pin: AnyIOPin, | ||||
|         flow_sensor_pin: AnyIOPin, | ||||
|         pcnt1: PCNT1, | ||||
|     ) -> anyhow::Result<TankSensor<'a>> { | ||||
|         let mut one_wire_pin = | ||||
|             PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin"); | ||||
|         one_wire_pin | ||||
|             .set_pull(Pull::Floating) | ||||
|             .expect("Failed to set pull"); | ||||
|  | ||||
|         let adc_config = AdcChannelConfig { | ||||
|             attenuation: attenuation::DB_11, | ||||
|             resolution: Resolution::Resolution12Bit, | ||||
|             calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve, | ||||
|         }; | ||||
|         let tank_driver = AdcDriver::new(adc1).expect("Failed to configure ADC"); | ||||
|         let tank_channel = AdcChannelDriver::new(tank_driver, gpio5, &adc_config) | ||||
|             .expect("Failed to configure ADC channel"); | ||||
|  | ||||
|         let mut tank_power = | ||||
|             PinDriver::input_output(tank_power_pin).expect("Failed to configure pin"); | ||||
|         tank_power | ||||
|             .set_pull(Pull::Floating) | ||||
|             .expect("Failed to set pull"); | ||||
|  | ||||
|         let one_wire_bus = | ||||
|             OneWire::new(one_wire_pin).expect("OneWire bus did not pull up after release"); | ||||
|  | ||||
|         let mut flow_counter = PcntDriver::new( | ||||
|             pcnt1, | ||||
|             Some(flow_sensor_pin), | ||||
|             Option::<AnyInputPin>::None, | ||||
|             Option::<AnyInputPin>::None, | ||||
|             Option::<AnyInputPin>::None, | ||||
|         )?; | ||||
|  | ||||
|         flow_counter.channel_config( | ||||
|             PcntChannel::Channel1, | ||||
|             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, | ||||
|             }, | ||||
|         )?; | ||||
|  | ||||
|         Ok(TankSensor { | ||||
|             one_wire_bus, | ||||
|             tank_channel, | ||||
|             tank_power, | ||||
|             flow_counter, | ||||
|             delay: Default::default(), | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn reset_flow_meter(&mut self) { | ||||
|         self.flow_counter.counter_pause().unwrap(); | ||||
|         self.flow_counter.counter_clear().unwrap(); | ||||
|     } | ||||
|  | ||||
|     pub fn start_flow_meter(&mut self) { | ||||
|         self.flow_counter.counter_resume().unwrap(); | ||||
|     } | ||||
|  | ||||
|     pub fn get_flow_meter_value(&mut self) -> i16 { | ||||
|         self.flow_counter.get_counter_value().unwrap() | ||||
|     } | ||||
|  | ||||
|     pub fn stop_flow_meter(&mut self) -> i16 { | ||||
|         self.flow_counter.counter_pause().unwrap(); | ||||
|         self.get_flow_meter_value() | ||||
|     } | ||||
|  | ||||
|     pub async fn water_temperature_c(&mut self) -> anyhow::Result<f32> { | ||||
|         //multisample should be moved to water_temperature_c | ||||
|         let mut attempt = 1; | ||||
|         let water_temp: Result<f32, anyhow::Error> = loop { | ||||
|             let temp = self.single_temperature_c(); | ||||
|             match &temp { | ||||
|                 Ok(res) => { | ||||
|                     log::info!("Water temp is {}", res); | ||||
|                     break temp; | ||||
|                 } | ||||
|                 Err(err) => { | ||||
|                     log::info!("Could not get water temp {} attempt {}", err, attempt) | ||||
|                 } | ||||
|             } | ||||
|             if attempt == 5 { | ||||
|                 break temp; | ||||
|             } | ||||
|             attempt += 1; | ||||
|         }; | ||||
|         water_temp | ||||
|     } | ||||
|  | ||||
|     async fn single_temperature_c(&mut self) -> anyhow::Result<f32> { | ||||
|         self.one_wire_bus | ||||
|             .reset(&mut self.delay) | ||||
|             .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; | ||||
|         let first = self.one_wire_bus.devices(false, &mut self.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::<EspError>(device_address) | ||||
|             .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; | ||||
|  | ||||
|         water_temp_sensor | ||||
|             .start_temp_measurement(&mut self.one_wire_bus, &mut self.delay) | ||||
|             .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; | ||||
|         ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.delay); | ||||
|         let sensor_data = water_temp_sensor | ||||
|             .read_data(&mut self.one_wire_bus, &mut self.delay) | ||||
|             .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?; | ||||
|         if sensor_data.temperature == 85_f32 { | ||||
|             bail!("Ds18b20 dummy temperature returned"); | ||||
|         } | ||||
|         anyhow::Ok(sensor_data.temperature / 10_f32) | ||||
|     } | ||||
|  | ||||
|     pub async fn tank_sensor_voltage(&mut self) -> anyhow::Result<f32> { | ||||
|         self.tank_power.set_high()?; | ||||
|         //let stabilize | ||||
|         self.delay.delay_ms(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; | ||||
|         } | ||||
|         self.tank_power.set_low()?; | ||||
|  | ||||
|         store.sort(); | ||||
|         let median_mv = store[6] as f32 / 1000_f32; | ||||
|         anyhow::Ok(median_mv) | ||||
|     } | ||||
| } | ||||
| @@ -1,14 +1,13 @@ | ||||
| use crate::vec; | ||||
| use alloc::string::ToString; | ||||
| use alloc::vec::Vec; | ||||
| use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||||
| use embassy_sync::lazy_lock::LazyLock; | ||||
| use embassy_sync::mutex::Mutex; | ||||
| use serde::Serialize; | ||||
| use std::{collections::HashMap, sync::Mutex}; | ||||
| use strum::EnumIter; | ||||
| use strum_macros::IntoStaticStr; | ||||
| use strum_macros::{EnumIter, IntoStaticStr}; | ||||
|  | ||||
| use esp_idf_svc::systime::EspSystemTime; | ||||
| use once_cell::sync::Lazy; | ||||
| use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; | ||||
| use text_template::Template; | ||||
| use unit_enum::UnitEnum; | ||||
|  | ||||
| const TXT_SHORT_LENGTH: usize = 8; | ||||
| @@ -20,8 +19,8 @@ const BUFFER_SIZE: usize = 220; | ||||
| static mut BUFFER: ConstGenericRingBuffer<LogEntry, BUFFER_SIZE> = | ||||
|     ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new(); | ||||
| #[allow(static_mut_refs)] | ||||
| static BUFFER_ACCESS: Lazy<Mutex<&mut ConstGenericRingBuffer<LogEntry, BUFFER_SIZE>>> = | ||||
|     Lazy::new(|| unsafe { Mutex::new(&mut BUFFER) }); | ||||
| static BUFFER_ACCESS: LazyLock<Mutex<CriticalSectionRawMutex,&mut ConstGenericRingBuffer<LogEntry, BUFFER_SIZE>>> = | ||||
|     LazyLock::new(|| unsafe { Mutex::new(&mut BUFFER) }); | ||||
|  | ||||
| #[derive(Serialize, Debug, Clone)] | ||||
| pub struct LogEntry { | ||||
| @@ -33,11 +32,11 @@ pub struct LogEntry { | ||||
|     pub txt_long: heapless::String<TXT_LONG_LENGTH>, | ||||
| } | ||||
|  | ||||
| pub fn init() { | ||||
| pub async fn init() { | ||||
|     unsafe { | ||||
|         BUFFER = ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new(); | ||||
|     }; | ||||
|     let mut access = BUFFER_ACCESS.lock().unwrap(); | ||||
|     let mut access = BUFFER_ACCESS.get().lock().await; | ||||
|     access.drain().for_each(|_| {}); | ||||
| } | ||||
|  | ||||
| @@ -59,8 +58,8 @@ fn limit_length<const LIMIT: usize>(input: &str, target: &mut heapless::String<L | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn get_log() -> Vec<LogEntry> { | ||||
|     let buffer = BUFFER_ACCESS.lock().unwrap(); | ||||
| pub async fn get_log() -> Vec<LogEntry> { | ||||
|     let buffer = BUFFER_ACCESS.get().lock().await; | ||||
|     let mut read_copy = Vec::new(); | ||||
|     for entry in buffer.iter() { | ||||
|         let copy = entry.clone(); | ||||
| @@ -70,32 +69,35 @@ pub fn get_log() -> Vec<LogEntry> { | ||||
|     read_copy | ||||
| } | ||||
|  | ||||
| pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &str, txt_long: &str) { | ||||
| pub async fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &str, txt_long: &str) { | ||||
|     let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new(); | ||||
|     let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new(); | ||||
|  | ||||
|     limit_length(txt_short, &mut txt_short_stack); | ||||
|     limit_length(txt_long, &mut txt_long_stack); | ||||
|  | ||||
|     let time = EspSystemTime {}.now().as_millis() as u64; | ||||
|     //TODO | ||||
|     let time = 0; | ||||
|  | ||||
|     // let time = EspSystemTime {}.now().as_millis() as u64; | ||||
|     // | ||||
|     let ordinal = message_key.ordinal() as u16; | ||||
|     let template_string: &str = message_key.into(); | ||||
|  | ||||
|     let mut values: HashMap<&str, &str> = HashMap::new(); | ||||
|     let number_a_str = number_a.to_string(); | ||||
|     let number_b_str = number_b.to_string(); | ||||
|  | ||||
|     values.insert("number_a", &number_a_str); | ||||
|     values.insert("number_b", &number_b_str); | ||||
|     values.insert("txt_short", txt_short); | ||||
|     values.insert("txt_long", txt_long); | ||||
|  | ||||
|     let template = Template::from(template_string); | ||||
|     let serial_entry = template.fill_in(&values); | ||||
|  | ||||
|     log::info!("{serial_entry}"); | ||||
|     //TODO push to mqtt? | ||||
|     // let template_string: &str = message_key.into(); | ||||
|     // | ||||
|     // let mut values: HashMap<&str, &str> = HashMap::new(); | ||||
|     // let number_a_str = number_a.to_string(); | ||||
|     // let number_b_str = number_b.to_string(); | ||||
|     // | ||||
|     // values.insert("number_a", &number_a_str); | ||||
|     // values.insert("number_b", &number_b_str); | ||||
|     // values.insert("txt_short", txt_short); | ||||
|     // values.insert("txt_long", txt_long); | ||||
|     // | ||||
|     // let template = Template::from(template_string); | ||||
|     // let serial_entry = template.fill_in(&values); | ||||
|     // | ||||
|     // log::info!("{serial_entry}"); | ||||
|     // //TODO push to mqtt? | ||||
|  | ||||
|     let entry = LogEntry { | ||||
|         timestamp: time, | ||||
| @@ -106,29 +108,10 @@ pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &st | ||||
|         txt_long: txt_long_stack, | ||||
|     }; | ||||
|  | ||||
|     let mut buffer = BUFFER_ACCESS.lock().unwrap(); | ||||
|     let mut buffer = BUFFER_ACCESS.get().lock().await; | ||||
|     buffer.push(entry); | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|  | ||||
|     #[test] | ||||
|     fn within_limit() { | ||||
|         let test = "12345678"; | ||||
|  | ||||
|         let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new(); | ||||
|         let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new(); | ||||
|         limit_length(test, &mut txt_short_stack); | ||||
|         limit_length(test, &mut txt_long_stack); | ||||
|  | ||||
|         assert_eq!(txt_short_stack.as_str(), test); | ||||
|         assert_eq!(txt_long_stack.as_str(), test); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(IntoStaticStr, EnumIter, Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, UnitEnum)] | ||||
| #[derive(IntoStaticStr, Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, UnitEnum)] | ||||
| pub enum LogMessage { | ||||
|     #[strum( | ||||
|         serialize = "Reset due to ${txt_long} requires rtc clear ${number_a} and force config mode ${number_b}" | ||||
|   | ||||
							
								
								
									
										596
									
								
								rust/src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										596
									
								
								rust/src/main.rs
									
									
									
									
									
								
							| @@ -5,6 +5,10 @@ | ||||
|     reason = "mem::forget is generally not safe to do with esp_hal types, especially those \ | ||||
|     holding buffers for the duration of a data transfer." | ||||
| )] | ||||
|  | ||||
| esp_bootloader_esp_idf::esp_app_desc!(); | ||||
| use esp_backtrace as _; | ||||
|  | ||||
| use crate::config::PlantConfig; | ||||
| use crate::{ | ||||
|     config::BoardVersion::INITIAL, | ||||
| @@ -16,24 +20,25 @@ use alloc::borrow::ToOwned; | ||||
| use alloc::string::{String, ToString}; | ||||
| use alloc::sync::Arc; | ||||
| use alloc::{format, vec}; | ||||
| use core::any::Any; | ||||
| use anyhow::{bail, Context}; | ||||
| use chrono::{DateTime, Datelike, Timelike, Utc}; | ||||
| use chrono_tz::Tz::{self, UTC}; | ||||
| use chrono_tz::Tz::{self}; | ||||
| use core::sync::atomic::Ordering; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; | ||||
| use embassy_sync::lazy_lock::LazyLock; | ||||
| use embassy_sync::mutex::Mutex; | ||||
| use embassy_sync::mutex::MutexGuard; | ||||
| use embassy_time::Timer; | ||||
| use esp_hal::{clock::CpuClock, delay::Delay, timer::systimer::SystemTimer}; | ||||
| use esp_println::logger; | ||||
| use esp_println::{logger, println}; | ||||
| use hal::battery::BatteryState; | ||||
| use log::{log, LogMessage}; | ||||
| use plant_state::PlantState; | ||||
| use portable_atomic::AtomicBool; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use tank::*; | ||||
| use crate::tank::{TankError, WATER_FROZEN_THRESH}; | ||||
|  | ||||
| //use tank::*; | ||||
| mod config; | ||||
| mod hal; | ||||
| mod log; | ||||
| @@ -44,7 +49,9 @@ extern crate alloc; | ||||
| //mod webserver; | ||||
|  | ||||
| pub static BOARD_ACCESS: LazyLock<Mutex<CriticalSectionRawMutex, HAL>> = | ||||
|     LazyLock::new(|| PlantHal::create().unwrap()); | ||||
|     LazyLock::new(|| { | ||||
|         PlantHal::create().unwrap() | ||||
|     }); | ||||
| pub static STAY_ALIVE: AtomicBool = AtomicBool::new(false); | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug, PartialEq)] | ||||
| @@ -163,10 +170,10 @@ async fn safe_main() -> anyhow::Result<()> { | ||||
|     //}; | ||||
|     //log(LogMessage::PartitionState, 0, 0, "", ota_state_string); | ||||
|     let ota_state_string = "unknown"; | ||||
|  | ||||
|     println!("faul led"); | ||||
|     let mut board = BOARD_ACCESS.get().lock().await; | ||||
|     board.board_hal.general_fault(false).await; | ||||
|  | ||||
|     println!("faul led2"); | ||||
|     let cur = board | ||||
|         .board_hal | ||||
|         .get_rtc_module() | ||||
| @@ -204,7 +211,7 @@ async fn safe_main() -> anyhow::Result<()> { | ||||
|         board.board_hal.get_esp().set_restart_to_conf(false); | ||||
|     } else if board.board_hal.get_esp().mode_override_pressed() { | ||||
|         board.board_hal.general_fault(true).await; | ||||
|         log(LogMessage::ConfigModeButtonOverride, 0, 0, "", ""); | ||||
|         log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "").await; | ||||
|         for _i in 0..5 { | ||||
|             board.board_hal.general_fault(true).await; | ||||
|             Timer::after_millis(100).await; | ||||
| @@ -251,19 +258,21 @@ async fn safe_main() -> anyhow::Result<()> { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let timezone = match &board.board_hal.get_config().timezone { | ||||
|         Some(tz_str) => tz_str.parse::<Tz>().unwrap_or_else(|_| { | ||||
|             info!("Invalid timezone '{}', falling back to UTC", tz_str); | ||||
|             UTC | ||||
|         }), | ||||
|         None => UTC, // Fallback to UTC if no timezone is set | ||||
|     }; | ||||
|  | ||||
|     let timezone_time = cur.with_timezone(&timezone); | ||||
|     // let timezone = match &board.board_hal.get_config().timezone { | ||||
|     //     Some(tz_str) => tz_str.parse::<Tz>().unwrap_or_else(|_| { | ||||
|     //         info!("Invalid timezone '{}', falling back to UTC", tz_str); | ||||
|     //         UTC | ||||
|     //     }), | ||||
|     //     None => UTC, // Fallback to UTC if no timezone is set | ||||
|     // }; | ||||
|     let timezone = Tz::UTC; | ||||
|  | ||||
|     let timezone_time = cur;//TODO.with_timezone(&timezone); | ||||
|     info!( | ||||
|         "Running logic at utc {} and {} {}", | ||||
|         cur, | ||||
|         timezone.name(), | ||||
|         "todo timezone.name()", | ||||
|         timezone_time | ||||
|     ); | ||||
|  | ||||
| @@ -273,7 +282,7 @@ async fn safe_main() -> anyhow::Result<()> { | ||||
|             partition_address, | ||||
|             ota_state_string, | ||||
|             ip_address, | ||||
|             timezone_time, | ||||
|             &timezone_time.to_rfc3339(), | ||||
|         ) | ||||
|         .await; | ||||
|         publish_battery_state().await; | ||||
| @@ -294,9 +303,10 @@ async fn safe_main() -> anyhow::Result<()> { | ||||
|             .to_string() | ||||
|             .as_str(), | ||||
|         "", | ||||
|     ); | ||||
|     ).await; | ||||
|  | ||||
|     drop(board); | ||||
|     //TODO must drop board here? | ||||
|     //drop(board); | ||||
|  | ||||
|     if to_config { | ||||
|         //check if client or ap mode and init Wi-Fi | ||||
| @@ -307,52 +317,54 @@ async fn safe_main() -> anyhow::Result<()> { | ||||
|         //let _webserver = httpd(reboot_now.clone()); | ||||
|         wait_infinity(WaitType::ConfigButton, reboot_now.clone()).await; | ||||
|     } else { | ||||
|         log(LogMessage::NormalRun, 0, 0, "", ""); | ||||
|         log(LogMessage::NormalRun, 0, 0, "", "").await; | ||||
|     } | ||||
|  | ||||
|     let dry_run = false; | ||||
|  | ||||
|     let tank_state = determine_tank_state(&mut board); | ||||
|  | ||||
|     if tank_state.is_enabled() { | ||||
|         if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) { | ||||
|             match err { | ||||
|                 TankError::SensorDisabled => { /* unreachable */ } | ||||
|                 TankError::SensorMissing(raw_value_mv) => log( | ||||
|                     LogMessage::TankSensorMissing, | ||||
|                     raw_value_mv as u32, | ||||
|                     0, | ||||
|                     "", | ||||
|                     "", | ||||
|                 ), | ||||
|                 TankError::SensorValueError { value, min, max } => log( | ||||
|                     LogMessage::TankSensorValueRangeError, | ||||
|                     min as u32, | ||||
|                     max as u32, | ||||
|                     &format!("{}", value), | ||||
|                     "", | ||||
|                 ), | ||||
|                 TankError::BoardError(err) => { | ||||
|                     log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()) | ||||
|                 } | ||||
|             } | ||||
|             // disabled cannot trigger this because of wrapping if is_enabled | ||||
|             board.board_hal.general_fault(true); | ||||
|         } else if tank_state | ||||
|             .warn_level(&board.board_hal.get_config().tank) | ||||
|             .is_ok_and(|warn| warn) | ||||
|         { | ||||
|             log(LogMessage::TankWaterLevelLow, 0, 0, "", ""); | ||||
|             board.board_hal.general_fault(true); | ||||
|         } | ||||
|     } | ||||
|     // | ||||
|     // let tank_state = determine_tank_state(&mut board); | ||||
|     // | ||||
|     // if tank_state.is_enabled() { | ||||
|     //     if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) { | ||||
|     //         match err { | ||||
|     //             TankError::SensorDisabled => { /* unreachable */ } | ||||
|     //             TankError::SensorMissing(raw_value_mv) => log( | ||||
|     //                 LogMessage::TankSensorMissing, | ||||
|     //                 raw_value_mv as u32, | ||||
|     //                 0, | ||||
|     //                 "", | ||||
|     //                 "", | ||||
|     //             ).await, | ||||
|     //             TankError::SensorValueError { value, min, max } => log( | ||||
|     //                 LogMessage::TankSensorValueRangeError, | ||||
|     //                 min as u32, | ||||
|     //                 max as u32, | ||||
|     //                 &format!("{}", value), | ||||
|     //                 "", | ||||
|     //             ).await, | ||||
|     //             TankError::BoardError(err) => { | ||||
|     //                 log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()).await | ||||
|     //             } | ||||
|     //         } | ||||
|     //         // disabled cannot trigger this because of wrapping if is_enabled | ||||
|     //         board.board_hal.general_fault(true).await; | ||||
|     //     } else if tank_state | ||||
|     //         .warn_level(&board.board_hal.get_config().tank) | ||||
|     //         .is_ok_and(|warn| warn) | ||||
|     //     { | ||||
|     //         log(LogMessage::TankWaterLevelLow, 0, 0, "", "").await; | ||||
|     //         board.board_hal.general_fault(true).await; | ||||
|     //     } | ||||
|     // } | ||||
|  | ||||
|     let mut water_frozen = false; | ||||
|     let water_temp = board | ||||
|         .board_hal | ||||
|         .get_tank_sensor() | ||||
|         .context("no sensor") | ||||
|         .and_then(async |f| f.water_temperature_c().await); | ||||
|     //TODO | ||||
|     let water_temp = anyhow::Ok(12_f32); | ||||
|     // board | ||||
|     //     .board_hal | ||||
|     //     .get_tank_sensor() | ||||
|     //     .context("no sensor") | ||||
|     //     .and_then(async |f| f.water_temperature_c().await); | ||||
|  | ||||
|     if let Ok(res) = water_temp { | ||||
|         if res < WATER_FROZEN_THRESH { | ||||
| @@ -360,80 +372,89 @@ async fn safe_main() -> anyhow::Result<()> { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     publish_tank_state(&tank_state, &water_temp); | ||||
|     //publish_tank_state(&tank_state, &water_temp).await; | ||||
|  | ||||
|     let plantstate: [PlantState; PLANT_COUNT] = | ||||
|         core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board).await); | ||||
|     publish_plant_states(&timezone_time, &plantstate).await; | ||||
|  | ||||
|     let pump_required = plantstate | ||||
|         .iter() | ||||
|         .zip(&board.board_hal.get_config().plants) | ||||
|         .any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time)) | ||||
|         && !water_frozen; | ||||
|     if pump_required { | ||||
|         log(LogMessage::EnableMain, dry_run as u32, 0, "", ""); | ||||
|         for (plant_id, (state, plant_config)) in plantstate | ||||
|             .iter() | ||||
|             .zip(&board.board_hal.get_config().plants.clone()) | ||||
|             .enumerate() | ||||
|         { | ||||
|             if state.needs_to_be_watered(plant_config, &timezone_time) { | ||||
|                 let pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id) + 1; | ||||
|                 board | ||||
|                     .board_hal | ||||
|                     .get_esp() | ||||
|                     .store_consecutive_pump_count(plant_id, pump_count); | ||||
|     let plantstate: [PlantState; PLANT_COUNT] = [ | ||||
|         PlantState::read_hardware_state(0, &mut board).await, | ||||
|         PlantState::read_hardware_state(1, &mut board).await, | ||||
|         PlantState::read_hardware_state(2, &mut board).await, | ||||
|         PlantState::read_hardware_state(3, &mut board).await, | ||||
|         PlantState::read_hardware_state(4, &mut board).await, | ||||
|         PlantState::read_hardware_state(5, &mut board).await, | ||||
|         PlantState::read_hardware_state(6, &mut board).await, | ||||
|         PlantState::read_hardware_state(7, &mut board).await, | ||||
|     ]; | ||||
|     //publish_plant_states(&timezone_time.clone(), &plantstate).await; | ||||
|  | ||||
|                 let pump_ineffective = pump_count > plant_config.max_consecutive_pump_count as u32; | ||||
|                 if pump_ineffective { | ||||
|                     log( | ||||
|                         LogMessage::ConsecutivePumpCountLimit, | ||||
|                         pump_count, | ||||
|                         plant_config.max_consecutive_pump_count as u32, | ||||
|                         &(plant_id + 1).to_string(), | ||||
|                         "", | ||||
|                     ); | ||||
|                     board.board_hal.fault(plant_id, true).await?; | ||||
|                 } | ||||
|                 log( | ||||
|                     LogMessage::PumpPlant, | ||||
|                     (plant_id + 1) as u32, | ||||
|                     plant_config.pump_time_s as u32, | ||||
|                     &dry_run.to_string(), | ||||
|                     "", | ||||
|                 ); | ||||
|                 board | ||||
|                     .board_hal | ||||
|                     .get_esp() | ||||
|                     .store_last_pump_time(plant_id, cur); | ||||
|                 board.board_hal.get_esp().last_pump_time(plant_id); | ||||
|                 //state.active = true; | ||||
|  | ||||
|                 pump_info(plant_id, true, pump_ineffective, 0, 0, 0, false).await; | ||||
|  | ||||
|                 let result = do_secure_pump(plant_id, plant_config, dry_run).await?; | ||||
|                 board.board_hal.pump(plant_id, false).await?; | ||||
|                 pump_info( | ||||
|                     plant_id, | ||||
|                     false, | ||||
|                     pump_ineffective, | ||||
|                     result.median_current_ma, | ||||
|                     result.max_current_ma, | ||||
|                     result.min_current_ma, | ||||
|                     result.error, | ||||
|                 ) | ||||
|                 .await; | ||||
|             } else if !state.pump_in_timeout(plant_config, &timezone_time) { | ||||
|                 // plant does not need to be watered and is not in timeout | ||||
|                 // -> reset consecutive pump count | ||||
|                 board | ||||
|                     .board_hal | ||||
|                     .get_esp() | ||||
|                     .store_consecutive_pump_count(plant_id, 0); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     // let pump_required = plantstate | ||||
|     //     .iter() | ||||
|     //     .zip(&board.board_hal.get_config().plants) | ||||
|     //     .any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time)) | ||||
|     //     && !water_frozen; | ||||
|     // if pump_required { | ||||
|     //     log(LogMessage::EnableMain, dry_run as u32, 0, "", ""); | ||||
|     //     for (plant_id, (state, plant_config)) in plantstate | ||||
|     //         .iter() | ||||
|     //         .zip(&board.board_hal.get_config().plants.clone()) | ||||
|     //         .enumerate() | ||||
|     //     { | ||||
|     //         if state.needs_to_be_watered(plant_config, &timezone_time) { | ||||
|     //             let pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id) + 1; | ||||
|     //             board | ||||
|     //                 .board_hal | ||||
|     //                 .get_esp() | ||||
|     //                 .store_consecutive_pump_count(plant_id, pump_count); | ||||
|     // | ||||
|     //             let pump_ineffective = pump_count > plant_config.max_consecutive_pump_count as u32; | ||||
|     //             if pump_ineffective { | ||||
|     //                 log( | ||||
|     //                     LogMessage::ConsecutivePumpCountLimit, | ||||
|     //                     pump_count, | ||||
|     //                     plant_config.max_consecutive_pump_count as u32, | ||||
|     //                     &(plant_id + 1).to_string(), | ||||
|     //                     "", | ||||
|     //                 ); | ||||
|     //                 board.board_hal.fault(plant_id, true).await?; | ||||
|     //             } | ||||
|     //             log( | ||||
|     //                 LogMessage::PumpPlant, | ||||
|     //                 (plant_id + 1) as u32, | ||||
|     //                 plant_config.pump_time_s as u32, | ||||
|     //                 &dry_run.to_string(), | ||||
|     //                 "", | ||||
|     //             ); | ||||
|     //             board | ||||
|     //                 .board_hal | ||||
|     //                 .get_esp() | ||||
|     //                 .store_last_pump_time(plant_id, cur); | ||||
|     //             board.board_hal.get_esp().last_pump_time(plant_id); | ||||
|     //             //state.active = true; | ||||
|     // | ||||
|     //             pump_info(plant_id, true, pump_ineffective, 0, 0, 0, false).await; | ||||
|     // | ||||
|     //             let result = do_secure_pump(plant_id, plant_config, dry_run).await?; | ||||
|     //             board.board_hal.pump(plant_id, false).await?; | ||||
|     //             pump_info( | ||||
|     //                 plant_id, | ||||
|     //                 false, | ||||
|     //                 pump_ineffective, | ||||
|     //                 result.median_current_ma, | ||||
|     //                 result.max_current_ma, | ||||
|     //                 result.min_current_ma, | ||||
|     //                 result.error, | ||||
|     //             ) | ||||
|     //             .await; | ||||
|     //         } else if !state.pump_in_timeout(plant_config, &timezone_time) { | ||||
|     //             // plant does not need to be watered and is not in timeout | ||||
|     //             // -> reset consecutive pump count | ||||
|     //             board | ||||
|     //                 .board_hal | ||||
|     //                 .get_esp() | ||||
|     //                 .store_consecutive_pump_count(plant_id, 0); | ||||
|     //         } | ||||
|     //     } | ||||
|     // } | ||||
|  | ||||
|     let is_day = board.board_hal.is_day(); | ||||
|     let state_of_charge = board | ||||
| @@ -455,67 +476,67 @@ async fn safe_main() -> anyhow::Result<()> { | ||||
|         enabled: board.board_hal.get_config().night_lamp.enabled, | ||||
|         ..Default::default() | ||||
|     }; | ||||
|     if light_state.enabled { | ||||
|         light_state.is_day = is_day; | ||||
|         light_state.out_of_work_hour = !in_time_range( | ||||
|             &timezone_time, | ||||
|             board | ||||
|                 .board_hal | ||||
|                 .get_config() | ||||
|                 .night_lamp | ||||
|                 .night_lamp_hour_start, | ||||
|             board.board_hal.get_config().night_lamp.night_lamp_hour_end, | ||||
|         ); | ||||
|  | ||||
|         if state_of_charge | ||||
|             < board | ||||
|                 .board_hal | ||||
|                 .get_config() | ||||
|                 .night_lamp | ||||
|                 .low_soc_cutoff | ||||
|                 .into() | ||||
|         { | ||||
|             board.board_hal.get_esp().set_low_voltage_in_cycle(); | ||||
|         } else if state_of_charge | ||||
|             > board | ||||
|                 .board_hal | ||||
|                 .get_config() | ||||
|                 .night_lamp | ||||
|                 .low_soc_restore | ||||
|                 .into() | ||||
|         { | ||||
|             board.board_hal.get_esp().clear_low_voltage_in_cycle(); | ||||
|         } | ||||
|         light_state.battery_low = board.board_hal.get_esp().low_voltage_in_cycle(); | ||||
|  | ||||
|         if !light_state.out_of_work_hour { | ||||
|             if board | ||||
|                 .board_hal | ||||
|                 .get_config() | ||||
|                 .night_lamp | ||||
|                 .night_lamp_only_when_dark | ||||
|             { | ||||
|                 if !light_state.is_day { | ||||
|                     if light_state.battery_low { | ||||
|                         board.board_hal.light(false)?; | ||||
|                     } else { | ||||
|                         light_state.active = true; | ||||
|                         board.board_hal.light(true)?; | ||||
|                     } | ||||
|                 } | ||||
|             } else if light_state.battery_low { | ||||
|                 board.board_hal.light(false)?; | ||||
|             } else { | ||||
|                 light_state.active = true; | ||||
|                 board.board_hal.light(true)?; | ||||
|             } | ||||
|         } else { | ||||
|             light_state.active = false; | ||||
|             board.board_hal.light(false)?; | ||||
|         } | ||||
|  | ||||
|         info!("Lightstate is {:?}", light_state); | ||||
|     } | ||||
|     // if light_state.enabled { | ||||
|     //     light_state.is_day = is_day; | ||||
|     //     light_state.out_of_work_hour = !in_time_range( | ||||
|     //         &timezone_time, | ||||
|     //         board | ||||
|     //             .board_hal | ||||
|     //             .get_config() | ||||
|     //             .night_lamp | ||||
|     //             .night_lamp_hour_start, | ||||
|     //         board.board_hal.get_config().night_lamp.night_lamp_hour_end, | ||||
|     //     ); | ||||
|     // | ||||
|     //     if state_of_charge | ||||
|     //         < board | ||||
|     //             .board_hal | ||||
|     //             .get_config() | ||||
|     //             .night_lamp | ||||
|     //             .low_soc_cutoff | ||||
|     //             .into() | ||||
|     //     { | ||||
|     //         board.board_hal.get_esp().set_low_voltage_in_cycle(); | ||||
|     //     } else if state_of_charge | ||||
|     //         > board | ||||
|     //             .board_hal | ||||
|     //             .get_config() | ||||
|     //             .night_lamp | ||||
|     //             .low_soc_restore | ||||
|     //             .into() | ||||
|     //     { | ||||
|     //         board.board_hal.get_esp().clear_low_voltage_in_cycle(); | ||||
|     //     } | ||||
|     //     light_state.battery_low = board.board_hal.get_esp().low_voltage_in_cycle(); | ||||
|     // | ||||
|     //     if !light_state.out_of_work_hour { | ||||
|     //         if board | ||||
|     //             .board_hal | ||||
|     //             .get_config() | ||||
|     //             .night_lamp | ||||
|     //             .night_lamp_only_when_dark | ||||
|     //         { | ||||
|     //             if !light_state.is_day { | ||||
|     //                 if light_state.battery_low { | ||||
|     //                     board.board_hal.light(false)?; | ||||
|     //                 } else { | ||||
|     //                     light_state.active = true; | ||||
|     //                     board.board_hal.light(true)?; | ||||
|     //                 } | ||||
|     //             } | ||||
|     //         } else if light_state.battery_low { | ||||
|     //             board.board_hal.light(false)?; | ||||
|     //         } else { | ||||
|     //             light_state.active = true; | ||||
|     //             board.board_hal.light(true)?; | ||||
|     //         } | ||||
|     //     } else { | ||||
|     //         light_state.active = false; | ||||
|     //         board.board_hal.light(false)?; | ||||
|     //     } | ||||
|     // | ||||
|     //     info!("Lightstate is {:?}", light_state); | ||||
|     // } | ||||
|  | ||||
|     match serde_json::to_string(&light_state) { | ||||
|         Ok(state) => { | ||||
| @@ -596,24 +617,25 @@ pub async fn do_secure_pump( | ||||
|     let mut pump_time_s = 0; | ||||
|     let board = &mut BOARD_ACCESS.get().lock().await; | ||||
|     if !dry_run { | ||||
|         board | ||||
|             .board_hal | ||||
|             .get_tank_sensor() | ||||
|             .unwrap() | ||||
|             .reset_flow_meter(); | ||||
|         board | ||||
|             .board_hal | ||||
|             .get_tank_sensor() | ||||
|             .unwrap() | ||||
|             .start_flow_meter(); | ||||
|         // board | ||||
|         //     .board_hal | ||||
|         //     .get_tank_sensor() | ||||
|         //     .unwrap() | ||||
|         //     .reset_flow_meter(); | ||||
|         // board | ||||
|         //     .board_hal | ||||
|         //     .get_tank_sensor() | ||||
|         //     .unwrap() | ||||
|         //     .start_flow_meter(); | ||||
|         board.board_hal.pump(plant_id, true).await?; | ||||
|         Timer::after_millis(10).await; | ||||
|         for step in 0..plant_config.pump_time_s as usize { | ||||
|             let flow_value = board | ||||
|                 .board_hal | ||||
|                 .get_tank_sensor() | ||||
|                 .unwrap() | ||||
|                 .get_flow_meter_value(); | ||||
|             // let flow_value = board | ||||
|             //     .board_hal | ||||
|             //     .get_tank_sensor() | ||||
|             //     .unwrap() | ||||
|             //     .get_flow_meter_value(); | ||||
|             let flow_value = 1; | ||||
|             flow_collector[step] = flow_value; | ||||
|             let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; | ||||
|  | ||||
| @@ -691,12 +713,13 @@ pub async fn do_secure_pump( | ||||
|             pump_time_s += 1; | ||||
|         } | ||||
|     } | ||||
|     board.board_hal.get_tank_sensor().unwrap().stop_flow_meter(); | ||||
|     let final_flow_value = board | ||||
|         .board_hal | ||||
|         .get_tank_sensor() | ||||
|         .unwrap() | ||||
|         .get_flow_meter_value(); | ||||
|     // board.board_hal.get_tank_sensor().unwrap().stop_flow_meter(); | ||||
|     // let final_flow_value = board | ||||
|     //     .board_hal | ||||
|     //     .get_tank_sensor() | ||||
|     //     .unwrap() | ||||
|     //     .get_flow_meter_value(); | ||||
|     let final_flow_value = 12; | ||||
|     let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; | ||||
|     info!( | ||||
|         "Final flow value is {} with {} ml", | ||||
| @@ -715,8 +738,42 @@ pub async fn do_secure_pump( | ||||
| } | ||||
|  | ||||
| async fn update_charge_indicator() { | ||||
|     let board = BOARD_ACCESS.get().lock().await; | ||||
|     let board = &mut BOARD_ACCESS.get().lock().await; | ||||
|     //we have mppt controller, ask it for charging current | ||||
|     // let tank_state = determine_tank_state(&mut board); | ||||
|     // | ||||
|     // if tank_state.is_enabled() { | ||||
|     //     if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) { | ||||
|     //         match err { | ||||
|     //             TankError::SensorDisabled => { /* unreachable */ } | ||||
|     //             TankError::SensorMissing(raw_value_mv) => log( | ||||
|     //                 LogMessage::TankSensorMissing, | ||||
|     //                 raw_value_mv as u32, | ||||
|     //                 0, | ||||
|     //                 "", | ||||
|     //                 "", | ||||
|     //             ).await, | ||||
|     //             TankError::SensorValueError { value, min, max } => log( | ||||
|     //                 LogMessage::TankSensorValueRangeError, | ||||
|     //                 min as u32, | ||||
|     //                 max as u32, | ||||
|     //                 &format!("{}", value), | ||||
|     //                 "", | ||||
|     //             ).await, | ||||
|     //             TankError::BoardError(err) => { | ||||
|     //                 log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()).await | ||||
|     //             } | ||||
|     //         } | ||||
|     //         // disabled cannot trigger this because of wrapping if is_enabled | ||||
|     //         board.board_hal.general_fault(true).await; | ||||
|     //     } else if tank_state | ||||
|     //         .warn_level(&board.board_hal.get_config().tank) | ||||
|     //         .is_ok_and(|warn| warn) | ||||
|     //     { | ||||
|     //         log(LogMessage::TankWaterLevelLow, 0, 0, "", "").await; | ||||
|     //         board.board_hal.general_fault(true).await; | ||||
|     //     } | ||||
|     // } | ||||
|     if let Ok(current) = board.board_hal.get_mptt_current().await { | ||||
|         let _ = board | ||||
|             .board_hal | ||||
| @@ -726,7 +783,7 @@ async fn update_charge_indicator() { | ||||
|     else if let Ok(charging) = board | ||||
|         .board_hal | ||||
|         .get_battery_monitor() | ||||
|         .average_current_milli_ampere() | ||||
|         .average_current_milli_ampere().await | ||||
|     { | ||||
|         let _ = board.board_hal.set_charge_indicator(charging > 20); | ||||
|     } else { | ||||
| @@ -734,55 +791,55 @@ async fn update_charge_indicator() { | ||||
|         let _ = board.board_hal.set_charge_indicator(false); | ||||
|     } | ||||
| } | ||||
| // | ||||
| // async fn publish_tank_state(tank_state: &TankState, water_temp: &anyhow::Result<f32>) { | ||||
| //     let board = &mut BOARD_ACCESS.get().lock().await; | ||||
| //     match serde_json::to_string( | ||||
| //         &tank_state.as_mqtt_info(&board.board_hal.get_config().tank, water_temp), | ||||
| //     ) { | ||||
| //         Ok(state) => { | ||||
| //             let _ = board | ||||
| //                 .board_hal | ||||
| //                 .get_esp() | ||||
| //                 .mqtt_publish("/water", state.as_bytes()); | ||||
| //         } | ||||
| //         Err(err) => { | ||||
| //             info!("Error publishing tankstate {}", err); | ||||
| //         } | ||||
| //     }; | ||||
| // } | ||||
|  | ||||
| async fn publish_tank_state(tank_state: &TankState, water_temp: &anyhow::Result<f32>) { | ||||
|     let board = &mut BOARD_ACCESS.get().lock().await; | ||||
|     match serde_json::to_string( | ||||
|         &tank_state.as_mqtt_info(&board.board_hal.get_config().tank, water_temp), | ||||
|     ) { | ||||
|         Ok(state) => { | ||||
|             let _ = board | ||||
|                 .board_hal | ||||
|                 .get_esp() | ||||
|                 .mqtt_publish("/water", state.as_bytes()); | ||||
|         } | ||||
|         Err(err) => { | ||||
|             info!("Error publishing tankstate {}", err); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| async fn publish_plant_states(timezone_time: &DateTime<Tz>, plantstate: &[PlantState; 8]) { | ||||
|     let board = &mut BOARD_ACCESS.get().lock().await; | ||||
|     for (plant_id, (plant_state, plant_conf)) in plantstate | ||||
|         .iter() | ||||
|         .zip(&board.board_hal.get_config().plants.clone()) | ||||
|         .enumerate() | ||||
|     { | ||||
|         match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time)) { | ||||
|             Ok(state) => { | ||||
|                 let plant_topic = format!("/plant{}", plant_id + 1); | ||||
|                 let _ = board | ||||
|                     .board_hal | ||||
|                     .get_esp() | ||||
|                     .mqtt_publish(&plant_topic, state.as_bytes()) | ||||
|                     .await; | ||||
|                 //TODO? reduce speed as else messages will be dropped | ||||
|                 Timer::after_millis(200).await | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 error!("Error publishing plant state {}", err); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| // async fn publish_plant_states(timezone_time: &DateTime<Tz>, plantstate: &[PlantState; 8]) { | ||||
| //     let board = &mut BOARD_ACCESS.get().lock().await; | ||||
| //     for (plant_id, (plant_state, plant_conf)) in plantstate | ||||
| //         .iter() | ||||
| //         .zip(&board.board_hal.get_config().plants.clone()) | ||||
| //         .enumerate() | ||||
| //     { | ||||
| //         match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time)) { | ||||
| //             Ok(state) => { | ||||
| //                 let plant_topic = format!("/plant{}", plant_id + 1); | ||||
| //                 let _ = board | ||||
| //                     .board_hal | ||||
| //                     .get_esp() | ||||
| //                     .mqtt_publish(&plant_topic, state.as_bytes()) | ||||
| //                     .await; | ||||
| //                 //TODO? reduce speed as else messages will be dropped | ||||
| //                 Timer::after_millis(200).await | ||||
| //             } | ||||
| //             Err(err) => { | ||||
| //                 error!("Error publishing plant state {}", err); | ||||
| //             } | ||||
| //         }; | ||||
| //     } | ||||
| // } | ||||
|  | ||||
| async fn publish_firmware_info( | ||||
|     version: VersionInfo, | ||||
|     address: u32, | ||||
|     ota_state_string: &str, | ||||
|     ip_address: &String, | ||||
|     timezone_time: DateTime<Tz>, | ||||
|     timezone_time: &String, | ||||
| ) { | ||||
|     let board = &mut BOARD_ACCESS.get().lock().await; | ||||
|     let _ = board | ||||
| @@ -802,7 +859,7 @@ async fn publish_firmware_info( | ||||
|         .await; | ||||
|     let _ = board.board_hal.get_esp().mqtt_publish( | ||||
|         "/firmware/last_online", | ||||
|         timezone_time.to_rfc3339().as_bytes(), | ||||
|         timezone_time.as_bytes(), | ||||
|     ); | ||||
|     let _ = board | ||||
|         .board_hal | ||||
| @@ -821,7 +878,7 @@ async fn publish_firmware_info( | ||||
| } | ||||
|  | ||||
| async fn try_connect_wifi_sntp_mqtt() -> NetworkMode { | ||||
|     let board = BOARD_ACCESS.get().lock().await; | ||||
|     let board = &mut BOARD_ACCESS.get().lock().await; | ||||
|     let nw_conf = &board.board_hal.get_config().network.clone(); | ||||
|     match board.board_hal.get_esp().wifi(nw_conf).await { | ||||
|         Ok(ip_info) => { | ||||
| @@ -860,7 +917,7 @@ async fn try_connect_wifi_sntp_mqtt() -> NetworkMode { | ||||
|         } | ||||
|         Err(_) => { | ||||
|             info!("Offline mode"); | ||||
|             board.board_hal.general_fault(true); | ||||
|             board.board_hal.general_fault(true).await; | ||||
|             NetworkMode::OFFLINE | ||||
|         } | ||||
|     } | ||||
| @@ -922,8 +979,8 @@ async fn publish_mppt_state() -> anyhow::Result<()> { | ||||
| } | ||||
|  | ||||
| async fn publish_battery_state() -> () { | ||||
|     let board = BOARD_ACCESS.get().lock().await; | ||||
|     let state = board.board_hal.get_battery_monitor().get_battery_state(); | ||||
|     let board = &mut BOARD_ACCESS.get().lock().await; | ||||
|     let state = board.board_hal.get_battery_monitor().get_battery_state().await; | ||||
|     if let Ok(serialized_battery_state_bytes) = | ||||
|         serde_json::to_string(&state).map(|s| s.into_bytes()) | ||||
|     { | ||||
| @@ -995,13 +1052,10 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! { | ||||
| async fn main(spawner: Spawner) { | ||||
|     // intialize embassy | ||||
|     logger::init_logger_from_env(); | ||||
|     let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); | ||||
|     let peripherals = esp_hal::init(config); | ||||
|  | ||||
|     esp_alloc::heap_allocator!(size: 64 * 1024); | ||||
|  | ||||
|     let timer0 = SystemTimer::new(peripherals.SYSTIMER); | ||||
|     esp_hal_embassy::init(timer0.alarm0); | ||||
|     //force init here! | ||||
|     let board = BOARD_ACCESS.get().lock().await; | ||||
|     drop(board); | ||||
|     println!("test"); | ||||
|  | ||||
|     info!("Embassy initialized!"); | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ use crate::{ | ||||
|     hal::{Sensor, HAL}, | ||||
|     in_time_range, | ||||
| }; | ||||
| use alloc::string::String; | ||||
| use alloc::string::{String, ToString}; | ||||
| use chrono::{DateTime, TimeDelta, Utc}; | ||||
| use chrono_tz::Tz; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| @@ -118,7 +118,7 @@ fn map_range_moisture( | ||||
| impl PlantState { | ||||
|     pub async fn read_hardware_state(plant_id: usize, board: &mut HAL<'_>) -> Self { | ||||
|         let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a { | ||||
|             match board.board_hal.measure_moisture_hz(plant_id, Sensor::A) { | ||||
|             match board.board_hal.measure_moisture_hz(plant_id, Sensor::A).await { | ||||
|                 Ok(raw) => match map_range_moisture( | ||||
|                     raw, | ||||
|                     board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency, | ||||
| @@ -139,7 +139,7 @@ impl PlantState { | ||||
|         }; | ||||
|  | ||||
|         let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b { | ||||
|             match board.board_hal.measure_moisture_hz(plant_id, Sensor::B) { | ||||
|             match board.board_hal.measure_moisture_hz(plant_id, Sensor::B).await { | ||||
|                 Ok(raw) => match map_range_moisture( | ||||
|                     raw, | ||||
|                     board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency, | ||||
| @@ -264,50 +264,50 @@ impl PlantState { | ||||
|             PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn to_mqtt_info( | ||||
|         &self, | ||||
|         plant_conf: &PlantConfig, | ||||
|         current_time: &DateTime<Tz>, | ||||
|     ) -> PlantInfo<'_> { | ||||
|         PlantInfo { | ||||
|             sensor_a: &self.sensor_a, | ||||
|             sensor_b: &self.sensor_b, | ||||
|             mode: plant_conf.mode, | ||||
|             do_water: self.needs_to_be_watered(plant_conf, current_time), | ||||
|             dry: if let Some(moisture_percent) = self.plant_moisture().0 { | ||||
|                 moisture_percent < plant_conf.target_moisture | ||||
|             } else { | ||||
|                 false | ||||
|             }, | ||||
|             cooldown: self.pump_in_timeout(plant_conf, current_time), | ||||
|             out_of_work_hour: in_time_range( | ||||
|                 current_time, | ||||
|                 plant_conf.pump_hour_start, | ||||
|                 plant_conf.pump_hour_end, | ||||
|             ), | ||||
|             consecutive_pump_count: self.pump.consecutive_pump_count, | ||||
|             pump_error: self.pump.is_err(plant_conf), | ||||
|             last_pump: self | ||||
|                 .pump | ||||
|                 .previous_pump | ||||
|                 .map(|t| t.with_timezone(¤t_time.timezone())), | ||||
|             next_pump: if matches!( | ||||
|                 plant_conf.mode, | ||||
|                 PlantWateringMode::TimerOnly | ||||
|                     | PlantWateringMode::TargetMoisture | ||||
|                     | PlantWateringMode::MinMoisture | ||||
|             ) { | ||||
|                 self.pump.previous_pump.and_then(|last_pump| { | ||||
|                     last_pump | ||||
|                         .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into())) | ||||
|                         .map(|t| t.with_timezone(¤t_time.timezone())) | ||||
|                 }) | ||||
|             } else { | ||||
|                 None | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|     // | ||||
|     // pub fn to_mqtt_info( | ||||
|     //     &self, | ||||
|     //     plant_conf: &PlantConfig, | ||||
|     //     current_time: &DateTime<Tz>, | ||||
|     // ) -> PlantInfo<'_> { | ||||
|     //     PlantInfo { | ||||
|     //         sensor_a: &self.sensor_a, | ||||
|     //         sensor_b: &self.sensor_b, | ||||
|     //         mode: plant_conf.mode, | ||||
|     //         do_water: self.needs_to_be_watered(plant_conf, current_time), | ||||
|     //         dry: if let Some(moisture_percent) = self.plant_moisture().0 { | ||||
|     //             moisture_percent < plant_conf.target_moisture | ||||
|     //         } else { | ||||
|     //             false | ||||
|     //         }, | ||||
|     //         cooldown: self.pump_in_timeout(plant_conf, current_time), | ||||
|     //         out_of_work_hour: in_time_range( | ||||
|     //             current_time, | ||||
|     //             plant_conf.pump_hour_start, | ||||
|     //             plant_conf.pump_hour_end, | ||||
|     //         ), | ||||
|     //         consecutive_pump_count: self.pump.consecutive_pump_count, | ||||
|     //         pump_error: self.pump.is_err(plant_conf), | ||||
|     //         last_pump: self | ||||
|     //             .pump | ||||
|     //             .previous_pump | ||||
|     //             .map(|t| t.with_timezone(¤t_time.timezone())), | ||||
|     //         next_pump: if matches!( | ||||
|     //             plant_conf.mode, | ||||
|     //             PlantWateringMode::TimerOnly | ||||
|     //                 | PlantWateringMode::TargetMoisture | ||||
|     //                 | PlantWateringMode::MinMoisture | ||||
|     //         ) { | ||||
|     //             self.pump.previous_pump.and_then(|last_pump| { | ||||
|     //                 last_pump | ||||
|     //                     .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into())) | ||||
|     //                     .map(|t| t.with_timezone(¤t_time.timezone())) | ||||
|     //             }) | ||||
|     //         } else { | ||||
|     //             None | ||||
|     //         }, | ||||
|     //     } | ||||
|     // } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, PartialEq, Serialize)] | ||||
| @@ -330,8 +330,8 @@ pub struct PlantInfo<'a> { | ||||
|     /// how often has the pump been watered without reaching target moisture | ||||
|     consecutive_pump_count: u32, | ||||
|     pump_error: Option<PumpError>, | ||||
|     /// last time when the pump was active | ||||
|     last_pump: Option<DateTime<Tz>>, | ||||
|     /// next time when pump should activate | ||||
|     next_pump: Option<DateTime<Tz>>, | ||||
|     // /// last time when the pump was active | ||||
|     // last_pump: Option<DateTime<Tz>>, | ||||
|     // /// next time when pump should activate | ||||
|     // next_pump: Option<DateTime<Tz>>, | ||||
| } | ||||
|   | ||||
| @@ -151,21 +151,21 @@ impl TankState { | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn determine_tank_state(board: &mut std::sync::MutexGuard<'_, HAL<'_>>) -> TankState { | ||||
|     if board.board_hal.get_config().tank.tank_sensor_enabled { | ||||
|         match board | ||||
|             .board_hal | ||||
|             .get_tank_sensor() | ||||
|             .context("no sensor") | ||||
|             .and_then(|f| f.tank_sensor_voltage()) | ||||
|         { | ||||
|             Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv), | ||||
|             Err(err) => TankState::Error(TankError::BoardError(err.to_string())), | ||||
|         } | ||||
|     } else { | ||||
|         TankState::Disabled | ||||
|     } | ||||
| } | ||||
| // pub fn determine_tank_state(board: &mut std::sync::MutexGuard<'_, HAL<'_>>) -> TankState { | ||||
| //     if board.board_hal.get_config().tank.tank_sensor_enabled { | ||||
| //         match board | ||||
| //             .board_hal | ||||
| //             .get_tank_sensor() | ||||
| //             .context("no sensor") | ||||
| //             .and_then(|f| f.tank_sensor_voltage()) | ||||
| //         { | ||||
| //             Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv), | ||||
| //             Err(err) => TankState::Error(TankError::BoardError(err.to_string())), | ||||
| //         } | ||||
| //     } else { | ||||
| //         TankState::Disabled | ||||
| //     } | ||||
| // } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| /// Information structure send to mqtt for monitoring purposes | ||||
|   | ||||
		Reference in New Issue
	
	Block a user