The atomic room runtime for the Plato Matrix — a universal agent-space interface.
Plato Engine Block is a Rust library that implements the concept of a Room — a self-contained unit of sensor/actuator interaction that:
- Ticks at a configurable rate (Hz), reading all sensors each tick
- Records a rolling history of tick snapshots in a circular buffer
- Evaluates alarm rules against sensor data with cooldown semantics
- Accepts a human-friendly text protocol for queries and control
- Streams live updates to subscribed TCP clients
Think of it as the smallest building block in a larger sensor/actuator mesh — a single "room" in the Plato Matrix that can be composed, replicated, and networked into arbitrarily complex systems.
┌──────────────────────────────────────────────┐
│ PlatoEngine │
│ │
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Sensors │ │Actuators │ │ Alarms │ │
│ │ (read) │ │ (write) │ │ (evaluate) │ │
│ └────┬────┘ └────┬─────┘ └─────┬──────┘ │
│ │ │ │ │
│ ▼ │ │ │
│ ┌─────────┐ │ │ │
│ │ Tick │───────┼──────────────┘ │
│ │(snapshot)│ │ │
│ └────┬────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────┐ │
│ │ History Buffer │ │
│ │ (circular, N ticks) │ │
│ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ Protocol Handler │ │
│ │ (text command parser) │ │
│ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ (server feat) │
│ │ TCP Server │ │
│ │ (tokio, multi-client) │ │
│ └─────────────────────────┘ │
└──────────────────────────────────────────────┘
| Feature | Default | Description |
|---|---|---|
std |
✅ | Standard library support (File, println, etc.) |
server |
❌ | Tokio-based TCP multi-client server |
The core engine is designed to be no_std + alloc compatible. The std flag enables convenience features, and server pulls in tokio for TCP networking.
[dependencies]
plato-engine-block = { version = "0.1", default-features = false } # no_std
plato-engine-block = "0.1" # std
plato-engine-block = { version = "0.1", features = ["server"] } # fulluse plato_engine_block::{PlatoEngine, PlatoEngineBuilder};
let mut engine = PlatoEngine::builder()
.sensor("temperature", Box::new(|| 22.5))
.sensor("humidity", Box::new(|| 45.0))
.actuator("heater", Box::new(|v| { println!("heater={}", v); true }))
.actuator("fan", Box::new(|v| { println!("fan={}", v); true }))
.alarm(
"overheat",
Box::new(|data| {
data.iter().any(|(name, val)| name == "temperature" && *val > 30.0)
}),
5, // cooldown: 5 ticks between fires
)
.tick_hz(10.0) // 10 ticks per second
.history_capacity(1000) // remember last 1000 ticks
.build();// Take one tick — reads all sensors, evaluates alarms, pushes to history
let tick = engine.tick();
println!("Tick {}: temp={:?}", tick.index, tick.get("temperature"));// Query and control via text commands
let response = engine.handle_command("tick");
// → "tick 0 @ 0.000s\n temperature = 22.5000\n humidity = 45.0000"
let response = engine.handle_command("history 5");
// → Shows last 5 ticks
let response = engine.handle_command("heater 75.0");
// → "heater ← 75.0000"
let response = engine.handle_command("subscribe");
// → "subscribed"
let response = engine.handle_command("help");
// → Shows all available commandsuse plato_engine_block::server::run_server;
#[tokio::main]
async fn main() {
let engine = PlatoEngine::builder()
.sensor("temp", Box::new(|| read_temperature()))
.tick_hz(1.0)
.history_capacity(100)
.build();
let (handle, _join) = run_server(engine, "0.0.0.0:7070")
.await
.unwrap();
// Broadcast custom messages to all subscribers
handle.broadcast("System online".to_string());
}| Command | Description |
|---|---|
tick |
Take one tick, read all sensors |
history [N] |
Show last N ticks (default 10) |
<actuator> <value> |
Set named actuator to floating-point value |
alarm list |
List all alarm rules and their states |
subscribe |
Subscribe to live tick/alarm broadcasts |
unsubscribe |
Unsubscribe from broadcasts |
help |
Show command reference |
Alarms evaluate a user-defined condition against each tick's sensor data. They follow a state machine:
Idle → Active (condition met) → Cooldown (condition cleared) → Idle
↑ |
└─────────── condition re-met after cooldown ───────────┘
- Cooldown prevents alarm spam: after an alarm fires, it won't re-fire for N ticks.
- Active state persists as long as the condition remains true.
- Cooldown begins when the condition transitions from true to false.
.alarm(
"low_battery",
Box::new(|data| {
data.iter().any(|(n, v)| n == "battery" && *v < 10.0)
}),
100, // cooldown: 100 ticks (~10 seconds at 10Hz)
)The circular buffer provides efficient O(1) push and O(N) query:
- Latest: constant-time access to the most recent tick
- Query(N): returns the last N ticks in chronological order
- Overflow: oldest ticks are automatically evicted when capacity is reached
engine.tick();
engine.tick();
engine.tick();
let latest = engine.latest().unwrap();
let recent = engine.history(3);
assert_eq!(recent.len(), 3);The core engine works without the standard library, requiring only alloc:
// In your Cargo.toml:
// plato-engine-block = { version = "0.1", default-features = false }
// In your no_std crate:
#![no_std]
use plato_engine_block::{PlatoEngine, PlatoEngineBuilder};
// Builder, ticking, history, alarms, and protocol all work in no_stdSimplicity first. This is an engine block — the fundamental, boring, reliable core that everything else bolts onto. No async runtime required for the core. No framework opinions. Just sensors, actuators, alarms, history, and a text protocol.
Composable. A single engine block represents one room/device/agent. Stack them, network them, orchestrate them with whatever higher-level system you want.
Observable. Every tick is recorded. Every alarm is tracked. The text protocol means you can nc localhost 7070 and interact with a running room in real time.
Embedded-friendly. no_std + alloc means this runs on microcontrollers, WASM, or anywhere Rust's allocator works.
src/
├── lib.rs — Re-exports, crate docs, test suite
├── engine.rs — PlatoEngine struct, PlatoEngineBuilder
├── tick.rs — Tick struct (timestamp + sensor data)
├── sensor.rs — Sensor trait, SensorSpec, SensorFn
├── actuator.rs — Actuator trait, ActuatorSpec, ActuatorFn
├── alarm.rs — AlarmRule, AlarmState, condition evaluation
├── history.rs — Circular buffer of Ticks, query API
├── protocol.rs — Text command parser, response formatter
└── server.rs — Tokio TCP server (behind "server" feature)
MIT