Skip to content

Mental Models

Cloud-Native DCS implements the ISA-88 batch model on Kubernetes. If you've worked in industrial automation before, you already know most of the ideas. They're just packaged differently here.

This page maps Cloud-Native DCS concepts to the vocabulary you already know, so the rest of the library reads as "oh, I've seen this before".

Two starting points

People approach this library from two different backgrounds. Both arrive at the same place, but the path is different.

If you're coming from a class-based distributed control toolchain

(Typical environment: a class-based library of Phase Classes and Equipment Modules, a graphical control-strategy editor, a runtime that loads compiled classes onto controllers, batch manager software layered on top.)

You're already fluent in ISA-88. The terminology here is identical: Unit, Phase, Operation, Unit Procedure, Master Recipe, Control Recipe. Where it gets different:

What you have What Cloud-Native DCS calls it How it's different
Phase Class PhaseTemplate Authored in YAML + SFC, version-controlled in git. One template, many instances.
Control-module class ControlModuleTemplate (the CRD kind) YAML-authored template describing the function-block network. A per-unit ControlModule CR then instantiates it with specific I/O bindings.
Control-strategy drawing (FBD) ControlModuleTemplate (canonical function-block network) Identical concept. The template stores one canonical function-block network; the FBD diagram and IEC 61131-3 Structured Text are two live, editable views of that network — author in either, ST compiles down to the network, and the runtime executes the network (ADR 0012).
Compiled phase running on a controller unit-runtime pod on the device node One pod per Unit, running the function-block network on the local controller hardware.
Batch manager Gateway + Batch controller Batch resource tracks recipe execution state; the Batch controller drives ISA-88 state transitions.
Historian historian binary + PostgreSQL (+ Timescale extension) via MQTT Tag values flow MQTT → historian consumer → PostgreSQL hypertables. In the deployed chart, Postgres runs under CloudNativePG with the Timescale extension enabled. Same purpose, different storage engine.
Alarm & event journal AuditRecord CRD + alarm CRDs ISA-18.2 state model, immutable records, queryable via dcs audit.
Recipe builder / recipe server MasterRecipe CRD + recipe controller Recipes are Kubernetes resources under dcs apply + approval workflow.
Operator HMI Gateway web UI — /system (engineering), /hmi (operations), /data (trends) Browser-based, role-aware, split into role-aligned sub-apps sharing the same backend.

What will feel different:

  • Everything is declarative. You change a YAML file, dcs apply, and the controllers reconcile. There's no "download to controller" step.
  • Git is the change-control system. There is no separate database of approved configurations. The approved state is whatever's in the repo on the branch you deployed from.
  • One template, many instances, runtime binding. A PhaseTemplate is parameterized, and a recipe step supplies the parameter values and the role bindings at execution time.
  • SFC with embedded Structured Text is the preferred phase logic. Bare-ST fields (actionST, holdingST, …) on a PhaseTemplate still work as a deprecated fallback for terse one-step phases, but new phases should be authored as SFC charts. Ladder and standalone FBD are not supported at the phase layer.

If you're coming from a ladder / AOI-based PLC environment

(Typical environment: a Studio-style IDE with ladder, Structured Text, SFC, and Function Block Diagram per IEC 61131-3, plus tag-based I/O mapping, AOIs or function blocks for reusable logic, and batch/recipe execution bolted on as a separate application layer.)

You're fluent in IEC 61131-3 but may be less familiar with ISA-88 terminology. The programming concepts are the same. The recipe layer is what's new.

What you have What Cloud-Native DCS calls it How it's different
AOI / user function block ControlModule template Reusable function-block wrapper with parameters, tags, and scan interval. Instantiated per equipment instance.
AOI instance on a routine ControlModule resource (a CR) kind: ControlModule, templateRef: discrete-valve, tagBindings: {...}.
Routine / subroutine (SFC, ST) PhaseTemplate An SFC chart whose steps and transitions are written in Structured Text. Driven by a recipe step.
Tag-based I/O map IOModule + tagBinding strings "reactor-sim:discrete.0" binds a ControlModule tag to channel 0 of the reactor-sim IOModule.
Program on a controller unit-runtime pod Runs the function-block network for one Unit. hostNetwork: true so it can reach local I/O.
GV / global tag Runtime tag store In-memory tag store inside the unit-runtime pod. MQTT export for historian and HMI.
Batch / recipe application MasterRecipe → ControlRecipe → Batch Native CRDs, with no bolt-on layer. Each has its own controller.

New vocabulary to learn, all ISA-88 terms from the Terminology page:

  • Unit — a physical equipment instance (reactor-1, mix-tank-1). Like a "machine" in packaging-line terms, but for process equipment.
  • Phase — a reusable step of process logic (e.g. "charge reactor", "heat and hold"). Closest analog: a routine or subroutine that's called with parameters. Always an SFC.
  • Operation — a sequence of phases that makes sense as a unit (e.g. "CIP rinse operation" = fill + agitate + drain + conductivity check). Think of it as a routine that calls several phase routines in order.
  • Unit Procedure — a sequence of operations bound to one specific unit (e.g. "clean the mix tank"). Composes operations.
  • Master Recipe — the top level. References unit procedures for each unit involved in a batch. Also declares batch sizing, formulas, and the procedural flow across units.

Key mental shifts:

  • SFC is mandatory. In many PLC environments SFC is one language among several and you might never use it. Here, every phase is an SFC chart. Transition guards and step actions are written in Structured Text, embedded inside the SFC YAML.
  • Parameter passing is by name. A recipe step binds parameter values (fillLevel: 300) to a PhaseTemplate's named parameters. No wires connect to function-block inputs at this layer.
  • Roles decouple logic from instances. A phase template says "I need a solvent_valve" (a role name). The recipe step binds that role to a specific ControlModule instance (r1-solvent-valve). The phase logic never references the instance directly, which is what makes templates reusable.

Hold vs. Stop vs. Abort

These three commands are not interchangeable and mean exactly what ISA-88 says they mean:

  • Hold is recoverable — Restart resumes the phase from where it paused. Use for alarms that might clear.
  • Stop is a clean-stop at the next safe boundary — Reset returns the phase to Idle. Use when finishing this batch cleanly but not continuing.
  • Abort is emergency-stop — also Reset to return to Idle, but without the safe-boundary contract. Use when the only acceptable outcome is "stop now".

Pick the right one in AlarmDefinition.exceptionAction. Mis-wiring a recoverable alarm to Abort is the most common mistake when porting from a toolchain where "Stop" meant "halt immediately".

The four-layer stack

Everyone, regardless of background, needs this mental model:

flowchart TD
    MR["Master Recipe<br/>Heat-and-react recipe for reactor-1<br/>recipe-level, per-product"]
    Proc["Procedure Template (optional)<br/>Full API isolation procedure<br/>procedural, cross-unit"]
    UP["Unit Procedure Template (optional)<br/>CIP a mix tank<br/>procedural, per-unit"]
    Op["Operation Template (optional)<br/>CIP rinse operation<br/>procedural, reusable"]
    Phase["Phase Template<br/>charge-reactor<br/>phase logic, reusable"]
    CM["Control Module (instance)<br/>r1-wfi-valve (from discrete-valve template)<br/>function blocks, per-instance"]
    IO["IOModule<br/>reactor-sim (simulated) or reactor-wago (real)<br/>I/O driver"]

    MR -->|procedure.chart references| Proc
    Proc -->|composed from| UP
    UP -->|composed from| Op
    Op -->|composed from| Phase
    Phase -->|commands / reads| CM
    CM -->|reads / writes| IO

The same stack in motion: each layer lands in turn, then one command pulse travels the whole chain from recipe to I/O channel:

The stack assembles top-down, then one setpoint command rides it from recipe to I/O.

What each layer contributes:

  • Master Recipe — batch sizing (min/nominal/max), procedural flow across units, step parameters bound from formulas.
  • Procedure Template — top of the ISA-88 procedural hierarchy, sequencing unit procedures across multiple units.
  • Unit Procedure Template — sequence of operations bound to one unit's capability.
  • Operation Template — a parameterized, reusable sequence of phases.
  • Phase Template — SFC chart with ST actions and guards, declaring parameters and required control-module roles.
  • Control Module (instance) — FB network running on the unit-runtime pod, with tags bound to IOModule channels.
  • IOModule — protocol driver (sim, modbus, opcua, …) and its channel list.

The Procedure, Unit Procedure, and Operation layers are all optional. A simple recipe can reference phase templates directly from the master recipe, skipping all three. The reference plant uses this flat pattern for single-unit recipes and the full stack for the multi-unit API isolation recipe. Use the layers that make your recipe clearer. Don't add ceremony you don't need.

Templates vs. instances

This distinction trips people up. Templates are class-like. They live in the Equipment Library, describe a pattern, and are versioned under change control. Instances are object-like. They're created when the template is attached to a specific unit and bound to specific I/O.

Template (class) Instance (object)
discrete-valve (ControlModuleTemplate) r1-wfi-valve (ControlModule on reactor-1)
charge-reactor (PhaseTemplate) The charge step of a running batch on reactor-1
cip-rinse-op (OperationTemplate) The pre-rinse step of a running CIP batch on mix-tank-1
sim-reaction-full-m1 (MasterRecipe) Batch batch-000123, a specific run of this recipe

In Kubernetes terms: templates live in their own CRDs (ControlModuleTemplate, PhaseTemplate, OperationTemplate, UnitProcedureTemplate, ProcedureTemplate, MasterRecipe). Per-unit instances are either separate CRs (a ControlModule resource is a per-equipment instance of a ControlModuleTemplate) or transient runtime state (a running batch's current phase/step doesn't get a dedicated CR, and is tracked in the Batch / Phase resource status).

Recipe parameters vs. control-module parameters

Two different "parameter" concepts that both appear in the library:

  • PhaseTemplate parameters — named scalar values the phase needs to execute (e.g. fillPercent: 75, duration: 300). Supplied by the recipe step that calls the phase. Typed and range-checked against the template's parameter declaration.
  • ControlModule parameters — configuration for a control-module instance (e.g. on pid-loop, kp: 1.2, eng_units: "degC"). Set when the module is instantiated on a unit. Static during normal operation. Changing them is an engineering change, and no recipe carries one.

Rule of thumb: phase parameters are what the recipe asks for, and control module parameters are how the equipment is configured. A fresh recipe with different ingredients should change phase parameters. A change in hardware or tuning should change control-module parameters.