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);
|
|
|
|
let path = AppConfig::get().relay_runtime_file_path(id, day);
|
|
|
|
|
|
|
|
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],
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/// 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:31:51 +00:00
|
|
|
let path = AppConfig::get().relay_runtime_file_path(self.id, self.day);
|
|
|
|
files_utils::create_directory_if_missing(path.parent().unwrap())?;
|
|
|
|
std::fs::write(path, &self.buff)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::devices::device::DeviceRelayID;
|
|
|
|
use crate::energy::relay_state_history::RelayStateHistory;
|
|
|
|
|
|
|
|
#[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
|
|
|
}
|
|
|
|
}
|