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 type-vs-instance split in depth see The Type/Instance Model — this page — and Tags, Data Types & Physical Roles for the tag schema; to author your own, see Creating Device Types.
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/AI commands and the tag writes they perform |
Alarms and commands are covered on Alarms & Commands; the simulation profile and tag schema on Tags, Data Types & Physical Roles.
The manifest
Section titled “The manifest”manifest.yaml is the canonical behavioural contract for the type. It is the one artefact that is identical across every deployment topology (see below) — the contract is declared once and each topology’s control implementation is certified against it. 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,MotorCurrent,BearingTemp, and control outputsFault,FaultCode,SetpointRangeFault. The full schema and the role semantics are on Tags, Data Types & Physical Roles.calculations— derived values from tags, e.g.HydraulicPowerandEfficiency.states— the advisory state-machine definition. The pump declaresStopped,Running,Faulted,InterlockedandLocal, each with booleanconditionsover tags and a displaycolor.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 split identity and control into peer grains (the four-object model), while legacy types use a single combined instance grain. 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 grain — so a library update alone can change a type’s routing, with no platform release. The routing is a property of the type, not a per-instance choice.
controlModel | Routing | Used by |
|---|---|---|
peer | Identity and control split into peer grains (the four-object model) | Process types: pump, motorised valve, flow/level/pressure sensors, PID controller |
legacy | Single combined instance grain | 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 is migrating, not migrated: only the process types in the pumps library route peer today. The rest — motor/VFD, storage tank, the generic sequencer and the materials-handling types — route legacy and move to peer as customer demand brings them.
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/>scenario-context-keyed simulation run] 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 | Re-keyed 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 keyed by site, device instance and scenario context (grain key {siteId}/{deviceInstanceId}/{scenarioContextKey}) — 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 topology and tag mappings) → Operational (control runs against real or simulated inputs). That lifecycle is on the roadmap: today an instance is configured at creation (identity configuration cascades to the control peer synchronously), no lifecycle state is persisted on the instance, and legacy-routed types do not have a DeviceControlInstance. A DeviceSimulationInstance requires its DeviceControlInstance peer to exist and feeds it through a canonical injection abstraction, 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 — determines where control code is built and deployed and where the platform reads and writes tags: a derive seam resolves each device’s acquisition path (site-direct, edge, or vPLC) and read/write location from its assigned controller, so the routing can never drift from the assignment. The three topologies below are the conceptual map that assignment anchors, with the manifest contract identical across all three.
| Topology | Where control runs | Wire to Consystence |
|---|---|---|
| Native | The Consystence runtime — edge device or site server, whichever is closest. .NET grain, in-process. | In-process; no protocol bridge. |
| Gateway-vPLCnext | A Phoenix Contact vPLCnext container provisioned by Consystence, running the Consystence standard control blocks on the PLCnext eCLR runtime. | gRPC-over-UDS to the vPLCnext, reading and writing PLCnext GDS/PORT variables. |
| Gateway-legacy-PLC | The customer’s existing controller (for example a Rockwell ControlLogix). | Reached over its native field protocol — EtherNet/IP CIP — via the PLCnext border gateway. Driving EtherNet/IP CIP directly from the edge remains a supported direct-interop path. |
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”- Tags, Data Types & Physical Roles — the tag schema,
physicalRole, and the simulator/controller data flow - Alarms & Commands — alarm conditions, command tag writes, preconditions and auditing
- Creating Device Types — authoring and validating a manifest
- The Marketplace — discovering and installing libraries (planned)
- Architecture Overview — where types and instances sit in the platform
- API Reference — type-registry and instance endpoints