OPC UA Companion Specifications: Implementation Tutorial (2026)

OPC UA Companion Specifications: Implementation Tutorial (2026)

OPC UA Companion Specifications: Implementation Tutorial (2026)

Companion specifications are where Industry 4.0 stops being a slide and starts being bytes on the wire. An OPC UA companion specification tutorial that only covers what they are, without showing how to author a NodeSet, compile it into a server, and bind its nodes to real PLC tags, leaves the integrator exactly where they started: hand-rolling a custom node hierarchy that no MES on the other side knows how to read. This guide walks the full chain — design in SiOME, export NodeSet2 XML, compile with open62541‘s nodeset_compiler, bind to a Siemens S7-1500 over the embedded OPC UA server, and certify against the OPC Foundation’s Compliance Test Tool. The worked example is a slim subset of the OPC UA Robotics CS for a single-arm pick-and-place cell, plus a small vendor extension. By the end, you will have a running opc.tcp:// endpoint that an off-the-shelf UaExpert can browse without any custom code on the client side.

Architecture at a glance

OPC UA Companion Specifications: Implementation Tutorial (2026) — architecture diagram
Architecture diagram — OPC UA Companion Specifications: Implementation Tutorial (2026)
OPC UA Companion Specifications: Implementation Tutorial (2026) — architecture diagram
Architecture diagram — OPC UA Companion Specifications: Implementation Tutorial (2026)
OPC UA Companion Specifications: Implementation Tutorial (2026) — architecture diagram
Architecture diagram — OPC UA Companion Specifications: Implementation Tutorial (2026)
OPC UA Companion Specifications: Implementation Tutorial (2026) — architecture diagram
Architecture diagram — OPC UA Companion Specifications: Implementation Tutorial (2026)
OPC UA Companion Specifications: Implementation Tutorial (2026) — architecture diagram
Architecture diagram — OPC UA Companion Specifications: Implementation Tutorial (2026)

What is an OPC UA companion specification?

An OPC UA companion specification is a published information model that defines ObjectTypes, VariableTypes, ReferenceTypes, and DataTypes layered on the base UA meta-model (IEC 62541 Part 5), so that all servers for a given device class — robots, injection-molding machines, IO-Link masters — expose the same browse path and the same semantics for the same data. They ship as a NodeSet2 XML file plus a human-readable PDF.

The OPC Foundation maintains roughly forty published CS as of early 2026, jointly authored with industry associations such as VDMA, EUROMAP, AIM, and the Robotic Industries Association. Robotics CS 1.01 (OPC 40010-1) is the canonical example: it defines MotionDeviceSystemType with a fixed sub-tree containing MotionDevices, Controllers, SafetyStates, and TaskControls. A KUKA KR210 and a FANUC R-2000iC running compliant servers expose the same ActualPose Variable at the same browse position, with the same CartesianCoordinatesType DataType.

The hierarchy is strictly additive. The UA base ships with about 2000 nodes. OPC UA for Devices (DI, OPC 10000-100) adds device topology primitives such as DeviceType and FunctionalGroupType. Machinery CS (OPC 40001-1) adds shared building blocks like MachineIdentificationType. Domain CS — Robotics, Plastics, Tightening — extend those. Vendors then extend the domain CS with their own NodeSet that lives in a higher namespace index.

OPC UA companion specification layered information model from base UA to vendor extensions

Why companion specs are the real Industry 4.0 surface

The shop-floor data integration market has spent the last fifteen years promising that some new layer — first OPC Classic DA, then OPC UA itself, then MQTT, then Sparkplug B — would deliver vendor-neutral semantics. Each delivered a piece. Companion specifications are the layer where the semantic promise actually closes, because they bind a specific browse position to a specific physical meaning under a published governance process. Everything below them is transport; everything above them is application.

The original thesis of this post: companion specifications are where Industry 4.0 actually materializes on the wire, because they convert OPC UA from a transport protocol into a semantic contract. Without them, the protocol guarantees only that the client can read a Double at ns=2;s=Robot1.X — but not that Robot1.X is a tool-center-point X in millimeters in base frame. With a CS, the contract is enforced by the schema.

That single shift collapses two costs that dominate shop-floor integration projects. The first is the per-pair adapter work: a 12-line, 8-cell plant historically needed ~50 PLC-to-MES mapping spreadsheets, one per device variant. With Robotics CS, all 12 robots browse identically, so the OEE collector is one connector, not twelve. The second cost is the MES vendor lock-in: any MES that speaks Robotics CS can plug into any conformant robot, which is the prerequisite for a vendor-neutral unified namespace architecture using HiveMQ and Sparkplug B at the broker layer.

VDMA’s working groups have been the dominant authoring force. As of mid-2026 they coordinate around forty CS — including OPC UA for Plastics and Rubber Machinery (the former Euromap 77 / 83 / 84 suite, now harmonized as OPC 40082, 40083, 40084), OPC UA for Machine Tools (40501-1), and OPC UA for Tightening (40451-1). The pattern is consistent: a domain association brings the use cases, the OPC Foundation reviews the model, the spec ships as a NodeSet2 XML plus PDF.

The third-cost reduction is less obvious but compounds over time: tooling reuse. Once your fleet speaks Robotics CS, the same UaExpert tree, the same Grafana OPC UA datasource queries, the same anomaly-detection feature extractors all work across the fleet without per-device modification. This is the same argument that drove SNMP MIBs in the networking world thirty years ago — semantic standardization at the management surface is what makes a monitoring tool industry possible. Industrial DevOps tooling is just now reaching the inflection point that network monitoring reached around 1998, and CS adoption is the trigger.

The pushback you will hear from a skeptical plant engineer is that CS adoption is slow because the published models are too generic. That is partially true — Robotics CS 1.01 deliberately under-specifies the trajectory and program-execution model so that work-cell and articulated-arm vendors can both implement it. The work-group response in 1.02 draft is to add an optional TrajectoryControl ObjectType that vendors can implement, leaving 1.01 conformance intact. This is the right pattern, and it is why hard-coding NodeIds in your client is a worse mistake than hard-coding BrowseNames.

Designing the NodeSet: the authoring workflow

Authoring a NodeSet by hand-editing XML is technically possible and practically a mistake. The schema has enough cross-references and modelling rules that a single dropped attribute will be caught only at server-load time, often with a cryptic error. Use a graphical modeler. The two viable choices in 2026 are Unified Automation UaModeler (commercial, polished, Windows) and Siemens SiOME (free, less polished, also Windows). VDMA work groups standardize on SiOME because it is free; in-house teams often prefer UaModeler for the diff and merge ergonomics.

The authoring workflow runs from a graphical modeler — SiOME or UaModeler — through NodeSet2 XML export, through schema validation against UANodeSet.xsd, into the OPC Compliance Test Tool, and only then into a release. Skipping the schema-validation step is the single most common reason a vendor’s CS implementation fails CTT later.

OPC UA companion specification NodeSet authoring and certification sequence diagram

Picking a namespace and assigning NodeIds

Each CS owns a namespace URI. Robotics CS uses http://opcfoundation.org/UA/Robotics/. Your custom CS for a hypothetical pick-and-place cell needs its own URI — pick one you control, such as https://example.com/UA/PickPlace/. Inside the NodeSet, every node carries a NodeId with two parts: the namespace index (assigned at load time) and the identifier. Use numeric identifiers for stability — string identifiers are valid but increase XML size by 30-50 percent and have slightly higher browse cost in open62541.

A reasonable NodeId allocation scheme for a 200-node CS:
– 1000-1999: ObjectTypes
– 2000-2999: VariableTypes
– 3000-3999: DataTypes and Enumerations
– 4000-4999: ReferenceTypes
– 5000-9999: Instances and InstanceDeclarations under types

A minimal NodeSet2 XML

Here is a working NodeSet2 fragment that declares one custom ObjectType (PickPlaceCellType) that inherits from the Machinery CS MachineryItemType, with two child Variables. It assumes the Machinery CS NodeSet is loaded first as a dependency.

<?xml version="1.0" encoding="UTF-8"?>
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd">
  <NamespaceUris>
    <Uri>https://example.com/UA/PickPlace/</Uri>
    <Uri>http://opcfoundation.org/UA/Machinery/</Uri>
    <Uri>http://opcfoundation.org/UA/DI/</Uri>
  </NamespaceUris>
  <Models>
    <Model ModelUri="https://example.com/UA/PickPlace/"
           Version="1.0.0"
           PublicationDate="2026-05-28T00:00:00Z">
      <RequiredModel ModelUri="http://opcfoundation.org/UA/Machinery/"
                     Version="1.3.0"
                     PublicationDate="2024-05-15T00:00:00Z"/>
    </Model>
  </Models>

  <UAObjectType NodeId="ns=1;i=1001"
                BrowseName="1:PickPlaceCellType">
    <DisplayName>PickPlaceCellType</DisplayName>
    <References>
      <Reference ReferenceType="HasSubtype" IsForward="false">
        ns=2;i=1004</Reference>
      <Reference ReferenceType="HasComponent">ns=1;i=5001</Reference>
      <Reference ReferenceType="HasComponent">ns=1;i=5002</Reference>
    </References>
  </UAObjectType>

  <UAVariable NodeId="ns=1;i=5001"
              BrowseName="1:CycleCount"
              DataType="UInt32"
              ParentNodeId="ns=1;i=1001"
              AccessLevel="1">
    <DisplayName>CycleCount</DisplayName>
    <References>
      <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
      <Reference ReferenceType="HasModellingRule">i=78</Reference>
      <Reference ReferenceType="HasComponent" IsForward="false">
        ns=1;i=1001</Reference>
    </References>
  </UAVariable>

  <UAVariable NodeId="ns=1;i=5002"
              BrowseName="1:LastPickPose"
              DataType="ns=2;i=3010"
              ParentNodeId="ns=1;i=1001"
              AccessLevel="1">
    <DisplayName>LastPickPose</DisplayName>
    <References>
      <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
      <Reference ReferenceType="HasModellingRule">i=78</Reference>
      <Reference ReferenceType="HasComponent" IsForward="false">
        ns=1;i=1001</Reference>
    </References>
  </UAVariable>
</UANodeSet>

A few things to notice. HasTypeDefinition pointing to i=63 makes CycleCount a BaseDataVariableType instance — that is the standard pattern for a “data” Variable as opposed to a “property” Variable (i=68). HasModellingRule i=78 is Mandatory, which tells any importer that the Variable must be present on every instance of PickPlaceCellType. The IsForward="false" on the HasSubtype reference reverses the direction so the parent type points down to the subtype, which is how nodeset_compiler resolves the inheritance chain.

Validating against the UANodeSet schema

Before you touch a server, validate the XML against the published schema. The schema lives at opcfoundation.org/UA/schemas/1.05/UANodeSet.xsd. On a Linux box:

curl -sLO https://files.opcfoundation.org/schemas/UA/1.05/UANodeSet.xsd
curl -sLO https://files.opcfoundation.org/schemas/UA/1.05/Opc.Ua.NodeSet2.Services.xsd
xmllint --schema UANodeSet.xsd PickPlace.NodeSet2.xml --noout

If xmllint returns “validates”, you have eliminated the entire class of malformed-XML errors. CTT later only checks information-model consistency — duplicate NodeIds, dangling references, missing mandatory Variables — not XML syntax.

Importing the NodeSet into an OPC UA server

A NodeSet is dead XML until a server stack loads it. The loading step is where you commit to a deployment topology — embedded ahead-of-time compilation on a microcontroller, runtime JVM loading on a Linux gateway, or a managed C++ server inside a Windows IPC. Each has real cost and capability differences worth picking deliberately rather than by accident. The three paths below cover roughly 95 percent of production CS implementations in the field today; the rest are either bespoke .NET stacks or proprietary embedded SDKs from controller vendors.

Loading a NodeSet into a server is either a compile-time code-generation step (open62541) or a runtime loader call (Eclipse Milo, Unified Automation C++ SDK). Both end in an in-memory AddressSpace with the same browse semantics; the choice between them is mostly a deployment one.

OPC UA companion specification server-side import flow from XML to AddressSpace

open62541: ahead-of-time NodeSet compilation

open62541 is the most common embedded choice — Apache 2.0, C99, builds for ARM Cortex-M down to ~120 kB ROM. Its tools/nodeset_compiler/nodeset_compiler.py script consumes one or more NodeSet2 XML files and emits a pair of C source and header files that statically declare every node. You then link those into your server binary. This avoids runtime XML parsing entirely, which matters on devices without a filesystem.

# Clone open62541 (v1.4.x is current as of 2026-05)
git clone --branch v1.4.10 https://github.com/open62541/open62541.git
cd open62541

# Run the nodeset_compiler against the dependency chain
python3 tools/nodeset_compiler/nodeset_compiler.py \
    --types-array=UA_TYPES \
    --existing tools/schema/Opc.Ua.NodeSet2.Reduced.xml \
    --xml deps/Opc.Ua.Di.NodeSet2.xml \
    --xml deps/Opc.Ua.Machinery.NodeSet2.xml \
    --xml PickPlace.NodeSet2.xml \
    PickPlace_nodeids

The output PickPlace_nodeids.c and .h define a single function, UA_StatusCode PickPlace_nodeids(UA_Server *server), which when called populates the server’s AddressSpace with every node declared across all the XML inputs. The minimal C server that boots this is short.

#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <signal.h>
#include "PickPlace_nodeids.h"

static volatile UA_Boolean running = true;
static void stopHandler(int sig) { running = false; }

int main(void) {
    signal(SIGINT, stopHandler);
    UA_Server *server = UA_Server_new();
    UA_ServerConfig *cfg = UA_Server_getConfig(server);
    UA_ServerConfig_setMinimal(cfg, 4840, NULL);

    /* Inject the compiled NodeSet (DI + Machinery + PickPlace). */
    UA_StatusCode rc = PickPlace_nodeids(server);
    if (rc != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(cfg->logging, UA_LOGCATEGORY_SERVER,
                     "NodeSet load failed: %s", UA_StatusCode_name(rc));
        UA_Server_delete(server);
        return 1;
    }

    UA_Server_runUntilInterrupt(server);
    UA_Server_delete(server);
    return 0;
}

Build with gcc server.c PickPlace_nodeids.c -lopen62541 -o pp_server. On boot, the server listens on opc.tcp://0.0.0.0:4840. UaExpert pointed at that endpoint will see, under the Objects folder, every type defined in the three NodeSets, including your PickPlaceCellType.

Eclipse Milo: runtime loading on the JVM

For server-side MES gateways or Linux x86 deployments where startup time is fine and you want to swap NodeSets without recompiling, Eclipse Milo is the workhorse. Milo’s UaNodeSetLoader reads a NodeSet2 XML at runtime and populates the live AddressSpace.

OpcUaServer server = new OpcUaServer(config);
server.startup().get();

UaNodeContext ctx = server.getAddressSpaceManager().getNodeContext();
NodeSet2Parser parser = new NodeSet2Parser();
try (InputStream di = getClass().getResourceAsStream("/Opc.Ua.Di.NodeSet2.xml");
     InputStream mc = getClass().getResourceAsStream("/Opc.Ua.Machinery.NodeSet2.xml");
     InputStream pp = getClass().getResourceAsStream("/PickPlace.NodeSet2.xml")) {
    parser.parse(ctx, di);
    parser.parse(ctx, mc);
    parser.parse(ctx, pp);
}

Load order matters. If PickPlace.NodeSet2.xml references types defined in Machinery, Machinery must be parsed first or the loader will throw UnknownReferenceTargetException. The XML’s <RequiredModel> declarations are advisory — Milo does not auto-resolve dependencies.

Mapping companion spec nodes to PLC tags

The boundary between the information model and the controller is where most integration projects accumulate technical debt. The model defines the contract; the controller defines the truth. Bridging them cleanly means treating the mapping as configuration, exposing it for review, and binding lazily — never copying values into the AddressSpace ahead of demand. Done well, the mapping survives PLC firmware upgrades and CS revisions without code changes. Done poorly, every new device in the cell triggers a recompile.

The information model says what the nodes mean. The mapping step says where their values come from. In production you almost never write Variable values from C — you bind them to PLC tags via a DataSource callback so that every read or subscription notification fetches the current value from the controller.

PLC to OPC UA companion specification mapping topology Siemens Rockwell Beckhoff

Siemens S7-1500 over the embedded UA server

S7-1500 firmware 2.8.1 and newer ships with a built-in OPC UA server. The simplest topology is: S7-1500 exposes its DB tags as UA Variables in ns=3 under DeviceSet/PLC_1/DataBlocksGlobal/; your aggregating server on a Linux gateway browses them and re-exposes them under the Robotics CS browse path.

The aggregation pattern in open62541 uses the client API for the upstream connection and a DataSource callback for the downstream Variable.

typedef struct {
    UA_Client *plc;
    UA_NodeId  plc_node;     /* e.g. ns=3;s="DB_Robot"."ActualX" */
} PLCBinding;

static UA_StatusCode
readFromPLC(UA_Server *srv, const UA_NodeId *sid, void *sctx,
            const UA_NodeId *nid, void *nctx, UA_Boolean source_ts,
            const UA_NumericRange *range, UA_DataValue *out) {
    PLCBinding *b = (PLCBinding *)nctx;
    UA_Variant v;
    UA_Variant_init(&v);
    UA_StatusCode rc = UA_Client_readValueAttribute(b->plc, b->plc_node, &v);
    if (rc != UA_STATUSCODE_GOOD) {
        out->status = rc;
        out->hasStatus = true;
        return UA_STATUSCODE_GOOD;
    }
    out->value = v;
    out->hasValue = true;
    out->sourceTimestamp = UA_DateTime_now();
    out->hasSourceTimestamp = true;
    return UA_STATUSCODE_GOOD;
}

/* Wire it up after PickPlace_nodeids() runs. */
UA_DataSource ds = { .read = readFromPLC, .write = NULL };
UA_Server_setVariableNode_dataSource(server,
    UA_NODEID_NUMERIC(1, 5002),   /* LastPickPose */
    ds);
UA_Server_setNodeContext(server,
    UA_NODEID_NUMERIC(1, 5002),
    &g_plc_bindings.lastPickPose);

The DataSource pattern means the value is never copied into the server’s AddressSpace unless a client actually asks. For 5 ms cyclic robotics data this matters: a 6-DOF pose at 200 Hz with naive write-through is 1200 AddressSpace writes per second per robot. With ten robots in a cell, the AddressSpace mutation rate dominates server CPU. A pull-on-demand DataSource brings that to whatever the subscription publishing rate is — typically 100 ms for HMI, 1000 ms for OEE collection — which is two orders of magnitude lower.

The other reason DataSource matters is timestamp honesty. sourceTimestamp is supposed to be when the value was sampled at the source, not when the server cached it. With a DataSource you can fetch the timestamp from the PLC side along with the value — Siemens S7-1500 exposes it as a sibling Variable — and propagate it. Naive write-through stamps the cache time, which is almost always wrong by tens of milliseconds and breaks any downstream causal analysis.

Rockwell ControlLogix and Beckhoff TwinCAT

Rockwell does not ship an OPC UA server in ControlLogix. The standard approach is a gateway — Kepware KEPServerEX, Matrikon FLEX, or open-source HighByte Intelligence Hub — which speaks CIP / EtherNet/IP southbound and OPC UA northbound, then a second hop into your aggregating server that overlays the CS browse path on top of the gateway’s flat tag list.

Beckhoff is the easiest of the three: the TwinCAT OPC UA Server is a TwinCAT supplement and exposes GVL symbols directly. Bind by symbol name: nsu=urn:BECKHOFF:TcOpcUaServer;s=PLC1.GVL.RobotPose[0].

The mapping table belongs in configuration, not code. A YAML format that survives review and is consumed by your aggregator at boot:

- cs_node: "ns=1;i=5002"        # PickPlaceCellType/LastPickPose
  plc_endpoint: "opc.tcp://192.168.10.10:4840"
  plc_node: 'ns=3;s="DB_Robot"."LastPick"'
  sampling_ms: 100
  data_type: "ns=2;i=3010"      # MotionDeviceSystem CartesianCoordinatesType

- cs_node: "ns=1;i=5001"        # PickPlaceCellType/CycleCount
  plc_endpoint: "opc.tcp://192.168.10.10:4840"
  plc_node: 'ns=3;s="DB_Robot"."Cycles"'
  sampling_ms: 1000
  data_type: "i=7"              # UInt32

Validating with UaExpert and the Compliance Test Tool

Validation discipline is what separates a CS implementation that ships and a CS implementation that lingers in customer-acceptance limbo. The cost of skipping smoke tests is measured in customer-site debug days, which are roughly thirty times more expensive than lab debug days. A two-stage validation strategy — UaExpert in the lab, CTT before the certification audit — converts almost all field defects into in-house defects. The third stage, real-world client interoperability, is where things still occasionally break: write a small Eclipse Milo client that walks the type tree from BrowseName, not NodeId, and use it as a cross-stack sanity check before any field demo.

Validation has two distinct phases. UaExpert is the smoke test — does the model load, does it browse, can a subscription return values. The Compliance Test Tool is the certification step — does the model match the published CS exactly, with every Mandatory node present, every reference correctly directed, and every DataType bound.

OPC UA companion specification conformance and certification state machine

UaExpert smoke test

Free download, connects to opc.tcp://localhost:4840, anonymous session. After connect:

  1. Expand Root > Objects > DeviceSet. You should see your instance with HasTypeDefinition pointing back to PickPlaceCellType from ns=1.
  2. Right-click LastPickPose, choose Properties. Confirm DataType is CartesianCoordinatesType (ns=2;i=3010), not a plain Double.
  3. Drag LastPickPose into the Data Access view. The value should update at the configured sampling rate.
  4. Right-click PickPlaceCellType and view References. Every Mandatory child should be present with HasModellingRule = Mandatory.

If UaExpert displays a Variable as BadTypeDefinitionInvalid, the HasTypeDefinition reference is broken — usually a typo in the NodeId after a re-export.

The OPC Compliance Test Tool

CTT is the only path to the OPC Foundation “OPC UA Certified” logo. It runs as a Windows desktop application; the test scripts are CSV-driven and selectable per CS. A typical certification matrix for Robotics CS 1.01 runs the Core Server profile (about 450 test cases), the DataAccess profile (about 200), and the Robotics-specific profile add-ons (about 60), for a total of around 12 hours of automated testing on a reference build.

Common failure modes the team will hit on first run:

  • Test 005.001: Browse from ObjectsFolder fails because a Mandatory InstanceDeclaration was omitted on an instance. Fix in the NodeSet, re-compile, re-run.
  • Test 011.002: TranslateBrowsePathsToNodeIds fails because BrowseNames in instances do not match the type’s InstanceDeclaration BrowseName. Match exactly.
  • Test 012.005: ReferenceType direction fails when IsForward is set wrong on a HasComponent. Inverse direction breaks browse semantics.

For an end-to-end view of how OPC UA Robotics fits into the broader OPC UA FX field exchange reference architecture, look at how the same NodeSet plus the FX UDT profile combines to give controller-to-controller deterministic communication on top of CS-defined semantics.

A CTT failure is rarely a one-line fix. The typical remediation loop is: read the test report (CTT writes a per-test XML and HTML log), find the offending NodeId, open the source NodeSet in your modeler, fix, re-export, re-run nodeset_compiler, restart the server, re-run the failing test in isolation. Budget twenty minutes per failure once you have the workflow tight; the first few will take an hour while you build muscle memory. Most teams build a small shell wrapper that does the compile-restart-test cycle as one command, which is the productivity inflection point.

Worked example: a four-node Robotics CS profile

The previous sections covered authoring, importing, and binding in the abstract. The worked example below pins each step to a concrete shop-floor scenario — a single SCARA pick-and-place cell running on a Siemens S7-1500 with a TIA Portal V18 program — so you can map the explanation onto a real device pair. Every NodeId, every BrowseName, and every PLC tag in the table is what a working integration looks like in production, not a stylized abstraction.

The Robotics CS 1.01 root is MotionDeviceSystemType. The minimum viable profile for a single-arm cell needs four nodes underneath:

  1. MotionDevices/1 — typed as MotionDeviceType. Carries ParameterSet, Manufacturer, Model, SerialNumber.
  2. Controllers/1 — typed as ControllerType. Carries Software (folder of SoftwareType).
  3. SafetyStates/1 — typed as SafetyStateType. Carries EmergencyStop and ProtectiveStop Booleans.
  4. TaskControls/1 — typed as TaskControlType. Carries TaskProgramName, ParameterSet/CurrentJointPositions.

In the NodeSet this is roughly 80 nodes after expanding mandatory Variables. The PLC-side data behind it: 36 bytes of joint-position floats sampled at 200 Hz, plus a handful of Booleans and Strings at 1 Hz. The total OPC UA upstream payload at steady state is around 30 kbit/s per robot — well within the 100 Mbit field network budget.

The vendor extension namespace adds whatever is not in the standard. For a SCARA pick-and-place that needs throughput telemetry, you might add an extension ObjectType PickThroughputType with Variables PicksPerMinute and RejectRate, hung off MotionDeviceSystemType via HasComponent. Clients that don’t know your extension simply don’t browse those nodes; clients that do can use them. That is the brownfield migration model: ship a compliant base CS, layer extensions for the things that have not been standardized yet, and contribute extensions back to the work group when they stabilize.

Putting the four-node profile and the vendor extension together into a concrete browse path makes the structure clearer. An OEE collector connecting to the cell sees:

Root/Objects/DeviceSet/
  PickPlaceCell_01/                   (PickPlaceCellType, ns=1)
    Identification/                   (inherited from MachineIdentificationType)
      Manufacturer = "ExampleCorp"
      Model        = "PP-200"
      SerialNumber = "PP200-0001"
    MotionDeviceSystem/               (MotionDeviceSystemType, ns=2)
      MotionDevices/
        1/                            (MotionDeviceType)
          ParameterSet/
            ActualPose                (CartesianCoordinatesType)
            ActualSpeed               (Double, m/s)
      Controllers/
        1/                            (ControllerType)
          Software/
            1/ (Name="ControlOS", SoftwareRevision="3.1.2")
      SafetyStates/
        1/ (EmergencyStop=False, ProtectiveStop=False)
      TaskControls/
        1/ (TaskProgramName="PickRoutine_A")
    CycleCount = 4821                 (vendor ext, ns=1)
    LastPickPose                      (vendor ext, ns=1)
    Throughput/                       (vendor ext PickThroughputType, ns=1)
      PicksPerMinute = 38.2
      RejectRate     = 0.012

That tree is what UaExpert renders, what the CTT scripts walk, and what every MES connector built on Robotics CS knows how to consume. No custom adapter, no per-vendor spreadsheet. The vendor-extension nodes are visible to anyone but only consumed by collectors that understand them — the additive model preserves base conformance.

Trade-offs and failure modes

Every architectural choice has a corresponding cost surface, and CS adoption is no exception. The published specs are useful, the tooling has matured, and the OPC Foundation’s review process is rigorous — but you still take on a real engineering tax in exchange for the interoperability win. The list below is the honest accounting from real projects, not vendor marketing. Read it before committing a roadmap.

Companion specifications are not free. The honest cost ledger:

  • Modeling overhead. A 200-node CS takes a competent modeler a

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *