diff --git a/rust/Cargo.toml b/rust/Cargo.toml index cbf7567..edbd5ed 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -111,6 +111,7 @@ embedded-hal-bus = { version = "0.3.0" } onewire = "0.4.0" #strum = { version = "0.27.0", default-feature = false, features = ["derive"] } measurements = "0.11.0" +ds323x = "0.6.0" #json serde = { version = "1.0.219", features = ["derive", "alloc"], default-features = false } @@ -141,6 +142,8 @@ littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc"] } littlefs2-core = "0.1.1" bytemuck = { version = "1.23.2", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] } deranged = "0.5.3" +embassy-embedded-hal = "0.5.0" +bincode = { version = "2.0.1", default-features = false, features = ["derive"] } [patch.crates-io] #bq34z100 = { path = "../../bq34z100_rust" } diff --git a/rust/src/FatError.rs b/rust/src/FatError.rs index e8e25f3..7e66649 100644 --- a/rust/src/FatError.rs +++ b/rust/src/FatError.rs @@ -1,8 +1,12 @@ +use alloc::format; use alloc::string::{String, ToString}; use core::convert::Infallible; use core::fmt; +use core::str::Utf8Error; +use embassy_embedded_hal::shared_bus::I2cDeviceError; use embassy_executor::SpawnError; use embassy_sync::mutex::TryLockError; +use esp_hal::i2c::master::ConfigError; use esp_wifi::wifi::WifiError; use littlefs2_core::PathError; use onewire::Error; @@ -41,6 +45,15 @@ pub enum FatError { PartitionError { error: esp_bootloader_esp_idf::partitions::Error, }, + I2CConfigError { + error: ConfigError, + }, + DS323 { + error: String, + }, + Eeprom24x { + error: String, + }, } pub type FatResult = Result; @@ -65,6 +78,9 @@ impl fmt::Display for FatError { FatError::NoBatteryMonitor => { write!(f, "No Battery Monitor") } + FatError::I2CConfigError { error } => write!(f, "I2CConfigError {:?}", error), + FatError::DS323 { error } => write!(f, "DS323 {:?}", error), + FatError::Eeprom24x { error } => write!(f, "Eeprom24x {:?}", error), } } } @@ -150,3 +166,57 @@ impl From for FatError { FatError::PartitionError { error: value } } } + +impl From for FatError { + fn from(value: Utf8Error) -> Self { + FatError::String { + error: value.to_string(), + } + } +} + +impl From> for FatError { + fn from(value: edge_http::io::Error) -> Self { + FatError::String { + error: format!("HttpIoError {:?}", value), + } + } +} + +impl From> for FatError { + fn from(value: ds323x::Error) -> Self { + FatError::DS323 { + error: format!("Ds323xError {:?}", value), + } + } +} + +impl From> for FatError { + fn from(value: eeprom24x::Error) -> Self { + FatError::Eeprom24x { + error: format!("Eeprom24xError {:?}", value), + } + } +} + +impl From for FatError { + fn from(value: bincode::error::DecodeError) -> Self { + FatError::Eeprom24x { + error: format!("Eeprom24xError {:?}", value), + } + } +} + +impl From for FatError { + fn from(value: bincode::error::EncodeError) -> Self { + FatError::Eeprom24x { + error: format!("Eeprom24xError {:?}", value), + } + } +} + +impl From for FatError { + fn from(value: ConfigError) -> Self { + FatError::I2CConfigError { error: value } + } +} diff --git a/rust/src/hal/battery.rs b/rust/src/hal/battery.rs index ba321b5..aed5fec 100644 --- a/rust/src/hal/battery.rs +++ b/rust/src/hal/battery.rs @@ -1,3 +1,4 @@ +use crate::hal::Box; use crate::FatError::{FatError, FatResult}; use alloc::string::String; use async_trait::async_trait; diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs index 1ca9246..084bb97 100644 --- a/rust/src/hal/initial_hal.rs +++ b/rust/src/hal/initial_hal.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use crate::alloc::boxed::Box; use crate::hal::water::TankSensor; use crate::hal::{BoardInteraction, FreePeripherals, Sensor}; -use crate::FatError::FatError; +use crate::FatError::{FatError, FatResult}; use crate::{ bail, config::PlantControllerConfig, @@ -32,11 +32,15 @@ impl RTCModuleInteraction for NoRTC { bail!("Please configure board revision") } - async fn get_backup_config(&mut self) -> Result, FatError> { + async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)> { bail!("Please configure board revision") } - async fn backup_config(&mut self, _bytes: &[u8]) -> Result<(), FatError> { + async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()> { + bail!("Please configure board revision") + } + + async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()> { bail!("Please configure board revision") } diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index baa4775..e5523a7 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -2,13 +2,13 @@ pub(crate) mod battery; pub mod esp; mod initial_hal; mod little_fs2storage_adapter; -mod rtc; +pub(crate) mod rtc; mod v4_hal; mod water; //mod water; use crate::alloc::string::ToString; -use crate::hal::rtc::RTCModuleInteraction; +use crate::hal::rtc::{DS3231Module, RTCModuleInteraction}; use esp_hal::peripherals::Peripherals; use esp_hal::peripherals::GPIO0; use esp_hal::peripherals::GPIO1; @@ -54,10 +54,20 @@ use alloc::format; use alloc::sync::Arc; use async_trait::async_trait; use chrono::{DateTime, FixedOffset, Utc}; +use core::cell::RefCell; +use ds323x::ic::DS3231; +use ds323x::interface::I2cInterface; +use ds323x::{DateTimeAccess, Ds323x}; +use eeprom24x::addr_size::TwoBytes; +use eeprom24x::page_size::B32; +use eeprom24x::unique_serial::No; +use eeprom24x::{Eeprom24x, SlaveAddr, Storage}; +use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; use embassy_executor::Spawner; +use embassy_sync::blocking_mutex::{CriticalSectionMutex, NoopMutex}; //use battery::BQ34Z100G1; //use bq34z100::Bq34z100g1Driver; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; use esp_bootloader_esp_idf::partitions::{ AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry, }; @@ -74,10 +84,14 @@ use embassy_sync::once_lock::OnceLock; use esp_alloc as _; use esp_backtrace as _; use esp_bootloader_esp_idf::ota::Slot; +use esp_hal::delay::Delay; +use esp_hal::i2c::master::{BusTimeout, Config, I2c}; use esp_hal::rng::Rng; use esp_hal::rtc_cntl::{Rtc, SocResetReason}; use esp_hal::system::reset_reason; +use esp_hal::time::Rate; use esp_hal::timer::timg::TimerGroup; +use esp_hal::Blocking; use esp_storage::FlashStorage; use esp_wifi::{init, EspWifiController}; use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem}; @@ -90,8 +104,9 @@ pub static TIME_ACCESS: OnceLock = OnceLock::new(); pub const PLANT_COUNT: usize = 8; const TANK_MULTI_SAMPLE: usize = 11; - -//pub static I2C_DRIVER: LazyLock>> = LazyLock::new(PlantHal::create_i2c); +static I2C_DRIVER: OnceLock< + embassy_sync::blocking_mutex::Mutex>>, +> = OnceLock::new(); #[derive(Debug, PartialEq)] pub enum Sensor { @@ -127,22 +142,16 @@ pub trait BoardInteraction<'a> { fn set_config(&mut self, config: PlantControllerConfig); async fn get_mptt_voltage(&mut self) -> Result; async fn get_mptt_current(&mut self) -> Result; -} -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) { + 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 { - match self.fault(led, current == led as u32).await { - Result::Ok(_) => {} - Err(err) => { - warn!("Fault on plant {}: {:?}", led, err); - } + if let Err(err) = self.fault(led, current == led as u32).await { + warn!("Fault on plant {}: {:?}", led, err); } } - let _ = self.general_fault(even.into()); + let _ = self.general_fault(even.into()).await; } } @@ -196,7 +205,7 @@ macro_rules! mk_static { const GW_IP_ADDR_ENV: Option<&'static str> = option_env!("GATEWAY_IP"); impl PlantHal { - // fn create_i2c() -> Mutex> { + //fn create_i2c() -> Mutex { // let peripherals = unsafe { Peripherals::new() }; // // let config = I2cConfig::new() @@ -210,7 +219,7 @@ impl PlantHal { // let sda = peripherals.pins.gpio20.downgrade(); // // Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) - // } + //} pub async fn create( spawner: Spawner, @@ -427,28 +436,55 @@ impl PlantHal { let config = esp.load_config().await; log::info!("Init rtc 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); - // } - // } - // let storage = Storage::new(eeprom, Delay::new(1000)); - let rtc_module: Box = Box::new(initial_hal::NoRTC {}); - // Box::new(DS3231Module { rtc, storage }) as Box; + let sda = peripherals.GPIO20; + let scl = peripherals.GPIO19; + + let i2c = I2c::new( + peripherals.I2C0, + Config::default() + .with_frequency(Rate::from_hz(100)) + .with_timeout(BusTimeout::Maximum), + )? + .with_scl(scl) + .with_sda(sda); + let i2c_bus: embassy_sync::blocking_mutex::Mutex< + CriticalSectionRawMutex, + RefCell>, + > = CriticalSectionMutex::new(RefCell::new(i2c)); + + I2C_DRIVER.init(i2c_bus).expect("Could not init i2c driver"); + + let i2c_bus = I2C_DRIVER.get().await; + let rtc_device = I2cDevice::new(&i2c_bus); + let eeprom_device = I2cDevice::new(&i2c_bus); + + let mut rtc: Ds323x< + I2cInterface>>, + DS3231, + > = Ds323x::new_ds3231(rtc_device); + + info!("Init rtc eeprom driver"); + let eeprom = Eeprom24x::new_24x32(eeprom_device, SlaveAddr::Alternative(true, true, true)); + let rtc_time = rtc.datetime(); + match rtc_time { + Ok(tt) => { + log::info!("Rtc Module reports time at UTC {}", tt); + } + Err(err) => { + log::info!("Rtc Module could not be read {:?}", err); + } + } + + let storage: Storage< + I2cDevice<'static, CriticalSectionRawMutex, I2c>, + B32, + TwoBytes, + No, + Delay, + > = Storage::new(eeprom, Delay::new()); + let rtc_module: Box = + Box::new(DS3231Module { rtc, storage }) as Box; let hal = match config { Result::Ok(config) => { diff --git a/rust/src/hal/rtc.rs b/rust/src/hal/rtc.rs index d923c0a..306dac6 100644 --- a/rust/src/hal/rtc.rs +++ b/rust/src/hal/rtc.rs @@ -1,139 +1,138 @@ +use crate::bail; use crate::hal::Box; use crate::FatError::FatResult; +use alloc::vec; use alloc::vec::Vec; use async_trait::async_trait; +use bincode::config::Configuration; +use bincode::{config, Decode, Encode}; use chrono::{DateTime, Utc}; +use crc::Digest; +use ds323x::ic::DS3231; +use ds323x::interface::I2cInterface; +use ds323x::{DateTimeAccess, Ds323x}; +use eeprom24x::addr_size::TwoBytes; +use eeprom24x::page_size::B32; +use eeprom24x::unique_serial::No; +use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +use embedded_storage::{ReadStorage, Storage}; +use esp_hal::delay::Delay; +use esp_hal::i2c::master::I2c; +use esp_hal::Blocking; +use littlefs2_core::{PathBuf, SeekFrom}; +use serde::{Deserialize, Serialize}; -// 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 = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); -// const CONFIG: Configuration = config::standard(); +pub const X25: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); +const CONFIG: Configuration = config::standard(); // #[async_trait] pub trait RTCModuleInteraction { async fn get_backup_info(&mut self) -> FatResult; - async fn get_backup_config(&mut self) -> FatResult>; - async fn backup_config(&mut self, bytes: &[u8]) -> FatResult<()>; + async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)>; + async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()>; + async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()>; async fn get_rtc_time(&mut self) -> FatResult>; async fn set_rtc_time(&mut self, time: &DateTime) -> FatResult<()>; } // -// 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::ic::DS3231>, -// -// pub(crate) storage: Storage>, B32, TwoBytes, No, Delay>, -// } -// -// impl RTCModuleInteraction for DS3231Module<'_> { -// fn get_backup_info(&mut self) -> anyhow::Result { -// 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> { -// 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> { -// 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) -> 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 { + pub(crate) rtc: Ds323x< + I2cInterface>>, + DS3231, + >, + + pub(crate) storage: eeprom24x::Storage< + I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>, + B32, + TwoBytes, + No, + Delay, + >, +} + +#[async_trait] +impl RTCModuleInteraction for DS3231Module { + async fn get_backup_info(&mut self) -> FatResult { + let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; + + self.storage.read(0, &mut header_page_buffer)?; + + let (header, len): (BackupHeader, usize) = + bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?; + + log::info!("Raw header is {:?} with size {}", header_page_buffer, len); + Ok(header) + } + + async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)> { + let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; + + self.storage.read(0, &mut header_page_buffer)?; + let (header, _header_size): (BackupHeader, usize) = + bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?; + + let mut buf = [0_u8; 32]; + let offset = chunk * buf.len() + BACKUP_HEADER_MAX_SIZE; + + let end: usize = header.size as usize + BACKUP_HEADER_MAX_SIZE; + let current_end = offset + buf.len(); + let chunk_size = if current_end > end { + end - offset + } else { + buf.len() + }; + if chunk_size == 0 { + Ok((buf, 0, header.crc16)) + } else { + self.storage.read(offset as u32, &mut buf)?; + let data = &buf[..chunk_size]; + Ok((buf, chunk_size, header.crc16)) + } + } + async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()> { + //skip header and write after + self.storage + .write((BACKUP_HEADER_MAX_SIZE + offset) as u32, &bytes)?; + + Ok(()) + } + + async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()> { + let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; + + let time = self.get_rtc_time().await?.timestamp_millis(); + let header = BackupHeader { + crc16: crc, + timestamp: time, + size: length 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)?; + Ok(()) + } + + async fn get_rtc_time(&mut self) -> FatResult> { + Ok(self.rtc.datetime()?.and_utc()) + } + + async fn set_rtc_time(&mut self, time: &DateTime) -> FatResult<()> { + let naive_time = time.naive_utc(); + Ok(self.rtc.set_datetime(&naive_time)?) + } +} diff --git a/rust/src/main.rs b/rust/src/main.rs index a41d7f0..80fc1f9 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -384,10 +384,10 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { // } let mut _water_frozen = false; - let water_temp = board - .board_hal - .get_tank_sensor() - .and_then(async |sensor| sensor.water_temperature_c().await); + let water_temp: FatResult = match board.board_hal.get_tank_sensor() { + Ok(sensor) => sensor.water_temperature_c().await, + Err(e) => Err(e), + }; if let Ok(res) = water_temp { if res < WATER_FROZEN_THRESH { diff --git a/rust/src/webserver/mod.rs b/rust/src/webserver/mod.rs index de43276..bfc0d89 100644 --- a/rust/src/webserver/mod.rs +++ b/rust/src/webserver/mod.rs @@ -1,6 +1,7 @@ //offer ota and config mode use crate::config::PlantControllerConfig; +use crate::hal::rtc::X25; use crate::hal::{esp_set_time, esp_time}; use crate::log::LOG_ACCESS; use crate::tank::{determine_tank_state, TankInfo}; @@ -123,56 +124,9 @@ pub struct NightLampCommand { // anyhow::Ok(Some(json)) // } // + // -// fn backup_config( -// request: &mut Request<&mut EspHttpConnection>, -// ) -> Result, anyhow::Error> { -// let all = read_up_to_bytes_from_request(request, Some(3072))?; -// let mut board = BOARD_ACCESS.lock().expect("board access"); -// -// //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())) -// } -// -// fn get_backup_config( -// _request: &mut Request<&mut EspHttpConnection>, -// ) -> Result, anyhow::Error> { -// let mut board = BOARD_ACCESS.lock().expect("board access"); -// let json = match board.board_hal.get_rtc_module().get_backup_config() { -// Ok(config) => from_utf8(&config)?.to_owned(), -// Err(err) => { -// log::info!("Error get backup config {:?}", err); -// err.to_string() -// } -// }; -// anyhow::Ok(Some(json)) -// } -// -// fn backup_info( -// _request: &mut Request<&mut EspHttpConnection>, -// ) -> Result, anyhow::Error> { -// let mut board = BOARD_ACCESS.lock().expect("Should never fail"); -// 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(); -// let wbh = WebBackupHeader { -// timestamp: timestamp.to_rfc3339(), -// size: h.size, -// }; -// serde_json::to_string(&wbh)? -// } -// Err(err) => { -// let wbh = WebBackupHeader { -// timestamp: err.to_string(), -// size: 0, -// }; -// serde_json::to_string(&wbh)? -// } -// }; -// anyhow::Ok(Some(json)) -// } + // // @@ -415,6 +369,10 @@ impl Handler for HttpHandler { let buf = get_log(conn).await; Some(200) } + "/get_backup_config" => { + get_backup_config(conn).await?; + Some(200) + } &_ => { let json = match path { "/version" => Some(get_version_web(conn).await), @@ -424,7 +382,8 @@ impl Handler for HttpHandler { "/get_config" => Some(get_config(conn).await), "/files" => Some(list_files(conn).await), "/log_localization" => Some(get_log_localization_config(conn).await), - "/wifiscan" => Some(wifi_scan(conn).await), + "/tank" => Some(tank_info(conn).await), + "/backup_info" => Some(backup_info(conn).await), _ => None, }; match json { @@ -438,6 +397,7 @@ impl Handler for HttpHandler { "/wifiscan" => Some(wifi_scan(conn).await), "/set_config" => Some(set_config(conn).await), "/time" => Some(write_time(conn).await), + "/backup_config" => Some(backup_config(conn).await), "/reboot" => { let mut board = BOARD_ACCESS.get().await.lock().await; board.board_hal.get_esp().set_restart_to_conf(true); @@ -471,101 +431,173 @@ impl Handler for HttpHandler { } } -// .fn_handler("/file", Method::Get, move |request| { -// let filename = query_param(request.uri(), "filename").unwrap(); -// let file_handle = BOARD_ACCESS -// .lock() -// .unwrap() -// .board_hal -// .get_esp() -// .get_file_handle(&filename, false); -// match file_handle { -// Ok(mut file_handle) => { -// let headers = [("Access-Control-Allow-Origin", "*")]; -// let mut response = request.into_response(200, None, &headers)?; -// const BUFFER_SIZE: usize = 512; -// let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; -// let mut total_read: usize = 0; -// loop { -// unsafe { vTaskDelay(1) }; -// let read = std::io::Read::read(&mut file_handle, &mut buffer)?; -// total_read += read; -// let to_write = &buffer[0..read]; -// response.write(to_write)?; -// if read == 0 { -// break; -// } -// } -// log::info!("wrote {total_read} for file {filename}"); -// drop(file_handle); -// response.flush()?; -// } -// Err(err) => { -// //todo set headers here for filename to be error -// let error_text = err.to_string(); -// log::info!("error handling get file {}", error_text); -// cors_response(request, 500, &error_text)?; -// } -// } -// anyhow::Ok(()) -// }) -// .unwrap(); -// server -// .fn_handler("/file", Method::Post, move |mut request| { -// let filename = query_param(request.uri(), "filename").unwrap(); -// let mut board = BOARD_ACCESS.lock().unwrap(); -// let file_handle = board.board_hal.get_esp().get_file_handle(&filename, true); -// match file_handle { -// //TODO get free filesystem size, check against during write if not to large -// Ok(mut file_handle) => { -// const BUFFER_SIZE: usize = 512; -// let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; -// let mut total_read: usize = 0; -// let mut lastiter = 0; -// loop { -// let iter = (total_read / 1024) % 8; -// if iter != lastiter { -// for i in 0..PLANT_COUNT { -// let _ = board.board_hal.fault(i, iter == i); -// } -// lastiter = iter; -// } -// -// let read = request.read(&mut buffer)?; -// total_read += read; -// let to_write = &buffer[0..read]; -// std::io::Write::write(&mut file_handle, to_write)?; -// if read == 0 { -// break; -// } -// } -// cors_response(request, 200, &format!("saved {total_read} bytes"))?; -// } -// Err(err) => { -// //todo set headers here for filename to be error -// let error_text = err.to_string(); -// log::info!("error handling get file {}", error_text); -// cors_response(request, 500, &error_text)?; -// } -// } -// drop(board); -// anyhow::Ok(()) -// }) -// .unwrap(); +async fn get_backup_config( + conn: &mut Connection<'_, T, { N }>, +) -> Result<(), FatError> +where + T: Read + Write, +{ + let mut checksum = X25.digest(); + let mut chunk = 0_usize; + loop { + info!("Chunk {}", chunk); + let mut board = BOARD_ACCESS.get().await.lock().await; + board.board_hal.progress(chunk as u32).await; + let read_chunk = board + .board_hal + .get_rtc_module() + .get_backup_config(chunk) + .await?; + let length = read_chunk.1; + info!("Length {}", length); + if length == 0 { + let check = checksum.finalize(); + if check != read_chunk.2 { + if check != read_chunk.2 { + conn.initiate_response( + 409, + Some( + format!("Checksum mismatch expected {} got {}", read_chunk.2, check) + .as_str(), + ), + &[], + ) + .await?; + } + } + info!("file request for backup finished"); + break; + } + let data = &read_chunk.0[0..length]; + checksum.update(&data); + if length < read_chunk.0.len() { + let check = checksum.finalize(); + if check != read_chunk.2 { + conn.initiate_response( + 409, + Some( + format!("Checksum mismatch expected {} got {}", read_chunk.2, check) + .as_str(), + ), + &[], + ) + .await?; + } + info!("file request for backup finished"); + break; + } + chunk = chunk + 1; + } + conn.initiate_response(200, Some("OK"), &[]).await?; -async fn tank_info( - request: &mut Connection<'_, T, N>, + let mut chunk = 0; + loop { + let mut board = BOARD_ACCESS.get().await.lock().await; + board.board_hal.progress(chunk as u32).await; + let read_chunk = board + .board_hal + .get_rtc_module() + .get_backup_config(chunk) + .await?; + let length = read_chunk.1; + if length == 0 { + info!("file request for backup finished"); + break; + } + let data = &read_chunk.0[0..length]; + conn.write_all(data).await?; + if length < read_chunk.0.len() { + info!("file request for backup finished"); + break; + } + chunk = chunk + 1; + } + + Ok(()) +} + +async fn backup_config( + conn: &mut Connection<'_, T, N>, +) -> FatResult> +where + T: Read + Write, +{ + let mut offset = 0_usize; + let mut buf = [0_u8; 32]; + + let mut checksum = crate::hal::rtc::X25.digest(); + + let mut counter = 0; + loop { + let to_write = conn.read(&mut buf).await?; + if to_write == 0 { + info!("backup finished"); + break; + } else { + let mut board = BOARD_ACCESS.get().await.lock().await; + board.board_hal.progress(counter).await; + + counter = counter + 1; + board + .board_hal + .get_rtc_module() + .backup_config(offset, &buf[0..to_write]) + .await?; + checksum.update(&buf[0..to_write]); + } + offset = offset + to_write; + } + + let mut board = BOARD_ACCESS.get().await.lock().await; + board + .board_hal + .get_rtc_module() + .backup_config_finalize(checksum.finalize(), offset) + .await?; + Ok(Some("saved".to_owned())) +} + +async fn backup_info( + _request: &mut Connection<'_, T, N>, ) -> Result, FatError> where T: Read + Write, { let mut board = BOARD_ACCESS.get().await.lock().await; - determine_tank_state(&mut board); - //should be multsampled + let header = board.board_hal.get_rtc_module().get_backup_info().await; + let json = match header { + Ok(h) => { + let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap(); + let wbh = WebBackupHeader { + timestamp: timestamp.to_rfc3339(), + size: h.size, + }; + serde_json::to_string(&wbh)? + } + Err(err) => { + let wbh = WebBackupHeader { + timestamp: err.to_string(), + size: 0, + }; + serde_json::to_string(&wbh)? + } + }; + Ok(Some(json)) +} + +async fn tank_info( + _request: &mut Connection<'_, T, N>, +) -> Result, FatError> +where + T: Read + Write, +{ + let mut board = BOARD_ACCESS.get().await.lock().await; + let tank_state = determine_tank_state(&mut board).await; + //should be multisampled let sensor = board.board_hal.get_tank_sensor()?; - let water_temp = sensor.water_temperature_c().await?; - Ok(Some(serde_json::to_string(&tank_info.as_mqtt_info( + let water_temp: FatResult = sensor.water_temperature_c().await; + Ok(Some(serde_json::to_string(&tank_state.as_mqtt_info( &board.board_hal.get_config().tank, &water_temp, ))?)) @@ -892,29 +924,6 @@ pub async fn httpd(reboot_now: Arc, stack: Stack<'static>) { // server // - // server - // .fn_handler("/file", Method::Delete, move |request| { - // let filename = query_param(request.uri(), "filename").unwrap(); - // let copy = filename.clone(); - // let mut board = BOARD_ACCESS.lock().unwrap(); - // match board.board_hal.get_esp().delete_file(&filename) { - // Ok(_) => { - // let info = format!("Deleted file {copy}"); - // cors_response(request, 200, &info)?; - // } - // Err(err) => { - // let info = format!("Could not delete file {copy} {err:?}"); - // cors_response(request, 400, &info)?; - // } - // } - // anyhow::Ok(()) - // }) - // .unwrap(); - // server - // .fn_handler("/file", Method::Options, |request| { - // cors_response(request, 200, "") - // }) - // .unwrap(); // unsafe { vTaskDelay(1) }; // server // .fn_handler("/", Method::Get, move |request| { diff --git a/rust/src_webpack/src/main.ts b/rust/src_webpack/src/main.ts index 3487f2d..671966b 100644 --- a/rust/src_webpack/src/main.ts +++ b/rust/src_webpack/src/main.ts @@ -69,45 +69,44 @@ export class Controller { }); } - getBackupInfo(): Promise { - return fetch(PUBLIC_URL + "/backup_info") - .then(response => response.json()) - .then(json => json as BackupHeader) - .then(header => { - controller.submitView.setBackupInfo(header) - }) - .catch(error => { - console.log(error); - }); + async getBackupInfo(): Promise { + try { + const response = await fetch(PUBLIC_URL + "/backup_info"); + const json = await response.json(); + const header = json as BackupHeader; + controller.submitView.setBackupInfo(header); + } catch (error) { + console.log(error); + } } - populateTimezones(): Promise { - return fetch(PUBLIC_URL + '/timezones') - .then(response => response.json()) - .then(json => json as string[]) - .then(timezones => { - controller.timeView.timezones(timezones) - }) - .catch(error => console.error('Error fetching timezones:', error)); + async populateTimezones(): Promise { + try { + const response = await fetch(PUBLIC_URL + '/timezones'); + const json = await response.json(); + const timezones = json as string[]; + controller.timeView.timezones(timezones); + } catch (error) { + return console.error('Error fetching timezones:', error); + } } - updateFileList(): Promise { - return fetch(PUBLIC_URL + "/files") - .then(response => response.json()) - .then(json => json as FileList) - .then(filelist => { - controller.fileview.setFileList(filelist, PUBLIC_URL) - }) - .catch(error => { - console.log(error); - }); + async updateFileList(): Promise { + try { + const response = await fetch(PUBLIC_URL + "/files"); + const json = await response.json(); + const filelist = json as FileList; + controller.fileview.setFileList(filelist, PUBLIC_URL); + } catch (error) { + console.log(error); + } } uploadFile(file: File, name: string) { - var current = 0; - var max = 100; + let current = 0; + let max = 100; controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")") - var ajax = new XMLHttpRequest(); + const ajax = new XMLHttpRequest(); ajax.upload.addEventListener("progress", event => { current = event.loaded / 1000; max = event.total / 1000; @@ -133,7 +132,7 @@ export class Controller { deleteFile(name: string) { controller.progressview.addIndeterminate("file_delete", "Deleting " + name); - var ajax = new XMLHttpRequest(); + const ajax = new XMLHttpRequest(); ajax.open("DELETE", PUBLIC_URL + "/file?filename=" + name); ajax.send(); ajax.addEventListener("error", () => { @@ -153,50 +152,47 @@ export class Controller { controller.updateFileList() } - updateRTCData(): Promise { - return fetch(PUBLIC_URL + "/time") - .then(response => response.json()) - .then(json => json as GetTime) - .then(time => { - controller.timeView.update(time.native, time.rtc) - }) - .catch(error => { - controller.timeView.update("n/a", "n/a") - console.log(error); - }); + async updateRTCData(): Promise { + try { + const response = await fetch(PUBLIC_URL + "/time"); + const json = await response.json(); + const time = json as GetTime; + controller.timeView.update(time.native, time.rtc); + } catch (error) { + controller.timeView.update("n/a", "n/a"); + console.log(error); + } } - updateBatteryData(): Promise { - return fetch(PUBLIC_URL + "/battery") - .then(response => response.json()) - .then(json => json as BatteryState) - .then(battery => { - controller.batteryView.update(battery) - }) - .catch(error => { - controller.batteryView.update(null) - console.log(error); - }) + async updateBatteryData(): Promise { + try { + const response = await fetch(PUBLIC_URL + "/battery"); + const json = await response.json(); + const battery = json as BatteryState; + controller.batteryView.update(battery); + } catch (error) { + controller.batteryView.update(null); + console.log(error); + } } - updateSolarData(): Promise { - return fetch(PUBLIC_URL + "/solar") - .then(response => response.json()) - .then(json => json as SolarState) - .then(solar => { - controller.solarView.update(solar) - }) - .catch(error => { - controller.solarView.update(null) - console.log(error); - }) + async updateSolarData(): Promise { + try { + const response = await fetch(PUBLIC_URL + "/solar"); + const json = await response.json(); + const solar = json as SolarState; + controller.solarView.update(solar); + } catch (error) { + controller.solarView.update(null); + console.log(error); + } } uploadNewFirmware(file: File) { - var current = 0; - var max = 100; + let current = 0; + let max = 100; controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")") - var ajax = new XMLHttpRequest(); + const ajax = new XMLHttpRequest(); ajax.upload.addEventListener("progress", event => { current = event.loaded / 1000; max = event.total / 1000; @@ -218,15 +214,13 @@ export class Controller { ajax.send(file); } - version(): Promise { + async version(): Promise { controller.progressview.addIndeterminate("version", "Getting buildVersion") - return fetch(PUBLIC_URL + "/version") - .then(response => response.json()) - .then(json => json as VersionInfo) - .then(versionInfo => { - controller.progressview.removeProgress("version") - controller.firmWareView.setVersion(versionInfo); - }) + const response = await fetch(PUBLIC_URL + "/version"); + const json = await response.json(); + const versionInfo = json as VersionInfo; + controller.progressview.removeProgress("version"); + controller.firmWareView.setVersion(versionInfo); } getBackupConfig() { @@ -243,7 +237,7 @@ export class Controller { controller.progressview.addIndeterminate("get_config", "Downloading Config") const response = await fetch(PUBLIC_URL + "/get_config"); const loaded = await response.json(); - var currentConfig = loaded as PlantControllerConfig; + const currentConfig = loaded as PlantControllerConfig; controller.setInitialConfig(currentConfig); controller.setConfig(currentConfig); //sync json view initially @@ -268,12 +262,12 @@ export class Controller { controller.downloadConfig() } - backupConfig(json: string): Promise { - return fetch(PUBLIC_URL + "/backup_config", { + async backupConfig(json: string): Promise { + const response = await fetch(PUBLIC_URL + "/backup_config", { method: "POST", body: json, - }) - .then(response => response.text()); + }); + return await response.text(); } syncRTCFromBrowser() { @@ -310,9 +304,9 @@ export class Controller { } testNightLamp(active: boolean) { - var body: NightLampCommand = { + const body: NightLampCommand = { active: active - } + }; var pretty = JSON.stringify(body, undefined, 1); fetch(PUBLIC_URL + "/lamptest", { method: "POST",