Skip to content

SuperInstance/plato-engine-block

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Plato Engine Block

The atomic room runtime for the Plato Matrix — a universal agent-space interface.

What Is This?

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.

Architecture

┌──────────────────────────────────────────────┐
│                 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 Flags

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"] }      # full

Quick Start

Building an Engine

use 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();

Ticking

// Take one tick — reads all sensors, evaluates alarms, pushes to history
let tick = engine.tick();
println!("Tick {}: temp={:?}", tick.index, tick.get("temperature"));

Text Protocol

// 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 commands

TCP Server (with server feature)

use 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());
}

Text Protocol Reference

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

Alarm System

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)
)

History Buffer

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);

no_std Support

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_std

Design Philosophy

Simplicity 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.

Module Layout

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)

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages