use crate::controllers::Controller;
use crate::types::{Action, Message};
use chrono::{DateTime, Duration, Utc};
use crate::input::Input;
use crate::output::Output;
use crate::scheduler::Scheduler;
enum State {
BelowThreshold,
WithinTolerance,
AboveThreshold,
}
#[derive(Debug)]
pub struct BidirectionalThreshold<I, O, O2>
where
I: Fn() -> String,
O: FnMut(bool),
O2: FnMut(bool),
{
name: Option<String>,
threshold: f32,
tolerance: f32,
input: Input<I>,
increase_output: Output<O>,
decrease_output: Output<O2>,
interval: Duration,
schedule: Scheduler,
}
impl<I, O, O2> BidirectionalThreshold<I, O, O2>
where
I: Fn() -> String,
O: FnMut(bool),
O2: FnMut(bool),
{
pub fn new(
threshold: f32,
tolerance: f32,
input: Input<I>,
increase_output: Output<O>,
decrease_output: Output<O2>,
interval: Duration,
) -> Self {
Self {
name: None,
threshold,
tolerance,
input,
increase_output,
decrease_output,
interval,
schedule: Scheduler::new(),
}.schedule_next(None)
}
pub fn new_without_scheduled(
threshold: f32,
tolerance: f32,
input: Input<I>,
increase_output: Output<O>,
decrease_output: Output<O2>,
interval: Duration,
) -> Self {
Self {
name: None,
threshold,
tolerance,
input,
increase_output,
decrease_output,
interval,
schedule: Scheduler::new(),
}
}
fn get_state(&mut self) -> State {
let value = self.input.read().parse::<f32>().unwrap();
if value > self.threshold + self.tolerance {
State::AboveThreshold
} else if value < self.threshold - self.tolerance {
State::BelowThreshold
} else {
State::WithinTolerance
}
}
fn handle_above_threshold(&mut self) {
self.decrease_output.activate();
self.increase_output.deactivate();
}
fn handle_below_threshold(&mut self) {
self.increase_output.activate();
self.decrease_output.deactivate();
}
fn handle_within_tolerance(&mut self) {
self.increase_output.deactivate();
self.decrease_output.deactivate();
}
fn schedule_next_in_place(&mut self, time: DateTime<Utc>) {
self.schedule.schedule_read(time + self.interval);
}
pub fn schedule_next<T>(mut self, time: T) -> Self
where T: Into<Option<DateTime<Utc>>>{
let time= time.into().unwrap_or_else(|| Utc::now());
self.schedule_next_in_place(time);
self
}
}
impl<I, O, O2> Controller for BidirectionalThreshold<I, O, O2>
where
I: Fn() -> String,
O: FnMut(bool),
O2: FnMut(bool),
{
fn set_name(&mut self, name: String) {
self.name = Some(name);
}
fn get_name(&self) -> Option<String> {
self.name.clone()
}
fn poll(&mut self, time: DateTime<Utc>) -> Option<Message> {
if let Some(event) = self.schedule.attempt_execution(time) {
match event.get_action() {
Action::Read => {
let msg = match self.get_state() {
State::AboveThreshold => {
self.handle_above_threshold();
"Above Threshold".to_string()
},
State::BelowThreshold => {
self.handle_below_threshold();
"Below Threshold".to_string()
},
State::WithinTolerance => {
self.handle_within_tolerance();
"Within Tolerance".to_string()
},
};
self.schedule_next_in_place(time);
let read_state = self.input.get_state().clone();
return Some(Message::new(
self.get_name().unwrap_or_default(),
msg,
event.get_timestamp().clone(),
read_state,
));
}
_ => {}
}
}
None
}
}
impl Default for BidirectionalThreshold<fn() -> String, fn(bool), fn(bool)> {
fn default() -> Self {
Self::new_without_scheduled(
0.0,
0.0,
Input::default(),
Output::default(),
Output::default(),
Duration::seconds(1),
)
}
}
#[cfg(test)]
mod tests {
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use super::*;
#[test]
fn test_new() {
let threshold = 10.0;
let tolerance = 1.0;
let input = Input::default();
let increase_output = Output::default();
let decrease_output = Output::default();
let interval = Duration::seconds(1);
let controller = BidirectionalThreshold::new_without_scheduled(
threshold,
tolerance,
input,
increase_output,
decrease_output,
interval,
);
assert_eq!(controller.threshold, threshold);
assert_eq!(controller.tolerance, tolerance);
assert_eq!(controller.interval, interval);
assert!(!controller.schedule.has_future_events());
assert!(controller.increase_output.get_state().is_none());
assert!(controller.decrease_output.get_state().is_none());
}
#[test]
fn test_with_time() {
let threshold = 10.0;
let tolerance = 1.0;
let input = Input::default();
let increase_output = Output::default();
let decrease_output = Output::default();
let interval = Duration::seconds(1);
let controller = BidirectionalThreshold::new(
threshold,
tolerance,
input,
increase_output,
decrease_output,
interval,
);
assert_eq!(controller.threshold, threshold);
assert_eq!(controller.tolerance, tolerance);
assert_eq!(controller.interval, interval);
assert!(controller.schedule.has_future_events());
}
#[test]
fn test_handle_above_threshold() {
let threshold = 10.0;
let tolerance = 1.0;
let input = Input::default();
let increase_output = Output::default();
let decrease_output = Output::default();
let interval = Duration::seconds(1);
let mut controller = BidirectionalThreshold::new_without_scheduled(
threshold,
tolerance,
input,
increase_output,
decrease_output,
interval,
);
controller.handle_above_threshold();
assert_eq!(controller.increase_output.get_state(), Some(false));
assert_eq!(controller.decrease_output.get_state(), Some(true));
}
#[test]
fn test_handle_below_threshold() {
let threshold = 10.0;
let tolerance = 1.0;
let input = Input::default();
let increase_output = Output::default();
let decrease_output = Output::default();
let interval = Duration::seconds(1);
let mut controller = BidirectionalThreshold::new_without_scheduled(
threshold,
tolerance,
input,
increase_output,
decrease_output,
interval,
);
controller.handle_below_threshold();
assert_eq!(controller.increase_output.get_state(), Some(true));
assert_eq!(controller.decrease_output.get_state(), Some(false));
}
#[test]
fn test_handle_within_tolerance() {
let threshold = 10.0;
let tolerance = 1.0;
let input = Input::default();
let increase_output = Output::default();
let decrease_output = Output::default();
let interval = Duration::seconds(1);
let mut controller = BidirectionalThreshold::new_without_scheduled(
threshold,
tolerance,
input,
increase_output,
decrease_output,
interval,
);
controller.handle_within_tolerance();
assert_eq!(controller.increase_output.get_state(), Some(false));
assert_eq!(controller.decrease_output.get_state(), Some(false));
}
#[test]
fn test_get_set_name() {
let mut controller = BidirectionalThreshold::default();
assert_eq!(controller.get_name(), None);
controller.set_name(String::from("test"));
assert_eq!(controller.get_name(), Some(String::from("test")));
}
#[test]
fn test_poll() {
let threshold = 10.0;
let tolerance = 1.0;
let input_values = Arc::new(Mutex::new(VecDeque::from([
"8.0".to_string(),
"10.5".to_string(),
"12.0".to_string(),
])));
let input = Input::new(||
input_values.lock().unwrap().pop_front().unwrap()
);
let increase_output = Output::default();
let decrease_output = Output::default();
let interval = Duration::seconds(1);
let time = Utc::now();
let mut controller = BidirectionalThreshold::new_without_scheduled(
threshold,
tolerance,
input,
increase_output,
decrease_output,
interval,
).schedule_next(time);
assert!(controller.increase_output.get_state().is_none());
assert!(controller.decrease_output.get_state().is_none());
controller.poll(time);
assert!(controller.increase_output.get_state().is_none());
assert!(controller.decrease_output.get_state().is_none());
let message = controller.poll(time + Duration::milliseconds(500));
assert!(controller.increase_output.get_state().is_none());
assert!(controller.decrease_output.get_state().is_none());
assert!(message.is_none());
let message = controller.poll(time + Duration::seconds(1));
assert_eq!(controller.increase_output.get_state(), Some(true));
assert_eq!(controller.decrease_output.get_state(), Some(false));
assert!(message.is_some());
assert_eq!(message.as_ref().unwrap().get_read_state().unwrap(), "8.0".to_string());
assert_eq!(message.as_ref().unwrap().get_content(), "Below Threshold".to_string());
let message = controller.poll(time + Duration::seconds(1) + Duration::milliseconds(500));
assert_eq!(controller.increase_output.get_state(), Some(true));
assert_eq!(controller.decrease_output.get_state(), Some(false));
assert!(message.is_none());
let message = controller.poll(time + Duration::seconds(2));
assert_eq!(controller.increase_output.get_state(), Some(false));
assert_eq!(controller.decrease_output.get_state(), Some(false));
assert!(message.is_some());
assert_eq!(message.as_ref().unwrap().get_read_state().unwrap(), "10.5".to_string());
assert_eq!(message.as_ref().unwrap().get_content(), "Within Tolerance".to_string());
let message = controller.poll(time + Duration::seconds(2) + Duration::milliseconds(500));
assert_eq!(controller.increase_output.get_state(), Some(false));
assert_eq!(controller.decrease_output.get_state(), Some(false));
assert!(message.is_none());
let message = controller.poll(time + Duration::seconds(3));
assert_eq!(controller.increase_output.get_state(), Some(false));
assert_eq!(controller.decrease_output.get_state(), Some(true));
assert!(message.is_some());
assert_eq!(message.as_ref().unwrap().get_read_state().unwrap(), "12.0".to_string());
assert_eq!(message.as_ref().unwrap().get_content(), "Above Threshold".to_string());
let message = controller.poll(time + Duration::seconds(3) + Duration::milliseconds(500));
assert_eq!(controller.increase_output.get_state(), Some(false));
assert_eq!(controller.decrease_output.get_state(), Some(true));
assert!(message.is_none());
}
}