165 lines
4.8 KiB
Rust
165 lines
4.8 KiB
Rust
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<EnergyConsumption>,
|
|
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<Self> {
|
|
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<usize> {
|
|
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<EnergyConsumption> {
|
|
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<Vec<EnergyConsumption>> {
|
|
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
|
|
);
|
|
}
|
|
}
|
|
}
|