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