split rtc module out to reduce repreated exactly same code
This commit is contained in:
		| @@ -5,20 +5,20 @@ target = "riscv32imac-esp-espidf" | ||||
| [target.riscv32imac-esp-espidf] | ||||
| linker = "ldproxy" | ||||
| #runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv -b no-reset" # Select this runner in case of usb ttl | ||||
| #runner = "espflash flash --monitor" | ||||
| runner = "cargo runner" | ||||
| runner = "espflash flash --monitor" | ||||
| #runner = "cargo runner" | ||||
|  | ||||
|  | ||||
| #runner = "espflash flash --monitor --partition-table partitions.csv -b no-reset" # create upgrade image file for webupload | ||||
| # runner = espflash erase-parts otadata  //ensure flash is clean | ||||
|  | ||||
| rustflags = [ "--cfg",  "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 | ||||
| rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 | ||||
|  | ||||
| [unstable] | ||||
| build-std = ["std", "panic_abort"] | ||||
|  | ||||
| [env] | ||||
| MCU="esp32c6" | ||||
| MCU = "esp32c6" | ||||
| # Note: this variable is not used by the pio builder (`cargo build --features pio`) | ||||
| ESP_IDF_VERSION = "v5.2.1" | ||||
| CHRONO_TZ_TIMEZONE_FILTER = "UTC|America/New_York|America/Chicago|America/Los_Angeles|Europe/London|Europe/Berlin|Europe/Paris|Asia/Tokyo|Asia/Shanghai|Asia/Kolkata|Australia/Sydney|America/Sao_Paulo|Africa/Johannesburg|Asia/Dubai|Pacific/Auckland" | ||||
|   | ||||
| @@ -10,7 +10,7 @@ resolver = "2" | ||||
| # Explicitly disable LTO which the Xtensa codegen backend has issues | ||||
| lto = false | ||||
| strip = false | ||||
| debug = true  | ||||
| debug = true | ||||
| overflow-checks = true | ||||
| panic = "abort" | ||||
| incremental = true | ||||
| @@ -54,7 +54,7 @@ esp-idf-sys = { version = "0.36.1", features = ["binstart", "native"] } | ||||
| esp-idf-svc = { version = "0.51.0", default-features = false } | ||||
| embedded-hal = "1.0.0" | ||||
| heapless = { version = "0.8", features = ["serde"] } | ||||
| embedded-hal-bus = { version = "0.2.0", features = ["std"] } | ||||
| embedded-hal-bus = { version = "0.3.0", features = ["std"] } | ||||
|  | ||||
| #Hardware additional driver | ||||
| ds18b20 = "0.1.1" | ||||
| @@ -69,16 +69,16 @@ strum = { version = "0.27.0", features = ["derive"] } | ||||
| measurements = "0.11.0" | ||||
|  | ||||
| #json | ||||
| serde = { version = "1.0.192",  features = ["derive"] } | ||||
| serde = { version = "1.0.192", features = ["derive"] } | ||||
| serde_json = "1.0.108" | ||||
|  | ||||
| #timezone | ||||
| chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone" , "alloc", "serde"] } | ||||
| chrono-tz = {version="0.8.0", default-features = false , features = [ "filter-by-regex" ]} | ||||
| 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"] } | ||||
| eeprom24x = "0.7.2" | ||||
| url = "2.5.3" | ||||
| crc = "3.2.1" | ||||
| bincode = "1.3.3" | ||||
| bincode = "2.0.1" | ||||
| ringbuffer = "0.15.0" | ||||
| text-template = "0.1.0" | ||||
| strum_macros = "0.27.0" | ||||
| @@ -98,5 +98,5 @@ ina219 = { version = "0.2.0", features = ["std"] } | ||||
|  | ||||
| [build-dependencies] | ||||
| cc = "=1.1.30" | ||||
| embuild =  { version= "0.32.0", features = ["espidf"]} | ||||
| embuild = { version = "0.32.0", features = ["espidf"] } | ||||
| vergen = { version = "8.2.6", features = ["build", "git", "gitcl"] } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| partition_table="partitions.csv" | ||||
| partition_table = "partitions.csv" | ||||
| [connection] | ||||
| serial = "/dev/ttyACM0" | ||||
| baudrate = 921600 | ||||
| [flash] | ||||
| size = "16MB" | ||||
| @@ -2,5 +2,5 @@ nvs,      data, nvs,     ,        16k, | ||||
| otadata,  data, ota,     ,        8k, | ||||
| phy_init, data, phy,     ,        4k, | ||||
| ota_0,    app,  ota_0,   ,        3968k, | ||||
| ota_1,    app,  ota_0,   ,        3968k, | ||||
| ota_1,    app,  ota_1,   ,        3968k, | ||||
| storage,  data, spiffs,  ,        8M, | ||||
|   | ||||
| 
 | 
| @@ -240,14 +240,6 @@ impl Esp<'_> { | ||||
|         println!("Wrote config config {:?}", config); | ||||
|         anyhow::Ok(()) | ||||
|     } | ||||
|     pub(crate) fn delete_config(&self) -> anyhow::Result<()> { | ||||
|         let config = Path::new(Self::CONFIG_FILE); | ||||
|         if config.exists() { | ||||
|             println!("Removing config"); | ||||
|             fs::remove_file(config)? | ||||
|         } | ||||
|         anyhow::Ok(()) | ||||
|     } | ||||
|     pub(crate) fn mount_file_system(&mut self) -> anyhow::Result<()> { | ||||
|         log(LogMessage::MountingFilesystem, 0, 0, "", ""); | ||||
|         let base_path = CString::new("/spiffs")?; | ||||
| @@ -308,10 +300,7 @@ impl Esp<'_> { | ||||
|                         OkStd(file) => { | ||||
|                             let f = FileInfo { | ||||
|                                 filename: file.file_name().into_string().unwrap(), | ||||
|                                 size: file | ||||
|                                     .metadata() | ||||
|                                     .map(|it| it.len()) | ||||
|                                     .unwrap_or_default() | ||||
|                                 size: file.metadata().map(|it| it.len()).unwrap_or_default() | ||||
|                                     as usize, | ||||
|                             }; | ||||
|                             result.push(f); | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| use crate::hal::esp::Esp; | ||||
| use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor}; | ||||
| use crate::hal::rtc::{BackupHeader, RTCModuleInteraction}; | ||||
| use crate::hal::{deep_sleep, BoardInteraction, FreePeripherals, Sensor}; | ||||
| use crate::{ | ||||
|     config::PlantControllerConfig, | ||||
|     hal::battery::{BatteryInteraction, NoBatteryMonitor}, | ||||
| @@ -16,6 +17,31 @@ pub struct Initial<'a> { | ||||
|     pub(crate) esp: Esp<'a>, | ||||
|     pub(crate) config: PlantControllerConfig, | ||||
|     pub(crate) battery: Box<dyn BatteryInteraction + Send>, | ||||
|     pub rtc: Box<dyn RTCModuleInteraction + Send>, | ||||
| } | ||||
|  | ||||
| struct NoRTC {} | ||||
|  | ||||
| impl RTCModuleInteraction for NoRTC { | ||||
|     fn get_backup_info(&mut self) -> Result<BackupHeader> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn get_backup_config(&mut self) -> Result<Vec<u8>> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn backup_config(&mut self, _bytes: &[u8]) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(crate) fn create_initial_board( | ||||
| @@ -36,6 +62,7 @@ pub(crate) fn create_initial_board( | ||||
|         config, | ||||
|         esp, | ||||
|         battery: Box::new(NoBatteryMonitor {}), | ||||
|         rtc: Box::new(NoRTC {}), | ||||
|     }; | ||||
|     Ok(Box::new(v)) | ||||
| } | ||||
| @@ -53,6 +80,10 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { | ||||
|         &mut self.battery | ||||
|     } | ||||
|  | ||||
|     fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send> { | ||||
|         &mut self.rtc | ||||
|     } | ||||
|  | ||||
|     fn set_charge_indicator(&mut self, _charging: bool) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
| @@ -61,22 +92,9 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { | ||||
|         deep_sleep(duration_in_ms) | ||||
|     } | ||||
|  | ||||
|     fn get_backup_info(&mut self) -> Result<BackupHeader> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn get_backup_config(&mut self) -> Result<Vec<u8>> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn backup_config(&mut self, _bytes: &[u8]) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn is_day(&self) -> bool { | ||||
|         false | ||||
|     } | ||||
|  | ||||
|     fn water_temperature_c(&mut self) -> Result<f32> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
| @@ -87,13 +105,14 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { | ||||
|     fn light(&mut self, _enable: bool) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn pump(&mut self, _plant: usize, _enable: bool) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     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> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
| @@ -102,17 +121,6 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { | ||||
|         let _ = self.general_fault.set_state(enable.into()); | ||||
|     } | ||||
|  | ||||
|     fn factory_reset(&mut self) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|     fn test_pump(&mut self, _plant: usize) -> Result<()> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| pub(crate) mod battery; | ||||
| mod esp; | ||||
| mod initial_hal; | ||||
| mod rtc; | ||||
| mod v3_hal; | ||||
| mod v4_hal; | ||||
|  | ||||
| use crate::hal::rtc::{DS3231Module, RTCModuleInteraction}; | ||||
| use crate::{ | ||||
|     config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, | ||||
|     hal::{ | ||||
| @@ -15,7 +17,8 @@ use crate::{ | ||||
| use anyhow::{Ok, Result}; | ||||
| use battery::BQ34Z100G1; | ||||
| use bq34z100::Bq34z100g1Driver; | ||||
| use chrono::{DateTime, Utc}; | ||||
| use ds323x::{DateTimeAccess, Ds323x}; | ||||
| use eeprom24x::{Eeprom24x, SlaveAddr}; | ||||
| use embedded_hal_bus::i2c::MutexDevice; | ||||
| use esp_idf_hal::{ | ||||
|     adc::ADC1, | ||||
| @@ -39,7 +42,6 @@ use esp_idf_sys::{ | ||||
| use esp_ota::mark_app_valid; | ||||
| use measurements::{Current, Voltage}; | ||||
| use once_cell::sync::Lazy; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::result::Result::Ok as OkStd; | ||||
| use std::sync::Mutex; | ||||
| use std::time::Duration; | ||||
| @@ -52,8 +54,6 @@ const TANK_MULTI_SAMPLE: usize = 11; | ||||
|  | ||||
| pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(PlantHal::create_i2c); | ||||
|  | ||||
| const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC); | ||||
|  | ||||
| fn deep_sleep(duration_in_ms: u64) -> ! { | ||||
|     unsafe { | ||||
|         //if we don't do this here, we might just revert newly flashed firmware | ||||
| @@ -84,22 +84,14 @@ pub struct HAL<'a> { | ||||
|     pub board_hal: Box<dyn BoardInteraction<'a> + Send>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, PartialEq, Debug, Default)] | ||||
| pub struct BackupHeader { | ||||
|     pub timestamp: i64, | ||||
|     crc16: u16, | ||||
|     pub size: usize, | ||||
| } | ||||
|  | ||||
| pub trait BoardInteraction<'a> { | ||||
|     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>; | ||||
|     fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send>; | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; | ||||
|     fn deep_sleep(&mut self, duration_in_ms: u64) -> !; | ||||
|     fn get_backup_info(&mut self) -> Result<BackupHeader>; | ||||
|     fn get_backup_config(&mut self) -> Result<Vec<u8>>; | ||||
|     fn backup_config(&mut self, bytes: &[u8]) -> Result<()>; | ||||
|  | ||||
|     fn is_day(&self) -> bool; | ||||
|     //should be multsampled | ||||
|     fn water_temperature_c(&mut self) -> Result<f32>; | ||||
| @@ -110,9 +102,7 @@ pub trait BoardInteraction<'a> { | ||||
|     fn fault(&mut self, plant: usize, enable: bool) -> Result<()>; | ||||
|     fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>; | ||||
|     fn general_fault(&mut self, enable: bool); | ||||
|     fn factory_reset(&mut self) -> Result<()>; | ||||
|     fn get_rtc_time(&mut self) -> Result<DateTime<Utc>>; | ||||
|     fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> Result<()>; | ||||
|  | ||||
|     fn test_pump(&mut self, plant: usize) -> Result<()>; | ||||
|     fn test(&mut self) -> Result<()>; | ||||
|     fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>; | ||||
| @@ -120,6 +110,18 @@ pub trait BoardInteraction<'a> { | ||||
|     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 | ||||
|     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(); | ||||
|         } | ||||
|         let _ = self.general_fault(even.into()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(dead_code)] | ||||
| pub struct FreePeripherals { | ||||
|     pub gpio0: Gpio0, | ||||
| @@ -259,6 +261,37 @@ impl PlantHal { | ||||
|  | ||||
|         let config = esp.load_config(); | ||||
|  | ||||
|         println!("Init rtc driver"); | ||||
|         let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); | ||||
|  | ||||
|         println!("Init rtc eeprom driver"); | ||||
|         let mut eeprom = { | ||||
|             Eeprom24x::new_24x32( | ||||
|                 MutexDevice::new(&I2C_DRIVER), | ||||
|                 SlaveAddr::Alternative(true, true, true), | ||||
|             ) | ||||
|         }; | ||||
|         let rtc_time = rtc.datetime(); | ||||
|         match rtc_time { | ||||
|             OkStd(tt) => { | ||||
|                 println!("Rtc Module reports time at UTC {}", tt); | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 println!("Rtc Module could not be read {:?}", err); | ||||
|             } | ||||
|         } | ||||
|         match eeprom.read_byte(0) { | ||||
|             OkStd(byte) => { | ||||
|                 println!("Read first byte with status {}", byte); | ||||
|             } | ||||
|             Err(err) => { | ||||
|                 println!("Eeprom could not read first byte {:?}", err); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let rtc_module: Box<dyn RTCModuleInteraction + Send> = | ||||
|             Box::new(DS3231Module { rtc, eeprom }) as Box<dyn RTCModuleInteraction + Send>; | ||||
|  | ||||
|         let hal = match config { | ||||
|             Result::Ok(config) => { | ||||
|                 let battery_interaction: Box<dyn BatteryInteraction + Send> = | ||||
| @@ -296,10 +329,10 @@ impl PlantHal { | ||||
|                         initial_hal::create_initial_board(free_pins, fs_mount_error, config, esp)? | ||||
|                     } | ||||
|                     BoardVersion::V3 => { | ||||
|                         v3_hal::create_v3(free_pins, esp, config, battery_interaction)? | ||||
|                         v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)? | ||||
|                     } | ||||
|                     BoardVersion::V4 => { | ||||
|                         v4_hal::create_v4(free_pins, esp, config, battery_interaction)? | ||||
|                         v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)? | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										157
									
								
								rust/src/hal/rtc.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								rust/src/hal/rtc.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | ||||
| use anyhow::{anyhow, bail}; | ||||
| use bincode::{config, Decode, Encode}; | ||||
| use chrono::{DateTime, Utc}; | ||||
| use ds323x::{DateTimeAccess, Ds323x}; | ||||
| use eeprom24x::{Eeprom24x, Eeprom24xTrait}; | ||||
| use embedded_hal_bus::i2c::MutexDevice; | ||||
| use esp_idf_hal::delay::Delay; | ||||
| use esp_idf_hal::i2c::I2cDriver; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::result::Result::Ok as OkStd; | ||||
|  | ||||
| const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC); | ||||
|  | ||||
| pub trait RTCModuleInteraction { | ||||
|     fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader>; | ||||
|     fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>>; | ||||
|     fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()>; | ||||
|     fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>>; | ||||
|     fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()>; | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)] | ||||
| pub struct BackupHeader { | ||||
|     pub timestamp: i64, | ||||
|     crc16: u16, | ||||
|     pub size: usize, | ||||
| } | ||||
|  | ||||
| pub struct DS3231Module<'a> { | ||||
|     pub(crate) rtc: | ||||
|         Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>, | ||||
|     pub(crate) eeprom: Eeprom24x< | ||||
|         MutexDevice<'a, I2cDriver<'a>>, | ||||
|         eeprom24x::page_size::B32, | ||||
|         eeprom24x::addr_size::TwoBytes, | ||||
|         eeprom24x::unique_serial::No, | ||||
|     >, | ||||
| } | ||||
|  | ||||
| impl RTCModuleInteraction for DS3231Module<'_> { | ||||
|     fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader> { | ||||
|         let config = config::standard(); | ||||
|         let store = bincode::encode_to_vec(&BackupHeader::default(), config)?.len(); | ||||
|         let mut header_page_buffer = vec![0_u8; store]; | ||||
|  | ||||
|         self.eeprom | ||||
|             .read_data(0, &mut header_page_buffer) | ||||
|             .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; | ||||
|  | ||||
|         println!("Raw header is {:?} with size {}", header_page_buffer, store); | ||||
|         let (header, _len): (BackupHeader, usize) = | ||||
|             bincode::decode_from_slice(&header_page_buffer[..], config)?; | ||||
|         anyhow::Ok(header) | ||||
|     } | ||||
|  | ||||
|     fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>> { | ||||
|         let config = config::standard(); | ||||
|         let store = bincode::encode_to_vec(&BackupHeader::default(), config)?.len(); | ||||
|         let mut header_page_buffer = vec![0_u8; store]; | ||||
|  | ||||
|         self.eeprom | ||||
|             .read_data(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)?; | ||||
|  | ||||
|         //skip page 0, used by the header | ||||
|         let data_start_address = self.eeprom.page_size() as u32; | ||||
|         let mut data_buffer = vec![0_u8; header.size]; | ||||
|         self.eeprom | ||||
|             .read_data(data_start_address, &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 time = self.get_rtc_time()?.timestamp_millis(); | ||||
|  | ||||
|         let delay = Delay::new_default(); | ||||
|  | ||||
|         let checksum = X25.checksum(bytes); | ||||
|         let page_size = self.eeprom.page_size(); | ||||
|  | ||||
|         let header = BackupHeader { | ||||
|             crc16: checksum, | ||||
|             timestamp: time, | ||||
|             size: bytes.len(), | ||||
|         }; | ||||
|         let config = config::standard(); | ||||
|         let encoded = bincode::encode_to_vec(&header, config)?; | ||||
|         if encoded.len() > page_size { | ||||
|             bail!( | ||||
|                 "Size limit reached header is {}, but firest page is only {}", | ||||
|                 encoded.len(), | ||||
|                 page_size | ||||
|             ) | ||||
|         } | ||||
|         let as_u8: &[u8] = &encoded; | ||||
|  | ||||
|         match self.eeprom.write_page(0, as_u8) { | ||||
|             OkStd(_) => {} | ||||
|             Err(err) => bail!("Error writing eeprom {:?}", err), | ||||
|         }; | ||||
|         delay.delay_ms(5); | ||||
|  | ||||
|         let to_write = bytes.chunks(page_size); | ||||
|  | ||||
|         let mut lastiter = 0; | ||||
|         let mut current_page = 1; | ||||
|         for chunk in to_write { | ||||
|             let address = current_page * page_size as u32; | ||||
|             self.eeprom | ||||
|                 .write_page(address, chunk) | ||||
|                 .map_err(|err| anyhow!("Error writing eeprom {:?}", err))?; | ||||
|             current_page += 1; | ||||
|  | ||||
|             let iter = (current_page % 8) as usize; | ||||
|             if iter != lastiter { | ||||
|                 //todo we want to call progress here, how to do this? | ||||
|                 //target.progress(); | ||||
|                 lastiter = iter; | ||||
|             } | ||||
|  | ||||
|             delay.delay_ms(5); | ||||
|         } | ||||
|         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,6 +1,7 @@ | ||||
| use crate::hal::rtc::RTCModuleInteraction; | ||||
| use crate::hal::{ | ||||
|     deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, | ||||
|     REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25, | ||||
|     deep_sleep, BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT, REPEAT_MOIST_MEASURE, | ||||
|     TANK_MULTI_SAMPLE, | ||||
| }; | ||||
| use crate::log::{log, LogMessage}; | ||||
| use crate::{ | ||||
| @@ -8,21 +9,15 @@ use crate::{ | ||||
|     hal::{battery::BatteryInteraction, esp::Esp}, | ||||
| }; | ||||
| use anyhow::{anyhow, bail, Ok, Result}; | ||||
| use chrono::{DateTime, Utc}; | ||||
| use ds18b20::Ds18b20; | ||||
| use ds323x::{DateTimeAccess, Ds323x}; | ||||
| use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr}; | ||||
| use embedded_hal::digital::OutputPin; | ||||
| use embedded_hal_bus::i2c::MutexDevice; | ||||
| use esp_idf_hal::{ | ||||
|     adc::{ | ||||
|         attenuation, | ||||
|         oneshot::{config::AdcChannelConfig, AdcChannelDriver, AdcDriver}, | ||||
|         Resolution, | ||||
|     }, | ||||
|     delay::Delay, | ||||
|     gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, PinDriver, Pull}, | ||||
|     i2c::I2cDriver, | ||||
|     pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex}, | ||||
| }; | ||||
| use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; | ||||
| @@ -79,6 +74,7 @@ const FAULT_2: usize = 23; | ||||
| pub struct V3<'a> { | ||||
|     config: PlantControllerConfig, | ||||
|     battery_monitor: Box<dyn BatteryInteraction + Send>, | ||||
|     rtc_module: Box<dyn RTCModuleInteraction + Send>, | ||||
|     esp: Esp<'a>, | ||||
|     shift_register: ShiftRegister40< | ||||
|         PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
| @@ -95,14 +91,6 @@ pub struct V3<'a> { | ||||
|     general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
|     signal_counter: PcntDriver<'a>, | ||||
|     one_wire_bus: OneWire<PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>>, | ||||
|     rtc: | ||||
|         Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>, | ||||
|     eeprom: Eeprom24x< | ||||
|         MutexDevice<'a, I2cDriver<'a>>, | ||||
|         eeprom24x::page_size::B32, | ||||
|         eeprom24x::addr_size::TwoBytes, | ||||
|         eeprom24x::unique_serial::No, | ||||
|     >, | ||||
| } | ||||
|  | ||||
| pub(crate) fn create_v3( | ||||
| @@ -110,6 +98,7 @@ pub(crate) fn create_v3( | ||||
|     esp: Esp<'static>, | ||||
|     config: PlantControllerConfig, | ||||
|     battery_monitor: Box<dyn BatteryInteraction + Send>, | ||||
|     rtc_module: Box<dyn RTCModuleInteraction + Send>, | ||||
| ) -> Result<Box<dyn BoardInteraction<'static> + Send>> { | ||||
|     let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?; | ||||
|     clock.set_pull(Pull::Floating)?; | ||||
| @@ -141,40 +130,9 @@ pub(crate) fn create_v3( | ||||
|     let ms4 = &mut shift_register.decompose()[MS_4]; | ||||
|     ms4.set_high()?; | ||||
|  | ||||
|     println!("Init battery driver"); | ||||
|  | ||||
|     println!("Init rtc driver"); | ||||
|     let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); | ||||
|  | ||||
|     println!("Init rtc eeprom driver"); | ||||
|     let mut eeprom = { | ||||
|         Eeprom24x::new_24x32( | ||||
|             MutexDevice::new(&I2C_DRIVER), | ||||
|             SlaveAddr::Alternative(true, true, true), | ||||
|         ) | ||||
|     }; | ||||
|  | ||||
|     let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?; | ||||
|     one_wire_pin.set_pull(Pull::Floating)?; | ||||
|  | ||||
|     let rtc_time = rtc.datetime(); | ||||
|     match rtc_time { | ||||
|         OkStd(tt) => { | ||||
|             println!("Rtc Module reports time at UTC {}", tt); | ||||
|         } | ||||
|         Err(err) => { | ||||
|             println!("Rtc Module could not be read {:?}", err); | ||||
|         } | ||||
|     } | ||||
|     match eeprom.read_byte(0) { | ||||
|         OkStd(byte) => { | ||||
|             println!("Read first byte with status {}", byte); | ||||
|         } | ||||
|         Err(err) => { | ||||
|             println!("Eeprom could not read first byte {:?}", err); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let mut signal_counter = PcntDriver::new( | ||||
|         peripherals.pcnt0, | ||||
|         Some(peripherals.gpio22), | ||||
| @@ -233,6 +191,7 @@ pub(crate) fn create_v3( | ||||
|     Ok(Box::new(V3 { | ||||
|         config, | ||||
|         battery_monitor, | ||||
|         rtc_module, | ||||
|         esp, | ||||
|         shift_register, | ||||
|         _shift_register_enable_invert: shift_register_enable_invert, | ||||
| @@ -244,32 +203,10 @@ pub(crate) fn create_v3( | ||||
|         general_fault, | ||||
|         signal_counter, | ||||
|         one_wire_bus, | ||||
|         rtc, | ||||
|         eeprom, | ||||
|     })) | ||||
| } | ||||
|  | ||||
| impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { | ||||
|         Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?) | ||||
|     } | ||||
|  | ||||
|     fn is_day(&self) -> bool { | ||||
|         self.solar_is_day.get_level().into() | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_voltage(&mut self) -> Result<Voltage> { | ||||
|         //if working this is the hardware set mppt voltage | ||||
|         if self.is_day() { | ||||
|             Ok(Voltage::from_volts(15_f64)) | ||||
|         } else { | ||||
|             Ok(Voltage::from_volts(0_f64)) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_current(&mut self) -> Result<Current> { | ||||
|         bail!("Board does not have current sensor") | ||||
|     } | ||||
|     fn get_esp(&mut self) -> &mut Esp<'a> { | ||||
|         &mut self.esp | ||||
|     } | ||||
| @@ -282,105 +219,20 @@ impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|         &mut self.battery_monitor | ||||
|     } | ||||
|  | ||||
|     fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send> { | ||||
|         &mut self.rtc_module | ||||
|     } | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { | ||||
|         Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?) | ||||
|     } | ||||
|  | ||||
|     fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { | ||||
|         let _ = self.shift_register.decompose()[AWAKE].set_low(); | ||||
|         deep_sleep(duration_in_ms) | ||||
|     } | ||||
|  | ||||
|     fn get_backup_info(&mut self) -> Result<BackupHeader> { | ||||
|         let store = bincode::serialize(&BackupHeader::default())?.len(); | ||||
|         let mut header_page_buffer = vec![0_u8; store]; | ||||
|  | ||||
|         self.eeprom | ||||
|             .read_data(0, &mut header_page_buffer) | ||||
|             .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; | ||||
|  | ||||
|         println!("Raw header is {:?} with size {}", header_page_buffer, store); | ||||
|         let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; | ||||
|         Ok(header) | ||||
|     } | ||||
|  | ||||
|     fn get_backup_config(&mut self) -> Result<Vec<u8>> { | ||||
|         let store = bincode::serialize(&BackupHeader::default())?.len(); | ||||
|         let mut header_page_buffer = vec![0_u8; store]; | ||||
|  | ||||
|         self.eeprom | ||||
|             .read_data(0, &mut header_page_buffer) | ||||
|             .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; | ||||
|  | ||||
|         let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; | ||||
|  | ||||
|         //skip page 0, used by the header | ||||
|         let data_start_address = self.eeprom.page_size() as u32; | ||||
|         let mut data_buffer = vec![0_u8; header.size]; | ||||
|         self.eeprom | ||||
|             .read_data(data_start_address, &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 | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         Ok(data_buffer) | ||||
|     } | ||||
|  | ||||
|     fn backup_config(&mut self, bytes: &[u8]) -> Result<()> { | ||||
|         let time = self.get_rtc_time()?.timestamp_millis(); | ||||
|  | ||||
|         let delay = Delay::new_default(); | ||||
|  | ||||
|         let checksum = X25.checksum(bytes); | ||||
|         let page_size = self.eeprom.page_size(); | ||||
|  | ||||
|         let header = BackupHeader { | ||||
|             crc16: checksum, | ||||
|             timestamp: time, | ||||
|             size: bytes.len(), | ||||
|         }; | ||||
|  | ||||
|         let encoded = bincode::serialize(&header)?; | ||||
|         if encoded.len() > page_size { | ||||
|             bail!( | ||||
|                 "Size limit reached header is {}, but firest page is only {}", | ||||
|                 encoded.len(), | ||||
|                 page_size | ||||
|             ) | ||||
|         } | ||||
|         let as_u8: &[u8] = &encoded; | ||||
|  | ||||
|         match self.eeprom.write_page(0, as_u8) { | ||||
|             OkStd(_) => {} | ||||
|             Err(err) => bail!("Error writing eeprom {:?}", err), | ||||
|         }; | ||||
|         delay.delay_ms(5); | ||||
|  | ||||
|         let to_write = bytes.chunks(page_size); | ||||
|  | ||||
|         let mut lastiter = 0; | ||||
|         let mut current_page = 1; | ||||
|         for chunk in to_write { | ||||
|             let address = current_page * page_size as u32; | ||||
|             self.eeprom | ||||
|                 .write_page(address, chunk) | ||||
|                 .map_err(|err| anyhow!("Error writing eeprom {:?}", err))?; | ||||
|             current_page += 1; | ||||
|  | ||||
|             let iter = (current_page % 8) as usize; | ||||
|             if iter != lastiter { | ||||
|                 for i in 0..PLANT_COUNT { | ||||
|                     let _ = self.fault(i, iter == i); | ||||
|                 } | ||||
|                 lastiter = iter; | ||||
|             } | ||||
|  | ||||
|             delay.delay_ms(5); | ||||
|         } | ||||
|         Ok(()) | ||||
|     fn is_day(&self) -> bool { | ||||
|         self.solar_is_day.get_level().into() | ||||
|     } | ||||
|  | ||||
|     fn water_temperature_c(&mut self) -> Result<f32> { | ||||
| @@ -427,13 +279,13 @@ impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|         let median_mv = store[6] as f32 / 1000_f32; | ||||
|         Ok(median_mv) | ||||
|     } | ||||
|  | ||||
|     fn light(&mut self, enable: bool) -> Result<()> { | ||||
|         unsafe { gpio_hold_dis(self.light.pin()) }; | ||||
|         self.light.set_state(enable.into())?; | ||||
|         unsafe { gpio_hold_en(self.light.pin()) }; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn pump(&mut self, plant: usize, enable: bool) -> Result<()> { | ||||
|         if enable { | ||||
|             self.main_pump.set_high()?; | ||||
| @@ -450,7 +302,6 @@ impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|             7 => PUMP8_BIT, | ||||
|             _ => bail!("Invalid pump {plant}",), | ||||
|         }; | ||||
|         //currently infallible error, keep for future as result anyway | ||||
|         self.shift_register.decompose()[index].set_state(enable.into())?; | ||||
|  | ||||
|         if !enable { | ||||
| @@ -537,8 +388,8 @@ impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|             self.shift_register.decompose()[MS_4].set_low()?; | ||||
|             self.shift_register.decompose()[SENSOR_ON].set_high()?; | ||||
|  | ||||
|             let measurement = 100; // TODO what is this scaling factor? what is its purpose? | ||||
|             let factor = 1000f32 / measurement as f32; | ||||
|             let measurement = 100; //how long to measure and then extrapolate to hz | ||||
|             let factor = 1000f32 / measurement as f32; //scale raw cound by this number to get hz | ||||
|  | ||||
|             //give some time to stabilize | ||||
|             self.esp.delay.delay_ms(10); | ||||
| @@ -572,34 +423,6 @@ impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|         unsafe { gpio_hold_en(self.general_fault.pin()) }; | ||||
|     } | ||||
|  | ||||
|     fn factory_reset(&mut self) -> Result<()> { | ||||
|         println!("factory resetting"); | ||||
|         self.esp.delete_config()?; | ||||
|         //destroy backup header | ||||
|         let dummy: [u8; 0] = []; | ||||
|         self.backup_config(&dummy)?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> { | ||||
|         match self.rtc.datetime() { | ||||
|             OkStd(rtc_time) => Ok(rtc_time.and_utc()), | ||||
|             Err(err) => { | ||||
|                 bail!("Error getting rtc time {:?}", err) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> Result<()> { | ||||
|         let naive_time = time.naive_utc(); | ||||
|         match self.rtc.set_datetime(&naive_time) { | ||||
|             OkStd(_) => Ok(()), | ||||
|             Err(err) => { | ||||
|                 bail!("Error getting rtc time {:?}", err) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn test_pump(&mut self, plant: usize) -> Result<()> { | ||||
|         self.pump(plant, true)?; | ||||
|         unsafe { vTaskDelay(30000) }; | ||||
| @@ -645,9 +468,22 @@ impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { | ||||
|     fn set_config(&mut self, config: PlantControllerConfig) -> Result<()> { | ||||
|         self.config = config; | ||||
|         self.esp.save_config(&self.config)?; | ||||
|         anyhow::Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_voltage(&mut self) -> Result<Voltage> { | ||||
|         //assuming module to work, these are the hardware set values | ||||
|         if self.is_day() { | ||||
|             Ok(Voltage::from_volts(15_f64)) | ||||
|         } else { | ||||
|             Ok(Voltage::from_volts(0_f64)) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_current(&mut self) -> Result<Current> { | ||||
|         bail!("Board does not have current sensor") | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,16 +1,16 @@ | ||||
| use crate::config::PlantControllerConfig; | ||||
| use crate::hal::battery::BatteryInteraction; | ||||
| use crate::hal::esp::Esp; | ||||
| use crate::hal::rtc::RTCModuleInteraction; | ||||
| use crate::hal::{ | ||||
|     deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, | ||||
|     REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25, | ||||
|     deep_sleep, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, | ||||
|     REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, | ||||
| }; | ||||
| use crate::log::{log, LogMessage}; | ||||
| use anyhow::{anyhow, bail}; | ||||
| use chrono::{DateTime, Utc}; | ||||
| use ds18b20::Ds18b20; | ||||
| use ds323x::{DateTimeAccess, Ds323x}; | ||||
| use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr}; | ||||
| use eeprom24x::{Eeprom24x, SlaveAddr}; | ||||
| use embedded_hal::digital::OutputPin; | ||||
| use embedded_hal_bus::i2c::MutexDevice; | ||||
| use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; | ||||
| @@ -111,6 +111,7 @@ impl Charger<'_> { | ||||
| pub struct V4<'a> { | ||||
|     esp: Esp<'a>, | ||||
|     charger: Charger<'a>, | ||||
|     rtc_module: Box<dyn RTCModuleInteraction + Send>, | ||||
|     battery_monitor: Box<dyn BatteryInteraction + Send>, | ||||
|     config: PlantControllerConfig, | ||||
|     tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>, | ||||
| @@ -119,17 +120,11 @@ pub struct V4<'a> { | ||||
|     light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
|     tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
|     one_wire_bus: OneWire<PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>>, | ||||
|     rtc: | ||||
|         Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>, | ||||
|     eeprom: Eeprom24x< | ||||
|         MutexDevice<'a, I2cDriver<'a>>, | ||||
|         eeprom24x::page_size::B32, | ||||
|         eeprom24x::addr_size::TwoBytes, | ||||
|         eeprom24x::unique_serial::No, | ||||
|     >, | ||||
|     general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
|     pump_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>, | ||||
|     sensor_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>, | ||||
|     extra1: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, | ||||
|     extra2: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, | ||||
| } | ||||
|  | ||||
| pub(crate) fn create_v4( | ||||
| @@ -137,6 +132,7 @@ pub(crate) fn create_v4( | ||||
|     esp: Esp<'static>, | ||||
|     config: PlantControllerConfig, | ||||
|     battery_monitor: Box<dyn BatteryInteraction + Send>, | ||||
|     rtc_module: Box<dyn RTCModuleInteraction + Send>, | ||||
| ) -> anyhow::Result<Box<dyn BoardInteraction<'static> + Send + 'static>> { | ||||
|     let mut awake = PinDriver::output(peripherals.gpio21.downgrade())?; | ||||
|     awake.set_high()?; | ||||
| @@ -160,7 +156,7 @@ pub(crate) fn create_v4( | ||||
|     extra1.set_high()?; | ||||
|  | ||||
|     let mut extra2 = PinDriver::output(peripherals.gpio15.downgrade())?; | ||||
|     extra1.set_high()?; | ||||
|     extra2.set_high()?; | ||||
|  | ||||
|     let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?; | ||||
|     one_wire_pin.set_pull(Pull::Floating)?; | ||||
| @@ -231,7 +227,6 @@ pub(crate) fn create_v4( | ||||
|     charge_indicator.set_low()?; | ||||
|  | ||||
|     let mut pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32); | ||||
|  | ||||
|     //todo error handing if init error | ||||
|     for pin in 0..8 { | ||||
|         let _ = pump_expander.pin_into_output(GPIOBank::Bank0, pin); | ||||
| @@ -278,6 +273,7 @@ pub(crate) fn create_v4( | ||||
|     }; | ||||
|  | ||||
|     let v = V4 { | ||||
|         rtc_module, | ||||
|         esp, | ||||
|         awake, | ||||
|         tank_channel, | ||||
| @@ -285,14 +281,14 @@ pub(crate) fn create_v4( | ||||
|         light, | ||||
|         tank_power, | ||||
|         one_wire_bus, | ||||
|         rtc, | ||||
|         eeprom, | ||||
|         general_fault, | ||||
|         pump_expander, | ||||
|         sensor_expander, | ||||
|         config, | ||||
|         battery_monitor, | ||||
|         charger, | ||||
|         extra1, | ||||
|         extra2, | ||||
|     }; | ||||
|     Ok(Box::new(v)) | ||||
| } | ||||
| @@ -310,6 +306,10 @@ impl<'a> BoardInteraction<'a> for V4<'a> { | ||||
|         &mut self.battery_monitor | ||||
|     } | ||||
|  | ||||
|     fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send> { | ||||
|         &mut self.rtc_module | ||||
|     } | ||||
|  | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> { | ||||
|         self.charger.set_charge_indicator(charging) | ||||
|     } | ||||
| @@ -320,102 +320,6 @@ impl<'a> BoardInteraction<'a> for V4<'a> { | ||||
|         deep_sleep(duration_in_ms); | ||||
|     } | ||||
|  | ||||
|     fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader> { | ||||
|         let store = bincode::serialize(&BackupHeader::default())?.len(); | ||||
|         let mut header_page_buffer = vec![0_u8; store]; | ||||
|  | ||||
|         self.eeprom | ||||
|             .read_data(0, &mut header_page_buffer) | ||||
|             .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; | ||||
|  | ||||
|         println!("Raw header is {:?} with size {}", header_page_buffer, store); | ||||
|         let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; | ||||
|         anyhow::Ok(header) | ||||
|     } | ||||
|  | ||||
|     fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>> { | ||||
|         let store = bincode::serialize(&BackupHeader::default())?.len(); | ||||
|         let mut header_page_buffer = vec![0_u8; store]; | ||||
|  | ||||
|         self.eeprom | ||||
|             .read_data(0, &mut header_page_buffer) | ||||
|             .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; | ||||
|  | ||||
|         let header: BackupHeader = bincode::deserialize(&header_page_buffer)?; | ||||
|  | ||||
|         //skip page 0, used by the header | ||||
|         let data_start_address = self.eeprom.page_size() as u32; | ||||
|         let mut data_buffer = vec![0_u8; header.size]; | ||||
|         self.eeprom | ||||
|             .read_data(data_start_address, &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 time = self.get_rtc_time()?.timestamp_millis(); | ||||
|  | ||||
|         let delay = Delay::new_default(); | ||||
|  | ||||
|         let checksum = X25.checksum(bytes); | ||||
|         let page_size = self.eeprom.page_size(); | ||||
|  | ||||
|         let header = BackupHeader { | ||||
|             crc16: checksum, | ||||
|             timestamp: time, | ||||
|             size: bytes.len(), | ||||
|         }; | ||||
|  | ||||
|         let encoded = bincode::serialize(&header)?; | ||||
|         if encoded.len() > page_size { | ||||
|             bail!( | ||||
|                 "Size limit reached header is {}, but firest page is only {}", | ||||
|                 encoded.len(), | ||||
|                 page_size | ||||
|             ) | ||||
|         } | ||||
|         let as_u8: &[u8] = &encoded; | ||||
|  | ||||
|         match self.eeprom.write_page(0, as_u8) { | ||||
|             OkStd(_) => {} | ||||
|             Err(err) => bail!("Error writing eeprom {:?}", err), | ||||
|         }; | ||||
|         delay.delay_ms(5); | ||||
|  | ||||
|         let to_write = bytes.chunks(page_size); | ||||
|  | ||||
|         let mut lastiter = 0; | ||||
|         let mut current_page = 1; | ||||
|         for chunk in to_write { | ||||
|             let address = current_page * page_size as u32; | ||||
|             self.eeprom | ||||
|                 .write_page(address, chunk) | ||||
|                 .map_err(|err| anyhow!("Error writing eeprom {:?}", err))?; | ||||
|             current_page += 1; | ||||
|  | ||||
|             let iter = (current_page % 8) as usize; | ||||
|             if iter != lastiter { | ||||
|                 for i in 0..PLANT_COUNT { | ||||
|                     let _ = self.fault(i, iter == i); | ||||
|                 } | ||||
|                 lastiter = iter; | ||||
|             } | ||||
|  | ||||
|             delay.delay_ms(5); | ||||
|         } | ||||
|         anyhow::Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn is_day(&self) -> bool { | ||||
|         self.charger.is_day() | ||||
|     } | ||||
| @@ -575,35 +479,6 @@ impl<'a> BoardInteraction<'a> for V4<'a> { | ||||
|         unsafe { gpio_hold_en(self.general_fault.pin()) }; | ||||
|     } | ||||
|  | ||||
|     fn factory_reset(&mut self) -> anyhow::Result<()> { | ||||
|         println!("factory resetting"); | ||||
|         self.esp.delete_config()?; | ||||
|         //destroy backup header | ||||
|         let dummy: [u8; 0] = []; | ||||
|         self.backup_config(&dummy)?; | ||||
|  | ||||
|         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) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn test_pump(&mut self, plant: usize) -> anyhow::Result<()> { | ||||
|         self.pump(plant, true)?; | ||||
|         self.esp.delay.delay_ms(30000); | ||||
|   | ||||
| @@ -33,8 +33,6 @@ mod webserver; | ||||
| pub static BOARD_ACCESS: Lazy<Mutex<HAL>> = Lazy::new(|| PlantHal::create().unwrap()); | ||||
| pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false)); | ||||
|  | ||||
|  | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug, PartialEq)] | ||||
| enum WaitType { | ||||
|     MissingConfig, | ||||
| @@ -156,6 +154,7 @@ fn safe_main() -> anyhow::Result<()> { | ||||
|  | ||||
|     let cur = board | ||||
|         .board_hal | ||||
|         .get_rtc_module() | ||||
|         .get_rtc_time() | ||||
|         .or_else(|err| { | ||||
|             println!("rtc module error: {:?}", err); | ||||
| @@ -680,7 +679,7 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard<HAL>) -> NetworkMode { | ||||
|             let sntp_mode: SntpMode = match board.board_hal.get_esp().sntp(1000 * 10) { | ||||
|                 Ok(new_time) => { | ||||
|                     println!("Using time from sntp"); | ||||
|                     let _ = board.board_hal.set_rtc_time(&new_time); | ||||
|                     let _ = board.board_hal.get_rtc_module().set_rtc_time(&new_time); | ||||
|                     SntpMode::SYNC { current: new_time } | ||||
|                 } | ||||
|                 Err(err) => { | ||||
|   | ||||
| @@ -81,7 +81,10 @@ fn write_time( | ||||
|         tv_usec: 0, | ||||
|     }; | ||||
|     unsafe { settimeofday(&now, core::ptr::null_mut()) }; | ||||
|     board.board_hal.set_rtc_time(&parsed.to_utc())?; | ||||
|     board | ||||
|         .board_hal | ||||
|         .get_rtc_module() | ||||
|         .set_rtc_time(&parsed.to_utc())?; | ||||
|     anyhow::Ok(None) | ||||
| } | ||||
|  | ||||
| @@ -97,6 +100,7 @@ fn get_time( | ||||
|         .unwrap_or("error".to_string()); | ||||
|     let rtc = board | ||||
|         .board_hal | ||||
|         .get_rtc_module() | ||||
|         .get_rtc_time() | ||||
|         .map(|t| t.to_rfc3339()) | ||||
|         .unwrap_or("error".to_string()); | ||||
| @@ -170,7 +174,9 @@ fn backup_config( | ||||
| ) -> Result<Option<std::string::String>, anyhow::Error> { | ||||
|     let all = read_up_to_bytes_from_request(request, Some(3072))?; | ||||
|     let mut board = BOARD_ACCESS.lock().expect("board access"); | ||||
|     board.board_hal.backup_config(&all)?; | ||||
|  | ||||
|     //TODO how to handle progress here? prior versions animated the fault leds while running | ||||
|     board.board_hal.get_rtc_module().backup_config(&all)?; | ||||
|     anyhow::Ok(Some("saved".to_owned())) | ||||
| } | ||||
|  | ||||
| @@ -178,7 +184,7 @@ fn get_backup_config( | ||||
|     _request: &mut Request<&mut EspHttpConnection>, | ||||
| ) -> Result<Option<std::string::String>, anyhow::Error> { | ||||
|     let mut board = BOARD_ACCESS.lock().expect("board access"); | ||||
|     let json = match board.board_hal.get_backup_config() { | ||||
|     let json = match board.board_hal.get_rtc_module().get_backup_config() { | ||||
|         Ok(config) => from_utf8(&config)?.to_owned(), | ||||
|         Err(err) => { | ||||
|             println!("Error get backup config {:?}", err); | ||||
| @@ -192,7 +198,7 @@ fn backup_info( | ||||
|     _request: &mut Request<&mut EspHttpConnection>, | ||||
| ) -> Result<Option<std::string::String>, anyhow::Error> { | ||||
|     let mut board = BOARD_ACCESS.lock().expect("Should never fail"); | ||||
|     let header = board.board_hal.get_backup_info(); | ||||
|     let header = board.board_hal.get_rtc_module().get_backup_info(); | ||||
|     let json = match header { | ||||
|         Ok(h) => { | ||||
|             let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap(); | ||||
| @@ -341,7 +347,7 @@ fn ota( | ||||
|  | ||||
|         let iter = (total_read / 1024) % 8; | ||||
|         if iter != lastiter { | ||||
|             board.board_hal.general_fault(iter%5==0); | ||||
|             board.board_hal.general_fault(iter % 5 == 0); | ||||
|             for i in 0..PLANT_COUNT { | ||||
|                 let _ = board.board_hal.fault(i, iter == i); | ||||
|             } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user