Skip to content

Architecture Overview

OpenCrate BMS is structured as a layered, event-driven platform. Every subsystem communicates through a central event bus, stores manage their own SQLite databases on dedicated threads, and the entire runtime is assembled by a single init_platform call.

Three-Layer Model

The platform is organized into three layers, each building on the one below it.

Layer 1: ModelState

The foundation. Owns the core data structures that every other layer reads and writes.

Component Purpose
PointStore In-memory thread-safe point values and status
NodeStore Unified object model (sites, spaces, equipment, points) with hot cache + SQLite
EventBus Broadcast channel (4096 capacity) distributing Arc<Event> to all subscribers
PluginRegistry Protocol plugins, history backends, alarm evaluators registered at startup
LoadedScenario The resolved scenario config and device profiles
HealthRegistry Thread-safe health status map (Healthy, Degraded, Down, Unknown)

Layer 2: AutomationState

Fourteen domain-specific stores, each running its own SQLite database on a dedicated thread. These stores implement the building automation logic: alarms, schedules, history, energy analytics, fault detection, reporting, and integrations.

Store Domain
alarm_store Alarm lifecycle and acknowledgment
schedule_store Time-based scheduling
history_store Point value history (trend data)
entity_store Legacy entity persistence
discovery_store Protocol device/point discovery state
program_store Logic engine programs and execution logs
notification_store Alarm routing recipients, rules, shelving
mqtt_store MQTT broker configs and topic patterns
commissioning_store Device commissioning sessions and checklists
report_store Report definitions, schedules, executions
energy_store Meters, rates, baselines, rollups
webhook_store Webhook endpoints and delivery logs
fdd_store Fault detection rules and fault history
export_store External export targets (InfluxDB, Postgres)

Layer 3: Platform

The assembled runtime. init_platform wires everything together and returns:

  • Platform containing ModelState, AutomationState, and the DiscoveryService
  • BridgeHandles containing active protocol bridges (BACnet networks, Modbus bridges)

For concurrent access from the API server and GUI, Platform is wrapped in SharedPlatform -- a Clone-able struct that includes a BridgeRegistry mapping protocol strings to Box<dyn ProtocolBridgeHandle>.

Event-Driven Design

The EventBus is the nervous system. When a point value changes, an alarm fires, or a device goes offline, an event is broadcast to all subscribers. No subsystem polls another -- they react.

Six permanent subscribers run as background tasks:

Subscriber Reacts To Action
AlarmRouter AlarmRaised, AlarmCleared, AlarmAcknowledged Dispatches notifications (email, SMS, webhook)
MqttPublisher ValueChanged, AlarmRaised, AlarmCleared, DeviceDown, DeviceDiscovered Publishes JSON to MQTT brokers
WebhookDispatcher AlarmRaised, AlarmCleared, AlarmAcknowledged, DeviceDown, DeviceRecovered, FddFaultRaised, FddFaultCleared Sends HTTP POSTs to registered endpoints
ExportPublisher ValueChanged, AlarmRaised, AlarmCleared Writes to InfluxDB and/or Postgres
FddEngine ValueChanged Evaluates fault detection rules
ExecutionEngine ValueChanged (OnChange triggers) Runs logic programs

Module Map

src/
  platform.rs          # init_platform, Platform, SharedPlatform
  lib.rs               # Feature gates, public API surface

  event/
    bus.rs             # EventBus, Event enum (18 variants)

  node/
    mod.rs             # Node, NodeType, NodeCapabilities, ProtocolBinding

  store/               # 20 stores (mpsc + SQLite pattern)
    point_store.rs     # In-memory point values
    node_store.rs      # Node hierarchy (hot cache + SQLite)
    history_store.rs   # Trend data
    alarm_store.rs     # Alarms
    schedule_store.rs  # Schedules
    entity_store.rs    # Legacy entities
    discovery_store.rs # Discovery state
    program_store.rs   # Logic programs
    notification_store.rs
    mqtt_store.rs
    commissioning_store.rs
    report_store.rs
    energy_store.rs
    webhook_store.rs
    fdd_store.rs
    export_store.rs
    user_store.rs
    audit_store.rs
    override_store.rs

  plugin/
    mod.rs             # PluginRegistry, traits

  bridge/
    traits.rs          # PointSource trait
    bacnet.rs          # BACnet bridge (multi-network)
    modbus.rs          # Modbus bridge (TCP + RTU)
    backoff.rs         # Shared exponential backoff

  protocol/
    mod.rs             # RawProtocolValue, ProtocolDriver, ValueSink, ProfileNormalizer

  discovery/
    mod.rs             # DiscoveryService, DiscoveredDevice, DiscoveredPoint
    bacnet_adapter.rs  # BACnet -> generic discovery
    bacnet_units.rs    # ASHRAE unit mapping

  api/
    mod.rs             # Axum router, ApiState, middleware
    routes/            # 17 route modules

  gui/
    app.rs             # Dioxus desktop app
    state.rs           # AppState
    components/        # UI components

  logic/
    mod.rs             # Block types, DAG model
    compiler.rs        # Topological sort -> Rhai codegen
    engine.rs          # ExecutionEngine (periodic + event-driven)
    store.rs           # ProgramStore

  notification/
    router.rs          # AlarmRouter
    channels/          # Email, SMS, webhook channels

  mqtt/
    publisher.rs       # MqttPublisher
    topic.rs           # Topic template engine

  webhook/
    dispatcher.rs      # WebhookDispatcher
    model.rs           # Endpoint, delivery models
    providers.rs       # Slack, Teams, PagerDuty, ntfy formatters

  reporting/
    engine.rs          # ReportEngine (7 section types)
    renderer.rs        # HTML renderer (inline CSS, dark theme)
    templates.rs       # 4 built-in templates

  energy/
    consumption.rs     # Trapezoidal kW -> kWh
    demand.rs          # 15-min sliding window peak
    degree_days.rs     # HDD/CDD regression
    cost.rs            # Rate structures
    rollup.rs          # Daily/monthly orchestration
    scheduler.rs       # 15-min background cycle

  fdd/                 # Fault Detection & Diagnostics
  export/              # External data export (InfluxDB, Postgres)

  config/
    profile.rs         # DeviceProfile, Point definitions
    scenario.rs        # ScenarioConfig, DeviceInstance
    loader.rs          # resolve_scenario()
    template.rs        # SystemTemplate, auto_create_nodes, auto-tagging

  auth.rs              # Permissions, JWT, roles

Design Principles

No dynamic loading. All plugins and protocol drivers are registered at compile time via the PluginRegistry. This keeps the binary self-contained and avoids unsafe FFI.

One store, one database. Each store owns a single SQLite file. No store reads another store's database. Cross-store coordination happens through the EventBus.

Zero-copy events. Events are wrapped in Arc<Event> so broadcast to N subscribers does not clone payloads.

Graceful degradation. HealthRegistry tracks per-component status. If a BACnet network goes down, the rest of the platform continues operating. Bridge reconnection uses exponential backoff.

Feature gates. The API server (api feature) and GUI (gui feature) are optional. The core platform can run headless.