plato_engine_block/
engine.rs1use crate::actuator::ActuatorSpec;
4use crate::alarm::AlarmRule;
5use crate::history::HistoryBuffer;
6use crate::sensor::SensorSpec;
7use crate::tick::Tick;
8
9pub struct PlatoEngine {
11 pub sensors: Vec<SensorSpec>,
12 pub actuators: Vec<ActuatorSpec>,
13 pub alarms: Vec<AlarmRule>,
14 pub history: HistoryBuffer,
15 pub tick_hz: f64,
16 pub tick_index: u64,
17 pub start_time: f64,
18 pub streaming: bool,
19 pub alarm_fires: Vec<String>,
21}
22
23impl PlatoEngine {
24 pub fn builder() -> PlatoEngineBuilder {
26 PlatoEngineBuilder::default()
27 }
28
29 pub fn tick(&mut self) -> &Tick {
31 let ts = self.start_time + self.tick_index as f64 / self.tick_hz.max(0.001);
32 let tick = Tick::from_sensors(self.tick_index, ts, &self.sensors);
33 let data_slice: Vec<(String, f64)> = tick.data.clone();
35 for alarm in &mut self.alarms {
36 if alarm.evaluate(&data_slice, self.tick_index) {
37 self.alarm_fires.push(alarm.name.clone());
38 }
39 }
40 self.tick_index += 1;
41 self.history.push(tick);
42 self.history.latest().unwrap()
43 }
44
45 pub fn latest(&self) -> Option<&Tick> {
47 self.history.latest()
48 }
49
50 pub fn history(&self, n: usize) -> Vec<&Tick> {
52 self.history.query(n)
53 }
54
55 pub fn set_actuator(&mut self, name: &str, value: f64) -> Result<bool, String> {
57 for act in &self.actuators {
58 if act.name == name {
59 return Ok(act.set(value));
60 }
61 }
62 Err(format!("unknown actuator: {}", name))
63 }
64
65 pub fn subscribe(&mut self) {
67 self.streaming = true;
68 }
69
70 pub fn unsubscribe(&mut self) {
72 self.streaming = false;
73 }
74
75 pub fn handle_command(&mut self, command: &str) -> String {
77 crate::protocol::ProtocolHandler::handle(self, command)
78 }
79}
80
81#[derive(Default)]
83pub struct PlatoEngineBuilder {
84 sensors: Vec<SensorSpec>,
85 actuators: Vec<ActuatorSpec>,
86 alarms: Vec<AlarmRule>,
87 tick_hz: f64,
88 history_capacity: usize,
89}
90
91impl PlatoEngineBuilder {
92 pub fn new() -> Self {
93 Self::default()
94 }
95
96 pub fn sensor(mut self, name: impl Into<String>, callback: crate::sensor::SensorFn) -> Self {
98 self.sensors.push(SensorSpec::new(name, callback));
99 self
100 }
101
102 pub fn actuator(
104 mut self,
105 name: impl Into<String>,
106 callback: crate::actuator::ActuatorFn,
107 ) -> Self {
108 self.actuators.push(ActuatorSpec::new(name, callback));
109 self
110 }
111
112 pub fn alarm(
114 mut self,
115 name: impl Into<String>,
116 condition: crate::alarm::AlarmCondition,
117 cooldown_ticks: u64,
118 ) -> Self {
119 self.alarms
120 .push(AlarmRule::new(name, condition, cooldown_ticks));
121 self
122 }
123
124 pub fn tick_hz(mut self, hz: f64) -> Self {
126 self.tick_hz = hz;
127 self
128 }
129
130 pub fn history_capacity(mut self, capacity: usize) -> Self {
132 self.history_capacity = capacity;
133 self
134 }
135
136 pub fn build(self) -> PlatoEngine {
138 let tick_hz = if self.tick_hz > 0.0 { self.tick_hz } else { 1.0 };
139 let history_capacity = if self.history_capacity > 0 {
140 self.history_capacity
141 } else {
142 100
143 };
144 PlatoEngine {
145 sensors: self.sensors,
146 actuators: self.actuators,
147 alarms: self.alarms,
148 history: HistoryBuffer::new(history_capacity),
149 tick_hz,
150 tick_index: 0,
151 start_time: 0.0,
152 streaming: false,
153 alarm_fires: Vec::new(),
154 }
155 }
156}