Skip to content

OEM Skids and Vendor Units

A vendor supplies a process skid with its own controller and its own logic, and the plant needs it under recipe control without re-engineering it. The skid keeps its sequences and its interlocks. Cloud-Native DCS commands it from a recipe phase and reads back what it reports.

Direction is worth settling before anything else, because this is the one southbound page in the Integration section. On every other page here an external system calls the DCS or subscribes to what it publishes. Toward a skid the DCS is the client and the orchestrator. It opens the session, it invokes, and it waits on the skid's own feedback. The gateway exposes no northbound OPC UA server, so a vendor asking for the address of your server to poll is describing an integration this product does not offer.

Which path the skid takes

Two questions settle the route, and the vendor can answer both. Does the skid serve OPC UA, and does it ship an MTP file?

What the skid exposes Route What lands in the cluster
An MTP ServiceControl interface over OPC UA Device discovery recognizes the interface and flips the candidate to the service plane A Unit whose serviceBinding declares mtp services carrying their resolved ServiceControl addresses
OPC UA Methods Device discovery introspects each Method's arguments A Unit whose serviceBinding declares one service per picked Method
OPC UA variables and no callable surface Device discovery on its ControlModule default ControlModule instances under a parent Unit you pick, sharing one opcua IOModule
Modbus TCP registers I/O authoring An IOModule carrying a control-device field-device record, and the ControlModules bound to its channels
Something with no driver in tree, such as CIP tags on ControlLogix, Profibus, or DeviceNet An OPC UA aggregation server in front of it Whichever row above matches what the aggregation server presents

The split that decides the shape is service plane against data plane, and the protocol sits underneath it (ADR 0020). A skid that runs its own sequences and offers them as named services becomes a Unit, and the DCS orchestrates it. A box that presents values and coils becomes control modules under a unit the DCS already owns, and the DCS runs the loop.

Bringing one up

Point discovery at the endpoint. The Device Discovery Wizard browses the skid's address space, classifies each node, and applies the emitted resources. It owns the mechanics of the security policy, the gateway's own client identity, the scan, per-row classification, and the apply.

Declare what the live server could not answer for. Discovery persists what it read and guesses at nothing else. For an MTP service, procedure identity and the self-completing versus continuous split are vendor engineering data that no live server serves. Declare procedures on the Unit after apply, or take them from the vendor's file with MTP import. The vendor's file is where they are written down. For a generic OPC UA device, emitted tags carry an address, a data type and no role, because discovery cannot tell a setpoint from a feedback (ADR 0016). Declare roles when you templatize the module. Two more items land after apply: a credentials Secret named in the wizard resolves in the Unit's own namespace, and the observed server-certificate fingerprint becomes a pin on spec.serviceBinding.security.serverCertSha256Pin.

Author the phases. An MTP service is driven with MTP_COMMAND, MTP_STATE and MTP_COMMAND_ENABLED. A Method service is called with CALL_SERVICE. The authoring shape is the same for both: the command goes in a step action, and the module's feedback goes in the transition that follows. See Structured Text for the builtins and examples/smart-skid/ for a worked Unit and its phase templates. A step whose action commands a skid should carry a timeoutSeconds, since the guard after it is waiting on a remote module.

A Modbus skid takes the I/O route. The IOModule is authored against the register map in the vendor's manual, and a phase commands the skid by writing its handshake tags and waiting on its feedback tags.

What the vendor's controller keeps owning

The skid's interlocks, its lower-level loops and its internal sequencing stay inside the skid. That boundary is a modeling rule in this product, and four consequences follow from it for the phases that command such a unit.

  • Orchestrate mode only. A Unit carrying a serviceBinding must carry no equipment modules, and admission rejects the pair. The Unit's state machine is the DCS view of the orchestration, and it never mirrors the skid's internal states (ADR 0001).
  • A refused command holds the phase, and the refusal is legible. Every MTP command is gated on the live CommandEn word the module publishes. A command the skid is not offering holds the phase with a message naming the state the service is in and the commands it would accept. An interlock inside the skid can therefore refuse a service the recipe treats as mandatory, and that arrives as a hold on the batch.
  • The skid's response to a phase hold is something you write. Holding a phase does not stop the skid. The holdingChart decides what the skid is told, and the restartingChart is the only thing that re-commands a step's action afterward, because a resumed step is never replayed (ADR 0047).
  • One physical thing gets one record. An IOModule addressing the same host as a serviceBinding Unit is that skid's data plane, and the Unit is the box. The reconciler reports the overlap on status.conditions[type=DistinctAssetRecord] and the asset position stays with the Unit (ADR 0033).

MTP, and how much of it is real

pkg/mtpservice carries VDI/VDE/NAMUR 2658 Blatt 4 verbatim: the 16 states in five levels, the ten commands, the Table 14 bit codings, and the Figure 3 transitions, with the ISA-88 correspondence declared in one place (ADR 0045). make test-mtp-interop writes a Table 14 command word to a node of each declared integer width on Microsoft's opc-plc, so the coding is proven against a server this project did not write.

The file half now reads. dcs mtp import and the gateway's import endpoint parse the AutomationML container a vendor ships and generate the resources. The manifest is the input, with no retyping step (#1497). See Importing the vendor's MTP file below. MTP export and the Blatt 5.1 runtime binding are still unimplemented, and no conformance claim is made. The full posture is on the MTP standards page.

One gap in the proof is worth knowing before a commissioning trip. The ServiceControl interaction has not run against a vendor PEA. Blatt 4 defines a variable shape with no companion NodeSet behind it, so there is no third-party address space to instantiate a fixture from. The PEA in this repository's captures therefore runs on the same Go stack as the client reading it (ADR 0045). The command-word coding is the part a foreign server has checked.

Importing the vendor's MTP file

A skid that ships an MTP file already has its services, procedures, parameters and every OPC UA address written down. There is therefore a second way in that does not need the skid powered on:

dcs mtp import ./GasControl.mtp \
  --site plant-01 --process-cell gas-cell | kubectl apply -f -

The CRs go to stdout and the report to stderr, so that pipeline applies the import while the report stays on the terminal. A --report-only run reads the file and emits nothing, which is the right first run against a manifest you have not seen. The file may be a .mtp/.amlx container or a bare .aml, decided by its contents alone. The gateway offers the same thing at POST /api/v1/import/mtp (API reference).

Import produces what discovery produces: an IOModule for the endpoint, a Unit whose serviceBinding declares each service, and a template-free ControlModule per data assembly. Two things it adds that a live scan cannot. Procedure identity and the self-completing split come out of the file, so the "declare what the live server could not answer for" step above is already done for procedures. And a tag's role is declared, because an MTP data assembly's attribute set is fixed by the standard. PosFbk on a valve is a feedback in every conforming file. That is a declaration, and the name-based inference ADR 0016 forbids plays no part.

Nothing binds to the file. What comes out is yours to edit from the moment it applies, exactly like the wizard's output.

Read the report

The import prints a finding per item it could not carry whole, and a partial import that quietly dropped a service would be worse than a refusal. Four are worth knowing before you run one.

A manifest in the older vocabulary has every service refused. Files in the field carry two incompatible service models. The current one declares a ServiceProcedure with a ProcedureID. The older one declares a ServiceStrategy with a StrategyID and names its channels CurrentState, CommandEnable and CurrentStrategy. The channels line up one for one, which is exactly why they are not treated as aliases: the older file does not say which coding its command words use, and this product writes the Table 14 coding. A declaration emitted anyway would connect and then command the skid with words it does not accept. That failure surfaces as a wedged phase, far from the author-time error the refusal gives you instead. Ask the vendor for an export in the current revision. The file's control modules still import.

An engineering range often is not in the file. The current revision addresses VSclMin and VSclMax as OPC UA nodes, so the numbers live on the PEA and the manifest carries only their addresses. Those modules land without engMin/engMax, and ADR 0050's range check has nothing to compare a commanded value against until you declare them. The older revision writes them as numbers and they are carried straight through.

Imported modules render in the generic group. deviceClass can only be declared on a ControlModuleTemplate, and an imported module is emitted template-free so the control operator does not compile a ControlProgram for logic the PEA owns. The report names each module's MTP class, which is what you would set the deviceClass from. Per-tag roles are declared and do apply.

A data assembly class this product does not model is skipped, by name. Its channel set is unknown, and a tag emitted with a guessed data type reads garbage silently.

Legacy controllers behind an aggregation server

Brownfield plants usually attach a legacy controller through an OPC UA aggregation server the site already owns. Kepware (KEPServerEX) is the industry default, FactoryTalk Linx Gateway is the Rockwell path, and Siemens IDLink fronts S7-300/400 and PCS 7. The vendor family table lists the recommended path per family and is the authority on it.

Three things change when the endpoint is an aggregation server.

  • The box at the address is not the box the signal came from. Declare spec.fieldDevice.type: gateway on the IOModule so the asset record says what it is.
  • One endpoint usually fronts many devices, so the emitted resources are one shared IOModule for the endpoint and a ControlModule per device.
  • An aggregation server projects tags, and a service surface rarely survives the hop. A skid reached this way is a data-plane device: the phase writes its start tag and waits on its done tag, and the sequencing stays in the vendor's controller with no service for CALL_SERVICE or the MTP builtins to reach.

What is proven, and what is not

I/O carries the protocol status matrix and the vendor table, and it is the only place they live. Three of its lines decide most skid projects.

  • OPC UA is implemented-untested against real metal. Connect, read, write, subscribe and the secured channel run end to end against opc-plc and open62541 on every full CI run. No PLC has answered yet, and field validation waits on a design partner.
  • EtherNet/IP is experimental and gated. unit-runtime refuses to load the driver without --enable-experimental-drivers, and the gateway UI does not offer it. It implements the Generic Device Profile with assembly-instance addressing. CIP-tag addressing on ControlLogix and CompactLogix is a different protocol layer, and the driver has no support for it (#272). Bridge a Rockwell skid through OPC UA.
  • Modbus TCP is field-proven on the reference rig for single-register values, and the four vendor word and byte layouts are proven in both directions against an independent Modbus implementation. No cross-register value has been read off physical hardware.

MTP import is proven against real vendor manifests. Two are vendored under pkg/mtpimport/testdata/vendor/ with their provenance: a current-revision export from WAGO's e!Cockpit and an older-revision one, both MIT-licensed. That mattered. A fixture written here would have agreed with our own parser by construction, and these two refuted two rules that would otherwise have shipped: a service joins its channel set by a shared RefID value where our draft rule joined by name, and the two vocabularies exist at all. What is still unproven is the same gap the ServiceControl interaction has: no vendor PEA has answered an imported declaration on live metal.

Modbus TCP and EtherNet/IP also carry no authentication and no encryption, so a skid on either one needs the network segmentation the threat model specifies. Where the skid offers OPC UA, run it signed or signed-and-encrypted.