SolarEnergy/central_backend/src/devices/device.rs

243 lines
7.5 KiB
Rust
Raw Normal View History

2024-07-17 16:57:23 +00:00
//! # Devices entities definition
2024-07-22 20:19:48 +00:00
use crate::constants::StaticConstraints;
use std::collections::HashMap;
2024-07-22 20:19:48 +00:00
2024-07-17 16:57:23 +00:00
/// Device information provided directly by the device during syncrhonisation.
///
/// It should not be editable fro the Web UI
2024-07-01 19:10:45 +00:00
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct DeviceInfo {
2024-07-17 16:57:23 +00:00
/// Device reference
2024-07-01 19:10:45 +00:00
reference: String,
2024-07-17 16:57:23 +00:00
/// Device firmware / software version
2024-07-01 19:10:45 +00:00
version: semver::Version,
2024-07-17 16:57:23 +00:00
/// Maximum number of relay that the device can support
2024-07-01 19:10:45 +00:00
max_relays: usize,
}
2024-07-01 20:24:03 +00:00
impl DeviceInfo {
2024-07-17 16:57:23 +00:00
/// Identify errors in device information definition
2024-07-01 20:24:03 +00:00
pub fn error(&self) -> Option<&str> {
if self.reference.trim().is_empty() {
return Some("Given device reference is empty or blank!");
}
if self.max_relays == 0 {
return Some("Given device cannot handle any relay!");
}
None
}
}
2024-07-17 16:57:23 +00:00
/// Device identifier
2024-07-01 19:10:45 +00:00
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct DeviceId(pub String);
2024-07-17 16:57:23 +00:00
/// Single device information
2024-07-01 19:10:45 +00:00
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Device {
/// The device ID
2024-07-02 20:55:51 +00:00
pub id: DeviceId,
2024-07-01 19:10:45 +00:00
/// Information about the device
2024-07-17 16:57:23 +00:00
///
/// These information shall not be editable from the webui. They are automatically updated during
/// device synchronization
2024-07-02 20:55:51 +00:00
pub info: DeviceInfo,
/// Time at which device was initially enrolled
pub time_create: u64,
/// Time at which device was last updated
pub time_update: u64,
2024-07-01 19:10:45 +00:00
/// Name given to the device on the Web UI
2024-07-02 20:55:51 +00:00
pub name: String,
2024-07-01 19:10:45 +00:00
/// Description given to the device on the Web UI
2024-07-02 20:55:51 +00:00
pub description: String,
/// Specify whether the device has been validated or not. Validated devices are given a
/// certificate
pub validated: bool,
2024-07-01 19:10:45 +00:00
/// Specify whether the device is enabled or not
2024-07-02 20:55:51 +00:00
pub enabled: bool,
2024-07-01 19:10:45 +00:00
/// Information about the relays handled by the device
2024-07-17 16:57:23 +00:00
///
/// There cannot be more than [info.max_relays] relays
2024-07-02 20:55:51 +00:00
pub relays: Vec<DeviceRelay>,
2024-07-01 19:10:45 +00:00
}
/// Structure that contains information about the minimal expected execution
/// time of a device
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct DailyMinRuntime {
2024-07-29 20:11:13 +00:00
/// Minimum time, in seconds, that this relay should run each day
2024-07-02 20:55:51 +00:00
pub min_runtime: usize,
2024-07-17 16:57:23 +00:00
/// The seconds in the days (from 00:00) where the counter is reset
2024-07-02 20:55:51 +00:00
pub reset_time: usize,
2024-07-17 16:57:23 +00:00
/// The hours during which the relay should be turned on to reach expected runtime
2024-07-02 20:55:51 +00:00
pub catch_up_hours: Vec<usize>,
2024-07-01 19:10:45 +00:00
}
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
2024-07-01 19:10:45 +00:00
pub struct DeviceRelayID(uuid::Uuid);
impl Default for DeviceRelayID {
fn default() -> Self {
Self(uuid::Uuid::new_v4())
}
}
2024-07-17 16:57:23 +00:00
/// Single device relay information
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Default)]
2024-07-01 19:10:45 +00:00
pub struct DeviceRelay {
2024-07-17 16:57:23 +00:00
/// Device relay id. Should be unique across the whole application
#[serde(default)]
2024-07-01 19:10:45 +00:00
id: DeviceRelayID,
2024-07-17 16:57:23 +00:00
/// Human-readable name for the relay
2024-07-01 19:10:45 +00:00
name: String,
2024-07-17 16:57:23 +00:00
/// Whether this relay can be turned on or not
2024-07-01 19:10:45 +00:00
enabled: bool,
2024-07-29 20:11:13 +00:00
/// Relay priority when selecting relays to turn on. 0 = lowest priority
2024-07-01 19:10:45 +00:00
priority: usize,
2024-07-17 16:57:23 +00:00
/// Estimated consumption of the electrical equipment triggered by the relay
2024-07-01 19:10:45 +00:00
consumption: usize,
2024-07-29 20:11:13 +00:00
/// Minimal time this relay shall be left on before it can be turned off (in seconds)
2024-07-01 19:10:45 +00:00
minimal_uptime: usize,
2024-07-29 20:11:13 +00:00
/// Minimal time this relay shall be left off before it can be turned on again (in seconds)
2024-07-01 19:10:45 +00:00
minimal_downtime: usize,
2024-07-17 16:57:23 +00:00
/// Optional minimal runtime requirements for this relay
2024-07-01 19:10:45 +00:00
daily_runtime: Option<DailyMinRuntime>,
2024-07-17 16:57:23 +00:00
/// Specify relay that must be turned on before this relay can be started
depends_on: Vec<DeviceRelayID>,
/// Specify relays that must be turned off before this relay can be started
conflicts_with: Vec<DeviceRelayID>,
2024-07-01 19:10:45 +00:00
}
2024-07-22 20:19:48 +00:00
impl DeviceRelay {
/// Check device relay for errors
pub fn error(&self, list: &[DeviceRelay]) -> Option<&'static str> {
let constraints = StaticConstraints::default();
if !constraints.relay_name_len.validate(&self.name) {
return Some("Invalid relay name length!");
}
if !constraints.relay_priority.validate_usize(self.priority) {
return Some("Invalid relay priority!");
}
if !constraints
.relay_consumption
.validate_usize(self.consumption)
{
return Some("Invalid consumption!");
}
if !constraints
.relay_minimal_uptime
.validate_usize(self.minimal_uptime)
{
return Some("Invalid minimal uptime!");
}
if !constraints
.relay_minimal_downtime
.validate_usize(self.minimal_downtime)
{
return Some("Invalid minimal uptime!");
}
if let Some(daily) = &self.daily_runtime {
if !constraints
.relay_daily_minimal_runtime
.validate_usize(daily.min_runtime)
{
return Some("Invalid minimal daily runtime!");
}
if daily.reset_time > 3600 * 24 {
return Some("Invalid daily reset time!");
}
if daily.catch_up_hours.is_empty() {
return Some("No catchup hours defined!");
}
if daily.catch_up_hours.iter().any(|h| h > &23) {
return Some("At least one catch up hour is invalid!");
}
}
let relays_map = list.iter().map(|r| (r.id, r)).collect::<HashMap<_, _>>();
if self.depends_on.iter().any(|d| !relays_map.contains_key(d)) {
return Some("A specified dependent relay does not exists!");
}
if self
.conflicts_with
.iter()
.any(|d| !relays_map.contains_key(d))
{
return Some("A specified conflicting relay does not exists!");
}
// TODO : check for loops
None
}
}
2024-07-22 20:19:48 +00:00
/// Device general information
///
/// This structure is used to update device information
#[derive(serde::Deserialize, Debug, Clone)]
pub struct DeviceGeneralInfo {
pub name: String,
pub description: String,
pub enabled: bool,
}
impl DeviceGeneralInfo {
/// Check for errors in the structure
pub fn error(&self) -> Option<&'static str> {
let constraints = StaticConstraints::default();
if !constraints.dev_name_len.validate(&self.name) {
return Some("Invalid device name length!");
}
if !constraints.dev_description_len.validate(&self.description) {
return Some("Invalid device description length!");
}
None
}
}
#[cfg(test)]
mod tests {
use crate::devices::device::DeviceRelay;
#[test]
fn check_device_relay_error() {
let unitary = DeviceRelay {
name: "unitary".to_string(),
..Default::default()
};
let bad_name = DeviceRelay {
name: "".to_string(),
..Default::default()
};
let dep_on_unitary = DeviceRelay {
name: "dep_on_unitary".to_string(),
depends_on: vec![unitary.id],
..Default::default()
};
assert_eq!(unitary.error(&[]), None);
assert_eq!(unitary.error(&[unitary.clone(), bad_name.clone()]), None);
assert!(bad_name.error(&[]).is_some());
assert_eq!(dep_on_unitary.error(&[unitary.clone()]), None);
assert!(dep_on_unitary.error(&[]).is_some());
}
}