ADR 0045: An MTP service is recognized by its ServiceControl variable shape and modeled with the VDI 2658-4 vocabulary; ISA-88 presentation is a declared correspondence, and no MTP conformance is claimed¶
Status: Accepted Date: 2026-08-10 Issue: #936
Context¶
ADR 0020 gave discovered OPC UA devices the ISA-88-correct shape — field
device → ControlModule, module/skid → Unit with a serviceBinding — and
ADR 0022 made data-plane classification native by keying on the PA-DIM /
OPC UA DI type-definition namespaces. The service plane stayed on two
proxies: classification by "exposes Methods", and a serviceBinding
whose services are raw OPC UA Method invocations with no states,
commands, or procedures. #928 deferred going native to #936 because the
normative source, VDI/VDE/NAMUR 2658 Blatt 4:2022, is a paid standard;
it was purchased on 2026-08-09 (cndcs-finance#38) and is archived in the
CNDCS Drive MTP Bundle folder, with gitignored derivative notes in
docs/mtp/vdi2658-4-service-model.md.
Blatt 4 defines the module service model: a PEA (Process Equipment
Assembly) exposes services a POL (Process Orchestration Layer)
orchestrates; each service implements a mandatory, non-configurable
state machine of 16 states in five levels, driven by ten commands with a
mandatory bitwise coding (Table 14); each service has one or more
procedures, self-completing or continuous; and each service exposes the
ServiceControl interface (Table 13) — the state feedback pair
(StateCur, CommandEn), three command channels
(CommandOp/CommandInt/CommandExt), procedure channels, and the
operation-mode machinery.
Three facts shape the design space:
- There is no type-definition to key on. MTP's OPC UA runtime
binding is Blatt 5.1, which maps ServiceControl attributes onto nodes
the MTP manifest file names — it is not a companion specification
with a public
PEATypeNodeID the way PA-DIM hasPADIMType. (Blatt 5.1 is also currently a draft edition at DIN Media and was deliberately not purchased.) The ADR 0022 mechanism therefore cannot extend to the service plane. - The state machine is similar to ISA-88, not identical. The
standard itself says services follow a state machine "similar to the
concept of ISA 88 or DIN EN 61512-1" (Section 4). Our
pkg/statemachineimplements the ISA-88 13-state/8-command model; MTP has 16 states and 10 commands, two of which (Restart, Complete) have no ISA-88 counterpart at all. The joint NAMUR/ISA-88 working group is aligning the models but they are not unified (ADR 0020). - A from-memory enumeration was already refuted once. #928 records that a candidate state/command set written from memory did not survive contact with the standard. The enumeration must be transcribed, and pinned by tests that a reviewer can check against the PDF.
Decision¶
Recognition: the ServiceControl variable shape is the service-plane
signal. A discovered node is recognized as an MTP service when its
child variables carry the full ServiceControl core signature — all of
StateCur, CommandEn, CommandOp, CommandInt, CommandExt,
ProcedureCur (mtpservice.IsServiceControlShape). All six are
required: the interface is not optional in the standard, a partial match
is far more likely a BrowseName collision than a partial MTP
implementation, and the cost of a false positive is emitting a Unit for
a field device. A node whose children include such service nodes is a
PEA and pre-classifies Unit. This is still structural in the letter —
we match names, not a type — but it is the standard's own mandatory
interface rather than the "exposes Methods" proxy, and it is exactly the
variable set the model consumes afterwards. The Methods proxy remains as
operator guidance for unrecognized nodes; recognition only moves the
default, the operator still decides.
Vocabulary: pkg/mtpservice carries the standard, verbatim and
pinned. The 16 states with their five levels, the ten commands, the
Table 14 bit codings (states, commands, CommandEn), the Figure 3
transition table with level preemption, and the procedure
self-completing/continuous split live in one package with no other
dependencies than pkg/s88. Unit tests pin every constant to the
integer values printed in Table 14 and every transition to Figure 3, so
a future edit is visibly an edit of a normative constant. The decoders
are strict: a state or command word that does not carry exactly one
defined bit decodes to nothing rather than a guess, because the standard
requires exactly one bit set and the PEA itself zeroes malformed control
words.
ISA-88 presentation: a declared correspondence in one place.
mtpservice.ToS88State / ToS88Command map the MTP vocabulary onto
pkg/s88 for every surface that presents an MTP service as an ISA-88
Equipment Procedural Element. The non-obvious rows are deliberate: MTP
Unhold/Unholding correspond to ISA-88 Restart/Restarting (both
are the Held → transient → running exit), while MTP's own Restart
command (Execute → Starting, re-running the procedure) and Complete
(external completion of a continuous procedure) have no ISA-88
counterpart and map to nothing. Surfaces consume the correspondence;
nobody re-derives it locally.
pkg/statemachine is not refactored. The ISA-88 engine keeps its
hardcoded 13/8 table; pkg/mtpservice is a parallel vocabulary package,
not a second tenant of a parameterized engine. We are the POL: we track
the state a PEA reports (StateCur is feedback, the PEA owns the
machine), we do not run an MTP state machine of our own, so the engine
mechanics — history, callbacks, mutexes — have no MTP consumer today. A
future MTP simulator can revisit this.
No conformance claim, stated where it binds. Nothing in this line of
work claims MTP conformance: the model is transcribed from Blatt 4, but
the runtime binding (Blatt 5.1) is unpurchased, the ISA-88/MTP alignment
is not unified, and no AML manifest import/export exists.
docs/standards/mtp.md carries the posture; the package doc and this
ADR repeat it so a future surface cannot inherit an implied claim.
Alternatives Considered¶
Keep the Methods proxy and only add the state vocabulary. Rejected: #936's first scope item exists because the proxy misclassifies — a PA-DIM instrument with one configuration Method reads as service-plane, and a PEA whose services are variable-driven (the normative interface is variables, not Methods) reads as data-plane.
Parameterize pkg/statemachine for both models. Rejected for blast
radius: every consumer of New()/NewWithState() (batch, physical,
procedural controllers) would recompile against a changed constructor
for a capability nothing needs — we do not run the PEA's machine, we
read its feedback.
Map all ten MTP commands onto ISA-88 by stretching the nearest neighbors (Restart → s88.Restart, Complete → SC). Rejected: MTP Restart and ISA-88 Restart are different transitions from different states with different meanings, and a correspondence that lies about two rows poisons trust in the other fourteen. Unmapped is honest and the compiler makes callers handle it.
Consequences¶
pkg/opcuaclientgrows aPlaneSignalServicealongsidePlaneSignalData(ADR 0022) and a ServiceControl-shape walk; the discovery wizard flips its default kind toUniton the service signal — the flip ADR 0022 explicitly deferred here.UnitServiceBindingcan grow a declared MTP service form (per-service ServiceControl node addresses and declared procedures) so an emitted PEA service carries states rather than a bare Method NodeID. The declare-at-emit pattern follows ADR 0043: discovery resolves and persists addresses; runtime consumes declarations, never re-guesses.- The two unmapped commands (Restart, Complete) mean any surface that
renders MTP commands through the ISA-88 command set will show eight of
ten; a native MTP surface must consume the MTP vocabulary directly.
The Unit detail's service panel
(
GET /units/{name}/mtp-services, #1405) is the first such surface and does exactly that: it renders the MTP state names and theCommandEnaccepts-list verbatim, and translates nothing into ISA-88. - Because a PEA's state is feedback and is never mirrored into a CRD status, every surface that shows it has to read it. That makes an OPC UA session a thing a read path needs and cannot open per request, so the gateway pools sessions the way the phase run does (#1379, #1405) — keyed on the resolved principal, so two Units reaching one PEA as different users never share one.
- Purchasing Blatt 5.1 when it leaves draft status would replace the BrowseName-shape recognition with the manifest-declared binding; the recognition function is the single point that would change.
- Recognizing a service by variable shape rather than by namespace also
means there is no MTP companion NodeSet, and so no third-party address
space to instantiate a fixture from. That settles the question #1463
asked: the PA-DIM rebuild (#1454) was a strict improvement because the
specification's own file replaced approximated nodes, and MTP has no
equivalent — any MTP fixture is hand-built on any stack. What a foreign
server can still disagree with us about is how it types a node, and one
did.
WriteServiceWordinferred the variant to encode from the node's sampled value; opc-plc serves Int64 for a node it declares UInt32 whenever its address-space file gave that node an initial value, so the inference produced a write the server refused where the standard's plain UInt32 would have landed. The variant now comes from the DataType attribute, which is the attribute a server validates a write against, and the sampled value is the fallback for a server that does not answer it or declares an enumeration.make test-mtp-interopholds that against opc-plc.