Record relays state
This commit is contained in:
parent
368eb13089
commit
565db05fb0
@ -35,4 +35,4 @@ tokio_schedule = "0.3.2"
|
|||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
rust-embed = "8.5.0"
|
rust-embed = "8.5.0"
|
||||||
jsonwebtoken = { version = "9.3.0", features = ["use_pem"] }
|
jsonwebtoken = { version = "9.3.0", features = ["use_pem"] }
|
||||||
prettytable-rs = "0.10.0"
|
prettytable-rs = "0.10.0"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::devices::device::DeviceId;
|
use crate::devices::device::{DeviceId, DeviceRelayID};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ pub struct AppConfig {
|
|||||||
pub production_margin: i32,
|
pub production_margin: i32,
|
||||||
|
|
||||||
/// Energy refresh operations interval, in seconds
|
/// 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,
|
pub refresh_interval: u64,
|
||||||
|
|
||||||
/// Consumption backend provider
|
/// Consumption backend provider
|
||||||
@ -235,6 +235,18 @@ impl AppConfig {
|
|||||||
pub fn device_csr_path(&self, id: &DeviceId) -> PathBuf {
|
pub fn device_csr_path(&self, id: &DeviceId) -> PathBuf {
|
||||||
self.devices_config_path().join(format!("{}.csr", id.0))
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -77,7 +77,7 @@ pub struct DailyMinRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
|
#[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 {
|
impl Default for DeviceRelayID {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -38,8 +38,11 @@ impl EnergyActor {
|
|||||||
constants::FALLBACK_PRODUCTION_VALUE
|
constants::FALLBACK_PRODUCTION_VALUE
|
||||||
});
|
});
|
||||||
|
|
||||||
self.engine
|
let devices_list = self.devices.full_list();
|
||||||
.refresh(self.curr_consumption, &self.devices.full_list());
|
|
||||||
|
self.engine.refresh(self.curr_consumption, &devices_list);
|
||||||
|
|
||||||
|
self.engine.persist_relays_state(&devices_list)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use prettytable::{row, Table};
|
|||||||
use crate::constants;
|
use crate::constants;
|
||||||
use crate::devices::device::{Device, DeviceId, DeviceRelay, DeviceRelayID};
|
use crate::devices::device::{Device, DeviceId, DeviceRelay, DeviceRelayID};
|
||||||
use crate::energy::consumption::EnergyConsumption;
|
use crate::energy::consumption::EnergyConsumption;
|
||||||
|
use crate::energy::relay_state_history::RelayStateHistory;
|
||||||
use crate::utils::time_utils::time_secs;
|
use crate::utils::time_utils::time_secs;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -320,4 +321,18 @@ impl EnergyEngine {
|
|||||||
|
|
||||||
self.print_summary(curr_consumption, devices);
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,15 @@
|
|||||||
|
use crate::app_config::AppConfig;
|
||||||
use crate::devices::device::DeviceRelayID;
|
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
|
/// # RelayStateHistory
|
||||||
///
|
///
|
||||||
@ -7,38 +18,124 @@ use crate::devices::device::DeviceRelayID;
|
|||||||
/// These file are binary file optimizing used space.
|
/// These file are binary file optimizing used space.
|
||||||
pub struct RelayStateHistory {
|
pub struct RelayStateHistory {
|
||||||
id: DeviceRelayID,
|
id: DeviceRelayID,
|
||||||
day: usize,
|
day: u64,
|
||||||
buff: Vec<u8>,
|
buff: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelayStateHistory {
|
impl RelayStateHistory {
|
||||||
/// Open relay state history file, if it exist, or create an empty one
|
/// Open relay state history file, if it exists, or create an empty one
|
||||||
pub fn open(id: DeviceRelayID, day: usize) -> anyhow::Result<Self> {
|
pub fn open(id: DeviceRelayID, time: u64) -> anyhow::Result<Self> {
|
||||||
todo!()
|
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
|
/// Create a new in memory dev relay state history
|
||||||
fn new_memory(id: DeviceRelayID, day: usize) -> Self {
|
fn new_memory(id: DeviceRelayID, day: u64) -> Self {
|
||||||
todo!()
|
Self {
|
||||||
|
id,
|
||||||
|
day,
|
||||||
|
buff: vec![0; 3600 * 24 / TIME_INTERVAL],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve time offset of a given time in buffer
|
/// Resolve time offset of a given time in buffer
|
||||||
fn resolve_offset(&self, time: i64) -> anyhow::Result<(usize, u8)> {
|
fn resolve_offset(&self, time: u64) -> anyhow::Result<(usize, u8)> {
|
||||||
todo!()
|
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
|
/// Set new state of relay
|
||||||
fn set_state(&mut self, time: i64, on: bool) -> anyhow::Result<()> {
|
pub fn set_state(&mut self, time: u64, on: bool) -> anyhow::Result<()> {
|
||||||
todo!()
|
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
|
/// Get the state of relay at a given time
|
||||||
fn get_state(&self, time: i64) -> anyhow::Result<bool> {
|
pub fn get_state(&self, time: u64) -> anyhow::Result<bool> {
|
||||||
todo!()
|
let (idx, offset) = self.resolve_offset(time)?;
|
||||||
|
|
||||||
|
Ok(self.buff[idx] & (0x1 << offset) != 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Persist device relay state history
|
/// Persist device relay state history
|
||||||
pub fn save(&self) -> anyhow::Result<()> {
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
// Initialize storage
|
// Initialize storage
|
||||||
create_directory_if_missing(AppConfig::get().pki_path()).unwrap();
|
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().devices_config_path()).unwrap();
|
||||||
|
create_directory_if_missing(AppConfig::get().relays_runtime_stats_storage_path()).unwrap();
|
||||||
|
|
||||||
// Initialize PKI
|
// Initialize PKI
|
||||||
pki::initialize_root_ca().expect("Failed to initialize Root CA!");
|
pki::initialize_root_ca().expect("Failed to initialize Root CA!");
|
||||||
|
@ -15,3 +15,21 @@ pub fn time_millis() -> u128 {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.as_millis()
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user