Skip to content

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.

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.yaml

library.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:

ArtefactOwns
manifest.yamlIdentity, control model, tags, calculations, state machine, AI context, scene bindings
simulation.yamlPhysical-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.yamlAlarm definitions and conditions
commands/commands.yamlOperator 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.

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.centrifugal
controlModel: peer # control-routing model for this type
version: "1.0.0"
name: Centrifugal Pump
description: >
A general-purpose centrifugal pump with VFD speed control, including
monitoring of discharge pressure, flow rate, and motor parameters.
publisher: Consystence
license: MIT
category: Pumps
platform: ">=1.0.0"

Below the header, the manifest declares (all present in the real pump):

  • tags — the type’s signal surface. Each tag has a name, a dataType (Bool, Int, DInt, Real, String), an access mode (Read, Write, ReadWrite), an optional unit / rangeMin / rangeMax, and a physicalRole (Input, Output, the rare Internal, or the controller-intrinsic System). The pump declares Running (Bool, Output); Speed (Real, ReadWrite, Output, 0–3600 rpm); SpeedSetpoint (Real, Input); sensor inputs such as DischargePressure, FlowRate, MotorCurrent and BearingTemp; command ports StartCmd, StopCmd, ResetCmd, EnableAutoCmd and DisableAutoCmd (Bool, Write, Input), which commands write and the control block consumes; and control outputs Fault, FaultCode and SetpointRangeFault. The full schema and the role semantics are on Tag Schema.
  • calculations — derived values from tags, e.g. HydraulicPower and Efficiency.
  • states — the advisory state-machine definition, evaluated in order with the first match winning. The pump declares Faulted, Interlocked, Running (Local), Local, Running and Stopped, each with boolean conditions over tags and a display color; the type loader rejects a state that can never be reached.
  • aiContext — a natural-language description plus typicalQueries and troubleshooting entries 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.

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.

controlModelRoutingUsed by
peerIdentity 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
legacyOne combined runtime objectI/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.

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
ObjectCarriesLifecycle
DeviceInstanceStable identity, display name, reference to its DeviceType, location, process-graph relationshipsSurvives asset replacement
DeviceInstanceProfileThe physical asset filling the role — vendor, model, serial, calibration, maintenance historyReplaced when the asset is swapped
DeviceControlInstanceThe 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
DeviceSimulationInstanceA running simulation of the device in one scenario context — one-to-many per deviceCreated/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.

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.

TopologyWhere control runsWire to Consystence
NativeThe 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-vPLCnextA 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-PLCThe 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.

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.