Sterfive Specification

OPC UA YAML DSL

A human-friendly, declarative authoring format for OPC UA NodeSet2 information models
Document version: 0.1 (public draft) · Status: Working Draft · Date: 2026-09-19 · Editor: Etienne Rossignon, Sterfive SAS

Abstract

This specification defines the Sterfive OPC UA YAML DSL, a declarative, human-readable authoring format that compiles to conformant OPC UA NodeSet2 XML information models. The DSL is intended as a generic, vendor-neutral specification and is open to multiple, independent implementations. It expresses the full OPC UA address-space vocabulary — ObjectTypes, VariableTypes, DataTypes (basic, enumeration, structure, union, option set), ReferenceTypes, InterfaceTypes, EventTypes, FiniteStateMachine types, instances, methods, add-ins and additional references — while shielding the author from the syntactic burden of NodeSet2 XML and from explicit numeric NodeId assignment.

The semantics of the produced address space are anchored in the OPC UA standard, and in particular in [OPC 10000-3] Address Space Model and [OPC 10000-5] Information Model. Where this specification constrains a feature more tightly than the OPC UA standard (for example by requiring a syntactic convention), the constraint is called out explicitly.

The companion JSON Schema artefact published as schemas/nodeset2.schema.json is the machine-readable representation of the syntactic rules in this specification and is normative for syntactic validation. This document is normative for the semantics, the cross-property invariants and the equivalence between a DSL document and the OPC UA address space it produces.

Status of This Document

This is a Sterfive Working Draft published for public review. The specification may be updated, replaced or made obsolete by Sterfive at any time. Publication as a Sterfive Working Draft does not imply endorsement by, or affiliation with, the OPC Foundation.

References to OPC UA terminology and node classes are provided for interoperability. OPC, OPC UA and NodeSet2 are trademarks of the OPC Foundation; their use in this document is purely descriptive and refers to the format the DSL targets.

Changes in This Revision

Revision 0.1 (2026-09-19) brings the specification in line with what the schema and the reference processor accept, found by round-tripping the OPC Foundation companion NodeSets:

1Introduction

OPC UA address spaces are conventionally distributed as NodeSet2 XML documents. NodeSet2 XML is faithful to the OPC UA Address Space Model defined in [OPC 10000-3] but is verbose, tightly coupled to numeric NodeIds, and laborious to author or review. The Sterfive OPC UA YAML DSL provides a compact, declarative representation of the same model:

A DSL document is consumed by a Conformant Processor which produces a NodeSet2 XML document. Implementations may additionally produce TypeScript declaration files, Markdown or PDF documentation; those auxiliary artefacts are out of scope of this specification.

1.1Positioning: an authoring format, not an interchange format

The DSL is a source format, in the tradition of the OPC Foundation's own ModelDesign.xml: a compact description that a compiler turns into a NodeSet2 document, with the assigned NodeIds kept in a companion CSV so that regeneration is stable. NodeSet2 XML (and the JSON forms defined by [OPC 10000-6]) remains the only interchange format: it is what a Conformant Processor emits, what other tools consume, and what a working group commits and reviews. A DSL document is never required for interoperability, and a tool that has no knowledge of this specification loses nothing. The DSL exists to make the model easier to write, review in version control, and generate with assistance; it does not compete with NodeSet2 and is not proposed as a replacement for it.

NOTE

Self-contained profile. A document and its symbol table are two files, which is the right shape for authoring (the CSV is generated, the YAML is hand-written) but not for exchange. The companion Self-Contained Profile specification (draft, profile: self-contained) packs a model, its NodeId symbol table and what it needs from its dependencies into one YAML stream, for the cases where a single file is preferable.

namespaceUri: http://acme.com/UA/TemperatureSensor
version: 1.0.0
publicationDate: 2026-01-01T00:00:00.000Z
namespaces:
  - di
objectTypes:
  - browseName: TemperatureSensorType
    subtypeOf: di:DeviceType
    components:
      - browseName: di:ParameterSet
        components:
          - browseName: Temperature
            typeDefinition: ua:AnalogItemType
            dataType: ua:Double
            engineeringUnits: "degree Celsius"
instances:
  - browseName: Sensor01
    typeDefinition: TemperatureSensorType
    organizedBy: /ua:Objects/di:DeviceSet

2Conformance

This document uses normative keywords as defined by RFC 2119 / RFC 8174. The terms must, must not, required, shall, shall not, should, should not, recommended, may and optional, when typeset in this manner, denote the corresponding requirement levels.

This specification defines two conformance classes:

Conformant Document
A YAML 1.2 document whose root mapping satisfies every must requirement in sections 4 through 18.
Conformant Processor
An implementation that, given a Conformant Document, produces a NodeSet2 XML document NodeSet2-equivalent to the address space described by the document, and rejects with a diagnostic any document that violates a must requirement of this specification.
NORMATIVE

A Conformant Processor must implement sections 4–15 in full and must reject documents that contain unknown root properties or unknown sub-properties flagged by this specification as having additionalProperties: false. Sections 16–18 (Events, State Machines, Initializers) describe optional features. A processor that does not implement an optional feature must reject documents that use it rather than silently ignore the feature.

3Terminology

document
A YAML 1.2 file whose root is a mapping conforming to this specification.
own namespace
The namespace whose URI is given by the namespaceUri property at the document root. New nodes defined by the document are created in this namespace.
imported namespace
A namespace declared through namespaces or imports. Its nodes are visible to the document but not redefined by it.
browse name
A qualified name. Written as a bare PascalCase identifier when in the own namespace, or as alias:Name otherwise.
placeholder browse name
A browse name surrounded by <>, e.g. <ImageId>. It denotes a member created by an OptionalPlaceholder or MandatoryPlaceholder modelling rule.
browse path
A solidus-separated sequence of browse names. Browse paths starting with / are absolute and rooted at ua:RootFolder; paths starting with $Identifier/ are anchored at a node defined in the same document; otherwise the path is relative to the closest enclosing context.
connected object
An instance linked to its parent through exactly one of the connection properties listed in §14.1.
instance
An OPC UA Object or Variable that lives in the address space at run time, as opposed to a Type which is a template.
add-in
A reusable Object aggregate, instantiated by reference to its typeDefinition rather than by writing out its members, with browse-name resolution rules described in §12.
symbol table
A CSV file (conventionally named <model>.symbols.csv) that stores the auto-assigned NodeId of every node in the document. Persistence of NodeIds across regeneration is achieved by consulting and updating this file.

4Document Structure

The root of a document is a mapping. namespaceUri is required; every other property is optional. A processor must reject a root property not listed here.

Property Type Section / meaning
namespaceUri string The URI of the own namespace.
version string The model version, written to the NodeSet2 Model element.
publicationDate date The model publication date.
profile string self-contained: the file is a YAML stream carrying the model, its symbol table and its dependencies (see the self-contained profile).
namespaces, imports array The namespaces the document builds on (Namespace Imports).
documentation mapping Rendered documentation only (title, author, header, options.diagrams); no effect on the address space.
lint mapping Per-document diagnostic policy: silence drops the listed codes or code prefixes, error escalates warnings to errors (Diagnostics).
outputBinaryTypeDictionary boolean Also emit the OPC UA 1.03 binary and XML TypeDictionary nodes. Default false.
dataTypes array DataTypes.
referenceTypes array ReferenceTypes.
objectTypes, variableTypes array ObjectTypes and VariableTypes.
interfaceTypes array InterfaceTypes.
eventTypes array Events.
stateMachines array State Machines.
instances array Instances.
objects array Low-level Objects, created as written: the members of their typeDefinition are not instantiated. Use instances for an instance of a type.
namespaceMetadatas array The NamespaceMetadata Object of the own namespace; typeDefinition defaults to ua:NamespaceMetadataType and the parent to /ua:Objects/ua:Server/ua:Namespaces.
references array Additional References.
initializers array Initializers.

The collections are processed in dependency order, not document order (Processing Model): a type may reference a type declared later in the same document.

5Lexical Conventions

5.1Browse names and placeholders

A bare browse name must match the regular expression ([A-Za-z0-9]+:)?(([A-Z][a-zA-Z0-9]*)|(<[A-Z][a-zA-Z0-9°#]*>)). The optional alias: prefix selects an imported namespace; without it the browse name belongs to the own namespace. PascalCase should be used for the local part. English should be preferred over other languages when authoring browse names.

The browse name of a namespaceMetadatas entry is the namespace URI itself (http://…), as [OPC 10000-5] names a NamespaceMetadata Object; a URI should not be used as a browse name anywhere else.

A placeholder browse name is enclosed in <>, e.g. <ItemId>. It must appear only on a member that declares modellingRule: OptionalPlaceholder or modellingRule: MandatoryPlaceholder.

The alias ua is reserved for the OPC UA standard namespace http://opcfoundation.org/UA/. A document must not redefine ua. Aliases are case-sensitive.

5.2Browse paths and anchors

A browse path is a string of solidus-separated browse names matching (/(alias:)?Name)+ with an optional anchor prefix.

Form Meaning
/ua:Objects/... Absolute path rooted at ua:RootFolder.
$Robot1/Axes Anchored path: $Robot1 resolves to the instance of this document whose anchor is Robot1 (see below).
Robot1/Axes Relative path; the resolver searches first the current parent, then sibling instances of the document by browse name.

An instance's anchor is its $anchor property when given (an identifier, [A-Za-z_][A-Za-z0-9_]*), otherwise the local part of its browse name; a processor must append a numeric suffix to make duplicate anchors unique and should report the duplicate. Authors write $anchor to keep a path stable when a browse name repeats.

A Conformant Processor must reject a document if any browse path cannot be unambiguously resolved.

5.3Namespace aliases

The following aliases are reserved by this specification and resolve to companion-spec namespaces published by the OPC Foundation. A document must not use any of them as a custom imports[].alias.

adi, amb, autoId, cnc, commercialKitchenEquipment, di, fxAc, fxCm, fxData, gds, glass, ia, ijtBase, iolink, iolinkIODD, irdi, isa95JobControl, machinery, machineryJobs, machineryProcessValues, machineryResult, machineTool, machineVision, metalForming, packML, padim, robotics, standard, surfaceTechnologyGeneralTypes, surfaceTechnologyOctMss, tightening, woodWorking.

5.4NodeId assignment

Numeric NodeIds should not be authored by hand. A Conformant Processor must assign a fresh numeric NodeId to every node that lacks an explicit nodeId, persisting the assignment in the symbol table so it remains stable across regenerations.

When required (for example to interoperate with an external system that hard-codes a NodeId), a top-level instance may declare an explicit nodeId string matching one of:

The namespace prefix ns=<index>; must not appear: the namespace is fixed by the own namespace. When an instance declares a string nodeId, every node materialised below it, by instantiation of its type or inline, derives its string NodeId from its parent's, joined by -:

instances:
  - browseName: Foo
    typeDefinition: FooType
    organizedBy: /ua:Objects
    nodeId: s=Foo          # Foo            → ns=1;s=Foo
                           # its Motor      → ns=1;s=Foo-Motor
                           # Motor's Speed  → ns=1;s=Foo-Motor-Speed

nodeId is an instance key. The members of an instance are created by instantiation and have no entry of their own to carry one; types, their members and DataTypes take the numeric NodeIds of the symbol table. A nodeId written anywhere but on a top-level instance is not applied.

5.5Localized text

A property typed as LocalizedText accepts either:

6Namespace Imports

The namespaces property declares an ordered list of well-known companion-spec aliases (see §5.3). Order matters: a namespace must appear after every namespace it depends on. For example, because adi depends on di, the order [di, adi] is required.

The imports property declares additional, non-well-known imports. Each entry must be a mapping containing alias plus at least one of uri or filename:

Key Type Card. Notes
alias string 1 Short name used to qualify browse names; case-sensitive; must not collide with a reserved alias.
uri URI string 0..1 Absolute namespace URI — the identity of the import. Resolution matches it against each candidate's <Model ModelUri>.
filename string 0..1 Path to the NodeSet2 XML, resolved relative to the DSL document. A hint: if it is missing or stale and uri is given, uri is used.
publicationDate date 0..1 Require a nodeset published on or after this date (OPC UA's PublicationDate >= required rule). Not a version range.
sha256 string 0..1 Integrity pin: the sha256 of the exact file expected. A mismatch must be rejected: a pin that only warned would not pin anything.

An import that names only a uri is resolved by scanning, in order, the document's own folder and a ./deps/ folder beside it, reading each candidate's <Model ModelUri> header. Filenames inside ./deps/ are therefore irrelevant — what identifies a nodeset is what it declares itself to be, which means the same thing on every machine, whereas a path does not.

When both are given, filename is tried first and the loaded ModelUri is verified against the declared uri; a mismatch is reported as a warning and the file is still loaded, because uri was informative-only in earlier versions and incorrect values exist in existing models.

A filename that is not a NodeSet2 XML is rejected with a diagnostic naming the file. In particular, a YAML model cannot be imported directly — compile it to a NodeSet2 first (opcua-modeler generate) and import the result.

{.props}

namespaces:
  - di
  - ia
  - machinery

imports:
  - alias: ts
    uri: http://acme.com/UA/TemperatureSensor/
    filename: ../temperature_sensor.NodeSet2.xml

7DataTypes

DataTypes describe the structure of Variant values, as defined in [OPC 10000-3] §5.8. A DataType definition must declare browseName. It may declare any of subtypeOf, isAbstract, description, plus the kind-specific properties below. The kind of a DataType is inferred from the keys present, as defined in §7.1–§7.5.

7.1Basic DataType

A simple alias of an existing DataType. The mapping must declare subtypeOf and must not declare fields, enum, isUnion or isOptionSet.

7.2Enumeration

Enumerations are subtypes of ua:Enumeration as described in [OPC 10000-3] §5.8.3. An enumeration may be declared with or without an explicit subtypeOf: ua:Enumeration; the latter form is implied when an enum property is present. The enum property accepts either:

  1. an array of bare strings — values are auto-assigned starting at 0; or
  2. an array of mappings each with at least name and value, optionally description and displayName.

Within a single enumeration the set of names must be unique and the set of integer values must be unique.

dataTypes:
  - browseName: SoftwareVersionFileType
    subtypeOf: ua:Enumeration
    enum:
      - name: Current
        value: 0
        description:
          text: The currently used version.
      - name: Pending
        value: 1
        description:
          text: The pending version that could be installed.
      - name: Fallback
        value: 2

7.3Structure

A DataType with a fields array is a Structure. Each field must declare name and dataType; it may declare description, valueRank, arrayDimensions, isOptional and allowSubTypes.

allowSubTypes: true lets the field hold a subtype of its dataType (the NodeSet's AllowSubTypes): the Structure becomes a StructureWithSubtypedValues ([OPC 10000-3] §8.51). A Structure should not mix allowSubTypes and isOptional fields; the two select different encodings, and isOptional wins.

A Structure DataType may reference itself or a sibling defined later in the same document (forward references are permitted; see §19).

dataTypes:
  - browseName: MyType
    fields:
      - name: Field1
        dataType: ua:Double
        valueRank: 1

7.4Union

A Structure with isUnion: true denotes a discriminated Union as defined in [OPC 10000-3] §5.8.4: at most one field carries a value at run time. isOptional must not appear on a Union field.

7.5Option set

A DataType with isOptionSet: true is an OptionSet: a bit mask, in the UInteger form of [OPC 10000-3] §5.8. Its subtypeOf must be ua:Byte, ua:UInt16, ua:UInt32 or ua:UInt64 (or a subtype of one), and defaults to ua:UInt32. Its enum names the bits, with the same two forms as an Enumeration, but each value is the mask of one bit (1, 2, 4, 8, …), so that values combine with |. Without values, the masks follow the order of the list.

dataTypes:
  - browseName: PermissionsType
    subtypeOf: ua:UInt32
    isOptionSet: true
    enum:
      - name: Read
        value: 1
      - name: Write
        value: 2
      - name: Execute
        value: 8
        displayName: Run

A Conformant Processor must reject a value that is not a single bit, two entries on the same bit, a bit beyond the width of subtypeOf, and an OptionSet that also declares fields or isUnion. It must also reject an enum on an unsigned integer that does not declare isOptionSet.

In the NodeSet, the Definition carries IsOptionSet="true" and each field's Value is the bit position (the mask 8 is Value="3"), as in the standard's own AccessLevelType. The DataType gets an OptionSetValues Property: one LocalizedText per bit, the entry's displayName or else its name, with an empty text for a bit the OptionSet does not use.

A value of an OptionSet DataType (in value, propertyValues or an initializer) may be written as its flag names combined with | (Read | Write), as a list of names, or as the number itself. This holds for the OptionSets of imported namespaces too (ua:AccessLevelType: CurrentRead | CurrentWrite). A processor must reject a name that is not a flag, and a number that sets a bit no flag defines.

The structured form of §5.8 (a subtype of the ua:OptionSet Structure, with value and validBits) is not expressed by this key.

7.6Companion VariableType

A Structure DataType may request the automatic creation of a companion VariableType by setting hasVariableType: true. The companion VariableType browse name is derived by replacing the trailing DataType suffix with Type (e.g. MyStuffDataType → MyStuffType). When this convention does not apply, the processor must still emit a deterministic browse name and document the rule.

Each field becomes a Variable of the companion VariableType, referenced by HasStructuredComponent ([OPC 10000-3] §7.22) and typed BaseDataVariableType, unless the field's DataType has a companion VariableType of its own, which is then instantiated. A field is Optional when the Structure has optional fields and the field is one of them, and Mandatory otherwise. A scalar Structure field whose DataType has no VariableType carries its own fields the same way, one level deep.

variableType refines the generated VariableType, and implies hasVariableType: true. Its components and properties entries follow two rules: an entry that names a generated member adjusts it (modellingRule, dataType, valueRank, and nested components for that member's own fields), and any other entry adds a member.

dataTypes:
  - browseName: ResultDataType
    hasVariableType: true
    fields:
      - name: ResultMetaData
        dataType: ResultMetaDataType
      - name: ResultContent
        dataType: ua:BaseDataType
        valueRank: 1
    variableType:
      components:
        - browseName: ResultContent       # a generated field: only the rule changes
          modellingRule: Optional
        - browseName: ReducedResultContent # no field: a member of its own
          dataType: ua:BaseDataType
          valueRank: 1
          modellingRule: Optional

8ReferenceTypes

A ReferenceType definition must declare browseName and should declare subtypeOf (default ua:NonHierarchicalReferences when omitted). It may declare inverseName, symmetric, isAbstract and description. The reserved hierarchical references of OPC UA Part 3 (HasChild, HasComponent, …) must not be redefined.

symmetric: true declares a ReferenceType that means the same in both directions ([OPC 10000-3] §5.3.3), such as DI's ConnectsTo; it is written Symmetric="true" in the NodeSet. A symmetric ReferenceType must not declare inverseName.

When symmetric is omitted, a ReferenceType with an inverseName other than its own browse name is not symmetric; one without an inverseName, or whose inverseName is its own browse name, is symmetric, except that a Has… name without one gets the Is…Of inverse name and a warning. symmetric: false without inverseName declares a non-symmetric type with no inverse name, as some companion specifications do (machineVision's FromTransition), although [OPC 10000-3] expects an inverse name on a concrete non-symmetric type.

9ObjectTypes and VariableTypes

An entry of objectTypes describes one OPC UA ObjectType. The mapping must declare browseName; if subtypeOf is omitted the type inherits from ua:BaseObjectType. variableTypes entries follow the same shape with the addition of dataType, valueRank, arrayDimensions and value (the access attributes of §9.2 belong to Variables, not to VariableTypes); an unspecified subtypeOf defaults to ua:BaseDataVariableType and dataType is required for non-abstract VariableTypes.

Property Type Notes
browseName browse name Required.
subtypeOf type reference Default per node-class.
isAbstract boolean Default false.
description localized text Markdown permitted in the long form.
documentation string Markdown shown in the rendered docs only.
specRef URI, or false The node's NodeSet2 <Documentation> link. See below.
defaultInstanceBrowseName browse name See §9.4.
properties array HasProperty children. See §9.1.
components array HasComponent children.
methods array UAMethod members. See §13.
organizes array FolderType-like children (Organizes).
orderedComponents array HasOrderedComponent children. See §9.3.
physicalComponents array HasPhysicalComponent children.
containedComponents array HasContainedComponent children.
attachedComponents array HasAttachedComponent children.
interfaces array InterfaceTypes implemented by this type. See §11.
addIns array Add-Ins. See §12.
references array Per-node additional references. See §15.
generatedEvents array of EventType reference ObjectTypes and state machines. See §16.
eventSources array of browse path HasEventSource to each of the type's own members. See §16.

specRef is a link into the specification text that defines the node, such as https://reference.opcfoundation.org/Machinery/v104/docs/8.2. It applies to every node (types, members, methods, DataTypes, ReferenceTypes, instances), is written to the NodeSet2 as the node's <Documentation> element and comes back from the reverse. It is not documentation, which is Markdown for the rendered documents and never reaches the NodeSet2.

A member that states no specRef and is the copy of a type's member — a component of an instance, or of a member typed by another type — takes the specRef of that member when both are in the same namespace, and none when the type belongs to another namespace. specRef: false gives such a copy no link. This is what the companion specifications do: over the 33 NodeSets of node-opcua-nodesets a copy of a same-namespace member carries the member's link 95 times, none 51 times and another link once, and a copy of another namespace's member never carries one (0 of 239).

{.props}

9.1Aggregate members

Aggregate properties translate to standard hierarchical ReferenceTypes defined in [OPC 10000-3] §7.

Property OPC UA ReferenceType Permitted children
properties HasProperty Variables only (NodeClass Variable typed by PropertyType per [OPC 10000-3] §5.6.2).
components HasComponent Variables, Objects or Methods.
orderedComponents HasOrderedComponent Variables or Objects.
methods HasComponent (NodeClass Method) UAMethod children.
organizes Organizes Objects (typically FolderType-rooted), Variables or Properties (the DI FunctionalGroup pattern). One with a modelling rule is an InstanceDeclaration like any aggregate: an instance gets it, and optionals may name members below it.

Each child must declare browseName. A child whose typeDefinition is omitted inherits the default for its node-class (ua:BaseDataVariableType for Variables, ua:BaseObjectType for Objects). A child whose nodeClass is omitted has its node-class inferred from typeDefinition when possible, then from the presence of dataType (Variable) or its absence (Object). Authors may nevertheless write nodeClass: Object | Variable | Method explicitly, and a processor must reject inconsistencies between the explicit node-class and the inferred one.

objectTypes:
  - browseName: MySensorType
    components:
      - browseName: MyMandatoryComponent
        typeDefinition: ua:BaseObjectType
      - browseName: MyOptionalComponent
        typeDefinition: ua:BaseObjectType
        modellingRule: Optional
      - browseName: MyComponentWithNoModelingRule
        typeDefinition: ua:BaseObjectType
        modellingRule: false
      - browseName: <MyOptionalPlaceholderComponent>
        typeDefinition: ua:BaseObjectType
        modellingRule: OptionalPlaceholder
      - browseName: <MyMandatoryPlaceholderComponent>
        typeDefinition: ua:BaseObjectType
        modellingRule: MandatoryPlaceholder

9.2Variable attributes

A Variable, whether a type member or an instance, may declare the Variable Attributes of [OPC 10000-3] §5.6.2:

Property Type Meaning
dataType DataType ref The DataType; required unless inherited.
valueRank integer Default -1 (scalar).
arrayDimensions integer array One entry per dimension; 0 means unknown length.
value Variant The initial value.
accessLevel flag list |-separated: CurrentRead, CurrentWrite, HistoryRead, HistoryWrite, SemanticChange, StatusWrite, TimestampWrite.
userAccessLevel flag list Same syntax; it can only narrow accessLevel, never widen it.
minimumSamplingInterval number The fastest sampling rate the server supports, in milliseconds; 0 means continuously updated.
historizing boolean Whether the server collects history for the value.

A Variable typed by ua:DataItemType or one of its subtypes (ua:AnalogItemType, ua:AnalogUnitType, …) may also declare the values of that type's Properties: definition (string), valuePrecision (number), engineeringUnits (a UNECE unit, by symbol or name), euRange and instrumentRange (each [low, high] or { low, high }). Declaring instrumentRange activates the Optional InstrumentRange Property.

exposesItsArray: true on a member Variable of a type makes it the element of its parent's array: it is the ExposesItsArray modelling rule ([OPC 10000-3] §6.4.4.4.5), written as a property of the member. A processor must reject it on an instance, on a VariableType entry, next to another modellingRule, and under a parent that is not a Variable or VariableType holding an array (valueRank other than -1).

9.3Advanced component containment

The orderedComponents, physicalComponents, containedComponents and attachedComponents arrays declare child references using the OPC UA reference types HasOrderedComponent, HasPhysicalComponent, HasContainedComponent and HasAttachedComponent respectively. The order of items in orderedComponents is significant and must be preserved by a Conformant Processor when emitting NodeSet2 XML.

objectTypes:
  - browseName: MyObjectType
    orderedComponents:
      - browseName: First
        nodeClass: Object
        typeDefinition: ua:BaseObjectType
      - browseName: Second
        nodeClass: Object
        typeDefinition: ua:BaseObjectType
    physicalComponents:
      - browseName: Phys1
        nodeClass: Object
    containedComponents:
      - browseName: Contained
        nodeClass: Object
    attachedComponents:
      - browseName: Attached1
        nodeClass: Object

9.4Default instance browse name

An ObjectType may declare a defaultInstanceBrowseName. When this type is consumed as an Add-In with no explicit browseName, the processor must use the default to name the instantiated child.

10Modelling Rules and Optionals

Modelling rules describe the cardinality and constraints of a member of a Type. This specification adopts the standard modelling rules defined in [OPC 10000-3] §6.4.4.4 and adds one purely syntactic convenience (false). The modellingRule property of a child member must take one of the following values:

NOTE

[OPC 10000-3] §6.4.4.4 defines modelling rules as an extensible concept: vendors may introduce additional modelling rules beyond those listed above. A Conformant Processor may accept additional values, but must reject a document if it cannot materialise them.

A modelling rule must not appear on a member of an instance, since instance members do not declare cardinality.

10.1Placeholder browse-name convention

[OPC 10000-3] §6.4.4.4.6 and §6.4.4.4.7 recommend that the browse name and display name of OptionalPlaceholder and MandatoryPlaceholder instance declarations be enclosed in angle brackets. This specification tightens the recommendation into a requirement: the browse name of a member declaring a placeholder modelling rule must be a placeholder browse name (matching <Name>), and conversely a placeholder browse name must be paired with a placeholder modelling rule.

10.2Subtype and instantiation overrides

A subtype may tighten the modelling rule of an inherited member following the rules of [OPC 10000-3] Table 21, reproduced here for convenience:

Rule on supertype Permitted rule on subtype
Mandatory Mandatory
Optional Mandatory or Optional
MandatoryPlaceholder MandatoryPlaceholder
OptionalPlaceholder MandatoryPlaceholder or OptionalPlaceholder

A Conformant Processor must reject any override that violates this table. Loosening (e.g. Mandatory → Optional) is not permitted.

10.3optionals

An optionals array enumerates browse names of inherited optional members that this type or instance opts in to. Nested members may be expressed using the dot-separated form "Property.ChildProperty", mirroring the symbolic-name convention of [OPC 10000-5] §4.1. Each segment may carry an alias: prefix (di:Identification.di:SerialNumber), including on a placeholder member. A Conformant Processor must resolve names by walking the fully-inherited InstanceDeclaration hierarchy, and must report a name that matches no member rather than ignore it.

On an instance, the array need not list an optional member the document already assigns a value to: assigning a value implies presence, per §18.1. Listing it anyway is redundant, not an error.

10.4promotedToMandatory

A subtype, an instance, or an interface application may promote inherited Optional members to Mandatory, or OptionalPlaceholder members to MandatoryPlaceholder, by listing their browse names in promotedToMandatory. The transformations must respect Table 21 above. Promotion is transitive across ancestors and across implemented interfaces.

objectTypes:
  - browseName: PlateType
    interfaces:
      - browseName: di:IVendorNameplateType
        promotedToMandatory:
          - di:DeviceClass
          - di:SerialNumber
          - di:SoftwareRevision
    defaultInstanceBrowseName: Identification

11InterfaceTypes

An interfaceTypes entry has the same shape as an ObjectType but its subtypeOf defaults to ua:BaseInterfaceType. InterfaceTypes are subtypes of BaseInterfaceType per [OPC 10000-5] and form their own hierarchy through subtypeOf.

The interfaces property of an ObjectType, VariableType or instance lists the InterfaceTypes that the node implements. Each entry must be a mapping with:

A Conformant Processor must add a HasInterface reference (defined in [OPC 10000-5]) from the implementing Type to the InterfaceType.

11.1Lean materialisation

This specification adopts a lean interpretation of interface application that follows OPC UA Part 3 v1.05 §6.3.3.1 to the letter:

"Subtypes inherit the fully-inherited parent type's InstanceDeclarations. As long as those InstanceDeclarations are not overridden they are not referenced by the subtype."

Combined with §4.10.2, which describes interface members as living in the "fully-inherited InstanceDeclarationHierarchy of the ObjectType" (a conceptual collection that already includes inherited and interface-derived members), the rule a Conformant Processor must apply when an ObjectType / VariableType implements an interface is:

This deliberately diverges from the eager-copy behaviour of some upstream node-opcua releases. The lean approach keeps the interface as the single source of truth for its members, prevents redundant nodes on every implementer, and makes overrides — the only legitimate reason for a Type to reference an interface member directly — visually unambiguous in the generated NodeSet2 XML.

11.2Instance-time interface walk

Because the implementing Type is lean, instance materialisation must walk the Type's HasInterface references (transitively up the HasSubtype chain) and apply each interface to the instance under the lean rules of §11.1. Specifically, for every interface implemented (directly or by inheritance) by the instance's typeDefinition:

The HasInterface reference itself stays on the TypeDefinitionNode; an instance must not add a duplicate HasInterface reference (Part 3 §4.10.2: "Instances should not have a HasInterface Reference to an Interface if it was already applied to the TypeDefinitionNode").

11.3Instance with its own interfaces: block

An instance may declare its own interfaces: block to apply an interface that the TypeDefinitionNode does not already implement. In that case the instance:

interfaceTypes:
  - browseName: ILevelType
    properties:
      - browseName: L1Prop
        dataType: ua:String
  - browseName: ILevel2Type
    subtypeOf: ILevelType
    properties:
      - browseName: L2Prop1
        dataType: ua:String
      - browseName: L2Prop2
        dataType: ua:String
        modellingRule: Optional

objectTypes:
  - browseName: XYZDeviceType
    interfaces:
      - browseName: ILevel2Type

instances:
  - browseName: XYZInstance1
    typeDefinition: XYZDeviceType
    organizedBy: /ua:Objects
    optionals: [L2Prop2]

12Add-Ins

An Add-In is an Object aggregate referenced by its typeDefinition rather than enumerated member-by-member. The addIns property of a Type or instance may list Add-Ins; each entry is a mapping with at least typeDefinition and optionally browseName, description, modellingRule and optionals. An entry may also declare members of its own beyond those of its type (components, properties, methods) and nested addIns, attached to it by HasAddIn (OPC 40001-1's MachineryBuildingBlocks folder carrying LifetimeCounters and Monitoring).

The browse name of the materialised Add-In instance is determined as follows, in order:

  1. the explicit browseName of the Add-In entry, if present;
  2. otherwise the defaultInstanceBrowseName of the Add-In's typeDefinition;
  3. otherwise the browseName of the typeDefinition with the trailing Type suffix removed.

A Conformant Processor must reject a document if rule 3 is reached but the resulting browse name is empty or collides with an existing sibling.

objectTypes:
  - browseName: ThermometerComponentType
    components:
      - browseName: Temperature
        dataType: ua:Float
    defaultInstanceBrowseName: Thermometer

instances:
  - browseName: Equipment
    typeDefinition: ua:BaseObjectType
    organizedBy: /ua:Objects
    addIns:
      - typeDefinition: ThermometerComponentType
        browseName: MyThermometer1
      - typeDefinition: ThermometerComponentType
        browseName: MyThermometer2

13Methods

A method declaration must declare browseName. inputArguments and outputArguments are ordered arrays of mappings; each entry must declare name and dataType and may declare description, valueRank (default -1) and arrayDimensions.

On a Type-side member, a method may declare modellingRule; on an instance-side member it must not. A method may declare an executable boolean (default true).

14Instances

An entry of the top-level instances array describes an Object or Variable instance. The mapping must declare browseName and typeDefinition, and must declare its parent through exactly one of the connection properties. An instance may additionally declare any of:

Property Notes
nodeId Explicit string NodeId; the nodes below derive theirs from it. See §5.4.
description, displayName Localized text overrides.
optionals Inherited optional members to instantiate.
interfaces InterfaceTypes implemented by this instance.
addIns Add-Ins to instantiate; see §12.
properties, components, organizes, methods Inline children that override or augment the instantiated members; see §14.2.
propertyValues Values for the instance's members, addressed by nesting; see §14.4.
initializers Values for the instance's members, addressed by browse path; see §18.
value, accessLevel Permitted only when the instance is a Variable (i.e. typeDefinition resolves to a VariableType).

14.1Connected-object rule

Every Object or Variable in an OPC UA address space is reached from the Server's Objects, Types or Views folder through hierarchical References ([OPC 10000-3] §7.5). Accordingly an instance in this DSL must declare its parent through exactly one of the following properties; the JSON Schema ConnectedObject definition enforces this with a oneOf:

Property OPC UA reference
organizedBy Organizes
componentOf HasComponent
propertyOf HasProperty
orderedComponentOf HasOrderedComponent
physicalComponentOf HasPhysicalComponent
containedComponentOf HasContainedComponent

The value of each connection property is a browse path (absolute, anchored or relative). Two top-level instances may use different connection properties.

An instance may also take its place in the event-notifier hierarchy the same way, with browse paths resolved like a connection property: eventSourceOf names its notifier (a HasEventSource from that node to the instance) and eventSources the nodes it is the notifier of (a HasEventSource from the instance to each). See §16.

14.2Inline children and overrides

An instance may include properties, components, organizes or methods arrays. The processor must match each inline child by browseName against the members materialised from typeDefinition (and from implemented interfaces / add-ins):

  1. If a matching member exists, the inline child overrides it and may set value, description, displayName, dataType (when narrowing is permitted by OPC UA), valueRank, arrayDimensions, accessLevel, sub-aggregates and inline references. The inline child must not declare a modellingRule.
  2. If no matching member exists, the inline child is treated as a new member added to this instance. The processor must require typeDefinition in this case for non-Method children.
objectTypes:
  - browseName: MySubObjectType
    components:
      - browseName: ComponentA
        nodeClass: Object
      - browseName: ComponentVarB
        nodeClass: Variable
        dataType: ua:Double
        valueRank: -2          # any
  - browseName: MyObjectType
    components:
      - browseName: SubObject1
        typeDefinition: MySubObjectType
        components:
          - browseName: ComponentVarB
            dataType: ua:Double
            valueRank: 1        # narrowed to array

14.3Browse-name uniqueness

Two distinct nodes may share the same browse name as long as they do not share the same hierarchical parent. A processor must reject documents in which two sibling nodes share the same browse name. This rule applies uniformly to top-level instances, inline children and members materialised through Add-Ins or interfaces.

instances:
  - browseName: MyInstance
    typeDefinition: MyObjectType
    organizedBy: /ua:Objects

  - browseName: SomeLittleThing
    typeDefinition: LittleThingType
    componentOf: MyInstance/Folder1

  - browseName: SomeLittleThing
    typeDefinition: LittleThingType
    componentOf: MyInstance/Folder2

14.4PropertyValues

propertyValues assigns values to an instance's members by nesting rather than by path. It is a mapping whose shape mirrors the instance's aggregate structure: each key is a member browse name, alias-qualified per §5.3, and each value is either the value to assign or a nested mapping addressing that member's own children.

instances:
  - browseName: Pump1
    typeDefinition: di:DeviceType
    organizedBy: /ua:Objects
    propertyValues:
      di:Manufacturer: Sterfive
      di:ParameterSet:
        Pressure: 4.2

A Conformant Processor must resolve each key against the children of the node the enclosing mapping addresses, and must reject a key that names no such member rather than creating one — a misspelled member is an authoring error, not a new node. A leaf must address a Variable; assigning to an Object is an error.

$value

A member that carries both a value and children of its own is addressed with the reserved key $value, which assigns to the enclosing Variable itself:

    propertyValues:
      Level:
        $value: 42.0
        ua:EngineeringUnits: "%"

$value must be rejected when the enclosing node is not a Variable.

Value literals

A mapping is normally a sub-tree, which makes the two OPC UA structures that a model most often writes inline ambiguous. A Conformant Processor must therefore treat a mapping as a VALUE, not a sub-tree, when — and only when — it matches one of these shapes against the target Variable's DataType:

The test must be applied before recursing, and must not be relaxed to a subset or superset of those keys: { txt: "Acme" } is not a LocalizedText with a typo, it is a reference to a child named txt, and must produce the same "no such member" error any other unknown key would. Every other mapping recurses.

Relationship to initializers

propertyValues and initializers express the same thing — values for existing Variables — and differ only in how the target is addressed: by nesting, or by a browse path. A document may use either or both. Both are subject to §18.1: a value written at any depth of a propertyValues tree opts every Optional node on its path in.

15Additional References

The top-level references array, and the per-node references sub-arrays on Types, instances and add-ins, declare references that are not expressible through aggregates or connection properties (typically non-hierarchical or cross-namespace references).

Each entry must be a mapping with:

In the per-node form, target may be a node declaration instead of a browse path. The node is then created under the owner (which becomes its ParentNodeId), linked by referenceType alone: the way to declare a member owned through a ReferenceType with no dedicated key. DI does this with ConnectsTo, a non-hierarchical ReferenceType. source must be /; referenceType must not be HasTypeDefinition, HasModellingRule, HasSubtype, HasEncoding or GeneratesEvent.

objectTypes:
  - browseName: NetworkType
    references:
      - referenceType: ConnectsTo
        source: /
        target:
          browseName: <CPIdentifier>
          typeDefinition: ConnectionPointType
          modellingRule: OptionalPlaceholder

16Events

Event types are declared in the top-level eventTypes array; each entry has the shape of an ObjectType with default subtypeOf: ua:BaseEventType.

The generatedEvents property of an ObjectType (and so of an EventType or an InterfaceType, which share its shape) or of a state machine lists the event types it raises; the processor must emit a GeneratesEvent reference for each entry. eventSources and eventSourceOf build the event-notifier hierarchy ([OPC 10000-3] §7.17), one HasEventSource reference per entry. On an ObjectType, eventSources names the type's own members (/Motor), and instantiation carries the references to each instance. On an instance, eventSources and eventSourceOf take browse paths resolved like organizedBy. eventSourceOf must not appear on a type: a type node is the event source of nothing.

objectTypes:
  - browseName: MyObjectType
    generatedEvents:
      - MyEventType
  - browseName: PlantType
    components:
      - browseName: Pump
        typeDefinition: MyObjectType
    eventSources:
      - /Pump

eventTypes:
  - browseName: MyEventType

instances:
  - browseName: Plant1
    typeDefinition: PlantType
    organizedBy: /ua:Objects
    eventSourceOf: /ua:Objects/ua:Server

17State Machines

A stateMachines entry produces a FiniteStateMachine type as defined in [OPC 10000-5] §B (FiniteStateMachineType, StateType, TransitionType). The default subtypeOf is ua:FiniteStateMachineType. The mapping must declare:

A state machine may additionally declare subtypeOf, isAbstract, transitionHaveEffect (the EventType raised on each transition), generatedEvents, components, methods (e.g. the Start, Suspend, Resume, Halt and Reset methods of a ProgramStateMachine) and promotedToMandatory.

stateMachines:
  - browseName: MyFiniteStateMachine
    states: [LowLow, Low, High, HighHigh]
    transitions:
      - from: LowLow
        to: Low
      - from: Low
        to: High
      - from: High
        to: HighHigh
      - from: HighHigh
        to: High
      - from: High
        to: Low
      - from: Low
        to: LowLow

17.1Transitions to a state of a sub state machine

A bare name in from or to must be a state declared in the states of the same state machine. [OPC 10000-16] also lets a transition enter or leave a state of a sub state machine, that is a state owned by another state machine type. Such a state is written <StateMachineType>.<State>:

The same form names a state inherited from a supertype, which a subtype does not declare again: from: BaseMachineType.Running.

stateMachines:
  - browseName: AutomaticModeStateMachineType
    states: [Initialized, Ready]
    transitions:
      - from: Initialized
        to: Ready

  - browseName: VisionStateMachineType
    components:
      - browseName: AutomaticModeStateMachine
        typeDefinition: AutomaticModeStateMachineType
    states: [Preoperational, Operational]
    transitions:
      - from: Preoperational
        to: Operational
      # enters the sub state machine directly in its Initialized state
      - from: Preoperational
        to: AutomaticModeStateMachineType.Initialized
      # leaves it from its Ready state
      - from: AutomaticModeStateMachineType.Ready
        to: Preoperational

18Initializers

The initializers array declares default values for existing variables, identified by variable (a browse path, in which . may replace / to reach a HasProperty child, as in di:ParameterSet/Temperature.ua:EngineeringUnits). Each entry must declare variable and value. It may also declare description, which sets the Description attribute of that Variable (every OPC UA node has one), and, on an AnalogItem-like Variable, engineeringUnits and range, which set its EngineeringUnits and EURange properties. An initializer takes no other key. Initializers run after materialisation, so an initializer's description replaces one set by an inline override of the same member.

An initializers block may appear on an instance, where variable is a path relative to that instance, or at the top level of the document, where the first segment of variable must name a symbol — an instance declared by the document, written with or without the $ sigil. The two forms are equivalent: $Robot1/FlangeLoad written at the top level and FlangeLoad written inside Robot1 denote the same Variable and must have the same effect.

Initializers are applied after all node materialisation has completed and before NodeSet2 XML serialisation. They must not create or remove nodes.

18.1Presence implied by initialization

Assigning a value implies the presence of the node that receives it. Where variable resolves, through the fully-inherited InstanceDeclaration hierarchy, to a member whose modelling rule is Optional — or reaches such a member through an Optional ancestor — a Conformant Processor must treat every Optional node on that path as though it had been listed in the target instance's optionals array.

This is a materialisation-phase rule, not an exception to the paragraph above: the nodes are created in phase 4 like any other opted-in optional member, and phase 6 still only writes values to nodes that already exist. A processor that defers the decision to phase 6 cannot implement it, because instantiate() has already run.

The same rule governs propertyValues, where a value written at any depth of the tree opts its path in.

An entry that names a member the type does not declare must not be opted in — it is an authoring error, and the diagnostic a processor already emits for an unresolvable variable path is the correct response.

19Processing Model

A Conformant Processor must process a document in the following phases:

  1. Parse and schema-validate. Reject documents that fail YAML parsing or the JSON Schema validation.
  2. Resolve namespaces. Load every namespace declared in namespaces and imports; assign each a stable namespace index.
  3. Topological sort. Build a dependency graph over types defined in the document; resolve forward references; reject cycles unless the cycle is a self-reference permitted by OPC UA (e.g. a Structure field whose dataType is its enclosing type).
  4. Materialise nodes. Create ReferenceTypes, DataTypes, ObjectTypes, VariableTypes, EventTypes, InterfaceTypes, FiniteStateMachine types, then top-level instances. For each Type, copy inherited members, apply interfaces, addIns, promotedToMandatory, optionals and inline children (in that order). Before an instance is materialised, its optionals set is extended with the Optional members implied by the values the document assigns to it — from its own initializers and propertyValues, and from the top-level initializers entries anchored at it (see §18.1). The top-level block is only readable this early because its paths are rooted at symbols, not at nodes.
  5. Apply additional references. Resolve and add every entry of the top-level and per-node references arrays.
  6. Apply initializers. Set Variant values on existing Variable nodes.
  7. Persist NodeIds. Update the symbol table so that all auto-assigned NodeIds remain stable across regenerations.
  8. Serialise. Emit a NodeSet2 XML document that is NodeSet2-equivalent to the address space described by the document.

20Equivalence with NodeSet2 XML

A NodeSet2 XML document is described by [OPC 10000-6]. Two address spaces are NodeSet2-equivalent if and only if, after canonicalisation, they declare:

  1. the same set of nodes (matched by NodeId after namespace-index remapping); each node has the same NodeClass and the same set of Attributes as defined in [OPC 10000-3] §5;
  2. identical Attribute values for each node, modulo whitespace in LocalizedText fields;
  3. identical hierarchical and non-hierarchical References, modulo ordering of References elements except where ordering is mandated by the ReferenceType (e.g. HasOrderedComponent);
  4. identical InstanceDeclaration hierarchies for every TypeDefinitionNode, preserving every HasModellingRule reference required by the modelling rules of §10.

A Conformant Processor must produce, for any Conformant Document, a NodeSet2 XML document that is NodeSet2-equivalent to the address space described by the document.

21Diagnostics

A Conformant Processor must reject a document and emit a diagnostic when:

A processor should emit a non-fatal warning when:

22Security Considerations

The DSL is a static description of an OPC UA model and does not by itself transmit data over the network. However:

23References

[OPC 10000-3]
OPC Foundation, OPC Unified Architecture — Part 3: Address Space Model, version 1.05. https://reference.opcfoundation.org/Core/Part3/v105/docs/
[OPC 10000-5]
OPC Foundation, OPC Unified Architecture — Part 5: Information Model, version 1.05. https://reference.opcfoundation.org/core/Part5/v105/docs/
[OPC 10000-6]
OPC Foundation, OPC Unified Architecture — Part 6: Mappings (defines the NodeSet2 / UANodeSet XML schema). https://reference.opcfoundation.org/Core/Part6/
[OPC 10000-16]
OPC Foundation, OPC Unified Architecture — Part 16: State Machines, version 1.05. https://reference.opcfoundation.org/Core/Part16/v105/docs/
[YAML 1.2.2]
YAML Language Development Team, YAML Ain't Markup Language (YAML™) version 1.2 (revision 1.2.2). https://yaml.org/spec/1.2.2/
[JSON Schema 2020-12]
JSON Schema Organisation, JSON Schema Specification, Draft 2020-12. https://json-schema.org/draft/2020-12/json-schema-core.html
[RFC 2119]
Bradner, S., Key words for use in RFCs to Indicate Requirement Levels, IETF, 1997. https://www.rfc-editor.org/rfc/rfc2119
[RFC 8174]
Leiba, B., Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words, IETF, 2017. https://www.rfc-editor.org/rfc/rfc8174
[RFC 3339]
Klyne, G., Newman, C., Date and Time on the Internet: Timestamps, IETF, 2002. https://www.rfc-editor.org/rfc/rfc3339