Record relays state
This commit is contained in:
		@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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)]
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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(())
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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!");
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user