Record relays state

This commit is contained in:
Pierre HUBERT 2024-09-17 22:31:51 +02:00
parent 368eb13089
commit 565db05fb0
8 changed files with 165 additions and 19 deletions

View File

@ -35,4 +35,4 @@ tokio_schedule = "0.3.2"
mime_guess = "2.0.5"
rust-embed = "8.5.0"
jsonwebtoken = { version = "9.3.0", features = ["use_pem"] }
prettytable-rs = "0.10.0"
prettytable-rs = "0.10.0"

View File

@ -1,4 +1,4 @@
use crate::devices::device::DeviceId;
use crate::devices::device::{DeviceId, DeviceRelayID};
use clap::{Parser, Subcommand};
use std::path::{Path, PathBuf};
@ -81,7 +81,7 @@ pub struct AppConfig {
pub production_margin: i32,
/// Energy refresh operations interval, in seconds
#[arg(short('i'), long, env, default_value_t = 30)]
#[arg(short('i'), long, env, default_value_t = 20)]
pub refresh_interval: u64,
/// Consumption backend provider
@ -235,6 +235,18 @@ impl AppConfig {
pub fn device_csr_path(&self, id: &DeviceId) -> PathBuf {
self.devices_config_path().join(format!("{}.csr", id.0))
}
/// Get relays runtime storage path
pub fn relays_runtime_stats_storage_path(&self) -> PathBuf {
self.storage_path().join("relays_runtime")
}
/// Get relay runtime stats path for a given day
pub fn relay_runtime_file_path(&self, relay_id: DeviceRelayID, day: u64) -> PathBuf {
self.relays_runtime_stats_storage_path()
.join(relay_id.0.to_string())
.join(day.to_string())
}
}
#[cfg(test)]

View File

@ -77,7 +77,7 @@ pub struct DailyMinRuntime {
}
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct DeviceRelayID(uuid::Uuid);
pub struct DeviceRelayID(pub uuid::Uuid);
impl Default for DeviceRelayID {
fn default() -> Self {

View File

@ -38,8 +38,11 @@ impl EnergyActor {
constants::FALLBACK_PRODUCTION_VALUE
});
self.engine
.refresh(self.curr_consumption, &self.devices.full_list());
let devices_list = self.devices.full_list();
self.engine.refresh(self.curr_consumption, &devices_list);
self.engine.persist_relays_state(&devices_list)?;
Ok(())
}

View File

@ -6,6 +6,7 @@ use prettytable::{row, Table};
use crate::constants;
use crate::devices::device::{Device, DeviceId, DeviceRelay, DeviceRelayID};
use crate::energy::consumption::EnergyConsumption;
use crate::energy::relay_state_history::RelayStateHistory;
use crate::utils::time_utils::time_secs;
#[derive(Default)]
@ -320,4 +321,18 @@ impl EnergyEngine {
self.print_summary(curr_consumption, devices);
}
/// Save relays state to disk
pub fn persist_relays_state(&mut self, devices: &[Device]) -> anyhow::Result<()> {
// Save all relays state
for d in devices {
for r in &d.relays {
let mut file = RelayStateHistory::open(r.id, time_secs())?;
file.set_state(time_secs(), self.relay_state(r.id).is_on())?;
file.save()?;
}
}
Ok(())
}
}

View File

@ -1,4 +1,15 @@
use crate::app_config::AppConfig;
use crate::devices::device::DeviceRelayID;
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,
}
/// # RelayStateHistory
///
@ -7,38 +18,124 @@ use crate::devices::device::DeviceRelayID;
/// These file are binary file optimizing used space.
pub struct RelayStateHistory {
id: DeviceRelayID,
day: usize,
day: u64,
buff: Vec<u8>,
}
impl RelayStateHistory {
/// Open relay state history file, if it exist, or create an empty one
pub fn open(id: DeviceRelayID, day: usize) -> anyhow::Result<Self> {
todo!()
/// 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))
}
}
/// Create a new in memory dev relay state history
fn new_memory(id: DeviceRelayID, day: usize) -> Self {
todo!()
fn new_memory(id: DeviceRelayID, day: u64) -> Self {
Self {
id,
day,
buff: vec![0; 3600 * 24 / TIME_INTERVAL],
}
}
/// Resolve time offset of a given time in buffer
fn resolve_offset(&self, time: i64) -> anyhow::Result<(usize, u8)> {
todo!()
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))
}
/// Set new state of relay
fn set_state(&mut self, time: i64, on: bool) -> anyhow::Result<()> {
todo!()
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(())
}
/// Get the state of relay at a given time
fn get_state(&self, time: i64) -> anyhow::Result<bool> {
todo!()
pub fn get_state(&self, time: u64) -> anyhow::Result<bool> {
let (idx, offset) = self.resolve_offset(time)?;
Ok(self.buff[idx] & (0x1 << offset) != 0)
}
/// Persist device relay state history
pub fn save(&self) -> anyhow::Result<()> {
todo!()
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());
}
}

View File

@ -17,6 +17,7 @@ async fn main() -> std::io::Result<()> {
// Initialize storage
create_directory_if_missing(AppConfig::get().pki_path()).unwrap();
create_directory_if_missing(AppConfig::get().devices_config_path()).unwrap();
create_directory_if_missing(AppConfig::get().relays_runtime_stats_storage_path()).unwrap();
// Initialize PKI
pki::initialize_root_ca().expect("Failed to initialize Root CA!");

View File

@ -15,3 +15,21 @@ pub fn time_millis() -> u128 {
.unwrap()
.as_millis()
}
/// Get the number of the day since 01-01-1970 of a given UNIX timestamp
pub fn day_number(time: u64) -> u64 {
time / (3600 * 24)
}
#[cfg(test)]
mod test {
use crate::utils::time_utils::day_number;
#[test]
fn test_time_of_day() {
assert_eq!(day_number(500), 0);
assert_eq!(day_number(1726592301), 19983);
assert_eq!(day_number(1726592401), 19983);
assert_eq!(day_number(1726498701), 19982);
}
}