SolarEnergy/central_backend/src/energy/relay_state_history.rs

176 lines
4.9 KiB
Rust
Raw Normal View History

2024-09-17 20:31:51 +00:00
use crate::app_config::AppConfig;
2024-09-16 20:41:20 +00:00
use crate::devices::device::DeviceRelayID;
2024-09-17 20:31:51 +00:00
use crate::utils::files_utils;
use crate::utils::time_utils::day_number;
const TIME_INTERVAL: usize = 30;
#[derive(thiserror::Error, Debug)]
pub enum RelayStateHistoryError {
#[error("Given time is out of file bounds!")]
TimeOutOfFileBound,
}
2024-09-16 20:41:20 +00:00
/// # RelayStateHistory
///
/// This structures handles the manipulation of relay state history files
///
/// These file are binary file optimizing used space.
pub struct RelayStateHistory {
id: DeviceRelayID,
2024-09-17 20:31:51 +00:00
day: u64,
2024-09-16 20:41:20 +00:00
buff: Vec<u8>,
}
impl RelayStateHistory {
2024-09-17 20:31:51 +00:00
/// Open relay state history file, if it exists, or create an empty one
pub fn open(id: DeviceRelayID, time: u64) -> anyhow::Result<Self> {
let day = day_number(time);
2024-09-17 20:42:24 +00:00
let path = AppConfig::get().relay_runtime_day_file_path(id, day);
2024-09-17 20:31:51 +00:00
if path.exists() {
Ok(Self {
id,
day,
buff: std::fs::read(path)?,
})
} else {
log::debug!(
"Stats for relay {id:?} for day {day} does not exists yet, creating memory buffer"
);
Ok(Self::new_memory(id, day))
}
2024-09-16 20:41:20 +00:00
}
/// Create a new in memory dev relay state history
2024-09-17 20:31:51 +00:00
fn new_memory(id: DeviceRelayID, day: u64) -> Self {
Self {
id,
day,
buff: vec![0; (3600 * 24 / (TIME_INTERVAL * 8)) + 1],
2024-09-17 20:31:51 +00:00
}
2024-09-16 20:41:20 +00:00
}
/// Resolve time offset of a given time in buffer
2024-09-17 20:31:51 +00:00
fn resolve_offset(&self, time: u64) -> anyhow::Result<(usize, u8)> {
let start_of_day = self.day * 3600 * 24;
if time < start_of_day || time >= start_of_day + 3600 * 24 {
return Err(RelayStateHistoryError::TimeOutOfFileBound.into());
}
let relative_time = (time - start_of_day) / TIME_INTERVAL as u64;
Ok(((relative_time / 8) as usize, (relative_time % 8) as u8))
2024-09-16 20:41:20 +00:00
}
/// Check if a time is contained in this history
pub fn contains_time(&self, time: u64) -> bool {
self.resolve_offset(time).is_ok()
}
2024-09-16 20:41:20 +00:00
/// Set new state of relay
2024-09-17 20:31:51 +00:00
pub fn set_state(&mut self, time: u64, on: bool) -> anyhow::Result<()> {
let (idx, offset) = self.resolve_offset(time)?;
self.buff[idx] = if on {
self.buff[idx] | (0x1 << offset)
} else {
self.buff[idx] & !(0x1 << offset)
};
Ok(())
2024-09-16 20:41:20 +00:00
}
/// Get the state of relay at a given time
2024-09-17 20:31:51 +00:00
pub fn get_state(&self, time: u64) -> anyhow::Result<bool> {
let (idx, offset) = self.resolve_offset(time)?;
Ok(self.buff[idx] & (0x1 << offset) != 0)
2024-09-16 20:41:20 +00:00
}
/// Persist device relay state history
pub fn save(&self) -> anyhow::Result<()> {
2024-09-17 20:42:24 +00:00
let path = AppConfig::get().relay_runtime_day_file_path(self.id, self.day);
2024-09-17 20:31:51 +00:00
files_utils::create_directory_if_missing(path.parent().unwrap())?;
std::fs::write(path, &self.buff)?;
Ok(())
}
}
/// Get the total runtime of a relay during a given time window
pub fn relay_total_runtime(device_id: DeviceRelayID, from: u64, to: u64) -> anyhow::Result<usize> {
let mut total = 0;
let mut file = RelayStateHistory::open(device_id, from)?;
let mut curr_time = from;
while curr_time < to {
if !file.contains_time(curr_time) {
file = RelayStateHistory::open(device_id, curr_time)?;
}
if file.get_state(curr_time)? {
total += TIME_INTERVAL;
}
curr_time += TIME_INTERVAL as u64;
}
Ok(total)
}
2024-09-17 20:31:51 +00:00
#[cfg(test)]
mod tests {
use crate::devices::device::DeviceRelayID;
use crate::energy::relay_state_history::{relay_total_runtime, RelayStateHistory};
2024-09-17 20:31:51 +00:00
#[test]
fn test_relay_state_history() {
let mut history = RelayStateHistory::new_memory(DeviceRelayID::default(), 0);
let val_1 = 5 * 30;
let val_2 = 7 * 30;
for i in 0..500 {
assert!(!history.get_state(i).unwrap())
}
history.set_state(val_1, true).unwrap();
for i in 0..500 {
assert_eq!(history.get_state(i).unwrap(), (i / 30) * 30 == val_1);
}
history.set_state(val_2, true).unwrap();
for i in 0..500 {
assert_eq!(
history.get_state(i).unwrap(),
(i / 30) * 30 == val_1 || (i / 30) * 30 == val_2
);
}
history.set_state(val_2, false).unwrap();
for i in 0..500 {
assert_eq!(history.get_state(i).unwrap(), (i / 30) * 30 == val_1);
}
history.set_state(val_1, false).unwrap();
for i in 0..500 {
assert!(!history.get_state(i).unwrap())
}
assert!(history.get_state(8989898).is_err());
2024-09-16 20:41:20 +00:00
}
#[test]
fn test_relay_total_runtime() {
assert_eq!(
relay_total_runtime(DeviceRelayID::default(), 50, 3600 * 24 * 60 + 500).unwrap(),
0
);
}
2024-09-16 20:41:20 +00:00
}