use core::fmt::Display;
use bumpalo::Bump;
use serde::{Deserialize, Serialize};
use crate::heapless::LinearMap;
use crate::properties::RULES_AMOUNT;
use crate::rules::measurements::SystemState;
use crate::rules::strategy::average::{MeanBelowStrategy, MeanOverStrategy};
use crate::rules::strategy::max::MaxOverStrategy;
use crate::rules::strategy::min::MinBelowStrategy;
use crate::rules::strategy::{Rule, RuleStrategy, RuleType};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum SystemStatus {
DECREASE,
INCREASE,
OK,
}
impl Display for SystemStatus {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SystemStatus::DECREASE => formatter.write_str("DECREASE"),
SystemStatus::INCREASE => formatter.write_str("INCREASE"),
SystemStatus::OK => formatter.write_str("OK"),
}
}
}
pub struct RulesEngine {
rules: RulesPool,
current: RuleType,
control: i32,
}
impl RulesEngine {
pub fn new(tag: RuleType, threshold: i32) -> Self {
RulesEngine {
rules: RulesPool::dcs(),
current: tag,
control: threshold,
}
}
pub fn evaluate(&mut self, state: &SystemState) -> SystemStatus {
self.rules
.get(self.current)
.map(|rule| rule.evaluate(self.control, &state.last_measurements()))
.unwrap_or(SystemStatus::OK)
}
pub fn update_strategy(&mut self, change: Rule) {
self.current = change.name;
self.control = change.threshold;
}
pub fn current(&self) -> Rule {
Rule { name: self.current, threshold: self.control }
}
}
#[derive(Default)]
pub struct RulesPool {
strategies: LinearMap<RuleType, *const dyn RuleStrategy, RULES_AMOUNT>,
bump: Bump,
}
impl RulesPool {
pub fn dcs() -> Self {
let mut pool = RulesPool::empty();
pool.add(RuleType::MaxOver, MaxOverStrategy);
pool.add(RuleType::MinBelow, MinBelowStrategy);
pool.add(RuleType::MeanOver, MeanOverStrategy);
pool.add(RuleType::MeanBelow, MeanBelowStrategy);
pool
}
pub fn empty() -> Self {
Self {
strategies: LinearMap::new(),
bump: Bump::new(),
}
}
pub fn is_empty(&self) -> bool {
self.strategies.is_empty()
}
pub fn add<T: RuleStrategy + 'static>(&mut self, tag: RuleType, rule: T) {
let bumped: *const dyn RuleStrategy = self.bump.alloc(rule);
let _ = self.strategies.insert(tag, bumped);
}
pub fn get(&self, tag: RuleType) -> Option<&dyn RuleStrategy> {
self.strategies.get(&tag).and_then(|ptr| {
unsafe { ptr.as_ref() }
})
}
}
#[cfg(test)]
mod test_utils {
use crate::heapless::Vec;
use crate::nodes::SystemNodeId;
use crate::properties::MeasurementsVec;
use crate::rules::manager::SystemStatus;
use crate::rules::measurements::{Measurement, ClusterType, SystemState};
use crate::rules::strategy::RuleStrategy;
#[derive(Debug)]
pub struct FakeStrategy {
status: SystemStatus,
}
impl FakeStrategy {
pub fn new(status: SystemStatus) -> Self {
Self { status }
}
}
impl RuleStrategy for FakeStrategy {
fn evaluate(&self, _: i32, _: &MeasurementsVec) -> SystemStatus {
self.status.clone()
}
}
pub fn some_measurements() -> MeasurementsVec {
Vec::new()
}
pub fn some_state() -> SystemState {
SystemState::new(
SystemNodeId::default(),
Measurement::new(ClusterType::TEMPERATURE, 20),
)
}
}
#[cfg(test)]
mod engine_test {
use crate::rules::manager::test_utils::{some_state, FakeStrategy};
use crate::rules::manager::*;
impl RulesEngine {
pub fn test(rules: RulesPool, current: RuleType) -> Self {
RulesEngine {
rules,
current,
control: 0,
}
}
}
#[test]
fn returns_result_from_strategy() {
let mut rules = RulesPool::empty();
rules.add(
RuleType::MeanOver,
FakeStrategy::new(SystemStatus::DECREASE),
);
let mut engine = RulesEngine::test(rules, RuleType::MeanOver);
matches!(engine.evaluate(&some_state()), SystemStatus::DECREASE);
}
#[test]
fn can_update_rule_strategy() {
let mut rules = RulesPool::empty();
rules.add(
RuleType::MeanOver,
FakeStrategy::new(SystemStatus::DECREASE),
);
rules.add(
RuleType::MeanBelow,
FakeStrategy::new(SystemStatus::INCREASE),
);
let mut engine = RulesEngine::test(rules, RuleType::MeanOver);
engine.update_strategy(Rule {
name: RuleType::MeanBelow,
threshold: 0,
});
matches!(engine.evaluate(&some_state()), SystemStatus::INCREASE);
}
}
#[cfg(test)]
mod pool_tests {
use crate::rules::manager::test_utils::{some_measurements, FakeStrategy};
use super::*;
#[test]
fn can_create_empty_pool() {
let pool = RulesPool::empty();
assert!(pool.is_empty());
}
#[test]
fn after_adding_rule_then_is_not_empty() {
let mut pool = RulesPool::empty();
pool.add(RuleType::MeanOver, rule_returning_increase());
assert!(!pool.is_empty());
}
#[test]
fn after_adding_rule_then_can_get_the_rule() {
let mut pool = RulesPool::empty();
pool.add(RuleType::MeanOver, rule_returning_increase());
let rule = pool.get(RuleType::MeanOver).unwrap();
matches!(
rule.evaluate(0, &some_measurements()),
SystemStatus::INCREASE
);
}
#[test]
fn getting_nonexistent_rule_returns_none() {
let pool = RulesPool::empty();
let rule = pool.get(RuleType::MeanOver);
assert!(rule.is_none());
}
#[test]
fn given_pool_with_multiple_rules_then_can_get_both() {
let mut pool = RulesPool::empty();
pool.add(RuleType::MeanBelow, rule_returning_increase());
pool.add(RuleType::MeanOver, rule_returning_decrease());
let rule1 = pool.get(RuleType::MeanBelow).unwrap();
let rule2 = pool.get(RuleType::MeanOver).unwrap();
matches!(
rule1.evaluate(0, &some_measurements()),
SystemStatus::INCREASE
);
matches!(
rule2.evaluate(0, &some_measurements()),
SystemStatus::DECREASE
);
}
fn rule_returning_increase() -> FakeStrategy {
FakeStrategy::new(SystemStatus::INCREASE)
}
fn rule_returning_decrease() -> FakeStrategy {
FakeStrategy::new(SystemStatus::INCREASE)
}
}
#[macro_export]
macro_rules! pool {
( $( $tag:ident, $x:ident ),* ) => {
{
let mut pool = RulesPool::new();
$(
pool.add($tag, $x)
)*
pool
}
};
}