A high-performance, embeddable Rust control engine that natively executes the OBC / LBL Control Description Language (CDL) for smart-building equipment control sequences.
CDL is a declarative, object-oriented language — a strict subset of Modelica — that expresses building control logic as block diagrams. Its determinism contract (CDL §7.16: synchronous data flow + single assignment) means identical inputs and parameters yield identical outputs, which makes the Open Control Engine a valid executable specification for commissioning and continuous functional verification — the same control sequence, run bit-for-bit reproducibly.
It is designed to be the core engine under the hood of larger building-control products: the public API and core semantics stay small, embeddable, and stable.
The architecture specification is the design of record and is complete; the implementation is progressing milestone by milestone, each gated by extensive tests.
| Milestone | Scope | State |
|---|---|---|
| M0 | Deterministic execution core — workspace, types, scheduler, tick loop | ✅ done |
| M1 | CXF ingest, database-free, end-to-end load → simulate | ✅ done |
| M2 | CDL block-library breadth (G36) + conformance/oracle + parameter validation | ✅ done (in-repo) |
| M3 | Durability through the oce-store port (reference adapter) |
⬜ planned |
| M4 | Docs/point-list export, model-from-semantic, hardening, and oce-py PyO3 bindings (MVP) |
⬜ planned |
| M5 | Python free-threading (gil_used = false) + free-threaded wheels + first PyPI publish |
⬜ planned |
Today the engine loads representative ASHRAE Guideline 36 sequences (AHU supply-air-temperature reset, AHU economizer, single-zone VAV) from CXF and simulates them end-to-end through the frozen facade, against ~70 CDL elementary blocks, with a self-contained schedule cross-check oracle, whole-sequence bit-exact determinism goldens, an independent closed-form conformance oracle, and block-parameter validation. External Modelica/Buildings reference cross-checks are a deliberately deferred tail. The project is pre-1.0 and not yet published to crates.io.
CDL §7.17 states that point lists, trends, display units, tags, and all Brick / Haystack / ASHRAE 223P semantics do not affect the computation of a control signal. That single rule is the cleanest seam in the system, and the engine is built around it.
- An execution core — a small, deterministic, in-memory dataflow machine that sees only blocks, typed connections, and values. This is the hot path. It has zero dependency on any database.
- A storage layer behind a trait — everything the evaluator must not read (equipment topology,
points, instance structure, parameters, trends, semantic triples) plus durable persistence — is
reached only through the
oce-storeport traits. The library ships no first-party database; durable/queryable backends are app-side adapters behind the port, with an in-memory default (oce-store-mem).
A downstream project can embed the engine for load → flatten → validate → schedule → tick → simulate with no database at all.
The engine is, by design:
- Library-only — no
main, no daemon, no server, no network listener. The host owns process lifecycle, transport, TLS, authN/Z, multi-tenancy, off-host durability, and metrics export. - Synchronous, in-process — every public method is a blocking synchronous call. No async runtime is pulled at any layer.
#![forbid(unsafe_code)]in every crate.- edition 2024, Rust 1.95.0 (pinned in
rust-toolchain.toml),resolver = "3". - Deterministic on the tick — a frozen, topologically-sorted schedule evaluated over flat arrays: no graph walks, no hashing, no allocation, no I/O, and no store access on the hot path.
The facade crate is
oce-api, published under the umbrella nameopen-control-engine. Until the first crates.io release, depend on it via git:
[dependencies]
oce-api = { git = "https://github.com/jscott3201/open-control-engine" }A minimal embed — load a CDL sequence from CXF and simulate it (illustrative sketch):
use oce_api::{CollectSpec, Engine, InputSource, SimSpec, Value};
// 1. An engine with the default in-memory store — no database.
let mut engine = Engine::in_memory();
// 2. Load a CDL sequence from CXF (JSON-LD): parse, validate, freeze the schedule.
engine.load_cxf(cxf_bytes)?;
// 3. Simulate: feed inputs per tick, collect named outputs.
let metrics = engine.simulate(&SimSpec {
t_start: 0.0,
t_stop: 4.0,
step: 1.0,
inputs: InputSource::Closure(Box::new(|t| {
vec![("zone_temp".to_string(), Value::Real(22.0 + t))]
})),
collect: CollectSpec::Named { points: vec!["sat_setpoint".to_string()], stride: 1 },
})?;The dependency direction is intentional and acyclic, organized around the seam above.
Execution core (Group A — no store, no database):
| Crate | Responsibility |
|---|---|
oce-model |
Pure value/connector/instance/connection types; the Value enum (Real/Integer/Boolean/String/Enum) and the flattened model graph — the shared executable truth. |
oce-expr |
The CDL §7.7.2 binding-expression parser/evaluator (closed-world; pure, total). |
oce-blocks |
The Block trait and the native CDL elementary-block library (stateless [A] / stateful [S]). |
oce-flatten |
Elaboration / CXF-path resolution (CXF arrives pre-flattened; full .mo flattening is deferred). |
oce-validate |
Loader conformance: subset rejection, single-assignment, type/attribute unification, parameter rules. |
oce-graph |
The deterministic scheduler/executor: direct-feedthrough DAG, algebraic-loop rejection, own Kahn topological sort, the tick loop. |
oce-cxf |
CXF (Control eXchange Format) JSON-LD import/export ↔ the model graph. |
oce-semantics |
Vendor-annotation parsing → effective (non-computational) point/trend/semantic metadata. |
oce-diag |
The shared diagnostic vocabulary (Severity / DiagCode / Diagnostic) across the ingest path. |
Storage ports (the seam — traits only, no database types):
| Crate | Responsibility |
|---|---|
oce-store |
The seam. The ModelStore / PointStore / SemanticStore traits + DTOs. No database types. |
oce-store-mem |
The default in-memory backend, so the engine runs with no database. |
Verification, externals & host facade:
| Crate | Responsibility |
|---|---|
oce-conformance |
The funnel-style tolerance-band / golden-trace conformance harness. |
oce-extension |
The FMI / extension-block boundary (v1 surfaces extension blocks as unresolved externals). |
oce-docs |
Sequence-spec (Word/HTML) and point-list document export. |
oce-api |
The embeddable host facade: Engine<S: Store = MemStore> — the single public surface. Published as open-control-engine. |
This engine controls real equipment, so a wrong result is a physical hazard — testing is a
first-class deliverable, not an afterthought. Every change ships extensive edge-case tests, golden
tests (checked-in expected outputs compared bit-exactly), oracle cross-checks (results compared
against independently-derived references), and determinism goldens. See
TESTING.md for the full standard.
cargo-nextest is the test runner:
cargo nextest run # unit + integration tests
cargo test --doc # doctests (nextest does not run these)CI is dev-light / release-heavy: per-PR gates into development run fmt / clippy / build /
rustdoc / file-size / no-secret / database-free checks; the full test suite runs on
development → main release gates.
cargo build --workspace # the engine only — no database, no async runtimeoce-api exposes one feature today: default = ["mem"], which wires the in-memory store as the
default Store backend. Durable/queryable backends are the consuming application's responsibility,
authored app-side as an adapter behind the oce-store port.
Install the shared git hooks once after cloning (fast format/lint/no-DB gates on commit and push):
bash scripts/install-hooks.shChanges land via pull requests into the development branch, behind the CI gate in
.github/workflows/ci.yml. Contributions are welcome — see
CONTRIBUTING.md, and CHANGELOG.md for notable changes.
Dual-licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual-licensed as above, without any additional terms or conditions.