Data Model

The hierarchical entity tree, schemas, fields, types, and inheritance.

ControlBird is built on a single, unified data model: a hierarchical, schema-driven entity database held in memory by the Store. Every device reading, configuration value, mapping rule, and service state is an entity: a typed node in a tree. Entities are connected by parent/child relationships, described by reusable schemas, and composed from a small set of strongly typed field values. Understanding this model is the foundation for everything else: configuring protocols, writing automation, and reacting to changes.

The Entity Tree

All entities live in one hierarchy rooted at Root. Every entity except the root has exactly one parent and may have any number of children. Two structural fields express this relationship and always exist on every entity:

  • Parent: an EntityReference pointing at the single parent entity.
  • Children: an EntityList holding an ordered set of child entities.
  • Name: the entity's local name, unique among its siblings.

Because the tree is addressable, entities also have a path: a forward-slash notation that walks from the root to the entity, for example Root/Devices/ModbusController1. Paths are how entities are referenced in configuration and how tooling addresses entities.

Structural fields are special

Name, Parent, and Children are handled specially by the Store and always exist. Parent is a single EntityReference; Children is an EntityList. Every other field is optional and falls back to a schema-defined default.

Schemas and Inheritance

Each entity type is defined by a schema that declares its fields, their value types, default values, and an optional parent type to inherit from. Schemas form an inheritance chain: a concrete type pulls in every field declared by its ancestors. The canonical example is the protocol stack:

Object
  └── ProtocolController
        └── ModbusController

Object is the root of the type hierarchy: every entity inherits from it directly or indirectly. It contributes the universal fields Name, Description, Faceplates, Children, and Parent. ProtocolController adds protocol concerns such as Disabled, EndpointConnectionMode, and State. ModbusController is concrete and inherits all of those fields without redeclaring them.

Common base types

TypeRoleRepresentative fields
ObjectRoot of the type hierarchy; base for everythingName, Description, Faceplates, Children, Parent
ProtocolControllerBase for protocol integrations (Modbus, MQTT, OPC UA)Disabled, EndpointConnectionMode, State
ProtocolMapperMaps a source path to a target entity fieldDirection, PollIntervalMs, ReadMode, SourcePath, TargetEntity, TargetField
ProtocolEndpointA protocol connection endpointCertificate, TlsEnabled, MaxRetryBackoffMs

Inheritance comes from the schema

Field inheritance is defined by the schema: a concrete type pulls in every field declared by its ancestors without redeclaring them. Type hierarchies and field schemas are part of a deployment's configuration rather than something changed on a live system.

Field Value Types

Every field holds a value of one of the following strongly typed kinds.

TypeDescription
BlobRaw bytes
BoolTrue or false
ChoiceOne of a fixed set of named options
EntityListAn ordered list of entity references
EntityReferenceA reference to a single other entity (enables field indirection)
Float64-bit IEEE 754 floating-point number
Int64-bit signed integer
StringUTF-8 text
TimestampAn ISO 8601 instant
DecimalExact base-10 decimal for monetary and precise values

Choice fields

A Choice value is one of a fixed set of named options defined by the schema. For example, a RegisterType field offers options such as Coil and DiscreteInput, and an AlarmBehavior.DisplayRule field offers options such as None, StateChange, and Acknowledged. You select one of the named options for the field.

Identity

Every entity carries a stable runtime identity and knows its own type. You rarely work with raw identifiers directly: configuration and tooling address entities by path, and the Store resolves those paths to the right entity when it loads. Because each entity's type travels with its identity, the Store can filter entities by type and route change notifications efficiently.

Storage Scopes

Every field has a storage scope that determines whether its value survives a restart:

  • Configuration: persisted. These fields are saved as part of the deployment's configuration and reloaded on startup. Examples include a controller's Name or a mapper's PollIntervalMs.
  • Runtime: transient. These fields live only in memory and are lost on restart. Examples include a controller's live State or a service instance's availability signal.

The scope is defined per field by the schema. Every Configuration-scoped field has a schema-defined default, so a configuration field always loads with a valid value.

Field Indirection

Because relationships are first-class EntityReference and EntityList values, a single read can traverse multiple hops. You pass a path of field types, and the Store dereferences each reference in turn, atomically. To read the name of an entity's parent you pass [Parent, Name]: the Store resolves the Parent reference, then reads Name on the target. This same mechanism powers atomic notification context. See Notifications & Reactivity.

Authoring Configuration

You build the entity tree and its schemas through the platform's configuration tooling. Entities are addressed by path, and a mapper wires a source to a target by referencing the target entity's path. For example, a ProtocolMapper configured with a Direction of Read, a TargetEntity of Root/Devices/Device1, a PollIntervalMs of 1000, and a ReadMode of Subscribe reads its source on a one-second interval and writes the value into the target entity. When the configuration loads, the Store resolves each path to its live entity.

Limitations to Know

  • Entity type hierarchies and field schemas are part of a deployment's configuration and are not changed on a live, running system.
  • Choice values must be one of the named options defined by the schema.
  • EntityReference and EntityList fields reference other entities by path and resolve to live entities at load time. Circular references are not allowed.
  • Every Configuration-scoped field has a schema-defined default, so a configuration field always loads with a valid value.

With the data model in hand, the next step is to see how the Store turns writes into live events. Continue to Notifications & Reactivity, or jump ahead to configuring your first entities.