#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[cfg(test)]
extern crate serde_json;
use std::{
collections::HashMap,
io::{Error, ErrorKind, Result},
ops::Not,
time::Duration,
};
mod comparison;
mod entities;
pub mod fsm;
mod parser;
mod runtime;
pub mod util;
mod value;
pub use self::{comparison::*, entities::*, runtime::*, value::*};
pub mod pid;
pub mod bang_bang;
pub trait Controller<Input, Output> {
fn next(&mut self, input: Input) -> Output;
}
pub trait PureController<Input, Output> {
fn next(&self, input: Input) -> Output;
}
pub trait TimeStepController<Input, Output> {
fn next(&mut self, input: Input, delta_t: &Duration) -> Output;
}
impl<I, O, C> TimeStepController<I, O> for C
where
for<'a> C: Controller<(I, &'a Duration), O>,
{
fn next(&mut self, input: I, delta_t: &Duration) -> O {
(self as &mut Controller<(I, &Duration), O>).next((input, delta_t))
}
}
pub trait SyncIoSystem {
fn read(&mut self, id: &str) -> Result<Value>;
fn read_output(&mut self, id: &str) -> Result<Option<Value>>;
fn write(&mut self, id: &str, value: &Value) -> Result<()>;
}
#[derive(Debug, Clone)]
pub enum ControllerType {
Pid(pid::Pid),
BangBang(bang_bang::BangBang),
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ControllerConfig {
Pid(pid::PidConfig),
BangBang(bang_bang::BangBangConfig),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ControllerState {
Pid(pid::PidState),
BangBang(bang_bang::BangBangState),
}
impl<'a>
PureController<
(&'a ControllerState, &'a IoState, &'a Duration),
Result<(ControllerState, IoState)>,
> for Loop
{
fn next(
&self,
input: (&ControllerState, &IoState, &Duration),
) -> Result<(ControllerState, IoState)> {
let (controller, io, dt) = input;
if self.inputs.len() != 1 || self.outputs.len() != 1 {
return Err(Error::new(
ErrorKind::Other,
"Loop has invalid length of inputs/outputs",
));
}
let input_id = &self.inputs[0];
if let Some(Value::Decimal(v)) = io.inputs.get(input_id) {
let mut io = io.clone();
let output_id = self.outputs[0].clone();
match self.controller {
ControllerConfig::Pid(ref cfg) => match controller {
ControllerState::Pid(s) => {
let (pid_state, y) = cfg.next((*s, *v, dt));
io.outputs.insert(output_id, y.into());
let controller = ControllerState::Pid(pid_state);
Ok((controller, io))
}
_ => Err(Error::new(
ErrorKind::InvalidData,
"Invalid controller state: a PID state is is required",
)),
},
ControllerConfig::BangBang(ref cfg) => match controller {
ControllerState::BangBang(s) => {
let bb_state = cfg.next((*s, *v));
io.outputs.insert(output_id, bb_state.current.into());
let controller = ControllerState::BangBang(bb_state);
Ok((controller, io))
}
_ => Err(Error::new(
ErrorKind::InvalidData,
"Invalid controller state: a BangBang state is is required",
)),
},
}
} else {
Err(Error::new(
ErrorKind::InvalidData,
"Invalid input data type: a decimal value is required",
))
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IoState {
pub inputs: HashMap<String, Value>,
pub outputs: HashMap<String, Value>,
pub mem: HashMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SystemState {
pub io: IoState,
pub setpoints: HashMap<String, Value>,
pub controllers: HashMap<String, ControllerState>,
pub inactive_loops: Vec<String>,
pub state_machines: HashMap<String, String>,
pub rules: HashMap<String, bool>,
pub timeouts: HashMap<String, Value>,
}
impl SystemState {
pub fn get<'a>(&'a self, src: &'a Source) -> Option<&'a Value> {
use crate::Source::*;
match src {
In(id) => self.io.inputs.get(id),
Out(id) => self.io.outputs.get(id),
Mem(id) => self.io.mem.get(id),
Timeout(id) => self.timeouts.get(id),
Const(v) => Some(v),
Setpoint(id) => self.setpoints.get(id),
}
}
}
impl Default for SystemState {
fn default() -> Self {
SystemState {
io: IoState::default(),
setpoints: HashMap::new(),
controllers: HashMap::new(),
inactive_loops: vec![],
state_machines: HashMap::new(),
rules: HashMap::new(),
timeouts: HashMap::new(),
}
}
}
impl Default for IoState {
fn default() -> Self {
IoState {
inputs: HashMap::new(),
outputs: HashMap::new(),
mem: HashMap::new(),
}
}
}
impl SyncIoSystem for IoState {
fn read(&mut self, id: &str) -> Result<Value> {
Ok(self
.inputs
.get(id)
.ok_or_else(|| Error::new(ErrorKind::NotFound, "no such input"))?
.clone())
}
fn read_output(&mut self, id: &str) -> Result<Option<Value>> {
Ok(self.outputs.get(id).cloned())
}
fn write(&mut self, id: &str, v: &Value) -> Result<()> {
self.outputs.insert(id.into(), v.clone());
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Source {
In(String),
Out(String),
Mem(String),
Setpoint(String),
Timeout(String),
Const(Value),
}
impl Source {
pub fn cmp_eq(self, right: Source) -> Comparison {
self.cmp(right, Comparator::Equal)
}
pub fn cmp_le(self, right: Source) -> Comparison {
self.cmp(right, Comparator::LessOrEqual)
}
pub fn cmp_ge(self, right: Source) -> Comparison {
self.cmp(right, Comparator::GreaterOrEqual)
}
pub fn cmp_ne(self, right: Source) -> Comparison {
self.cmp(right, Comparator::NotEqual)
}
pub fn cmp_lt(self, right: Source) -> Comparison {
self.cmp(right, Comparator::Less)
}
pub fn cmp_gt(self, right: Source) -> Comparison {
self.cmp(right, Comparator::Greater)
}
fn cmp(self, right: Source, cmp: Comparator) -> Comparison {
Comparison {
left: self,
cmp,
right,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BoolExpr<T> {
True,
False,
And(Box<BoolExpr<T>>, Box<BoolExpr<T>>),
Or(Box<BoolExpr<T>>, Box<BoolExpr<T>>),
Not(Box<BoolExpr<T>>),
Eval(T),
}
pub trait Evaluation<In> {
type Output;
fn eval(&self, input: &In) -> Result<Self::Output>;
}
pub trait Sources {
fn sources(&self) -> Vec<Source>;
}
impl Sources for BoolExpr<Comparison> {
fn sources(&self) -> Vec<Source> {
use crate::BoolExpr::*;
match self {
And(ref a, ref b) | Or(ref a, ref b) => {
let mut srcs = a.sources();
srcs.append(&mut b.sources());
srcs
}
Not(ref x) => x.sources(),
Eval(ref x) => vec![x.left.clone(), x.right.clone()],
True | False => vec![],
}
}
}
impl<T> Evaluation<SystemState> for BoolExpr<T>
where
T: Evaluation<SystemState, Output = bool>,
{
type Output = bool;
fn eval(&self, state: &SystemState) -> Result<Self::Output> {
use crate::BoolExpr::*;
match self {
True => Ok(true),
False => Ok(false),
And(ref a, ref b) => Ok(a.eval(state)? && b.eval(state)?),
Or(ref a, ref b) => Ok(a.eval(state)? || b.eval(state)?),
Not(ref x) => Ok(!x.eval(state)?),
Eval(ref x) => x.eval(state),
}
}
}
impl<T> Not for BoolExpr<T> {
type Output = Self;
fn not(self) -> Self {
BoolExpr::Not(Box::new(self))
}
}
impl<T> From<T> for Source
where
T: Into<Value>,
{
fn from(x: T) -> Source {
Source::Const(x.into())
}
}
impl From<Comparison> for BoolExpr<Comparison> {
fn from(c: Comparison) -> Self {
BoolExpr::Eval(c)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn io_state_as_sync_io_system() {
let mut io = IoState::default();
assert!(io.read("foo").is_err());
assert!(io.read_output("foo").unwrap().is_none());
assert!(io.write("foo", &Value::Decimal(3.3)).is_ok());
assert!(io.read("foo").is_err());
assert_eq!(io.read_output("foo").unwrap(), Some(Value::Decimal(3.3)));
io.inputs.insert("foo".into(), Value::Bit(true));
assert_eq!(io.read("foo").unwrap(), Value::Bit(true));
}
#[test]
fn bool_expr_eval() {
use crate::BoolExpr::*;
use crate::Source::*;
let mut state = SystemState::default();
let x_gt_5 = In("x".into()).cmp_gt(5.0.into());
let expr = Eval(x_gt_5.clone());
state.io.inputs.insert("x".into(), 5.0.into());
assert_eq!(expr.eval(&state).unwrap(), false);
let y_eq_true = In("y".into()).cmp_eq(true.into());
let expr = And(
Box::new(Eval(x_gt_5.clone())),
Box::new(Eval(y_eq_true.clone())),
);
state.io.inputs.insert("x".into(), 5.1.into());
state.io.inputs.insert("y".into(), true.into());
assert_eq!(expr.eval(&state).unwrap(), true);
state.io.inputs.insert("y".into(), false.into());
assert_eq!(expr.eval(&state).unwrap(), false);
let expr = Or(
Box::new(Eval(x_gt_5.clone())),
Box::new(Eval(y_eq_true.clone())),
);
state.io.inputs.insert("x".into(), 3.0.into());
state.io.inputs.insert("y".into(), true.into());
assert_eq!(expr.eval(&state).unwrap(), true);
state.io.inputs.insert("y".into(), false.into());
assert_eq!(expr.eval(&state).unwrap(), false);
let expr = Not(Box::new(Eval(x_gt_5)));
state.io.inputs.insert("x".into(), 6.0.into());
assert_eq!(expr.eval(&state).unwrap(), false);
let expr: BoolExpr<Comparison> = True;
assert_eq!(expr.eval(&state).unwrap(), true);
}
#[test]
fn bool_expr_sources() {
use crate::BoolExpr::*;
use crate::Source::*;
let x_gt_5 = In("x".into()).cmp_gt(5.0.into());
let expr = Eval(x_gt_5.clone());
assert_eq!(expr.sources(), vec![In("x".into()), Const(5.0.into())]);
let y_eq_z = Out("y".into()).cmp_eq(In("z".into()));
let expr = And(Box::new(Eval(x_gt_5)), Box::new(Eval(y_eq_z)));
assert_eq!(
expr.sources(),
vec![
In("x".into()),
Const(5.0.into()),
Out("y".into()),
In("z".into()),
]
);
}
#[test]
fn bool_expr_from_comparison() {
use crate::Source::*;
let x_gt_5 = In("x".into()).cmp_gt(5.0.into());
let expr = BoolExpr::from(x_gt_5.clone());
assert_eq!(expr, BoolExpr::Eval(x_gt_5));
}
#[test]
fn bool_expr_not_operation() {
use crate::Source::*;
let x_eq_1 = In("x".into()).cmp_eq(1.0.into());
let expr = BoolExpr::from(x_eq_1.clone());
let not_expr = !expr;
assert_eq!(not_expr, BoolExpr::Not(Box::new(BoolExpr::Eval(x_eq_1))));
}
#[test]
fn pure_pid_loop() {
let mut pid_cfg = pid::PidConfig::default();
pid_cfg.k_p = 2.0;
let l = Loop {
id: "pid".into(),
inputs: vec!["x".into()],
outputs: vec!["y".into()],
controller: ControllerConfig::Pid(pid_cfg),
};
let mut io = IoState::default();
io.inputs.insert("x".into(), 140.0.into());
let mut pid_state = pid::PidState::default();
pid_state.target = 150.0;
let controller = ControllerState::Pid(pid_state);
let dt = Duration::from_secs(1);
let (c, io) = l.next((&controller, &io, &dt)).unwrap();
assert_eq!(*io.outputs.get("y").unwrap(), Value::Decimal(20.0));
match c {
ControllerState::Pid(pid) => {
assert_eq!(pid.prev_value, Some(140.0));
}
_ => {
panic!("invalid controller state");
}
}
}
#[test]
fn pure_bb_loop() {
let mut bb_cfg = bang_bang::BangBangConfig::default();
bb_cfg.default_threshold = 5.0;
let l = Loop {
id: "bb".into(),
inputs: vec!["x".into()],
outputs: vec!["y".into()],
controller: ControllerConfig::BangBang(bb_cfg),
};
let mut io = IoState::default();
io.inputs.insert("x".into(), 5.1.into());
let controller = ControllerState::BangBang(bang_bang::BangBangState::default());
let dt = Duration::from_secs(1);
let (_, io) = l.next((&controller, &io, &dt)).unwrap();
assert_eq!(*io.outputs.get("y").unwrap(), Value::Bit(true));
}
#[test]
fn check_loops_inputs_and_outputs_len() {
let controller = ControllerConfig::BangBang(bang_bang::BangBangConfig::default());
let dt = Duration::from_millis(5);
let mut loop0 = Loop {
id: "foo".into(),
inputs: vec![],
outputs: vec![],
controller,
};
let mut io = IoState::default();
io.inputs.insert("input".into(), 0.0.into());
let controller = ControllerState::BangBang(bang_bang::BangBangState::default());
assert!(loop0.next((&controller, &io, &dt)).is_err());
loop0.inputs = vec!["input".into()];
assert!(loop0.next((&controller, &io, &dt)).is_err());
loop0.outputs = vec!["output".into()];
assert!(loop0.next((&controller, &io, &dt)).is_ok());
}
}