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

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

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.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, BearingTemp, and control outputs Fault, FaultCode, SetpointRangeFault. The full schema and the role semantics are on Tags, Data Types & Physical Roles.
  • calculations — derived values from tags, e.g. HydraulicPower and Efficiency.
  • states — the advisory state-machine definition. The pump declares Stopped, Running, Faulted, Interlocked and Local, each with boolean conditions over tags and a display color.
  • 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 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.

controlModelRoutingUsed by
peerIdentity and control split into peer grains (the four-object model)Process types: pump, motorised valve, flow/level/pressure sensors, PID controller
legacySingle combined instance grainI/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.

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
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 historyRe-keyed 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 keyed by site, device instance and scenario context (grain key {siteId}/{deviceInstanceId}/{scenarioContextKey}) — 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 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.

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.

TopologyWhere control runsWire to Consystence
NativeThe Consystence runtime — edge device or site server, whichever is closest. .NET grain, in-process.In-process; no protocol bridge.
Gateway-vPLCnextA 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-PLCThe 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.

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.