Skip to main content

plato_engine_block/
engine.rs

1//! Engine — the core PlatoEngine struct.
2
3use crate::actuator::ActuatorSpec;
4use crate::alarm::AlarmRule;
5use crate::history::HistoryBuffer;
6use crate::sensor::SensorSpec;
7use crate::tick::Tick;
8
9/// The core engine: sensors, actuators, history, alarms.
10pub 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    /// Alarm names that fired on the last tick (consumed by protocol handler).
20    pub alarm_fires: Vec<String>,
21}
22
23impl PlatoEngine {
24    /// Create a new builder.
25    pub fn builder() -> PlatoEngineBuilder {
26        PlatoEngineBuilder::default()
27    }
28
29    /// Take one tick: read sensors, push to history, evaluate alarms.
30    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        // Evaluate alarms before pushing
34        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    /// Get the latest tick.
46    pub fn latest(&self) -> Option<&Tick> {
47        self.history.latest()
48    }
49
50    /// Get the last `n` ticks from history.
51    pub fn history(&self, n: usize) -> Vec<&Tick> {
52        self.history.query(n)
53    }
54
55    /// Set an actuator by name.
56    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    /// Subscribe to live updates.
66    pub fn subscribe(&mut self) {
67        self.streaming = true;
68    }
69
70    /// Unsubscribe from live updates.
71    pub fn unsubscribe(&mut self) {
72        self.streaming = false;
73    }
74
75    /// Handle a text command.
76    pub fn handle_command(&mut self, command: &str) -> String {
77        crate::protocol::ProtocolHandler::handle(self, command)
78    }
79}
80
81/// Builder for PlatoEngine.
82#[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    /// Add a sensor with a callback.
97    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    /// Add an actuator with a callback.
103    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    /// Add an alarm rule.
113    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    /// Set tick rate in Hz.
125    pub fn tick_hz(mut self, hz: f64) -> Self {
126        self.tick_hz = hz;
127        self
128    }
129
130    /// Set history buffer capacity.
131    pub fn history_capacity(mut self, capacity: usize) -> Self {
132        self.history_capacity = capacity;
133        self
134    }
135
136    /// Build the engine.
137    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}