The Type System
A device type is a reusable, versioned template that declares everything Consystence needs to know about a class of equipment — its tags, alarms, commands, state machine, UI components, AI context and simulation behaviour. A device instance is that template commissioned at a specific site, with its tags mapped to real controller addresses. One type (consystence.pump.centrifugal) describes every centrifugal pump on the platform; each pump on a site is an instance of it.
This page describes the on-disk package, the manifest at its centre, the controlModel field, and how a single type decomposes into the four runtime objects. Every claim below is anchored in the real centrifugal-pump package shipped in the consystence.pumps library.
For the tag schema see Tag Schema; for the authoring format, with a worked example, see Creating a Device Type.
The package on disk
Section titled “The package on disk”A device type is a directory of YAML, distributed inside a library. The library is a directory with a library.yaml manifest plus a types/ subdirectory; each subdirectory under types/ is one device type. The real layout for the centrifugal pump:
consystence.pumps/ # library├── library.yaml # library manifest (id, version, type list)└── types/ └── consystence.pump.centrifugal/ # one device type ├── manifest.yaml # the behavioural contract (canonical) ├── simulation.yaml # physical-input model (optional) ├── components/ │ ├── faceplate.yaml # main control faceplate │ └── status-card.yaml # compact dashboard card ├── alarms/ │ └── alarms.yaml └── commands/ └── commands.yamllibrary.yaml for this library declares id: consystence.pumps, name: Consystence Industrial Equipment Library, version: "1.1.0", and a typeIds list that includes consystence.pump.centrifugal, consystence.motor.vfd, the flow/pressure/level sensors, the motorised valve, the storage tank, a PID controller and a generic sequencer. Library and type versions are independent: this library is at 1.1.0 while the pump type itself is at 1.0.0.
The five artefacts split cleanly by concern:
| Artefact | Owns |
|---|---|
manifest.yaml | Identity, control model, tags, calculations, state machine, AI context, scene bindings |
simulation.yaml | Physical-input behaviour — how sensor and external tags move when no real controller is present |
components/ | The server-driven UI tree (faceplate, status card) for this type |
alarms/alarms.yaml | Alarm definitions and conditions |
commands/commands.yaml | Operator commands and the tag writes they perform |
Alarms and commands are covered on Alarms & Commands, the simulation profile on Simulation Profiles, and the tag schema on Tag Schema.
The manifest
Section titled “The manifest”manifest.yaml is the canonical behavioural contract for the type. It is the one artefact that is the same in every deployment topology (see below): the contract is declared once. The Standard Library control blocks that implement it come from one behavioural master written in C#: the native simulator runs the master directly, ControlLogix blocks are checked against test vectors the master produces, and a generated PLCnext runtime program is built from the same source and checked with live tests on a virtual PLCnext only, with no conformance replay yet; per-block vector replay on PLCnext is planned. On controllers, conformance testing and the device and sequence layers are ControlLogix today. The aim is equivalent behaviour, not identical execution. The real top of the pump manifest:
typeId: consystence.pump.centrifugalcontrolModel: peer # control-routing model for this typeversion: "1.0.0"name: Centrifugal Pumpdescription: > A general-purpose centrifugal pump with VFD speed control, including monitoring of discharge pressure, flow rate, and motor parameters.publisher: Consystencelicense: MITcategory: Pumpsplatform: ">=1.0.0"Below the header, the manifest declares (all present in the real pump):
tags— the type’s signal surface. Each tag has aname, adataType(Bool,Int,DInt,Real,String), anaccessmode (Read,Write,ReadWrite), an optionalunit/rangeMin/rangeMax, and aphysicalRole(Input,Output, the rareInternal, or the controller-intrinsicSystem). The pump declaresRunning(Bool,Output);Speed(Real,ReadWrite,Output,0–3600 rpm);SpeedSetpoint(Real,Input); sensor inputs such asDischargePressure,FlowRate,MotorCurrentandBearingTemp; command portsStartCmd,StopCmd,ResetCmd,EnableAutoCmdandDisableAutoCmd(Bool,Write,Input), which commands write and the control block consumes; and control outputsFault,FaultCodeandSetpointRangeFault. The full schema and the role semantics are on Tag Schema.calculations— derived values from tags, e.g.HydraulicPowerandEfficiency.states— the advisory state-machine definition, evaluated in order with the first match winning. The pump declaresFaulted,Interlocked,Running (Local),Local,RunningandStopped, each with booleanconditionsover tags and a displaycolor; the type loader rejects a state that can never be reached.aiContext— a natural-language description plustypicalQueriesandtroubleshootingentries that ground the assistant’s answers in equipment reality.sceneBindings— per-type binding aliases the process-scene renderer joins with a placement prefix to drive P&ID visuals.
A manifest may also declare dependencies — other device types this one builds on, each a type id plus a semver version constraint (for example consystence.sensor.temperature at >=1.0.0). Dependencies are checked when a type is installed into a registry: the install is rejected if they are not satisfied. None of the first-party types currently declares any.
The controlModel field
Section titled “The controlModel field”controlModel describes how a type’s instances are realised at runtime: peer types keep a device’s identity and its control runtime in separate runtime objects (the four-object model below), while legacy types keep both in one combined runtime object. The field is carried on the type manifest: controlModel: peer routes instances to the peer model, while a type that omits the field (or declares legacy) stays on the combined model — so changing a type’s routing is a manifest edit, with no platform code change. The routing is a property of the type, not a per-instance choice.
controlModel | Routing | Used by |
|---|---|---|
peer | Identity and control in separate runtime objects (the four-object model) | Process types: in the pumps library the pump, motorised valve, flow/level/pressure sensors and PID controller; in materials handling the conveyor, chute, crusher, feeder, vibrating screen and weightometer; and all four wet-processing types |
legacy | One combined runtime object | I/O and controller types (controller.logix, io.eth, io.di32, io.do16) permanently, plus every type that omits controlModel |
For I/O the split is permanent, not transitional: low-level I/O types have no separable control behaviour to peel off, so they remain legacy by design. Process equipment in the Standard Library is migrating, not migrated: the pumps library’s VFD motor, storage tank and two sequencers, and the materials-handling temperature and vibration sensors, still route legacy; moving them to peer is planned.
The four-object decomposition
Section titled “The four-object decomposition”Consystence decomposes “a device in the process model” into four separately-lifecycled objects. The device type is shared; the four objects are the runtime realisation of a single instance:
graph TD T[DeviceType<br/>manifest.yaml<br/>canonical behavioural contract] I[DeviceInstance<br/>identity + place in the process model] P[DeviceInstanceProfile<br/>asset metadata<br/>vendor / serial / service history] C[DeviceControlInstance<br/>tag mappings + control impl] S[DeviceSimulationInstance<br/>one simulation run per scenario] I -->|references| T I --> P I --> C C -.->|input sink| S
| Object | Carries | Lifecycle |
|---|---|---|
DeviceInstance | Stable identity, display name, reference to its DeviceType, location, process-graph relationships | Survives asset replacement |
DeviceInstanceProfile | The physical asset filling the role — vendor, model, serial, calibration, maintenance history | Replaced when the asset is swapped |
DeviceControlInstance | The control runtime — current state, live tag values, active alarms and acknowledgements — plus a cached copy of the instance config. Deployment topology is not carried here: it derives from the device’s controller assignment (see below) | Created alongside the instance for peer types; legacy types never have one |
DeviceSimulationInstance | A running simulation of the device in one scenario context — one-to-many per device | Created/torn down ad hoc |
The design includes a commissioning state machine for devices: Declared (DeviceInstance only — visible in the model, not operational) → Configured (a DeviceControlInstance exists, with a controller assignment and tag mappings) → Operational (control runs against real or simulated inputs). That lifecycle is on the roadmap: today an instance is configured at creation (its configuration is passed to its DeviceControlInstance straight away), no lifecycle state is persisted on the instance, and legacy-routed types do not have a DeviceControlInstance. A DeviceSimulationInstance needs its device’s DeviceControlInstance to exist, and writes simulated values to that control instance’s inputs through one common injection interface, so a simulator never needs to know which topology its target runs in.
Deployment topologies
Section titled “Deployment topologies”Where a device’s control logic physically executes follows from its controller assignment: every device instance carries a validated assignment to a controller (ctl_…) in the site’s controller model — a physical controller, or a platform-provisioned vPLC, which is itself a typed controller in that model (platform Vplc) rather than a separate category. The assignment — not a per-instance topology field — sets the device’s topology: the platform derives each device’s path (site-direct, through an edge, or a vPLC) from its assigned controller, and generates control code for the controller a device is assigned to. The three topologies below are the conceptual map that assignment anchors, with the same manifest contract in all three.
| Topology | Where control runs | Wire to Consystence |
|---|---|---|
| Native | The Consystence vPLC, inside the Consystence server (site server or hosted service), running the Consystence standard control blocks against simulated field inputs. It is provisioned for simulation only; physical IO stays with your controllers. | In-process; no protocol bridge. |
| Gateway-vPLCnext | A Phoenix Contact vPLCnext container running the Consystence standard control blocks on the PLCnext runtime. | gRPC (the PLCnext GDS API) to the vPLCnext, reading and writing PLCnext GDS/PORT variables. |
| Gateway-legacy-PLC | The customer’s existing controller (for example a Rockwell ControlLogix). | Read over its native field protocol, EtherNet/IP CIP, through an edge, or for a ControlLogix directly by the site server, read-only (early access: our engineers enable it per controller; it has run at a site trial since 28 October 2026; off by default) (see PLC communication). |
In this model a “vPLC” is no longer a category — it is a typed controller in the site’s controller model, and the topology is a consequence of assigning a device to it. The same canonical device-type definition targets all three.
Symbols and PLC exports (planned)
Section titled “Symbols and PLC exports (planned)”The pump package contains no ISA-5.1 symbol.svg and no Rockwell plc/*.L5X exports. P&ID symbol assets and per-vendor control-code exports are a deferred capability — treat any reference to symbol.svg (ISA-5.1) or bundled plc/aoi.L5X / udt.L5X artefacts as planned, not present. The canonical package is the five-artefact structure shown above. Controller code for the Gateway topologies is generated by the platform from the process model and the Standard Library blocks — it is not bundled in the type package, and generated code becomes site-owned once instantiated.
Next steps
Section titled “Next steps”- Tag Schema — data types, access, ranges,
physicalRole, and the simulator/controller data flow - Alarms & Commands — alarm conditions, command tag writes, preconditions and auditing
- Creating a Device Type — authoring and validating a manifest
- The Marketplace — discovering and installing libraries (planned)
- Architecture Overview — where types and instances sit in the platform
- API Reference — device-instance endpoints