use crate::app_config::{AppConfig, ConsumptionHistoryType}; use crate::energy::consumption::EnergyConsumption; use crate::utils::math_utils::median; use crate::utils::time_utils::day_number; const TIME_INTERVAL: usize = 10; #[derive(thiserror::Error, Debug)] pub enum ConsumptionHistoryError { #[error("Given time is out of file bounds!")] TimeOutOfFileBound, } /// # ConsumptionHistoryFile /// /// Stores the history of house consumption pub struct ConsumptionHistoryFile { day: u64, buff: Vec, r#type: ConsumptionHistoryType, } impl ConsumptionHistoryFile { /// Open consumption history file, if it exists, or create an empty one pub fn open(time: u64, r#type: ConsumptionHistoryType) -> anyhow::Result { let day = day_number(time); let path = AppConfig::get().energy_consumption_history_day(day, r#type); if path.exists() { Ok(Self { day, buff: bincode::decode_from_slice( &std::fs::read(path)?, bincode::config::standard(), )? .0, r#type, }) } else { log::debug!( "Energy consumption stats for day {day} does not exists yet, creating memory buffer" ); Ok(Self::new_memory(day, r#type)) } } /// Create a new in memory consumption history fn new_memory(day: u64, r#type: ConsumptionHistoryType) -> Self { Self { day, buff: vec![0; (3600 * 24 / TIME_INTERVAL) + 1], r#type, } } /// Resolve time offset of a given time in buffer fn resolve_offset(&self, time: u64) -> anyhow::Result { let start_of_day = self.day * 3600 * 24; if time < start_of_day || time >= start_of_day + 3600 * 24 { return Err(ConsumptionHistoryError::TimeOutOfFileBound.into()); } let relative_time = (time - start_of_day) / TIME_INTERVAL as u64; Ok(relative_time as usize) } /// Check if a time is contained in this history pub fn contains_time(&self, time: u64) -> bool { self.resolve_offset(time).is_ok() } /// Set new state of relay pub fn set_consumption( &mut self, time: u64, consumption: EnergyConsumption, ) -> anyhow::Result<()> { let idx = self.resolve_offset(time)?; self.buff[idx] = consumption; Ok(()) } /// Get the consumption recorded at a given time pub fn get_consumption(&self, time: u64) -> anyhow::Result { let idx = self.resolve_offset(time)?; Ok(self.buff[idx]) } /// Persist device relay state history pub fn save(&self) -> anyhow::Result<()> { let path = AppConfig::get().energy_consumption_history_day(self.day, self.r#type); std::fs::write( path, bincode::encode_to_vec(&self.buff, bincode::config::standard())?, )?; Ok(()) } /// Get the total runtime of a relay during a given time window pub fn get_history( r#type: ConsumptionHistoryType, from: u64, to: u64, interval: u64, ) -> anyhow::Result> { let mut res = Vec::with_capacity(((to - from) / interval) as usize); let mut file = Self::open(from, r#type)?; let mut curr_time = from; let mut intermediate_values = Vec::new(); while curr_time < to { if !file.contains_time(curr_time) { file = Self::open(curr_time, r#type)?; } intermediate_values.push(file.get_consumption(curr_time)?); if curr_time % interval == from % interval { res.push(median(&intermediate_values)); intermediate_values = Vec::new(); } curr_time += TIME_INTERVAL as u64; } Ok(res) } } #[cfg(test)] mod tests { use crate::app_config::ConsumptionHistoryType; use crate::energy::consumption::EnergyConsumption; use crate::energy::consumption_history_file::{ConsumptionHistoryFile, TIME_INTERVAL}; #[test] fn test_consumption_history() { let mut history = ConsumptionHistoryFile::new_memory(0, ConsumptionHistoryType::GridConsumption); for i in 0..50 { assert_eq!( history.get_consumption(i * TIME_INTERVAL as u64).unwrap(), 0 ); } for i in 0..50 { history .set_consumption(i * TIME_INTERVAL as u64, i as EnergyConsumption * 2) .unwrap(); } for i in 0..50 { assert_eq!( history.get_consumption(i * TIME_INTERVAL as u64).unwrap(), i as EnergyConsumption * 2 ); } } }