use uuid::Uuid;
use crate::base::{
block::{Block, BlockDesc, BlockProps, BlockState},
input::{InputProps, input_reader::InputReader},
output::Output,
};
use crate::blocks::utils::{input_as_number, input_as_number_matching};
use libhaystack::val::{Bool, Value, kind::HaystackKind};
use crate::{blocks::InputImpl, blocks::OutputImpl};
#[block]
#[derive(BlockProps, Debug)]
#[category = "control"]
pub struct Economizer {
#[input(kind = "Number")]
pub oa: InputImpl,
#[input(kind = "Number")]
pub ra: InputImpl,
#[input(name = "highLimit", kind = "Number")]
pub high_limit: InputImpl,
#[input(kind = "Number")]
pub deadband: InputImpl,
#[output(name = "enable", kind = "Bool")]
pub out: OutputImpl,
}
impl Block for Economizer {
async fn execute(&mut self) {
self.read_inputs_until_ready().await;
let oa_n = match input_as_number(&self.oa) {
Some(n) => n,
None => return,
};
let oa = oa_n.value;
let ra = match input_as_number_matching(&self.ra, oa_n.unit) {
Some(v) => v,
None => return,
};
let high_limit =
input_as_number_matching(&self.high_limit, oa_n.unit).unwrap_or(f64::INFINITY);
let deadband = input_as_number_matching(&self.deadband, oa_n.unit)
.unwrap_or(0.0)
.abs();
let current = matches!(&self.out.value, Value::Bool(b) if b.value);
let next = if oa > ra || oa > high_limit {
false
} else if oa < ra - deadband && oa < high_limit {
true
} else {
current
};
self.out.set(Bool { value: next }.into());
}
}
#[cfg(test)]
mod test {
use crate::{
base::block::Block, base::block::test_utils::write_block_inputs,
blocks::control::Economizer,
};
async fn run(oa: f64, ra: f64, high: f64, deadband: f64) -> bool {
let mut block = Economizer::new();
write_block_inputs([
(&mut block.oa, oa),
(&mut block.ra, ra),
(&mut block.high_limit, high),
(&mut block.deadband, deadband),
])
.await;
block.execute().await;
matches!(block.out.value, libhaystack::val::Value::Bool(b) if b.value)
}
#[tokio::test]
async fn test_economizer_enables_when_oa_cooler() {
assert!(run(12.0, 24.0, 25.0, 1.0).await);
}
#[tokio::test]
async fn test_economizer_disables_above_high_limit() {
assert!(!run(28.0, 30.0, 25.0, 1.0).await);
}
#[tokio::test]
async fn test_economizer_disables_when_oa_warmer() {
assert!(!run(26.0, 24.0, 30.0, 1.0).await);
}
#[tokio::test]
async fn test_economizer_holds_in_deadband() {
let mut block = Economizer::new();
write_block_inputs([
(&mut block.oa, 12.0),
(&mut block.ra, 24.0),
(&mut block.high_limit, 30.0),
(&mut block.deadband, 2.0),
])
.await;
block.execute().await;
assert!(matches!(block.out.value, libhaystack::val::Value::Bool(b) if b.value));
write_block_inputs([(&mut block.oa, 23.0)]).await;
block.execute().await;
assert!(matches!(block.out.value, libhaystack::val::Value::Bool(b) if b.value));
write_block_inputs([(&mut block.oa, 24.5)]).await;
block.execute().await;
assert!(matches!(block.out.value, libhaystack::val::Value::Bool(b) if !b.value));
write_block_inputs([(&mut block.oa, 23.0)]).await;
block.execute().await;
assert!(matches!(block.out.value, libhaystack::val::Value::Bool(b) if !b.value));
}
}