use core::fmt::Display;
use heapless::Vec;
use log::warn;
use serde::{Deserialize, Serialize};
use crate::properties::MeasurementsVec;
use crate::rules::manager::SystemStatus;
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Copy, Clone)]
pub struct Rule {
pub name: RuleType,
pub threshold: i32,
}
impl Display for Rule {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "[Rule: {} | Threshold: {}]", self.name, self.threshold)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum RuleType {
MeanOver,
MeanBelow,
MaxOver,
MinBelow,
}
impl Display for RuleType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
RuleType::MeanBelow => f.write_str("MeanBelow"),
RuleType::MeanOver => f.write_str("MeanOver"),
RuleType::MaxOver => f.write_str("MaxOver"),
RuleType::MinBelow => f.write_str("MinBelow"),
}
}
}
impl RuleType {
pub fn from_strategy(rule: &str) -> (RuleType, i32) {
let parts = rule.split(":").collect::<Vec<&str, 2>>();
if parts.len() != 2 {
warn!("Cannot extract rule from {}, defaulting for MeanOver with threshold 24.", rule);
return (RuleType::MeanOver, 24)
}
let strategy = match parts[0] {
"MeanOver" => RuleType::MeanOver,
"MeanBelow" => RuleType::MeanBelow,
"MaxOver" => RuleType::MaxOver,
"MinBelow" => RuleType::MinBelow,
_ => {
warn!("Cannot convert {} to RuleType, defaulting for MeanOver.", parts[0]);
RuleType::MeanOver
}
};
let threshold = match parts[1].parse::<i32>() {
Ok(value) => value,
Err(_) => {
warn!("Cannot parse {} to i32 for threshold, defaulting with 24.", parts[1]);
24
}
};
(strategy, threshold)
}
pub fn from(rule: &str) -> RuleType {
match rule {
"MeanOver" => RuleType::MeanOver,
"MeanBelow" => RuleType::MeanBelow,
"MaxOver" => RuleType::MaxOver,
"MinBelow" => RuleType::MinBelow,
_ => {
warn!("Cannot convert {} to RuleType, defaulting for MeanOver.", rule);
RuleType::MeanOver
}
}
}
}
pub struct RuleStrategyOperators;
impl RuleStrategyOperators {
pub fn average(measurements: &MeasurementsVec) -> i32 {
let mut total = 0;
for measurement in measurements.iter() {
total += measurement.value;
}
total
.checked_div(measurements.len() as i32)
.unwrap_or_default()
}
pub fn max(measurements: &MeasurementsVec) -> Option<i32> {
measurements
.iter()
.max()
.map(|measurement| measurement.value)
}
pub fn min(measurements: &MeasurementsVec) -> Option<i32> {
measurements
.iter()
.min()
.map(|measurement| measurement.value)
}
pub fn over(control: i32, value: i32) -> bool {
value > control
}
pub fn below(control: i32, value: i32) -> bool {
value < control
}
}
pub trait RuleStrategy {
fn evaluate(&self, control: i32, measurements: &MeasurementsVec) -> SystemStatus;
}
pub mod max {
use log::info;
use crate::properties::MeasurementsVec;
use crate::rules::manager::SystemStatus;
use crate::rules::strategy::RuleStrategyOperators;
use super::RuleStrategy;
#[derive(Default)]
pub struct MaxOverStrategy;
impl RuleStrategy for MaxOverStrategy {
fn evaluate(&self, control: i32, measurements: &MeasurementsVec) -> SystemStatus {
let value = RuleStrategyOperators::max(measurements);
if value.is_none() {
return SystemStatus::OK;
}
let value = value.unwrap();
if RuleStrategyOperators::over(control, value) {
info!(
"Max of measured values is {}, which is over the control value of {}.",
value, control
);
SystemStatus::DECREASE
} else {
SystemStatus::OK
}
}
}
}
pub mod min {
use log::info;
use crate::properties::MeasurementsVec;
use crate::rules::manager::SystemStatus;
use crate::rules::strategy::RuleStrategyOperators;
use super::RuleStrategy;
#[derive(Default)]
pub struct MinBelowStrategy;
impl RuleStrategy for MinBelowStrategy {
fn evaluate(&self, control: i32, measurements: &MeasurementsVec) -> SystemStatus {
let value = RuleStrategyOperators::min(measurements);
if value.is_none() {
return SystemStatus::OK;
}
let value = value.unwrap();
if RuleStrategyOperators::below(control, value) {
info!(
"Min of measured values is {}, which is below the control value of {}.",
value, control
);
SystemStatus::INCREASE
} else {
SystemStatus::OK
}
}
}
}
pub mod average {
use log::info;
use crate::properties::MeasurementsVec;
use crate::rules::manager::SystemStatus;
use crate::rules::strategy::RuleStrategyOperators;
use super::RuleStrategy;
#[derive(Default)]
pub struct MeanBelowStrategy;
impl RuleStrategy for MeanBelowStrategy {
fn evaluate(&self, control: i32, measurements: &MeasurementsVec) -> SystemStatus {
let value = RuleStrategyOperators::average(measurements);
if RuleStrategyOperators::below(control, value) {
info!(
"Mean of measured values is {}, which is below the control value of {}.",
value, control
);
SystemStatus::INCREASE
} else {
SystemStatus::OK
}
}
}
#[derive(Default)]
pub struct MeanOverStrategy;
impl RuleStrategy for MeanOverStrategy {
fn evaluate(&self, control: i32, measurements: &MeasurementsVec) -> SystemStatus {
let value = RuleStrategyOperators::average(measurements);
if RuleStrategyOperators::over(control, value) {
info!(
"Mean of measured values is {}, which is over the control value of {}.",
value, control
);
SystemStatus::DECREASE
} else {
SystemStatus::OK
}
}
}
}
#[cfg(test)]
#[allow(unused_must_use)]
mod max_strategy_tests {
use crate::properties::MeasurementsVec;
use crate::rules::manager::SystemStatus;
use crate::rules::measurements::{Measurement, ClusterType};
use crate::rules::strategy::max::MaxOverStrategy;
use crate::rules::strategy::RuleStrategy;
#[test]
fn given_a_max_over_strategy_and_values_over_threshold_should_alert() {
let strategy = MaxOverStrategy::default();
let mut measurements = MeasurementsVec::new();
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 10,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 20,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 30,
});
matches!(strategy.evaluate(25, &measurements), SystemStatus::DECREASE);
}
#[test]
fn given_a_max_over_strategy_and_values_below_threshold_should_not_alert() {
let strategy = MaxOverStrategy::default();
let mut measurements = MeasurementsVec::new();
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 10,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 20,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 30,
});
matches!(strategy.evaluate(35, &measurements), SystemStatus::OK);
}
}
#[cfg(test)]
#[allow(unused_must_use)]
mod min_strategy_tests {
use crate::properties::MeasurementsVec;
use crate::rules::manager::SystemStatus;
use crate::rules::measurements::{Measurement, ClusterType};
use crate::rules::strategy::min::MinBelowStrategy;
use crate::rules::strategy::RuleStrategy;
#[test]
fn given_a_min_below_strategy_and_values_below_threshold_should_alert() {
let strategy = MinBelowStrategy::default();
let mut measurements = MeasurementsVec::new();
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 10,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 20,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 30,
});
matches!(strategy.evaluate(15, &measurements), SystemStatus::INCREASE);
}
#[test]
fn given_a_min_below_strategy_and_values_over_threshold_should_not_alert() {
let strategy = MinBelowStrategy::default();
let mut measurements = MeasurementsVec::new();
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 10,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 20,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 30,
});
matches!(strategy.evaluate(5, &measurements), SystemStatus::OK);
}
}
#[cfg(test)]
#[allow(unused_must_use)]
mod average_strategy_tests {
use crate::properties::MeasurementsVec;
use crate::rules::manager::SystemStatus;
use crate::rules::measurements::{Measurement, ClusterType};
use crate::rules::strategy::average::{MeanBelowStrategy, MeanOverStrategy};
use crate::rules::strategy::RuleStrategy;
#[test]
fn given_a_mean_below_strategy_and_values_below_threshold_should_alert() {
let strategy = MeanBelowStrategy::default();
let mut measurements = MeasurementsVec::new();
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 10,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 20,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 30,
});
matches!(strategy.evaluate(25, &measurements), SystemStatus::INCREASE);
}
#[test]
fn given_a_mean_below_strategy_and_values_over_threshold_should_not_alert() {
let strategy = MeanBelowStrategy::default();
let mut measurements = MeasurementsVec::new();
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 10,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 20,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 30,
});
matches!(strategy.evaluate(15, &measurements), SystemStatus::OK);
}
#[test]
fn given_a_mean_over_strategy_and_values_below_threshold_should_not_alert() {
let strategy = MeanOverStrategy::default();
let mut measurements = MeasurementsVec::new();
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 10,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 20,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 30,
});
matches!(strategy.evaluate(15, &measurements), SystemStatus::DECREASE);
}
#[test]
fn given_a_mean_over_strategy_and_values_over_threshold_should_alert() {
let strategy = MeanOverStrategy::default();
let mut measurements = MeasurementsVec::new();
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 10,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 20,
});
measurements.push(Measurement {
typed: ClusterType::TEMPERATURE,
value: 30,
});
matches!(strategy.evaluate(25, &measurements), SystemStatus::OK);
}
}