diff --git a/README.md b/README.md index dd7a7a7..2d4f4a5 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full workflow, validation, and - `companion-specs/` — draft OPC UA companion specifications, one folder per domain. - `Generators/` — draft Companion Specification for electrical power **Generator Sets (GenSets)**: the information model (`Opc.Ua.Generators.NodeSet2.xml`), the NodeId assignments (`Opc.Ua.Generators.NodeIds.csv`), the specification document, and `tools/build_model.py` — a generator that emits the NodeSet, CSV, and reference tables from a single source of truth. +- `wot-specs/` — Web of Things specification drafts and their supporting tools/examples, kept separate from OPC UA Core and domain companion-specification work. + - `WoT-Binding/` — draft **OPC UA — Web of Things (WoT) Binding**: a standalone revision of OPC 10101 that describes an OPC UA interface as a W3C Thing Description / Thing Model — preserving the official `uav` vocabulary, the Read / Write / Observe / Call and security mappings, and the URI rules — and adds a collision-safe model/platform vocabulary, NamespaceUri-qualified model-name hints beside definitive NodeIds, plus bidirectional NodeSet2 conversion. The semantic/readable mapping is primary, the complete `uav:nodes` projection is added only for source facts not yet expressible by the vocabulary, unmapped WoT members survive as pointer-addressed NodeSet extension residue, and `uav:nodeSet` is reserved for explicit byte archival or a demonstrated final fallback. Contains the specification, the JSON-LD context, an extension/preservation JSON Schema, worked examples, and a standard-library `tools/validate_local.py`. + - `WoT-Connectivity/` — draft **OPC UA — WoT Connectivity** (additive revision **1.1**): a **registry-first** revision of OPC 10100-1 that layers a W3C Thing Model / Thing Description **document registry** over the abstract [`xregistry/`](core-specs/xregistry/) base model (`WoTRegistryType` subtypes `RegistryType`; `ThingDescriptionGroupType`/`ThingModelGroupType` subtype `GroupType`; `ThingDescriptionFileType`/`ThingModelFileType` subtype the abstract `WoTDocumentType` `ResourceType`), exposed as a well-known `WoTRegistry` Object under the **Server** object (`i=2253`). The stored documents/versions are canonical; the OPC UA AddressSpace is a **derived, shadow-switched, generational projection** (Thing Models → types, Thing Descriptions → instances, affordances → Variables/Methods/EventTypes, links → References, forms → binder plans) with a `Refresh` Method, validation/load/refresh events, and desired/active version lifecycle. The full **OPC 10100-1 v1.02** model is **incorporated into the same combined NodeSet and namespace** — preserving every published NodeId (`1..172`), type and method signature — and its superseded management surface is marked `Deprecated` (per OPC 11030); the revised WoT Binding `uav` vocabulary is a normative JSON-LD dependency (not a NodeSet `RequiredModel`). Contains the specification, the generated combined NodeSet (`Opc.Ua.WoTCon.NodeSet2.xml`) / CSV, the pinned 1.02 sources under `legacy/`, the canonical `tools/build_model.py`, a standard-library `tools/validate_local.py`, and worked examples. - `core-specs/` — draft **extensions to the base OPC UA specification** (proposed additions to the `http://opcfoundation.org/UA/` namespace), intended for submission to an OPC Foundation Working Group. Each encoding/catalog folder below contains only the **normative** spec documents, its `README.md`, and the **base reference schema**; all tooling, examples, generated (non-base) schemas, and the shared validation package live under `core-specs/extras/` (a parallel, mirrored tree). - `observability-export/` — draft *OPC UA — Observability Export*: a small, transport-neutral layer that lets a Server, or a companion specification, declare **how its data lands in an observability system** — as OpenTelemetry (OTEL) **metrics, logs and traces** — so a generic read-only **bridge** can forward it over the **classic client/server (RPC) interface** and, **optionally**, over **PubSub (Part 14)** without understanding the domain. Contains the base NodeSet, CSV, and specification document, plus one **standardized subfolder per companion spec** (`pumps/`, `robotics/`, `facets/`, `di/`), each holding the companion-spec **addendum(s)** and the instance-overlay `NodeSet2.xml`. The generator tooling and example descriptor sources live under `core-specs/extras/observability-export/` (below). A non-normative **overview deck** ([`core-specs/observability-export/README.md`](core-specs/observability-export/README.md)) summarizes the why, what and how. - `avro-encoding/` — **Apache Avro (binary)** DataEncoding: a Part 6 mapping of the full OPC UA type model and a Part 14 **PubSub** message mapping, including **Action invoke/response** and **Discovery** messages. Reversible (`decode(encode(x)) == x`), with a NodeSet-driven schema generator and a SchemaId handshake. diff --git a/core-specs/xregistry/OPC-UA-xRegistry.md b/core-specs/xregistry/OPC-UA-xRegistry.md index 249e06a..ae2df28 100644 --- a/core-specs/xregistry/OPC-UA-xRegistry.md +++ b/core-specs/xregistry/OPC-UA-xRegistry.md @@ -116,6 +116,12 @@ A writer registers a document by creating a file in the target group folder and On `Close` the server **auto-bootstraps** (§6.5): it assigns the entity's `xid`, `epoch`, `CreatedAt`/`ModifiedAt`, and any domain-derived attributes, and links the new file under its group and registry so it is immediately visible in all three representations. A server that is read-only (a published catalogue or a mirror) need not expose `CreateResource`. +### 5.3 SecureChannel requirements + +Every operation that creates, modifies or deletes registry content or metadata shall be accepted only over an OPC UA SecureChannel using `MessageSecurityMode` `SignAndEncrypt`. This requirement applies to `CreateGroup`, `GetOrCreateGroup`, `CreateResource`, `GetOrCreateResource`, FileType `Open` for writing, `Write`, the `Close` that commits written content, `Delete`, `AddAttribute`, `RemoveAttribute`, and domain-specific Methods that change version selection, enablement or other registry state. Roles and Permissions remain independently applicable and may impose stricter authorization. + +Registry reads should use a secured channel. An implementation may expose read-only Browse, Read and FileType `Open`/`Read`/`Close` operations over `MessageSecurityMode` `None` when its deployment policy permits this. A client using such an endpoint shall not infer authenticity, integrity or confidentiality for the returned registry metadata or document bytes. + ## 6 Information model The abstract base namespace is `http://opcfoundation.org/UA/xRegistry/`. Draft numeric NodeIds use the provisional `63000+` block; final NodeIds are assigned by the OPC Foundation. The four base ObjectTypes and their members are the normative node reference in Annex A. This clause describes their intent. Every Variable in the model has an explicit TypeDefinition: fixed attributes are `PropertyType` Variables, and each dynamic label is a `PropertyType` Variable under an `AttributesType` container (§6.6). A server **shall** set each group's, resource's and version's BrowseName to its identifier (`GroupId` / `ResourceId` / `VersionId`, a URL-safe token) so a client selects and filters entities directly from Browse results without a Read per candidate; the [*xRegistry — OPC UA API*](xRegistry-OPC-UA-Api.md) relies on this for read-free collection filtering. diff --git a/wot-specs/WoT-Binding/OPC-UA-WoT-Binding.md b/wot-specs/WoT-Binding/OPC-UA-WoT-Binding.md new file mode 100644 index 0000000..7404655 --- /dev/null +++ b/wot-specs/WoT-Binding/OPC-UA-WoT-Binding.md @@ -0,0 +1,782 @@ +# OPC UA — Web of Things (WoT) Binding + +**Release 1.10 — Draft (standalone revision of OPC 10101 v1.00)** +**Namespace:** `http://opcfoundation.org/UA/WoT-Binding/` +**Prefix:** `uav` +**Publication date:** 2026-07-20 + +> Status: Experimental working-group draft. This document supersedes [OPC 10101 — OPC UA for WoT Binding](https://reference.opcfoundation.org/specs/OPC-10101/) as a complete, standalone specification. It preserves the published namespace, prefix, terms, and normative behaviour while adding a collision-safe model and platform vocabulary together with bidirectional NodeSet2 conversion. It is **not** an addendum: it can be read on its own. Nothing here is normative, official, or endorsed by the OPC Foundation or the W3C; the use of `opcfoundation.org` namespace URIs is for prototyping only. + +--- + +## 1 Scope + +This specification defines how a [W3C Web of Things Thing Description](https://www.w3.org/TR/wot-thing-description11/) (TD) and Thing Model (TM) describe an OPC UA interface, and how an OPC UA information model expressed as a NodeSet2 document and a Thing Description or Thing Model are converted into one another. + +It has three layers, each usable on its own: + +- A **preserved protocol binding** that lets a Thing Description carry enough metadata for a client to open a session with an OPC UA Server, select a security configuration, and Read, Write, Observe (monitor), or Call a datapoint. This layer is byte-for-byte compatible with the published baseline vocabulary and semantics. +- A **model and platform vocabulary** that lets a Thing Model express the structural facts of an OPC UA type — composition, references, groups, units, scaling, configuration, metadata, and modelling rules — so that a Thing Model is a faithful, tool-processable projection of an ObjectType. +- A **bidirectional NodeSet2 conversion** whose semantic/readable vocabulary is primary, whose `uav:nodes` projection covers source facts not yet expressible by that vocabulary, and whose `uav:nodeSet` envelope is reserved for explicit byte-exact archival or a future construct that cannot be represented structurally. + +Out of scope: the OPC UA wire protocol itself (defined by OPC 10000-6), transport security key management, and the domain semantics of any particular companion specification. This binding references the Variables, Methods, and types that a domain model already defines; it does not re-model process data. + +### 1.1 Differences from the published OPC 10101 v1.00 + +This document supersedes [OPC 10101 v1.00](https://reference.opcfoundation.org/specs/OPC-10101/), preserves its namespace, `uav` prefix, vocabulary, service mappings, URI rules, access-level mapping, and security mapping (Section 5), and adds the following substantive capabilities. A Thing Description written against v1.00 remains valid here. + +- **Event mapping (new).** An explicit mapping of OPC UA events (`BaseEventType` subtypes) to WoT event affordances, anchored by the `uav:eventType` type annotation and the `uav:isEvent` flag, including the standard event fields and `subscribeevent`/`unsubscribeevent` realized by OPC UA event MonitoredItems (Section 8). The published baseline had no event mapping. +- **Model and platform vocabulary (new).** A collision-safe vocabulary that lets a Thing Model record the structural facts of an OPC UA type — composition, references, groups, units, scaling, configuration, metadata, inheritance and modelling rules (Section 6) — so a Thing Model is a faithful, tool-processable projection of an ObjectType, not only a client-facing description. +- **Exact NodeSet2 round trip and preservation (new).** A bidirectional NodeSet2 ↔ WoT conversion with defined round-trip invariants (Section 9), a versioned and schema-complete `uav:nodes` projection, pointer-addressed preservation of unmapped WoT JSON members in standard NodeSet `Extensions`, and an exceptional digest-verified `uav:nodeSet` byte archive (Section 10). +- **Implementer guidance (new).** Independent conformance units and recommended profiles (Section 11), a deterministic standard-library validator, worked examples, and an iterative implementer walkthrough (Annex D) covering both conversion directions. + +## 2 Normative and informative references + +- [OPC 10101 — OPC UA for WoT Binding](https://reference.opcfoundation.org/specs/OPC-10101/) — the published version superseded by this specification. +- [OPC 10000-3](https://reference.opcfoundation.org/specs/OPC-10000-3/) — Address Space Model (NodeClasses, attributes, references, modelling rules). +- [OPC 10000-4](https://reference.opcfoundation.org/specs/OPC-10000-4/) — Services (Read, Write, Call, and the Subscription/MonitoredItem services used for Observe). +- [OPC 10000-5](https://reference.opcfoundation.org/specs/OPC-10000-5/) — Information Model (BaseEventType and the standard event fields). +- [OPC 10000-6](https://reference.opcfoundation.org/specs/OPC-10000-6/) — Mappings, in particular the string encoding of `NodeId`, `QualifiedName`, and `ExpandedNodeId`, and the NodeSet2 XML schema. +- [OPC 10000-7](https://reference.opcfoundation.org/specs/OPC-10000-7/) — Profiles and Conformance Units. +- [W3C Web of Things (WoT) Thing Description 1.1](https://www.w3.org/TR/wot-thing-description11/) — the TD and TM information model, security schemes, forms, and links. +- [W3C Web of Things (WoT) Binding Templates](https://www.w3.org/TR/wot-binding-templates/) — the pattern this binding follows for protocol- and payload-specific annotation. +- [QUDT](http://qudt.org/) — quantity kinds and units, reused for physical semantics. +- [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) — URI syntax and percent-encoding. +- [RFC 4648](https://www.rfc-editor.org/rfc/rfc4648) — Base16 and Base64 data encodings. +- [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901) — JSON Pointer. + +## 3 Terms, definitions, and conventions + +### 3.1 Normative keywords + +The keywords **shall**, **shall not**, **should**, **should not**, and **may** are used deliberately and carry their usual normative meaning. **shall** and **shall not** state absolute requirements; **should** and **should not** state strong recommendations that may be waived with good reason; **may** states an option. + +### 3.2 JSON-LD conventions + +A Thing Description and a Thing Model are JSON-LD 1.1 documents. Every example in this specification declares an `@context` array whose entries bind the base WoT Thing Description context, the `uav` prefix to the namespace of Section 4, and the companion context document [`opc-ua-wot-binding.context.jsonld`](opc-ua-wot-binding.context.jsonld). A `uav` member is written in prefixed form, for example `uav:browseName`. The structural constraints of the `uav` members, native projection, and exceptional preservation envelope are stated by [`opc-ua-wot-binding.schema.json`](opc-ua-wot-binding.schema.json) and validated by [`tools/validate_local.py`](tools/validate_local.py). + +### 3.3 Abbreviations + +- **TD** — Thing Description, the description of a concrete Thing (an OPC UA Object or an OPC UA Server interface). +- **TM** — Thing Model, a reusable, class-level template (an OPC UA ObjectType or VariableType). +- **NodeSet2** — the XML serialization of an OPC UA information model defined by OPC 10000-6. + +## 4 Namespace and prefix + +The terms this specification adds to a Thing Description are identified by the namespace + +```text +http://opcfoundation.org/UA/WoT-Binding/ +``` + +and the prefix **uav** is bound to that namespace. Both the namespace URI and the prefix are preserved unchanged from the published baseline. A conforming document **shall** bind `uav` to exactly this URI and **shall not** rebind the prefix to any other namespace. + +## 5 Preserved OPC 10101 vocabulary and service mappings + +This section defines the preserved OPC 10101 vocabulary and its service, URI, access-level, and security mappings. + +### 5.1 Node and path terms + +| Term | Where used | Type | Meaning | +| --- | --- | --- | --- | +| `uav:id` | in `href` context and on an affordance | string | The identity of the UA Node an affordance targets, given as an **ExpandedNodeId** in the string form of OPC 10000-6 (Section 5.1.1). | +| `uav:browsePath` | in a form, or on an affordance | string | The browse path of a UA Node as a string, following OPC 10000-4 Annex A.2 and starting at the root of the AddressSpace. Non-base path elements use a context prefix (preferred) or the NamespaceUri form, for example `/Objects/pump:Machine/pump:Pressure`. | +| `uav:browseName` | at Thing level, on a property, or on an action | string | The originating `BrowseName` of the UA Node, encoded as a portable OPC 10000-6 QualifiedName string (Section 5.1.3). | + +#### 5.1.1 Portable identity (ExpandedNodeId) + +A persisted or interchanged Thing Description or Thing Model **shall** identify UA Nodes and types portably, so that a document keeps referring to the same Nodes even after a Server reorders its namespace table. Every NodeId-valued term — `uav:id`, each entry of `uav:hasComponent` and `uav:componentOf`, `uav:mapToNodeId`, `uav:mapToType`, and `uav:refId` (Section 6.2) — **shall** be written as an OPC 10000-6 **ExpandedNodeId** string that names its namespace by URI: + +```text +nsu=;= +``` + +for example `nsu=http://example.com/demo/pump;s=Pump` or `nsu=http://example.com/demo/pump;i=1001`. For a Node in the base OPC UA namespace (namespace 0) the canonical namespace-0 form `i=` **may** be used without an `nsu=` prefix, for example the `HasOrderedComponent` ReferenceType `i=49`. + +A document **shall not** use the session-local `ns=` form in any of these terms, because the namespace index is only meaningful within a single Server session and is invalidated by a namespace-table reordering. A client resolves each `nsu=` to the target Server's current namespace index at session establishment, reading the Server `NamespaceArray` (OPC 10000-5), before issuing a request. Namespace indexes still appear legitimately inside `uav:nodes` and the canonical NodeSet2 XML carried by `uav:nodeSet`; each representation resolves them through its own `NamespaceUris` table. + +#### 5.1.2 NamespaceUri-qualified model names + +An OPC UA model-definition concept — for example a ReferenceType, ObjectType, VariableType, EventType, or DataType — may additionally be described by its NamespaceUri-qualified BrowseName. This Binding writes that semantic lookup hint as a **compact model name**: + +```text +: +``` + +The non-numeric `prefix` **shall** be bound in the active `@context` to the exact NamespaceUri that qualifies the BrowseName. The prefix `ua` is reserved for `http://opcfoundation.org/UA/`; for example `ua:HasOrderedComponent` denotes the ReferenceType whose BrowseName is `HasOrderedComponent` in the base OPC UA namespace. A companion model may use a domain prefix such as `isa:MaterialClassType` when `isa` is bound to that model's NamespaceUri. + +For example, the `pump:` names used throughout this specification come from this context binding: + +```jsonc +"@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "pump": "http://example.com/demo/pump" } +] +``` + +A compact model name is a Binding string convention resolved through JSON-LD context prefix bindings; it is not a NodeId and is not authoritative identity. This specification uses it directly as the `rel` of a typed Reference and in `uav:mapToTypeName` and `uav:congruentTypeName`. It **shall not** replace an ExpandedNodeId for an arbitrary Object, Variable, Method, or other instance Node, because multiple instances may have the same NamespaceUri-qualified BrowseName. + +An author: + +1. **shall** bind the prefix to the defining NamespaceUri and **shall not** use a numeric prefix (numeric `namespaceIndex:name` remains the lexical form of `uav:browseName`); +2. **should** provide the compact model name whenever it makes a model concept easier to understand; +3. **shall** also provide the definitive ExpandedNodeId where this specification requires one (`uav:mapToType`) or where lookup may be unavailable or ambiguous; +4. **shall not** assume that textual equality of two compact names proves identity, because different documents may bind different prefixes to the same NamespaceUri. + +A consumer resolves the prefix to a NamespaceUri, searches the loaded model definitions for the expected NodeClass and that NamespaceUri-qualified BrowseName, and uses the result only when exactly one candidate exists. If no candidate or more than one candidate exists, the consumer **shall** use the accompanying ExpandedNodeId; if no definitive identifier is available, it **shall** report an unresolved or ambiguous model concept and **shall not** invent a NodeId. When the compact model name and ExpandedNodeId resolve to different Nodes, the document is invalid. + +`uav:semanticId` identifies external/domain semantics, while a compact model name identifies an OPC UA model-definition Node. `uav:nameNamespace` declares the naming namespace of the type being authored; it does not by itself identify another model concept. + +#### 5.1.3 Portable QualifiedName + +`uav:browseName` is a QualifiedName. In WoT documents, the preferred portable presentation is the context-prefix form: + +```text +: +``` + +where the non-numeric prefix is bound by `@context` to the QualifiedName NamespaceUri. A consumer **shall** resolve that prefix before constructing the OPC UA QualifiedName; it shall not pass the compact presentation unchanged to an OPC 10000-6 decoder. + +The normative OPC 10000-6 Section 5.1.12 string encoding is also allowed. A BrowseName in the base OPC UA namespace uses the bare `` form. A BrowseName in any other namespace uses: + +```text +nsu=; +``` + +For example: + +```text +InputArguments +pump:OverTemperatureEventType +nsu=http://example.com/demo/pump;OverTemperatureEventType +``` + +Semicolons in the NamespaceUri **shall** be percent-encoded as `%3B`; the name follows the delimiter semicolon and is otherwise retained as the QualifiedName `Name`. The numeric `:` form (for example `1:PumpType`) **shall not** be used in persisted readable `uav:browseName` values because a namespace index is session/document local. + +The same compact lexical form is used for model concepts (Section 5.1.2), but the containing term disambiguates the meaning: in `uav:browseName` it is a QualifiedName presentation; in a typed link `rel` it identifies a model-definition concept. NodeSet-local `browseName` fields inside `uav:nodes` remain in their UANodeSet lexical form and are resolved through that projection's `namespaceUris` table. + +### 5.2 Type-annotation terms + +The `@type` of a Thing or affordance is annotated to record which NodeClass it projects. + +| Term | Applies to | Projects | +| --- | --- | --- | +| `uav:object` | `@type` at Thing level of a TD | a UA Object | +| `uav:objectType` | `@type` at Thing level of a TM | a UA ObjectType (this is why the document is a Thing Model) | +| `uav:variable` | `@type` of a TD property | a UA Variable | +| `uav:variableType` | `@type` of a TM property | a UA VariableType | +| `uav:method` | `@type` of an action | a UA Method | +| `uav:eventType` | `@type` of an event affordance | a UA EventType (a subtype of `BaseEventType`, OPC 10000-5) | + +`uav:objectType` and `uav:variableType` are only meaningful in a Thing Model. `uav:eventType` annotates an event affordance that projects a UA EventType; because an OPC UA event is always defined by a type derived from `BaseEventType`, it is meaningful in both a Thing Model and a Thing Description. `uav:eventType` is the type-annotation counterpart of the `uav:isEvent` flag (Section 6.1): the annotation records the projected NodeClass in `@type`, exactly as the other rows of this table do, while `uav:isEvent` is the boolean anchor of the event mapping (Section 8). When an event affordance carries `uav:eventType` in its `@type`, it **shall** be treated as projecting a UA EventType and `uav:isEvent` **shall not** be `false`; an author **should** set both for symmetry. A converter treats an event affordance as an EventType projection when either `uav:eventType` or `uav:isEvent: true` is present. + +### 5.3 Component reference terms + +| Term | Type | Meaning | +| --- | --- | --- | +| `uav:hasComponent` | array of string | One or more **ExpandedNodeId** values (Section 5.1.1) of child Nodes; equivalent to a forward `HasComponent` reference, and covering every subtype of `HasComponent` for parent-child discovery. | +| `uav:componentOf` | array of string | One or more **ExpandedNodeId** values (Section 5.1.1) of parent Nodes; equivalent to an inverse `HasComponent` reference, and covering every subtype of `HasComponent` for parent-child discovery. | + +**Component subtypes.** `uav:hasComponent` and `uav:componentOf` record parent-child ownership uniformly across `HasComponent` **and all of its subtypes** (for example `HasOrderedComponent`). A consumer that only needs to *discover* the children (or the parent) of a Node treats every `HasComponent`-derived reference the same way and reads these terms directly. When the exact subtype semantics matter — for example the ordering guaranteed by `HasOrderedComponent` — the document **shall** additionally record the reference as a typed link (Section 6.2): a `links` entry whose `rel` is the ReferenceType's compact model name and whose `uav:refName` names the reference. The link **should** also carry the ReferenceType's ExpandedNodeId in `uav:refId`; it **shall** do so when compact-name lookup may be unavailable or ambiguous. Reverse conversion recreates the exact subtype from that typed link; when no typed link is present for a listed component, conversion uses plain `HasComponent`. + +```jsonc +// Two ordered stages: discoverable through uav:hasComponent, and pinned to +// HasOrderedComponent by its semantic model name, with i=49 as the definitive fallback. +"uav:hasComponent": [ + "nsu=http://example.com/demo/pump;s=Stage_1", + "nsu=http://example.com/demo/pump;s=Stage_2" +], +"links": [ + { "rel": "ua:HasOrderedComponent", "href": "nsu=http://example.com/demo/pump;s=Stage_1", + "uav:refId": "i=49", "uav:refName": "Stage_1" }, + { "rel": "ua:HasOrderedComponent", "href": "nsu=http://example.com/demo/pump;s=Stage_2", + "uav:refId": "i=49", "uav:refName": "Stage_2" } +] +``` + +*Explanation.* Both stages appear in `uav:hasComponent`, so any consumer can enumerate the children without knowing the reference subtype. The two typed links use `ua:HasOrderedComponent` directly as their relation and `i=49` as the definitive fallback, so a converter recreates ordered components rather than plain `HasComponent`; a document that omitted the typed links would round-trip the stages as plain `HasComponent`. + +### 5.4 Runtime Node and DataType mapping terms + +These terms map a runtime property value either to an external OPC UA instance Node or to the DataType/field that defines its encoded data. They are used at property level and **shall not** appear at form level. + +| Term | Type | Meaning | +| --- | --- | --- | +| `uav:mapToNodeId` | string | The **ExpandedNodeId** (Section 5.1.1) of an external target instance Node (normally a Variable) that supplies or receives the runtime value. Because this is instance identity, a compact model name is not permitted. | +| `uav:mapToType` | string | The definitive **ExpandedNodeId** (Section 5.1.1) of the external target DataType used to interpret the runtime value. | +| `uav:mapToTypeName` | string | The compact model name (Section 5.1.2) of the same target DataType. Authors **should** provide this semantic hint, but it **shall** be accompanied by `uav:mapToType` because field decoding requires definitive DataType identity. | +| `uav:mapByFieldPath` | string | Used only together with `uav:mapToType`. When the target type is a Structure, names the field within that type to which the runtime data maps. | + +```jsonc +"uav:mapToTypeName": "pump:MeasurementDataType", +"uav:mapToType": "nsu=http://example.com/demo/pump;i=3010", +"uav:mapByFieldPath": "Value" +``` + +*Explanation.* The compact name conveys which DataType concept is intended; the ExpandedNodeId remains mandatory and definitive for runtime DataType resolution and field decoding. + +### 5.5 URI, base, and href rules + +The OPC UA client/server address of a Thing follows this grammar: + +```text +opc.tcp://
:[/]/?id= +``` + +where `
` is the Server endpoint address, `` is the Server port, `` is an optional endpoint resource path, and `` is the target Node identity, written as an **ExpandedNodeId** (Section 5.1.1). + +The following percent-encoding rules **shall** be applied to `` to keep the URI unambiguous under RFC 3986: every `#` **shall** be written as `%23` and every `&` **shall** be written as `%26`. When the whole URI is transmitted, all non-ASCII characters **shall** first be encoded as UTF-8 bytes and each byte then percent-encoded. These rules apply to the whole ExpandedNodeId, including the characters of an `nsu=` prefix. + +The address may be given whole in a form `href`, or split into the Thing-level `base` (the Server location only) and a per-form `href` that is relative to `base` and carries only the `?id=` fragment. The `` in `?id=` **shall** be the same portable ExpandedNodeId form required by Section 5.1.1, for example `href: "/?id=nsu=http://example.com/demo/pump;s=PumpSpeed"`; a document **shall not** use the session-local `ns=` form here. A client resolves the `nsu=` to the addressed Server's current namespace index (Section 5.8) at session establishment, so the reference survives a namespace-table reordering. A Node in the base OPC UA namespace **may** use the canonical `i=` form. + +### 5.6 Service mappings (Read, Write, Observe, Call) + +The OPC UA Service that an interaction uses is expressed by the standard WoT `op` term. The mapping is fixed: + +| `op` value | OPC UA Service | +| --- | --- | +| `readproperty` | Read | +| `writeproperty` | Write | +| `observeproperty` | Monitor (a Subscription MonitoredItem, per OPC 10000-4) | +| `invokeaction` | Call | + +When the Server's default serialization is used, a form's `contentType` **should** be `application/octet-stream`. + +Access is expressed with the standard WoT DataSchema and PropertyAffordance terms: a readable Variable carries `readproperty` and, when subscribable, `observeproperty` with `observable: true`; a read-only Variable sets `readOnly: true`; a writable Variable carries `writeproperty`; a write-only Variable sets `writeOnly: true`. + +### 5.7 Security schemes + +An OPC UA Server's endpoint security may be described implicitly with the standard WoT schemes or explicitly with the OPC UA schemes. + +- `nosec` — the standard WoT scheme, used when the Server offers a single endpoint with `securityMode` `None` and `securityPolicy` `None`. +- `auto` — the standard WoT scheme, used to signal that the Server offers several endpoints and the client is expected to call `GetEndpoints` and choose one during session establishment. +- `uav:channelsec` — the OPC UA secure-channel scheme. It carries `uav:securityMode` (one of `None`, `Sign`, `SignAndEncrypt`) and `uav:securityPolicy` (one of `None`, `Basic256Sha256`, `Aes128_Sha256_RsaOaep`, `Aes256_Sha256_RsaPss`; the outdated `Basic256` and `Basic128Rsa15` remain permitted but are not recommended). +- `uav:authentication` — the OPC UA user-authentication scheme. It carries `uav:userIdentityToken` (one of `Anonymous`, `UserName`, `Certificate`, `IssuedToken`) and, when the token is `IssuedToken`, an optional `uav:issueToken` that references another security scheme (for example an `oauth2` scheme) in the same document. + +The two OPC UA schemes are combined with the standard WoT `combo` scheme using `allOf`. Credentials such as passwords and certificates are never carried in a Thing Description; they are supplied out of band. A worked example is [`examples/01-opcua-td-pump.jsonld`](examples/01-opcua-td-pump.jsonld). + +### 5.8 Namespaces in the @context + +Every NamespaceUri used by a compact model name (Section 5.1.2) **shall** be bound to a stable, non-numeric prefix in the active `@context`; `ua` is reserved for the base OPC UA NamespaceUri. Authors should use recognizable domain prefixes such as `isa`, while a converter that has no authored prefix may generate deterministic `ns1`, `ns2`, and so on from the NodeSet `NamespaceUris` order. + +These context prefixes identify model concepts and provide the preferred portable presentation for readable QualifiedNames and BrowsePath elements. `uav:browseName` may alternatively use the OPC 10000-6 URI-qualified syntax of Section 5.1.3, and `uav:browsePath` may use OPC 10000-4 `{NamespaceUri}` qualification. Numeric namespace indexes are permitted only inside representations that carry their own namespace table, such as `uav:nodes`. + +### 5.9 Address-space example + +The figure below reads left-to-right: a short WoT Thing Description sample on the **left** and the OPC UA AddressSpace Nodes and References it projects to on the **right**. A UA Object `1:Pump` with a UA Variable `1:PumpSpeed` (`HasComponent`, `Double` `DataType`, `AccessLevel` `CurrentRead`) and a UA Method `1:Reset` becomes a Thing whose `@type` is `uav:object`; its property `pumpSpeed` (`@type uav:variable`) and action `reset` (`@type uav:method`) carry the browse names and the Read/Observe/Call forms. + +```mermaid +graph LR + subgraph WoT["WoT Thing Description (left)"] + direction TB + T["Thing @type=uav:object
uav:browseName pump:Pump"] + P["property pumpSpeed
@type uav:variable
type number, readOnly, observable
form op readproperty/observeproperty"] + A["action reset
@type uav:method
form op invokeaction"] + T --> P + T --> A + end + subgraph UA["OPC UA AddressSpace (right)"] + direction TB + O["Object 1:Pump
(HasTypeDefinition ...)"] + V["Variable 1:PumpSpeed
DataType Double, AccessLevel CurrentRead"] + M["Method 1:Reset"] + O -- HasComponent --> V + O -- HasComponent --> M + end + T -. projects to .-> O + P -. Read/Observe .-> V + A -. Call .-> M +``` + +**How to read it.** The Thing maps to the `Object` (`uav:browseName` → `BrowseName`); the property maps to the `Variable` with its `readproperty`/`observeproperty` forms realized as OPC UA Read and Subscription MonitoredItem services, and `readOnly`/`observable` reflect the Variable's `AccessLevel`; the action maps to the `Method`, invoked by the `invokeaction` form. The `HasComponent` References that hold the members are recovered from the containment terms (Section 5.3) or the model links (Section 6.2). References, NodeClasses, and attributes map in full as described in Section 9.1, and a complete worked instance is [`examples/01-opcua-td-pump.jsonld`](examples/01-opcua-td-pump.jsonld). + +## 6 Model and platform vocabulary + +This section adds terms that let a Thing Model record the structural facts of an OPC UA type that the preserved vocabulary alone does not capture. Every term is collision-safe with the base WoT context and with the preserved vocabulary, and each is documented below with the concept it expresses, **when and why** it represents an OPC UA model fact, its normative usage, and a short, explained example. Each term's domain, range, and conflict rules are also tabulated in Section 7. + +### 6.1 Composition and events + +**`uav:isComposite`** (boolean) — declares that a type is *composite*: an OPC UA ObjectType that is meaningfully decomposed into named sub-components rather than being a single leaf. It is an OPC UA model fact because a composite type owns child Objects/Variables through `HasComponent` References; a converter uses the flag to decide whether to walk and materialize the parts (Section 6.3) or treat the type as atomic. A composite type **shall** declare its parts through the containment terms of Section 6.3 and the link terms of Section 6.2. + +```jsonc +"@type": ["tm:ThingModel", "uav:objectType"], "uav:isComposite": true, "uav:contains": ["Impeller"] +``` + +*Explanation.* The Thing Model is a composite ObjectType with one directly contained part named `Impeller`; a converter expands that part into a component sub-node rather than a scalar value. + +**`uav:isEvent`** (boolean) — on a WoT event affordance, declares that the affordance projects an OPC UA event (a type derived from `BaseEventType`, OPC 10000-5) rather than an ad-hoc notification. This is an OPC UA model fact whenever the source model defines an EventType; the flag is the anchor of the event mapping of Section 8 and tells a consumer to realize subscription with event MonitoredItems. It is the boolean counterpart of the `uav:eventType` type annotation (Section 5.2): an event affordance annotated with `@type: uav:eventType` **shall not** set `uav:isEvent: false`, and an author **should** set both for symmetry with the other NodeClass annotations. + +```jsonc +"events": { "overTemperature": { "@type": "uav:eventType", "uav:isEvent": true, + "uav:browseName": "pump:OverTemperatureEventType" } } +``` + +*Explanation.* `overTemperature` is not a plain notification: its `@type` annotation `uav:eventType` (with the matching `uav:isEvent: true` flag) projects the `OverTemperatureEventType` EventType in the pump NamespaceUri, so `subscribeevent` becomes an OPC UA event MonitoredItem. + +### 6.2 Links and references + +References between types are carried on WoT `links`. A Binding-defined relation such as `uav:componentModel` selects a general mapping, while a NamespaceUri-qualified ReferenceType compact model name may be used directly in `rel` to express an exact OPC UA ReferenceType. These forms exist because an OPC UA type graph is more than containment: types reference each other hierarchically and non-hierarchically, and a faithful Thing Model must record which ReferenceType connects them. + +- **`rel: uav:capability`** — the linked resource is a capability the type exposes (an interface-like mix-in), projecting to a `HasInterface`-style facet. +- **`rel: uav:componentModel`** — the linked resource is the Thing Model of a contained sub-component (a strong, owned `HasComponent` part). +- **`rel: uav:reference`** — the linked resource is referenced non-hierarchically, without a specific reference type. +- **`rel: `** — the linked resource is referenced by that exact OPC UA ReferenceType, for example `rel: ua:HasOrderedComponent` or `rel: pump:MaterialReference`. This is also how a subtype of a hierarchical reference is pinned when its exact semantics matter. +- **`rel: uav:componentOf`** — the linked resource is the **parent** (container) of this Thing or type; it projects to an inverse `HasComponent` (a `HasComponent` from the parent to this node). It lets a Thing Description author select the parent under which its projected instance is exposed (used by [WoT Connectivity](../WoT-Connectivity/OPC-UA-WoT-Connectivity.md) §7.3); the link is directional, naming the parent, and a materializer resolves the target and creates the OPC UA `HasComponent` Reference. +- **`uav:refName`** (string) — the browse name a reference is exposed under on the referencing type; unique among the references of one type. +- **`uav:refId`** (string) — the definitive ReferenceType ExpandedNodeId used to disambiguate or resolve the model-name relation. The name makes clear that this member is an identifier outside the context-expanded `rel`. It **shall not** use the session-local `ns=` form; a base-namespace ReferenceType may use `i=`. + +If the ReferenceType relation resolves uniquely, `uav:refId` is optional; if lookup is unavailable or ambiguous, `uav:refId` is required. If both are present they **shall** resolve to the same ReferenceType Node. + +```jsonc +"links": [ + { "rel": "uav:componentModel", "href": "./impeller.tm.jsonld", + "uav:refName": "Impeller", + "uav:refId": "i=47" }, + { "rel": "pump:MaterialReference", "href": "./motor.tm.jsonld", + "uav:refName": "Drive", + "uav:refId": "nsu=http://example.com/demo/pump;i=5001" }, + { "rel": "uav:componentOf", "href": "urn:machine:line-01" } +] +``` + +*Explanation.* The first link owns an `Impeller` component through the Binding's component-model relation; the second references a `Motor` through the companion-defined `pump:MaterialReference`, with the ExpandedNodeId available for definitive resolution; the third targets a concrete parent instance and therefore uses an instance IRI rather than a compact model name. + +### 6.3 Containment + +**`uav:contains`** (array of string) — on a composite type, the `uav:refName` values of the sub-components it directly contains. **`uav:containedIn`** (string) — on a contained type, the name of the single composite that contains it. Together they record the OPC UA composition graph (the tree of `HasComponent` ownership) explicitly and symmetrically, so a converter can rebuild the parent/child edges and detect a broken model. Containment **shall** be acyclic and each side **shall** match the other (Section 7). + +```jsonc +// composite: // part: +"uav:contains": ["Impeller"] "uav:containedIn": "PumpType" +``` + +*Explanation.* `PumpType` contains a part reached by the `Impeller` link, and the part declares it is contained in `PumpType`; the reciprocal pair is what a validator checks for a consistent `HasComponent` tree. + +### 6.4 Type identity and naming + +**`uav:congruentType`** (string) — the definitive ExpandedNodeId (or an external absolute IRI) of a type that is structurally congruent with this one: a shared, co-typed definition used to reconcile two models that describe the same OPC UA type. **`uav:congruentTypeName`** (string) — the compact model name (Section 5.1.2) of that OPC UA type; when used, it **shall** accompany `uav:congruentType`. **`uav:nameNamespace`** (absolute IRI) — the naming namespace against which this type's local names are resolved; it corresponds to the OPC UA namespace that qualifies the type's BrowseNames and **shall** be an absolute IRI. + +```jsonc +"uav:nameNamespace": "http://example.com/demo/pump", +"uav:congruentTypeName": "pump:PumpType", +"uav:congruentType": "nsu=http://example.com/demo/pump;i=1001" +``` + +*Explanation.* The type's local names resolve within the pump NamespaceUri. `pump:PumpType` conveys the model concept, while the ExpandedNodeId definitively identifies the congruent type so independently authored prefixes do not affect comparison. + +### 6.5 Units, quantity kinds, and scaling + +**`uav:unitProperty`** (RFC 6901 JSON Pointer) — a canonical JSON Pointer that locates the string property carrying the engineering unit of a value; it corresponds to the OPC UA `EngineeringUnits` fact of an `AnalogUnitType`. Quantity kinds are expressed with QUDT (for example `qudt-quantitykind:AngularVelocity`) and are not given a `uav` term. **`uav:scaleFactor`** (number, non-zero) — the linear factor relating the raw transport value to the engineering value; the direction is fixed as `engineering = raw * scaleFactor`. **`uav:decimalPlaces`** (integer ≥ 0) — the number of fractional decimal places retained after scaling. These capture the analog-scaling model facts a raw transport value needs to become an engineering value. + +```jsonc +"pumpSpeed": { "type": "number", "unit": "qudt-quantitykind:AngularVelocity", + "uav:unitProperty": "/properties/pumpSpeed/unit", "uav:scaleFactor": 0.1, "uav:decimalPlaces": 2 } +``` + +*Explanation.* A raw reading of `2500` becomes `250.0` rpm (`2500 × 0.1`, two decimals); the unit string is located by the JSON Pointer, and the quantity kind is angular velocity. + +### 6.6 Groups and membership + +**`uav:propertyGroups`, `uav:eventGroups`, `uav:actionGroups`** (arrays of group objects) — declare named groups of properties, events, and actions respectively; each group object has a required `title` and may carry a `description` and a `uav:semanticId`. **`uav:memberOf`** (string) — on a property, event, or action, the `title` of the group it belongs to. Groups project to OPC UA grouping/organizing folders (or a `FunctionalGroupType`-style facet), so they record a model fact about how a type presents its members. Group titles **shall** be globally unique across all three group kinds of a type, and a `uav:memberOf` value **shall** name a group of the matching kind (Section 7). + +`uav:actionGroups` is intentionally named after the WoT **action** affordance that it groups. Each grouped action projects to an OPC UA Method, but naming the group `methodGroups` would expose the target protocol model instead of the WoT document model. + +```jsonc +"uav:propertyGroups": [{ "title": "Operational" }], +"pumpSpeed": { "uav:memberOf": "Operational" } +``` + +*Explanation.* `pumpSpeed` is presented under the `Operational` property group, which a converter can expose as an organizing folder over the projected Variables. + +### 6.7 Metadata, semantics, and configuration + +**`uav:metadata`** (JSON value) — an opaque object of implementation- or vendor-defined annotations, carried verbatim; it preserves model facts a converter does not interpret. **`uav:semanticId`** (absolute IRI) — a stable semantic identifier for a type, affordance, or group, corresponding to the OPC UA semantic-reference (`HasDictionaryEntry`-style) fact. **`uav:propertyConfiguration`, `uav:actionConfiguration`, `uav:eventConfiguration`** (JSON values) — opaque, per-affordance configuration objects, carried verbatim. Opaque members **shall** be carried unchanged and **shall not** cause a consumer to reject a document (Section 7). + +```jsonc +"uav:semanticId": "http://example.com/ontology/Pump", +"uav:metadata": { "revision": 3, "maintainer": "Modeling WG" } +``` + +*Explanation.* The type carries a stable semantic identity and a verbatim metadata bag; a converter preserves both even though it does not act on the metadata. + +### 6.8 Inheritance and open content + +**`uav:includeInherited`** (boolean) — whether the Thing Model is understood to include the members it inherits from its supertypes (`true`) or only the members it declares itself (`false`); this maps to whether a projection walks the OPC UA supertype chain. **`uav:additionalProperties`** (boolean) — whether instances may carry members beyond those the model declares (`true`, open content) or not (`false`, closed content), corresponding to whether the ObjectType is extensible. + +```jsonc +"uav:includeInherited": true, "uav:additionalProperties": false +``` + +*Explanation.* The model spans inherited members, and instances are closed: a validator rejects any instance member the type does not declare. + +### 6.9 Generic mapping terms and modelling rules + +**`uav:externalSchema`** (string) — a URI or path to an external schema that defines a custom DataType or payload encoding the affordance uses; it records the model fact that a member's DataType is not inline. **`uav:modellingRule`** (string) — the OPC UA modelling rule of a member; its value **shall** be exactly one of `Mandatory`, `Optional`, `MandatoryPlaceholder`, or `OptionalPlaceholder` (OPC 10000-3). The modelling rule is the single most important type-level model fact: it decides whether an instance must, may, or may repeatedly instantiate the member. + +```jsonc +"serialNumber": { "uav:modellingRule": "Mandatory" }, +"stage": { "uav:modellingRule": "MandatoryPlaceholder" } +``` + +*Explanation.* Every instance must carry a `serialNumber`; `stage` is a placeholder that expands, per instance, into one or more uniquely named `stage` members. + +### 6.10 Native projection and preservation terms + +**`uav:nodes`** (object) — the exceptional, schema-complete structured projection defined in Sections 9.2 and 10.1. Its `@type` is `uav:NodeModel`; it carries the UANodeSet root tables and one complete structured record for every node when some source information cannot yet be expressed by the readable vocabulary. + +```jsonc +"uav:nodes": { + "@type": "uav:NodeModel", "profileVersion": "1.0", + "namespaceUris": ["http://example.com/demo/pump"], + "nodes": [{ "nodeClass": "ObjectType", "nodeId": "ns=1;i=1001", + "browseName": "1:PumpType", "references": [ ... ] }] +} +``` + +*Explanation.* This fallback is used only when the semantic/readable vocabulary is incomplete for the source NodeSet. It covers every field of the UANodeSet schema and is reconstructed and compared before a converter claims completeness. + +**`uav:nodeSet`** (object) — the exceptional preservation envelope defined in Section 10.3. It carries an authoritative, byte-exact NodeSet2 baseline for explicit archival or a reported fallback when a future/unsupported construct cannot be represented by the supported `uav:NodeModel` profile. A converter **shall not** emit it by default when `uav:nodes` is complete. When present, a consumer **shall** verify its digest before use. + +```jsonc +"uav:nodeSet": { "@type": "uav:nodeSet", "contentType": "application/opcua-nodeset+xml", + "encoding": "base64", "sha256": "…", "data": "…" } +``` + +*Explanation.* The envelope pins exact NodeSet2 bytes and their SHA-256. It is separate from the native completeness proof and is not required for current UANodeSet constructs. + +## 7 Validation rules + +A document that uses the vocabulary of Section 6 **shall** satisfy the following rules. A consumer that finds a violation **shall** treat the document as invalid rather than silently repairing it. + +**Per-term domain and range.** + +| Term | Domain (where it may appear) | Range / allowed values | +| --- | --- | --- | +| `uav:isComposite` | type (TM root) | boolean | +| `uav:isEvent` | event affordance | boolean | +| `uav:eventType` | `@type` of an event affordance | the literal term; requires `uav:isEvent` not `false` | +| `uav:capability`, `uav:componentModel`, `uav:reference`, `uav:componentOf` | a link `rel` value | the literal term | +| ReferenceType compact model name | a link `rel` value | Section 5.1.2 model name resolving to a ReferenceType | +| `uav:refName` | a link | non-empty string, unique among a type's links | +| `uav:refId` | a typed ReferenceType relation or `uav:componentModel` link | definitive ExpandedNodeId (Section 5.1.1) | +| `uav:contains` | composite type | array of `uav:refName` values declared on the same type | +| `uav:containedIn` | contained type | the name of exactly one composite | +| `uav:id`, `uav:hasComponent`, `uav:componentOf`, `uav:mapToNodeId`, `uav:mapToType` | as defined in Sections 5.1, 5.3, 5.4 | ExpandedNodeId (Section 5.1.1) with no `ns=` | +| `uav:mapToTypeName` | property | compact model name of the `uav:mapToType` target; requires `uav:mapToType` | +| `uav:congruentType` | type | ExpandedNodeId or absolute IRI | +| `uav:congruentTypeName` | type | compact model name of the `uav:congruentType` target; requires `uav:congruentType` | +| `uav:nameNamespace` | type | absolute IRI | +| `uav:scaleFactor` | property | number, non-zero | +| `uav:decimalPlaces` | property | integer, `>= 0` | +| `uav:propertyGroups`, `uav:eventGroups`, `uav:actionGroups` | type | array of group objects, each with a non-empty `title` | +| `uav:memberOf` | property, event, or action | the `title` of a declared group of the matching kind | +| `uav:unitProperty` | property | non-empty RFC 6901 JSON Pointer to a string property | +| `uav:metadata`, `uav:propertyConfiguration`, `uav:actionConfiguration`, `uav:eventConfiguration` | as named | any JSON value | +| `uav:semanticId` | type, affordance, or group | absolute IRI | +| `uav:includeInherited`, `uav:additionalProperties` | type | boolean | +| `uav:externalSchema` | affordance | URI or path | +| `uav:modellingRule` | member | one of `Mandatory`, `Optional`, `MandatoryPlaceholder`, `OptionalPlaceholder` | +| `uav:nodes` | TD or TM root | `uav:NodeModel` object with supported `profileVersion` and a complete `nodes` array | +| `uav:nodeSet` | TD or TM root | exceptional envelope of Section 10.3 | + +**Cross-cutting rules.** + +- **Unique group titles.** The `title` of every group is globally unique across `uav:propertyGroups`, `uav:eventGroups`, and `uav:actionGroups` of a type. Two groups **shall not** share a title. +- **Membership target.** A `uav:memberOf` value **shall** name a group of the matching kind: a property's `uav:memberOf` **shall** name a `uav:propertyGroups` title, an event's an `uav:eventGroups` title, and an action's an `uav:actionGroups` title. +- **Portable identity.** Every NodeId-valued readable term — `uav:id`, each entry of `uav:hasComponent` and `uav:componentOf`, `uav:mapToNodeId`, `uav:mapToType`, `uav:refId`, and the `` of a `?id=` href — **shall** be an ExpandedNodeId per Section 5.1.1 and **shall not** use the session-local `ns=` form. NodeSet-local identities inside `uav:nodes` and namespace indexes inside `uav:nodeSet` are resolved through their own namespace tables. +- **Portable names and paths.** `uav:browseName` **shall** use a context-bound non-numeric prefix or the URI-qualified QualifiedName syntax of Section 5.1.3 (or the bare name for namespace 0). `uav:browsePath` **shall** use context-bound or NamespaceUri-qualified non-base path elements. Neither readable term shall persist numeric namespace indexes. +- **Model concept names.** Every typed Reference `rel`, `uav:mapToTypeName`, and `uav:congruentTypeName` value **shall** use the compact model-name form of Section 5.1.2, with a non-numeric prefix bound to the defining NamespaceUri. A consumer resolves it against the expected NodeClass; zero or multiple matches require the accompanying ExpandedNodeId, and disagreement between the two forms is an error. +- **Component subtypes.** `uav:hasComponent` / `uav:componentOf` cover `HasComponent` and all of its subtypes for parent-child discovery. When a listed component's exact subtype matters (for example `HasOrderedComponent`), the document **shall** also carry a link whose `rel` is that ReferenceType's compact model name and whose `uav:refName` names the reference; `uav:refId` supplies the definitive ExpandedNodeId when required. A converter recreates the exact subtype from that link and otherwise uses plain `HasComponent` (Section 5.3). +- **Event annotation consistency.** An event affordance annotated with `@type: uav:eventType` **shall not** set `uav:isEvent: false`; the two forms record the same fact (Section 5.2), and a consumer treats an affordance as an EventType projection when either is present. +- **Containment consistency.** If a composite `A` lists refName `b` in `uav:contains`, the type reached by the link named `b` **shall** declare `uav:containedIn: "A"`; conversely every `uav:containedIn: "A"` **shall** be matched by an entry in `A`'s `uav:contains`. +- **Parent link direction.** A `rel: uav:componentOf` link names the **parent** (container) of this Thing or type: it projects to an OPC UA `HasComponent` Reference *from the linked (parent) node to this node* (equivalently an inverse `HasComponent` from this node to its parent). A materializer **shall** resolve the link `href` to the parent node and create that Reference; it **shall not** invert the direction. +- **Cycle-safety.** The containment graph induced by `uav:contains` / `uav:containedIn` **shall** be acyclic; a type **shall not** transitively contain itself. +- **Scale direction and rounding.** The engineering value is computed as `raw * scaleFactor`; when `uav:decimalPlaces` is present, the engineering value is rounded to that many fractional decimal places after scaling. Consumers **shall not** apply the factor in the inverse direction. +- **Unit pointer.** `uav:unitProperty` **shall** be a canonical RFC 6901 JSON Pointer that resolves, within the same document, to a string-valued property. +- **Absolute IRIs.** `uav:semanticId` and `uav:nameNamespace` **shall** be absolute IRIs (they have a scheme). +- **Opaque objects.** `uav:metadata` and the three configuration members are opaque: a consumer that does not understand them **shall** carry them unchanged and **shall not** reject a document for their content. +- **Placeholders.** A member whose `uav:modellingRule` is `MandatoryPlaceholder` or `OptionalPlaceholder` is a template that expands, per instance, to zero or more uniquely named members; an optional non-placeholder member is omitted per instance when absent. + +## 8 Thing Description and Thing Model mapping + +A Thing Model is the class-level projection and a Thing Description is the instance-level projection of the same OPC UA constructs. + +- **ObjectType maps to a Thing Model.** An ObjectType becomes a TM whose `@type` includes `uav:objectType`; its InstanceDeclarations become the TM's affordances with a `uav:modellingRule`. +- **Object maps to a Thing Description.** An Object becomes a TD whose `@type` includes `uav:object`; a TD may reference the TM of its type through a `tm:instanceOf` style link and carries concrete values and forms. +- **Variables map to properties.** A UA Variable or the declaration of a VariableType member becomes a WoT property with `@type` `uav:variable` (TD) or `uav:variableType` (TM), a `type` from the DataType, and `readOnly` / `writeOnly` / `observable` from the AccessLevel. `observable: true` and an `observeproperty` form declare that the TD exposes observation through this binding; their absence does not state that the UA Variable is technically incapable of becoming a MonitoredItem, because OPC UA permits monitoring any Variable for which the Server grants the required access. +- **Methods map to actions.** A UA Method becomes a WoT action with `@type` `uav:method`; its input and output arguments become the action's `input` and `output` DataSchemas. +- **OPC UA events map to WoT events.** An EventType (derived from `BaseEventType`) becomes a WoT event affordance whose `@type` includes `uav:eventType` and that carries `uav:isEvent: true`; the event's fields become the event `data` schema, the standard fields (`EventId`, `EventType`, `SourceNode`, `Time`, `Severity`, `Message`) map to the corresponding `data` properties, and `uav:eventConfiguration` carries opaque delivery configuration. Subscription uses the WoT `subscribeevent` / `unsubscribeevent` operations, realized by OPC UA event MonitoredItems. This event mapping is new in this document. +- **Links and references.** Non-hierarchical and typed references map to `links` as in Section 6.2; hierarchical component references map to `uav:hasComponent` / `uav:componentOf` (Section 5.3) or, at model level, to `uav:contains` / `uav:containedIn`. +- **Custom DataTypes and encodings.** A custom DataType maps to a WoT DataSchema; where the schema cannot be expressed inline, `uav:externalSchema` points to its definition and, for a Structure, `uav:mapByFieldPath` addresses a field. +- **Placeholders and optionality.** Modelling rules map to `uav:modellingRule`; placeholder members are templates (Section 7) and optional members are omitted per instance when absent. + +## 9 NodeSet2 and WoT conversion + +Conversion is bidirectional. The semantic/readable mapping is primary and lets a WoT consumer act on a Thing without decoding model records. If a NodeSet contains information that this vocabulary cannot yet express, `uav:nodes` provides an exceptional, versioned, structured projection of the complete UANodeSet schema. No construct defined by the current UANodeSet schema requires the opaque envelope of Section 10.3. + +### 9.1 Native readable mapping + +| OPC UA construct | WoT projection | +| --- | --- | +| Object (NodeClass) | Thing / nested Thing, `@type` `uav:object` | +| Variable | property, `@type` `uav:variable` | +| Method | action, `@type` `uav:method` | +| ObjectType | Thing Model, `@type` `uav:objectType` | +| VariableType | property in a TM, `@type` `uav:variableType` | +| EventType (subtype of `BaseEventType`) | event affordance, `@type` `uav:eventType`, `uav:isEvent: true` | +| DataType | WoT DataSchema; custom types via `uav:externalSchema` | +| ReferenceType | compact model name used directly in link `rel`, with optional/required `uav:refId` | +| View | link (`rel` `collection`) grouping the viewed Nodes | +| `NodeId` | `uav:id` (ExpandedNodeId, Section 5.1.1) | +| `BrowseName` | `uav:browseName` | +| `DisplayName` | `title` | +| `Description` | `description` | +| `DataType` + `ValueRank` + `ArrayDimensions` | `type` and array `items` of the DataSchema | +| `AccessLevel` | `readOnly` / `writeOnly` / `observable` | +| `Value` | property value or `const` / `default` | +| `HasComponent` / `HasProperty` (forward) | `uav:hasComponent`, or a component `link` | +| `HasComponent` (inverse) | `uav:componentOf` | +| `HasComponent` subtype (for example `HasOrderedComponent`) | `uav:hasComponent` for discovery **and** a link whose `rel` is the ReferenceType compact model name, with `uav:refId` when required (Section 5.3) | +| Other typed references | link whose `rel` is the ReferenceType compact model name and whose `uav:refId` is present when required | +| Modelling rule (`Mandatory` `i=78`, `Optional` `i=80`, `MandatoryPlaceholder` `i=11510`, `OptionalPlaceholder` `i=11508`) | `uav:modellingRule` | +| Namespace table | `@context` prefix bindings keyed by namespace index; identity terms name namespaces by URI (Section 5.1.1) | +| Events (`BaseEventType` subtypes) | event affordance, `@type` `uav:eventType`, `uav:isEvent: true` | + +### 9.2 Exceptional structured `uav:nodes` projection + +A converter from NodeSet2 **shall first** emit and verify the semantic/readable mapping of Section 9.1. It **shall omit** `uav:nodes` when converting that readable document back reproduces an equivalent UANodeSet. If the comparison identifies source information not expressible by the current vocabulary, the converter **shall** add a `uav:nodes` object whose `@type` is `uav:NodeModel` and whose `profileVersion` identifies the projection grammar. Version `1.0` represents: + +- the complete `NamespaceUris`, `ServerUris`, `Models`, `Aliases`, root `Extensions`, and `LastModified` fields; +- every top-level and required model entry, including role permissions and access restrictions; +- every node of all eight NodeClasses, in source order; +- every common and NodeClass-specific UANodeSet attribute, localized text, category, documentation, role permission, Reference, method argument, translation, DataType definition field, and Value; +- each arbitrary XML `Value` or `Extension` element as an individual XML fragment, not as an encoded copy of the NodeSet document. + +The projection keeps NodeSet-local `ns=` identities together with its own `namespaceUris` table. Those internal values are not persisted AddressSpace identities and are therefore outside the portable-identity rule of Section 5.1.1. The readable `uav:id`, link, and form identities remain URI-qualified ExpandedNodeIds. + +Because UANodeSet provides `Extensions` at the document and UANode levels and because arbitrary XML content is representable as an extension fragment, the projection has no exception for a construct defined by the current UANodeSet schema. A converter **shall** reconstruct the projection and compare the result with its source before claiming lossless conversion. Emitting `uav:nodes` merely as a duplicate of an already-complete readable mapping is non-conformant. + +### 9.3 Layered exceptional preservation policy + +The conversion layers are applied in this order: + +- The semantic/readable vocabulary is the default. If its reverse conversion is equivalent to the source NodeSet2, the converter **shall not** emit `uav:nodes` or `uav:nodeSet`. +- `uav:nodes` is emitted only when the comparison finds information that the current vocabulary cannot yet express. +- A converter **may** emit `uav:nodeSet` when a caller explicitly requests byte-exact archival. +- A converter **may** use `uav:nodeSet` as a last-resort fallback for a future schema revision or unsupported extension that it can demonstrate is not represented by the supported `uav:NodeModel` profile. It **shall** report that fallback. +- A conformance/completeness test **shall** forbid or remove `uav:nodeSet`; a test that succeeds by decoding the envelope does not prove the readable mapping plus exceptional structured projection complete. + +When `uav:nodeSet` is present it is authoritative for the exact XML baseline. A consumer **shall** verify its digest before use. A simultaneously present `uav:nodes` projection and readable members **shall** be consistent with that baseline; a conflict **shall** be reported and **shall not** be silently resolved. + +### 9.4 Synthesis from an authored TD or TM + +When an authored document has no `uav:nodes`, a converter synthesizes NodeSet2 from the readable members. NodeIds that the document does not supply are generated deterministically from the target namespace URI and browse path. The same document therefore yields the same identities. + +Recognized WoT and `uav` facts are represented as NodeSet model facts. A JSON-LD member that the converter does not recognize or cannot map is **not** grounds for a whole-document envelope. Instead, the converter stores only that unmapped value as a `WoTJsonResidue` entry in the root UANodeSet `Extensions` collection (Section 10.2). Each entry identifies the original location by RFC 6901 JSON Pointer and carries the JSON value with an integrity digest. Mapped members **shall not** be copied into the residue. + +On conversion back to WoT, the converter first generates the document from OPC UA model facts and then applies each residue entry. A residue entry that targets an already generated member **shall** have the same JSON value; otherwise it is a conflict and the converter **shall** report an error rather than overwrite the OPC UA fact. + +### 9.5 Roundtrip invariants + +- **NodeSet2 → WoT → NodeSet2 (default).** With `uav:nodeSet` absent, the readable mapping alone, or the readable mapping plus exceptional `uav:nodes` when required, reproduces an equivalent UANodeSet: all model-table data, nodes, attributes, References, values, definitions, aliases, permissions, and extensions are retained. XML formatting or prefix choices need not be byte-identical. +- **WoT → NodeSet2 → WoT.** Every recognized member is regenerated from OPC UA model facts and every unrecognized/unmapped JSON-LD member is restored from pointer-addressed residue with the same JSON value. +- **Explicit byte archive.** When a caller explicitly retains `uav:nodeSet`, decoding it reproduces the original NodeSet2 bytes exactly after digest verification. + +## 10 Native projection, residue, and exceptional envelope formats + +### 10.1 `uav:nodes` + +The exceptional `uav:nodes` value is a JSON object: + +| Member | Required | Value | +| --- | --- | --- | +| `@type` | yes | the constant `uav:NodeModel` | +| `profileVersion` | yes | the native projection grammar, currently `1.0` | +| `namespaceUris`, `serverUris` | when present in source | ordered URI arrays | +| `models` | when present in source | ordered model-table entries and recursive `requiredModels` | +| `aliases` | when present in source | ordered alias/value records | +| `extensions` | when present in source | ordered XML extension fragments | +| `lastModified` | when present in source | XML Schema `dateTime` | +| `nodes` | yes | ordered complete node records | + +Each node record has `nodeClass`, `nodeId`, and `browseName`, followed by the applicable fields of `UANode` and its concrete NodeClass in the UANodeSet schema. The grammar uses lower-camel-case names corresponding to the XSD field names. `references`, localized-text arrays, DataType `definition`, `argumentDescriptions`, and `translations` are structured JSON. `valueXml` and entries of `extensions` contain one well-formed XML element each. A worked example is [`examples/05-native-node-model.jsonld`](examples/05-native-node-model.jsonld). + +### 10.2 `WoTJsonResidue` NodeSet Extension + +The root UANodeSet `Extensions` collection may contain one `WoTJsonResidue` element in the Binding namespace: + +```xml + + eyJtb2RlIjoiZ29vZCJ9 + eyJocmVmbGFuZyI6ImVuIn0= + +``` + +`Pointer` is an RFC 6901 JSON Pointer into the regenerated WoT document. `Encoding` is `base64`; the decoded bytes are exactly one JSON value; `Sha256` is the lower-case hexadecimal SHA-256 digest of those decoded bytes. A link whose array position is not stable across regeneration uses `Pointer="/links/-"` and the optional selector attributes `LinkRel`, `LinkHref`, `LinkRefId`, and `LinkRefName`; the decoded JSON object then contains only the unmapped link members. The converter matches the regenerated link by those stable identifiers or creates it if the readable mapping did not emit one. It **shall not** address mapped-link extras by the source array index. + +A converter **shall** verify the digest, JSON syntax, pointer syntax, size/depth limits, selectors, and conflicts before applying residue. It **shall** store only unmapped members or unmapped submembers, never a copy of the complete TD/TM and never a duplicate of a mapped OPC UA fact. Additional `@context` terms are merged into the regenerated `uav` context object by term name rather than by the authored array position. + +### 10.3 Exceptional `uav:nodeSet` envelope + +The optional `uav:nodeSet` envelope is a JSON object with these members: + +| Member | Required | Value | +| --- | --- | --- | +| `@type` | yes | the constant `uav:nodeSet` | +| `contentType` | yes | the media type of the decoded bytes, for example `application/opcua-nodeset+xml` | +| `encoding` | yes | the constant `base64` (RFC 4648 section 4) | +| `sha256` | yes | the lower-case hexadecimal SHA-256 digest of the **decoded** bytes | +| `data` | yes | the base64 encoding of the canonical NodeSet2 XML bytes | +| `profileVersion` | recommended | the preservation profile version, for example `1.0` | + +The decoded bytes **shall** be a well-formed XML document whose root element is `UANodeSet` in the namespace `http://opcfoundation.org/UA/2011/03/UANodeSet.xsd`. This format is not the default conversion representation. An explicit archival example is [`examples/03-nodeset-preservation-envelope.jsonld`](examples/03-nodeset-preservation-envelope.jsonld). + +## 11 Conformance units and profiles + +A Server, a Thing Description author, or a converter declares conformance to the units it implements. The units are independent; a profile is a named set of units. + +| Unit | Requirement | +| --- | --- | +| **WoT-ProtocolBinding** | The preserved protocol binding of Section 5: URI/base/href, the four service mappings, access levels, and the security schemes. | +| **WoT-NativeMapping** | The semantic/readable mapping of Section 9.1, including proof that `uav:nodes` is omitted when that mapping is complete. | +| **WoT-StructuredFallback** | The exceptional complete `uav:nodes` projection of Sections 9.2 and 10.1. | +| **WoT-JsonResidue** | Pointer-addressed unmapped-member preservation using the NodeSet Extension of Section 10.2. | +| **WoT-NodeSetPreservation** | The exceptional `uav:nodeSet` envelope of Section 10.3, including digest verification. | +| **WoT-ExactRoundtrip** | The native, envelope-free roundtrip invariants of Section 9.5, including JSON residue. | +| **WoT-EventMapping** | The OPC UA event to WoT event mapping of Section 8. | +| **WoT-ModelVocabulary** | The model and platform vocabulary of Section 6 and its validation rules in Section 7. | +| **WoT-ExternalResolver** | Resolution of `uav:externalSchema`, `uav:mapToType`, `uav:mapToNodeId`, and cross-document links. | + +Recommended profiles: + +- **WoT-Reader** — WoT-ProtocolBinding and WoT-NativeMapping. The minimum for a client that reads a Thing Description and talks to the Server. +- **WoT-Modeller** — WoT-Reader plus WoT-ModelVocabulary and WoT-EventMapping. For tools that author or interpret Thing Models. +- **WoT-Converter** — WoT-Modeller plus WoT-StructuredFallback, WoT-JsonResidue and WoT-ExactRoundtrip. For tools that convert between NodeSet2 and WoT losslessly without requiring an envelope. +- **WoT-ArchivalConverter** — WoT-Converter plus WoT-NodeSetPreservation. For callers that explicitly require the original NodeSet2 bytes. + +## Annex A — JSON-LD context and schema (informative) + +The machine-readable vocabulary is [`opc-ua-wot-binding.context.jsonld`](opc-ua-wot-binding.context.jsonld); it binds the `uav` prefix and declares every term of Sections 5, 6, and 10. The structural constraints are [`opc-ua-wot-binding.schema.json`](opc-ua-wot-binding.schema.json), a JSON Schema (2020-12) that validates the `uav` members, native projection, and exceptional preservation envelope in addition to the base Thing Description schema. + +## Annex B — Examples (informative) + +- [`examples/01-opcua-td-pump.jsonld`](examples/01-opcua-td-pump.jsonld) — a Thing Description using the preserved Read, Write, Observe, Call, and security vocabulary. +- [`examples/02-thing-model-pump.jsonld`](examples/02-thing-model-pump.jsonld) — a Thing Model using the model and platform vocabulary. +- [`examples/03-nodeset-preservation-envelope.jsonld`](examples/03-nodeset-preservation-envelope.jsonld) — a preservation envelope carrying a canonical NodeSet2 baseline. +- [`examples/04-type-reference-modelling-rule.jsonld`](examples/04-type-reference-modelling-rule.jsonld) — a Thing Model exercising type, reference (including a `HasOrderedComponent` subtype pinned by a typed link), and modelling-rule mappings. +- [`examples/05-native-node-model.jsonld`](examples/05-native-node-model.jsonld) — the exceptional, schema-complete `uav:nodes` projection with no opaque preservation envelope. + +## Annex C — Validation (informative) + +Run the deterministic, standard-library validator from the repository root: + +```bash +python wot-specs/WoT-Binding/tools/validate_local.py +``` + +It confirms that every artifact parses, that the context declares every documented `uav` term, that each example declares the `uav` context, that the native example carries `uav:NodeModel` profile `1.0` and no envelope, that each exceptional envelope's base64 and SHA-256 are valid and decode to a well-formed `UANodeSet`, that internal relative references resolve, that every NodeId-valued readable term in an example is a portable ExpandedNodeId and never the session-local `ns=` form (Section 5.1.1), that `@type: uav:eventType` is never paired with `uav:isEvent: false` (Section 5.2), and that no forbidden vendor prefix, namespace, or legacy modelling-language name appears. + +## Annex D — Implementer walkthrough (informative) + +This annex walks an implementer through both conversion directions for the pump example, step by step, using concise snippets rather than full files. The source files are [`examples/02-thing-model-pump.jsonld`](examples/02-thing-model-pump.jsonld) (the `PumpType` Thing Model) and [`examples/01-opcua-td-pump.jsonld`](examples/01-opcua-td-pump.jsonld) (a `Pump` Thing Description). + +### D.1 Forward: Thing Model → OPC UA types + +**Step 1 — Root type.** The TM root `@type` selects the NodeClass. `uav:objectType` → an `ObjectType`; `uav:browseName` becomes its `BrowseName`, `uav:nameNamespace` its namespace. + +```jsonc +"@type": ["tm:ThingModel", "uav:objectType"], +"uav:browseName": "pump:PumpType", +"uav:isComposite": true +``` + +→ `ObjectType 1:PumpType` (composite; its parts follow from `uav:contains` / links). + +**Step 2 — Property members → Variable declarations.** Each property becomes an instance-declaration `Variable`; `type` → DataType, `uav:modellingRule` → the modelling rule, unit/scaling from Section 6.5. + +```jsonc +"pumpSpeed": { "@type": "uav:variableType", + "uav:browseName": "pump:PumpSpeed", + "type": "number", "uav:modellingRule": "Mandatory", "uav:scaleFactor": 0.1 } +``` + +→ `Variable 1:PumpSpeed` (`DataType Double`, `HasModellingRule Mandatory`, scaling 0.1). A `MandatoryPlaceholder` property (for example `stage`) becomes a placeholder declaration. + +**Step 3 — Action members → Method declarations.** `uav:method` → a `Method`; its `input`/`output` schemas become `InputArguments`/`OutputArguments`. + +```jsonc +"reset": { "@type": "uav:method", + "uav:browseName": "pump:Reset", + "uav:modellingRule": "Optional" } +``` + +→ `Method 1:Reset` (`HasModellingRule Optional`). + +**Step 4 — Event members → EventTypes.** An affordance whose `@type` includes `uav:eventType` (equivalently `uav:isEvent: true`) projects an EventType derived from `BaseEventType`; its `data` schema becomes the event fields (Section 8). + +```jsonc +"overTemperature": { "@type": "uav:eventType", "uav:isEvent": true, + "uav:browseName": "pump:OverTemperatureEventType" } +``` + +→ `EventType 1:OverTemperatureEventType` (a `BaseEventType` subtype); `subscribeevent` → an event MonitoredItem. + +**Step 5 — References.** `links` become References: `uav:componentModel` → `HasComponent` (the `Impeller` part), a compact ReferenceType model name in `rel` → that exact ReferenceType with `uav:refId` as fallback, `uav:reference`/`uav:capability` → non-hierarchical/`HasInterface` references, and `uav:componentOf` → the parent `HasComponent` (Section 6.2). `uav:contains` / `uav:containedIn` rebuild the `HasComponent` ownership tree. A component listed in `uav:hasComponent` is recreated as a plain `HasComponent` unless a matching link pins a subtype such as `rel: "ua:HasOrderedComponent"` (`uav:refId: "i=49"`), in which case that exact subtype is emitted (Section 5.3). + +**Step 6 — Groups and modelling rules.** `uav:propertyGroups` etc. become organizing folders over the members; the modelling rules from Step 2/3 govern how instances of `PumpType` are built. + +### D.2 Forward: Thing Description → OPC UA instance + +**Step 1 — Root object.** `uav:object` → an `Object`; a `tm:instanceOf`/`links rel=type` to `PumpType` sets its `HasTypeDefinition`. + +```jsonc +"@type": ["Thing", "uav:object"], +"uav:browseName": "pump:Pump", +"uav:id": "nsu=...;s=Pump" +``` + +→ `Object 1:Pump` (`HasTypeDefinition PumpType`, `NodeId` resolved from the `uav:id` ExpandedNodeId; the `nsu=` namespace URI is mapped to the Server's current namespace index at session time, Section 5.1.1). + +**Step 2 — Properties → Variables with values and forms.** Each TD property is a `Variable`; `readOnly`/`writeOnly`/`observable` set the `AccessLevel`; each `form` compiles to a service call. + +```jsonc +"pumpSpeed": { "@type": "uav:variable", "uav:id": "nsu=...;s=PumpSpeed", "readOnly": true, + "observable": true, "forms": [{ "op": ["readproperty","observeproperty"] }] } +``` + +→ `Variable 1:PumpSpeed` (`AccessLevel CurrentRead`); `readproperty` → Read, `observeproperty` → a Subscription MonitoredItem. The operation advertises observation through the WoT binding; it does not define whether the Variable is technically monitorable in OPC UA, because any Variable may be monitored when the Server permits it. + +**Step 3 — Actions → Methods; events → event MonitoredItems.** `uav:method` actions become callable `Method` instances (`invokeaction` → Call); events annotated `@type uav:eventType` (with `uav:isEvent: true`) are subscribed via event MonitoredItems (`subscribeevent`). + +**Step 4 — Parent placement.** With no parent link, the `Object` is `Organizes`d under `Objects`; a `rel: uav:componentOf` link makes it a `HasComponent` child of the resolved parent (Section 6.2). + +### D.3 Reverse: OPC UA AddressSpace / NodeSet2 → Thing Model / Thing Description + +The reverse is the mirror image; a converter walks the NodeSet2, emits the readable `uav` terms, and emits the complete `uav:nodes` projection. It does not emit `uav:nodeSet` in the normal case. + +**Step 1 — Classify the root.** An `ObjectType` → a TM with `@type uav:objectType`; an `Object` → a TD with `@type uav:object`. Emit `uav:browseName` from `BrowseName`, `title` from `DisplayName`, `description` from `Description`. + +**Step 2 — Members → affordances.** Each `HasComponent`/`HasProperty` `Variable` → a property (`uav:variable`/`uav:variableType`); each `Method` → an action; each `GeneratesEvent` EventType → an event with `@type uav:eventType` and `uav:isEvent: true`. Emit `type` from `DataType` + `ValueRank`, `readOnly`/`observable` from `AccessLevel`, and `uav:modellingRule` from the `HasModellingRule` target. Emit every identity term (`uav:id`, `uav:hasComponent`, `uav:componentOf`) as an ExpandedNodeId, mapping each namespace index to its `NamespaceUris` URI (Section 5.1.1). + +```text +Variable 1:PumpSpeed (DataType Double, CurrentRead, Mandatory) +→ "pumpSpeed": { "@type":"uav:variableType", "type":"number", "readOnly":true, "uav:modellingRule":"Mandatory" } +``` + +**Step 3 — References → links / containment.** `HasComponent` (forward) → `uav:contains` / a `uav:componentModel` link; `HasComponent` (inverse) → a `uav:componentOf` link; a typed Reference → a link whose `rel` is the ReferenceType compact model name and whose `uav:refId` is present when required. A `HasComponent` **subtype** such as `HasOrderedComponent` is emitted both as a `uav:hasComponent` entry (for discovery) and as a link carrying `rel: "ua:HasOrderedComponent"` plus `uav:refId: "i=49"`, so the exact subtype survives the round trip (Section 5.3). Other non-hierarchical References → `uav:reference`/`uav:capability`. + +**Step 4 — Units, scaling, groups, semantics.** An `AnalogUnitType` `EngineeringUnits` Property → `uav:unitProperty` + a QUDT quantity kind; scaling Properties → `uav:scaleFactor` / `uav:decimalPlaces`; organizing folders → `uav:propertyGroups`/`memberOf`; `HasDictionaryEntry`-style references → `uav:semanticId`. + +**Step 5 — Test the readable mapping, then use structured fallback if needed.** Convert the readable TM back to UANodeSet and compare it with the source. If it is equivalent, omit `uav:nodes`. Otherwise emit `uav:nodes` with `@type: uav:NodeModel`, profile `1.0`, the namespace/model/alias tables, and complete records for every node. Values and XML extensions are individual XML fragments. Reconstruct this projection and compare it with the source before reporting success. + +**Step 6 — Use the envelope only by exception.** Do not emit `uav:nodeSet` when Step 5 is complete. Emit it only when the caller explicitly requests byte-exact archival or when a future/unsupported construct demonstrably cannot be represented by the supported native profile, and report that fallback. + +**Roundtrip check.** Converting `PumpType` to a TM and back with `uav:nodeSet` forbidden reproduces every member declaration, modelling rule, unit, reference, value, DataType definition, permission, alias and extension. This envelope-free check is the conformance proof; an optional archival check may separately verify byte identity. diff --git a/wot-specs/WoT-Binding/README.md b/wot-specs/WoT-Binding/README.md new file mode 100644 index 0000000..6055eae --- /dev/null +++ b/wot-specs/WoT-Binding/README.md @@ -0,0 +1,47 @@ +# OPC UA — Web of Things (WoT) Binding + +A complete, standalone draft revision of the OPC UA companion specification for Web of Things connectivity. It is not an addendum: the specification can be read on its own. It preserves every namespace, prefix, term, and normative behaviour of the published baseline and adds a collision-safe model and platform vocabulary together with semantic-first, bidirectional NodeSet2 conversion. + +> Experimental and non-normative. Nothing here is official or endorsed by the OPC Foundation or the W3C; `opcfoundation.org` namespace URIs are used for prototyping only. + +## Purpose + +- Describe an OPC UA interface as a W3C Thing Description or Thing Model, using the preserved Read / Write / Observe / Call and security vocabulary. +- Express the structural facts of an OPC UA type — composition, references, groups, units, scaling, configuration, metadata, and modelling rules — in a Thing Model. +- Pair definitive ExpandedNodeIds with NamespaceUri-qualified model names such as `ua:HasOrderedComponent` so type-level concepts remain semantically readable without weakening instance identity. +- Convert between an OPC UA NodeSet2 information model and a Thing Description or Thing Model without loss through the semantic/readable vocabulary, adding the complete `uav:nodes` projection only when source facts cannot yet be expressed. Unmapped WoT members survive as pointer-addressed NodeSet extension residue; `uav:nodeSet` is reserved for explicit byte archival or a demonstrated final fallback. + +## Sources + +- [OPC 10101 — OPC UA for WoT Binding](https://reference.opcfoundation.org/specs/OPC-10101/) — the published version superseded by this draft. +- [W3C Web of Things (WoT) Thing Description 1.1](https://www.w3.org/TR/wot-thing-description11/) and [WoT Binding Templates](https://www.w3.org/TR/wot-binding-templates/). +- [OPC 10000-3](https://reference.opcfoundation.org/specs/OPC-10000-3/), [10000-4](https://reference.opcfoundation.org/specs/OPC-10000-4/), [10000-5](https://reference.opcfoundation.org/specs/OPC-10000-5/), [10000-6](https://reference.opcfoundation.org/specs/OPC-10000-6/), [10000-7](https://reference.opcfoundation.org/specs/OPC-10000-7/). +- [QUDT](http://qudt.org/) for quantity kinds and units; [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901), [RFC 4648](https://www.rfc-editor.org/rfc/rfc4648), and [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986). +- Two architecture decision records (ADR 0029 and ADR 0032) are used only as design inputs for the model vocabulary; a design-input crosswalk is kept, informatively, under [`../extras/WoT-Binding/adr-to-uav-crosswalk.md`](../extras/WoT-Binding/adr-to-uav-crosswalk.md) — **not** in the normative specification. No vocabulary, prefix, or namespace of those inputs is reused. + +## Artifacts + +- [`OPC-UA-WoT-Binding.md`](OPC-UA-WoT-Binding.md) — the full specification. +- [`opc-ua-wot-binding.context.jsonld`](opc-ua-wot-binding.context.jsonld) — the JSON-LD context binding the `uav` prefix and every documented term. +- [`opc-ua-wot-binding.schema.json`](opc-ua-wot-binding.schema.json) — the extension and preservation JSON Schema (2020-12). +- [`examples/`](examples/) — worked examples: + - [`01-opcua-td-pump.jsonld`](examples/01-opcua-td-pump.jsonld) — a Thing Description using the preserved Read / Write / Observe / Call and security vocabulary. + - [`02-thing-model-pump.jsonld`](examples/02-thing-model-pump.jsonld) — a Thing Model using the model and platform vocabulary. + - [`03-nodeset-preservation-envelope.jsonld`](examples/03-nodeset-preservation-envelope.jsonld) — a `uav:nodeSet` preservation envelope carrying a canonical NodeSet2 baseline. + - [`04-type-reference-modelling-rule.jsonld`](examples/04-type-reference-modelling-rule.jsonld) — type, semantic model-name/reference (including `ua:HasOrderedComponent` plus its `i=49` fallback), and modelling-rule mappings. + - [`05-native-node-model.jsonld`](examples/05-native-node-model.jsonld) — the exceptional schema-complete `uav:nodes` representation without an opaque envelope. +- [`tools/validate_local.py`](tools/validate_local.py) — the deterministic, standard-library validator. + +## Namespace and prefix + +The vocabulary namespace is `http://opcfoundation.org/UA/WoT-Binding/`, bound to the prefix `uav`. Both are preserved unchanged from the published baseline. + +## Validation + +Run the validator from the repository root (standard library only, no dependencies): + +```bash +python wot-specs/WoT-Binding/tools/validate_local.py +``` + +It checks that every JSON and JSON-LD artifact parses, that the context contains every documented `uav` term, that each example declares the `uav` context, that the native example uses `uav:NodeModel` profile `1.0` without an envelope, that each exceptional envelope's base64 and SHA-256 are valid and decode to a well-formed `UANodeSet` root, that internal relative references resolve, that every NodeId-valued readable term in an example is a portable ExpandedNodeId (never the session-local `ns=` form), that `@type: uav:eventType` is never paired with `uav:isEvent: false`, and that no forbidden vendor prefix, namespace, or legacy modelling-language name appears. It prints `OK` and exits `0` on success. diff --git a/wot-specs/WoT-Binding/examples/01-opcua-td-pump.jsonld b/wot-specs/WoT-Binding/examples/01-opcua-td-pump.jsonld new file mode 100644 index 0000000..2138837 --- /dev/null +++ b/wot-specs/WoT-Binding/examples/01-opcua-td-pump.jsonld @@ -0,0 +1,87 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { + "uav": "http://opcfoundation.org/UA/WoT-Binding/", + "pump": "http://example.com/demo/pump" + }, + "../opc-ua-wot-binding.context.jsonld" + ], + "@type": "uav:object", + "id": "urn:dev:opcua:pump-01", + "title": "Pump 01", + "description": "A Thing Description of a single OPC UA Object using only the preserved OPC 10101 vocabulary: Read, Write, Observe (Monitor) and Call, plus the explicit OPC UA security schemes.", + "uav:browseName": "pump:Pump", + "uav:id": "nsu=http://example.com/demo/pump;s=Pump", + "links": [ + { "rel": "uav:componentOf", "href": "nsu=http://example.com/demo/pump;s=Line01" } + ], + "securityDefinitions": { + "opcua_channel_sc": { + "scheme": "uav:channelsec", + "uav:securityMode": "SignAndEncrypt", + "uav:securityPolicy": "Aes256_Sha256_RsaPss" + }, + "opcua_authentication_sc": { + "scheme": "uav:authentication", + "uav:userIdentityToken": "UserName" + }, + "opcua_sc": { + "scheme": "combo", + "allOf": ["opcua_channel_sc", "opcua_authentication_sc"] + } + }, + "security": "opcua_sc", + "base": "opc.tcp://opcuademo.com:4840", + "properties": { + "pumpSpeed": { + "@type": "uav:variable", + "title": "Pump Speed", + "uav:browseName": "pump:PumpSpeed", + "uav:browsePath": "/Objects/pump:Pump/pump:PumpSpeed", + "type": "number", + "unit": "rpm", + "readOnly": true, + "observable": true, + "forms": [ + { + "href": "/?id=nsu=http://example.com/demo/pump;s=PumpSpeed", + "contentType": "application/octet-stream", + "op": ["readproperty", "observeproperty"] + } + ] + }, + "speedSetpoint": { + "@type": "uav:variable", + "title": "Speed Setpoint", + "uav:browseName": "pump:SpeedSetpoint", + "uav:browsePath": "/Objects/pump:Pump/pump:SpeedSetpoint", + "type": "number", + "unit": "rpm", + "readOnly": false, + "observable": true, + "forms": [ + { + "href": "/?id=nsu=http://example.com/demo/pump;s=SpeedSetpoint", + "contentType": "application/octet-stream", + "op": ["readproperty", "writeproperty", "observeproperty"] + } + ] + } + }, + "actions": { + "reset": { + "@type": "uav:method", + "title": "Reset", + "uav:browseName": "pump:Reset", + "uav:browsePath": "/Objects/pump:Pump/pump:Reset", + "forms": [ + { + "href": "/?id=nsu=http://example.com/demo/pump;s=Reset", + "contentType": "application/octet-stream", + "op": ["invokeaction"] + } + ] + } + } +} diff --git a/wot-specs/WoT-Binding/examples/02-thing-model-pump.jsonld b/wot-specs/WoT-Binding/examples/02-thing-model-pump.jsonld new file mode 100644 index 0000000..8821983 --- /dev/null +++ b/wot-specs/WoT-Binding/examples/02-thing-model-pump.jsonld @@ -0,0 +1,114 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { + "uav": "http://opcfoundation.org/UA/WoT-Binding/", + "pump": "http://example.com/demo/pump", + "qudt-quantitykind": "http://qudt.org/vocab/quantitykind/" + }, + "../opc-ua-wot-binding.context.jsonld" + ], + "@type": ["tm:ThingModel", "uav:objectType"], + "title": "PumpType", + "description": "A Thing Model of an OPC UA ObjectType that exercises the model and platform vocabulary: composition, groups, membership, references, units and scaling, configuration, metadata and modelling rules.", + "uav:browseName": "pump:PumpType", + "uav:id": "nsu=http://example.com/demo/pump;i=1001", + "uav:isComposite": true, + "uav:includeInherited": true, + "uav:additionalProperties": false, + "uav:nameNamespace": "http://example.com/demo/pump", + "uav:semanticId": "http://example.com/ontology/Pump", + "uav:congruentTypeName": "pump:PumpType", + "uav:congruentType": "nsu=http://example.com/demo/pump;i=1001", + "uav:contains": ["Impeller"], + "uav:metadata": { + "revision": 3, + "maintainer": "Modeling Working Group" + }, + "uav:propertyGroups": [ + { + "title": "Operational", + "description": "Live operational data.", + "uav:semanticId": "http://example.com/ontology/Operational" + }, + { "title": "Nameplate" } + ], + "uav:eventGroups": [{ "title": "Diagnostics" }], + "uav:actionGroups": [{ "title": "Maintenance" }], + "links": [ + { + "rel": "uav:componentModel", + "href": "./04-type-reference-modelling-rule.jsonld", + "uav:refName": "Impeller", + "uav:refId": "i=47" + }, + { + "rel": "pump:DriveReference", + "href": "https://example.com/models/MotorType.tm.jsonld", + "uav:refName": "Drive", + "uav:refId": "nsu=http://example.com/demo/pump;i=4002" + }, + { "rel": "uav:capability", "href": "https://example.com/capabilities/telemetry" }, + { "rel": "uav:reference", "href": "https://example.com/models/NameplateType.tm.jsonld" } + ], + "properties": { + "pumpSpeed": { + "@type": "uav:variableType", + "title": "Pump Speed", + "uav:browseName": "pump:PumpSpeed", + "type": "number", + "unit": "qudt-quantitykind:AngularVelocity", + "uav:modellingRule": "Mandatory", + "uav:memberOf": "Operational", + "uav:unitProperty": "/properties/pumpSpeed/unit", + "uav:scaleFactor": 0.1, + "uav:decimalPlaces": 2, + "observable": true, + "readOnly": true + }, + "serialNumber": { + "@type": "uav:variableType", + "title": "Serial Number", + "uav:browseName": "pump:SerialNumber", + "type": "string", + "uav:modellingRule": "Mandatory", + "uav:memberOf": "Nameplate" + }, + "stage": { + "@type": "uav:variableType", + "title": "Stage", + "uav:browseName": "pump:Stage", + "type": "number", + "uav:modellingRule": "MandatoryPlaceholder", + "uav:memberOf": "Operational" + } + }, + "actions": { + "reset": { + "@type": "uav:method", + "title": "Reset", + "uav:browseName": "pump:Reset", + "uav:modellingRule": "Optional", + "uav:memberOf": "Maintenance", + "uav:actionConfiguration": { "timeoutMs": 5000, "confirm": true }, + "uav:externalSchema": "https://example.com/schemas/reset-args.json" + } + }, + "events": { + "overTemperature": { + "@type": "uav:eventType", + "title": "Over Temperature", + "uav:isEvent": true, + "uav:browseName": "pump:OverTemperatureEventType", + "uav:memberOf": "Diagnostics", + "uav:semanticId": "http://example.com/ontology/OverTemperature", + "uav:eventConfiguration": { "severity": 500 }, + "data": { + "type": "object", + "properties": { + "temperature": { "type": "number", "unit": "qudt-quantitykind:Temperature" } + } + } + } + } +} diff --git a/wot-specs/WoT-Binding/examples/03-nodeset-preservation-envelope.jsonld b/wot-specs/WoT-Binding/examples/03-nodeset-preservation-envelope.jsonld new file mode 100644 index 0000000..038b18d --- /dev/null +++ b/wot-specs/WoT-Binding/examples/03-nodeset-preservation-envelope.jsonld @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { + "uav": "http://opcfoundation.org/UA/WoT-Binding/", + "pump": "http://example.com/demo/pump" + }, + "../opc-ua-wot-binding.context.jsonld" + ], + "@type": "uav:object", + "id": "urn:dev:opcua:pump-01-preserved", + "title": "Pump 01 (explicit byte-exact NodeSet archive)", + "description": "An exceptional archival example that explicitly carries a byte-exact NodeSet2 baseline in a uav:nodeSet envelope. The semantic/readable vocabulary is primary, uav:nodes is an exceptional structured fallback, and this opaque envelope is emitted only when byte identity is requested or a future/unsupported construct cannot be represented structurally.", + "uav:browseName": "pump:Pump", + "uav:id": "nsu=http://example.com/demo/pump;s=Pump", + "uav:nodeSet": { + "@type": "uav:nodeSet", + "contentType": "application/opcua-nodeset+xml", + "encoding": "base64", + "sha256": "2612f43e0094a2ae38802ae6a5055d66415c2d81552224cfe3ab7811e9ce3868", + "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPFVBTm9kZVNldCB4bWxucz0iaHR0cDovL29wY2ZvdW5kYXRpb24ub3JnL1VBLzIwMTEvMDMvVUFOb2RlU2V0LnhzZCIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSI+CiAgPE5hbWVzcGFjZVVyaXM+CiAgICA8VXJpPmh0dHA6Ly9leGFtcGxlLmNvbS9kZW1vL3B1bXA8L1VyaT4KICA8L05hbWVzcGFjZVVyaXM+CiAgPE1vZGVscz4KICAgIDxNb2RlbCBNb2RlbFVyaT0iaHR0cDovL2V4YW1wbGUuY29tL2RlbW8vcHVtcCIgVmVyc2lvbj0iMS4wLjAiIFB1YmxpY2F0aW9uRGF0ZT0iMjAyNi0wMS0wMVQwMDowMDowMFoiPgogICAgICA8UmVxdWlyZWRNb2RlbCBNb2RlbFVyaT0iaHR0cDovL29wY2ZvdW5kYXRpb24ub3JnL1VBLyIgVmVyc2lvbj0iMS4wNS4wMyIgUHVibGljYXRpb25EYXRlPSIyMDIzLTEyLTE1VDAwOjAwOjAwWiIgLz4KICAgIDwvTW9kZWw+CiAgPC9Nb2RlbHM+CiAgPFVBT2JqZWN0VHlwZSBOb2RlSWQ9Im5zPTE7aT0xMDAxIiBCcm93c2VOYW1lPSIxOlB1bXBUeXBlIiBJc0Fic3RyYWN0PSJmYWxzZSI+CiAgICA8RGlzcGxheU5hbWU+UHVtcFR5cGU8L0Rpc3BsYXlOYW1lPgogICAgPFJlZmVyZW5jZXM+CiAgICAgIDxSZWZlcmVuY2UgUmVmZXJlbmNlVHlwZT0iSGFzU3VidHlwZSIgSXNGb3J3YXJkPSJmYWxzZSI+aT01ODwvUmVmZXJlbmNlPgogICAgICA8UmVmZXJlbmNlIFJlZmVyZW5jZVR5cGU9Ikhhc0NvbXBvbmVudCI+bnM9MTtpPTYwMDE8L1JlZmVyZW5jZT4KICAgIDwvUmVmZXJlbmNlcz4KICA8L1VBT2JqZWN0VHlwZT4KICA8VUFWYXJpYWJsZSBOb2RlSWQ9Im5zPTE7aT02MDAxIiBCcm93c2VOYW1lPSIxOlB1bXBTcGVlZCIgRGF0YVR5cGU9IkRvdWJsZSIgUGFyZW50Tm9kZUlkPSJucz0xO2k9MTAwMSI+CiAgICA8RGlzcGxheU5hbWU+UHVtcFNwZWVkPC9EaXNwbGF5TmFtZT4KICAgIDxSZWZlcmVuY2VzPgogICAgICA8UmVmZXJlbmNlIFJlZmVyZW5jZVR5cGU9Ikhhc1R5cGVEZWZpbml0aW9uIj5pPTYzPC9SZWZlcmVuY2U+CiAgICAgIDxSZWZlcmVuY2UgUmVmZXJlbmNlVHlwZT0iSGFzTW9kZWxsaW5nUnVsZSI+aT03ODwvUmVmZXJlbmNlPgogICAgICA8UmVmZXJlbmNlIFJlZmVyZW5jZVR5cGU9Ikhhc0NvbXBvbmVudCIgSXNGb3J3YXJkPSJmYWxzZSI+bnM9MTtpPTEwMDE8L1JlZmVyZW5jZT4KICAgIDwvUmVmZXJlbmNlcz4KICA8L1VBVmFyaWFibGU+CjwvVUFOb2RlU2V0Pgo=", + "profileVersion": "1.0" + }, + "properties": { + "pumpSpeed": { + "@type": "uav:variable", + "title": "Pump Speed", + "uav:browseName": "pump:PumpSpeed", + "uav:id": "nsu=http://example.com/demo/pump;i=6001", + "type": "number", + "unit": "rpm", + "readOnly": true, + "observable": true, + "forms": [ + { + "href": "/?id=nsu=http://example.com/demo/pump;i=6001", + "contentType": "application/octet-stream", + "op": [ + "readproperty", + "observeproperty" + ] + } + ] + } + } +} diff --git a/wot-specs/WoT-Binding/examples/04-type-reference-modelling-rule.jsonld b/wot-specs/WoT-Binding/examples/04-type-reference-modelling-rule.jsonld new file mode 100644 index 0000000..ce96505 --- /dev/null +++ b/wot-specs/WoT-Binding/examples/04-type-reference-modelling-rule.jsonld @@ -0,0 +1,85 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { + "uav": "http://opcfoundation.org/UA/WoT-Binding/", + "ua": "http://opcfoundation.org/UA/", + "pump": "http://example.com/demo/pump", + "qudt-quantitykind": "http://qudt.org/vocab/quantitykind/" + }, + "../opc-ua-wot-binding.context.jsonld" + ], + "@type": ["tm:ThingModel", "uav:objectType"], + "title": "ImpellerType", + "description": "A Thing Model of an OPC UA ObjectType showing type, reference and modelling-rule mappings: subtype extension, contained-in composition, congruent type identity, forward and inverse HasComponent references, a typed reference, a HasOrderedComponent subtype pinned by a typed link, and the four modelling-rule values.", + "uav:browseName": "pump:ImpellerType", + "uav:id": "nsu=http://example.com/demo/pump;i=1005", + "uav:containedIn": "PumpType", + "uav:componentOf": ["nsu=http://example.com/demo/pump;i=1001"], + "uav:congruentTypeName": "pump:ImpellerType", + "uav:congruentType": "nsu=http://example.com/demo/pump;i=1005", + "uav:hasComponent": [ + "nsu=http://example.com/demo/pump;s=Blade_1", + "nsu=http://example.com/demo/pump;s=Blade_2" + ], + "links": [ + { "rel": "tm:extends", "href": "https://example.com/models/BaseComponentType.tm.jsonld" }, + { + "rel": "pump:MaterialReference", + "href": "https://example.com/models/MaterialType.tm.jsonld", + "uav:refName": "Material", + "uav:refId": "nsu=http://example.com/demo/pump;i=5001" + }, + { + "rel": "ua:HasOrderedComponent", + "href": "nsu=http://example.com/demo/pump;s=Blade_1", + "uav:refName": "Blade_1", + "uav:refId": "i=49" + }, + { + "rel": "ua:HasOrderedComponent", + "href": "nsu=http://example.com/demo/pump;s=Blade_2", + "uav:refName": "Blade_2", + "uav:refId": "i=49" + } + ], + "properties": { + "diameter": { + "@type": "uav:variableType", + "title": "Diameter", + "uav:browseName": "pump:Diameter", + "type": "number", + "unit": "qudt-quantitykind:Length", + "uav:modellingRule": "Mandatory", + "uav:unitProperty": "/properties/diameter/unit" + }, + "bladeAngle": { + "@type": "uav:variableType", + "title": "Blade Angle", + "uav:browseName": "pump:BladeAngle", + "type": "number", + "unit": "qudt-quantitykind:Angle", + "uav:modellingRule": "OptionalPlaceholder", + "uav:scaleFactor": 0.01, + "uav:decimalPlaces": 3 + }, + "vibration": { + "@type": "uav:variableType", + "title": "Vibration", + "uav:browseName": "pump:Vibration", + "type": "number", + "uav:mapToTypeName": "pump:MeasurementDataType", + "uav:mapToType": "nsu=http://example.com/demo/pump;i=3010", + "uav:modellingRule": "Optional" + } + }, + "actions": { + "recalibrate": { + "@type": "uav:method", + "title": "Recalibrate", + "uav:browseName": "pump:Recalibrate", + "uav:modellingRule": "Optional", + "uav:hasComponent": ["nsu=http://example.com/demo/pump;i=7001"] + } + } +} diff --git a/wot-specs/WoT-Binding/examples/05-native-node-model.jsonld b/wot-specs/WoT-Binding/examples/05-native-node-model.jsonld new file mode 100644 index 0000000..64a222a --- /dev/null +++ b/wot-specs/WoT-Binding/examples/05-native-node-model.jsonld @@ -0,0 +1,84 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { + "uav": "http://opcfoundation.org/UA/WoT-Binding/", + "pump": "http://example.com/demo/pump" + }, + "../opc-ua-wot-binding.context.jsonld" + ], + "@type": [ + "tm:ThingModel", + "uav:objectType" + ], + "title": "PumpType (exceptional structured NodeSet projection)", + "uav:browseName": "pump:PumpType", + "uav:id": "nsu=http://example.com/demo/pump;i=1001", + "uav:nodes": { + "@type": "uav:NodeModel", + "profileVersion": "1.0", + "namespaceUris": [ + "http://example.com/demo/pump" + ], + "models": [ + { + "modelUri": "http://example.com/demo/pump", + "version": "1.0.0", + "publicationDate": "2026-01-01T00:00:00Z", + "requiredModels": [ + { + "modelUri": "http://opcfoundation.org/UA/", + "version": "1.05.03" + } + ] + } + ], + "aliases": [ + { + "alias": "PumpType", + "value": "ns=1;i=1001" + } + ], + "nodes": [ + { + "nodeClass": "ObjectType", + "nodeId": "ns=1;i=1001", + "browseName": "1:PumpType", + "displayName": [ + { + "value": "PumpType" + } + ], + "references": [ + { + "referenceType": "HasSubtype", + "isForward": false, + "target": "i=58" + }, + { + "referenceType": "HasComponent", + "target": "ns=1;i=6001" + } + ] + }, + { + "nodeClass": "Variable", + "nodeId": "ns=1;i=6001", + "browseName": "1:PumpSpeed", + "parentNodeId": "ns=1;i=1001", + "dataType": "Double", + "accessLevel": 1, + "references": [ + { + "referenceType": "HasTypeDefinition", + "target": "i=63" + }, + { + "referenceType": "HasModellingRule", + "target": "i=78" + } + ] + } + ] + } +} diff --git a/wot-specs/WoT-Binding/opc-ua-wot-binding.context.jsonld b/wot-specs/WoT-Binding/opc-ua-wot-binding.context.jsonld new file mode 100644 index 0000000..aaadf7d --- /dev/null +++ b/wot-specs/WoT-Binding/opc-ua-wot-binding.context.jsonld @@ -0,0 +1,92 @@ +{ + "@context": { + "@version": 1.1, + "uav": "http://opcfoundation.org/UA/WoT-Binding/", + "ua": "http://opcfoundation.org/UA/", + "qudt": "http://qudt.org/schema/qudt/", + "qudt-quantitykind": "http://qudt.org/vocab/quantitykind/", + + "uav:id": { "@id": "uav:id" }, + "uav:browsePath": { "@id": "uav:browsePath" }, + "uav:browseName": { "@id": "uav:browseName" }, + "uav:object": { "@id": "uav:object" }, + "uav:objectType": { "@id": "uav:objectType" }, + "uav:variable": { "@id": "uav:variable" }, + "uav:variableType": { "@id": "uav:variableType" }, + "uav:method": { "@id": "uav:method" }, + "uav:eventType": { "@id": "uav:eventType" }, + "uav:hasComponent": { "@id": "uav:hasComponent", "@container": "@set" }, + "uav:componentOf": { "@id": "uav:componentOf", "@container": "@set" }, + "uav:mapToNodeId": { "@id": "uav:mapToNodeId" }, + "uav:mapToType": { "@id": "uav:mapToType" }, + "uav:mapToTypeName": { "@id": "uav:mapToTypeName" }, + "uav:mapByFieldPath": { "@id": "uav:mapByFieldPath" }, + + "uav:channelsec": { "@id": "uav:channelsec" }, + "uav:authentication": { "@id": "uav:authentication" }, + "uav:securityMode": { "@id": "uav:securityMode" }, + "uav:securityPolicy": { "@id": "uav:securityPolicy" }, + "uav:userIdentityToken": { "@id": "uav:userIdentityToken" }, + "uav:issueToken": { "@id": "uav:issueToken" }, + + "uav:isComposite": { "@id": "uav:isComposite" }, + "uav:isEvent": { "@id": "uav:isEvent" }, + + "uav:capability": { "@id": "uav:capability" }, + "uav:componentModel": { "@id": "uav:componentModel" }, + "uav:reference": { "@id": "uav:reference" }, + "uav:refName": { "@id": "uav:refName" }, + "uav:refId": { "@id": "uav:refId" }, + + "uav:contains": { "@id": "uav:contains", "@container": "@set" }, + "uav:containedIn": { "@id": "uav:containedIn" }, + + "uav:congruentType": { "@id": "uav:congruentType" }, + "uav:congruentTypeName": { "@id": "uav:congruentTypeName" }, + "uav:nameNamespace": { "@id": "uav:nameNamespace", "@type": "@id" }, + + "uav:scaleFactor": { "@id": "uav:scaleFactor" }, + "uav:decimalPlaces": { "@id": "uav:decimalPlaces" }, + + "uav:propertyGroups": { "@id": "uav:propertyGroups", "@container": "@set" }, + "uav:eventGroups": { "@id": "uav:eventGroups", "@container": "@set" }, + "uav:actionGroups": { "@id": "uav:actionGroups", "@container": "@set" }, + "uav:memberOf": { "@id": "uav:memberOf" }, + + "uav:unitProperty": { "@id": "uav:unitProperty" }, + + "uav:metadata": { "@id": "uav:metadata", "@type": "@json" }, + "uav:semanticId": { "@id": "uav:semanticId", "@type": "@id" }, + + "uav:actionConfiguration": { "@id": "uav:actionConfiguration", "@type": "@json" }, + "uav:propertyConfiguration": { "@id": "uav:propertyConfiguration", "@type": "@json" }, + "uav:eventConfiguration": { "@id": "uav:eventConfiguration", "@type": "@json" }, + + "uav:includeInherited": { "@id": "uav:includeInherited" }, + + "uav:additionalProperties": { "@id": "uav:additionalProperties" }, + "uav:externalSchema": { "@id": "uav:externalSchema" }, + "uav:modellingRule": { "@id": "uav:modellingRule" }, + + "uav:NodeModel": { "@id": "uav:NodeModel" }, + "uav:nodes": { + "@id": "uav:nodes", + "@context": { + "@version": 1.1, + "@vocab": "http://opcfoundation.org/UA/WoT-Binding/" + } + }, + + "uav:nodeSet": { + "@id": "uav:nodeSet", + "@context": { + "@version": 1.1, + "contentType": "uav:contentType", + "encoding": "uav:encoding", + "sha256": "uav:sha256", + "data": "uav:data", + "profileVersion": "uav:profileVersion" + } + } + } +} diff --git a/wot-specs/WoT-Binding/opc-ua-wot-binding.schema.json b/wot-specs/WoT-Binding/opc-ua-wot-binding.schema.json new file mode 100644 index 0000000..628889f --- /dev/null +++ b/wot-specs/WoT-Binding/opc-ua-wot-binding.schema.json @@ -0,0 +1,264 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://opcfoundation.org/UA/WoT-Binding/opc-ua-wot-binding.schema.json", + "title": "OPC UA WoT Binding extension and NodeSet conversion schema", + "description": "Structural constraints for the uav model/platform vocabulary, the complete uav:nodes native projection, and the exceptional uav:nodeSet preservation envelope defined by the OPC UA Web of Things Binding. This schema validates extension members that a Thing Description or Thing Model MAY carry; it does not restate the base W3C Thing Description schema. Apply it in addition to the base Thing Description validation.", + "type": "object", + "$defs": { + "expandedNodeId": { + "type": "string", + "minLength": 1, + "pattern": "^(svr=[0-9]+;)?(nsu=[^;]+;)?[isgb]=.+$", + "not": { "pattern": "ns=[0-9]" }, + "description": "OPC 10000-6 ExpandedNodeId string form that identifies a UA Node portably: 'nsu=;=' (for example 'nsu=http://example.com/demo/pump;s=Pump') or, for the base OPC UA namespace, the canonical 'i=' (for example 'i=49'). The session-local 'ns=' form is not permitted in a persisted or interchanged document because a Server may reorder its namespace table." + }, + "absoluteIri": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:", + "description": "An absolute IRI (has a scheme)." + }, + "modelConceptName": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9._-]*:.+$", + "description": "Binding compact model name ':'. The prefix resolves to the defining OPC UA NamespaceUri. This is a semantic model lookup hint, not Node identity." + }, + "qualifiedName": { + "type": "string", + "minLength": 1, + "pattern": "^(nsu=[^;]+;.+|[A-Za-z_][A-Za-z0-9._-]*:.+|(?![0-9]+:).+)$", + "description": "Portable QualifiedName presentation: preferred ':', bare Name for namespace 0, or OPC 10000-6 'nsu=;'. Numeric NamespaceIndex prefixes are not permitted in persisted readable members." + }, + "jsonPointer": { + "type": "string", + "pattern": "^(/([^/~]|~0|~1)*)+$", + "description": "A non-empty RFC 6901 JSON Pointer that locates a string property." + }, + "modellingRule": { + "type": "string", + "enum": ["Mandatory", "Optional", "MandatoryPlaceholder", "OptionalPlaceholder"] + }, + "group": { + "type": "object", + "required": ["title"], + "properties": { + "title": { "type": "string", "minLength": 1 }, + "description": { "type": "string" }, + "uav:semanticId": { "$ref": "#/$defs/absoluteIri" } + } + }, + "groupSet": { + "type": "array", + "items": { "$ref": "#/$defs/group" } + }, + "opaqueJson": { + "description": "An opaque JSON value carried verbatim; consumers that do not understand it preserve it unchanged.", + "type": ["object", "array", "string", "number", "boolean", "null"] + }, + "nodeSetEnvelope": { + "type": "object", + "description": "The exceptional uav:nodeSet preservation envelope: an explicit byte-exact archival baseline or a last-resort fallback when a future/unsupported construct cannot be represented by uav:nodes. A converter shall not emit it when the semantic mapping or structured projection is complete.", + "required": ["@type", "contentType", "encoding", "sha256", "data"], + "properties": { + "@type": { "const": "uav:nodeSet" }, + "contentType": { + "type": "string", + "description": "The media type of the decoded bytes.", + "default": "application/opcua-nodeset+xml" + }, + "encoding": { "const": "base64" }, + "sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "Lower-case hexadecimal SHA-256 digest of the decoded (canonical NodeSet2 XML) bytes." + }, + "data": { + "type": "string", + "pattern": "^[A-Za-z0-9+/]+={0,2}$", + "description": "Base64 (RFC 4648 section 4) encoding of the canonical NodeSet2 XML bytes." + }, + "profileVersion": { + "type": "string", + "description": "The preservation profile version, for example '1.0'." + } + } + }, + "nodeModel": { + "type": "object", + "description": "The exceptional, versioned, schema-complete structured projection of the UANodeSet XSD, used only when source information cannot yet be expressed by the semantic/readable vocabulary. It represents root metadata, namespace/server URI tables, models and dependencies, aliases, XML extensions, and one complete record for each of the eight NodeClasses. XML-valued Value and Extension members are represented as individual XML fragments, never as a whole-document NodeSet blob.", + "required": ["@type", "profileVersion", "nodes"], + "properties": { + "@type": { "const": "uav:NodeModel" }, + "profileVersion": { "const": "1.0" }, + "namespaceUris": { + "type": "array", + "items": { "type": "string" } + }, + "serverUris": { + "type": "array", + "items": { "type": "string" } + }, + "models": { + "type": "array", + "items": { "type": "object" } + }, + "aliases": { + "type": "array", + "items": { + "type": "object", + "required": ["alias", "value"], + "properties": { + "alias": { "type": "string", "minLength": 1 }, + "value": { "type": "string", "minLength": 1 } + } + } + }, + "extensions": { + "type": "array", + "items": { "type": "string" } + }, + "lastModified": { + "type": "string", + "format": "date-time" + }, + "nodes": { + "type": "array", + "items": { + "type": "object", + "required": ["nodeClass", "nodeId", "browseName"], + "properties": { + "nodeClass": { + "enum": [ + "Object", + "Variable", + "Method", + "View", + "ObjectType", + "VariableType", + "DataType", + "ReferenceType" + ] + }, + "nodeId": { "type": "string", "minLength": 1 }, + "browseName": { "type": "string", "minLength": 1 }, + "references": { + "type": "array", + "items": { + "type": "object", + "required": ["referenceType", "target"], + "properties": { + "referenceType": { "type": "string", "minLength": 1 }, + "isForward": { "type": "boolean" }, + "target": { "type": "string", "minLength": 1 } + } + } + } + } + } + } + } + }, + "uavMembers": { + "type": "object", + "description": "The uav extension members that MAY appear on a Thing Description, Thing Model, affordance, form, or link.", + "properties": { + "uav:id": { "$ref": "#/$defs/expandedNodeId" }, + "uav:browsePath": { "type": "string" }, + "uav:browseName": { "$ref": "#/$defs/qualifiedName" }, + "uav:hasComponent": { "type": "array", "items": { "$ref": "#/$defs/expandedNodeId" } }, + "uav:componentOf": { "type": "array", "items": { "$ref": "#/$defs/expandedNodeId" } }, + "uav:mapToNodeId": { "$ref": "#/$defs/expandedNodeId" }, + "uav:mapToType": { "$ref": "#/$defs/expandedNodeId" }, + "uav:mapToTypeName": { "$ref": "#/$defs/modelConceptName" }, + "uav:mapByFieldPath": { "type": "string" }, + + "uav:isComposite": { "type": "boolean" }, + "uav:isEvent": { "type": "boolean" }, + "uav:refName": { "type": "string" }, + "uav:refId": { "$ref": "#/$defs/expandedNodeId" }, + "uav:contains": { "type": "array", "items": { "type": "string" } }, + "uav:containedIn": { "type": "string" }, + "uav:congruentType": { "type": "string" }, + "uav:congruentTypeName": { "$ref": "#/$defs/modelConceptName" }, + "uav:nameNamespace": { "$ref": "#/$defs/absoluteIri" }, + "uav:scaleFactor": { "type": "number" }, + "uav:decimalPlaces": { "type": "integer", "minimum": 0 }, + "uav:propertyGroups": { "$ref": "#/$defs/groupSet" }, + "uav:eventGroups": { "$ref": "#/$defs/groupSet" }, + "uav:actionGroups": { "$ref": "#/$defs/groupSet" }, + "uav:memberOf": { "type": "string" }, + "uav:unitProperty": { "$ref": "#/$defs/jsonPointer" }, + "uav:metadata": { "$ref": "#/$defs/opaqueJson" }, + "uav:semanticId": { "$ref": "#/$defs/absoluteIri" }, + "uav:actionConfiguration": { "$ref": "#/$defs/opaqueJson" }, + "uav:propertyConfiguration": { "$ref": "#/$defs/opaqueJson" }, + "uav:eventConfiguration": { "$ref": "#/$defs/opaqueJson" }, + "uav:includeInherited": { "type": "boolean" }, + "uav:additionalProperties": { "type": "boolean" }, + "uav:externalSchema": { "type": "string" }, + "uav:modellingRule": { "$ref": "#/$defs/modellingRule" }, + "uav:nodes": { "$ref": "#/$defs/nodeModel" }, + "uav:nodeSet": { "$ref": "#/$defs/nodeSetEnvelope" } + }, + "dependentRequired": { + "uav:mapToTypeName": ["uav:mapToType"], + "uav:congruentTypeName": ["uav:congruentType"] + } + }, + "securityChannelScheme": { + "type": "object", + "properties": { + "scheme": { "const": "uav:channelsec" }, + "uav:securityMode": { + "type": "string", + "enum": ["None", "Sign", "SignAndEncrypt"] + }, + "uav:securityPolicy": { + "type": "string", + "enum": [ + "None", + "Basic256Sha256", + "Aes128_Sha256_RsaOaep", + "Aes256_Sha256_RsaPss", + "Basic256", + "Basic128Rsa15" + ] + } + }, + "required": ["scheme", "uav:securityMode", "uav:securityPolicy"] + }, + "securityAuthenticationScheme": { + "type": "object", + "properties": { + "scheme": { "const": "uav:authentication" }, + "uav:userIdentityToken": { + "type": "string", + "enum": ["Anonymous", "UserName", "Certificate", "IssuedToken"] + }, + "uav:issueToken": { "type": "string" } + }, + "required": ["scheme", "uav:userIdentityToken"] + }, + "eventTypeAnnotation": { + "description": "An event affordance whose @type includes the uav:eventType annotation projects a UA EventType (a subtype of BaseEventType, OPC 10000-5). uav:eventType is the type-annotation counterpart of the uav:isEvent flag: where the annotation is present, uav:isEvent shall not be false.", + "if": { + "type": "object", + "required": ["@type"], + "properties": { + "@type": { + "anyOf": [ + { "const": "uav:eventType" }, + { "type": "array", "contains": { "const": "uav:eventType" } } + ] + } + } + }, + "then": { + "properties": { "uav:isEvent": { "not": { "const": false } } } + } + } + }, + "allOf": [ + { "$ref": "#/$defs/uavMembers" }, + { "$ref": "#/$defs/eventTypeAnnotation" } + ] +} diff --git a/wot-specs/WoT-Binding/tools/validate_local.py b/wot-specs/WoT-Binding/tools/validate_local.py new file mode 100644 index 0000000..66249d4 --- /dev/null +++ b/wot-specs/WoT-Binding/tools/validate_local.py @@ -0,0 +1,710 @@ +#!/usr/bin/env python3 +"""Deterministic, standard-library validator for the OPC UA WoT Binding draft. + +Run from anywhere: python wot-specs/WoT-Binding/tools/validate_local.py + +The validator uses only the Python standard library and performs a fixed, +order-independent set of checks so that a clean checkout always produces the +same result. It verifies, for the wot-specs/WoT-Binding/ folder: + + 1. Every JSON and JSON-LD artifact parses. + 2. The JSON-LD context declares the official uav namespace/prefix and contains + every documented uav term (preserved OPC 10101 terms, the model/platform + vocabulary, and the preservation-envelope member terms). + 3. Every example declares an @context that binds the uav namespace. + 4. The default native example uses uav:NodeModel profile 1.0 without an + envelope, and every exceptional uav:nodeSet envelope is internally consistent: encoding + is base64, the SHA-256 field matches the digest of the decoded bytes, and + the decoded bytes are a well-formed XML document rooted at UANodeSet. + 5. Every internal relative reference resolves to a file on disk, and the + required artifacts are all present. + 6. No forbidden vendor prefixes, namespaces, or legacy modelling-language + names appear in any committed file. + 7. Every `uav:unitProperty` is a canonical RFC 6901 JSON Pointer that resolves, + within the same document, to a non-empty string value. + 8. Every `uav:containedIn` names an actual composite (matched by `title`) that + declares `uav:isComposite`; when a local `uav:componentModel` link connects + the two examples, the composite's `uav:contains` must reciprocally list the + link's `uav:refName` (containment consistency, Section 7 of the spec). + 9. Every NodeId-valued term (`uav:id`, `uav:hasComponent`, `uav:componentOf`, + `uav:mapToNodeId`, `uav:mapToType`, `uav:refId`, and each + `?id=` / NodeId link `href`) in an example is a portable OPC 10000-6 + ExpandedNodeId and never the session-local `ns=` form (Section 5.1.1). + 10. Event annotations are consistent: an affordance annotated `@type uav:eventType` + never sets `uav:isEvent: false` (Section 5.2). + 11. The native projection example has the required root/model/node structure. + 12. Compact model names use non-numeric context prefixes bound to OPC UA + NamespaceUris, require definitive identifiers where specified, and agree + with known base-namespace ReferenceTypes. + 13. Readable uav:browseName and uav:browsePath values do not persist numeric + namespace indexes; QualifiedNames use OPC 10000-6 NamespaceUri form. + +Exit code is 0 and the last line is "OK" on success; non-zero with an ERRORS +list otherwise. +""" +from __future__ import annotations + +import base64 +import binascii +import hashlib +import json +import os +import re +import sys +import xml.etree.ElementTree as ET + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(HERE) + +CONTEXT = os.path.join(ROOT, "opc-ua-wot-binding.context.jsonld") +SCHEMA = os.path.join(ROOT, "opc-ua-wot-binding.schema.json") +SPEC = os.path.join(ROOT, "OPC-UA-WoT-Binding.md") +README = os.path.join(ROOT, "README.md") +EXAMPLES = os.path.join(ROOT, "examples") + +UAV_NS = "http://opcfoundation.org/UA/WoT-Binding/" +UANODESET_LOCALNAME = "UANodeSet" + +# Every uav term the specification documents. Each MUST be discoverable in the +# JSON-LD context as a "uav:" key or value. +DOCUMENTED_TERMS = [ + # Preserved OPC 10101 vocabulary. + "id", "browsePath", "browseName", + "object", "objectType", "variable", "variableType", "method", "eventType", + "hasComponent", "componentOf", + "mapToNodeId", "mapToType", "mapToTypeName", "mapByFieldPath", + # Preserved OPC 10101 security vocabulary. + "channelsec", "authentication", "securityMode", "securityPolicy", + "userIdentityToken", "issueToken", + # Model and platform vocabulary. + "isComposite", "isEvent", + "capability", "componentModel", "reference", + "refName", "refId", + "contains", "containedIn", + "congruentType", "congruentTypeName", "nameNamespace", + "scaleFactor", "decimalPlaces", + "propertyGroups", "eventGroups", "actionGroups", "memberOf", + "unitProperty", + "metadata", "semanticId", + "actionConfiguration", "propertyConfiguration", "eventConfiguration", + "includeInherited", + "additionalProperties", "externalSchema", "modellingRule", + # Preservation envelope. + "nodes", "NodeModel", + "nodeSet", "contentType", "encoding", "sha256", "data", "profileVersion", +] + +REQUIRED_EXAMPLES = [ + "01-opcua-td-pump.jsonld", + "02-thing-model-pump.jsonld", + "03-nodeset-preservation-envelope.jsonld", + "04-type-reference-modelling-rule.jsonld", + "05-native-node-model.jsonld", +] + +# Forbidden tokens are assembled from fragments so that this file never contains +# the literal itself; this keeps the file scannable by its own check. +FORBIDDEN = [ + "d" + "ov:", # vendor prefix + "d" + "tv:", # vendor prefix + "a" + "ov:", # vendor prefix + "d" + "tmi:", # vendor node-identifier namespace scheme + "d" + "tdl", # legacy source modelling-language name +] + +TEXT_EXTS = {".md", ".json", ".jsonld", ".py", ".txt"} +REL_RE = re.compile(r"^\.{1,2}/") +HEX64_RE = re.compile(r"^[0-9a-f]{64}$") + +# Portable identity (Section 5.1.1): NodeId-valued terms shall be OPC 10000-6 +# ExpandedNodeId strings (nsu=;=, or a namespace-0 +# canonical i=) and shall not use the session-local ns= form. +PORTABLE_ID_STRING_KEYS = ("uav:id", "uav:mapToNodeId", "uav:mapToType") +PORTABLE_ID_ARRAY_KEYS = ("uav:hasComponent", "uav:componentOf") +SESSION_NS_RE = re.compile(r"ns=[0-9]") +EXPANDED_NODEID_RE = re.compile(r"^(svr=[0-9]+;)?(nsu=[^;]+;)?[isgb]=.+$") +NODEID_HREF_PREFIX_RE = re.compile(r"^(svr=|nsu=|ns=|[isgb]=)") +MODEL_CONCEPT_RE = re.compile(r"^([A-Za-z_][A-Za-z0-9._-]*):(.+)$") +QUALIFIED_NAME_URI_RE = re.compile(r"^nsu=([^;]+);(.+)$") +NUMERIC_QUALIFIED_NAME_RE = re.compile(r"^[0-9]+:") +NUMERIC_PATH_ELEMENT_RE = re.compile(r"(^|/)[0-9]+:") +UA_NS = "http://opcfoundation.org/UA/" +KNOWN_LINK_RELS = { + "tm:extends", "uav:capability", "uav:componentModel", + "uav:reference", "uav:componentOf", "type", "collection", "item", + "alternate", "describedby", "self" +} + +ERR: list[str] = [] + + +def err(msg: str) -> None: + ERR.append(msg) + + +def rel(path: str) -> str: + return os.path.relpath(path, ROOT).replace(os.sep, "/") + + +def load_json(path: str): + with open(path, encoding="utf-8") as fh: + return json.load(fh) + + +def iter_strings(obj): + """Yield every dict key and string value in a parsed JSON document.""" + if isinstance(obj, dict): + for key, value in obj.items(): + yield key + yield from iter_strings(value) + elif isinstance(obj, list): + for item in obj: + yield from iter_strings(item) + elif isinstance(obj, str): + yield obj + + +def iter_dicts(obj): + if isinstance(obj, dict): + yield obj + for value in obj.values(): + yield from iter_dicts(value) + elif isinstance(obj, list): + for item in obj: + yield from iter_dicts(item) + + +def all_text_files(): + for dirpath, dirnames, filenames in os.walk(ROOT): + dirnames[:] = [d for d in dirnames if d != "__pycache__"] + for name in sorted(filenames): + if os.path.splitext(name)[1].lower() in TEXT_EXTS: + yield os.path.join(dirpath, name) + + +def json_files(): + for path in all_text_files(): + if os.path.splitext(path)[1].lower() in (".json", ".jsonld"): + yield path + + +def check_required_files(): + for path in [CONTEXT, SCHEMA, SPEC, README]: + if not os.path.isfile(path): + err(f"missing required artifact {rel(path)}") + if not os.path.isdir(EXAMPLES): + err("missing required examples/ directory") + return + for name in REQUIRED_EXAMPLES: + if not os.path.isfile(os.path.join(EXAMPLES, name)): + err(f"missing required example examples/{name}") + + +def check_json_parses(): + parsed = {} + for path in sorted(json_files()): + try: + parsed[path] = load_json(path) + except (json.JSONDecodeError, OSError) as exc: + err(f"{rel(path)}: JSON does not parse ({exc})") + return parsed + + +def context_term_strings(ctx_doc): + return set(iter_strings(ctx_doc)) + + +def check_context(ctx_doc): + strings = context_term_strings(ctx_doc) + if UAV_NS not in strings: + err(f"context does not declare the uav namespace {UAV_NS}") + if "uav" not in strings: + err("context does not declare the 'uav' prefix key") + for term in DOCUMENTED_TERMS: + if f"uav:{term}" not in strings: + err(f"context is missing documented term uav:{term}") + + +def check_examples(parsed): + example_paths = [ + p for p in sorted(parsed) + if os.path.dirname(p) == EXAMPLES and p.lower().endswith(".jsonld") + ] + if len(example_paths) < 5: + err(f"expected at least 5 .jsonld examples, found {len(example_paths)}") + for path in example_paths: + doc = parsed[path] + if not isinstance(doc, dict) or "@context" not in doc: + err(f"{rel(path)}: example does not declare a top-level @context") + continue + if UAV_NS not in set(iter_strings(doc.get("@context"))): + err(f"{rel(path)}: @context does not bind the uav namespace {UAV_NS}") + + +def find_envelopes(parsed): + envelopes = [] + for path, doc in parsed.items(): + for node in iter_dicts(doc): + if node.get("@type") == "uav:nodeSet": + envelopes.append((path, node)) + return envelopes + + +def check_envelope(path, env): + where = rel(path) + for field in ("contentType", "encoding", "sha256", "data"): + if field not in env: + err(f"{where}: preservation envelope missing '{field}'") + if env.get("encoding") not in (None, "base64"): + err(f"{where}: preservation envelope encoding must be 'base64'") + digest = env.get("sha256", "") + if not isinstance(digest, str) or not HEX64_RE.match(digest): + err(f"{where}: preservation envelope sha256 is not a 64-char lower-hex digest") + data = env.get("data") + if not isinstance(data, str): + err(f"{where}: preservation envelope data is not a string") + return + try: + raw = base64.b64decode(data, validate=True) + except (binascii.Error, ValueError): + err(f"{where}: preservation envelope data is not valid base64") + return + actual = hashlib.sha256(raw).hexdigest() + if isinstance(digest, str) and HEX64_RE.match(digest) and actual != digest: + err(f"{where}: preservation envelope sha256 mismatch " + f"(declared {digest}, computed {actual})") + try: + rootel = ET.fromstring(raw) + except ET.ParseError as exc: + err(f"{where}: preservation envelope data does not decode to well-formed XML ({exc})") + return + localname = rootel.tag.split("}")[-1] + if localname != UANODESET_LOCALNAME: + err(f"{where}: preservation envelope XML root is <{localname}>, expected <{UANODESET_LOCALNAME}>") + + +def check_envelopes(parsed): + envelopes = find_envelopes(parsed) + if not envelopes: + err("no uav:nodeSet preservation envelope found in any example") + for path, env in envelopes: + check_envelope(path, env) + + +def check_native_projection(parsed): + path = os.path.join(EXAMPLES, "05-native-node-model.jsonld") + doc = parsed.get(path) + if not isinstance(doc, dict): + return + if "uav:nodeSet" in doc: + err(f"{rel(path)}: native completeness example shall not contain uav:nodeSet") + projection = doc.get("uav:nodes") + if not isinstance(projection, dict): + err(f"{rel(path)}: missing uav:nodes object") + return + if projection.get("@type") != "uav:NodeModel": + err(f"{rel(path)}: uav:nodes @type shall be uav:NodeModel") + if projection.get("profileVersion") != "1.0": + err(f"{rel(path)}: uav:nodes profileVersion shall be 1.0") + nodes = projection.get("nodes") + if not isinstance(nodes, list) or not nodes: + err(f"{rel(path)}: uav:nodes nodes shall be a non-empty array") + return + node_classes = set() + for index, node in enumerate(nodes): + if not isinstance(node, dict): + err(f"{rel(path)}: uav:nodes nodes[{index}] is not an object") + continue + for field in ("nodeClass", "nodeId", "browseName"): + if not isinstance(node.get(field), str) or not node[field]: + err(f"{rel(path)}: uav:nodes nodes[{index}] missing '{field}'") + if isinstance(node.get("nodeClass"), str): + node_classes.add(node["nodeClass"]) + if "ObjectType" not in node_classes or "Variable" not in node_classes: + err(f"{rel(path)}: native example shall include ObjectType and Variable records") + + +def check_relative_refs(parsed): + for path, doc in parsed.items(): + base = os.path.dirname(path) + for value in iter_strings(doc): + if REL_RE.match(value): + target = os.path.normpath(os.path.join(base, value)) + if not os.path.exists(target): + err(f"{rel(path)}: internal relative reference '{value}' does not resolve") + + +def resolve_json_pointer(doc, pointer): + """Resolve an RFC 6901 JSON Pointer against a parsed document. Returns (value, found).""" + if pointer == "": + return doc, True + if not pointer.startswith("/"): + return None, False + node = doc + for raw_part in pointer.split("/")[1:]: + part = raw_part.replace("~1", "/").replace("~0", "~") + if isinstance(node, dict): + if part not in node: + return None, False + node = node[part] + elif isinstance(node, list): + if not part.isdigit() or int(part) >= len(node): + return None, False + node = node[int(part)] + else: + return None, False + return node, True + + +def check_unit_properties(parsed): + """uav:unitProperty shall be a canonical JSON Pointer resolving, in-document, to a + non-empty string (Section 6.5 / 7 'Unit pointer' rule). Checked on examples only: the + context and schema files declare the term itself, not a usage of it.""" + for path, doc in parsed.items(): + if os.path.dirname(path) != EXAMPLES or not isinstance(doc, dict): + continue + for node in iter_dicts(doc): + if not isinstance(node, dict) or "uav:unitProperty" not in node: + continue + pointer = node["uav:unitProperty"] + if not isinstance(pointer, str) or not pointer.startswith("/"): + err(f"{rel(path)}: uav:unitProperty '{pointer}' is not a canonical " + f"RFC 6901 JSON Pointer") + continue + value, found = resolve_json_pointer(doc, pointer) + if not found: + err(f"{rel(path)}: uav:unitProperty '{pointer}' does not resolve within the document") + elif not isinstance(value, str) or not value: + err(f"{rel(path)}: uav:unitProperty '{pointer}' resolves to a non-string or " + f"empty value") + + +def check_containment(parsed): + """uav:containedIn shall name an actual composite (Section 7 'Containment consistency'): + the value must match the title of a document declaring uav:isComposite, and where a + local uav:componentModel link connects the two examples, the composite's uav:contains + must reciprocally list that link's uav:refName. Checked on examples only: the context + and schema files declare the term itself, not a usage of it.""" + titles = {} + for path, doc in parsed.items(): + if os.path.dirname(path) == EXAMPLES and isinstance(doc, dict) and isinstance(doc.get("title"), str): + titles.setdefault(doc["title"], []).append((path, doc)) + + for path, doc in parsed.items(): + if os.path.dirname(path) != EXAMPLES or not isinstance(doc, dict): + continue + for node in iter_dicts(doc): + if not isinstance(node, dict) or "uav:containedIn" not in node: + continue + contained_in = node["uav:containedIn"] + if not isinstance(contained_in, str) or not contained_in: + err(f"{rel(path)}: uav:containedIn is not a non-empty string") + continue + candidates = [(cpath, cdoc) for cpath, cdoc in titles.get(contained_in, []) + if cdoc.get("uav:isComposite")] + if not candidates: + err(f"{rel(path)}: uav:containedIn '{contained_in}' does not match the " + f"title of any known composite (uav:isComposite: true)") + continue + reciprocal_ok = False + for cpath, cdoc in candidates: + contains = cdoc.get("uav:contains") or [] + base = os.path.dirname(cpath) + for link in cdoc.get("links", []) or []: + if not isinstance(link, dict) or link.get("rel") != "uav:componentModel": + continue + href = link.get("href") + if not isinstance(href, str) or not REL_RE.match(href): + continue + target = os.path.normpath(os.path.join(base, href)) + if os.path.normpath(target) != os.path.normpath(path): + continue + if link.get("uav:refName") in contains: + reciprocal_ok = True + if not reciprocal_ok: + err(f"{rel(path)}: uav:containedIn '{contained_in}' has no reciprocal " + f"uav:componentModel link + matching uav:contains entry in a composite " + f"named '{contained_in}'") + + +def _expanded_nodeid(path, where, value): + """Assert that value is a portable OPC 10000-6 ExpandedNodeId (Section 5.1.1): + no session-local ns= form, and a recognizable idtype (i/s/g/b).""" + if not isinstance(value, str) or not value: + err(f"{rel(path)}: {where} is not a non-empty string") + return + if SESSION_NS_RE.search(value): + err(f"{rel(path)}: {where} uses the session-local ns= form ('{value}'); " + f"a persisted document shall use an ExpandedNodeId (nsu=;... " + f"or namespace-0 i=...)") + return + if not EXPANDED_NODEID_RE.match(value): + err(f"{rel(path)}: {where} '{value}' is not a valid ExpandedNodeId string") + + +def check_portable_identity(parsed): + """Portable identity (Section 5.1.1 / Section 7 'Portable identity'): every + NodeId-valued term shall be an ExpandedNodeId and shall not use ns=. + Checked on examples only: the context and schema files declare the terms, not + usages of them. The canonical NodeSet2 XML inside a uav:nodeSet envelope is + carried base64-encoded under the 'data' key. NodeSet-local nodeId/reference + fields inside uav:nodes are paired with that projection's namespaceUris table + and are not readable identity terms, so neither representation is affected by + this rule.""" + for path, doc in parsed.items(): + if os.path.dirname(path) != EXAMPLES or not isinstance(doc, dict): + continue + for node in iter_dicts(doc): + if not isinstance(node, dict): + continue + for key in PORTABLE_ID_STRING_KEYS: + if key in node: + _expanded_nodeid(path, key, node[key]) + for key in PORTABLE_ID_ARRAY_KEYS: + if key in node: + values = node[key] + if not isinstance(values, list): + err(f"{rel(path)}: {key} is not an array") + continue + for value in values: + _expanded_nodeid(path, f"{key} entry", value) + ref_id = node.get("uav:refId") + if ref_id is not None: + _expanded_nodeid(path, "uav:refId", ref_id) + href = node.get("href") + if isinstance(href, str): + if "?id=" in href: + _expanded_nodeid(path, "form href ?id=", href.split("?id=", 1)[1]) + elif NODEID_HREF_PREFIX_RE.match(href): + _expanded_nodeid(path, "link href NodeId", href) + + +def _context_prefixes(doc): + prefixes = {"uav": UAV_NS, "ua": UA_NS} + context = doc.get("@context") if isinstance(doc, dict) else None + entries = context if isinstance(context, list) else [context] + for entry in entries: + if not isinstance(entry, dict): + continue + for prefix, value in entry.items(): + if isinstance(value, str) and MODEL_CONCEPT_RE.match(f"{prefix}:x"): + prefixes[prefix] = value + return prefixes + + +def _model_concept(path, where, value, prefixes): + if not isinstance(value, str) or not value: + err(f"{rel(path)}: {where} is not a non-empty compact model name") + return + match = MODEL_CONCEPT_RE.match(value) + if not match: + err(f"{rel(path)}: {where} '{value}' is not ':'") + return + prefix = match.group(1) + if prefix not in prefixes: + err(f"{rel(path)}: {where} prefix '{prefix}' is not bound in @context") + elif prefix == "ua" and prefixes[prefix] != UA_NS: + err(f"{rel(path)}: reserved prefix 'ua' shall bind {UA_NS}") + + +def check_model_concept_names(parsed): + """Section 5.1.2 compact model names are semantic lookup hints, not Node + identity. Prefixes shall resolve to NamespaceUris; data-plane/congruence + hints require their definitive identifiers; known base concepts shall agree.""" + for path, doc in parsed.items(): + if os.path.dirname(path) != EXAMPLES or not isinstance(doc, dict): + continue + prefixes = _context_prefixes(doc) + name_namespace = doc.get("uav:nameNamespace") + browse_name = doc.get("uav:browseName") + if isinstance(name_namespace, str) and isinstance(browse_name, str): + model_match = MODEL_CONCEPT_RE.match(browse_name) + uri_match = QUALIFIED_NAME_URI_RE.match(browse_name) + browse_namespace = ( + prefixes.get(model_match.group(1)) if model_match + else uri_match.group(1) if uri_match + else UA_NS + ) + if browse_namespace != name_namespace: + err(f"{rel(path)}: uav:nameNamespace '{name_namespace}' does not " + f"match the uav:browseName namespace '{browse_namespace}'") + for node in iter_dicts(doc): + if not isinstance(node, dict): + continue + for key in ("uav:mapToTypeName", "uav:congruentTypeName"): + if key in node: + _model_concept(path, key, node[key], prefixes) + relation = node.get("rel") + if isinstance(relation, str) and relation not in KNOWN_LINK_RELS: + if relation.startswith(("http:", "https:", "urn:")): + pass + elif relation.startswith("uav:"): + err(f"{rel(path)}: Binding relation '{relation}' is not defined") + elif ("uav:refId" in node or "uav:refName" in node or + relation.startswith("ua:") or + re.match(r"^ns[0-9]+:", relation)): + _model_concept(path, "typed Reference rel", relation, prefixes) + if "uav:mapToTypeName" in node and "uav:mapToType" not in node: + err(f"{rel(path)}: uav:mapToTypeName requires uav:mapToType") + if "uav:congruentTypeName" in node and "uav:congruentType" not in node: + err(f"{rel(path)}: uav:congruentTypeName requires uav:congruentType") + if node.get("rel") == "ua:HasOrderedComponent": + fallback = node.get("uav:refId") + if fallback is not None and fallback != "i=49": + err(f"{rel(path)}: ua:HasOrderedComponent conflicts with " + f"uav:refId '{fallback}', expected i=49") + + +def check_portable_qualified_names(parsed): + """Readable QualifiedNames use OPC 10000-6 §5.1.12 URI form; internal + uav:nodes records retain their own NamespaceUris/indexes.""" + def walk(path, node, prefixes): + if isinstance(node, dict): + browse_name = node.get("uav:browseName") + if browse_name is not None: + if not isinstance(browse_name, str) or not browse_name: + err(f"{rel(path)}: uav:browseName is not a non-empty string") + elif NUMERIC_QUALIFIED_NAME_RE.match(browse_name): + err(f"{rel(path)}: uav:browseName '{browse_name}' persists a " + "numeric NamespaceIndex; use nsu=;") + elif browse_name.startswith("nsu=") and not QUALIFIED_NAME_URI_RE.match( + browse_name): + err(f"{rel(path)}: uav:browseName '{browse_name}' is not a valid " + "NamespaceUri-qualified QualifiedName") + elif (match := MODEL_CONCEPT_RE.match(browse_name)) and ( + match.group(1) not in prefixes): + err(f"{rel(path)}: uav:browseName prefix '{match.group(1)}' " + "is not bound in @context") + browse_path = node.get("uav:browsePath") + if isinstance(browse_path, str) and NUMERIC_PATH_ELEMENT_RE.search(browse_path): + err(f"{rel(path)}: uav:browsePath '{browse_path}' persists a numeric " + "NamespaceIndex; use context-prefix or NamespaceUri-qualified elements") + elif isinstance(browse_path, str): + for element in browse_path.split("/"): + if not element or element.startswith("{"): + continue + match = MODEL_CONCEPT_RE.match(element) + if match and match.group(1) not in prefixes: + err(f"{rel(path)}: uav:browsePath element '{element}' uses " + f"unbound prefix '{match.group(1)}'") + for key, value in node.items(): + if key != "uav:nodes": + walk(path, value, prefixes) + elif isinstance(node, list): + for item in node: + walk(path, item, prefixes) + + for path, doc in parsed.items(): + if os.path.dirname(path) == EXAMPLES: + walk(path, doc, _context_prefixes(doc)) + + +def check_event_annotations(parsed): + """Event annotation consistency (Section 5.2 / Section 7 'Event annotation + consistency'): uav:eventType is the @type counterpart of the uav:isEvent flag, + so an affordance annotated @type uav:eventType shall not set uav:isEvent: false. + Checked on examples only.""" + for path, doc in parsed.items(): + if os.path.dirname(path) != EXAMPLES or not isinstance(doc, dict): + continue + for node in iter_dicts(doc): + if not isinstance(node, dict): + continue + types = node.get("@type") + type_list = (types if isinstance(types, list) + else [types] if isinstance(types, str) else []) + if "uav:eventType" in type_list and node.get("uav:isEvent") is False: + err(f"{rel(path)}: an affordance annotated @type uav:eventType shall not " + f"set uav:isEvent: false (Section 5.2)") + + +MODELLING_RULE_IDS = { + "Mandatory": "i=78", + "Optional": "i=80", + "MandatoryPlaceholder": "i=11510", + "OptionalPlaceholder": "i=11508", +} + +MODELLING_RULE_TABLE_RE = re.compile( + r"Modelling rule \(`Mandatory` `(i=\d+)`, `Optional` `(i=\d+)`, " + r"`MandatoryPlaceholder` `(i=\d+)`, `OptionalPlaceholder` `(i=\d+)`\)" +) + + +def check_modelling_rule_ids(): + """The normative modelling-rule NodeIds documented in the spec (Section 9 + NodeSet2 and WoT conversion table) shall match the OPC 10000-3 standard + values: Mandatory i=78, Optional i=80, MandatoryPlaceholder i=11510, + OptionalPlaceholder i=11508. This guards against the two placeholder + NodeIds being swapped or mistyped.""" + try: + text = open(SPEC, encoding="utf-8").read() + except OSError as exc: + err(f"{rel(SPEC)}: cannot read ({exc})") + return + match = MODELLING_RULE_TABLE_RE.search(text) + if not match: + err(f"{rel(SPEC)}: modelling rule NodeId table not found or not in expected format") + return + found = { + "Mandatory": match.group(1), + "Optional": match.group(2), + "MandatoryPlaceholder": match.group(3), + "OptionalPlaceholder": match.group(4), + } + for rule, expected in MODELLING_RULE_IDS.items(): + if found[rule] != expected: + err(f"{rel(SPEC)}: modelling rule '{rule}' documented as '{found[rule]}', " + f"expected '{expected}'") + + +def check_forbidden_tokens(): + for path in all_text_files(): + try: + text = open(path, encoding="utf-8").read().lower() + except OSError as exc: + err(f"{rel(path)}: cannot read ({exc})") + continue + for token in FORBIDDEN: + if token in text: + err(f"{rel(path)}: contains forbidden token '{token}'") + + +def main() -> int: + check_required_files() + parsed = check_json_parses() + + ctx_doc = parsed.get(CONTEXT) + if ctx_doc is None and os.path.isfile(CONTEXT): + # parse failure already reported + pass + elif ctx_doc is not None: + check_context(ctx_doc) + + check_examples(parsed) + check_native_projection(parsed) + check_envelopes(parsed) + check_relative_refs(parsed) + check_unit_properties(parsed) + check_containment(parsed) + check_portable_identity(parsed) + check_model_concept_names(parsed) + check_portable_qualified_names(parsed) + check_event_annotations(parsed) + check_modelling_rule_ids() + check_forbidden_tokens() + + print(f"context: {rel(CONTEXT)}") + print(f"json artifacts checked: {len(parsed)}") + print(f"documented uav terms: {len(DOCUMENTED_TERMS)}") + if ERR: + print(f"ERRORS: {len(ERR)}") + for message in ERR: + print(f" - {message}") + return 1 + print("OK") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/wot-specs/WoT-Connectivity/OPC-UA-WoT-Connectivity.md b/wot-specs/WoT-Connectivity/OPC-UA-WoT-Connectivity.md new file mode 100644 index 0000000..2ad0ff1 --- /dev/null +++ b/wot-specs/WoT-Connectivity/OPC-UA-WoT-Connectivity.md @@ -0,0 +1,1104 @@ +# OPC UA — WoT Connectivity + +**Release 1.1.0 — Draft (additive revision of OPC 10100-1 v1.02)** +**Namespace:** `http://opcfoundation.org/UA/WoT-Con/` +**Publication date:** 2026-07-22 + +> Status: Working-group draft, intended for submission to the **OPC Foundation Web of Things (WoT) Working Group**. This document, together with the generated `Opc.Ua.WoTCon.NodeSet2.xml` and `Opc.Ua.WoTCon.NodeIds.csv`, defines an **additive revision 1.1** of the OPC UA companion specification for W3C Web of Things (WoT) connectivity. It is **registry-first**: it layers a W3C Thing Model / Thing Description **document registry** over the abstract [OPC UA — xRegistry](../../core-specs/xregistry/OPC-UA-xRegistry.md) base model and treats the stored documents and their versions as the single source of truth from which the OPC UA AddressSpace and any code-behind are **derived**. Revision 1.1 keeps the published `http://opcfoundation.org/UA/WoT-Con/` namespace and **incorporates the full OPC 10100-1 v1.02 model into one combined NodeSet**, preserving every published NodeId, type and method signature and marking the superseded 1.02 management surface `Deprecated`. It can be read on its own. Nothing here is official or endorsed by the OPC Foundation or the W3C; the numeric NodeIds of the additive `64000+` registry block are provisional and final identifiers are assigned by the OPC Foundation. + +--- + +## 1 Scope + +This specification defines **WoT Connectivity 1.1**: an OPC UA information model and normative behaviour for a server that stores, validates, versions and **projects** W3C Web of Things documents. It is an **additive revision** of OPC 10100-1 v1.02 published in the same namespace `http://opcfoundation.org/UA/WoT-Con/`. + +- A **Thing Model** (WoT-TM/1.1) describes a reusable class of Things; this specification projects it to OPC UA **types**. +- A **Thing Description** (WoT-TD/1.1) describes a concrete Thing instance; this specification projects it to OPC UA **instances** whose interaction affordances are bound to protocol bindings. + +The registry files and versions are canonical. The AddressSpace that a client browses — types from Thing Models, instances from Thing Descriptions, References from links, and monitored values driven by binder plans built from forms — is a **derived projection** that a server refreshes, atomically and idempotently, from the stored documents. + +This specification **incorporates** the published OPC 10100-1 v1.02 WoT Connectivity model (namespace `http://opcfoundation.org/UA/WoT-Con/`, model version 1.02.0) into the **same combined NodeSet and namespace**. Every published NodeId (`1..172`), type, method signature and the well-known `WoTAssetConnectionManagement` object is preserved exactly (§13). Because the additive registry supersedes the flat asset-management surface, the 1.02 management and upload types are marked **deprecated** (`ReleaseStatus="Deprecated"`, following OPC 11030) — deprecated, not removed, so existing 1.02 clients keep working unchanged. This is a single combined model in one namespace, not a separate namespace and not a dual profile. + +Out of scope: the WoT vocabulary itself (defined by the revised [OPC UA — WoT Binding](../WoT-Binding/OPC-UA-WoT-Binding.md) JSON-LD vocabulary, a normative dependency of this specification), the xRegistry document/API semantics (defined by [OPC UA — xRegistry](../../core-specs/xregistry/OPC-UA-xRegistry.md)), and the concrete wire protocols of individual W3C binding templates. + +## 2 Normative and informative references + +- [OPC 10000-3](https://reference.opcfoundation.org/specs/OPC-10000-3/) — Address Space Model (model change, NodeVersion, DataTypes, References). +- [OPC 10000-5](https://reference.opcfoundation.org/specs/OPC-10000-5/) — Information Model (BaseObjectType, FolderType, PropertyType, BaseEventType, GeneralModelChangeEventType, structures). +- [OPC 10000-20](https://reference.opcfoundation.org/specs/OPC-10000-20/) — File Transfer (FileType). +- [OPC 11030](https://reference.opcfoundation.org/) — Compatibility and versioning rules for OPC UA information models (applied in §7.10 and §13). +- [OPC 10100-1 v1.02](https://reference.opcfoundation.org/specs/OPC-10100-1/) — the published WoT Connectivity baseline incorporated and preserved in §13. +- [OPC UA — xRegistry](../../core-specs/xregistry/OPC-UA-xRegistry.md) — the abstract registry base model this specification extends (RequiredModel). +- [OPC UA — WoT Binding](../WoT-Binding/OPC-UA-WoT-Binding.md) — the `uav` JSON-LD vocabulary and NodeSet↔WoT mapping (normative vocabulary dependency; not a NodeSet RequiredModel). +- [W3C WoT Thing Description 1.1](https://www.w3.org/TR/wot-thing-description11/) and [WoT Binding Templates](https://www.w3.org/TR/wot-binding-templates/). +- [WoT Registry (xRegistry WoT extension) 1.0-rc](https://github.com/varunpuranik/xregistry_spec/blob/WoT1/wot/spec.md) — the WoT document-registry model reconciled here (`thingdescriptiongroups`/`thingdescriptions`, `thingmodelgroups`/`thingmodels`, formats `WoT-TD/1.1` / `WoT-TM/1.1`). +- [xRegistry 1.0-rc3](https://github.com/xregistry/spec) — the core registry document/API specification. + +## 3 Terms, definitions, and conventions + +### 3.1 Normative keywords + +The key words **shall**, **shall not**, **should**, **should not**, **may** and **optional** are used as defined in the OPC UA specifications. **shall** and **shall not** are absolute requirements; **should**/**should not** are strong recommendations; **may**/**optional** denote freedom. + +### 3.2 Abbreviations + +- **TD** — WoT Thing Description; **TM** — WoT Thing Model. +- **Affordance** — a WoT interaction affordance: a property, action or event. +- **Form** — a WoT `forms` entry binding an affordance to a protocol endpoint. +- **Projection** — the derived AddressSpace (and code-behind) materialized from stored documents. +- **Generation** — a monotonically increasing counter identifying one committed projection state of the registry. +- **Closure** — a document together with the transitive set of documents it depends on (the dependency DAG reachable from it). +- **Binder** — the component that turns a form into an executable read/write/observe/invoke/subscribe plan for a specific protocol binding. + +### 3.3 Terms + +- **Canonical document** — a stored TD or TM version; the authoritative source of truth. Everything a client browses is derived from it. +- **Desired version** vs **active version** — the version an operator wants projected vs the version whose projection is currently serving. +- **Shadow generation** — a fully materialized but not-yet-visible projection built beside the active one and switched in atomically. +- **Incorporated 1.02 model** — the published OPC 10100-1 v1.02 nodes, preserved unchanged in this combined NodeSet at their published NodeIds and marked `Deprecated` (§13). + +## 4 Overview and architecture + +This version of the WoT Connectivity specification is organised around a single principle: **the registry is the source of truth; the AddressSpace is a cache of it.** A server ingests TDs and TMs as registry documents, validates and versions them with xRegistry semantics, resolves their dependency graph, and **projects** the valid closure into OPC UA nodes. Clients interact with those projected nodes exactly as they would with any OPC UA model; the registry additionally exposes the documents, their lifecycle state, and a `Refresh` control surface. + +```mermaid +graph TD + subgraph canonical["Sources of truth (canonical)"] + R[WoTRegistry i=64100] + TDG[ThingDescriptionGroup] + TMG[ThingModelGroup] + TDF[ThingDescriptionFile - TD bytes] + TMF[ThingModelFile - TM bytes] + R --> TDG --> TDF + R --> TMG --> TMF + end + subgraph derived["Derived projection (code-behind)"] + OT[ObjectType / VariableType] + OBJ[Object instance] + VARS[Variables / Methods / EventTypes] + BIND[Binder plans from forms] + end + TMF -- TM to types --> OT + TDF -- TD to instances --> OBJ + OBJ --> VARS + VARS -- forms --> BIND + TDF -. links rel=type .-> TMF + R -- HasWoTProjection --> OBJ + R -- Refresh / generation --> VARS +``` + +**Layering.** This specification reuses the abstract xRegistry base (`RegistryType`/`GroupType`/`ResourceType`, namespace index 1) and adds, in the same own namespace (index 2) that carries the incorporated 1.02 model, the WoT-specific subtypes, DataTypes, events and the well-known `WoTRegistry` object. The WoT vocabulary that governs how a document maps to nodes is the `uav` JSON-LD vocabulary of the revised [OPC UA — WoT Binding](../WoT-Binding/OPC-UA-WoT-Binding.md); it is a **normative dependency but not a NodeSet RequiredModel**, because it is a JSON-LD vocabulary, not an OPC UA information model. + +**Relationship to OPC 10100-1 v1.02.** The published v1.02 asset-management surface (`WoTAssetConnectionManagement`, the asset types and the `WoTFile`/`CloseAndUpdate` upload flow) is incorporated into the same NodeSet and namespace and remains callable, but is marked `Deprecated`: it is a special, flat case of the general registry, and §13 records how it is backed by the registry without any signature change. New deployments use the registry surface directly. + +**Separation of concerns.** Routing/lifecycle metadata (load state, generation, desired/active version, validation outcomes, content digest, selected bindings) lives on the registry and document nodes; the *semantic* mapping of affordances to nodes is carried by the stored document and the `uav` vocabulary. This mirrors the xRegistry separation of registry metadata from resource content. + +## 5 Namespace, model dependencies, and NodeId allocation + +- **NamespaceUri:** `http://opcfoundation.org/UA/WoT-Con/` — the **same** namespace as the published OPC 10100-1 v1.02 baseline. Revision 1.1 is additive within this one namespace; there is no separate `V2/` namespace. +- **Namespace order in the NodeSet:** index 0 Core (`http://opcfoundation.org/UA/`), index 1 xRegistry (`http://opcfoundation.org/UA/xRegistry/`), index 2 this specification (which carries both the incorporated 1.02 nodes and the additive registry nodes). +- **RequiredModels:** Core (`1.05.04`) and xRegistry (`0.1.0`). The WoT Binding vocabulary is **not** a RequiredModel. +- **NodeId allocation:** the incorporated OPC 10100-1 v1.02 nodes keep their exact published numeric identifiers `1..172` (preserved from the pinned `legacy/WotConnection.csv`; reserved ids stay reserved). The additive registry nodes use a dedicated **64000+** block for types (ObjectTypes, event types, DataTypes and the reference type), with member declarations (properties, methods, arguments, enum strings, encodings and the well-known instance) allocated **append-only** from **64500**. The 64000 block was chosen to avoid the preserved 1.02 range (`1..172`) and the ranges already used by sibling drafts in this repository (Generators `1001-6xxx`, Schema Registry `62000`, xRegistry `63000`) and does not overlap any published OPC Foundation range. Because member allocation is append-only in source order, new declarations shall only be added at the end of their block; reordering or inserting declarations renumbers many nodes and is prohibited without an explicit NodeId-impact review. + +The generated artifacts are the normative machine-readable form: `Opc.Ua.WoTCon.NodeSet2.xml`, `Opc.Ua.WoTCon.NodeIds.csv` and the Annex A reference (`tools/model-reference.md`), all emitted deterministically by `tools/build_model.py` from the in-code registry model and the pinned legacy sources under `legacy/`. They shall not be hand-edited. + +## 6 Information model + +This section defines each information-model concept the additive registry introduces: **what** it is, **where** it appears in the AddressSpace, **why** it exists, and **how** it is used. Every type links to its normative node reference in [Annex A](#annex-a). Annex A also documents the incorporated 1.02 legacy types (§13). All registry NodeIds are provisional. + +### 6.1 Registry root — `WoTRegistryType` + +[`WoTRegistryType`](#type-WoTRegistryType) (`i=64000`) is the registry root type, a subtype of the abstract xRegistry [`RegistryType`](../../core-specs/xregistry/OPC-UA-xRegistry.md#type-RegistryType) (itself a `FolderType`). + +It is instantiated once as the well-known `WoTRegistry` object under the `Server` object (§6.7). + +It is the single entry point from which a client discovers every stored Thing Model and Thing Description and controls their projection. + +It holds the two group placeholders `` / `` and adds registry-wide lifecycle state (`RefreshGeneration`, `AutoRefresh`, `RefreshMode`, `RefreshInterval`, `LastRefreshTime`, `LastRefreshSummary`, `DefaultAtomicity`, `DeletePolicy`), validation policy (`ValidateFormat`, `ValidateCompatibility`, `StrictValidation`, `VocabularyVersion`), the binding surface (`SupportedBindings`, `SelectedBindings`) and the `Refresh` Method (§9). A client browses `Server → WoTRegistry`, reads `RefreshGeneration` to learn the current projection generation, and calls `Refresh` to re-project on demand. + +### 6.2 Groups — `ThingDescriptionGroupType` and `ThingModelGroupType` + +[`ThingDescriptionGroupType`](#type-ThingDescriptionGroupType) (`i=64001`) and [`ThingModelGroupType`](#type-ThingModelGroupType) (`i=64002`) are xRegistry [`GroupType`](../../core-specs/xregistry/OPC-UA-xRegistry.md#type-GroupType) subtypes. + +Instances appear directly under the registry, one group per related set of documents (for example one group per site or per vendor catalog). + +Groups are the unit of organisation, access control and policy: read access to a group is the authorisation boundary for discovering the Things it contains (§7.11). + +Each group carries the group-level validation policy (`ValidateFormat`, `ValidateCompatibility`, `ConsistentFormat`) and a constrained `` / `` placeholder that limits its members to the matching document subtype. A Thing Description group holds only Thing Descriptions; a Thing Model group holds only Thing Models. + +### 6.3 Documents — `WoTDocumentType`, `ThingDescriptionFileType`, `ThingModelFileType` + +[`WoTDocumentType`](#type-WoTDocumentType) (`i=64003`, **abstract**) is the base of a stored WoT document, a subtype of the xRegistry [`ResourceType`](../../core-specs/xregistry/OPC-UA-xRegistry.md#type-ResourceType). [`ThingDescriptionFileType`](#type-ThingDescriptionFileType) (`i=64004`) and [`ThingModelFileType`](#type-ThingModelFileType) (`i=64005`) are the concrete subtypes. + +Document instances are the members of the groups. + +The document *is* the canonical source of truth; everything a client browses in the projection is derived from these bytes and their versions. + +Because `ResourceType` is a `FileType`, the JSON-LD document bytes are read and written with the inherited `Open`/`Read`/`Write`/`Close` Methods, so a client fetches the exact stored TD/TM. `WoTDocumentType` adds the derived-projection metadata (`DocumentKind`, `Enabled`, `LoadState`, `DesiredVersionId`, `ActiveVersionId`, `IsDefault`, `Ancestor`, `Compatibility`, `AutoRefresh`, `RefreshGeneration`, `LastRefreshTime`, `ContentDigest`, `ValidationOutcome`, `MaterializedNodeCount`, `RootNodeId`, `SelectedBindings`) and the `Validate` / `SetEnabled` / `SetDefaultVersion` Methods. `ThingDescriptionFileType` adds the TD instance identity (`ThingId`, `ThingTitle`, `BaseUri`, `ModelReference`); `ThingModelFileType` adds the TM type identity (`ModelTitle`, `ModelVersion`, `DerivedTypeNodeId`). For example, after a Thing Model is projected, a client reads `ThingModelFileType.DerivedTypeNodeId` to find the ObjectType it produced. + +### 6.4 Bindings — `WoTBindingType` + +[`WoTBindingType`](#type-WoTBindingType) (`i=64006`) is a browseable protocol-binding descriptor. + +The registry's `SupportedBindings` folder holds one `WoTBindingType` object per protocol binding the server can realise (OPC UA, HTTP, Modbus, …). + +It lets a client discover which W3C binding templates the server supports, at which pinned document version and maturity, before relying on a form. + +Its `BindingUri`, `Title`, `ProfileVersion`, `DraftMaturity`, `Enabled`, `ContentTypes` and a `Capabilities` snapshot are individual, browseable nodes. Immutable *snapshots* of the selected binding set are additionally exposed as arrays of [`WoTBindingCapabilityDataType`](#type-WoTBindingCapabilityDataType) (`SelectedBindings`, on the registry and on each document). Browseable policy/identity is always exposed as Objects/Properties; arrays are used **only** for immutable snapshots. No credentials or secrets are ever exposed on a binding node. + +### 6.5 DataTypes + +The model defines eight enumerations and seven structures. + +They type the Properties, method arguments and event fields above. + +The enumerations give lifecycle and outcome values a stable, machine-readable meaning; the structures package related, versioned facts as a single immutable snapshot. + +The enumerations are [`WoTDocumentKindEnum`](#type-WoTDocumentKindEnum), [`WoTLoadStateEnum`](#type-WoTLoadStateEnum), [`WoTRefreshModeEnum`](#type-WoTRefreshModeEnum), [`WoTAtomicityEnum`](#type-WoTAtomicityEnum), [`WoTDeletePolicyEnum`](#type-WoTDeletePolicyEnum), [`WoTOutcomeEnum`](#type-WoTOutcomeEnum), [`WoTPhaseEnum`](#type-WoTPhaseEnum) and [`WoTBindingCapabilityEnum`](#type-WoTBindingCapabilityEnum). The structures — [`WoTValidationOutcomeDataType`](#type-WoTValidationOutcomeDataType), [`WoTBindingCapabilityDataType`](#type-WoTBindingCapabilityDataType), [`WoTRefreshOptionsDataType`](#type-WoTRefreshOptionsDataType), [`WoTResourceSelectorDataType`](#type-WoTResourceSelectorDataType), [`WoTResourceLoadResultDataType`](#type-WoTResourceLoadResultDataType), [`WoTRefreshSummaryDataType`](#type-WoTRefreshSummaryDataType) and [`WoTDependencyDataType`](#type-WoTDependencyDataType) — are each an **immutable versioned snapshot**, read as a single Variant and never mutated in place; each carries `Default Binary` and `Default JSON` encodings. `examples/04-refresh-results.json` (§12) shows the two refresh structures populated. + +### 6.6 Events and the notifier chain + +[`WoTResourceEventType`](#type-WoTResourceEventType) (`i=64010`, **abstract**, subtype of `BaseEventType`) is the common WoT resource event, carrying the affected identity (`Xid`, `ResourceId`, `VersionId`), `DocumentKind`, `Generation`, `Phase` and `Outcome`. Its concrete failure subtypes are [`WoTValidationFailureEventType`](#type-WoTValidationFailureEventType) (`i=64011`), [`WoTLoadFailureEventType`](#type-WoTLoadFailureEventType) (`i=64012`) and [`WoTBindingFailureEventType`](#type-WoTBindingFailureEventType) (`i=64013`); [`WoTRefreshCompletedEventType`](#type-WoTRefreshCompletedEventType) (`i=64014`) carries the `Summary` and committed `Generation`. + +The failing **resource** is the source of a failure event; the **registry** is the source of the refresh-completed event. + +Events let an operator observe validation, projection and binding problems and refresh completion without polling. + +The notifier chain is **Server → WoTRegistry → groups → resources**: the well-known `WoTRegistry` object declares `EventNotifier = SubscribeToEvents` and is a `HasNotifier` target of the `Server` object (`i=2253`); groups are `HasNotifier` targets of the registry and resources of their group. A subscriber on `WoTRegistry` therefore receives every failure event raised by any contained resource and the refresh-completed event raised by the registry itself. + +### 6.7 Reference type and projection correlation — `HasWoTProjection` + +[`HasWoTProjection`](#type-HasWoTProjection) (`i=64060`, subtype of `NonHierarchicalReferences`, inverse `WoTProjectionOf`) links a stored document resource (source) to the root node of its derived projection (target). + +It lets a client navigate in both directions — find the projected node behind a document and the document behind a projected node — and it anchors `NodeVersion` correlation (§7.9). + +After a Thing Description is projected to an Object, the resource carries `HasWoTProjection` to that Object; browsing the inverse `WoTProjectionOf` from any projected node reaches its canonical document. + +### 6.8 Well-known registry instance — `WoTRegistry` + +`WoTRegistry` (`i=64100`, type definition `WoTRegistryType`) is a `HasComponent` of the `Server` object (`i=2253`). + +A fixed, well-known location makes the registry discoverable without configuration, exactly as the incorporated 1.02 `WoTAssetConnectionManagement` object is discoverable under `Objects`. + +The generated NodeSet materialises the instance with a functional `Refresh` Method and concrete Values for every Mandatory member (own and inherited), so loading the NodeSet alone yields a structurally complete, callable registry; a server binds the concrete handlers. + +## 7 Registry semantics and lifecycle (normative) + +### 7.1 Canonical files, derived projection + +The stored TD/TM files and their versions are canonical. The projected AddressSpace and any generated code-behind are **derived**: a server shall be able to rebuild them entirely from the stored documents, the pinned `uav` vocabulary version and the registry's model, without any additional hidden state. A client shall not rely on projected nodes surviving a change to the underlying document except as governed by the generation and retirement rules below. + +### 7.2 Projection mapping + +A valid document is projected using the `uav` JSON-LD vocabulary defined by the revised [OPC UA — WoT Binding](../WoT-Binding/OPC-UA-WoT-Binding.md); that specification is the normative source for how each `uav` term maps to an OPC UA construct, and this section applies it: + +- A **Thing Model** projects to a **type**: `uav:objectType` → an `ObjectType`, `uav:variableType` → a `VariableType`; its affordances become **member declarations** with the declared modelling rule (`uav:modellingRule`), unit (`uav:unitProperty` / QUDT), scaling (`uav:scaleFactor`, `uav:decimalPlaces`) and grouping (`uav:memberOf`, `uav:propertyGroups`/`eventGroups`/`actionGroups`). The materialized type NodeId is exposed as `ThingModelFileType.DerivedTypeNodeId`. +- A **Thing Description** projects to an **instance**: `uav:object` → an `Object` whose affordances become **Variables** (properties), **Methods** (actions) and **event sources** (events → subtypes of an event type, per the TD's `uav:eventType`/`uav:isEvent`/`data`). +- **Affordances** map to nodes as above; **links** map to OPC UA **References** (`uav:componentModel` → `HasComponent`, a ReferenceType compact model name used directly in `rel` → that typed Reference with `uav:refId` as identifier, `uav:reference`/`uav:capability` → non-hierarchical references, `uav:componentOf` → the parent `HasComponent` of §7.3) or, when no native reference fits, to an explicit link representation carried on the projected node. +- **Forms** do not become nodes; each form is compiled into a **binder plan** (§8) that the server executes to read/write/observe/invoke/subscribe the affordance over the form's protocol binding. + +### 7.3 Placement and parent selection (no mandatory flat root) + +This specification **shall not** impose a mandatory flat "Assets" root as a container for every projected instance; the authored hierarchy and references of the stored documents determine where each Thing appears. + +A Thing Description author selects the parent under which its projected Object is exposed by adding a WoT `links` entry with the relation **`rel: uav:componentOf`** (defined by the WoT Binding vocabulary, [§6.2](../WoT-Binding/OPC-UA-WoT-Binding.md)). The relation is directional: `uav:componentOf` states that *this* Thing is a component of the **linked** resource, so the link target is the intended **parent** (container). The link `href` **shall** identify the parent in one of three ways, and the materializer **shall** resolve it in this order: + +1. another document in the same registry (a relative/registry href, resolved to that document's projection root in the committed generation); +2. an existing AddressSpace node, given as an OPC UA `NodeId` or a `uav:browsePath`; +3. the projection root of the Thing Model the TD derives from (a self-reference to the type instance owner), when the href names it. + +When the parent resolves, the materializer **shall** create a forward OPC UA `HasComponent` reference from the resolved parent node to the projected Object (equivalently an inverse `HasComponent` from the Object to its parent). When a `uav:componentOf` link is present but its target cannot be resolved within the committed generation, the document's projection **shall** fail (`LoadState = Failed`, `WoTLoadFailureEventType`, `Phase = Projection`) rather than silently falling back. When no `uav:componentOf` link is present, the projected Object **shall** be `Organizes`d directly under the `Objects` folder. A server **shall not** require, and clients **shall not** assume, any additional convenience container. + +### 7.4 Dependency graph construction and closure atomicity + +Documents form a **dependency DAG**. A refresh **shall** construct this graph before projecting: a Thing Description depends on the Thing Models it derives from (`links rel=type`) and on any parent it references (`links rel=uav:componentOf`, §7.3); a Thing Model depends on the Thing Models it extends or references (`tm:extends`, `tm:ref`). Each resolved and unresolved edge **shall** be recorded as a `WoTDependencyDataType` (`SourceXid`, `TargetXid`, `TargetUri`, `RefType`, `Resolved`) so the closure is inspectable. A document together with its transitive dependencies is a **closure**. + +A dependency shall be resolved against the selected registry snapshot. A matching local resource version is used first; if no local match exists, a configured xRegistry federation provider may supply a pinned remote registry resource and version. Known contexts and schemas may be satisfied from implementation-provided, version-pinned local catalogs. A materializer shall not dereference an arbitrary Web URL found in a context, schema reference, link or form while refreshing a projection. `ExternalReference` and `ResourceUrl` are federation metadata and do not by themselves grant permission to fetch a document. + +TD form endpoints and security declarations are informative to protocol clients and may be inputs to an explicitly enabled protocol binder. They do not select the registry transport, establish trust, authorize dependency resolution or weaken the registry access requirements of §7.11. + +A refresh **shall** reject a graph that is not a DAG: if a cycle is detected (for example two Thing Models that `tm:extends` each other), the affected documents **shall** be reported `Failed` with `Phase = DependencyResolution` and a `WoTLoadFailureEventType`, and **shall not** be projected. When the applied atomicity is `PerClosure` or coarser, the whole closure **shall** be projected into a shadow generation and committed together or not at all: a closure with any unresolved or invalid dependency **shall not** be partially activated — no node of that closure becomes visible. When the applied atomicity is `PerResource`, an independent resource with no unresolved dependency **may** commit even if an unrelated resource in the same refresh fails. + +*Example.* Refreshing `pump-01` (which `links rel=type` to `PumpType`) with `Atomicity = PerClosure` activates the pair `{pump-01, PumpType}` atomically; if `PumpType` is missing, neither is projected and `pump-01` is reported `Failed` with an unresolved `WoTDependencyDataType` edge. + +### 7.5 Invalid documents and desired-versus-active divergence + +Validation failure **shall not** destroy data. An invalid document (format or compatibility) **shall** remain stored; its `LoadState` **shall** become `Failed`, its `ValidationOutcome` **shall** record the failing phase and reason, and a `WoTValidationFailureEventType` **shall** be raised from the resource. The **previously active valid projection of that resource shall remain active and unchanged**: a refresh that would activate an invalid document **shall** instead keep the prior committed generation of that closure serving. + +`DesiredVersionId` records the version an operator wants projected; `ActiveVersionId` records the version currently serving. They **may** differ transiently during a switch. When the desired version is invalid, they **shall** diverge persistently — the last valid active version keeps serving while `DesiredVersionId` points at the rejected version — and this divergence **shall** be observable so operator tooling can surface it. A server **shall not** silently activate a desired version that fails validation. + +### 7.6 Shadow prepare, switch and retirement + +Projection **shall** be generational and use a shadow-then-switch discipline so a client never observes a half-built model. A refresh **shall** proceed through the phases of `WoTPhaseEnum`: it **prepares** the new nodes as a **shadow generation** beside the active one (`LoadState = Loading`), validates and binds them, then **switches atomically** to make them visible (`LoadState = Active`) and increments `RefreshGeneration`. + +The implementation shall apply one documented retirement policy after the switch: + +- **graceful retirement** — the superseded generation enters `Superseded`, then `Retiring`. Existing MonitoredItems, continuation points and in-flight requests continue against it until they drain or are migrated to equivalent nodes. New service requests use the active generation. The server removes the old nodes and binding plans only after the retained work drains; or +- **immediate retirement** — new service requests use the active generation and the prior generation is retired without waiting for its MonitoredItems to drain. The server shall invalidate every affected MonitoredItem. Each affected data-change MonitoredItem shall report `BadNodeIdUnknown`; affected event MonitoredItems shall cease producing events and subsequent monitored-item service operations on them shall return `BadNodeIdUnknown`. Clients may then delete or recreate those MonitoredItems. + +The policy may be implementation- or configuration-selected but shall be deterministic for a refresh and documented by the implementation. Graceful retirement is the continuity-preserving policy validated by [PR #4015](https://github.com/OPCFoundation/UA-.NETStandard/pull/4015); immediate retirement is suitable when retaining an old projection is not supported or desired. The number of generations retired **shall** be reported in the refresh summary (`WoTRefreshSummaryDataType.Retired`). + +```mermaid +stateDiagram-v2 + [*] --> Unloaded + Unloaded --> Loading: prepare shadow generation + Loading --> Active: atomic switch (RefreshGeneration++) + Loading --> Failed: validation/projection error + Active --> Superseded: newer generation switched in + Superseded --> Retiring: graceful retirement + Superseded --> Retired: immediate retirement / BadNodeIdUnknown + Retiring --> Retired: monitored items and requests drained + Failed --> Loading: corrected document refreshed +``` + +### 7.7 Automatic and explicit refresh; idempotence + +The registry **shall** support both automatic and explicit refresh. When `AutoRefresh` is true, the registry **shall** re-project per `RefreshMode`: `Periodic` on each `RefreshInterval`, `EventDriven` on a stored-document change (a write or, for a legacy asset, `CloseAndUpdate`), or `Scheduled` on an implementation-defined schedule. The `Refresh` Method (§9) **shall** re-project a selection on demand regardless of `AutoRefresh`. + +A refresh **shall** be idempotent: a document whose `ContentDigest` is unchanged since its last projection at the current `VocabularyVersion` **shall** be reported `Unchanged` and **shall not** be re-materialized, unless `Options.Force` is set. Re-running an unchanged refresh **shall** produce the same active generation and the same node set (no NodeId churn, no `RefreshGeneration` increment). A server **should** compute `ContentDigest` over the canonical document bytes so that semantically insignificant reformatting does not force re-projection. + +### 7.8 Version switch, unload, delete and federation + +- **Version switch.** Setting `DesiredVersionId` (or calling `SetDefaultVersion`) and refreshing **shall** shadow-switch the resource's projection to the selected version per §7.6. With graceful retirement, live subscriptions continue against the prior generation until drain or migration. With immediate retirement, affected MonitoredItems report `BadNodeIdUnknown`. +- **Unload.** `SetEnabled(false)` **shall** request unloading the projection while keeping the stored document. Dependents **shall** be treated per the effective `DeletePolicy`: `Reject` **shall** refuse the operation while any loaded document still depends on the resource; `Retire` **shall** retire the projection but keep the document resolvable for dependents; `Cascade` **shall** unload dependents that resolve only through this document; `Force` **shall** unload the projection even while dependents remain, marking those dependents `Failed`. +- **Delete.** The inherited xRegistry `Delete` **shall** remove the stored resource/version; its projection **shall** be retired first, subject to the same `DeletePolicy`. +- **Federation.** A document **may** be served by reference through the inherited xRegistry `ExternalReference` (`ExpandedNodeId`) / `ResourceUrl`. A server **shall** resolve it only through a configured xRegistry federation provider, not by treating either Property as permission for an arbitrary network fetch. A federated Thing Model **may** satisfy a local Thing Description's dependency closure when the provider pins its registry identity, version and digest. A federated dependency that cannot be resolved **shall** be reported as an unresolved `WoTDependencyDataType` edge and handled as in §7.4. + +*Example.* Unloading `PumpType` with `DeletePolicy = Reject` while `pump-01` still depends on it is rejected; retrying with `Cascade` unloads `pump-01`'s projection first, then `PumpType`'s. + +### 7.9 Model change events and NodeVersion correlation + +A committed generation switch changes the AddressSpace graph. The server shall emit OPC UA **model change events** (`GeneralModelChangeEventType`) for the committed node additions/removals/reference changes, and shall stamp affected nodes' `NodeVersion` so a client can correlate a node's version with the `RefreshGeneration` that produced it (the `HasWoTProjection` reference ties the node back to its document). Model change events are emitted for **committed** graph changes only — never for shadow-generation scratch work. + +### 7.10 Semantic change events (optional) + +When a refresh changes the *meaning* of a type or instance (for example a DataType, EngineeringUnit or semantic identifier changes in a way governed by the Part 3/Part 5 model-change rules and the OPC 11030 compatibility rules), the server MAY additionally emit an OPC UA **semantic change event** (`SemanticChangeEventType`) for the affected nodes. Concrete subtypes of the abstract model/semantic change event types are defined only where required for emission; this specification relies on the Core types unless a WoT-specific subtype is needed. + +### 7.11 Security + +Document dependencies shall be resolved only from the selected local registry snapshot, version-pinned local catalogs or a configured xRegistry federation provider. Federation transport shall use the registry's configured trust and credentials. Credentials for federation and protocol-binding endpoints are held out of band and **never** exposed on registry or binding nodes. + +Management Methods (`Refresh`, `Validate`, `SetEnabled`, `SetDefaultVersion`, and the inherited xRegistry create/delete Methods) are subject to OPC UA role-based access control. Every operation that creates, modifies, selects, enables, disables or deletes registry content or metadata, including FileType write/commit operations and `Refresh`, shall be accepted only over an OPC UA SecureChannel using `MessageSecurityMode` `SignAndEncrypt`. Registry reads should use a secured channel. An implementation may expose read-only Browse, Read and FileType download operations over `MessageSecurityMode` `None` when its deployment policy permits this; clients shall not infer authenticity, integrity or confidentiality from such a channel. + +Read access to a group MAY be restricted to authorise discovery of the Things it contains. TD form endpoints and security declarations are informative to protocol clients and may be consumed by an explicitly enabled binder. They do not configure or secure the registry, authorize document resolution, or override the server's endpoint allowlist, trust store, credential provider or SecureChannel policy. + +## 8 Protocol binder (normative) + +The **binder** turns a form into an executable plan. It has a protocol-independent **core** and a set of **per-binding** modules. + +- **Core:** parses a form, resolves its `href` against the TD `base`, selects the binding by the form's protocol vocabulary, maps the WoT `op` set to OPC UA service semantics (readproperty→Read, writeproperty→Write, observeproperty→MonitoredItem, invokeaction→Call, subscribeevent→event MonitoredItem), and produces a plan whose capabilities are recorded in the document's `SelectedBindings` snapshot. +- **Per-binding:** each W3C binding template (OPC UA, HTTP, Modbus, MQTT, CoAP, …) is a module advertised as a `WoTBindingType` with its `Capabilities` (`WoTBindingCapabilityEnum` set) and content types. +- **Version pinning and maturity:** each binding is pinned to a specific W3C binding-document version (`ProfileVersion`) and exposes that document's **draft maturity** (`DraftMaturity`: WD/CR/PR/REC). A server shall bind a form only with a binding whose pinned document it implements, and shall surface the maturity so consumers can judge stability. A form that names an unknown binding or an operation the binding does not support raises a `WoTBindingFailureEventType`. + +## 9 Refresh Method and results (normative) + +### 9.1 Purpose and use cases + +`Refresh` is the control surface that (re-)derives the AddressSpace projection from the canonical documents. It exists because the projection is a *cache*: whenever the documents, their versions, or the pinned vocabulary change, the cache must be rebuilt deterministically and atomically. An implementer should treat `Refresh` as the single funnel through which every projection change flows, whether triggered explicitly or automatically (§7.7). Typical use cases are: **initial load** of a newly ingested Thing Model or Thing Description; **version switch** to a newly published version; **repair** after a previously failed document is corrected; **dry-run planning** to preview what a refresh would do before committing; and **forced re-projection** after a vocabulary-version change even though document bytes are unchanged. + +### 9.2 Signature + +`WoTRegistryType.Refresh(Selection, Options, ExpectedGeneration, RequestId) → (Summary, Results, NewGeneration)`: + +- **Selection** — an array of `WoTResourceSelectorDataType`; an empty array selects the whole registry. Selectors filter by kind, group, resource, version or a single `Xid`. +- **Options** — `WoTRefreshOptionsDataType`: `Atomicity`, `Force`, `DryRun`, `IncludeDependents`, `DeletePolicy`, `MaxParallelism`, `Timeout`. +- **ExpectedGeneration** — optimistic concurrency: if non-zero and unequal to `RefreshGeneration`, the call **shall** fail `Bad_InvalidState` and change nothing. +- **RequestId** — echoed into `Summary.RequestId` and the completion event for correlation. +- **Summary** — a `WoTRefreshSummaryDataType`: overall outcome, applied atomicity, counts (total/succeeded/unchanged/failed/skipped/retired), timing and the committed generation. +- **Results** — an array of `WoTResourceLoadResultDataType`, one per considered resource: identity, kind, per-resource outcome and phase, resulting load state, generation, materialized-node count and root, content digest and a message. +- **NewGeneration** — the committed generation; unchanged on a dry run or a full failure. + +### 9.3 Caller workflow + +A typical caller: (1) reads `RefreshGeneration` to capture the current generation; (2) calls `Refresh` with a `Selection`, `Options.DryRun = true` and `ExpectedGeneration` set to the captured value to obtain a plan (`Summary`/`Results`) without changing anything; (3) inspects the predicted per-resource outcomes; (4) calls `Refresh` again with `DryRun = false` and the same `ExpectedGeneration` to commit. Steps 1 and 4 make the operation safe under concurrency: if another refresh committed in between, the generation no longer matches and the commit fails without effect, so the caller re-plans against the new generation. + +### 9.4 Validation, planning and apply algorithm + +On each invocation the server **shall**: + +1. **Check concurrency.** If `ExpectedGeneration` is non-zero and ≠ `RefreshGeneration`, fail `Bad_InvalidState`, change nothing. +2. **Select.** Expand `Selection` to a resource set; an empty `Selection` selects the whole registry. If `Options.IncludeDependents`, add every document that transitively depends on a selected document. +3. **Resolve and parse.** For each selected document, read its bytes from the registry snapshot and resolve referenced contexts, schemas and linked TDs/TMs from that snapshot, a version-pinned local catalog or configured xRegistry federation only (`Phase = Fetch`/`Parse`). Do not perform arbitrary external Web resolution. +4. **Validate.** Perform format and, when enabled, compatibility validation (`Phase = FormatValidation`/`CompatibilityValidation`), recording a `WoTValidationOutcomeDataType`. An invalid document is marked `Failed` and excluded from projection; its prior active projection is retained (§7.5). +5. **Resolve dependencies.** Build the dependency DAG (§7.4) and group the valid documents into closures; a cyclic or unresolved-dependency closure is failed at `Phase = DependencyResolution`. +6. **Plan.** For each document, compare `ContentDigest` and vocabulary version against the last projection; unchanged documents are marked `Unchanged` and skipped unless `Options.Force`. +7. **Project (shadow).** Materialize the changed closures into a shadow generation (`Phase = Projection`, `LoadState = Loading`), honouring `MaxParallelism`. If `Options.DryRun`, stop here and report the plan without committing. +8. **Activate.** Switch the shadow generation in atomically at the granularity of `Options.Atomicity` (`Phase = Activation`), increment `RefreshGeneration`, emit Core model change events (§7.9), and stamp `NodeVersion`. +9. **Retire.** Apply the configured graceful or immediate retirement policy to superseded generations (`Phase = Retirement`, §7.6). Immediate retirement reports `BadNodeIdUnknown` for affected MonitoredItems. +10. **Summarize.** Populate `Summary`/`Results`, cache `Summary` as `LastRefreshSummary`, set `LastRefreshTime`, and raise `WoTRefreshCompletedEventType`. + +### 9.5 Concurrency and generation behaviour + +Concurrent `Refresh` calls **shall** be serialized with respect to committing a generation: at most one generation switch is in flight at a time. `RefreshGeneration` **shall** increment by exactly one per committed switch and **shall not** change on a dry run, a full failure, or an all-`Unchanged` refresh. Because a caller can pin `ExpectedGeneration`, a lost update is impossible: a stale plan cannot overwrite a newer generation. + +### 9.6 Outcomes, errors and implementer responsibilities + +`DryRun` **shall** validate and compute `Summary`/`Results` without committing any projection change. `Validate` (on a document) performs format/compatibility validation only and returns a `WoTValidationOutcomeDataType`. A per-resource `Outcome` is one of `Success`, `Unchanged`, `Warning`, `Skipped`, `Rejected` or `Failed`; a document that fails **shall** carry the failing `Phase` and a human-readable `Message`. A partial failure under `PerResource` atomicity **shall** leave successful resources committed and failed resources on their prior generation; a failure under `PerClosure`/`PerRegistry` atomicity **shall** leave the whole unit uncommitted. Implementers are responsible for: making the whole operation restartable (a crash mid-refresh must not leave a half-switched generation); ensuring idempotence via `ContentDigest`; and never destroying a stored document or a last-valid projection because of a refresh error. A representative populated result set is `examples/04-refresh-results.json` (§12.4). + +### 9.7 Refresh sequence + +```mermaid +sequenceDiagram + actor Op as Operator/Client + participant R as WoTRegistry (Refresh) + participant V as Validator + participant P as Projector (shadow) + participant AS as AddressSpace + Op->>R: Refresh(Selection, Options, ExpectedGeneration, RequestId) + R->>R: check ExpectedGeneration == RefreshGeneration + alt mismatch + R-->>Op: Bad_InvalidState (no change) + else match + R->>V: fetch, parse, validate selected documents + V-->>R: WoTValidationOutcome per document + R->>R: resolve dependency DAG -> closures + R->>P: materialize changed closures (shadow generation) + alt DryRun + P-->>R: predicted node counts / roots + R-->>Op: Summary + Results (NewGeneration unchanged) + else commit + P->>AS: atomic switch (Atomicity), RefreshGeneration++ + AS-->>R: model change events, NodeVersion stamped + alt graceful retirement + R->>AS: drain or migrate monitored items, then retire + else immediate retirement + R->>AS: report BadNodeIdUnknown and retire immediately + end + R-->>Op: Summary + Results + NewGeneration + R->>Op: WoTRefreshCompletedEventType + end + end +``` + +## 10 Events and change notifications (normative) + +Events let an operator observe the registry without polling; an implementer should raise them at the exact points below so tooling can react deterministically. A conformant registry **shall** raise: `WoTValidationFailureEventType` on a format or compatibility failure; `WoTLoadFailureEventType` when a validated document fails to materialize or a shadow generation cannot be activated; `WoTBindingFailureEventType` when a form cannot be bound (unknown binding or unsupported operation); and `WoTRefreshCompletedEventType` on every completed refresh, including automatic ones. Each failure event **shall** name the failing **resource** as its source and carry the `Phase` at which the failure occurred; the refresh-completed event **shall** name the **registry** and carry the committed `Summary` and `Generation`. On a committed generation switch the server **shall** additionally raise Core `GeneralModelChangeEventType` model change events for the added/removed nodes and reference changes (§7.9), and — where required by §7.10 — `SemanticChangeEventType`. Model change events **shall** be raised only for committed graph changes, never for shadow-generation scratch work. A subscriber on the well-known `WoTRegistry` object receives all of the above through the Server → registry → group → resource notifier chain (§6.6). + +## 11 Security (normative) + +Security applies at three boundaries: registry transport, federation, and optional protocol binders. + +Projection refresh shall resolve documents only from the selected local registry snapshot, a version-pinned local catalog or a configured xRegistry federation provider. It shall not issue a direct network request merely because a TD/TM contains a context URI, schema URI, link target, `ResourceUrl`, form endpoint or security declaration. A federation provider shall apply endpoint allowlists, trust policy, authentication, size limits and version/digest pinning. + +The management Methods — `Refresh`, `Validate`, `SetEnabled`, `SetDefaultVersion` and the inherited xRegistry create/delete Methods — **shall** be subject to OPC UA role-based access control and **shall** require a `SignAndEncrypt` SecureChannel for every mutation. Registry reads **should** use a secured channel; an implementation **may** permit read-only access with `MessageSecurityMode` `None`. Read access to a group **should** be restrictable, because it is the authorisation boundary that decides who can discover the Things a group contains. + +Credentials for federation and protocol-binding endpoints are held out of band and **shall never** be exposed on registry or binding nodes. TD endpoint and security metadata is informative to clients and optional binders; it does not establish registry trust or authorize document resolution. Operators **should** scope group read access accordingly. A server **shall not** weaken these controls for the incorporated 1.02 asset-management surface: the deprecated methods are subject to the same role-based access control as the registry methods that back them (§13.2). + +## 12 Worked examples (informative) + +The four documents under `examples/` are refreshed together in the `examples/04-refresh-results.json` run. Representative fragments are shown and discussed below; the full files carry the complete context and forms. + +### 12.1 Thing Model → ObjectType (`examples/01-thing-model-pump.tm.jsonld`) + +The Thing Model is a class template; its `@type` `uav:objectType` makes a refresh project it to an OPC UA ObjectType, and each affordance becomes a member declaration with the modelling rule the TM declares. + +```jsonc +"@type": ["tm:ThingModel", "uav:objectType"], +"title": "PumpType", +"uav:browseName": "1:PumpType", +"properties": { + "pumpSpeed": { + "@type": "uav:variableType", "uav:browseName": "1:PumpSpeed", + "type": "number", "unit": "qudt-quantitykind:AngularVelocity", + "uav:modellingRule": "Mandatory", "uav:scaleFactor": 0.1, "uav:decimalPlaces": 2 + } +}, +"actions": { "reset": { "@type": "uav:method", "uav:modellingRule": "Optional" } } +``` + +Here `PumpType` projects to an ObjectType whose `PumpSpeed` is a Mandatory Variable (scaled `engineering = raw × 0.1`, two decimals) and whose `reset` is an Optional Method. The resulting type NodeId is published back on `ThingModelFileType.DerivedTypeNodeId`. + +### 12.2 Thing Description → Object instance (`examples/02-thing-description-pump.td.jsonld`) + +The Thing Description is a concrete instance (`@type` `uav:object`) that derives from the Thing Model through a `links rel=type` entry — the dependency edge of §7.4 — and binds each affordance to a protocol form. + +```jsonc +"@type": ["Thing", "uav:object"], +"title": "Pump 01", +"links": [ { "rel": "type", "href": "../01-thing-model-pump.tm.jsonld" } ], +"base": "opc.tcp://opcuademo.com:4840", +"properties": { "pumpSpeed": { "@type": "uav:variable", + "forms": [ { "href": "/?id=nsu=http://example.com/demo/pump;s=PumpSpeed", + "op": ["readproperty", "observeproperty"] } ] } } +``` + +A refresh with `Atomicity = PerClosure` activates `{Pump 01, PumpType}` together; the `pumpSpeed` form compiles into a binder plan (`readproperty` → Read, `observeproperty` → MonitoredItem, §8), and the resource carries `HasWoTProjection` to the projected Object. A TD that also wanted to appear under a parent machine would add a `links` entry with `rel: uav:componentOf` (§7.3). + +### 12.3 Invalid Thing Description stays stored (`examples/03-invalid-thing-description.td.jsonld`) + +An intentionally invalid TD demonstrates §7.5: it stays stored, is marked `Failed`, and leaves any prior projection untouched. + +```jsonc +// missing top-level "title"; undefined security scheme; invalid form op +"security": "missing_sc", +"properties": { "pumpSpeed": { "forms": [ { "op": ["frobnicate"] } ] } }, +"links": [ { "rel": "type", "href": "../thingmodels/does-not-exist.tm.jsonld" } ] +``` + +Format validation fails at `Phase = FormatValidation`; the registry records a `WoTValidationOutcomeDataType` with `FormatOutcome = Failed`, raises a `WoTValidationFailureEventType` from the resource, and reports an unresolved `WoTDependencyDataType` edge for the missing Thing Model. + +### 12.4 Refresh results (`examples/04-refresh-results.json`) + +The `Refresh` output over the three documents pairs a `WoTRefreshSummaryDataType` with per-resource `WoTResourceLoadResultDataType` rows. + +```jsonc +"summary": { "Generation": 7, "Outcome": "Success", "Atomicity": "PerClosure", + "Total": 3, "Succeeded": 2, "Failed": 1, "Retired": 1 }, +"results": [ + { "ResourceId": "pumptype", "Outcome": "Success", "Phase": "Activation", + "LoadState": "Active", "MaterializedNodeCount": 12 }, + { "ResourceId": "pump-01", "Outcome": "Success", "Phase": "Activation", "LoadState": "Active" }, + { "ResourceId": "pump-broken","Outcome": "Failed", "Phase": "FormatValidation", "LoadState": "Failed" } +] +``` + +The summary reports two successes and one failure in generation 7, with one superseded generation retired under the configured policy (§7.6); the failed `pump-broken` is isolated because its closure never activated, exactly as §7.4 requires. + +## 13 Incorporated OPC 10100-1 v1.02 model (normative) + +Revision 1.1 **incorporates** the published OPC 10100-1 v1.02 WoT Connectivity model into this combined NodeSet, in the **same** namespace `http://opcfoundation.org/UA/WoT-Con/`. Every published node is preserved at its **exact** numeric NodeId (`1..172`) and NodeClass — sourced from the pinned `legacy/WotConnection.csv` and emitted by `tools/build_model.py`, not hand-copied — so existing 1.02 clients continue to work unchanged. Because the additive registry supersedes the flat asset-management surface, the incorporated types and the well-known asset-management object carry `ReleaseStatus="Deprecated"` (following OPC 11030): they are **deprecated, not removed**. This is one combined model in one namespace; there is no separate namespace and no dual profile. + +### 13.1 Preserved 1.02 model (unchanged) + +The entry point is the `WoTAssetConnectionManagement` object (an instance of `WoTAssetConnectionManagementType`) under the `Objects` folder, preserved at its published NodeId and callable. Because xRegistry occupies namespace index 1 in this combined document, the 1.02 nodes are the own-namespace nodes at index 2; their **numeric identifiers are unchanged** and all own-namespace references and BrowseNames are consistent. The types, methods and method signatures are: + +| Element | Kind | Preserved definition | +|---|---|---| +| `WoTAssetConnectionManagementType` | ObjectType (BaseObjectType) | `` Object placeholder (BaseObjectType, `HasInterface` IWoTAssetType); `SupportedWoTBindings` `UriString[]` Property; Methods below; `Configuration` (WoTAssetConfigurationType). | +| `CreateAsset` | Method (Mandatory) | `CreateAsset([in] String AssetName, [out] NodeId AssetId)`. | +| `DeleteAsset` | Method (Mandatory) | `DeleteAsset([in] NodeId AssetId)`. | +| `DiscoverAssets` | Method (Optional) | `DiscoverAssets([out] String[] AssetEndpoints)`. | +| `CreateAssetForEndpoint` | Method (Optional) | `CreateAssetForEndpoint([in] String AssetName, [in] String AssetEndpoint, [out] NodeId AssetId)`. | +| `ConnectionTest` | Method (Optional) | `ConnectionTest([in] String AssetEndpoint, [out] Boolean Success, [out] String Status)`. | +| `WoTAssetConfigurationType` | ObjectType | Vendor `` Properties; `License` String. | +| `IWoTAssetType` | Interface (abstract) | `` Variables via `HasWoTComponent`; `AssetEndpoint` String; `WoTFile` (WoTAssetFileType, Mandatory). | +| `WoTAssetFileType` | ObjectType (FileType) | `CloseAndUpdate([in] UInt32 FileHandle)` (Mandatory). | +| `HasWoTComponent` | ReferenceType | Subtype of `HasComponent`; InverseName `WoTComponentOf`. | + +Every 1.02 scenario continues to work: create-from-existing-TD (`CreateAsset` + `WoTFile` upload + `CloseAndUpdate`), discovery (`DiscoverAssets` → `ConnectionTest` → `CreateAssetForEndpoint` with an auto-generated TD file), deletion (`DeleteAsset`), and the supported-bindings advertisement (`SupportedWoTBindings`). The incorporated `NamespaceMetadata` carries the new `1.1.0` version while its NodeIds stay stable, and the deprecation is machine-readable (`ReleaseStatus="Deprecated"`) so tools can steer new development to the registry surface. + +### 13.2 Backing the deprecated surface with the registry (signatures unchanged) + +A server **may** back the deprecated asset-management surface with the registry so that both views stay consistent, **without changing any 1.02 signature**: + +- **`CreateAsset(AssetName) → AssetId`** — the server creates (or reuses) a default `ThingDescriptionGroup`, creates a `ThingDescriptionFileType` resource for the asset (xRegistry `CreateResource`), and returns the NodeId of the *projected* Object as `AssetId`. The `` object is the projected Object. +- **`WoTFile` / `WoTAssetFileType`** — the legacy `WoTFile` maps onto the `ThingDescriptionFileType` resource's inherited FileType (`Open`/`Read`/`Write`/`Close`): uploading the TD writes the resource's document bytes. +- **`CloseAndUpdate(FileHandle)`** — maps onto write-close of the resource followed by an implicit single-resource `Refresh` (validate + project). The 1.02 result codes are preserved: `Bad_DecodingError`/`Bad_NotSupported`/`Bad_NotFound` correspond to the format-validation and projection failures of this specification; on success the projected Variables appear exactly as in 1.02. +- **`DeleteAsset(AssetId)`** — maps onto `SetEnabled(false)` (unload projection) and the inherited xRegistry `Delete` of the backing resource, subject to the delete policy. +- **`DiscoverAssets` / `ConnectionTest` / `CreateAssetForEndpoint`** — preserved unchanged; a discovered/auto-generated TD is stored as a `ThingDescriptionFileType` resource, so discovery-created assets become first-class registry documents. +- **`SupportedWoTBindings` (`UriString[]`)** — surfaces the same binding set the registry advertises through `SupportedBindings` / `SelectedBindings`. +- **`HasWoTComponent`** — the legacy per-property reference remains valid on legacy-projected assets; registry-projected instances additionally carry `HasWoTProjection` back to their document. + +Because the incorporation keeps the 1.02 namespace, types, node numbering and method signatures intact, a legacy client cannot tell whether the registry backs the asset manager; a registry-aware client sees the same assets as registry documents. + +## 14 Conformance units and profiles + +Conformance is composed from independently implementable **conformance units (CUs)**, grouped into **profiles**. + +| CU | Requires | +|---|---| +| `WoT-Con Registry Discovery` | Well-known `WoTRegistry` under Server; browse registry/groups; read xRegistry + WoT metadata. | +| `WoT-Con Document Read` | Read a stored TD/TM via the inherited FileType `Open`/`Read`/`Close`. | +| `WoT-Con Document Write` | Create/write TD/TM resources via xRegistry `CreateResource` + FileType write. | +| `WoT-Con TD Validation` | Format + compatibility validation of TDs; `ValidationOutcome`; validation-failure events. | +| `WoT-Con TM Validation` | Format + compatibility validation of TMs. | +| `WoT-Con Type Materialization` | Project a TM to a type (`DerivedTypeNodeId`). | +| `WoT-Con Instance Materialization` | Project a TD to an instance with Variables/Methods/EventTypes. | +| `WoT-Con Reference Materialization` | Project links to References / explicit link representation (including parent `uav:componentOf`). | +| `WoT-Con Refresh` | `Refresh` Method with selection/options/expected-generation and detailed results. | +| `WoT-Con Events` | Resource lifecycle + refresh-completed events with the Server→registry→group→resource notifier chain. | +| `WoT-Con Model Change` | Model change events + NodeVersion correlation for committed generations. | +| `WoT-Con Semantic Change` | Optional semantic change events per §7.10. | +| `WoT-Con Version Lifecycle` | Desired/active version, version switch, unload, delete. | +| `WoT-Con Federation` | Resolve/project federated documents via `ExternalReference`/`ResourceUrl`. | +| `WoT-Con Binder Core` | Compile forms to binder plans; op→service mapping. | +| `WoT-Con Binder ` | A specific per-binding module (OPC UA, HTTP, Modbus, …) with capabilities + version pinning. | +| `WoT-Con Atomicity Modes` | Per-resource / per-group / per-closure / per-registry atomicity with shadow switch and documented graceful or immediate retirement. | +| `WoT-Con Legacy 1.02 Compatibility` | The incorporated OPC 10100-1 v1.02 model and scenarios, callable and preserved (§13). | + +**Profiles.** *WoT-Con Registry Server* = Registry Discovery + Document Read/Write + TD/TM Validation + Type/Instance/Reference Materialization + Refresh + Events + Version Lifecycle + Binder Core + at least one Binder module. *WoT-Con Full* adds Model/Semantic Change, Federation and Atomicity Modes. *WoT-Con Legacy 1.02 Compatibility* is the incorporated-and-deprecated 1.02 surface (§13), independently conformant so existing 1.02 clients are served without the registry profile. + +## 15 Acceptance scenarios + +Each scenario is an end-to-end acceptance test for the CUs it exercises. + +1. **Discover and read** — Browse `Server → WoTRegistry`, enumerate groups and resources, and read a stored TD's bytes via `Open`/`Read`/`Close`. *(Discovery, Document Read)* +2. **Ingest and validate a TM** — Create a `thingmodel` resource, write the `PumpType` TM, `Validate`; expect `FormatOutcome=Success`; `Refresh`; expect a projected ObjectType and `DerivedTypeNodeId` set. *(Document Write, TM Validation, Type Materialization)* +3. **Ingest a derived TD** — Create a `thingdescription` resource, write `Pump 01`, `Refresh` with `Atomicity=PerClosure`; expect the TD's closure (TD + PumpType TM) to activate atomically, an Object with `PumpSpeed`/`SpeedSetpoint`/`Reset`, forms bound to the OPC UA binder, and `HasWoTProjection` from the resource to the Object. *(Instance/Reference Materialization, Binder Core, Atomicity)* +4. **Invalid TD stays stored** — Write `pump-broken`, `Refresh`; expect `LoadState=Failed`, a `WoTValidationFailureEventType`, the document still readable, and any prior valid projection of that resource unchanged. *(TD Validation, Events)* +5. **Idempotent refresh** — Re-run scenario 3's `Refresh` unchanged; expect every result `Unchanged`, no node churn, `RefreshGeneration` unchanged. *(Refresh idempotence)* +6. **Version switch with graceful retirement** — Subscribe to `PumpSpeed`; publish `pump-01` v1.1.0; set `DesiredVersionId=1.1.0`; `Refresh`; expect a shadow switch, model change events for the committed change, `NodeVersion` updated, the old generation retired only after the subscription drains or migrates, and no lost notifications. *(Version Lifecycle, Model Change, Atomicity)* +7. **Version switch with immediate retirement** — Repeat the switch with immediate retirement selected; expect new reads to use v1.1.0, each affected MonitoredItem to report `BadNodeIdUnknown`, and the old generation to be disposed without waiting for drain. *(Version Lifecycle, Atomicity)* +8. **Unload with dependents** — `SetEnabled(false)` on the `PumpType` TM with `DeletePolicy=Reject` while `Pump 01` depends on it; expect rejection; retry with `Cascade`; expect the dependent TD's projection to unload. *(Version Lifecycle)* +9. **Federated dependency** — Point `Pump 01`'s TM dependency at a federated TM via `ResourceUrl`; configure that remote registry in the federation provider; `Refresh`; expect the closure to resolve and activate through the pinned federation link. *(Federation)* +10. **Legacy round-trip** — Via the deprecated `WoTAssetConnectionManagement`, `CreateAsset("Pump01")`, `Open`/`Write`/`CloseAndUpdate` the TD, browse the mapped Variables; when the registry backs the surface, expect the same asset to appear as a `thingdescription` registry document with an identical projection. *(Legacy 1.02 Compatibility)* + +## 16 NodeSet validation + +The NodeSet, CSV and Annex A are generated from `tools/build_model.py` (from the in-code registry model and the pinned `legacy/` sources); they shall not be hand-edited. `tools/validate_local.py` checks XML well-formedness, unique NodeIds (additive registry ids in the 64000+ block, incorporated 1.02 ids in the preserved 1..172 range), CSV↔NodeSet consistency, that every reference resolves against the own namespace, the loaded xRegistry base `NodeIds.csv` and (when the gitignored `tools/ref/UA.NodeIds.csv` aid is present) the base UA ids, that each type carries a `HasSubtype` inverse and each Structure its encodings, that the well-known `WoTRegistry` instance is a component and `HasNotifier` target of the `Server` object with `EventNotifier` set, and that the registry and document types generate the required events. It additionally **proves the 1.02 preservation**: the first 172 CSV rows match the pinned `legacy/WotConnection.csv` exactly (every NodeId and NodeClass), every concrete legacy id is present with its pinned NodeClass while reserved ids are not emitted, the required 1.02 symbols and the callable well-known `WoTAssetConnectionManagement` are present, the management/upload surface carries `ReleaseStatus="Deprecated"`, and the combined NodeSet declares the single `http://opcfoundation.org/UA/WoT-Con/` namespace at model version 1.1.0. Finally it confirms the generated Annex A is embedded verbatim in this document. + +--- + + + +## Annex A — Information model + +This annex is the normative node reference. It is generated from `tools/build_model.py` and always matches `Opc.Ua.WoTCon.NodeSet2.xml`. It documents one combined model in the companion namespace `http://opcfoundation.org/UA/WoT-Con/` (namespace index `2` in this NodeSet, after the required `http://opcfoundation.org/UA/xRegistry/` base model at index `1`). The additive **WoT Connectivity 1.1** registry types **extend the abstract [OPC UA — xRegistry](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md) base types** (`RegistryType`/`GroupType`/`ResourceType`) and use provisional NodeIds in the `64000+` block (final IDs are assigned by the OPC Foundation). The incorporated **OPC 10100-1 v1.02** legacy model is preserved unchanged at its published NodeIds `1..172` and is documented, with its `Deprecated` release status, under *Legacy model* below. The **Declared in** column marks members inherited from a supertype. + +### Type overview + +| NodeId | BrowseName | NodeClass | Subtype of | +|---|---|---|---| +| ns=2;i=64000 | [WoTRegistryType](#type-WoTRegistryType) | ObjectType | [RegistryType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-RegistryType) | +| ns=2;i=64001 | [ThingDescriptionGroupType](#type-ThingDescriptionGroupType) | ObjectType | [GroupType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-GroupType) | +| ns=2;i=64002 | [ThingModelGroupType](#type-ThingModelGroupType) | ObjectType | [GroupType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-GroupType) | +| ns=2;i=64003 | [WoTDocumentType](#type-WoTDocumentType) | ObjectType | [ResourceType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-ResourceType) | +| ns=2;i=64004 | [ThingDescriptionFileType](#type-ThingDescriptionFileType) | ObjectType | [WoTDocumentType](#type-WoTDocumentType) | +| ns=2;i=64005 | [ThingModelFileType](#type-ThingModelFileType) | ObjectType | [WoTDocumentType](#type-WoTDocumentType) | +| ns=2;i=64006 | [WoTBindingType](#type-WoTBindingType) | ObjectType | [BaseObjectType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) | +| ns=2;i=64010 | [WoTResourceEventType](#type-WoTResourceEventType) | ObjectType | [BaseEventType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.4.2) | +| ns=2;i=64011 | [WoTValidationFailureEventType](#type-WoTValidationFailureEventType) | ObjectType | [WoTResourceEventType](#type-WoTResourceEventType) | +| ns=2;i=64012 | [WoTLoadFailureEventType](#type-WoTLoadFailureEventType) | ObjectType | [WoTResourceEventType](#type-WoTResourceEventType) | +| ns=2;i=64013 | [WoTBindingFailureEventType](#type-WoTBindingFailureEventType) | ObjectType | [WoTResourceEventType](#type-WoTResourceEventType) | +| ns=2;i=64014 | [WoTRefreshCompletedEventType](#type-WoTRefreshCompletedEventType) | ObjectType | [BaseEventType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.4.2) | +| ns=2;i=64020 | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64021 | [WoTLoadStateEnum](#type-WoTLoadStateEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64022 | [WoTRefreshModeEnum](#type-WoTRefreshModeEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64023 | [WoTAtomicityEnum](#type-WoTAtomicityEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64024 | [WoTDeletePolicyEnum](#type-WoTDeletePolicyEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64025 | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64026 | [WoTPhaseEnum](#type-WoTPhaseEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64027 | [WoTBindingCapabilityEnum](#type-WoTBindingCapabilityEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64040 | [WoTValidationOutcomeDataType](#type-WoTValidationOutcomeDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64041 | [WoTBindingCapabilityDataType](#type-WoTBindingCapabilityDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64042 | [WoTRefreshOptionsDataType](#type-WoTRefreshOptionsDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64043 | [WoTResourceSelectorDataType](#type-WoTResourceSelectorDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64044 | [WoTResourceLoadResultDataType](#type-WoTResourceLoadResultDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64045 | [WoTRefreshSummaryDataType](#type-WoTRefreshSummaryDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64046 | [WoTDependencyDataType](#type-WoTDependencyDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64060 | [HasWoTProjection](#type-HasWoTProjection) | ReferenceType | [NonHierarchicalReferences](https://reference.opcfoundation.org/specs/OPC-10000-5/11.3) | + +### Object types + + + +#### WoTRegistryType (ns=2;i=64000) + +*Inherits from:* [RegistryType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-RegistryType) + +The WoT Connectivity 1.1 registry root - an xRegistry RegistryType (a FolderType) that holds ThingDescriptionGroupType and ThingModelGroupType groups. The stored Thing Description / Thing Model files and their versions are canonical; the projected AddressSpace (types from Thing Models, instances from Thing Descriptions) is derived code-behind. Exposed as a well-known WoTRegistry object under the Server object (i=2253). Adds registry-wide refresh, generation and validation-policy state and the Refresh Method. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| AutoRefresh | Variable | Boolean | Optional | WoTRegistryType | True if the registry automatically re-projects stored documents (per RefreshMode); false if only explicit Refresh calls re-project. | +| RefreshMode | Variable | [WoTRefreshModeEnum](#type-WoTRefreshModeEnum) | Optional | WoTRegistryType | How automatic refresh is triggered when AutoRefresh is true. | +| RefreshInterval | Variable | Duration | Optional | WoTRegistryType | The interval used when RefreshMode is Periodic. | +| RefreshGeneration | Variable | UInt32 | Mandatory | WoTRegistryType | The current committed projection generation; incremented on every committed refresh. Materialized nodes carry the generation in their NodeVersion for correlation. | +| LastRefreshTime | Variable | DateTime | Optional | WoTRegistryType | UTC time of the last completed refresh. | +| LastRefreshSummary | Variable | [WoTRefreshSummaryDataType](#type-WoTRefreshSummaryDataType) | Optional | WoTRegistryType | An immutable snapshot summarizing the last completed refresh. | +| DefaultAtomicity | Variable | [WoTAtomicityEnum](#type-WoTAtomicityEnum) | Optional | WoTRegistryType | The commit granularity applied when a Refresh omits an explicit atomicity. | +| DeletePolicy | Variable | [WoTDeletePolicyEnum](#type-WoTDeletePolicyEnum) | Optional | WoTRegistryType | The default policy for treating dependents on unload/delete. | +| ValidateFormat | Variable | Boolean | Optional | WoTRegistryType | Registry-wide default: validate document format on ingest/refresh. | +| ValidateCompatibility | Variable | Boolean | Optional | WoTRegistryType | Registry-wide default: validate version compatibility on ingest/refresh. | +| StrictValidation | Variable | Boolean | Optional | WoTRegistryType | If true, a validation warning is treated as a failure. | +| VocabularyVersion | Variable | String | Optional | WoTRegistryType | The version-pinned WoT Binding JSON-LD vocabulary this registry validates and projects against. | +| SelectedBindings | Variable | [WoTBindingCapabilityDataType](#type-WoTBindingCapabilityDataType)\[\] | Optional | WoTRegistryType | An immutable snapshot array of the protocol bindings currently selected/active registry-wide. | +| SupportedBindings | Object | | Optional | WoTRegistryType | A folder of browseable WoTBindingType binding descriptors the server can realize (the live, per-field form of the selected-bindings snapshot). | +| | Object | | OptionalPlaceholder | WoTRegistryType | A Thing Description Group held by this registry (constrained to the ThingDescriptionGroupType subtype). | +| | Object | | OptionalPlaceholder | WoTRegistryType | A Thing Model Group held by this registry (constrained to the ThingModelGroupType subtype). | +| Refresh | Method | | Optional | WoTRegistryType | Re-project selected stored documents into the AddressSpace. Idempotent: a document whose content digest is unchanged is reported Unchanged and not re-materialized unless Options.Force is set. Projects into a shadow generation and switches atomically per Options.Atomicity; superseded generations use the implementation's documented graceful or immediate retirement policy. If ExpectedGeneration is non-zero and does not equal RefreshGeneration, the call fails with Bad_InvalidState and changes nothing (optimistic concurrency). An empty Selection selects the whole registry. | + +*Generates events:* [WoTRefreshCompletedEventType](#type-WoTRefreshCompletedEventType) + + + +#### ThingDescriptionGroupType (ns=2;i=64001) + +*Inherits from:* [GroupType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-GroupType) + +An xRegistry GroupType that collects related ThingDescriptionFileType resources (a Thing Description Group per the WoT xRegistry model). Adds the group-level format/compatibility validation policy. Its placeholder constrains members to the Thing Description subtype. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| ValidateFormat | Variable | Boolean | Optional | ThingDescriptionGroupType | Group-level policy: validate Thing Description format (WoT-TD/1.1) on ingest. | +| ValidateCompatibility | Variable | Boolean | Optional | ThingDescriptionGroupType | Group-level policy: validate version compatibility on ingest. | +| ConsistentFormat | Variable | Boolean | Optional | ThingDescriptionGroupType | Group-level policy: require all versions of a resource to share one format. | +| | Object | | OptionalPlaceholder | ThingDescriptionGroupType | A Thing Description resource held by this group (constrained to the ThingDescriptionFileType subtype). | + + + +#### ThingModelGroupType (ns=2;i=64002) + +*Inherits from:* [GroupType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-GroupType) + +An xRegistry GroupType that collects related ThingModelFileType resources (a Thing Model Group per the WoT xRegistry model). Adds the group-level format/compatibility validation policy. Its placeholder constrains members to the Thing Model subtype. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| ValidateFormat | Variable | Boolean | Optional | ThingModelGroupType | Group-level policy: validate Thing Model format (WoT-TM/1.1) on ingest. | +| ValidateCompatibility | Variable | Boolean | Optional | ThingModelGroupType | Group-level policy: validate version compatibility on ingest. | +| ConsistentFormat | Variable | Boolean | Optional | ThingModelGroupType | Group-level policy: require all versions of a resource to share one format. | +| | Object | | OptionalPlaceholder | ThingModelGroupType | A Thing Model resource held by this group (constrained to the ThingModelFileType subtype). | + + + +#### WoTDocumentType (ns=2;i=64003) *(abstract)* + +*Inherits from:* [ResourceType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-ResourceType) + +The abstract base of a stored WoT document resource - an xRegistry ResourceType (a FileType) whose content bytes are the JSON-LD document, read/written with the inherited Open/Read/Write/Close Methods. Adds the derived-projection metadata (load state, desired/active version, validation and compatibility outcomes, content digest, materialized-node count and root, selected bindings) and the Validate, SetEnabled and SetDefaultVersion Methods. Concrete subtypes fix the document kind. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| DocumentKind | Variable | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | Mandatory | WoTDocumentType | Whether this document is a Thing Description or a Thing Model. Fixed by the concrete subtype. | +| Enabled | Variable | Boolean | Mandatory | WoTDocumentType | The desired enabled state: true requests that the document be validated and projected; false requests unload. | +| LoadState | Variable | [WoTLoadStateEnum](#type-WoTLoadStateEnum) | Mandatory | WoTDocumentType | The actual lifecycle state of this document's derived projection. | +| DesiredVersionId | Variable | String | Optional | WoTDocumentType | The versionid the operator wants active for this resource (the desired/pinned version). | +| ActiveVersionId | Variable | String | Optional | WoTDocumentType | The versionid whose projection is currently active. | +| IsDefault | Variable | Boolean | Optional | WoTDocumentType | xRegistry isdefault: true when this version is the resource's default (sticky) version. | +| Ancestor | Variable | String | Optional | WoTDocumentType | xRegistry ancestor: the versionid this version derives from (version lineage). | +| Compatibility | Variable | String | Optional | WoTDocumentType | The compatibility policy all versions of this resource adhere to (for example NONE, BACKWARD, FULL). | +| AutoRefresh | Variable | Boolean | Optional | WoTDocumentType | Per-document override of the registry AutoRefresh setting. | +| RefreshGeneration | Variable | UInt32 | Optional | WoTDocumentType | The registry generation at which this document was last projected. | +| LastRefreshTime | Variable | DateTime | Optional | WoTDocumentType | UTC time this document was last projected. | +| ContentDigest | Variable | ByteString | Optional | WoTDocumentType | The content digest (hash) of the stored document bytes; used to make refresh idempotent. | +| ValidationOutcome | Variable | [WoTValidationOutcomeDataType](#type-WoTValidationOutcomeDataType) | Optional | WoTDocumentType | An immutable snapshot of this document's format and compatibility validation result. | +| MaterializedNodeCount | Variable | UInt32 | Optional | WoTDocumentType | The number of AddressSpace nodes materialized from this document's active projection. | +| RootNodeId | Variable | [NodeId](https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.1) | Optional | WoTDocumentType | The root node of this document's active projection (the type or instance root). | +| SelectedBindings | Variable | [WoTBindingCapabilityDataType](#type-WoTBindingCapabilityDataType)\[\] | Optional | WoTDocumentType | An immutable snapshot array of the protocol bindings selected for this document's forms. | +| Validate | Method | | Optional | WoTDocumentType | Validate the stored document (format and, when enabled, compatibility) without changing its projection. Returns the outcome snapshot; also refreshes the ValidationOutcome Property. | +| SetEnabled | Method | | Optional | WoTDocumentType | Set the desired Enabled state of this document. Enabling requests validation and projection; disabling requests unload per the registry DeletePolicy. If ExpectedEpoch is non-zero and does not equal the resource's current Epoch the call fails with Bad_InvalidState and changes nothing. | +| SetDefaultVersion | Method | | Optional | WoTDocumentType | Make a specific version of this resource its default (sticky) version, so that resolvers selecting the resource without a versionid resolve to it. If ExpectedEpoch is non-zero and does not equal the resource's current Epoch the call fails with Bad_InvalidState and changes nothing. | + +*Generates events:* [WoTValidationFailureEventType](#type-WoTValidationFailureEventType), [WoTLoadFailureEventType](#type-WoTLoadFailureEventType), [WoTBindingFailureEventType](#type-WoTBindingFailureEventType) + + + +#### ThingDescriptionFileType (ns=2;i=64004) + +*Inherits from:* [WoTDocumentType](#type-WoTDocumentType) + +A concrete WoTDocumentType whose content is a W3C WoT Thing Description (WoT-TD/1.1, application/td+json). Projects to OPC UA instances: affordances become Variables, Methods and event sources; forms become binder plans. Adds the Thing instance identity (ThingId, base URI) and the link to the Thing Model it derives from. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| ThingId | Variable | String | Optional | ThingDescriptionFileType | The Thing Description id (a URI/URN identifying the concrete Thing instance). | +| ThingTitle | Variable | String | Optional | ThingDescriptionFileType | The Thing Description human-readable title. | +| BaseUri | Variable | String | Optional | ThingDescriptionFileType | The Thing Description base URI used to resolve relative form hrefs. | +| ModelReference | Variable | String | Optional | ThingDescriptionFileType | The xid or href of the Thing Model this Thing Description derives from (links rel=type), when present. | + + + +#### ThingModelFileType (ns=2;i=64005) + +*Inherits from:* [WoTDocumentType](#type-WoTDocumentType) + +A concrete WoTDocumentType whose content is a W3C WoT Thing Model (WoT-TM/1.1, application/tm+json). Projects to OPC UA types: it materializes an ObjectType or VariableType and the affordance member declarations and modelling rules. Adds the derived type NodeId and model version. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| ModelTitle | Variable | String | Optional | ThingModelFileType | The Thing Model human-readable title. | +| ModelVersion | Variable | String | Optional | ThingModelFileType | The Thing Model version (WoT version.model), when present. | +| DerivedTypeNodeId | Variable | [NodeId](https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.1) | Optional | ThingModelFileType | The ObjectType or VariableType materialized from this Thing Model. | + + + +#### WoTBindingType (ns=2;i=64006) + +*Inherits from:* [BaseObjectType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) + +A browseable protocol-binding descriptor: the live, per-field representation of one W3C WoT protocol binding the server can realize (its URI, title, version-pinned W3C document, draft maturity, enabled state, content types and a capability snapshot). Selected/active binding sets are additionally exposed as immutable WoTBindingCapabilityDataType array snapshots. Policy and identity are browseable; no credentials or secrets are ever exposed here. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| BindingUri | Variable | String | Mandatory | WoTBindingType | The WoT protocol-binding vocabulary URI this descriptor represents. | +| Title | Variable | String | Optional | WoTBindingType | Human-readable binding title. | +| ProfileVersion | Variable | String | Optional | WoTBindingType | The version-pinned W3C binding document version. | +| DraftMaturity | Variable | String | Optional | WoTBindingType | The W3C maturity of the pinned binding document (for example WD, CR, PR, REC). | +| Enabled | Variable | Boolean | Optional | WoTBindingType | True if the server currently realizes forms of this binding. | +| ContentTypes | Variable | String\[\] | Optional | WoTBindingType | The content types this binding produces/consumes. | +| Capabilities | Variable | [WoTBindingCapabilityDataType](#type-WoTBindingCapabilityDataType) | Optional | WoTBindingType | An immutable capability snapshot for this binding. | + +### Event types + + + +#### WoTResourceEventType (ns=2;i=64010) *(abstract)* + +*Subtype of:* [BaseEventType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.4.2) + +The common base event for a WoT resource lifecycle notification. Carries the identity of the affected resource/version, the document kind, the refresh generation, the phase reached and the outcome. Abstract; servers emit one of its concrete subtypes. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| Xid | String | Mandatory | WoTResourceEventType | The xRegistry xid of the affected resource/version. | +| ResourceId | String | Mandatory | WoTResourceEventType | The resourceid of the affected resource. | +| VersionId | String | Mandatory | WoTResourceEventType | The versionid of the affected version. | +| DocumentKind | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | Mandatory | WoTResourceEventType | Whether the document is a Thing Description or a Thing Model. | +| Generation | UInt32 | Mandatory | WoTResourceEventType | The refresh generation the notification relates to. | +| Phase | [WoTPhaseEnum](#type-WoTPhaseEnum) | Mandatory | WoTResourceEventType | The phase reached (the failing phase on a failure event). | +| Outcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | Mandatory | WoTResourceEventType | The outcome the notification reports. | + + + +#### WoTValidationFailureEventType (ns=2;i=64011) + +*Subtype of:* [WoTResourceEventType](#type-WoTResourceEventType) + +Raised when a document fails format or compatibility validation. The failing resource is the event source; the stored document is retained and any previous valid projection stays active. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| ValidationOutcome | [WoTValidationOutcomeDataType](#type-WoTValidationOutcomeDataType) | Mandatory | WoTValidationFailureEventType | The full validation outcome snapshot for the failure. | + + + +#### WoTLoadFailureEventType (ns=2;i=64012) + +*Subtype of:* [WoTResourceEventType](#type-WoTResourceEventType) + +Raised when a validated document fails to project (materialize) into the AddressSpace, or when its shadow generation cannot be activated. The failing resource is the event source. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| LoadState | [WoTLoadStateEnum](#type-WoTLoadStateEnum) | Mandatory | WoTLoadFailureEventType | The load state after the failed projection/activation. | +| FailedNodeId | [NodeId](https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.1) | Mandatory | WoTLoadFailureEventType | The node whose materialization failed, if identifiable. | +| Reason | String | Mandatory | WoTLoadFailureEventType | Human-readable failure reason. | + + + +#### WoTBindingFailureEventType (ns=2;i=64013) + +*Subtype of:* [WoTResourceEventType](#type-WoTResourceEventType) + +Raised when a form cannot be bound to its protocol binding (unknown binding, unsupported operation or a runtime binder error). The failing resource is the event source. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| BindingUri | String | Mandatory | WoTBindingFailureEventType | The binding URI that could not be bound. | +| Reason | String | Mandatory | WoTBindingFailureEventType | Human-readable binding failure reason. | + + + +#### WoTRefreshCompletedEventType (ns=2;i=64014) + +*Subtype of:* [BaseEventType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.4.2) + +Raised by the registry when a Refresh completes (including automatic refreshes). Carries the refresh summary and the committed generation. The registry object is the event source. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| Summary | [WoTRefreshSummaryDataType](#type-WoTRefreshSummaryDataType) | Mandatory | WoTRefreshCompletedEventType | The refresh summary snapshot. | +| RequestId | String | Mandatory | WoTRefreshCompletedEventType | The caller-supplied request identifier echoed from the Refresh call. | +| Generation | UInt32 | Mandatory | WoTRefreshCompletedEventType | The committed generation. | + +### DataTypes + + + +#### WoTDocumentKindEnum (ns=2;i=64020) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The kind of WoT document a resource carries: a Thing Description (a concrete instance) or a Thing Model (a reusable type template). + +| Name | Value | Description | +|---|---|---| +| ThingDescription | 0 | A W3C WoT Thing Description (WoT-TD/1.1); projects to OPC UA instances. | +| ThingModel | 1 | A W3C WoT Thing Model (WoT-TM/1.1); projects to OPC UA types. | + + + +#### WoTLoadStateEnum (ns=2;i=64021) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The lifecycle state of a WoT document's derived projection in the AddressSpace. The registry file always remains stored; this enum reflects only the state of the code-behind projection. + +| Name | Value | Description | +|---|---|---| +| Unloaded | 0 | Stored but not projected into the AddressSpace. | +| Validating | 1 | Format and compatibility validation is in progress. | +| Loading | 2 | The projection is being materialized under a shadow generation. | +| Active | 3 | The projection is committed and serving as the active generation. | +| Failed | 4 | Validation or projection failed; the last valid projection (if any) stays active. | +| Superseded | 5 | A newer generation has replaced this one; awaiting the configured retirement policy. | +| Retiring | 6 | Graceful retirement is waiting for monitored items and requests to drain. | +| Retired | 7 | The projection has been removed from the AddressSpace. | + + + +#### WoTRefreshModeEnum (ns=2;i=64022) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +How a registry or document triggers refresh of its derived projection. + +| Name | Value | Description | +|---|---|---| +| Manual | 0 | Only an explicit Refresh Method call re-projects. | +| Periodic | 1 | The registry re-projects on a fixed interval (RefreshInterval). | +| EventDriven | 2 | The registry re-projects when a stored document changes (write/CloseAndUpdate). | +| Scheduled | 3 | The registry re-projects on an implementation-defined schedule. | + + + +#### WoTAtomicityEnum (ns=2;i=64023) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The commit granularity applied when a refresh projects one or more documents. + +| Name | Value | Description | +|---|---|---| +| PerResource | 0 | Each resource commits independently; a failure isolates to that resource. | +| PerGroup | 1 | All resources of a group commit together or not at all. | +| PerClosure | 2 | A document and its full dependency closure (DAG) commit atomically. | +| PerRegistry | 3 | All selected documents commit as a single all-or-nothing transaction. | + + + +#### WoTDeletePolicyEnum (ns=2;i=64024) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +How the registry treats dependents when a document version is unloaded or deleted. + +| Name | Value | Description | +|---|---|---| +| Reject | 0 | Reject the operation while any other loaded document still depends on it. | +| Retire | 1 | Retire the projection but keep the stored document for dependents to resolve. | +| Cascade | 2 | Unload dependents that resolve only through this document. | +| Force | 3 | Force-unload the projection even while dependents remain, marking them Failed. | + + + +#### WoTOutcomeEnum (ns=2;i=64025) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The outcome of a validation, projection or refresh operation on a document or the registry. + +| Name | Value | Description | +|---|---|---| +| Success | 0 | The operation completed and changed the projection. | +| Unchanged | 1 | The operation was idempotent; the content digest matched and nothing changed. | +| Warning | 2 | The operation completed with non-fatal warnings. | +| Skipped | 3 | The operation was not applicable and was skipped. | +| Rejected | 4 | The operation was rejected by policy (for example concurrency or delete policy). | +| Failed | 5 | The operation failed; the previous valid projection (if any) remains active. | + + + +#### WoTPhaseEnum (ns=2;i=64026) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The processing phase a document reached, used to locate where an outcome was produced. + +| Name | Value | Description | +|---|---|---| +| Fetch | 0 | Reading document bytes and resolving registry-scoped context/schema references. | +| Parse | 1 | Parsing the JSON-LD document. | +| FormatValidation | 2 | Validating the document against its WoT-TD/WoT-TM format. | +| CompatibilityValidation | 3 | Validating the version against the resource compatibility policy. | +| DependencyResolution | 4 | Resolving the dependency closure (tm:extends, tm:ref, links rel=type). | +| Projection | 5 | Materializing types/instances into a shadow generation. | +| Activation | 6 | Committing the shadow generation as active. | +| Retirement | 7 | Applying the configured graceful or immediate retirement policy. | + + + +#### WoTBindingCapabilityEnum (ns=2;i=64027) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +A single interaction operation a protocol binding supports, aligned with the WoT form op vocabulary. + +| Name | Value | Description | +|---|---|---| +| ReadProperty | 0 | Read a property affordance. | +| WriteProperty | 1 | Write a property affordance. | +| ObserveProperty | 2 | Observe (subscribe to) a property affordance. | +| InvokeAction | 3 | Invoke an action affordance. | +| SubscribeEvent | 4 | Subscribe to an event affordance. | +| UnsubscribeEvent | 5 | Unsubscribe from an event affordance. | + + + +#### WoTValidationOutcomeDataType (ns=2;i=64040) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable snapshot of a document's format and compatibility validation result. Read as a single Variant value; a new snapshot is produced on each validation and never mutated in place. + +| Field | DataType | Description | +|---|---|---| +| FormatValidated | Boolean | True if format validation was performed. | +| FormatOutcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | Outcome of format validation (WoT-TD/WoT-TM conformance). | +| FormatReason | String | Human-readable reason for the format outcome (empty on success). | +| CompatibilityValidated | Boolean | True if compatibility validation was performed. | +| CompatibilityOutcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | Outcome of compatibility validation against the resource policy. | +| CompatibilityReason | String | Human-readable reason for the compatibility outcome (empty on success). | +| CompatibilityPolicy | String | The compatibility policy in force (for example NONE, BACKWARD, FULL). | +| ValidatedAt | DateTime | UTC time the validation completed. | +| VocabularyVersion | String | The pinned WoT Binding JSON-LD vocabulary version used for validation. | + + + +#### WoTBindingCapabilityDataType (ns=2;i=64041) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable snapshot of a protocol binding's identity, version-pinned W3C document, maturity and supported operations. Held as an array element only for immutable snapshots; browseable binding objects (WoTBindingType) carry the live, per-field form. + +| Field | DataType | Description | +|---|---|---| +| BindingUri | String | The WoT protocol-binding vocabulary URI (for example the OPC UA, HTTP or Modbus binding). | +| Title | String | Human-readable binding title. | +| ProfileVersion | String | The version-pinned W3C binding document version this capability snapshot was built against. | +| DraftMaturity | String | The W3C maturity of the pinned binding document (for example WD, CR, PR, REC). | +| Capabilities | [WoTBindingCapabilityEnum](#type-WoTBindingCapabilityEnum)\[\] | The interaction operations this binding supports. | +| ContentTypes | String\[\] | The content types this binding produces/consumes. | + + + +#### WoTRefreshOptionsDataType (ns=2;i=64042) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +Immutable options controlling a single Refresh invocation. + +| Field | DataType | Description | +|---|---|---| +| Atomicity | [WoTAtomicityEnum](#type-WoTAtomicityEnum) | Commit granularity for this refresh. | +| Force | Boolean | Re-project even when the content digest is unchanged. | +| DryRun | Boolean | Validate and compute results without committing any projection change. | +| IncludeDependents | Boolean | Also refresh documents that depend on the selected documents. | +| DeletePolicy | [WoTDeletePolicyEnum](#type-WoTDeletePolicyEnum) | How to treat dependents when a selected document is unloaded/retired. | +| MaxParallelism | UInt32 | Maximum number of documents projected concurrently; 0 lets the server decide. | +| Timeout | Duration | Overall time budget for the refresh; 0 lets the server decide. | + + + +#### WoTResourceSelectorDataType (ns=2;i=64043) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable selector identifying which stored documents a Refresh applies to. An empty selector array selects the whole registry. + +| Field | DataType | Description | +|---|---|---| +| Kind | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | Restrict to Thing Descriptions or Thing Models; omit to select both. | +| GroupId | String | Restrict to a group by groupid; empty selects all groups. | +| ResourceId | String | Restrict to a resource by resourceid; empty selects all resources. | +| VersionId | String | Restrict to a version by versionid; empty selects the resource's default version. | +| Xid | String | Select a single entity by its xRegistry xid; overrides the other fields when set. | + + + +#### WoTResourceLoadResultDataType (ns=2;i=64044) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable per-resource result row of a Refresh. Never mutated; the array is a point-in-time snapshot for one generation. + +| Field | DataType | Description | +|---|---|---| +| Xid | String | The xRegistry xid of the affected resource/version. | +| GroupId | String | The groupid of the resource's group. | +| ResourceId | String | The resourceid of the affected resource. | +| VersionId | String | The versionid that was projected. | +| Kind | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | Whether the document is a Thing Description or a Thing Model. | +| Outcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | The per-resource outcome. | +| Phase | [WoTPhaseEnum](#type-WoTPhaseEnum) | The phase the resource reached (the failing phase on failure). | +| LoadState | [WoTLoadStateEnum](#type-WoTLoadStateEnum) | The resulting load state of the projection. | +| Generation | UInt32 | The refresh generation this result belongs to. | +| MaterializedNodeCount | UInt32 | Number of AddressSpace nodes materialized for this resource. | +| RootNodeId | [NodeId](https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.1) | The root node of the materialized projection, if any. | +| ContentDigest | ByteString | The content digest (hash) of the projected document bytes. | +| Message | String | Human-readable detail for the outcome. | + + + +#### WoTRefreshSummaryDataType (ns=2;i=64045) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable summary of one Refresh invocation, also carried by the WoTRefreshCompletedEventType and cached on the registry as LastRefreshSummary. + +| Field | DataType | Description | +|---|---|---| +| RequestId | String | The caller-supplied request identifier echoed back for correlation. | +| Generation | UInt32 | The committed refresh generation (0 on a dry run or full failure). | +| Outcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | The overall outcome of the refresh. | +| Atomicity | [WoTAtomicityEnum](#type-WoTAtomicityEnum) | The commit granularity that was applied. | +| StartTime | DateTime | UTC start time of the refresh. | +| EndTime | DateTime | UTC end time of the refresh. | +| Total | UInt32 | Total number of resources considered. | +| Succeeded | UInt32 | Number of resources that changed successfully. | +| Unchanged | UInt32 | Number of resources that were idempotently unchanged. | +| Failed | UInt32 | Number of resources that failed. | +| Skipped | UInt32 | Number of resources skipped by selection or policy. | +| Retired | UInt32 | Number of superseded generations retired. | + + + +#### WoTDependencyDataType (ns=2;i=64046) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable edge of the document dependency DAG, used to describe closures in results and diagnostics. + +| Field | DataType | Description | +|---|---|---| +| SourceXid | String | The xid of the dependent document. | +| TargetXid | String | The xid of the document depended upon (empty if unresolved). | +| TargetUri | String | The raw href/URI of the dependency as authored in the document. | +| RefType | String | The dependency kind (for example tm:extends, tm:ref, links.rel=type). | +| Resolved | Boolean | True if the dependency resolved to a stored document. | + +### Reference types + + + +| NodeId | BrowseName | InverseName | Subtype of | Description | +|---|---|---|---|---| +| ns=2;i=64060 | HasWoTProjection | WoTProjectionOf | [NonHierarchicalReferences](https://reference.opcfoundation.org/specs/OPC-10000-5/11.3) | Links a stored WoT document resource (source) to the root node of its derived AddressSpace projection (target). Used to correlate materialized nodes and their NodeVersion with the canonical document, and to find the document behind a projected node. | + +### Methods + +| Method | Owning type | Input arguments | Output arguments | +|---|---|---|---| +| Refresh | [WoTRegistryType](#type-WoTRegistryType) | Selection, Options, ExpectedGeneration, RequestId | Summary, Results, NewGeneration | +| Validate | [WoTDocumentType](#type-WoTDocumentType) | (none) | Outcome | +| SetEnabled | [WoTDocumentType](#type-WoTDocumentType) | Enabled, ExpectedEpoch | (none) | +| SetDefaultVersion | [WoTDocumentType](#type-WoTDocumentType) | VersionId, ExpectedEpoch | (none) | + +### Well-known instances + +| BrowseName | NodeId | TypeDefinition | Note | +|---|---|---|---| +| WoTRegistry | ns=2;i=64100 | [WoTRegistryType](#type-WoTRegistryType) | The server-wide WoT Connectivity 1.1 registry, a well-known component of the Server object. Its stored Thing Description / Thing Model files are canonical; the projected AddressSpace is derived. It is the notifier for the WoT resource lifecycle events raised by its groups and resources. | + +### Legacy model (OPC 10100-1 v1.02 — preserved, deprecated) + +The published OPC 10100-1 v1.02 WoT Connectivity model is incorporated into this combined NodeSet unchanged, at its exact published NodeIds (`1..172`) and NodeClasses (preserved from the pinned `legacy/WotConnection.csv`). Because the additive registry supersedes it, the whole management/upload surface carries `ReleaseStatus="Deprecated"` — it is deprecated, not removed, so existing 1.02 clients keep working. The `WoTAssetConnectionManagement` object remains at its published NodeId and callable. Method signatures are unchanged and are listed in §13.1. + + + + + + + +| NodeId | BrowseName | NodeClass | Subtype of | Release status | +|---|---|---|---|---| +| ns=2;i=1 | WoTAssetConnectionManagementType | ObjectType | [BaseObjectType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) | Deprecated | +| ns=2;i=42 | IWoTAssetType | ObjectType | [BaseInterfaceType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) | Deprecated | +| ns=2;i=105 | WoTAssetConfigurationType | ObjectType | [BaseInterfaceType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) | Deprecated | +| ns=2;i=110 | WoTAssetFileType | ObjectType | [FileType](https://reference.opcfoundation.org/specs/OPC-10000-20/4.2) | Deprecated | +| ns=2;i=142 | HasWoTComponent | ReferenceType | [HasComponent](https://reference.opcfoundation.org/specs/OPC-10000-5/11.3) | Deprecated | + +| Well-known instance | NodeId | TypeDefinition | Release status | +|---|---|---|---| +| WoTAssetConnectionManagement | ns=2;i=31 | [WoTAssetConnectionManagementType](#type-WoTAssetConnectionManagementType) | Deprecated | diff --git a/wot-specs/WoT-Connectivity/Opc.Ua.WoTCon.NodeIds.csv b/wot-specs/WoT-Connectivity/Opc.Ua.WoTCon.NodeIds.csv new file mode 100644 index 0000000..81a748e --- /dev/null +++ b/wot-specs/WoT-Connectivity/Opc.Ua.WoTCon.NodeIds.csv @@ -0,0 +1,307 @@ +WoTAssetConnectionManagementType,1,ObjectType +WoTAssetConnectionManagementType_WoTAssetName_Placeholder,2,Object +WotConNamespaceMetadata_NamespaceFile,3,Object +WotConNamespaceMetadata_NamespaceFile_Size,4,Variable +WotConNamespaceMetadata_NamespaceFile_Writable,5,Variable +WotConNamespaceMetadata_NamespaceFile_UserWritable,6,Variable +WotConNamespaceMetadata_NamespaceFile_OpenCount,7,Variable +WotConNamespaceMetadata_NamespaceFile_MimeType,8,Variable +WotConNamespaceMetadata_NamespaceFile_MaxByteStringLength,9,Variable +WotConNamespaceMetadata_NamespaceFile_LastModifiedTime,10,Variable +WotConNamespaceMetadata_NamespaceFile_Open,11,Method +WotConNamespaceMetadata_NamespaceFile_Open_InputArguments,12,Variable +WotConNamespaceMetadata_NamespaceFile_Open_OutputArguments,13,Variable +WotConNamespaceMetadata_NamespaceFile_Close,14,Method +WotConNamespaceMetadata_NamespaceFile_Close_InputArguments,15,Variable +WotConNamespaceMetadata_NamespaceFile_Read,16,Method +WotConNamespaceMetadata_NamespaceFile_Read_InputArguments,17,Variable +WotConNamespaceMetadata_NamespaceFile_Read_OutputArguments,18,Variable +WotConNamespaceMetadata_NamespaceFile_Write,19,Method +WotConNamespaceMetadata_NamespaceFile_Write_InputArguments,20,Variable +WotConNamespaceMetadata_NamespaceFile_GetPosition,21,Method +WotConNamespaceMetadata_NamespaceFile_GetPosition_InputArguments,22,Variable +WotConNamespaceMetadata_NamespaceFile_GetPosition_OutputArguments,23,Variable +WotConNamespaceMetadata_NamespaceFile_SetPosition,24,Method +WotConNamespaceMetadata_NamespaceFile_SetPosition_InputArguments,25,Variable +WoTAssetConnectionManagementType_CreateAsset,26,Method +WoTAssetConnectionManagementType_CreateAsset_InputArguments,27,Variable +WoTAssetConnectionManagementType_CreateAsset_OutputArguments,28,Variable +WoTAssetConnectionManagementType_DeleteAsset,29,Method +WoTAssetConnectionManagementType_DeleteAsset_InputArguments,30,Variable +WoTAssetConnectionManagement,31,Object +WoTAssetConnectionManagement_CreateAsset,32,Method +WoTAssetConnectionManagement_CreateAsset_InputArguments,33,Variable +WoTAssetConnectionManagement_CreateAsset_OutputArguments,34,Variable +WoTAssetConnectionManagement_DeleteAsset,35,Method +WoTAssetConnectionManagement_DeleteAsset_InputArguments,36,Variable +WotConNamespaceMetadata_NamespaceFile_ExportNamespace,37,Method +WotConNamespaceMetadata_ConfigurationVersion,38,Variable +WotConNamespaceMetadata_ModelVersion,39,Variable +WoTAssetConnectionManagementType_SupportedWoTBindings,40,Variable +WoTAssetConnectionManagementType_DiscoverAssets,41,Method +IWoTAssetType,42,ObjectType +IWoTAssetType_WoTFile,43,Object +IWoTAssetType_WoTFile_Size,44,Variable +IWoTAssetType_WoTFile_Writable,45,Variable +IWoTAssetType_WoTFile_UserWritable,46,Variable +IWoTAssetType_WoTFile_OpenCount,47,Variable +WoTAssetConnectionManagementType_DiscoverAssets_OutputArguments,48,Variable +WoTAssetConnectionManagementType_CreateAssetForEndpoint,49,Method +WoTAssetConnectionManagementType_CreateAssetForEndpoint_InputArguments,50,Variable +IWoTAssetType_WoTFile_Open,51,Method +IWoTAssetType_WoTFile_Open_InputArguments,52,Variable +IWoTAssetType_WoTFile_Open_OutputArguments,53,Variable +IWoTAssetType_WoTFile_Close,54,Method +IWoTAssetType_WoTFile_Close_InputArguments,55,Variable +IWoTAssetType_WoTFile_Read,56,Method +IWoTAssetType_WoTFile_Read_InputArguments,57,Variable +IWoTAssetType_WoTFile_Read_OutputArguments,58,Variable +IWoTAssetType_WoTFile_Write,59,Method +IWoTAssetType_WoTFile_Write_InputArguments,60,Variable +IWoTAssetType_WoTFile_GetPosition,61,Method +IWoTAssetType_WoTFile_GetPosition_InputArguments,62,Variable +IWoTAssetType_WoTFile_GetPosition_OutputArguments,63,Variable +IWoTAssetType_WoTFile_SetPosition,64,Method +IWoTAssetType_WoTFile_SetPosition_InputArguments,65,Variable +IWoTAssetType_WoTPropertyName_Placeholder,66,Variable +WotConNamespaceMetadata,67,Object +WotConNamespaceMetadata_NamespaceUri,68,Variable +WotConNamespaceMetadata_NamespaceVersion,69,Variable +WotConNamespaceMetadata_NamespacePublicationDate,70,Variable +WotConNamespaceMetadata_IsNamespaceSubset,71,Variable +WotConNamespaceMetadata_StaticNodeIdTypes,72,Variable +WotConNamespaceMetadata_StaticNumericNodeIdRange,73,Variable +WotConNamespaceMetadata_StaticStringNodeIdPattern,74,Variable +WoTAssetConnectionManagementType_ConnectionTest,75,Method +WoTAssetConnectionManagementType_ConnectionTest_InputArguments,76,Variable +WoTAssetConnectionManagementType_ConnectionTest_OutputArguments,77,Variable +WoTAssetConnectionManagementType_Configuration,78,Object +WoTAssetConnectionManagementType_Configuration_License,79,Variable +WoTAssetConnectionManagement_SupportedWoTBindings,80,Variable +WoTAssetConnectionManagement_DiscoverAssets,81,Method +WoTAssetConnectionManagement_DiscoverAssets_OutputArguments,82,Variable +WoTAssetConnectionManagement_CreateAssetForEndpoint,83,Method +WoTAssetConnectionManagement_CreateAssetForEndpoint_InputArguments,84,Variable +WoTAssetConnectionManagement_ConnectionTest,85,Method +WoTAssetConnectionManagement_ConnectionTest_InputArguments,86,Variable +WoTAssetConnectionManagement_ConnectionTest_OutputArguments,87,Variable +WoTAssetConnectionManagement_Configuration,88,Object +WoTAssetConnectionManagement_Configuration_License,89,Variable +CreateAssetMethodType,90,Method +CreateAssetMethodType_InputArguments,91,Variable +CreateAssetMethodType_OutputArguments,92,Variable +DeleteAssetMethodType,93,Method +DeleteAssetMethodType_InputArguments,94,Variable +DiscoverAssetsMethodType,95,Method +DiscoverAssetsMethodType_OutputArguments,96,Variable +CreateAssetForEndpointMethodType,97,Method +CreateAssetForEndpointMethodType_InputArguments,98,Variable +WotConNamespaceMetadata_DefaultRolePermissions,99,Variable +WotConNamespaceMetadata_DefaultUserRolePermissions,100,Variable +WotConNamespaceMetadata_DefaultAccessRestrictions,101,Variable +ConnectionTestMethodType,102,Method +ConnectionTestMethodType_InputArguments,103,Variable +ConnectionTestMethodType_OutputArguments,104,Variable +WoTAssetConfigurationType,105,ObjectType +IWoTAssetType_WoTFile_CloseAndUpdate,106,Method +IWoTAssetType_WoTFile_CloseAndUpdate_InputArguments,107,Variable +WoTAssetConfigurationType_WoTConfigurationParameterName_Placeholder,108,Variable +WoTAssetConfigurationType_License,109,Variable +WoTAssetFileType,110,ObjectType +WoTAssetFileType_CloseAndUpdate,111,Method +WoTAssetFileType_CloseAndUpdate_InputArguments,112,Variable +IWoTAssetType_WoTFile_MimeType,113,Variable +IWoTAssetType_WoTFile_MaxByteStringLength,114,Variable +WoTAssetType,115,Unspecified +WoTAssetType_WoTFile,116,Unspecified +WoTAssetType_WoTFile_Size,117,Unspecified +WoTAssetType_WoTFile_Writable,118,Unspecified +WoTAssetType_WoTFile_UserWritable,119,Unspecified +WoTAssetType_WoTFile_OpenCount,120,Unspecified +IWoTAssetType_WoTFile_LastModifiedTime,121,Variable +IWoTAssetType_AssetEndpoint,122,Variable +CloseAndUpdateMethodType,123,Method +WoTAssetType_WoTFile_Open,124,Unspecified +WoTAssetType_WoTFile_Open_InputArguments,125,Unspecified +WoTAssetType_WoTFile_Open_OutputArguments,126,Unspecified +WoTAssetType_WoTFile_Close,127,Unspecified +WoTAssetType_WoTFile_Close_InputArguments,128,Unspecified +WoTAssetType_WoTFile_Read,129,Unspecified +WoTAssetType_WoTFile_Read_InputArguments,130,Unspecified +WoTAssetType_WoTFile_Read_OutputArguments,131,Unspecified +WoTAssetType_WoTFile_Write,132,Unspecified +WoTAssetType_WoTFile_Write_InputArguments,133,Unspecified +WoTAssetType_WoTFile_GetPosition,134,Unspecified +WoTAssetType_WoTFile_GetPosition_InputArguments,135,Unspecified +WoTAssetType_WoTFile_GetPosition_OutputArguments,136,Unspecified +WoTAssetType_WoTFile_SetPosition,137,Unspecified +WoTAssetType_WoTFile_SetPosition_InputArguments,138,Unspecified +WoTAssetType_WoTFile_CloseAndUpdate,139,Unspecified +WoTAssetType_WoTFile_CloseAndUpdate_InputArguments,140,Unspecified +WoTAssetType_WoTPropertyName_Placeholder,141,Unspecified +HasWoTComponent,142,ReferenceType +CloseAndUpdateMethodType_InputArguments,143,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile,144,Object +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Size,145,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Writable,146,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_UserWritable,147,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_OpenCount,148,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_MimeType,149,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_MaxByteStringLength,150,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_LastModifiedTime,151,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Open,152,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Open_InputArguments,153,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Open_OutputArguments,154,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Close,155,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Close_InputArguments,156,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Read,157,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Read_InputArguments,158,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Read_OutputArguments,159,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Write,160,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Write_InputArguments,161,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_GetPosition,162,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_GetPosition_InputArguments,163,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_GetPosition_OutputArguments,164,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_SetPosition,165,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_SetPosition_InputArguments,166,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_CloseAndUpdate,167,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_CloseAndUpdate_InputArguments,168,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_AssetEndpoint,169,Variable +WoTAssetConnectionManagementType_CreateAssetForEndpoint_OutputArguments,170,Variable +WoTAssetConnectionManagement_CreateAssetForEndpoint_OutputArguments,171,Variable +CreateAssetForEndpointMethodType_OutputArguments,172,Variable +WoTDocumentKindEnum,64020,DataType +WoTDocumentKindEnum_EnumStrings,64500,Variable +WoTLoadStateEnum,64021,DataType +WoTLoadStateEnum_EnumStrings,64501,Variable +WoTRefreshModeEnum,64022,DataType +WoTRefreshModeEnum_EnumStrings,64502,Variable +WoTAtomicityEnum,64023,DataType +WoTAtomicityEnum_EnumStrings,64503,Variable +WoTDeletePolicyEnum,64024,DataType +WoTDeletePolicyEnum_EnumStrings,64504,Variable +WoTOutcomeEnum,64025,DataType +WoTOutcomeEnum_EnumStrings,64505,Variable +WoTPhaseEnum,64026,DataType +WoTPhaseEnum_EnumStrings,64506,Variable +WoTBindingCapabilityEnum,64027,DataType +WoTBindingCapabilityEnum_EnumStrings,64507,Variable +WoTValidationOutcomeDataType,64040,DataType +WoTValidationOutcomeDataType_DefaultBinary,64508,Object +WoTValidationOutcomeDataType_DefaultJSON,64509,Object +WoTBindingCapabilityDataType,64041,DataType +WoTBindingCapabilityDataType_DefaultBinary,64510,Object +WoTBindingCapabilityDataType_DefaultJSON,64511,Object +WoTRefreshOptionsDataType,64042,DataType +WoTRefreshOptionsDataType_DefaultBinary,64512,Object +WoTRefreshOptionsDataType_DefaultJSON,64513,Object +WoTResourceSelectorDataType,64043,DataType +WoTResourceSelectorDataType_DefaultBinary,64514,Object +WoTResourceSelectorDataType_DefaultJSON,64515,Object +WoTResourceLoadResultDataType,64044,DataType +WoTResourceLoadResultDataType_DefaultBinary,64516,Object +WoTResourceLoadResultDataType_DefaultJSON,64517,Object +WoTRefreshSummaryDataType,64045,DataType +WoTRefreshSummaryDataType_DefaultBinary,64518,Object +WoTRefreshSummaryDataType_DefaultJSON,64519,Object +WoTDependencyDataType,64046,DataType +WoTDependencyDataType_DefaultBinary,64520,Object +WoTDependencyDataType_DefaultJSON,64521,Object +WoTRegistryType,64000,ObjectType +ThingDescriptionGroupType,64001,ObjectType +ThingModelGroupType,64002,ObjectType +WoTDocumentType,64003,ObjectType +ThingDescriptionFileType,64004,ObjectType +ThingModelFileType,64005,ObjectType +WoTBindingType,64006,ObjectType +WoTResourceEventType,64010,ObjectType +WoTValidationFailureEventType,64011,ObjectType +WoTLoadFailureEventType,64012,ObjectType +WoTBindingFailureEventType,64013,ObjectType +WoTRefreshCompletedEventType,64014,ObjectType +HasWoTProjection,64060,ReferenceType +WoTRegistryType_AutoRefresh,64522,Variable +WoTRegistryType_RefreshMode,64523,Variable +WoTRegistryType_RefreshInterval,64524,Variable +WoTRegistryType_RefreshGeneration,64525,Variable +WoTRegistryType_LastRefreshTime,64526,Variable +WoTRegistryType_LastRefreshSummary,64527,Variable +WoTRegistryType_DefaultAtomicity,64528,Variable +WoTRegistryType_DeletePolicy,64529,Variable +WoTRegistryType_ValidateFormat,64530,Variable +WoTRegistryType_ValidateCompatibility,64531,Variable +WoTRegistryType_StrictValidation,64532,Variable +WoTRegistryType_VocabularyVersion,64533,Variable +WoTRegistryType_SelectedBindings,64534,Variable +WoTRegistryType_SupportedBindings,64535,Object +WoTRegistryType_ThingDescriptionGroup,64536,Object +WoTRegistryType_ThingModelGroup,64537,Object +WoTRegistryType_Refresh,64538,Method +WoTRegistryType_Refresh_InputArguments,64539,Variable +WoTRegistryType_Refresh_OutputArguments,64540,Variable +ThingDescriptionGroupType_ValidateFormat,64541,Variable +ThingDescriptionGroupType_ValidateCompatibility,64542,Variable +ThingDescriptionGroupType_ConsistentFormat,64543,Variable +ThingDescriptionGroupType_ThingDescription,64544,Object +ThingModelGroupType_ValidateFormat,64545,Variable +ThingModelGroupType_ValidateCompatibility,64546,Variable +ThingModelGroupType_ConsistentFormat,64547,Variable +ThingModelGroupType_ThingModel,64548,Object +WoTDocumentType_DocumentKind,64549,Variable +WoTDocumentType_Enabled,64550,Variable +WoTDocumentType_LoadState,64551,Variable +WoTDocumentType_DesiredVersionId,64552,Variable +WoTDocumentType_ActiveVersionId,64553,Variable +WoTDocumentType_IsDefault,64554,Variable +WoTDocumentType_Ancestor,64555,Variable +WoTDocumentType_Compatibility,64556,Variable +WoTDocumentType_AutoRefresh,64557,Variable +WoTDocumentType_RefreshGeneration,64558,Variable +WoTDocumentType_LastRefreshTime,64559,Variable +WoTDocumentType_ContentDigest,64560,Variable +WoTDocumentType_ValidationOutcome,64561,Variable +WoTDocumentType_MaterializedNodeCount,64562,Variable +WoTDocumentType_RootNodeId,64563,Variable +WoTDocumentType_SelectedBindings,64564,Variable +WoTDocumentType_Validate,64565,Method +WoTDocumentType_Validate_OutputArguments,64566,Variable +WoTDocumentType_SetEnabled,64567,Method +WoTDocumentType_SetEnabled_InputArguments,64568,Variable +WoTDocumentType_SetDefaultVersion,64569,Method +WoTDocumentType_SetDefaultVersion_InputArguments,64570,Variable +ThingDescriptionFileType_ThingId,64571,Variable +ThingDescriptionFileType_ThingTitle,64572,Variable +ThingDescriptionFileType_BaseUri,64573,Variable +ThingDescriptionFileType_ModelReference,64574,Variable +ThingModelFileType_ModelTitle,64575,Variable +ThingModelFileType_ModelVersion,64576,Variable +ThingModelFileType_DerivedTypeNodeId,64577,Variable +WoTBindingType_BindingUri,64578,Variable +WoTBindingType_Title,64579,Variable +WoTBindingType_ProfileVersion,64580,Variable +WoTBindingType_DraftMaturity,64581,Variable +WoTBindingType_Enabled,64582,Variable +WoTBindingType_ContentTypes,64583,Variable +WoTBindingType_Capabilities,64584,Variable +WoTResourceEventType_Xid,64585,Variable +WoTResourceEventType_ResourceId,64586,Variable +WoTResourceEventType_VersionId,64587,Variable +WoTResourceEventType_DocumentKind,64588,Variable +WoTResourceEventType_Generation,64589,Variable +WoTResourceEventType_Phase,64590,Variable +WoTResourceEventType_Outcome,64591,Variable +WoTValidationFailureEventType_ValidationOutcome,64592,Variable +WoTLoadFailureEventType_LoadState,64593,Variable +WoTLoadFailureEventType_FailedNodeId,64594,Variable +WoTLoadFailureEventType_Reason,64595,Variable +WoTBindingFailureEventType_BindingUri,64596,Variable +WoTBindingFailureEventType_Reason,64597,Variable +WoTRefreshCompletedEventType_Summary,64598,Variable +WoTRefreshCompletedEventType_RequestId,64599,Variable +WoTRefreshCompletedEventType_Generation,64600,Variable +WoTRegistry,64100,Object +WoTRegistry_Refresh,64601,Method +WoTRegistry_Refresh_InputArguments,64602,Variable +WoTRegistry_Refresh_OutputArguments,64603,Variable +WoTRegistry_RegistryId,64604,Variable +WoTRegistry_RefreshGeneration,64605,Variable diff --git a/wot-specs/WoT-Connectivity/Opc.Ua.WoTCon.NodeSet2.xml b/wot-specs/WoT-Connectivity/Opc.Ua.WoTCon.NodeSet2.xml new file mode 100644 index 0000000..f2552d5 --- /dev/null +++ b/wot-specs/WoT-Connectivity/Opc.Ua.WoTCon.NodeSet2.xml @@ -0,0 +1,2746 @@ + + + + + http://opcfoundation.org/UA/xRegistry/ + http://opcfoundation.org/UA/WoT-Con/ + + + + + + + + + i=1 + i=3 + i=5 + i=6 + i=7 + i=9 + i=11 + i=12 + i=13 + i=294 + i=15 + i=17 + i=18 + i=21 + i=290 + i=296 + i=22 + i=29 + i=23751 + i=24 + i=35 + i=37 + i=40 + i=45 + i=46 + i=47 + i=38 + i=17603 + i=41 + i=48 + i=32 + + + WoTDocumentKindEnum + The kind of WoT document a resource carries: a Thing Description (a concrete instance) or a Thing Model (a reusable type template). + WoT Connectivity 1.1 DataTypes + + i=29 + ns=2;i=64500 + + A W3C WoT Thing Description (WoT-TD/1.1); projects to OPC UA instances.A W3C WoT Thing Model (WoT-TM/1.1); projects to OPC UA types. + + + EnumStrings + + i=78 + i=68 + ns=2;i=64020 + + ThingDescriptionThingModel + + + WoTLoadStateEnum + The lifecycle state of a WoT document's derived projection in the AddressSpace. The registry file always remains stored; this enum reflects only the state of the code-behind projection. + WoT Connectivity 1.1 DataTypes + + i=29 + ns=2;i=64501 + + Stored but not projected into the AddressSpace.Format and compatibility validation is in progress.The projection is being materialized under a shadow generation.The projection is committed and serving as the active generation.Validation or projection failed; the last valid projection (if any) stays active.A newer generation has replaced this one; awaiting the configured retirement policy.Graceful retirement is waiting for monitored items and requests to drain.The projection has been removed from the AddressSpace. + + + EnumStrings + + i=78 + i=68 + ns=2;i=64021 + + UnloadedValidatingLoadingActiveFailedSupersededRetiringRetired + + + WoTRefreshModeEnum + How a registry or document triggers refresh of its derived projection. + WoT Connectivity 1.1 DataTypes + + i=29 + ns=2;i=64502 + + Only an explicit Refresh Method call re-projects.The registry re-projects on a fixed interval (RefreshInterval).The registry re-projects when a stored document changes (write/CloseAndUpdate).The registry re-projects on an implementation-defined schedule. + + + EnumStrings + + i=78 + i=68 + ns=2;i=64022 + + ManualPeriodicEventDrivenScheduled + + + WoTAtomicityEnum + The commit granularity applied when a refresh projects one or more documents. + WoT Connectivity 1.1 DataTypes + + i=29 + ns=2;i=64503 + + Each resource commits independently; a failure isolates to that resource.All resources of a group commit together or not at all.A document and its full dependency closure (DAG) commit atomically.All selected documents commit as a single all-or-nothing transaction. + + + EnumStrings + + i=78 + i=68 + ns=2;i=64023 + + PerResourcePerGroupPerClosurePerRegistry + + + WoTDeletePolicyEnum + How the registry treats dependents when a document version is unloaded or deleted. + WoT Connectivity 1.1 DataTypes + + i=29 + ns=2;i=64504 + + Reject the operation while any other loaded document still depends on it.Retire the projection but keep the stored document for dependents to resolve.Unload dependents that resolve only through this document.Force-unload the projection even while dependents remain, marking them Failed. + + + EnumStrings + + i=78 + i=68 + ns=2;i=64024 + + RejectRetireCascadeForce + + + WoTOutcomeEnum + The outcome of a validation, projection or refresh operation on a document or the registry. + WoT Connectivity 1.1 DataTypes + + i=29 + ns=2;i=64505 + + The operation completed and changed the projection.The operation was idempotent; the content digest matched and nothing changed.The operation completed with non-fatal warnings.The operation was not applicable and was skipped.The operation was rejected by policy (for example concurrency or delete policy).The operation failed; the previous valid projection (if any) remains active. + + + EnumStrings + + i=78 + i=68 + ns=2;i=64025 + + SuccessUnchangedWarningSkippedRejectedFailed + + + WoTPhaseEnum + The processing phase a document reached, used to locate where an outcome was produced. + WoT Connectivity 1.1 DataTypes + + i=29 + ns=2;i=64506 + + Reading document bytes and resolving registry-scoped context/schema references.Parsing the JSON-LD document.Validating the document against its WoT-TD/WoT-TM format.Validating the version against the resource compatibility policy.Resolving the dependency closure (tm:extends, tm:ref, links rel=type).Materializing types/instances into a shadow generation.Committing the shadow generation as active.Applying the configured graceful or immediate retirement policy. + + + EnumStrings + + i=78 + i=68 + ns=2;i=64026 + + FetchParseFormatValidationCompatibilityValidationDependencyResolutionProjectionActivationRetirement + + + WoTBindingCapabilityEnum + A single interaction operation a protocol binding supports, aligned with the WoT form op vocabulary. + WoT Connectivity 1.1 DataTypes + + i=29 + ns=2;i=64507 + + Read a property affordance.Write a property affordance.Observe (subscribe to) a property affordance.Invoke an action affordance.Subscribe to an event affordance.Unsubscribe from an event affordance. + + + EnumStrings + + i=78 + i=68 + ns=2;i=64027 + + ReadPropertyWritePropertyObservePropertyInvokeActionSubscribeEventUnsubscribeEvent + + + WoTValidationOutcomeDataType + An immutable snapshot of a document's format and compatibility validation result. Read as a single Variant value; a new snapshot is produced on each validation and never mutated in place. + WoT Connectivity 1.1 DataTypes + + i=22 + ns=2;i=64508 + ns=2;i=64509 + + True if format validation was performed.Outcome of format validation (WoT-TD/WoT-TM conformance).Human-readable reason for the format outcome (empty on success).True if compatibility validation was performed.Outcome of compatibility validation against the resource policy.Human-readable reason for the compatibility outcome (empty on success).The compatibility policy in force (for example NONE, BACKWARD, FULL).UTC time the validation completed.The pinned WoT Binding JSON-LD vocabulary version used for validation. + + + Default Binary + + i=76 + ns=2;i=64040 + + + + Default JSON + + i=76 + ns=2;i=64040 + + + + WoTBindingCapabilityDataType + An immutable snapshot of a protocol binding's identity, version-pinned W3C document, maturity and supported operations. Held as an array element only for immutable snapshots; browseable binding objects (WoTBindingType) carry the live, per-field form. + WoT Connectivity 1.1 DataTypes + + i=22 + ns=2;i=64510 + ns=2;i=64511 + + The WoT protocol-binding vocabulary URI (for example the OPC UA, HTTP or Modbus binding).Human-readable binding title.The version-pinned W3C binding document version this capability snapshot was built against.The W3C maturity of the pinned binding document (for example WD, CR, PR, REC).The interaction operations this binding supports.The content types this binding produces/consumes. + + + Default Binary + + i=76 + ns=2;i=64041 + + + + Default JSON + + i=76 + ns=2;i=64041 + + + + WoTRefreshOptionsDataType + Immutable options controlling a single Refresh invocation. + WoT Connectivity 1.1 DataTypes + + i=22 + ns=2;i=64512 + ns=2;i=64513 + + Commit granularity for this refresh.Re-project even when the content digest is unchanged.Validate and compute results without committing any projection change.Also refresh documents that depend on the selected documents.How to treat dependents when a selected document is unloaded/retired.Maximum number of documents projected concurrently; 0 lets the server decide.Overall time budget for the refresh; 0 lets the server decide. + + + Default Binary + + i=76 + ns=2;i=64042 + + + + Default JSON + + i=76 + ns=2;i=64042 + + + + WoTResourceSelectorDataType + An immutable selector identifying which stored documents a Refresh applies to. An empty selector array selects the whole registry. + WoT Connectivity 1.1 DataTypes + + i=22 + ns=2;i=64514 + ns=2;i=64515 + + Restrict to Thing Descriptions or Thing Models; omit to select both.Restrict to a group by groupid; empty selects all groups.Restrict to a resource by resourceid; empty selects all resources.Restrict to a version by versionid; empty selects the resource's default version.Select a single entity by its xRegistry xid; overrides the other fields when set. + + + Default Binary + + i=76 + ns=2;i=64043 + + + + Default JSON + + i=76 + ns=2;i=64043 + + + + WoTResourceLoadResultDataType + An immutable per-resource result row of a Refresh. Never mutated; the array is a point-in-time snapshot for one generation. + WoT Connectivity 1.1 DataTypes + + i=22 + ns=2;i=64516 + ns=2;i=64517 + + The xRegistry xid of the affected resource/version.The groupid of the resource's group.The resourceid of the affected resource.The versionid that was projected.Whether the document is a Thing Description or a Thing Model.The per-resource outcome.The phase the resource reached (the failing phase on failure).The resulting load state of the projection.The refresh generation this result belongs to.Number of AddressSpace nodes materialized for this resource.The root node of the materialized projection, if any.The content digest (hash) of the projected document bytes.Human-readable detail for the outcome. + + + Default Binary + + i=76 + ns=2;i=64044 + + + + Default JSON + + i=76 + ns=2;i=64044 + + + + WoTRefreshSummaryDataType + An immutable summary of one Refresh invocation, also carried by the WoTRefreshCompletedEventType and cached on the registry as LastRefreshSummary. + WoT Connectivity 1.1 DataTypes + + i=22 + ns=2;i=64518 + ns=2;i=64519 + + The caller-supplied request identifier echoed back for correlation.The committed refresh generation (0 on a dry run or full failure).The overall outcome of the refresh.The commit granularity that was applied.UTC start time of the refresh.UTC end time of the refresh.Total number of resources considered.Number of resources that changed successfully.Number of resources that were idempotently unchanged.Number of resources that failed.Number of resources skipped by selection or policy.Number of superseded generations retired. + + + Default Binary + + i=76 + ns=2;i=64045 + + + + Default JSON + + i=76 + ns=2;i=64045 + + + + WoTDependencyDataType + An immutable edge of the document dependency DAG, used to describe closures in results and diagnostics. + WoT Connectivity 1.1 DataTypes + + i=22 + ns=2;i=64520 + ns=2;i=64521 + + The xid of the dependent document.The xid of the document depended upon (empty if unresolved).The raw href/URI of the dependency as authored in the document.The dependency kind (for example tm:extends, tm:ref, links.rel=type).True if the dependency resolved to a stored document. + + + Default Binary + + i=76 + ns=2;i=64046 + + + + Default JSON + + i=76 + ns=2;i=64046 + + + + WoTRegistryType + The WoT Connectivity 1.1 registry root - an xRegistry RegistryType (a FolderType) that holds ThingDescriptionGroupType and ThingModelGroupType groups. The stored Thing Description / Thing Model files and their versions are canonical; the projected AddressSpace (types from Thing Models, instances from Thing Descriptions) is derived code-behind. Exposed as a well-known WoTRegistry object under the Server object (i=2253). Adds registry-wide refresh, generation and validation-policy state and the Refresh Method. + WoT Connectivity 1.1 + + ns=1;i=63000 + ns=2;i=64522 + ns=2;i=64523 + ns=2;i=64524 + ns=2;i=64525 + ns=2;i=64526 + ns=2;i=64527 + ns=2;i=64528 + ns=2;i=64529 + ns=2;i=64530 + ns=2;i=64531 + ns=2;i=64532 + ns=2;i=64533 + ns=2;i=64534 + ns=2;i=64535 + ns=2;i=64536 + ns=2;i=64537 + ns=2;i=64538 + ns=2;i=64014 + + + + ThingDescriptionGroupType + An xRegistry GroupType that collects related ThingDescriptionFileType resources (a Thing Description Group per the WoT xRegistry model). Adds the group-level format/compatibility validation policy. Its <ThingDescription> placeholder constrains members to the Thing Description subtype. + WoT Connectivity 1.1 + + ns=1;i=63001 + ns=2;i=64541 + ns=2;i=64542 + ns=2;i=64543 + ns=2;i=64544 + + + + ThingModelGroupType + An xRegistry GroupType that collects related ThingModelFileType resources (a Thing Model Group per the WoT xRegistry model). Adds the group-level format/compatibility validation policy. Its <ThingModel> placeholder constrains members to the Thing Model subtype. + WoT Connectivity 1.1 + + ns=1;i=63001 + ns=2;i=64545 + ns=2;i=64546 + ns=2;i=64547 + ns=2;i=64548 + + + + WoTDocumentType + The abstract base of a stored WoT document resource - an xRegistry ResourceType (a FileType) whose content bytes are the JSON-LD document, read/written with the inherited Open/Read/Write/Close Methods. Adds the derived-projection metadata (load state, desired/active version, validation and compatibility outcomes, content digest, materialized-node count and root, selected bindings) and the Validate, SetEnabled and SetDefaultVersion Methods. Concrete subtypes fix the document kind. + WoT Connectivity 1.1 + + ns=1;i=63002 + ns=2;i=64549 + ns=2;i=64550 + ns=2;i=64551 + ns=2;i=64552 + ns=2;i=64553 + ns=2;i=64554 + ns=2;i=64555 + ns=2;i=64556 + ns=2;i=64557 + ns=2;i=64558 + ns=2;i=64559 + ns=2;i=64560 + ns=2;i=64561 + ns=2;i=64562 + ns=2;i=64563 + ns=2;i=64564 + ns=2;i=64565 + ns=2;i=64567 + ns=2;i=64569 + ns=2;i=64011 + ns=2;i=64012 + ns=2;i=64013 + + + + ThingDescriptionFileType + A concrete WoTDocumentType whose content is a W3C WoT Thing Description (WoT-TD/1.1, application/td+json). Projects to OPC UA instances: affordances become Variables, Methods and event sources; forms become binder plans. Adds the Thing instance identity (ThingId, base URI) and the link to the Thing Model it derives from. + WoT Connectivity 1.1 + + ns=2;i=64003 + ns=2;i=64571 + ns=2;i=64572 + ns=2;i=64573 + ns=2;i=64574 + + + + ThingModelFileType + A concrete WoTDocumentType whose content is a W3C WoT Thing Model (WoT-TM/1.1, application/tm+json). Projects to OPC UA types: it materializes an ObjectType or VariableType and the affordance member declarations and modelling rules. Adds the derived type NodeId and model version. + WoT Connectivity 1.1 + + ns=2;i=64003 + ns=2;i=64575 + ns=2;i=64576 + ns=2;i=64577 + + + + WoTBindingType + A browseable protocol-binding descriptor: the live, per-field representation of one W3C WoT protocol binding the server can realize (its URI, title, version-pinned W3C document, draft maturity, enabled state, content types and a capability snapshot). Selected/active binding sets are additionally exposed as immutable WoTBindingCapabilityDataType array snapshots. Policy and identity are browseable; no credentials or secrets are ever exposed here. + WoT Connectivity 1.1 + + i=58 + ns=2;i=64578 + ns=2;i=64579 + ns=2;i=64580 + ns=2;i=64581 + ns=2;i=64582 + ns=2;i=64583 + ns=2;i=64584 + + + + WoTResourceEventType + The common base event for a WoT resource lifecycle notification. Carries the identity of the affected resource/version, the document kind, the refresh generation, the phase reached and the outcome. Abstract; servers emit one of its concrete subtypes. + WoT Connectivity 1.1 Events + + i=2041 + ns=2;i=64585 + ns=2;i=64586 + ns=2;i=64587 + ns=2;i=64588 + ns=2;i=64589 + ns=2;i=64590 + ns=2;i=64591 + + + + WoTValidationFailureEventType + Raised when a document fails format or compatibility validation. The failing resource is the event source; the stored document is retained and any previous valid projection stays active. + WoT Connectivity 1.1 Events + + ns=2;i=64010 + ns=2;i=64592 + + + + WoTLoadFailureEventType + Raised when a validated document fails to project (materialize) into the AddressSpace, or when its shadow generation cannot be activated. The failing resource is the event source. + WoT Connectivity 1.1 Events + + ns=2;i=64010 + ns=2;i=64593 + ns=2;i=64594 + ns=2;i=64595 + + + + WoTBindingFailureEventType + Raised when a form cannot be bound to its protocol binding (unknown binding, unsupported operation or a runtime binder error). The failing resource is the event source. + WoT Connectivity 1.1 Events + + ns=2;i=64010 + ns=2;i=64596 + ns=2;i=64597 + + + + WoTRefreshCompletedEventType + Raised by the registry when a Refresh completes (including automatic refreshes). Carries the refresh summary and the committed generation. The registry object is the event source. + WoT Connectivity 1.1 Events + + i=2041 + ns=2;i=64598 + ns=2;i=64599 + ns=2;i=64600 + + + + HasWoTProjection + Links a stored WoT document resource (source) to the root node of its derived AddressSpace projection (target). Used to correlate materialized nodes and their NodeVersion with the canonical document, and to find the document behind a projected node. + WoTProjectionOf + WoT Connectivity 1.1 References + + i=32 + + + + AutoRefresh + True if the registry automatically re-projects stored documents (per RefreshMode); false if only explicit Refresh calls re-project. + + i=80 + i=68 + ns=2;i=64000 + + + + RefreshMode + How automatic refresh is triggered when AutoRefresh is true. + + i=80 + i=68 + ns=2;i=64000 + + + + RefreshInterval + The interval used when RefreshMode is Periodic. + + i=80 + i=68 + ns=2;i=64000 + + + + RefreshGeneration + The current committed projection generation; incremented on every committed refresh. Materialized nodes carry the generation in their NodeVersion for correlation. + + i=78 + i=68 + ns=2;i=64000 + + + + LastRefreshTime + UTC time of the last completed refresh. + + i=80 + i=68 + ns=2;i=64000 + + + + LastRefreshSummary + An immutable snapshot summarizing the last completed refresh. + + i=80 + i=68 + ns=2;i=64000 + + + + DefaultAtomicity + The commit granularity applied when a Refresh omits an explicit atomicity. + + i=80 + i=68 + ns=2;i=64000 + + + + DeletePolicy + The default policy for treating dependents on unload/delete. + + i=80 + i=68 + ns=2;i=64000 + + + + ValidateFormat + Registry-wide default: validate document format on ingest/refresh. + + i=80 + i=68 + ns=2;i=64000 + + + + ValidateCompatibility + Registry-wide default: validate version compatibility on ingest/refresh. + + i=80 + i=68 + ns=2;i=64000 + + + + StrictValidation + If true, a validation warning is treated as a failure. + + i=80 + i=68 + ns=2;i=64000 + + + + VocabularyVersion + The version-pinned WoT Binding JSON-LD vocabulary this registry validates and projects against. + + i=80 + i=68 + ns=2;i=64000 + + + + SelectedBindings + An immutable snapshot array of the protocol bindings currently selected/active registry-wide. + + i=80 + i=68 + ns=2;i=64000 + + + + SupportedBindings + A folder of browseable WoTBindingType binding descriptors the server can realize (the live, per-field form of the selected-bindings snapshot). + + i=80 + i=61 + ns=2;i=64000 + + + + <ThingDescriptionGroup> + A Thing Description Group held by this registry (constrained to the ThingDescriptionGroupType subtype). + + i=11508 + ns=2;i=64001 + ns=2;i=64000 + + + + <ThingModelGroup> + A Thing Model Group held by this registry (constrained to the ThingModelGroupType subtype). + + i=11508 + ns=2;i=64002 + ns=2;i=64000 + + + + Refresh + Re-project selected stored documents into the AddressSpace. Idempotent: a document whose content digest is unchanged is reported Unchanged and not re-materialized unless Options.Force is set. Projects into a shadow generation and switches atomically per Options.Atomicity; superseded generations use the implementation's documented graceful or immediate retirement policy. If ExpectedGeneration is non-zero and does not equal RefreshGeneration, the call fails with Bad_InvalidState and changes nothing (optimistic concurrency). An empty Selection selects the whole registry. + + i=80 + ns=2;i=64000 + ns=2;i=64539 + ns=2;i=64540 + + + + InputArguments + + i=78 + i=68 + ns=2;i=64538 + + i=297Selectionns=2;i=6404310The documents to refresh; empty selects the whole registry.i=297Optionsns=2;i=64042-1Options controlling atomicity, force, dry-run and dependents.i=297ExpectedGenerationi=7-1Expected current RefreshGeneration for optimistic concurrency; 0 disables the check.i=297RequestIdi=12-1Caller-supplied identifier echoed into the summary and the completion event. + + + OutputArguments + + i=78 + i=68 + ns=2;i=64538 + + i=297Summaryns=2;i=64045-1The refresh summary.i=297Resultsns=2;i=6404410The per-resource results.i=297NewGenerationi=7-1The committed generation (unchanged on dry run or full failure). + + + ValidateFormat + Group-level policy: validate Thing Description format (WoT-TD/1.1) on ingest. + + i=80 + i=68 + ns=2;i=64001 + + + + ValidateCompatibility + Group-level policy: validate version compatibility on ingest. + + i=80 + i=68 + ns=2;i=64001 + + + + ConsistentFormat + Group-level policy: require all versions of a resource to share one format. + + i=80 + i=68 + ns=2;i=64001 + + + + <ThingDescription> + A Thing Description resource held by this group (constrained to the ThingDescriptionFileType subtype). + + i=11508 + ns=2;i=64004 + ns=2;i=64001 + + + + ValidateFormat + Group-level policy: validate Thing Model format (WoT-TM/1.1) on ingest. + + i=80 + i=68 + ns=2;i=64002 + + + + ValidateCompatibility + Group-level policy: validate version compatibility on ingest. + + i=80 + i=68 + ns=2;i=64002 + + + + ConsistentFormat + Group-level policy: require all versions of a resource to share one format. + + i=80 + i=68 + ns=2;i=64002 + + + + <ThingModel> + A Thing Model resource held by this group (constrained to the ThingModelFileType subtype). + + i=11508 + ns=2;i=64005 + ns=2;i=64002 + + + + DocumentKind + Whether this document is a Thing Description or a Thing Model. Fixed by the concrete subtype. + + i=78 + i=68 + ns=2;i=64003 + + + + Enabled + The desired enabled state: true requests that the document be validated and projected; false requests unload. + + i=78 + i=68 + ns=2;i=64003 + + + + LoadState + The actual lifecycle state of this document's derived projection. + + i=78 + i=68 + ns=2;i=64003 + + + + DesiredVersionId + The versionid the operator wants active for this resource (the desired/pinned version). + + i=80 + i=68 + ns=2;i=64003 + + + + ActiveVersionId + The versionid whose projection is currently active. + + i=80 + i=68 + ns=2;i=64003 + + + + IsDefault + xRegistry isdefault: true when this version is the resource's default (sticky) version. + + i=80 + i=68 + ns=2;i=64003 + + + + Ancestor + xRegistry ancestor: the versionid this version derives from (version lineage). + + i=80 + i=68 + ns=2;i=64003 + + + + Compatibility + The compatibility policy all versions of this resource adhere to (for example NONE, BACKWARD, FULL). + + i=80 + i=68 + ns=2;i=64003 + + + + AutoRefresh + Per-document override of the registry AutoRefresh setting. + + i=80 + i=68 + ns=2;i=64003 + + + + RefreshGeneration + The registry generation at which this document was last projected. + + i=80 + i=68 + ns=2;i=64003 + + + + LastRefreshTime + UTC time this document was last projected. + + i=80 + i=68 + ns=2;i=64003 + + + + ContentDigest + The content digest (hash) of the stored document bytes; used to make refresh idempotent. + + i=80 + i=68 + ns=2;i=64003 + + + + ValidationOutcome + An immutable snapshot of this document's format and compatibility validation result. + + i=80 + i=68 + ns=2;i=64003 + + + + MaterializedNodeCount + The number of AddressSpace nodes materialized from this document's active projection. + + i=80 + i=68 + ns=2;i=64003 + + + + RootNodeId + The root node of this document's active projection (the type or instance root). + + i=80 + i=68 + ns=2;i=64003 + + + + SelectedBindings + An immutable snapshot array of the protocol bindings selected for this document's forms. + + i=80 + i=68 + ns=2;i=64003 + + + + Validate + Validate the stored document (format and, when enabled, compatibility) without changing its projection. Returns the outcome snapshot; also refreshes the ValidationOutcome Property. + + i=80 + ns=2;i=64003 + ns=2;i=64566 + + + + OutputArguments + + i=78 + i=68 + ns=2;i=64565 + + i=297Outcomens=2;i=64040-1The validation outcome snapshot. + + + SetEnabled + Set the desired Enabled state of this document. Enabling requests validation and projection; disabling requests unload per the registry DeletePolicy. If ExpectedEpoch is non-zero and does not equal the resource's current Epoch the call fails with Bad_InvalidState and changes nothing. + + i=80 + ns=2;i=64003 + ns=2;i=64568 + + + + InputArguments + + i=78 + i=68 + ns=2;i=64567 + + i=297Enabledi=1-1The desired enabled state.i=297ExpectedEpochi=7-1Expected current Epoch for optimistic concurrency; 0 disables the check. + + + SetDefaultVersion + Make a specific version of this resource its default (sticky) version, so that resolvers selecting the resource without a versionid resolve to it. If ExpectedEpoch is non-zero and does not equal the resource's current Epoch the call fails with Bad_InvalidState and changes nothing. + + i=80 + ns=2;i=64003 + ns=2;i=64570 + + + + InputArguments + + i=78 + i=68 + ns=2;i=64569 + + i=297VersionIdi=12-1The versionid to make default.i=297ExpectedEpochi=7-1Expected current Epoch for optimistic concurrency; 0 disables the check. + + + ThingId + The Thing Description id (a URI/URN identifying the concrete Thing instance). + + i=80 + i=68 + ns=2;i=64004 + + + + ThingTitle + The Thing Description human-readable title. + + i=80 + i=68 + ns=2;i=64004 + + + + BaseUri + The Thing Description base URI used to resolve relative form hrefs. + + i=80 + i=68 + ns=2;i=64004 + + + + ModelReference + The xid or href of the Thing Model this Thing Description derives from (links rel=type), when present. + + i=80 + i=68 + ns=2;i=64004 + + + + ModelTitle + The Thing Model human-readable title. + + i=80 + i=68 + ns=2;i=64005 + + + + ModelVersion + The Thing Model version (WoT version.model), when present. + + i=80 + i=68 + ns=2;i=64005 + + + + DerivedTypeNodeId + The ObjectType or VariableType materialized from this Thing Model. + + i=80 + i=68 + ns=2;i=64005 + + + + BindingUri + The WoT protocol-binding vocabulary URI this descriptor represents. + + i=78 + i=68 + ns=2;i=64006 + + + + Title + Human-readable binding title. + + i=80 + i=68 + ns=2;i=64006 + + + + ProfileVersion + The version-pinned W3C binding document version. + + i=80 + i=68 + ns=2;i=64006 + + + + DraftMaturity + The W3C maturity of the pinned binding document (for example WD, CR, PR, REC). + + i=80 + i=68 + ns=2;i=64006 + + + + Enabled + True if the server currently realizes forms of this binding. + + i=80 + i=68 + ns=2;i=64006 + + + + ContentTypes + The content types this binding produces/consumes. + + i=80 + i=68 + ns=2;i=64006 + + + + Capabilities + An immutable capability snapshot for this binding. + + i=80 + i=68 + ns=2;i=64006 + + + + Xid + The xRegistry xid of the affected resource/version. + + i=78 + i=68 + ns=2;i=64010 + + + + ResourceId + The resourceid of the affected resource. + + i=78 + i=68 + ns=2;i=64010 + + + + VersionId + The versionid of the affected version. + + i=78 + i=68 + ns=2;i=64010 + + + + DocumentKind + Whether the document is a Thing Description or a Thing Model. + + i=78 + i=68 + ns=2;i=64010 + + + + Generation + The refresh generation the notification relates to. + + i=78 + i=68 + ns=2;i=64010 + + + + Phase + The phase reached (the failing phase on a failure event). + + i=78 + i=68 + ns=2;i=64010 + + + + Outcome + The outcome the notification reports. + + i=78 + i=68 + ns=2;i=64010 + + + + ValidationOutcome + The full validation outcome snapshot for the failure. + + i=78 + i=68 + ns=2;i=64011 + + + + LoadState + The load state after the failed projection/activation. + + i=78 + i=68 + ns=2;i=64012 + + + + FailedNodeId + The node whose materialization failed, if identifiable. + + i=78 + i=68 + ns=2;i=64012 + + + + Reason + Human-readable failure reason. + + i=78 + i=68 + ns=2;i=64012 + + + + BindingUri + The binding URI that could not be bound. + + i=78 + i=68 + ns=2;i=64013 + + + + Reason + Human-readable binding failure reason. + + i=78 + i=68 + ns=2;i=64013 + + + + Summary + The refresh summary snapshot. + + i=78 + i=68 + ns=2;i=64014 + + + + RequestId + The caller-supplied request identifier echoed from the Refresh call. + + i=78 + i=68 + ns=2;i=64014 + + + + Generation + The committed generation. + + i=78 + i=68 + ns=2;i=64014 + + + + WoTRegistry + The server-wide WoT Connectivity 1.1 registry, a well-known component of the Server object. Its stored Thing Description / Thing Model files are canonical; the projected AddressSpace is derived. It is the notifier for the WoT resource lifecycle events raised by its groups and resources. + WoT Connectivity 1.1 Instances + + ns=2;i=64000 + i=2253 + i=2253 + ns=2;i=64601 + ns=2;i=64604 + ns=2;i=64605 + + + + Refresh + Re-project selected stored documents into the AddressSpace. The functional Refresh Method on the well-known WoTRegistry object; a server binds the concrete handler. + WoT Connectivity 1.1 Instances + + ns=2;i=64100 + ns=2;i=64602 + ns=2;i=64603 + + + + InputArguments + WoT Connectivity 1.1 Instances + + i=68 + ns=2;i=64601 + + i=297Selectionns=2;i=6404310The documents to refresh; empty selects the whole registry.i=297Optionsns=2;i=64042-1Options controlling atomicity, force, dry-run and dependents.i=297ExpectedGenerationi=7-1Expected current RefreshGeneration for optimistic concurrency; 0 disables the check.i=297RequestIdi=12-1Caller-supplied identifier echoed into the summary and the completion event. + + + OutputArguments + WoT Connectivity 1.1 Instances + + i=68 + ns=2;i=64601 + + i=297Summaryns=2;i=64045-1The refresh summary.i=297Resultsns=2;i=6404410The per-resource results.i=297NewGenerationi=7-1The committed generation (unchanged on dry run or full failure). + + + RegistryId + xRegistry registryid: the stable identifier of this registry (Mandatory, inherited from the xRegistry RegistryType). Default value for the well-known instance; a server MAY override it. + WoT Connectivity 1.1 Instances + + i=68 + ns=2;i=64100 + + WoTRegistry + + + RefreshGeneration + The current committed projection generation; incremented on every committed refresh (Mandatory). Materialized as 0 at load time, before any Refresh has committed. + WoT Connectivity 1.1 Instances + + i=68 + ns=2;i=64100 + + 0 + + + WoTAssetConnectionManagementType + WoT Connectivity 1.02 legacy (deprecated) + + i=58 + ns=2;i=2 + ns=2;i=26 + ns=2;i=29 + ns=2;i=40 + ns=2;i=41 + ns=2;i=49 + ns=2;i=75 + ns=2;i=78 + + + + <WoTAssetName> + WoT Connectivity 1.02 legacy (deprecated) + + i=11508 + ns=2;i=1 + i=58 + ns=2;i=144 + ns=2;i=169 + ns=2;i=42 + + + + NamespaceFile + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=11575 + ns=2;i=4 + ns=2;i=5 + ns=2;i=6 + ns=2;i=7 + ns=2;i=8 + ns=2;i=9 + ns=2;i=10 + ns=2;i=11 + ns=2;i=14 + ns=2;i=16 + ns=2;i=19 + ns=2;i=21 + ns=2;i=24 + ns=2;i=37 + + + + Size + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + i=68 + + + + Writable + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + i=68 + + + + UserWritable + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + i=68 + + + + OpenCount + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + i=68 + + + + MimeType + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + i=68 + + + + MaxByteStringLength + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + i=68 + + + + LastModifiedTime + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + i=68 + + + + Open + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + ns=2;i=12 + ns=2;i=13 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=11 + i=68 + + i=297Modei=3-1 + + + OutputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=11 + i=68 + + i=297FileHandlei=7-1 + + + Close + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + ns=2;i=15 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=14 + i=68 + + i=297FileHandlei=7-1 + + + Read + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + ns=2;i=17 + ns=2;i=18 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=16 + i=68 + + i=297FileHandlei=7-1i=297Lengthi=6-1 + + + OutputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=16 + i=68 + + i=297Datai=15-1 + + + Write + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + ns=2;i=20 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=19 + i=68 + + i=297FileHandlei=7-1i=297Datai=15-1 + + + GetPosition + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + ns=2;i=22 + ns=2;i=23 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=21 + i=68 + + i=297FileHandlei=7-1 + + + OutputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=21 + i=68 + + i=297Positioni=9-1 + + + SetPosition + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + ns=2;i=25 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=24 + i=68 + + i=297FileHandlei=7-1i=297Positioni=9-1 + + + CreateAsset + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=1 + ns=2;i=27 + ns=2;i=28 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=26 + i=68 + + i=297AssetNamei=12-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=26 + i=68 + + i=297AssetIdi=17-1 + + + DeleteAsset + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=1 + ns=2;i=30 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=29 + i=68 + + i=297AssetIdi=17-1 + + + WoTAssetConnectionManagement + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=1 + i=85 + ns=2;i=32 + ns=2;i=35 + ns=2;i=80 + ns=2;i=81 + ns=2;i=83 + ns=2;i=85 + ns=2;i=88 + + + + CreateAsset + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=31 + ns=2;i=33 + ns=2;i=34 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=32 + i=68 + + i=297AssetNamei=12-1 + + + OutputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=32 + i=68 + + i=297AssetIdi=17-1 + + + DeleteAsset + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=31 + ns=2;i=36 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=35 + i=68 + + i=297AssetIdi=17-1 + + + ExportNamespace + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + + + + ConfigurationVersion + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + + + ModelVersion + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + 1.1.0 + + + SupportedWoTBindings + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=1 + i=68 + + + + DiscoverAssets + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=1 + ns=2;i=48 + + + + IWoTAssetType + WoT Connectivity 1.02 legacy (deprecated) + + i=17602 + ns=2;i=43 + ns=2;i=66 + ns=2;i=122 + + + + WoTFile + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=42 + ns=2;i=110 + ns=2;i=44 + ns=2;i=45 + ns=2;i=46 + ns=2;i=47 + ns=2;i=51 + ns=2;i=54 + ns=2;i=56 + ns=2;i=59 + ns=2;i=61 + ns=2;i=64 + ns=2;i=106 + ns=2;i=113 + ns=2;i=114 + ns=2;i=121 + + + + Size + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + i=68 + + + + Writable + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + i=68 + + + + UserWritable + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + i=68 + + + + OpenCount + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + i=68 + + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=41 + i=68 + + i=297AssetEndpointsi=1210 + + + CreateAssetForEndpoint + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=1 + ns=2;i=50 + ns=2;i=170 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=49 + i=68 + + i=297AssetNamei=12-1i=297AssetEndpointi=12-1 + + + Open + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + ns=2;i=52 + ns=2;i=53 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=51 + i=68 + + i=297Modei=3-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=51 + i=68 + + i=297FileHandlei=7-1 + + + Close + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + ns=2;i=55 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=54 + i=68 + + i=297FileHandlei=7-1 + + + Read + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + ns=2;i=57 + ns=2;i=58 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=56 + i=68 + + i=297FileHandlei=7-1i=297Lengthi=6-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=56 + i=68 + + i=297Datai=15-1 + + + Write + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + ns=2;i=60 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=59 + i=68 + + i=297FileHandlei=7-1i=297Datai=15-1 + + + GetPosition + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + ns=2;i=62 + ns=2;i=63 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=61 + i=68 + + i=297FileHandlei=7-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=61 + i=68 + + i=297Positioni=9-1 + + + SetPosition + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + ns=2;i=65 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=64 + i=68 + + i=297FileHandlei=7-1i=297Positioni=9-1 + + + <WoTPropertyName> + WoT Connectivity 1.02 legacy (deprecated) + + i=11508 + ns=2;i=42 + i=63 + + + + http://opcfoundation.org/UA/WoT-Con/ + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=3 + ns=2;i=38 + ns=2;i=39 + i=11616 + i=11715 + ns=2;i=68 + ns=2;i=69 + ns=2;i=70 + ns=2;i=71 + ns=2;i=72 + ns=2;i=73 + ns=2;i=74 + ns=2;i=99 + ns=2;i=100 + ns=2;i=101 + + + + NamespaceUri + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + http://opcfoundation.org/UA/WoT-Con/ + + + NamespaceVersion + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + 1.1.0 + + + NamespacePublicationDate + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + 2026-07-22T00:00:00Z + + + IsNamespaceSubset + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + false + + + StaticNodeIdTypes + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + + + StaticNumericNodeIdRange + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + + + StaticStringNodeIdPattern + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + + + + ConnectionTest + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=1 + ns=2;i=76 + ns=2;i=77 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=75 + i=68 + + i=297AssetEndpointi=12-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=75 + i=68 + + i=297Successi=1-1i=297Statusi=12-1 + + + Configuration + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=1 + ns=2;i=105 + ns=2;i=79 + + + + License + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=78 + i=68 + + + + SupportedWoTBindings + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=31 + i=68 + + + + DiscoverAssets + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=31 + ns=2;i=82 + + + + OutputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=81 + i=68 + + i=297AssetEndpointsi=1210 + + + CreateAssetForEndpoint + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=31 + ns=2;i=84 + ns=2;i=171 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=83 + i=68 + + i=297AssetNamei=12-1i=297AssetEndpointi=12-1 + + + ConnectionTest + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=31 + ns=2;i=86 + ns=2;i=87 + + + + InputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=85 + i=68 + + i=297AssetEndpointi=12-1 + + + OutputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=85 + i=68 + + i=297Successi=1-1i=297Statusi=12-1 + + + Configuration + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=31 + ns=2;i=105 + ns=2;i=89 + + + + License + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=88 + i=68 + + + + CreateAssetMethodType + WoT Connectivity 1.02 legacy (deprecated) + + ns=2;i=91 + ns=2;i=92 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=90 + i=68 + + i=297AssetNamei=12-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=90 + i=68 + + i=297AssetIdi=17-1 + + + DeleteAssetMethodType + WoT Connectivity 1.02 legacy (deprecated) + + ns=2;i=94 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=93 + i=68 + + i=297AssetIdi=17-1 + + + DiscoverAssetsMethodType + WoT Connectivity 1.02 legacy (deprecated) + + ns=2;i=96 + + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=95 + i=68 + + i=297AssetEndpointsi=1210 + + + CreateAssetForEndpointMethodType + WoT Connectivity 1.02 legacy (deprecated) + + ns=2;i=98 + ns=2;i=172 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=97 + i=68 + + i=297AssetNamei=12-1i=297AssetEndpointi=12-1 + + + DefaultRolePermissions + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + + + DefaultUserRolePermissions + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + + + DefaultAccessRestrictions + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=67 + i=68 + + + + ConnectionTestMethodType + WoT Connectivity 1.02 legacy (deprecated) + + ns=2;i=103 + ns=2;i=104 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=102 + i=68 + + i=297AssetEndpointi=12-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=102 + i=68 + + i=297Successi=1-1i=297Statusi=12-1 + + + WoTAssetConfigurationType + WoT Connectivity 1.02 legacy (deprecated) + + i=17602 + ns=2;i=108 + ns=2;i=109 + + + + CloseAndUpdate + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=43 + ns=2;i=107 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=106 + i=68 + + i=297FileHandlei=7-1 + + + <WoTConfigurationParameterName> + WoT Connectivity 1.02 legacy (deprecated) + + i=11508 + ns=2;i=105 + i=68 + + + + License + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=105 + i=68 + + + + WoTAssetFileType + WoT Connectivity 1.02 legacy (deprecated) + + i=11575 + ns=2;i=111 + + + + CloseAndUpdate + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=110 + ns=2;i=112 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=111 + i=68 + + i=297FileHandlei=7-1 + + + MimeType + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=43 + i=68 + + + + MaxByteStringLength + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=43 + i=68 + + + + LastModifiedTime + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=43 + i=68 + + + + AssetEndpoint + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=42 + i=68 + + + + CloseAndUpdateMethodType + WoT Connectivity 1.02 legacy (deprecated) + + ns=2;i=143 + + + + HasWoTComponent + WoTComponentOf + WoT Connectivity 1.02 legacy (deprecated) + + i=47 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=123 + i=68 + + i=297FileHandlei=7-1 + + + WoTFile + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=2 + ns=2;i=110 + ns=2;i=145 + ns=2;i=146 + ns=2;i=147 + ns=2;i=148 + ns=2;i=149 + ns=2;i=150 + ns=2;i=151 + ns=2;i=152 + ns=2;i=155 + ns=2;i=157 + ns=2;i=160 + ns=2;i=162 + ns=2;i=165 + ns=2;i=167 + + + + Size + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + i=68 + + + + Writable + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + i=68 + + + + UserWritable + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + i=68 + + + + OpenCount + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + i=68 + + + + MimeType + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=144 + i=68 + + + + MaxByteStringLength + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=144 + i=68 + + + + LastModifiedTime + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=144 + i=68 + + + + Open + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + ns=2;i=153 + ns=2;i=154 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=152 + i=68 + + i=297Modei=3-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=152 + i=68 + + i=297FileHandlei=7-1 + + + Close + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + ns=2;i=156 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=155 + i=68 + + i=297FileHandlei=7-1 + + + Read + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + ns=2;i=158 + ns=2;i=159 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=157 + i=68 + + i=297FileHandlei=7-1i=297Lengthi=6-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=157 + i=68 + + i=297Datai=15-1 + + + Write + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + ns=2;i=161 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=160 + i=68 + + i=297FileHandlei=7-1i=297Datai=15-1 + + + GetPosition + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + ns=2;i=163 + ns=2;i=164 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=162 + i=68 + + i=297FileHandlei=7-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=162 + i=68 + + i=297Positioni=9-1 + + + SetPosition + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + ns=2;i=166 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=165 + i=68 + + i=297FileHandlei=7-1i=297Positioni=9-1 + + + CloseAndUpdate + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=144 + ns=2;i=168 + + + + InputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=167 + i=68 + + i=297FileHandlei=7-1 + + + AssetEndpoint + WoT Connectivity 1.02 legacy (deprecated) + + i=80 + ns=2;i=2 + i=68 + + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=49 + i=68 + + i=297AssetIdi=17-1 + + + OutputArguments + WoT Connectivity 1.02 Legacy Instances + + ns=2;i=83 + i=68 + + i=297AssetIdi=17-1 + + + OutputArguments + WoT Connectivity 1.02 legacy (deprecated) + + i=78 + ns=2;i=97 + i=68 + + i=297AssetIdi=17-1 + + diff --git a/wot-specs/WoT-Connectivity/README.md b/wot-specs/WoT-Connectivity/README.md new file mode 100644 index 0000000..cb51f63 --- /dev/null +++ b/wot-specs/WoT-Connectivity/README.md @@ -0,0 +1,44 @@ +# OPC UA — WoT Connectivity + +A **registry-first** revision **1.1** of the OPC UA companion specification for Web of Things (WoT) connectivity. Instead of a flat asset-connection manager, WoT Connectivity 1.1 layers a W3C **Thing Model / Thing Description document registry** over the abstract [OPC UA — xRegistry](../../core-specs/xregistry/) base model and treats the stored documents and their versions as the single source of truth from which the OPC UA AddressSpace and any code-behind are **derived**. It is an **additive** revision in the same published namespace: the full OPC 10100-1 v1.02 model is incorporated into one combined NodeSet and the superseded 1.02 surface is marked deprecated. + +> Experimental and non-normative, intended for submission to the OPC Foundation WoT Working Group. Nothing here is official or endorsed by the OPC Foundation or the W3C; the additive registry NodeIds (the `64000+` block) are provisional and used for prototyping only. + +## What it is + +- `WoTRegistryType` (subtype of xRegistry `RegistryType`) is a well-known `WoTRegistry` Object under the **Server** object (`i=2253`). It holds `ThingDescriptionGroupType` / `ThingModelGroupType` groups whose files are `ThingDescriptionFileType` / `ThingModelFileType` resources — concrete subtypes of the abstract `WoTDocumentType` (an xRegistry `ResourceType`, i.e. a `FileType`). +- **Thing Models project to OPC UA types; Thing Descriptions project to OPC UA instances**; affordances become Variables/Methods/EventTypes, links become References (including a parent `uav:componentOf`), and forms become protocol **binder plans**. The projection is a **shadow-switched, generational, idempotent** derivation of the canonical documents. +- Validation outcomes, load state, desired/active version, content digest, materialized nodes and selected bindings are exposed on the registry and document nodes; a `Refresh` Method drives explicit re-projection with detailed summary/results; resource lifecycle and refresh-completed **events** flow along a Server → registry → group → resource notifier chain. +- The revised [OPC UA — WoT Binding](../WoT-Binding/) `uav` JSON-LD vocabulary is a **normative dependency** governing the document→node mapping, but **not** a NodeSet `RequiredModel` (it is a vocabulary, not an information model). + +## Incorporated OPC 10100-1 v1.02 model + +The published **OPC 10100-1 v1.02** WoT Connectivity model (namespace `http://opcfoundation.org/UA/WoT-Con/`, v1.02.0) is **incorporated into the same combined NodeSet and namespace**, preserving every published NodeId (`1..172`), type and method signature (`CreateAsset`, `DeleteAsset`, `DiscoverAssets`, `CreateAssetForEndpoint`, `ConnectionTest`, `WoTFile`/`CloseAndUpdate`, `SupportedWoTBindings`, `HasWoTComponent`) and the well-known `WoTAssetConnectionManagement` object. Because the registry supersedes the flat asset surface, those types are marked `ReleaseStatus="Deprecated"` (per OPC 11030) — deprecated, not removed, so existing 1.02 clients keep working. The 1.02 sources are pinned under [`legacy/`](legacy/) and incorporated deterministically by the generator; §13 / Annex B of the specification record how the deprecated surface is backed by the registry without changing any signature. + +## Files + +- `OPC-UA-WoT-Connectivity.md` — the full standalone 1.1 specification (Annex A is the embedded generated node reference; Annexes B/C are informative). +- `Opc.Ua.WoTCon.NodeSet2.xml` — generated combined NodeSet (requires the Core and xRegistry base NodeSets as ``). +- `Opc.Ua.WoTCon.NodeIds.csv` — generated NodeIds (the preserved 1.02 rows `1..172` plus the additive registry rows). +- `legacy/WotConnection.xml` / `legacy/WotConnection.csv` — the pinned OPC 10100-1 v1.02 authoring sources (source input, not hand-copied output). +- `tools/build_model.py` — the canonical generator (single source of truth; parses the pinned legacy sources). +- `tools/model-reference.md` — generated Annex A (embedded verbatim in the spec). +- `tools/validate_local.py` — the deterministic, standard-library structural validator. +- `examples/` — a Thing Model, a matching Thing Description, an intentionally invalid Thing Description, and a representative refresh-results document. + +## Namespace and NodeIds + +One NamespaceUri `http://opcfoundation.org/UA/WoT-Con/` at model version `1.1.0` (namespace index 2 in the NodeSet, after Core at 0 and the xRegistry base at 1). The incorporated 1.02 nodes keep their published NodeIds `1..172`; the additive registry nodes use a provisional **64000+** block (types) with members allocated append-only from **64500**. Final registry NodeIds are assigned by the OPC Foundation. + +## Regenerate and validate + +The xRegistry base must be generated first, because this NodeSet references it: + +```powershell +python core-specs\xregistry\tools\build_model.py +python core-specs\xregistry\tools\validate_local.py +python wot-specs\WoT-Connectivity\tools\build_model.py +python wot-specs\WoT-Connectivity\tools\validate_local.py +``` + +The validator checks XML/CSV consistency, that references resolve against the own namespace and the loaded xRegistry base `NodeIds.csv`, the Server→WoTRegistry notifier topology, that each type has a `HasSubtype` inverse and each Structure its encodings, and that the generated Annex A is embedded verbatim in the specification. It additionally **proves the 1.02 preservation**: the first 172 CSV rows match `legacy/WotConnection.csv` exactly, every concrete legacy id is present with its pinned NodeClass, the deprecated management surface carries `ReleaseStatus="Deprecated"`, the callable well-known `WoTAssetConnectionManagement` is present, and the combined NodeSet declares one namespace at model version 1.1.0. For full base-namespace NodeId resolution, place `UA.NodeIds.csv` in `tools/ref/` (this local validation aid is gitignored). diff --git a/wot-specs/WoT-Connectivity/examples/01-thing-model-pump.tm.jsonld b/wot-specs/WoT-Connectivity/examples/01-thing-model-pump.tm.jsonld new file mode 100644 index 0000000..27f2813 --- /dev/null +++ b/wot-specs/WoT-Connectivity/examples/01-thing-model-pump.tm.jsonld @@ -0,0 +1,74 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { + "uav": "http://opcfoundation.org/UA/WoT-Binding/", + "qudt-quantitykind": "http://qudt.org/vocab/quantitykind/" + }, + "../../WoT-Binding/opc-ua-wot-binding.context.jsonld" + ], + "@type": ["tm:ThingModel", "uav:objectType"], + "title": "PumpType", + "description": "A WoT-TM/1.1 Thing Model stored as a thingmodel resource in a WoT Connectivity 1.1 registry. A Refresh projects it to an OPC UA ObjectType (uav:objectType) whose members carry the modelling rules and units declared here. This document is canonical; the ObjectType it produces is derived code-behind.", + "version": { "model": "1.2.0" }, + "uav:browseName": "1:PumpType", + "uav:id": "nsu=http://example.com/demo/pump;i=1001", + "uav:isComposite": true, + "uav:includeInherited": true, + "uav:nameNamespace": "http://example.com/demo/pump", + "uav:semanticId": "http://example.com/ontology/Pump", + "uav:propertyGroups": [ + { "title": "Operational", "uav:semanticId": "http://example.com/ontology/Operational" }, + { "title": "Nameplate" } + ], + "uav:eventGroups": [{ "title": "Diagnostics" }], + "uav:actionGroups": [{ "title": "Maintenance" }], + "properties": { + "pumpSpeed": { + "@type": "uav:variableType", + "title": "Pump Speed", + "uav:browseName": "1:PumpSpeed", + "type": "number", + "unit": "qudt-quantitykind:AngularVelocity", + "uav:modellingRule": "Mandatory", + "uav:memberOf": "Operational", + "uav:scaleFactor": 0.1, + "uav:decimalPlaces": 2, + "observable": true, + "readOnly": true + }, + "serialNumber": { + "@type": "uav:variableType", + "title": "Serial Number", + "uav:browseName": "1:SerialNumber", + "type": "string", + "uav:modellingRule": "Mandatory", + "uav:memberOf": "Nameplate" + } + }, + "actions": { + "reset": { + "@type": "uav:method", + "title": "Reset", + "uav:browseName": "1:Reset", + "uav:modellingRule": "Optional", + "uav:memberOf": "Maintenance" + } + }, + "events": { + "overTemperature": { + "@type": "uav:eventType", + "title": "Over Temperature", + "uav:isEvent": true, + "uav:browseName": "1:OverTemperatureEventType", + "uav:memberOf": "Diagnostics", + "uav:semanticId": "http://example.com/ontology/OverTemperature", + "data": { + "type": "object", + "properties": { + "temperature": { "type": "number", "unit": "qudt-quantitykind:Temperature" } + } + } + } + } +} diff --git a/wot-specs/WoT-Connectivity/examples/02-thing-description-pump.td.jsonld b/wot-specs/WoT-Connectivity/examples/02-thing-description-pump.td.jsonld new file mode 100644 index 0000000..3b8ebc9 --- /dev/null +++ b/wot-specs/WoT-Connectivity/examples/02-thing-description-pump.td.jsonld @@ -0,0 +1,86 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "uav": "http://opcfoundation.org/UA/WoT-Binding/" }, + "../../WoT-Binding/opc-ua-wot-binding.context.jsonld" + ], + "@type": ["Thing", "uav:object"], + "id": "urn:dev:opcua:pump-01", + "title": "Pump 01", + "description": "A WoT-TD/1.1 Thing Description stored as a thingdescription resource in a WoT Connectivity 1.1 registry. It derives from the PumpType Thing Model (links rel=type -> the Thing Model's registry self URL) and, when enabled and refreshed, is projected to an OPC UA Object instance whose Variables/Methods/EventTypes and forms are bound to the OPC UA protocol binding.", + "version": { "instance": "1.0.0" }, + "uav:browseName": "1:Pump", + "uav:id": "nsu=http://example.com/demo/pump;s=Pump", + "links": [ + { + "rel": "type", + "href": "../01-thing-model-pump.tm.jsonld", + "type": "application/tm+json" + } + ], + "securityDefinitions": { + "opcua_channel_sc": { + "scheme": "uav:channelsec", + "uav:securityMode": "SignAndEncrypt", + "uav:securityPolicy": "Aes256_Sha256_RsaPss" + }, + "opcua_authentication_sc": { + "scheme": "uav:authentication", + "uav:userIdentityToken": "UserName" + }, + "opcua_sc": { "scheme": "combo", "allOf": ["opcua_channel_sc", "opcua_authentication_sc"] } + }, + "security": "opcua_sc", + "base": "opc.tcp://opcuademo.com:4840", + "properties": { + "pumpSpeed": { + "@type": "uav:variable", + "title": "Pump Speed", + "uav:browseName": "1:PumpSpeed", + "uav:browsePath": "/Objects/1:Pump/1:PumpSpeed", + "type": "number", + "unit": "rpm", + "readOnly": true, + "observable": true, + "forms": [ + { + "href": "/?id=nsu=http://example.com/demo/pump;s=PumpSpeed", + "contentType": "application/octet-stream", + "op": ["readproperty", "observeproperty"] + } + ] + }, + "speedSetpoint": { + "@type": "uav:variable", + "title": "Speed Setpoint", + "uav:browseName": "1:SpeedSetpoint", + "uav:browsePath": "/Objects/1:Pump/1:SpeedSetpoint", + "type": "number", + "unit": "rpm", + "readOnly": false, + "observable": true, + "forms": [ + { + "href": "/?id=nsu=http://example.com/demo/pump;s=SpeedSetpoint", + "contentType": "application/octet-stream", + "op": ["readproperty", "writeproperty", "observeproperty"] + } + ] + } + }, + "actions": { + "reset": { + "@type": "uav:method", + "title": "Reset", + "uav:browseName": "1:Reset", + "uav:browsePath": "/Objects/1:Pump/1:Reset", + "forms": [ + { + "href": "/?id=nsu=http://example.com/demo/pump;s=Reset", + "contentType": "application/octet-stream", + "op": ["invokeaction"] + } + ] + } + } +} diff --git a/wot-specs/WoT-Connectivity/examples/03-invalid-thing-description.td.jsonld b/wot-specs/WoT-Connectivity/examples/03-invalid-thing-description.td.jsonld new file mode 100644 index 0000000..6eda030 --- /dev/null +++ b/wot-specs/WoT-Connectivity/examples/03-invalid-thing-description.td.jsonld @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "uav": "http://opcfoundation.org/UA/WoT-Binding/" }, + "../../WoT-Binding/opc-ua-wot-binding.context.jsonld" + ], + "@type": ["Thing", "uav:object"], + "id": "urn:dev:opcua:pump-broken", + "description": "An intentionally INVALID WoT-TD/1.1 Thing Description used to exercise validation-failure handling. A conformant WoT Connectivity 1.1 registry keeps this document stored, marks its LoadState Failed, records a WoTValidationOutcomeDataType with FormatOutcome=Failed, raises a WoTValidationFailureEventType, and leaves any previously active projection of pump-broken untouched. The specific defects are: (1) the required top-level 'title' member is missing; (2) 'security' names 'missing_sc', which is not present in securityDefinitions; (3) the pumpSpeed form declares op 'frobnicate', which is not a valid WoT form operation; (4) the links rel=type href points to a Thing Model that does not resolve in the registry.", + "uav:browseName": "1:PumpBroken", + "uav:id": "nsu=http://example.com/demo/pump;s=PumpBroken", + "links": [ + { + "rel": "type", + "href": "../thingmodels/does-not-exist.tm.jsonld", + "type": "application/tm+json" + } + ], + "securityDefinitions": { + "opcua_channel_sc": { + "scheme": "uav:channelsec", + "uav:securityMode": "SignAndEncrypt", + "uav:securityPolicy": "Aes256_Sha256_RsaPss" + } + }, + "security": "missing_sc", + "base": "opc.tcp://opcuademo.com:4840", + "properties": { + "pumpSpeed": { + "@type": "uav:variable", + "title": "Pump Speed", + "uav:browseName": "1:PumpSpeed", + "type": "number", + "unit": "rpm", + "readOnly": true, + "observable": true, + "forms": [ + { + "href": "/?id=nsu=http://example.com/demo/pump;s=PumpSpeed", + "contentType": "application/octet-stream", + "op": ["frobnicate"] + } + ] + } + } +} diff --git a/wot-specs/WoT-Connectivity/examples/04-refresh-results.json b/wot-specs/WoT-Connectivity/examples/04-refresh-results.json new file mode 100644 index 0000000..892e894 --- /dev/null +++ b/wot-specs/WoT-Connectivity/examples/04-refresh-results.json @@ -0,0 +1,94 @@ +{ + "$comment": "Representative JSON projection of the outputs of WoTRegistryType.Refresh (Summary + Results), mirroring WoTRefreshSummaryDataType and WoTResourceLoadResultDataType from Opc.Ua.WoTCon.NodeSet2.xml. Enumeration values are shown by their symbolic names; NodeIds and xids are strings; ContentDigest is base64. This run refreshed the whole registry after ingesting the PumpType Thing Model, the Pump 01 Thing Description, and the intentionally invalid pump-broken Thing Description.", + "requestId": "req-2026-07-20-0001", + "summary": { + "RequestId": "req-2026-07-20-0001", + "Generation": 7, + "Outcome": "Success", + "Atomicity": "PerClosure", + "StartTime": "2026-07-20T15:34:40Z", + "EndTime": "2026-07-20T15:34:41Z", + "Total": 3, + "Succeeded": 2, + "Unchanged": 0, + "Failed": 1, + "Skipped": 0, + "Retired": 1 + }, + "results": [ + { + "Xid": "/thingmodelgroups/fabrikam.pumps/thingmodels/pumptype/versions/1.2.0", + "GroupId": "fabrikam.pumps", + "ResourceId": "pumptype", + "VersionId": "1.2.0", + "Kind": "ThingModel", + "Outcome": "Success", + "Phase": "Activation", + "LoadState": "Active", + "Generation": 7, + "MaterializedNodeCount": 12, + "RootNodeId": "nsu=http://example.com/demo/pump;i=1001", + "ContentDigest": "3q2+7wAAAAA=", + "Message": "Projected Thing Model to ObjectType PumpType (1 mandatory Variable, 1 optional Method, 1 EventType)." + }, + { + "Xid": "/thingdescriptiongroups/fabrikam.factory-floor-01/thingdescriptions/pump-01/versions/1.0.0", + "GroupId": "fabrikam.factory-floor-01", + "ResourceId": "pump-01", + "VersionId": "1.0.0", + "Kind": "ThingDescription", + "Outcome": "Success", + "Phase": "Activation", + "LoadState": "Active", + "Generation": 7, + "MaterializedNodeCount": 5, + "RootNodeId": "nsu=http://example.com/demo/pump;s=Pump", + "ContentDigest": "98AF8w6M2sQ=", + "Message": "Projected Thing Description to Object Pump; 3 forms bound to the OPC UA binding." + }, + { + "Xid": "/thingdescriptiongroups/fabrikam.factory-floor-01/thingdescriptions/pump-broken/versions/1.0.0", + "GroupId": "fabrikam.factory-floor-01", + "ResourceId": "pump-broken", + "VersionId": "1.0.0", + "Kind": "ThingDescription", + "Outcome": "Failed", + "Phase": "FormatValidation", + "LoadState": "Failed", + "Generation": 7, + "MaterializedNodeCount": 0, + "RootNodeId": "i=0", + "ContentDigest": "AAECAwQFBgc=", + "Message": "Format validation failed: missing 'title'; 'security' names undefined scheme 'missing_sc'; invalid form op 'frobnicate'." + } + ], + "validationOutcomes": { + "pump-broken": { + "FormatValidated": true, + "FormatOutcome": "Failed", + "FormatReason": "missing 'title'; 'security' names undefined scheme 'missing_sc'; invalid form op 'frobnicate'", + "CompatibilityValidated": false, + "CompatibilityOutcome": "Skipped", + "CompatibilityReason": "", + "CompatibilityPolicy": "BACKWARD", + "ValidatedAt": "2026-07-20T15:34:41Z", + "VocabularyVersion": "http://opcfoundation.org/UA/WoT-Binding/@2026-07-16" + } + }, + "dependencies": [ + { + "SourceXid": "/thingdescriptiongroups/fabrikam.factory-floor-01/thingdescriptions/pump-01/versions/1.0.0", + "TargetXid": "/thingmodelgroups/fabrikam.pumps/thingmodels/pumptype/versions/1.2.0", + "TargetUri": "../01-thing-model-pump.tm.jsonld", + "RefType": "links.rel=type", + "Resolved": true + }, + { + "SourceXid": "/thingdescriptiongroups/fabrikam.factory-floor-01/thingdescriptions/pump-broken/versions/1.0.0", + "TargetXid": "", + "TargetUri": "../thingmodels/does-not-exist.tm.jsonld", + "RefType": "links.rel=type", + "Resolved": false + } + ] +} diff --git a/wot-specs/WoT-Connectivity/legacy/README.md b/wot-specs/WoT-Connectivity/legacy/README.md new file mode 100644 index 0000000..35f93ec --- /dev/null +++ b/wot-specs/WoT-Connectivity/legacy/README.md @@ -0,0 +1,10 @@ +# Pinned OPC 10100-1 v1.02 sources + +These files are the **pinned authoring sources** for the published OPC 10100-1 v1.02 WoT Connectivity model. They are a **source input** to `../tools/build_model.py`, which parses them and incorporates the 1.02 nodes into the combined `Opc.Ua.WoTCon.NodeSet2.xml` at their exact published NodeIds — they are **not** hand-copied output. + +- `WotConnection.xml` — the UA ModelDesign for the 1.02 model (type bases, method signatures, references). +- `WotConnection.csv` — the authoritative NodeId / NodeClass table (`SymbolicName,NodeId,NodeClass`) for every published node `1..172`, including reserved (`Unspecified`) ids. + +Do not edit these files to change the generated model; they pin the published baseline. `../tools/validate_local.py` proves the preservation by comparing the first 172 rows of the generated CSV against `WotConnection.csv` byte-for-byte and checking that every concrete legacy id appears in the NodeSet with its pinned NodeClass. + +Origin: `src/Opc.Ua.WotCon/Design/WotConnection.{xml,csv}` in the WoT Connectivity model assembly. diff --git a/wot-specs/WoT-Connectivity/legacy/WotConnection.csv b/wot-specs/WoT-Connectivity/legacy/WotConnection.csv new file mode 100644 index 0000000..be490cf --- /dev/null +++ b/wot-specs/WoT-Connectivity/legacy/WotConnection.csv @@ -0,0 +1,172 @@ +WoTAssetConnectionManagementType,1,ObjectType +WoTAssetConnectionManagementType_WoTAssetName_Placeholder,2,Object +WotConNamespaceMetadata_NamespaceFile,3,Object +WotConNamespaceMetadata_NamespaceFile_Size,4,Variable +WotConNamespaceMetadata_NamespaceFile_Writable,5,Variable +WotConNamespaceMetadata_NamespaceFile_UserWritable,6,Variable +WotConNamespaceMetadata_NamespaceFile_OpenCount,7,Variable +WotConNamespaceMetadata_NamespaceFile_MimeType,8,Variable +WotConNamespaceMetadata_NamespaceFile_MaxByteStringLength,9,Variable +WotConNamespaceMetadata_NamespaceFile_LastModifiedTime,10,Variable +WotConNamespaceMetadata_NamespaceFile_Open,11,Method +WotConNamespaceMetadata_NamespaceFile_Open_InputArguments,12,Variable +WotConNamespaceMetadata_NamespaceFile_Open_OutputArguments,13,Variable +WotConNamespaceMetadata_NamespaceFile_Close,14,Method +WotConNamespaceMetadata_NamespaceFile_Close_InputArguments,15,Variable +WotConNamespaceMetadata_NamespaceFile_Read,16,Method +WotConNamespaceMetadata_NamespaceFile_Read_InputArguments,17,Variable +WotConNamespaceMetadata_NamespaceFile_Read_OutputArguments,18,Variable +WotConNamespaceMetadata_NamespaceFile_Write,19,Method +WotConNamespaceMetadata_NamespaceFile_Write_InputArguments,20,Variable +WotConNamespaceMetadata_NamespaceFile_GetPosition,21,Method +WotConNamespaceMetadata_NamespaceFile_GetPosition_InputArguments,22,Variable +WotConNamespaceMetadata_NamespaceFile_GetPosition_OutputArguments,23,Variable +WotConNamespaceMetadata_NamespaceFile_SetPosition,24,Method +WotConNamespaceMetadata_NamespaceFile_SetPosition_InputArguments,25,Variable +WoTAssetConnectionManagementType_CreateAsset,26,Method +WoTAssetConnectionManagementType_CreateAsset_InputArguments,27,Variable +WoTAssetConnectionManagementType_CreateAsset_OutputArguments,28,Variable +WoTAssetConnectionManagementType_DeleteAsset,29,Method +WoTAssetConnectionManagementType_DeleteAsset_InputArguments,30,Variable +WoTAssetConnectionManagement,31,Object +WoTAssetConnectionManagement_CreateAsset,32,Method +WoTAssetConnectionManagement_CreateAsset_InputArguments,33,Variable +WoTAssetConnectionManagement_CreateAsset_OutputArguments,34,Variable +WoTAssetConnectionManagement_DeleteAsset,35,Method +WoTAssetConnectionManagement_DeleteAsset_InputArguments,36,Variable +WotConNamespaceMetadata_NamespaceFile_ExportNamespace,37,Method +WotConNamespaceMetadata_ConfigurationVersion,38,Variable +WotConNamespaceMetadata_ModelVersion,39,Variable +WoTAssetConnectionManagementType_SupportedWoTBindings,40,Variable +WoTAssetConnectionManagementType_DiscoverAssets,41,Method +IWoTAssetType,42,ObjectType +IWoTAssetType_WoTFile,43,Object +IWoTAssetType_WoTFile_Size,44,Variable +IWoTAssetType_WoTFile_Writable,45,Variable +IWoTAssetType_WoTFile_UserWritable,46,Variable +IWoTAssetType_WoTFile_OpenCount,47,Variable +WoTAssetConnectionManagementType_DiscoverAssets_OutputArguments,48,Variable +WoTAssetConnectionManagementType_CreateAssetForEndpoint,49,Method +WoTAssetConnectionManagementType_CreateAssetForEndpoint_InputArguments,50,Variable +IWoTAssetType_WoTFile_Open,51,Method +IWoTAssetType_WoTFile_Open_InputArguments,52,Variable +IWoTAssetType_WoTFile_Open_OutputArguments,53,Variable +IWoTAssetType_WoTFile_Close,54,Method +IWoTAssetType_WoTFile_Close_InputArguments,55,Variable +IWoTAssetType_WoTFile_Read,56,Method +IWoTAssetType_WoTFile_Read_InputArguments,57,Variable +IWoTAssetType_WoTFile_Read_OutputArguments,58,Variable +IWoTAssetType_WoTFile_Write,59,Method +IWoTAssetType_WoTFile_Write_InputArguments,60,Variable +IWoTAssetType_WoTFile_GetPosition,61,Method +IWoTAssetType_WoTFile_GetPosition_InputArguments,62,Variable +IWoTAssetType_WoTFile_GetPosition_OutputArguments,63,Variable +IWoTAssetType_WoTFile_SetPosition,64,Method +IWoTAssetType_WoTFile_SetPosition_InputArguments,65,Variable +IWoTAssetType_WoTPropertyName_Placeholder,66,Variable +WotConNamespaceMetadata,67,Object +WotConNamespaceMetadata_NamespaceUri,68,Variable +WotConNamespaceMetadata_NamespaceVersion,69,Variable +WotConNamespaceMetadata_NamespacePublicationDate,70,Variable +WotConNamespaceMetadata_IsNamespaceSubset,71,Variable +WotConNamespaceMetadata_StaticNodeIdTypes,72,Variable +WotConNamespaceMetadata_StaticNumericNodeIdRange,73,Variable +WotConNamespaceMetadata_StaticStringNodeIdPattern,74,Variable +WoTAssetConnectionManagementType_ConnectionTest,75,Method +WoTAssetConnectionManagementType_ConnectionTest_InputArguments,76,Variable +WoTAssetConnectionManagementType_ConnectionTest_OutputArguments,77,Variable +WoTAssetConnectionManagementType_Configuration,78,Object +WoTAssetConnectionManagementType_Configuration_License,79,Variable +WoTAssetConnectionManagement_SupportedWoTBindings,80,Variable +WoTAssetConnectionManagement_DiscoverAssets,81,Method +WoTAssetConnectionManagement_DiscoverAssets_OutputArguments,82,Variable +WoTAssetConnectionManagement_CreateAssetForEndpoint,83,Method +WoTAssetConnectionManagement_CreateAssetForEndpoint_InputArguments,84,Variable +WoTAssetConnectionManagement_ConnectionTest,85,Method +WoTAssetConnectionManagement_ConnectionTest_InputArguments,86,Variable +WoTAssetConnectionManagement_ConnectionTest_OutputArguments,87,Variable +WoTAssetConnectionManagement_Configuration,88,Object +WoTAssetConnectionManagement_Configuration_License,89,Variable +CreateAssetMethodType,90,Method +CreateAssetMethodType_InputArguments,91,Variable +CreateAssetMethodType_OutputArguments,92,Variable +DeleteAssetMethodType,93,Method +DeleteAssetMethodType_InputArguments,94,Variable +DiscoverAssetsMethodType,95,Method +DiscoverAssetsMethodType_OutputArguments,96,Variable +CreateAssetForEndpointMethodType,97,Method +CreateAssetForEndpointMethodType_InputArguments,98,Variable +WotConNamespaceMetadata_DefaultRolePermissions,99,Variable +WotConNamespaceMetadata_DefaultUserRolePermissions,100,Variable +WotConNamespaceMetadata_DefaultAccessRestrictions,101,Variable +ConnectionTestMethodType,102,Method +ConnectionTestMethodType_InputArguments,103,Variable +ConnectionTestMethodType_OutputArguments,104,Variable +WoTAssetConfigurationType,105,ObjectType +IWoTAssetType_WoTFile_CloseAndUpdate,106,Method +IWoTAssetType_WoTFile_CloseAndUpdate_InputArguments,107,Variable +WoTAssetConfigurationType_WoTConfigurationParameterName_Placeholder,108,Variable +WoTAssetConfigurationType_License,109,Variable +WoTAssetFileType,110,ObjectType +WoTAssetFileType_CloseAndUpdate,111,Method +WoTAssetFileType_CloseAndUpdate_InputArguments,112,Variable +IWoTAssetType_WoTFile_MimeType,113,Variable +IWoTAssetType_WoTFile_MaxByteStringLength,114,Variable +WoTAssetType,115,Unspecified +WoTAssetType_WoTFile,116,Unspecified +WoTAssetType_WoTFile_Size,117,Unspecified +WoTAssetType_WoTFile_Writable,118,Unspecified +WoTAssetType_WoTFile_UserWritable,119,Unspecified +WoTAssetType_WoTFile_OpenCount,120,Unspecified +IWoTAssetType_WoTFile_LastModifiedTime,121,Variable +IWoTAssetType_AssetEndpoint,122,Variable +CloseAndUpdateMethodType,123,Method +WoTAssetType_WoTFile_Open,124,Unspecified +WoTAssetType_WoTFile_Open_InputArguments,125,Unspecified +WoTAssetType_WoTFile_Open_OutputArguments,126,Unspecified +WoTAssetType_WoTFile_Close,127,Unspecified +WoTAssetType_WoTFile_Close_InputArguments,128,Unspecified +WoTAssetType_WoTFile_Read,129,Unspecified +WoTAssetType_WoTFile_Read_InputArguments,130,Unspecified +WoTAssetType_WoTFile_Read_OutputArguments,131,Unspecified +WoTAssetType_WoTFile_Write,132,Unspecified +WoTAssetType_WoTFile_Write_InputArguments,133,Unspecified +WoTAssetType_WoTFile_GetPosition,134,Unspecified +WoTAssetType_WoTFile_GetPosition_InputArguments,135,Unspecified +WoTAssetType_WoTFile_GetPosition_OutputArguments,136,Unspecified +WoTAssetType_WoTFile_SetPosition,137,Unspecified +WoTAssetType_WoTFile_SetPosition_InputArguments,138,Unspecified +WoTAssetType_WoTFile_CloseAndUpdate,139,Unspecified +WoTAssetType_WoTFile_CloseAndUpdate_InputArguments,140,Unspecified +WoTAssetType_WoTPropertyName_Placeholder,141,Unspecified +HasWoTComponent,142,ReferenceType +CloseAndUpdateMethodType_InputArguments,143,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile,144,Object +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Size,145,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Writable,146,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_UserWritable,147,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_OpenCount,148,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_MimeType,149,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_MaxByteStringLength,150,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_LastModifiedTime,151,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Open,152,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Open_InputArguments,153,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Open_OutputArguments,154,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Close,155,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Close_InputArguments,156,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Read,157,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Read_InputArguments,158,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Read_OutputArguments,159,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Write,160,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_Write_InputArguments,161,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_GetPosition,162,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_GetPosition_InputArguments,163,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_GetPosition_OutputArguments,164,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_SetPosition,165,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_SetPosition_InputArguments,166,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_CloseAndUpdate,167,Method +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_WoTFile_CloseAndUpdate_InputArguments,168,Variable +WoTAssetConnectionManagementType_WoTAssetName_Placeholder_AssetEndpoint,169,Variable +WoTAssetConnectionManagementType_CreateAssetForEndpoint_OutputArguments,170,Variable +WoTAssetConnectionManagement_CreateAssetForEndpoint_OutputArguments,171,Variable +CreateAssetForEndpointMethodType_OutputArguments,172,Variable diff --git a/wot-specs/WoT-Connectivity/legacy/WotConnection.xml b/wot-specs/WoT-Connectivity/legacy/WotConnection.xml new file mode 100644 index 0000000..4ce5a88 --- /dev/null +++ b/wot-specs/WoT-Connectivity/legacy/WotConnection.xml @@ -0,0 +1,188 @@ + + + + + http://opcfoundation.org/UA/WoT-Con/ + http://opcfoundation.org/UA/ + + + + http://opcfoundation.org/UA/WoT-Con/ + + + + http://opcfoundation.org/UA/WoT-Con/ + + + + + 1.02.0 + + + + + 1.2.0 + + + + + 2025-12-05 + + + + + false + + + + + + 0 + + + + + + + + + + + + + + + + ua:HasComponent + ua:Server_Namespaces + + + + + + + + <WoTAssetName> + + + + + + + ua:HasInterface + IWoTAssetType + + + HasWoTComponent + IWoTAssetType_WoTPropertyName_Placeholder + + + ua:Organizes + + + + + + + + + + + + + + + ua:Organizes + ua:ObjectsFolder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <WoTConfigurationParameterName> + + + + + + + + + + + <WoTPropertyName> + HasWoTComponent + + + + + + WoTComponentOf + + + + + + + + + + + + + + + diff --git a/wot-specs/WoT-Connectivity/tools/build_model.py b/wot-specs/WoT-Connectivity/tools/build_model.py new file mode 100644 index 0000000..6b3d48b --- /dev/null +++ b/wot-specs/WoT-Connectivity/tools/build_model.py @@ -0,0 +1,1596 @@ +#!/usr/bin/env python3 +""" +Generator for the OPC UA WoT Connectivity 1.1 companion NodeSet (WG draft). + +Emits, from a single deterministic source of truth (in-code registry model plus +the pinned legacy sources under legacy/): + * Opc.Ua.WoTCon.NodeSet2.xml - the combined machine-readable information model + * Opc.Ua.WoTCon.NodeIds.csv - the numeric NodeId assignments + * tools/model-reference.md - the generated Annex A node reference + +WoT Connectivity 1.1 is an *additive* revision of OPC 10100-1 (WoT Connectivity, +namespace http://opcfoundation.org/UA/WoT-Con/, published baseline v1.02.0). It +keeps the same NamespaceUri and incorporates the full published 1.02 model into +one combined NodeSet, then adds a registry-first document-registry layer over +the abstract OPC UA xRegistry base model: a WoTRegistryType RegistryType holds +ThingDescriptionGroupType / ThingModelGroupType groups whose files are +ThingDescriptionFileType / ThingModelFileType resources (concrete subtypes of +the abstract WoTDocumentType xRegistry ResourceType). The registry files and +versions are canonical; the projected AddressSpace (types from Thing Models, +instances from Thing Descriptions) is derived and refreshed. + +Namespace layout inside this combined NodeSet: + index 0 (implicit) : http://opcfoundation.org/UA/ (Core) + index 1 : http://opcfoundation.org/UA/xRegistry/ (xRegistry base, RequiredModel) + index 2 : http://opcfoundation.org/UA/WoT-Con/ (this spec, own - legacy + registry) + +The published 1.02 model is emitted in the SAME own namespace (index 2). Because +xRegistry occupies index 1 in this combined document, the 1.02 nodes are the +own-namespace nodes at index 2; every published numeric NodeId (1..172) and +NodeClass is preserved exactly from the pinned legacy/WotConnection.csv table, +and the legacy management/upload surface is marked ReleaseStatus="Deprecated" +(machine-readable, per OPC 11030) without being removed. The additive registry +types/members use a non-conflicting 64000+ block. The legacy sources are a +source INPUT (parsed at generation time), not hand-copied output. + +The revised WoT Binding JSON-LD vocabulary (http://opcfoundation.org/UA/WoT-Binding/) +is a normative dependency but NOT a NodeSet RequiredModel - it is a JSON-LD +vocabulary, not an OPC UA information model. + +Additive registry numeric identifiers are PROVISIONAL and drawn from a dedicated +64000+ block (types) with members allocated append-only from 64500; final NodeIds +are assigned by the OPC Foundation. The 64000 block was chosen to avoid the ranges +already used by sibling drafts in this repository (Generators 1001-6xxx, +Schema Registry 62000, xRegistry 63000) and does not overlap any published +OPC Foundation range or the preserved 1.02 range (1..172). +""" +from __future__ import annotations +import os +import re +import xml.sax.saxutils as sx +import xml.etree.ElementTree as ET + +# --------------------------------------------------------------------------- +# Well-known base NodeIds (Core, ns=0) +# --------------------------------------------------------------------------- +HasComponent = "i=47" +HasProperty = "i=46" +HasSubtype = "i=45" +Organizes = "i=35" +HasTypeDefinition = "i=40" +HasModellingRule = "i=37" +HasEncoding = "i=38" +GeneratesEvent = "i=41" +HasNotifier = "i=48" + +MR_Mandatory = "i=78" +MR_Optional = "i=80" +MR_OptionalPlaceholder = "i=11508" + +BaseObjectType = "i=58" +FolderType = "i=61" +BaseDataVariableType = "i=63" +PropertyType = "i=68" +DataTypeEncodingType = "i=76" +BaseEventType = "i=2041" +NonHierarchicalReferences = "i=32" + +Boolean = "i=1" +UInt32 = "i=7" +UInt64 = "i=9" +Double = "i=11" +String = "i=12" +DateTime = "i=13" +ByteString = "i=15" +NodeId = "i=17" +ExpandedNodeId = "i=18" +LocalizedText = "i=21" +Structure = "i=22" +Enumeration = "i=29" +Duration = "i=290" +Argument = "i=296" + +Server = "i=2253" + +# --- additional Core (ns=0) NodeIds used by the incorporated 1.02 legacy model --- +Byte = "i=3" +UInt16 = "i=5" +Int32 = "i=6" +BaseDataType = "i=24" +UtcTime = "i=294" +IdType = "i=256" +NumericRange = "i=291" +RolePermissionType = "i=96" +AccessRestrictionType = "i=95" +SemanticVersionString = "i=24263" +VersionTime = "i=20998" +UriString = "i=23751" +BaseInterfaceType = "i=17602" +HasInterface = "i=17603" +FileType = "i=11575" +NamespaceMetadataType = "i=11616" +ObjectsFolder = "i=85" +Server_Namespaces = "i=11715" +MR_Mandatory_ = MR_Mandatory +MR_MandatoryPlaceholder = "i=11510" + +# --------------------------------------------------------------------------- +# Namespace indices +# --------------------------------------------------------------------------- +XR_NS = 1 # required model: abstract xRegistry base +OWN_NS = 2 # this specification's own namespace (WoT-Con, index 2) +OWN_MIN = 64000 +_next_member = [64500] + + +def T(nid): + return f"ns={OWN_NS};i={nid}" + + +def X(nid): + """Reference to an abstract xRegistry base type (required model, ns=1).""" + return f"ns={XR_NS};i={nid}" + + +# abstract xRegistry base types this spec extends +XRegistry_RegistryType = X(63000) +XRegistry_GroupType = X(63001) +XRegistry_ResourceType = X(63002) + + +class Node: + __slots__ = ("nid", "cls", "bname", "symbolic", "display", "desc", "parent", + "attrs", "refs", "category", "definition", "value", "abstract", + "inverse") + + def __init__(self, nid, cls, bname, symbolic, display=None, desc=None, + parent=None, attrs=None, category=None, abstract=False, inverse=None): + self.nid = nid + self.cls = cls + self.bname = bname + self.symbolic = symbolic + self.display = display or bname + self.desc = desc + self.parent = parent + self.attrs = attrs or {} + self.refs = [] + self.category = category + self.definition = None + self.value = None + self.abstract = abstract + self.inverse = inverse + + +NODES = {} +ORDER = [] +ENUM_FIELDS = {} +STRUCT_FIELDS = {} + + +def _mid(): + v = _next_member[0] + _next_member[0] += 1 + return v + + +def add(nid, cls, bname, symbolic, display=None, desc=None, parent=None, + attrs=None, category=None, abstract=False, inverse=None): + n = Node(nid, cls, bname, symbolic, display, desc, parent, attrs, category, + abstract, inverse) + NODES[nid] = n + ORDER.append(nid) + return n + + +def ref(nid, reftype, target, forward=True): + NODES[nid].refs.append((reftype, target, forward)) + + +# --------------------------------------------------------------------------- +# Builders +# --------------------------------------------------------------------------- +def object_type(nid, name, base, desc, category, abstract=False): + add(nid, "UAObjectType", name, name, desc=desc, category=category, abstract=abstract) + ref(nid, HasSubtype, base, forward=False) + return nid + + +def event_type(nid, name, base, desc, category, abstract=False): + add(nid, "UAObjectType", name, name, desc=desc, category=category, abstract=abstract) + ref(nid, HasSubtype, base, forward=False) + return nid + + +def reference_type(nid, name, base, inverse, desc, category, abstract=False): + add(nid, "UAReferenceType", name, name, desc=desc, category=category, + abstract=abstract, inverse=inverse) + ref(nid, HasSubtype, base, forward=False) + return nid + + +def _member_var(owner, owner_sym, name, datatype, typedef, rule, reftype, desc, + valuerank="-1"): + nid = _mid() + attrs = {"DataType": datatype, "ValueRank": valuerank} + if valuerank == "1": + attrs["ArrayDimensions"] = "0" + add(nid, "UAVariable", name, f"{owner_sym}_{name.strip('<>')}", desc=desc, + parent=T(owner), attrs=attrs) + ref(nid, HasModellingRule, rule) + ref(nid, HasTypeDefinition, typedef) + ref(nid, reftype, T(owner), forward=False) + ref(owner, reftype, T(nid)) + return nid + + +def prop_var(owner, owner_sym, name, datatype, desc, rule=MR_Optional, valuerank="-1"): + return _member_var(owner, owner_sym, name, datatype, PropertyType, rule, + HasProperty, desc, valuerank) + + +def obj_member(owner, owner_sym, name, typedef, desc, rule=MR_Optional, + reftype=HasComponent): + nid = _mid() + add(nid, "UAObject", name, f"{owner_sym}_{name.strip('<>')}", desc=desc, + parent=T(owner)) + ref(nid, HasModellingRule, rule) + ref(nid, HasTypeDefinition, typedef) + ref(nid, reftype, T(owner), forward=False) + ref(owner, reftype, T(nid)) + return nid + + +def placeholder_obj(owner, owner_sym, name, typedef, desc, + rule=MR_OptionalPlaceholder, reftype=Organizes): + return obj_member(owner, owner_sym, name, typedef, desc, rule, reftype) + + +def event_field(owner, owner_sym, name, datatype, desc, rule=MR_Mandatory, valuerank="-1"): + """An event type field: a HasProperty PropertyType Variable of the event type.""" + return _member_var(owner, owner_sym, name, datatype, PropertyType, rule, + HasProperty, desc, valuerank) + + +def generates_event(owner, event_nid): + ref(owner, GeneratesEvent, T(event_nid)) + + +def method(owner, owner_sym, name, desc, rule=MR_Optional, inargs=None, outargs=None): + nid = _mid() + add(nid, "UAMethod", name, f"{owner_sym}_{name}", desc=desc, parent=T(owner)) + ref(nid, HasModellingRule, rule) + ref(nid, HasComponent, T(owner), forward=False) + ref(owner, HasComponent, T(nid)) + if inargs: + _args(nid, f"{owner_sym}_{name}", "InputArguments", inargs) + if outargs: + _args(nid, f"{owner_sym}_{name}", "OutputArguments", outargs) + return nid + + +def instance_method(owner, owner_sym, name, decl_nid, desc, inargs=None, outargs=None): + """Materialize a concrete (instance) method under a well-known instance object.""" + nid = _mid() + add(nid, "UAMethod", name, f"{owner_sym}_{name}", desc=desc, parent=T(owner), + category=CAT_INST, attrs={"MethodDeclarationId": T(decl_nid)}) + ref(nid, HasComponent, T(owner), forward=False) + ref(owner, HasComponent, T(nid)) + if inargs: + _args(nid, f"{owner_sym}_{name}", "InputArguments", inargs, instance=True) + if outargs: + _args(nid, f"{owner_sym}_{name}", "OutputArguments", outargs, instance=True) + return nid + + +def _scalar_value(uax_type, value): + """A fragment for a scalar built-in-typed Property, e.g. String or UInt32.""" + return (f'' + f'{sx.escape(str(value))}') + + +def instance_var(owner, owner_sym, name, datatype, desc, value_xml): + """Materialize a concrete (instance) Property Variable with a Value under a well-known + instance object. Used to make every Mandatory member (own or inherited) of a well-known + instance's type present at load time, since a well-known instance carries values rather + than a HasModellingRule declaration.""" + nid = _mid() + add(nid, "UAVariable", name, f"{owner_sym}_{name}", desc=desc, parent=T(owner), + category=CAT_INST, attrs={"DataType": datatype, "ValueRank": "-1"}) + ref(nid, HasTypeDefinition, PropertyType) + ref(nid, HasProperty, T(owner), forward=False) + ref(owner, HasProperty, T(nid)) + NODES[nid].value = value_xml + return nid + + +def _args(method_nid, method_sym, bname, args, instance=False): + nid = _mid() + add(nid, "UAVariable", bname, f"{method_sym}_{bname}", parent=T(method_nid), + attrs={"DataType": Argument, "ValueRank": "1", "ArrayDimensions": str(len(args)), + "_ns0bn": True}, + category=(CAT_INST if instance else None)) + if not instance: + ref(nid, HasModellingRule, MR_Mandatory) + ref(nid, HasTypeDefinition, PropertyType) + ref(nid, HasProperty, T(method_nid), forward=False) + ref(method_nid, HasProperty, T(nid)) + parts = ['', ''] + for arg in args: + aname, adtype, adesc = arg[0], arg[1], arg[2] + arank = arg[3] if len(arg) > 3 else -1 + parts.append("i=297") + parts.append(f"{sx.escape(aname)}{adtype}") + if arank is not None and arank >= 0: + parts.append(f"{arank}0") + else: + parts.append("-1") + if adesc: + parts.append(f"{sx.escape(adesc)}") + parts.append("") + parts.append("") + NODES[nid].value = "".join(parts) + + +def enum_type(nid, name, desc, category, fields): + """fields: list of (fieldname, value_int, description).""" + add(nid, "UADataType", name, name, desc=desc, category=category) + ref(nid, HasSubtype, Enumeration, forward=False) + ENUM_FIELDS[nid] = fields + dparts = [f''] + for (fname, val, fdesc) in fields: + if fdesc: + dparts.append(f'') + dparts.append(f'{sx.escape(fdesc)}') + else: + dparts.append(f'') + dparts.append("") + NODES[nid].definition = "".join(dparts) + es = _mid() + ref(nid, HasProperty, T(es)) + add(es, "UAVariable", "EnumStrings", f"{name}_EnumStrings", parent=T(nid), + attrs={"DataType": LocalizedText, "ValueRank": "1", "ArrayDimensions": str(len(fields)), + "_ns0bn": True}) + ref(es, HasModellingRule, MR_Mandatory) + ref(es, HasTypeDefinition, PropertyType) + ref(es, HasProperty, T(nid), forward=False) + vp = ['', ''] + for (fname, val, fdesc) in fields: + vp.append(f"{sx.escape(fname)}") + vp.append("") + NODES[es].value = "".join(vp) + return nid + + +def struct_type(nid, name, fields, desc, category, base=Structure): + """fields: list of (fieldname, datatype, description, valuerank) - valuerank optional (default -1).""" + add(nid, "UADataType", name, name, desc=desc, category=category) + ref(nid, HasSubtype, base, forward=False) + STRUCT_FIELDS[nid] = fields + parts = [f''] + for f in fields: + fname, fdt, fdesc = f[0], f[1], f[2] + frank = f[3] if len(f) > 3 else -1 + attrs = f'Name="{sx.escape(fname)}" DataType="{fdt}"' + if frank is not None and frank >= 0: + attrs += f' ValueRank="{frank}"' + parts.append(f"") + if fdesc: + parts.append(f"{sx.escape(fdesc)}") + parts.append("") + parts.append("") + NODES[nid].definition = "".join(parts) + for enc in ("Binary", "JSON"): + enc_nid = _mid() + add(enc_nid, "UAObject", f"Default {enc}", f"{name}_Default{enc}", parent=T(nid), + attrs={"_ns0bn": True}) + ref(enc_nid, HasTypeDefinition, DataTypeEncodingType) + ref(enc_nid, HasEncoding, T(nid), forward=False) + ref(nid, HasEncoding, T(enc_nid)) + return nid + + +# --------------------------------------------------------------------------- +# xRegistry attributes common to a registry/group/resource entity (ns=1 base +# types already declare them; concrete WoT subtypes inherit them and add their +# own members below, so we do NOT re-declare the inherited attributes here). +# --------------------------------------------------------------------------- + +# =========================================================================== +# ============================== MODEL DEFINITION ========================= +# =========================================================================== +CAT = "WoT Connectivity 1.1" +CAT_DT = "WoT Connectivity 1.1 DataTypes" +CAT_EV = "WoT Connectivity 1.1 Events" +CAT_REF = "WoT Connectivity 1.1 References" +CAT_INST = "WoT Connectivity 1.1 Instances" + +# --- DataTypes: enumerations ---------------------------------------------- +enum_type(64020, "WoTDocumentKindEnum", + "The kind of WoT document a resource carries: a Thing Description (a concrete instance) " + "or a Thing Model (a reusable type template).", CAT_DT, + [("ThingDescription", 0, "A W3C WoT Thing Description (WoT-TD/1.1); projects to OPC UA instances."), + ("ThingModel", 1, "A W3C WoT Thing Model (WoT-TM/1.1); projects to OPC UA types.")]) + +enum_type(64021, "WoTLoadStateEnum", + "The lifecycle state of a WoT document's derived projection in the AddressSpace. The registry " + "file always remains stored; this enum reflects only the state of the code-behind projection.", CAT_DT, + [("Unloaded", 0, "Stored but not projected into the AddressSpace."), + ("Validating", 1, "Format and compatibility validation is in progress."), + ("Loading", 2, "The projection is being materialized under a shadow generation."), + ("Active", 3, "The projection is committed and serving as the active generation."), + ("Failed", 4, "Validation or projection failed; the last valid projection (if any) stays active."), + ("Superseded", 5, "A newer generation has replaced this one; awaiting the configured retirement policy."), + ("Retiring", 6, "Graceful retirement is waiting for monitored items and requests to drain."), + ("Retired", 7, "The projection has been removed from the AddressSpace.")]) + +enum_type(64022, "WoTRefreshModeEnum", + "How a registry or document triggers refresh of its derived projection.", CAT_DT, + [("Manual", 0, "Only an explicit Refresh Method call re-projects."), + ("Periodic", 1, "The registry re-projects on a fixed interval (RefreshInterval)."), + ("EventDriven", 2, "The registry re-projects when a stored document changes (write/CloseAndUpdate)."), + ("Scheduled", 3, "The registry re-projects on an implementation-defined schedule.")]) + +enum_type(64023, "WoTAtomicityEnum", + "The commit granularity applied when a refresh projects one or more documents.", CAT_DT, + [("PerResource", 0, "Each resource commits independently; a failure isolates to that resource."), + ("PerGroup", 1, "All resources of a group commit together or not at all."), + ("PerClosure", 2, "A document and its full dependency closure (DAG) commit atomically."), + ("PerRegistry", 3, "All selected documents commit as a single all-or-nothing transaction.")]) + +enum_type(64024, "WoTDeletePolicyEnum", + "How the registry treats dependents when a document version is unloaded or deleted.", CAT_DT, + [("Reject", 0, "Reject the operation while any other loaded document still depends on it."), + ("Retire", 1, "Retire the projection but keep the stored document for dependents to resolve."), + ("Cascade", 2, "Unload dependents that resolve only through this document."), + ("Force", 3, "Force-unload the projection even while dependents remain, marking them Failed.")]) + +enum_type(64025, "WoTOutcomeEnum", + "The outcome of a validation, projection or refresh operation on a document or the registry.", CAT_DT, + [("Success", 0, "The operation completed and changed the projection."), + ("Unchanged", 1, "The operation was idempotent; the content digest matched and nothing changed."), + ("Warning", 2, "The operation completed with non-fatal warnings."), + ("Skipped", 3, "The operation was not applicable and was skipped."), + ("Rejected", 4, "The operation was rejected by policy (for example concurrency or delete policy)."), + ("Failed", 5, "The operation failed; the previous valid projection (if any) remains active.")]) + +enum_type(64026, "WoTPhaseEnum", + "The processing phase a document reached, used to locate where an outcome was produced.", CAT_DT, + [("Fetch", 0, "Reading document bytes and resolving registry-scoped context/schema references."), + ("Parse", 1, "Parsing the JSON-LD document."), + ("FormatValidation", 2, "Validating the document against its WoT-TD/WoT-TM format."), + ("CompatibilityValidation", 3, "Validating the version against the resource compatibility policy."), + ("DependencyResolution", 4, "Resolving the dependency closure (tm:extends, tm:ref, links rel=type)."), + ("Projection", 5, "Materializing types/instances into a shadow generation."), + ("Activation", 6, "Committing the shadow generation as active."), + ("Retirement", 7, "Applying the configured graceful or immediate retirement policy.")]) + +enum_type(64027, "WoTBindingCapabilityEnum", + "A single interaction operation a protocol binding supports, aligned with the WoT form op vocabulary.", CAT_DT, + [("ReadProperty", 0, "Read a property affordance."), + ("WriteProperty", 1, "Write a property affordance."), + ("ObserveProperty", 2, "Observe (subscribe to) a property affordance."), + ("InvokeAction", 3, "Invoke an action affordance."), + ("SubscribeEvent", 4, "Subscribe to an event affordance."), + ("UnsubscribeEvent", 5, "Unsubscribe from an event affordance.")]) + +# --- DataTypes: structures ------------------------------------------------- +struct_type(64040, "WoTValidationOutcomeDataType", + [("FormatValidated", Boolean, "True if format validation was performed."), + ("FormatOutcome", T(64025), "Outcome of format validation (WoT-TD/WoT-TM conformance)."), + ("FormatReason", String, "Human-readable reason for the format outcome (empty on success)."), + ("CompatibilityValidated", Boolean, "True if compatibility validation was performed."), + ("CompatibilityOutcome", T(64025), "Outcome of compatibility validation against the resource policy."), + ("CompatibilityReason", String, "Human-readable reason for the compatibility outcome (empty on success)."), + ("CompatibilityPolicy", String, "The compatibility policy in force (for example NONE, BACKWARD, FULL)."), + ("ValidatedAt", DateTime, "UTC time the validation completed."), + ("VocabularyVersion", String, "The pinned WoT Binding JSON-LD vocabulary version used for validation.")], + "An immutable snapshot of a document's format and compatibility validation result. Read as a single " + "Variant value; a new snapshot is produced on each validation and never mutated in place.", CAT_DT) + +struct_type(64041, "WoTBindingCapabilityDataType", + [("BindingUri", String, "The WoT protocol-binding vocabulary URI (for example the OPC UA, HTTP or Modbus binding)."), + ("Title", String, "Human-readable binding title."), + ("ProfileVersion", String, "The version-pinned W3C binding document version this capability snapshot was built against."), + ("DraftMaturity", String, "The W3C maturity of the pinned binding document (for example WD, CR, PR, REC)."), + ("Capabilities", T(64027), "The interaction operations this binding supports.", 1), + ("ContentTypes", String, "The content types this binding produces/consumes.", 1)], + "An immutable snapshot of a protocol binding's identity, version-pinned W3C document, maturity and " + "supported operations. Held as an array element only for immutable snapshots; browseable binding " + "objects (WoTBindingType) carry the live, per-field form.", CAT_DT) + +struct_type(64042, "WoTRefreshOptionsDataType", + [("Atomicity", T(64023), "Commit granularity for this refresh."), + ("Force", Boolean, "Re-project even when the content digest is unchanged."), + ("DryRun", Boolean, "Validate and compute results without committing any projection change."), + ("IncludeDependents", Boolean, "Also refresh documents that depend on the selected documents."), + ("DeletePolicy", T(64024), "How to treat dependents when a selected document is unloaded/retired."), + ("MaxParallelism", UInt32, "Maximum number of documents projected concurrently; 0 lets the server decide."), + ("Timeout", Duration, "Overall time budget for the refresh; 0 lets the server decide.")], + "Immutable options controlling a single Refresh invocation.", CAT_DT) + +struct_type(64043, "WoTResourceSelectorDataType", + [("Kind", T(64020), "Restrict to Thing Descriptions or Thing Models; omit to select both."), + ("GroupId", String, "Restrict to a group by groupid; empty selects all groups."), + ("ResourceId", String, "Restrict to a resource by resourceid; empty selects all resources."), + ("VersionId", String, "Restrict to a version by versionid; empty selects the resource's default version."), + ("Xid", String, "Select a single entity by its xRegistry xid; overrides the other fields when set.")], + "An immutable selector identifying which stored documents a Refresh applies to. An empty selector " + "array selects the whole registry.", CAT_DT) + +struct_type(64044, "WoTResourceLoadResultDataType", + [("Xid", String, "The xRegistry xid of the affected resource/version."), + ("GroupId", String, "The groupid of the resource's group."), + ("ResourceId", String, "The resourceid of the affected resource."), + ("VersionId", String, "The versionid that was projected."), + ("Kind", T(64020), "Whether the document is a Thing Description or a Thing Model."), + ("Outcome", T(64025), "The per-resource outcome."), + ("Phase", T(64026), "The phase the resource reached (the failing phase on failure)."), + ("LoadState", T(64021), "The resulting load state of the projection."), + ("Generation", UInt32, "The refresh generation this result belongs to."), + ("MaterializedNodeCount", UInt32, "Number of AddressSpace nodes materialized for this resource."), + ("RootNodeId", NodeId, "The root node of the materialized projection, if any."), + ("ContentDigest", ByteString, "The content digest (hash) of the projected document bytes."), + ("Message", String, "Human-readable detail for the outcome.")], + "An immutable per-resource result row of a Refresh. Never mutated; the array is a point-in-time " + "snapshot for one generation.", CAT_DT) + +struct_type(64045, "WoTRefreshSummaryDataType", + [("RequestId", String, "The caller-supplied request identifier echoed back for correlation."), + ("Generation", UInt32, "The committed refresh generation (0 on a dry run or full failure)."), + ("Outcome", T(64025), "The overall outcome of the refresh."), + ("Atomicity", T(64023), "The commit granularity that was applied."), + ("StartTime", DateTime, "UTC start time of the refresh."), + ("EndTime", DateTime, "UTC end time of the refresh."), + ("Total", UInt32, "Total number of resources considered."), + ("Succeeded", UInt32, "Number of resources that changed successfully."), + ("Unchanged", UInt32, "Number of resources that were idempotently unchanged."), + ("Failed", UInt32, "Number of resources that failed."), + ("Skipped", UInt32, "Number of resources skipped by selection or policy."), + ("Retired", UInt32, "Number of superseded generations retired.")], + "An immutable summary of one Refresh invocation, also carried by the WoTRefreshCompletedEventType and " + "cached on the registry as LastRefreshSummary.", CAT_DT) + +struct_type(64046, "WoTDependencyDataType", + [("SourceXid", String, "The xid of the dependent document."), + ("TargetXid", String, "The xid of the document depended upon (empty if unresolved)."), + ("TargetUri", String, "The raw href/URI of the dependency as authored in the document."), + ("RefType", String, "The dependency kind (for example tm:extends, tm:ref, links.rel=type)."), + ("Resolved", Boolean, "True if the dependency resolved to a stored document.")], + "An immutable edge of the document dependency DAG, used to describe closures in results and diagnostics.", + CAT_DT) + +# --- ObjectTypes: registry, groups, documents, bindings -------------------- +object_type(64000, "WoTRegistryType", XRegistry_RegistryType, + "The WoT Connectivity 1.1 registry root - an xRegistry RegistryType (a FolderType) that holds " + "ThingDescriptionGroupType and ThingModelGroupType groups. The stored Thing Description / Thing Model " + "files and their versions are canonical; the projected AddressSpace (types from Thing Models, instances " + "from Thing Descriptions) is derived code-behind. Exposed as a well-known WoTRegistry object under the " + "Server object (i=2253). Adds registry-wide refresh, generation and validation-policy state and the " + "Refresh Method.", CAT) + +object_type(64001, "ThingDescriptionGroupType", XRegistry_GroupType, + "An xRegistry GroupType that collects related ThingDescriptionFileType resources (a Thing Description " + "Group per the WoT xRegistry model). Adds the group-level format/compatibility validation policy. Its " + " placeholder constrains members to the Thing Description subtype.", CAT) + +object_type(64002, "ThingModelGroupType", XRegistry_GroupType, + "An xRegistry GroupType that collects related ThingModelFileType resources (a Thing Model Group per the " + "WoT xRegistry model). Adds the group-level format/compatibility validation policy. Its " + "placeholder constrains members to the Thing Model subtype.", CAT) + +object_type(64003, "WoTDocumentType", XRegistry_ResourceType, + "The abstract base of a stored WoT document resource - an xRegistry ResourceType (a FileType) whose " + "content bytes are the JSON-LD document, read/written with the inherited Open/Read/Write/Close Methods. " + "Adds the derived-projection metadata (load state, desired/active version, validation and compatibility " + "outcomes, content digest, materialized-node count and root, selected bindings) and the Validate, " + "SetEnabled and SetDefaultVersion Methods. Concrete subtypes fix the document kind.", CAT, abstract=True) + +object_type(64004, "ThingDescriptionFileType", T(64003), + "A concrete WoTDocumentType whose content is a W3C WoT Thing Description (WoT-TD/1.1, " + "application/td+json). Projects to OPC UA instances: affordances become Variables, Methods and event " + "sources; forms become binder plans. Adds the Thing instance identity (ThingId, base URI) and the link " + "to the Thing Model it derives from.", CAT) + +object_type(64005, "ThingModelFileType", T(64003), + "A concrete WoTDocumentType whose content is a W3C WoT Thing Model (WoT-TM/1.1, application/tm+json). " + "Projects to OPC UA types: it materializes an ObjectType or VariableType and the affordance member " + "declarations and modelling rules. Adds the derived type NodeId and model version.", CAT) + +object_type(64006, "WoTBindingType", BaseObjectType, + "A browseable protocol-binding descriptor: the live, per-field representation of one W3C WoT protocol " + "binding the server can realize (its URI, title, version-pinned W3C document, draft maturity, enabled " + "state, content types and a capability snapshot). Selected/active binding sets are additionally exposed " + "as immutable WoTBindingCapabilityDataType array snapshots. Policy and identity are browseable; no " + "credentials or secrets are ever exposed here.", CAT) + +# --- Event types ----------------------------------------------------------- +event_type(64010, "WoTResourceEventType", BaseEventType, + "The common base event for a WoT resource lifecycle notification. Carries the identity of the affected " + "resource/version, the document kind, the refresh generation, the phase reached and the outcome. Abstract; " + "servers emit one of its concrete subtypes.", CAT_EV, abstract=True) + +event_type(64011, "WoTValidationFailureEventType", T(64010), + "Raised when a document fails format or compatibility validation. The failing resource is the event " + "source; the stored document is retained and any previous valid projection stays active.", CAT_EV) + +event_type(64012, "WoTLoadFailureEventType", T(64010), + "Raised when a validated document fails to project (materialize) into the AddressSpace, or when its " + "shadow generation cannot be activated. The failing resource is the event source.", CAT_EV) + +event_type(64013, "WoTBindingFailureEventType", T(64010), + "Raised when a form cannot be bound to its protocol binding (unknown binding, unsupported operation or a " + "runtime binder error). The failing resource is the event source.", CAT_EV) + +event_type(64014, "WoTRefreshCompletedEventType", BaseEventType, + "Raised by the registry when a Refresh completes (including automatic refreshes). Carries the refresh " + "summary and the committed generation. The registry object is the event source.", CAT_EV) + +# --- Reference type -------------------------------------------------------- +reference_type(64060, "HasWoTProjection", NonHierarchicalReferences, "WoTProjectionOf", + "Links a stored WoT document resource (source) to the root node of its derived AddressSpace " + "projection (target). Used to correlate materialized nodes and their NodeVersion with the canonical " + "document, and to find the document behind a projected node.", CAT_REF) + +# =========================================================================== +# Members +# =========================================================================== + +# ---- WoTRegistryType members --------------------------------------------- +RG = "WoTRegistryType" +prop_var(64000, RG, "AutoRefresh", Boolean, + "True if the registry automatically re-projects stored documents (per RefreshMode); false if only explicit " + "Refresh calls re-project.", rule=MR_Optional) +prop_var(64000, RG, "RefreshMode", T(64022), "How automatic refresh is triggered when AutoRefresh is true.") +prop_var(64000, RG, "RefreshInterval", Duration, "The interval used when RefreshMode is Periodic.") +prop_var(64000, RG, "RefreshGeneration", UInt32, + "The current committed projection generation; incremented on every committed refresh. Materialized nodes " + "carry the generation in their NodeVersion for correlation.", rule=MR_Mandatory) +prop_var(64000, RG, "LastRefreshTime", DateTime, "UTC time of the last completed refresh.") +prop_var(64000, RG, "LastRefreshSummary", T(64045), "An immutable snapshot summarizing the last completed refresh.") +prop_var(64000, RG, "DefaultAtomicity", T(64023), "The commit granularity applied when a Refresh omits an explicit atomicity.") +prop_var(64000, RG, "DeletePolicy", T(64024), "The default policy for treating dependents on unload/delete.") +prop_var(64000, RG, "ValidateFormat", Boolean, "Registry-wide default: validate document format on ingest/refresh.") +prop_var(64000, RG, "ValidateCompatibility", Boolean, "Registry-wide default: validate version compatibility on ingest/refresh.") +prop_var(64000, RG, "StrictValidation", Boolean, "If true, a validation warning is treated as a failure.") +prop_var(64000, RG, "VocabularyVersion", String, + "The version-pinned WoT Binding JSON-LD vocabulary this registry validates and projects against.") +prop_var(64000, RG, "SelectedBindings", T(64041), + "An immutable snapshot array of the protocol bindings currently selected/active registry-wide.", valuerank="1") +obj_member(64000, RG, "SupportedBindings", FolderType, + "A folder of browseable WoTBindingType binding descriptors the server can realize (the live, per-field " + "form of the selected-bindings snapshot).") +placeholder_obj(64000, RG, "", T(64001), + "A Thing Description Group held by this registry (constrained to the ThingDescriptionGroupType subtype).") +placeholder_obj(64000, RG, "", T(64002), + "A Thing Model Group held by this registry (constrained to the ThingModelGroupType subtype).") +refresh_decl = method(64000, RG, "Refresh", + "Re-project selected stored documents into the AddressSpace. Idempotent: a document whose content digest is " + "unchanged is reported Unchanged and not re-materialized unless Options.Force is set. Projects into a shadow " + "generation and switches atomically per Options.Atomicity; superseded generations use the implementation's " + "documented graceful or immediate retirement policy. If ExpectedGeneration is non-zero and does not equal RefreshGeneration, the call " + "fails with Bad_InvalidState and changes nothing (optimistic concurrency). An empty Selection selects the " + "whole registry.", + inargs=[("Selection", T(64043), "The documents to refresh; empty selects the whole registry.", 1), + ("Options", T(64042), "Options controlling atomicity, force, dry-run and dependents."), + ("ExpectedGeneration", UInt32, "Expected current RefreshGeneration for optimistic concurrency; 0 disables the check."), + ("RequestId", String, "Caller-supplied identifier echoed into the summary and the completion event.")], + outargs=[("Summary", T(64045), "The refresh summary."), + ("Results", T(64044), "The per-resource results.", 1), + ("NewGeneration", UInt32, "The committed generation (unchanged on dry run or full failure).")]) +generates_event(64000, 64014) + +# ---- ThingDescriptionGroupType members ------------------------------------ +DG = "ThingDescriptionGroupType" +prop_var(64001, DG, "ValidateFormat", Boolean, "Group-level policy: validate Thing Description format (WoT-TD/1.1) on ingest.") +prop_var(64001, DG, "ValidateCompatibility", Boolean, "Group-level policy: validate version compatibility on ingest.") +prop_var(64001, DG, "ConsistentFormat", Boolean, "Group-level policy: require all versions of a resource to share one format.") +placeholder_obj(64001, DG, "", T(64004), + "A Thing Description resource held by this group (constrained to the ThingDescriptionFileType subtype).") + +# ---- ThingModelGroupType members ------------------------------------------ +MG = "ThingModelGroupType" +prop_var(64002, MG, "ValidateFormat", Boolean, "Group-level policy: validate Thing Model format (WoT-TM/1.1) on ingest.") +prop_var(64002, MG, "ValidateCompatibility", Boolean, "Group-level policy: validate version compatibility on ingest.") +prop_var(64002, MG, "ConsistentFormat", Boolean, "Group-level policy: require all versions of a resource to share one format.") +placeholder_obj(64002, MG, "", T(64005), + "A Thing Model resource held by this group (constrained to the ThingModelFileType subtype).") + +# ---- WoTDocumentType members (abstract) ----------------------------------- +DOC = "WoTDocumentType" +prop_var(64003, DOC, "DocumentKind", T(64020), + "Whether this document is a Thing Description or a Thing Model. Fixed by the concrete subtype.", rule=MR_Mandatory) +prop_var(64003, DOC, "Enabled", Boolean, + "The desired enabled state: true requests that the document be validated and projected; false requests unload.", + rule=MR_Mandatory) +prop_var(64003, DOC, "LoadState", T(64021), "The actual lifecycle state of this document's derived projection.", rule=MR_Mandatory) +prop_var(64003, DOC, "DesiredVersionId", String, + "The versionid the operator wants active for this resource (the desired/pinned version).") +prop_var(64003, DOC, "ActiveVersionId", String, "The versionid whose projection is currently active.") +prop_var(64003, DOC, "IsDefault", Boolean, "xRegistry isdefault: true when this version is the resource's default (sticky) version.") +prop_var(64003, DOC, "Ancestor", String, "xRegistry ancestor: the versionid this version derives from (version lineage).") +prop_var(64003, DOC, "Compatibility", String, "The compatibility policy all versions of this resource adhere to (for example NONE, BACKWARD, FULL).") +prop_var(64003, DOC, "AutoRefresh", Boolean, "Per-document override of the registry AutoRefresh setting.") +prop_var(64003, DOC, "RefreshGeneration", UInt32, "The registry generation at which this document was last projected.") +prop_var(64003, DOC, "LastRefreshTime", DateTime, "UTC time this document was last projected.") +prop_var(64003, DOC, "ContentDigest", ByteString, "The content digest (hash) of the stored document bytes; used to make refresh idempotent.") +prop_var(64003, DOC, "ValidationOutcome", T(64040), "An immutable snapshot of this document's format and compatibility validation result.") +prop_var(64003, DOC, "MaterializedNodeCount", UInt32, "The number of AddressSpace nodes materialized from this document's active projection.") +prop_var(64003, DOC, "RootNodeId", NodeId, "The root node of this document's active projection (the type or instance root).") +prop_var(64003, DOC, "SelectedBindings", T(64041), + "An immutable snapshot array of the protocol bindings selected for this document's forms.", valuerank="1") +method(64003, DOC, "Validate", + "Validate the stored document (format and, when enabled, compatibility) without changing its projection. " + "Returns the outcome snapshot; also refreshes the ValidationOutcome Property.", + outargs=[("Outcome", T(64040), "The validation outcome snapshot.")]) +method(64003, DOC, "SetEnabled", + "Set the desired Enabled state of this document. Enabling requests validation and projection; disabling " + "requests unload per the registry DeletePolicy. If ExpectedEpoch is non-zero and does not equal the " + "resource's current Epoch the call fails with Bad_InvalidState and changes nothing.", + inargs=[("Enabled", Boolean, "The desired enabled state."), + ("ExpectedEpoch", UInt32, "Expected current Epoch for optimistic concurrency; 0 disables the check.")]) +method(64003, DOC, "SetDefaultVersion", + "Make a specific version of this resource its default (sticky) version, so that resolvers selecting the " + "resource without a versionid resolve to it. If ExpectedEpoch is non-zero and does not equal the resource's " + "current Epoch the call fails with Bad_InvalidState and changes nothing.", + inargs=[("VersionId", String, "The versionid to make default."), + ("ExpectedEpoch", UInt32, "Expected current Epoch for optimistic concurrency; 0 disables the check.")]) +generates_event(64003, 64011) +generates_event(64003, 64012) +generates_event(64003, 64013) + +# ---- ThingDescriptionFileType members ------------------------------------- +TD = "ThingDescriptionFileType" +prop_var(64004, TD, "ThingId", String, "The Thing Description id (a URI/URN identifying the concrete Thing instance).") +prop_var(64004, TD, "ThingTitle", String, "The Thing Description human-readable title.") +prop_var(64004, TD, "BaseUri", String, "The Thing Description base URI used to resolve relative form hrefs.") +prop_var(64004, TD, "ModelReference", String, + "The xid or href of the Thing Model this Thing Description derives from (links rel=type), when present.") + +# ---- ThingModelFileType members ------------------------------------------- +TM = "ThingModelFileType" +prop_var(64005, TM, "ModelTitle", String, "The Thing Model human-readable title.") +prop_var(64005, TM, "ModelVersion", String, "The Thing Model version (WoT version.model), when present.") +prop_var(64005, TM, "DerivedTypeNodeId", NodeId, "The ObjectType or VariableType materialized from this Thing Model.") + +# ---- WoTBindingType members ----------------------------------------------- +BD = "WoTBindingType" +prop_var(64006, BD, "BindingUri", String, "The WoT protocol-binding vocabulary URI this descriptor represents.", rule=MR_Mandatory) +prop_var(64006, BD, "Title", String, "Human-readable binding title.") +prop_var(64006, BD, "ProfileVersion", String, "The version-pinned W3C binding document version.") +prop_var(64006, BD, "DraftMaturity", String, "The W3C maturity of the pinned binding document (for example WD, CR, PR, REC).") +prop_var(64006, BD, "Enabled", Boolean, "True if the server currently realizes forms of this binding.") +prop_var(64006, BD, "ContentTypes", String, "The content types this binding produces/consumes.", valuerank="1") +prop_var(64006, BD, "Capabilities", T(64041), "An immutable capability snapshot for this binding.") + +# ---- Event type fields ---------------------------------------------------- +RE = "WoTResourceEventType" +event_field(64010, RE, "Xid", String, "The xRegistry xid of the affected resource/version.") +event_field(64010, RE, "ResourceId", String, "The resourceid of the affected resource.") +event_field(64010, RE, "VersionId", String, "The versionid of the affected version.") +event_field(64010, RE, "DocumentKind", T(64020), "Whether the document is a Thing Description or a Thing Model.") +event_field(64010, RE, "Generation", UInt32, "The refresh generation the notification relates to.") +event_field(64010, RE, "Phase", T(64026), "The phase reached (the failing phase on a failure event).") +event_field(64010, RE, "Outcome", T(64025), "The outcome the notification reports.") + +VF = "WoTValidationFailureEventType" +event_field(64011, VF, "ValidationOutcome", T(64040), "The full validation outcome snapshot for the failure.") + +LF = "WoTLoadFailureEventType" +event_field(64012, LF, "LoadState", T(64021), "The load state after the failed projection/activation.") +event_field(64012, LF, "FailedNodeId", NodeId, "The node whose materialization failed, if identifiable.") +event_field(64012, LF, "Reason", String, "Human-readable failure reason.") + +BF = "WoTBindingFailureEventType" +event_field(64013, BF, "BindingUri", String, "The binding URI that could not be bound.") +event_field(64013, BF, "Reason", String, "Human-readable binding failure reason.") + +RC = "WoTRefreshCompletedEventType" +event_field(64014, RC, "Summary", T(64045), "The refresh summary snapshot.") +event_field(64014, RC, "RequestId", String, "The caller-supplied request identifier echoed from the Refresh call.") +event_field(64014, RC, "Generation", UInt32, "The committed generation.") + +# ---- Well-known WoTRegistry instance (component of the Server object) ----- +add(64100, "UAObject", "WoTRegistry", "WoTRegistry", + desc="The server-wide WoT Connectivity 1.1 registry, a well-known component of the Server object. Its stored " + "Thing Description / Thing Model files are canonical; the projected AddressSpace is derived. It is the " + "notifier for the WoT resource lifecycle events raised by its groups and resources.", + parent=Server, category=CAT_INST, attrs={"EventNotifier": "1"}) +ref(64100, HasTypeDefinition, T(64000)) +ref(64100, HasComponent, Server, forward=False) +ref(64100, HasNotifier, Server, forward=False) +instance_method(64100, "WoTRegistry", "Refresh", refresh_decl, + "Re-project selected stored documents into the AddressSpace. The functional Refresh Method on the well-known " + "WoTRegistry object; a server binds the concrete handler.", + inargs=[("Selection", T(64043), "The documents to refresh; empty selects the whole registry.", 1), + ("Options", T(64042), "Options controlling atomicity, force, dry-run and dependents."), + ("ExpectedGeneration", UInt32, "Expected current RefreshGeneration for optimistic concurrency; 0 disables the check."), + ("RequestId", String, "Caller-supplied identifier echoed into the summary and the completion event.")], + outargs=[("Summary", T(64045), "The refresh summary."), + ("Results", T(64044), "The per-resource results.", 1), + ("NewGeneration", UInt32, "The committed generation (unchanged on dry run or full failure).")]) +# Materialize the well-known instance's Mandatory members (own and inherited) with stable +# default values, so loading the NodeSet alone yields a structurally complete registry +# instance rather than one that depends on a server populating Mandatory properties later. +instance_var(64100, "WoTRegistry", "RegistryId", String, + "xRegistry registryid: the stable identifier of this registry (Mandatory, inherited from " + "the xRegistry RegistryType). Default value for the well-known instance; a server MAY " + "override it.", _scalar_value("String", "WoTRegistry")) +instance_var(64100, "WoTRegistry", "RefreshGeneration", UInt32, + "The current committed projection generation; incremented on every committed refresh " + "(Mandatory). Materialized as 0 at load time, before any Refresh has committed.", + _scalar_value("UInt32", 0)) + +# =========================================================================== +# ============== LEGACY OPC 10100-1 v1.02 MODEL (incorporated) ============ +# =========================================================================== +# The published WoT Connectivity 1.02 model (NamespaceUri +# http://opcfoundation.org/UA/WoT-Con/) is incorporated into THIS combined +# NodeSet from the pinned authoring sources under legacy/: +# * legacy/WotConnection.csv - the authoritative NodeId/NodeClass table +# (every published id 1..172 preserved exactly) +# * legacy/WotConnection.xml - the ModelDesign (type bases, method signatures) +# The nodes are emitted in the own namespace (index 2), because xRegistry +# occupies index 1 in this combined document; numeric identifiers are unchanged. +# The management/upload surface is marked ReleaseStatus="Deprecated" (per OPC +# 11030); nothing is removed. Standard inherited members (FileType, Part 20; +# NamespaceMetadataType, Part 5) are expanded from OPC UA base definitions. +CAT_LEGACY = "WoT Connectivity 1.02 legacy (deprecated)" +CAT_LEGACY_INST = "WoT Connectivity 1.02 Legacy Instances" + +_HERE = os.path.dirname(os.path.abspath(__file__)) +LEGACY_DIR = os.path.join(os.path.dirname(_HERE), "legacy") +LEGACY_CSV = os.path.join(LEGACY_DIR, "WotConnection.csv") +LEGACY_XML = os.path.join(LEGACY_DIR, "WotConnection.xml") +_OPC = "{http://opcfoundation.org/UA/ModelDesign.xsd}" + +# The 1.02 namespace this legacy surface belongs to, and the additive revision +# it is re-published under (values carried by the generated NamespaceMetadata). +LEGACY_NS = "http://opcfoundation.org/UA/WoT-Con/" + +# BrowseNames that are namespace-0 well-known inherited members. +_NS0_LEAF = { + "Size", "Writable", "UserWritable", "OpenCount", "MimeType", "MaxByteStringLength", + "LastModifiedTime", "Open", "Close", "Read", "Write", "GetPosition", "SetPosition", + "InputArguments", "OutputArguments", "ExportNamespace", + "NamespaceUri", "NamespaceVersion", "NamespacePublicationDate", "IsNamespaceSubset", + "StaticNodeIdTypes", "StaticNumericNodeIdRange", "StaticStringNodeIdPattern", + "ConfigurationVersion", "ModelVersion", + "DefaultRolePermissions", "DefaultUserRolePermissions", "DefaultAccessRestrictions", +} +# Placeholder leaf -> emitted BrowseName. +_PLACEHOLDER_BN = { + "WoTAssetName_Placeholder": "", + "WoTPropertyName_Placeholder": "", + "WoTConfigurationParameterName_Placeholder": "", +} +# Standard FileType Property member -> (DataType, modelling rule when a type member). +_FILE_VAR = { + "Size": (UInt64, "Mandatory"), "Writable": (Boolean, "Mandatory"), + "UserWritable": (Boolean, "Mandatory"), "OpenCount": (UInt16, "Mandatory"), + "MimeType": (String, "Optional"), "MaxByteStringLength": (UInt32, "Optional"), + "LastModifiedTime": (UtcTime, "Optional"), +} +# Standard method signatures (Part 20 FileType + the NamespaceMetadata export) +# not carried by the WoT ModelDesign. arg = (Name, DataType, ValueRank). +_STD_METHOD_ARGS = { + "Open": ([("Mode", Byte, -1)], [("FileHandle", UInt32, -1)]), + "Close": ([("FileHandle", UInt32, -1)], []), + "Read": ([("FileHandle", UInt32, -1), ("Length", Int32, -1)], [("Data", ByteString, -1)]), + "Write": ([("FileHandle", UInt32, -1), ("Data", ByteString, -1)], []), + "GetPosition": ([("FileHandle", UInt32, -1)], [("Position", UInt64, -1)]), + "SetPosition": ([("FileHandle", UInt32, -1), ("Position", UInt64, -1)], []), + "ExportNamespace": ([], []), +} +_FILE_METHOD_LEAVES = set(_STD_METHOD_ARGS) | {"CloseAndUpdate"} +# NamespaceMetadataType member -> (DataType, ValueRank as str). +_NS_META_DT = { + "NamespaceUri": (String, "-1"), "NamespaceVersion": (String, "-1"), + "NamespacePublicationDate": (DateTime, "-1"), "IsNamespaceSubset": (Boolean, "-1"), + "StaticNodeIdTypes": (IdType, "1"), "StaticNumericNodeIdRange": (NumericRange, "1"), + "StaticStringNodeIdPattern": (String, "-1"), "ConfigurationVersion": (VersionTime, "-1"), + "ModelVersion": (SemanticVersionString, "-1"), + "DefaultRolePermissions": (RolePermissionType, "1"), + "DefaultUserRolePermissions": (RolePermissionType, "1"), + "DefaultAccessRestrictions": (AccessRestrictionType, "-1"), +} +# Model-specific member -> (DataType, ValueRank as str). +_WOT_MEMBER_DT = { + "SupportedWoTBindings": (UriString, "1"), "AssetEndpoint": (String, "-1"), + "License": (String, "-1"), "WoTConfigurationParameterName_Placeholder": (BaseDataType, "-1"), + "WoTPropertyName_Placeholder": (BaseDataType, "-1"), +} +# Modelling rule of model-specific members when declared on a type. +_WOT_MEMBER_RULE = { + "WoTAssetName_Placeholder": "OptionalPlaceholder", + "WoTPropertyName_Placeholder": "OptionalPlaceholder", + "WoTConfigurationParameterName_Placeholder": "OptionalPlaceholder", + "WoTFile": "Mandatory", "AssetEndpoint": "Optional", "SupportedWoTBindings": "Optional", + "CreateAsset": "Mandatory", "DeleteAsset": "Mandatory", "DiscoverAssets": "Optional", + "CreateAssetForEndpoint": "Optional", "ConnectionTest": "Optional", + "Configuration": "Optional", "License": "Optional", "CloseAndUpdate": "Mandatory", +} +_RULE_ID = {"Mandatory": MR_Mandatory, "Optional": MR_Optional, + "OptionalPlaceholder": MR_OptionalPlaceholder, "MandatoryPlaceholder": MR_MandatoryPlaceholder} +# ModelDesign ua:Type name -> NodeId (for parsed DataTypes/base types). +_UA_NAME = { + "String": String, "NodeId": NodeId, "Boolean": Boolean, "UInt32": UInt32, "UInt64": UInt64, + "UInt16": UInt16, "Byte": Byte, "Int32": Int32, "ByteString": ByteString, "DateTime": DateTime, + "UriString": UriString, "SemanticVersionString": SemanticVersionString, "IdType": IdType, + "NumericRange": NumericRange, "BaseDataType": BaseDataType, + "BaseObjectType": BaseObjectType, "BaseInterfaceType": BaseInterfaceType, "FileType": FileType, + "HasComponent": HasComponent, "BaseDataVariableType": BaseDataVariableType, +} +# Instance root symbolics carry values / no modelling rule; type roots carry modelling rules. +_LEGACY_INSTANCE_ROOTS = {"WoTAssetConnectionManagement", "WotConNamespaceMetadata"} +_LEGACY_TYPE_ROOTS = {"WoTAssetConnectionManagementType", "IWoTAssetType", + "WoTAssetConfigurationType", "WoTAssetFileType"} +# Roots (and their whole subtree) marked deprecated: the entire 1.02 management/ +# upload surface is superseded by the additive registry. NamespaceMetadata is not. +_LEGACY_DEPRECATED_ROOTS = _LEGACY_TYPE_ROOTS | {"WoTAssetConnectionManagement", + "HasWoTComponent"} + + +def _ua(name): + return _UA_NAME.get((name or "").split(":")[-1], BaseDataType) + + +def _parse_modeldesign(): + """Parse the pinned ModelDesign for type bases/abstract/inverse and the WoT + method-argument signatures (the parts a NodeId table cannot carry).""" + root = ET.parse(LEGACY_XML).getroot() + types, methodsig, concrete_mt = {}, {}, {} + + def args_of(mel, which): + out = [] + grp = mel.find(_OPC + which) + if grp is None: + return out + for a in grp.findall(_OPC + "Argument"): + vr = (a.get("ValueRank") or "").lower() + out.append((a.get("Name"), _ua(a.get("DataType")), 1 if vr == "array" else -1)) + return out + + for e in list(root): + tag = e.tag.split("}")[-1] + sym = e.get("SymbolicName") + if tag in ("ObjectType", "ReferenceType"): + inv = e.find(_OPC + "InverseName") + types[sym] = (tag, e.get("BaseType"), e.get("IsAbstract", "false") == "true", + inv.text if inv is not None else None) + elif tag == "Method": + methodsig[sym] = (args_of(e, "InputArguments"), args_of(e, "OutputArguments")) + for m in root.iter(_OPC + "Method"): + td, sym = m.get("TypeDefinition"), m.get("SymbolicName") + if td and sym: + concrete_mt[sym] = td + return types, methodsig, concrete_mt + + +def _argument_value(args): + """Build the Argument[] fragment for a method InputArguments/OutputArguments.""" + parts = ['', ''] + for (aname, adtype, arank) in args: + parts.append("i=297") + parts.append(f"{sx.escape(aname)}{adtype}") + if arank is not None and arank >= 0: + parts.append(f"{arank}0") + else: + parts.append("-1") + parts.append("") + parts.append("") + return "".join(parts) + + +def build_legacy(): + """Reconstruct the incorporated 1.02 nodes from the pinned sources, preserving + every numeric NodeId and NodeClass. Reserved ('Unspecified') CSV rows are kept + in the CSV but not emitted as nodes.""" + types_md, methodsig_md, concrete_mt = _parse_modeldesign() + + rows = [] + with open(LEGACY_CSV, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + sym, sid, cls = line.split(",") + rows.append((sym, int(sid), cls)) + sym2id = {sym: sid for sym, sid, cls in rows} + + def parent_sym(sym): + best = None + for other in sym2id: + if other != sym and sym.startswith(other + "_") and (best is None or len(other) > len(best)): + best = other + return best + + parents = {sym: parent_sym(sym) for sym, _, _ in rows} + + def root_sym(sym): + cur = sym + while parents[cur] is not None: + cur = parents[cur] + return cur + + def leaf_of(sym): + p = parents[sym] + return sym[len(p) + 1:] if p else sym + + def method_args(method_leaf): + if method_leaf in _STD_METHOD_ARGS: + return _STD_METHOD_ARGS[method_leaf] + mt = concrete_mt.get(method_leaf) + if mt is None and method_leaf in methodsig_md: + mt = method_leaf + return methodsig_md.get(mt, ([], [])) + + # ---- pass 1: create the node objects (ids/classes from the pinned CSV) ----- + meta = {} + for sym, sid, cls in rows: + if cls == "Unspecified": + continue # reserved id, retained in CSV only + p = parents[sym] + leaf = leaf_of(sym) + root = root_sym(sym) + is_instance = root in _LEGACY_INSTANCE_ROOTS + bn = LEGACY_NS if sym == "WotConNamespaceMetadata" else _PLACEHOLDER_BN.get(leaf, leaf) + attrs = {} + if leaf in _NS0_LEAF: + attrs["_ns0bn"] = True + if root in _LEGACY_DEPRECATED_ROOTS: + attrs["_release_status"] = "Deprecated" + if p is not None: + parent_nid = T(sym2id[p]) + elif sym == "WoTAssetConnectionManagement": + parent_nid = ObjectsFolder + elif sym == "WotConNamespaceMetadata": + parent_nid = Server_Namespaces + else: + parent_nid = None + add(sid, "UA" + cls, bn, sym, desc=None, parent=parent_nid, attrs=attrs, + category=(CAT_LEGACY_INST if is_instance else CAT_LEGACY)) + meta[sid] = (sym, cls, p, leaf, root, is_instance) + + # ---- pass 2: attributes and references (all nodes now exist) --------------- + for sid, (sym, cls, p, leaf, root, is_instance) in meta.items(): + n = NODES[sid] + pid = sym2id[p] if p else None + is_type_member = root in _LEGACY_TYPE_ROOTS and p is not None + is_method_template_child = root.endswith("MethodType") and p is not None + + if cls == "ObjectType": + _, base, abstract, _inv = types_md.get(sym, ("ObjectType", "ua:BaseObjectType", False, None)) + ref(sid, HasSubtype, _ua(base), forward=False) + n.abstract = abstract + continue + if cls == "ReferenceType": + _, base, abstract, inv = types_md.get(sym, ("ReferenceType", "ua:HasComponent", False, None)) + ref(sid, HasSubtype, _ua(base), forward=False) + n.inverse = inv + continue + + # Instance/type roots (Objects with no legacy parent). + if p is None and sym == "WoTAssetConnectionManagement": + ref(sid, HasTypeDefinition, T(1)) + ref(sid, Organizes, ObjectsFolder, forward=False) + continue + if p is None and sym == "WotConNamespaceMetadata": + ref(sid, HasTypeDefinition, NamespaceMetadataType) + ref(sid, HasComponent, Server_Namespaces, forward=False) + continue + if p is None: + # Standalone *MethodType declaration node (Method). + continue + + # Hierarchical reference to the (legacy) parent. + if leaf == "WoTAssetName_Placeholder": + reftype = Organizes + elif leaf == "WoTPropertyName_Placeholder": + reftype = T(142) # HasWoTComponent + elif cls == "Variable": + reftype = HasProperty + else: + reftype = HasComponent + ref(sid, reftype, T(pid), forward=False) + ref(pid, reftype, T(sid)) + + # Modelling rule (type members and method-template args only). + if (is_type_member or is_method_template_child) and not is_instance: + if leaf in _WOT_MEMBER_RULE: + rule = _WOT_MEMBER_RULE[leaf] + elif leaf in _FILE_VAR: + rule = _FILE_VAR[leaf][1] + elif leaf in _FILE_METHOD_LEAVES or leaf == "ExportNamespace": + rule = "Mandatory" + elif leaf in ("InputArguments", "OutputArguments"): + rule = "Mandatory" + else: + rule = "Optional" + ref(sid, HasModellingRule, _RULE_ID[rule]) + + # TypeDefinition + DataType by NodeClass. + if cls == "Object": + if leaf == "WoTFile": + td = T(110) + elif leaf == "NamespaceFile": + td = FileType + elif leaf == "Configuration": + td = T(105) + elif leaf == "WoTAssetName_Placeholder": + td = BaseObjectType + else: + td = BaseObjectType + ref(sid, HasTypeDefinition, td) + elif cls == "Variable": + if leaf in ("InputArguments", "OutputArguments"): + which = 0 if leaf == "InputArguments" else 1 + margs = method_args(leaf_of(p))[which] + n.attrs["DataType"] = Argument + n.attrs["ValueRank"] = "1" + n.attrs["ArrayDimensions"] = str(len(margs)) + n.value = _argument_value(margs) + ref(sid, HasTypeDefinition, PropertyType) + else: + if leaf in _WOT_MEMBER_DT: + dt, vr = _WOT_MEMBER_DT[leaf] + elif leaf in _FILE_VAR: + dt, vr = _FILE_VAR[leaf][0], "-1" + elif leaf in _NS_META_DT: + dt, vr = _NS_META_DT[leaf] + else: + dt, vr = BaseDataType, "-1" + n.attrs["DataType"] = dt + n.attrs["ValueRank"] = vr + if vr == "1": + n.attrs["ArrayDimensions"] = "0" + td = BaseDataVariableType if leaf == "WoTPropertyName_Placeholder" else PropertyType + ref(sid, HasTypeDefinition, td) + # NamespaceMetadata instance property values reflect the 1.1 revision. + if is_instance and leaf in _NS_META_VALUE: + n.value = _NS_META_VALUE[leaf] + + # HasInterface on the placeholder (implements IWoTAssetType). + if 2 in NODES and 42 in NODES: + ref(2, HasInterface, T(42)) + + +# NamespaceMetadata property values for the incorporated legacy namespace, carrying +# the new 1.1 revision metadata while the stable NodeIds are preserved. +_NS_META_VALUE = { + "NamespaceUri": _scalar_value("String", LEGACY_NS), + "NamespaceVersion": _scalar_value("String", "1.1.0"), + "NamespacePublicationDate": _scalar_value("DateTime", "2026-07-22T00:00:00Z"), + "IsNamespaceSubset": _scalar_value("Boolean", "false"), + "StaticStringNodeIdPattern": _scalar_value("String", ""), + "ModelVersion": _scalar_value("String", "1.1.0"), +} + +build_legacy() + +# =========================================================================== +# Emission +# =========================================================================== +NAMESPACE = "http://opcfoundation.org/UA/WoT-Con/" +VERSION = "1.1.0" +PUBDATE = "2026-07-22T00:00:00Z" +XR_NAMESPACE = "http://opcfoundation.org/UA/xRegistry/" +XR_VERSION = "0.1.0" +XR_PUBDATE = "2026-07-16T00:00:00Z" +UA_REQUIRED_VERSION = "1.05.04" +UA_REQUIRED_PUBDATE = "2024-05-01T00:00:00Z" + +ALIASES = [ + ("Boolean", Boolean), ("Byte", Byte), ("UInt16", UInt16), ("Int32", Int32), + ("UInt32", UInt32), ("UInt64", UInt64), ("Double", Double), + ("String", String), ("DateTime", DateTime), ("UtcTime", UtcTime), ("ByteString", ByteString), + ("NodeId", NodeId), ("ExpandedNodeId", ExpandedNodeId), ("LocalizedText", LocalizedText), + ("Duration", Duration), ("Argument", Argument), ("Structure", Structure), + ("Enumeration", Enumeration), ("UriString", UriString), ("BaseDataType", BaseDataType), + ("Organizes", Organizes), ("HasModellingRule", HasModellingRule), + ("HasTypeDefinition", HasTypeDefinition), ("HasSubtype", HasSubtype), + ("HasProperty", HasProperty), ("HasComponent", HasComponent), ("HasEncoding", HasEncoding), + ("HasInterface", HasInterface), + ("GeneratesEvent", GeneratesEvent), ("HasNotifier", HasNotifier), + ("NonHierarchicalReferences", NonHierarchicalReferences), +] +REFTYPE_ALIAS = {v: k for k, v in ALIASES} +DATATYPE_ALIAS = {v: k for k, v in ALIASES} +_PRIO = {HasModellingRule: 0, HasSubtype: 1} + + +def _sorted_refs(refs): + return sorted(range(len(refs)), key=lambda i: (_PRIO.get(refs[i][0], 2), i)) + + +def _fmt_reftype(t): + return REFTYPE_ALIAS.get(t, t) + + +def _fmt_datatype(t): + return DATATYPE_ALIAS.get(t, t) + + +def _fmt_browse_name(n): + if n.attrs.get("_ns0bn"): + return sx.escape(n.bname) + return f"{OWN_NS}:{sx.escape(n.bname)}" + + +def _emit_node(n): + a = [f'{n.cls} NodeId="{T(n.nid)}"', f'BrowseName="{_fmt_browse_name(n)}"'] + if n.parent is not None: + a.append(f'ParentNodeId="{n.parent}"') + for k in ("DataType", "ValueRank", "ArrayDimensions", "MethodDeclarationId", "EventNotifier"): + if k in n.attrs: + v = _fmt_datatype(n.attrs[k]) if k == "DataType" else n.attrs[k] + a.append(f'{k}="{v}"') + if n.cls in ("UAObjectType", "UAReferenceType") and n.abstract: + a.append('IsAbstract="true"') + if n.attrs.get("_release_status"): + a.append(f'ReleaseStatus="{n.attrs["_release_status"]}"') + lines = [" <" + " ".join(a) + ">"] + lines.append(f" {sx.escape(n.display)}") + if n.desc: + lines.append(f" {sx.escape(n.desc)}") + if n.inverse: + lines.append(f" {sx.escape(n.inverse)}") + if n.category: + lines.append(f" {sx.escape(n.category)}") + lines.append(" ") + for i in _sorted_refs(n.refs): + rt, tgt, fwd = n.refs[i] + lines.append(f' {tgt}') + lines.append(" ") + if n.definition: + lines.append(" " + n.definition) + if n.value: + lines.append(" " + n.value) + lines.append(f" ") + return "\n".join(lines) + + +def emit(): + out = ['', + '', + '', + ' ', f' {XR_NAMESPACE}', f' {NAMESPACE}', ' ', + ' ', f' ', + f' ', + f' ', + ' ', ' ', ' '] + for name, val in ALIASES: + out.append(f' {val}') + out.append(' ') + for nid in ORDER: + out.append(_emit_node(NODES[nid])) + out.append('') + return "\n".join(out) + "\n" + + +def emit_csv(): + """Combined NodeId table: the pinned 1.02 rows verbatim (every id 1..172 and + NodeClass preserved, including reserved 'Unspecified' rows) followed by the + additive registry rows (64000+).""" + legacy = open(LEGACY_CSV, encoding="utf-8").read().replace("\r\n", "\n").strip("\n") + registry = "\n".join(f"{NODES[nid].symbolic},{nid},{NODES[nid].cls[2:]}" + for nid in ORDER if nid >= OWN_MIN) + return legacy + "\n" + registry + "\n" + + +# --------------------------------------------------------------------------- +# Annex A (model-reference.md) generation +# --------------------------------------------------------------------------- +XREGISTRY_SPEC = ( + "https://github.com/marcschier/opcua-drafts/blob/main/" + "core-specs/xregistry/OPC-UA-xRegistry.md" +) +WOT_BINDING_SPEC = "../WoT-Binding/OPC-UA-WoT-Binding.md" + +LINK_MAP = { + "BaseObjectType": "https://reference.opcfoundation.org/specs/OPC-10000-5/6.2", + "FolderType": "https://reference.opcfoundation.org/specs/OPC-10000-5/6.6", + "PropertyType": "https://reference.opcfoundation.org/specs/OPC-10000-5/7.3", + "BaseDataVariableType": "https://reference.opcfoundation.org/specs/OPC-10000-5/7.4", + "BaseEventType": "https://reference.opcfoundation.org/specs/OPC-10000-5/6.4.2", + "FileType": "https://reference.opcfoundation.org/specs/OPC-10000-20/4.2", + "Structure": "https://reference.opcfoundation.org/specs/OPC-10000-5/8.24", + "Enumeration": "https://reference.opcfoundation.org/specs/OPC-10000-3/8.40", + "NonHierarchicalReferences": "https://reference.opcfoundation.org/specs/OPC-10000-5/11.3", + "NodeId": "https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.1", + "ExpandedNodeId": "https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.3", + "BaseInterfaceType": "https://reference.opcfoundation.org/specs/OPC-10000-5/6.2", + "HasComponent": "https://reference.opcfoundation.org/specs/OPC-10000-5/11.3", + "RegistryType": XREGISTRY_SPEC + "#type-RegistryType", + "GroupType": XREGISTRY_SPEC + "#type-GroupType", + "ResourceType": XREGISTRY_SPEC + "#type-ResourceType", +} +_BASE_NAMES = { + BaseObjectType: "BaseObjectType", FolderType: "FolderType", BaseDataVariableType: "BaseDataVariableType", + PropertyType: "PropertyType", BaseEventType: "BaseEventType", Structure: "Structure", + Enumeration: "Enumeration", NonHierarchicalReferences: "NonHierarchicalReferences", + BaseInterfaceType: "BaseInterfaceType", FileType: "FileType", HasComponent: "HasComponent", +} +_XR_NAMES = {XRegistry_RegistryType: "RegistryType", XRegistry_GroupType: "GroupType", + XRegistry_ResourceType: "ResourceType"} +_OWN = None + + +def _friendly(tgt): + if tgt in _BASE_NAMES: + return _BASE_NAMES[tgt] + if tgt in _XR_NAMES: + return _XR_NAMES[tgt] + if tgt in DATATYPE_ALIAS: + return DATATYPE_ALIAS[tgt] + if tgt.startswith(f"ns={OWN_NS};i="): + num = int(tgt.split("i=")[1]) + if num in NODES: + return NODES[num].bname + return tgt + + +def _anchor(name): + return "type-" + name + + +def _link(display): + if not display: + return display + arr = "" + core = display + if core.endswith("[]"): + arr = r"\[\]"; core = core[:-2] + if core in _OWN: + return f"[{core}](#{_anchor(core)})" + arr + if core in LINK_MAP: + return f"[{core}]({LINK_MAP[core]})" + arr + return core + arr + + +def _member_rule(n): + for rt, tgt, fwd in n.refs: + if rt == HasModellingRule: + return {MR_Mandatory: "Mandatory", MR_Optional: "Optional", + MR_OptionalPlaceholder: "OptionalPlaceholder"}.get(tgt, "") + return "" + + +def _supertype(n): + for rt, tgt, fwd in n.refs: + if rt == HasSubtype and not fwd: + return tgt + return "" + + +def _members_of(nid): + out = [] + for rt, tgt, fwd in NODES[nid].refs: + if rt in (HasComponent, HasProperty, Organizes) and fwd and tgt.startswith(f"ns={OWN_NS};i="): + num = int(tgt.split("i=")[1]) + if num in NODES: + out.append(num) + return out + + +def _generated_events(nid): + out = [] + for rt, tgt, fwd in NODES[nid].refs: + if rt == GeneratesEvent and fwd and tgt.startswith(f"ns={OWN_NS};i="): + out.append(int(tgt.split("i=")[1])) + return out + + +def _dt_display(mn): + dt = _friendly(mn.attrs.get("DataType", "")) if mn.attrs.get("DataType") else "" + if mn.attrs.get("ValueRank") == "1" and dt: + dt += "[]" + return _link(dt) + + +def emit_md(): + global _OWN + _OWN = {NODES[nid].bname for nid in ORDER + if NODES[nid].cls in ("UAObjectType", "UADataType", "UAReferenceType")} + obj_types = [nid for nid in ORDER if NODES[nid].cls == "UAObjectType" and NODES[nid].category in (CAT,)] + event_types = [nid for nid in ORDER if NODES[nid].cls == "UAObjectType" and NODES[nid].category == CAT_EV] + enum_types = [nid for nid in ORDER if nid in ENUM_FIELDS] + struct_types = [nid for nid in ORDER if nid in STRUCT_FIELDS] + ref_types = [nid for nid in ORDER if NODES[nid].cls == "UAReferenceType" and NODES[nid].category == CAT_REF] + legacy_types = [nid for nid in ORDER if NODES[nid].category == CAT_LEGACY + and NODES[nid].cls in ("UAObjectType", "UAReferenceType")] + legacy_instances = [nid for nid in ORDER if NODES[nid].category == CAT_LEGACY_INST + and NODES[nid].cls == "UAObject" and NODES[nid].parent + and not NODES[nid].parent.startswith(f"ns={OWN_NS};") + and any(rt == HasTypeDefinition and str(tgt).startswith(f"ns={OWN_NS};") + for rt, tgt, fwd in NODES[nid].refs)] + method_args, method_out = {}, {} + for nid in ORDER: + n = NODES[nid] + if n.cls == "UAVariable" and n.bname in ("InputArguments", "OutputArguments") and n.value: + names = re.findall(r"([^<]+)", n.value) + pid = int(n.parent.split("i=")[1]) if n.parent else None + (method_args if n.bname == "InputArguments" else method_out)[pid] = names + + md = ['', '', '## Annex A — Information model\n', + 'This annex is the normative node reference. It is generated from `tools/build_model.py` and always ' + 'matches `Opc.Ua.WoTCon.NodeSet2.xml`. It documents one combined model in the companion namespace ' + f'`{NAMESPACE}` (namespace index `2` in this NodeSet, after the required ' + f'`{XR_NAMESPACE}` base model at index `1`). The additive **WoT Connectivity 1.1** registry types ' + f'**extend the abstract [OPC UA — xRegistry]({XREGISTRY_SPEC}) base types** ' + '(`RegistryType`/`GroupType`/`ResourceType`) and use provisional NodeIds in the `64000+` block (final IDs ' + 'are assigned by the OPC Foundation). The incorporated **OPC 10100-1 v1.02** legacy model is preserved ' + 'unchanged at its published NodeIds `1..172` and is documented, with its `Deprecated` release status, under ' + '*Legacy model* below. The **Declared in** column marks members inherited from a supertype.\n'] + + md.append('### Type overview\n') + md.append('| NodeId | BrowseName | NodeClass | Subtype of |') + md.append('|---|---|---|---|') + for nid in obj_types + event_types + enum_types + struct_types + ref_types: + n = NODES[nid] + md.append(f"| ns={OWN_NS};i={nid} | {_link(n.bname)} | {n.cls[2:]} | {_link(_friendly(_supertype(n)))} |") + md.append('') + + md.append('### Object types\n') + for nid in obj_types: + n = NODES[nid] + md.append(f'') + md.append('') + abstract = " *(abstract)*" if n.abstract else "" + md.append(f"#### {n.bname} (ns={OWN_NS};i={nid}){abstract}\n") + md.append(f"*Inherits from:* {_link(_friendly(_supertype(n)))}\n") + if n.desc: + md.append(n.desc + "\n") + rows = [] + for m in _members_of(nid): + mn = NODES[m] + rows.append((mn.bname, mn.cls[2:], _dt_display(mn), _member_rule(mn), n.bname, + (mn.desc or "").replace("|", "/"))) + if rows: + md.append('| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description |') + md.append('|---|---|---|---|---|---|') + for r in rows: + md.append(f"| {r[0]} | {r[1]} | {r[2]} | {r[3]} | {r[4]} | {r[5]} |") + md.append('') + ge = _generated_events(nid) + if ge: + md.append("*Generates events:* " + ", ".join(_link(NODES[e].bname) for e in ge) + "\n") + + md.append('### Event types\n') + for nid in event_types: + n = NODES[nid] + md.append(f'') + md.append('') + abstract = " *(abstract)*" if n.abstract else "" + md.append(f"#### {n.bname} (ns={OWN_NS};i={nid}){abstract}\n") + md.append(f"*Subtype of:* {_link(_friendly(_supertype(n)))}\n") + if n.desc: + md.append(n.desc + "\n") + rows = [] + for m in _members_of(nid): + mn = NODES[m] + rows.append((mn.bname, _dt_display(mn), _member_rule(mn), n.bname, (mn.desc or "").replace("|", "/"))) + if rows: + md.append('| Field | DataType | ModellingRule | Declared in | Description |') + md.append('|---|---|---|---|---|') + for r in rows: + md.append(f"| {r[0]} | {r[1]} | {r[2]} | {r[3]} | {r[4]} |") + md.append('') + + md.append('### DataTypes\n') + for nid in enum_types: + n = NODES[nid] + md.append(f'') + md.append('') + md.append(f"#### {n.bname} (ns={OWN_NS};i={nid})\n") + md.append(f"*Subtype of:* {_link(_friendly(_supertype(n)))}\n") + if n.desc: + md.append(n.desc + "\n") + md.append("| Name | Value | Description |") + md.append("|---|---|---|") + for (fname, val, fdesc) in ENUM_FIELDS[nid]: + md.append(f"| {fname} | {val} | {(fdesc or '').replace('|', '/')} |") + md.append('') + for nid in struct_types: + n = NODES[nid] + md.append(f'') + md.append('') + md.append(f"#### {n.bname} (ns={OWN_NS};i={nid})\n") + md.append(f"*Subtype of:* {_link(_friendly(_supertype(n)))}\n") + if n.desc: + md.append(n.desc + "\n") + md.append("| Field | DataType | Description |") + md.append("|---|---|---|") + for f in STRUCT_FIELDS[nid]: + fname, fdt, fdesc = f[0], f[1], f[2] + frank = f[3] if len(f) > 3 else -1 + dt = _friendly(fdt) + if frank is not None and frank >= 0: + dt += "[]" + md.append(f"| {fname} | {_link(dt)} | {(fdesc or '').replace('|', '/')} |") + md.append('') + + md.append('### Reference types\n') + for nid in ref_types: + md.append(f'') + md.append('') + md.append('| NodeId | BrowseName | InverseName | Subtype of | Description |') + md.append('|---|---|---|---|---|') + for nid in ref_types: + n = NODES[nid] + md.append(f"| ns={OWN_NS};i={nid} | {n.bname} | {n.inverse or ''} | {_link(_friendly(_supertype(n)))} | " + f"{(n.desc or '').replace('|', '/')} |") + md.append('') + + md.append('### Methods\n') + md.append('| Method | Owning type | Input arguments | Output arguments |') + md.append('|---|---|---|---|') + for nid in ORDER: + n = NODES[nid] + if n.cls != "UAMethod" or n.category is not None: + continue # registry declaration methods only; legacy methods are listed under §13.1 + owner = NODES[int(n.parent.split("i=")[1])].bname if n.parent else "" + ins = ", ".join(method_args.get(nid, [])) or "(none)" + outs = ", ".join(method_out.get(nid, [])) or "(none)" + md.append(f"| {n.bname} | {_link(owner)} | {ins} | {outs} |") + md.append('') + + md.append('### Well-known instances\n') + md.append('| BrowseName | NodeId | TypeDefinition | Note |') + md.append('|---|---|---|---|') + for nid in ORDER: + n = NODES[nid] + if n.category != CAT_INST or n.cls != "UAObject": + continue + td = "" + for rt, tgt, fwd in n.refs: + if rt == HasTypeDefinition: + td = _link(_friendly(tgt)) + md.append(f"| {n.bname} | ns={OWN_NS};i={nid} | {td} | {(n.desc or '').replace('|','/')} |") + md.append('') + + # ---- Legacy model (OPC 10100-1 v1.02, incorporated and deprecated) -------- + md.append('### Legacy model (OPC 10100-1 v1.02 — preserved, deprecated)\n') + md.append('The published OPC 10100-1 v1.02 WoT Connectivity model is incorporated into this combined NodeSet ' + 'unchanged, at its exact published NodeIds (`1..172`) and NodeClasses (preserved from the pinned ' + '`legacy/WotConnection.csv`). Because the additive registry supersedes it, the whole management/upload ' + 'surface carries `ReleaseStatus="Deprecated"` — it is deprecated, not removed, so existing 1.02 clients ' + 'keep working. The `WoTAssetConnectionManagement` object remains at its published NodeId and callable. ' + 'Method signatures are unchanged and are listed in §13.1.\n') + for nid in legacy_types: + md.append(f'') + md.append('') + md.append('| NodeId | BrowseName | NodeClass | Subtype of | Release status |') + md.append('|---|---|---|---|---|') + for nid in legacy_types: + n = NODES[nid] + status = n.attrs.get("_release_status", "Released") + md.append(f"| ns={OWN_NS};i={nid} | {n.bname} | {n.cls[2:]} | {_link(_friendly(_supertype(n)))} | {status} |") + md.append('') + if legacy_instances: + md.append('| Well-known instance | NodeId | TypeDefinition | Release status |') + md.append('|---|---|---|---|') + for nid in legacy_instances: + n = NODES[nid] + td = "" + for rt, tgt, fwd in n.refs: + if rt == HasTypeDefinition: + td = _link(_friendly(tgt)) + status = n.attrs.get("_release_status", "Released") + md.append(f"| {n.bname} | ns={OWN_NS};i={nid} | {td} | {status} |") + md.append('') + return "\n".join(md).rstrip("\n") + "\n" + + +if __name__ == "__main__": + here = os.path.dirname(os.path.abspath(__file__)) + outdir = os.path.dirname(here) + with open(os.path.join(outdir, "Opc.Ua.WoTCon.NodeSet2.xml"), "w", encoding="utf-8") as f: + f.write(emit()) + with open(os.path.join(outdir, "Opc.Ua.WoTCon.NodeIds.csv"), "w", encoding="utf-8") as f: + f.write(emit_csv()) + with open(os.path.join(here, "model-reference.md"), "w", encoding="utf-8") as f: + f.write(emit_md()) + nt = sum(1 for k in NODES if NODES[k].cls in ("UAObjectType", "UADataType", "UAReferenceType")) + print(f"Nodes: {len(NODES)} (types: {nt})") + print(f"Member id range: 64500..{_next_member[0] - 1}") diff --git a/wot-specs/WoT-Connectivity/tools/model-reference.md b/wot-specs/WoT-Connectivity/tools/model-reference.md new file mode 100644 index 0000000..d83e677 --- /dev/null +++ b/wot-specs/WoT-Connectivity/tools/model-reference.md @@ -0,0 +1,563 @@ + + +## Annex A — Information model + +This annex is the normative node reference. It is generated from `tools/build_model.py` and always matches `Opc.Ua.WoTCon.NodeSet2.xml`. It documents one combined model in the companion namespace `http://opcfoundation.org/UA/WoT-Con/` (namespace index `2` in this NodeSet, after the required `http://opcfoundation.org/UA/xRegistry/` base model at index `1`). The additive **WoT Connectivity 1.1** registry types **extend the abstract [OPC UA — xRegistry](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md) base types** (`RegistryType`/`GroupType`/`ResourceType`) and use provisional NodeIds in the `64000+` block (final IDs are assigned by the OPC Foundation). The incorporated **OPC 10100-1 v1.02** legacy model is preserved unchanged at its published NodeIds `1..172` and is documented, with its `Deprecated` release status, under *Legacy model* below. The **Declared in** column marks members inherited from a supertype. + +### Type overview + +| NodeId | BrowseName | NodeClass | Subtype of | +|---|---|---|---| +| ns=2;i=64000 | [WoTRegistryType](#type-WoTRegistryType) | ObjectType | [RegistryType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-RegistryType) | +| ns=2;i=64001 | [ThingDescriptionGroupType](#type-ThingDescriptionGroupType) | ObjectType | [GroupType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-GroupType) | +| ns=2;i=64002 | [ThingModelGroupType](#type-ThingModelGroupType) | ObjectType | [GroupType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-GroupType) | +| ns=2;i=64003 | [WoTDocumentType](#type-WoTDocumentType) | ObjectType | [ResourceType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-ResourceType) | +| ns=2;i=64004 | [ThingDescriptionFileType](#type-ThingDescriptionFileType) | ObjectType | [WoTDocumentType](#type-WoTDocumentType) | +| ns=2;i=64005 | [ThingModelFileType](#type-ThingModelFileType) | ObjectType | [WoTDocumentType](#type-WoTDocumentType) | +| ns=2;i=64006 | [WoTBindingType](#type-WoTBindingType) | ObjectType | [BaseObjectType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) | +| ns=2;i=64010 | [WoTResourceEventType](#type-WoTResourceEventType) | ObjectType | [BaseEventType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.4.2) | +| ns=2;i=64011 | [WoTValidationFailureEventType](#type-WoTValidationFailureEventType) | ObjectType | [WoTResourceEventType](#type-WoTResourceEventType) | +| ns=2;i=64012 | [WoTLoadFailureEventType](#type-WoTLoadFailureEventType) | ObjectType | [WoTResourceEventType](#type-WoTResourceEventType) | +| ns=2;i=64013 | [WoTBindingFailureEventType](#type-WoTBindingFailureEventType) | ObjectType | [WoTResourceEventType](#type-WoTResourceEventType) | +| ns=2;i=64014 | [WoTRefreshCompletedEventType](#type-WoTRefreshCompletedEventType) | ObjectType | [BaseEventType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.4.2) | +| ns=2;i=64020 | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64021 | [WoTLoadStateEnum](#type-WoTLoadStateEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64022 | [WoTRefreshModeEnum](#type-WoTRefreshModeEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64023 | [WoTAtomicityEnum](#type-WoTAtomicityEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64024 | [WoTDeletePolicyEnum](#type-WoTDeletePolicyEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64025 | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64026 | [WoTPhaseEnum](#type-WoTPhaseEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64027 | [WoTBindingCapabilityEnum](#type-WoTBindingCapabilityEnum) | DataType | [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) | +| ns=2;i=64040 | [WoTValidationOutcomeDataType](#type-WoTValidationOutcomeDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64041 | [WoTBindingCapabilityDataType](#type-WoTBindingCapabilityDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64042 | [WoTRefreshOptionsDataType](#type-WoTRefreshOptionsDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64043 | [WoTResourceSelectorDataType](#type-WoTResourceSelectorDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64044 | [WoTResourceLoadResultDataType](#type-WoTResourceLoadResultDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64045 | [WoTRefreshSummaryDataType](#type-WoTRefreshSummaryDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64046 | [WoTDependencyDataType](#type-WoTDependencyDataType) | DataType | [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) | +| ns=2;i=64060 | [HasWoTProjection](#type-HasWoTProjection) | ReferenceType | [NonHierarchicalReferences](https://reference.opcfoundation.org/specs/OPC-10000-5/11.3) | + +### Object types + + + +#### WoTRegistryType (ns=2;i=64000) + +*Inherits from:* [RegistryType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-RegistryType) + +The WoT Connectivity 1.1 registry root - an xRegistry RegistryType (a FolderType) that holds ThingDescriptionGroupType and ThingModelGroupType groups. The stored Thing Description / Thing Model files and their versions are canonical; the projected AddressSpace (types from Thing Models, instances from Thing Descriptions) is derived code-behind. Exposed as a well-known WoTRegistry object under the Server object (i=2253). Adds registry-wide refresh, generation and validation-policy state and the Refresh Method. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| AutoRefresh | Variable | Boolean | Optional | WoTRegistryType | True if the registry automatically re-projects stored documents (per RefreshMode); false if only explicit Refresh calls re-project. | +| RefreshMode | Variable | [WoTRefreshModeEnum](#type-WoTRefreshModeEnum) | Optional | WoTRegistryType | How automatic refresh is triggered when AutoRefresh is true. | +| RefreshInterval | Variable | Duration | Optional | WoTRegistryType | The interval used when RefreshMode is Periodic. | +| RefreshGeneration | Variable | UInt32 | Mandatory | WoTRegistryType | The current committed projection generation; incremented on every committed refresh. Materialized nodes carry the generation in their NodeVersion for correlation. | +| LastRefreshTime | Variable | DateTime | Optional | WoTRegistryType | UTC time of the last completed refresh. | +| LastRefreshSummary | Variable | [WoTRefreshSummaryDataType](#type-WoTRefreshSummaryDataType) | Optional | WoTRegistryType | An immutable snapshot summarizing the last completed refresh. | +| DefaultAtomicity | Variable | [WoTAtomicityEnum](#type-WoTAtomicityEnum) | Optional | WoTRegistryType | The commit granularity applied when a Refresh omits an explicit atomicity. | +| DeletePolicy | Variable | [WoTDeletePolicyEnum](#type-WoTDeletePolicyEnum) | Optional | WoTRegistryType | The default policy for treating dependents on unload/delete. | +| ValidateFormat | Variable | Boolean | Optional | WoTRegistryType | Registry-wide default: validate document format on ingest/refresh. | +| ValidateCompatibility | Variable | Boolean | Optional | WoTRegistryType | Registry-wide default: validate version compatibility on ingest/refresh. | +| StrictValidation | Variable | Boolean | Optional | WoTRegistryType | If true, a validation warning is treated as a failure. | +| VocabularyVersion | Variable | String | Optional | WoTRegistryType | The version-pinned WoT Binding JSON-LD vocabulary this registry validates and projects against. | +| SelectedBindings | Variable | [WoTBindingCapabilityDataType](#type-WoTBindingCapabilityDataType)\[\] | Optional | WoTRegistryType | An immutable snapshot array of the protocol bindings currently selected/active registry-wide. | +| SupportedBindings | Object | | Optional | WoTRegistryType | A folder of browseable WoTBindingType binding descriptors the server can realize (the live, per-field form of the selected-bindings snapshot). | +| | Object | | OptionalPlaceholder | WoTRegistryType | A Thing Description Group held by this registry (constrained to the ThingDescriptionGroupType subtype). | +| | Object | | OptionalPlaceholder | WoTRegistryType | A Thing Model Group held by this registry (constrained to the ThingModelGroupType subtype). | +| Refresh | Method | | Optional | WoTRegistryType | Re-project selected stored documents into the AddressSpace. Idempotent: a document whose content digest is unchanged is reported Unchanged and not re-materialized unless Options.Force is set. Projects into a shadow generation and switches atomically per Options.Atomicity; superseded generations use the implementation's documented graceful or immediate retirement policy. If ExpectedGeneration is non-zero and does not equal RefreshGeneration, the call fails with Bad_InvalidState and changes nothing (optimistic concurrency). An empty Selection selects the whole registry. | + +*Generates events:* [WoTRefreshCompletedEventType](#type-WoTRefreshCompletedEventType) + + + +#### ThingDescriptionGroupType (ns=2;i=64001) + +*Inherits from:* [GroupType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-GroupType) + +An xRegistry GroupType that collects related ThingDescriptionFileType resources (a Thing Description Group per the WoT xRegistry model). Adds the group-level format/compatibility validation policy. Its placeholder constrains members to the Thing Description subtype. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| ValidateFormat | Variable | Boolean | Optional | ThingDescriptionGroupType | Group-level policy: validate Thing Description format (WoT-TD/1.1) on ingest. | +| ValidateCompatibility | Variable | Boolean | Optional | ThingDescriptionGroupType | Group-level policy: validate version compatibility on ingest. | +| ConsistentFormat | Variable | Boolean | Optional | ThingDescriptionGroupType | Group-level policy: require all versions of a resource to share one format. | +| | Object | | OptionalPlaceholder | ThingDescriptionGroupType | A Thing Description resource held by this group (constrained to the ThingDescriptionFileType subtype). | + + + +#### ThingModelGroupType (ns=2;i=64002) + +*Inherits from:* [GroupType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-GroupType) + +An xRegistry GroupType that collects related ThingModelFileType resources (a Thing Model Group per the WoT xRegistry model). Adds the group-level format/compatibility validation policy. Its placeholder constrains members to the Thing Model subtype. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| ValidateFormat | Variable | Boolean | Optional | ThingModelGroupType | Group-level policy: validate Thing Model format (WoT-TM/1.1) on ingest. | +| ValidateCompatibility | Variable | Boolean | Optional | ThingModelGroupType | Group-level policy: validate version compatibility on ingest. | +| ConsistentFormat | Variable | Boolean | Optional | ThingModelGroupType | Group-level policy: require all versions of a resource to share one format. | +| | Object | | OptionalPlaceholder | ThingModelGroupType | A Thing Model resource held by this group (constrained to the ThingModelFileType subtype). | + + + +#### WoTDocumentType (ns=2;i=64003) *(abstract)* + +*Inherits from:* [ResourceType](https://github.com/marcschier/opcua-drafts/blob/main/core-specs/xregistry/OPC-UA-xRegistry.md#type-ResourceType) + +The abstract base of a stored WoT document resource - an xRegistry ResourceType (a FileType) whose content bytes are the JSON-LD document, read/written with the inherited Open/Read/Write/Close Methods. Adds the derived-projection metadata (load state, desired/active version, validation and compatibility outcomes, content digest, materialized-node count and root, selected bindings) and the Validate, SetEnabled and SetDefaultVersion Methods. Concrete subtypes fix the document kind. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| DocumentKind | Variable | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | Mandatory | WoTDocumentType | Whether this document is a Thing Description or a Thing Model. Fixed by the concrete subtype. | +| Enabled | Variable | Boolean | Mandatory | WoTDocumentType | The desired enabled state: true requests that the document be validated and projected; false requests unload. | +| LoadState | Variable | [WoTLoadStateEnum](#type-WoTLoadStateEnum) | Mandatory | WoTDocumentType | The actual lifecycle state of this document's derived projection. | +| DesiredVersionId | Variable | String | Optional | WoTDocumentType | The versionid the operator wants active for this resource (the desired/pinned version). | +| ActiveVersionId | Variable | String | Optional | WoTDocumentType | The versionid whose projection is currently active. | +| IsDefault | Variable | Boolean | Optional | WoTDocumentType | xRegistry isdefault: true when this version is the resource's default (sticky) version. | +| Ancestor | Variable | String | Optional | WoTDocumentType | xRegistry ancestor: the versionid this version derives from (version lineage). | +| Compatibility | Variable | String | Optional | WoTDocumentType | The compatibility policy all versions of this resource adhere to (for example NONE, BACKWARD, FULL). | +| AutoRefresh | Variable | Boolean | Optional | WoTDocumentType | Per-document override of the registry AutoRefresh setting. | +| RefreshGeneration | Variable | UInt32 | Optional | WoTDocumentType | The registry generation at which this document was last projected. | +| LastRefreshTime | Variable | DateTime | Optional | WoTDocumentType | UTC time this document was last projected. | +| ContentDigest | Variable | ByteString | Optional | WoTDocumentType | The content digest (hash) of the stored document bytes; used to make refresh idempotent. | +| ValidationOutcome | Variable | [WoTValidationOutcomeDataType](#type-WoTValidationOutcomeDataType) | Optional | WoTDocumentType | An immutable snapshot of this document's format and compatibility validation result. | +| MaterializedNodeCount | Variable | UInt32 | Optional | WoTDocumentType | The number of AddressSpace nodes materialized from this document's active projection. | +| RootNodeId | Variable | [NodeId](https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.1) | Optional | WoTDocumentType | The root node of this document's active projection (the type or instance root). | +| SelectedBindings | Variable | [WoTBindingCapabilityDataType](#type-WoTBindingCapabilityDataType)\[\] | Optional | WoTDocumentType | An immutable snapshot array of the protocol bindings selected for this document's forms. | +| Validate | Method | | Optional | WoTDocumentType | Validate the stored document (format and, when enabled, compatibility) without changing its projection. Returns the outcome snapshot; also refreshes the ValidationOutcome Property. | +| SetEnabled | Method | | Optional | WoTDocumentType | Set the desired Enabled state of this document. Enabling requests validation and projection; disabling requests unload per the registry DeletePolicy. If ExpectedEpoch is non-zero and does not equal the resource's current Epoch the call fails with Bad_InvalidState and changes nothing. | +| SetDefaultVersion | Method | | Optional | WoTDocumentType | Make a specific version of this resource its default (sticky) version, so that resolvers selecting the resource without a versionid resolve to it. If ExpectedEpoch is non-zero and does not equal the resource's current Epoch the call fails with Bad_InvalidState and changes nothing. | + +*Generates events:* [WoTValidationFailureEventType](#type-WoTValidationFailureEventType), [WoTLoadFailureEventType](#type-WoTLoadFailureEventType), [WoTBindingFailureEventType](#type-WoTBindingFailureEventType) + + + +#### ThingDescriptionFileType (ns=2;i=64004) + +*Inherits from:* [WoTDocumentType](#type-WoTDocumentType) + +A concrete WoTDocumentType whose content is a W3C WoT Thing Description (WoT-TD/1.1, application/td+json). Projects to OPC UA instances: affordances become Variables, Methods and event sources; forms become binder plans. Adds the Thing instance identity (ThingId, base URI) and the link to the Thing Model it derives from. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| ThingId | Variable | String | Optional | ThingDescriptionFileType | The Thing Description id (a URI/URN identifying the concrete Thing instance). | +| ThingTitle | Variable | String | Optional | ThingDescriptionFileType | The Thing Description human-readable title. | +| BaseUri | Variable | String | Optional | ThingDescriptionFileType | The Thing Description base URI used to resolve relative form hrefs. | +| ModelReference | Variable | String | Optional | ThingDescriptionFileType | The xid or href of the Thing Model this Thing Description derives from (links rel=type), when present. | + + + +#### ThingModelFileType (ns=2;i=64005) + +*Inherits from:* [WoTDocumentType](#type-WoTDocumentType) + +A concrete WoTDocumentType whose content is a W3C WoT Thing Model (WoT-TM/1.1, application/tm+json). Projects to OPC UA types: it materializes an ObjectType or VariableType and the affordance member declarations and modelling rules. Adds the derived type NodeId and model version. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| ModelTitle | Variable | String | Optional | ThingModelFileType | The Thing Model human-readable title. | +| ModelVersion | Variable | String | Optional | ThingModelFileType | The Thing Model version (WoT version.model), when present. | +| DerivedTypeNodeId | Variable | [NodeId](https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.1) | Optional | ThingModelFileType | The ObjectType or VariableType materialized from this Thing Model. | + + + +#### WoTBindingType (ns=2;i=64006) + +*Inherits from:* [BaseObjectType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) + +A browseable protocol-binding descriptor: the live, per-field representation of one W3C WoT protocol binding the server can realize (its URI, title, version-pinned W3C document, draft maturity, enabled state, content types and a capability snapshot). Selected/active binding sets are additionally exposed as immutable WoTBindingCapabilityDataType array snapshots. Policy and identity are browseable; no credentials or secrets are ever exposed here. + +| BrowseName | NodeClass | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---|---| +| BindingUri | Variable | String | Mandatory | WoTBindingType | The WoT protocol-binding vocabulary URI this descriptor represents. | +| Title | Variable | String | Optional | WoTBindingType | Human-readable binding title. | +| ProfileVersion | Variable | String | Optional | WoTBindingType | The version-pinned W3C binding document version. | +| DraftMaturity | Variable | String | Optional | WoTBindingType | The W3C maturity of the pinned binding document (for example WD, CR, PR, REC). | +| Enabled | Variable | Boolean | Optional | WoTBindingType | True if the server currently realizes forms of this binding. | +| ContentTypes | Variable | String\[\] | Optional | WoTBindingType | The content types this binding produces/consumes. | +| Capabilities | Variable | [WoTBindingCapabilityDataType](#type-WoTBindingCapabilityDataType) | Optional | WoTBindingType | An immutable capability snapshot for this binding. | + +### Event types + + + +#### WoTResourceEventType (ns=2;i=64010) *(abstract)* + +*Subtype of:* [BaseEventType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.4.2) + +The common base event for a WoT resource lifecycle notification. Carries the identity of the affected resource/version, the document kind, the refresh generation, the phase reached and the outcome. Abstract; servers emit one of its concrete subtypes. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| Xid | String | Mandatory | WoTResourceEventType | The xRegistry xid of the affected resource/version. | +| ResourceId | String | Mandatory | WoTResourceEventType | The resourceid of the affected resource. | +| VersionId | String | Mandatory | WoTResourceEventType | The versionid of the affected version. | +| DocumentKind | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | Mandatory | WoTResourceEventType | Whether the document is a Thing Description or a Thing Model. | +| Generation | UInt32 | Mandatory | WoTResourceEventType | The refresh generation the notification relates to. | +| Phase | [WoTPhaseEnum](#type-WoTPhaseEnum) | Mandatory | WoTResourceEventType | The phase reached (the failing phase on a failure event). | +| Outcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | Mandatory | WoTResourceEventType | The outcome the notification reports. | + + + +#### WoTValidationFailureEventType (ns=2;i=64011) + +*Subtype of:* [WoTResourceEventType](#type-WoTResourceEventType) + +Raised when a document fails format or compatibility validation. The failing resource is the event source; the stored document is retained and any previous valid projection stays active. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| ValidationOutcome | [WoTValidationOutcomeDataType](#type-WoTValidationOutcomeDataType) | Mandatory | WoTValidationFailureEventType | The full validation outcome snapshot for the failure. | + + + +#### WoTLoadFailureEventType (ns=2;i=64012) + +*Subtype of:* [WoTResourceEventType](#type-WoTResourceEventType) + +Raised when a validated document fails to project (materialize) into the AddressSpace, or when its shadow generation cannot be activated. The failing resource is the event source. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| LoadState | [WoTLoadStateEnum](#type-WoTLoadStateEnum) | Mandatory | WoTLoadFailureEventType | The load state after the failed projection/activation. | +| FailedNodeId | [NodeId](https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.1) | Mandatory | WoTLoadFailureEventType | The node whose materialization failed, if identifiable. | +| Reason | String | Mandatory | WoTLoadFailureEventType | Human-readable failure reason. | + + + +#### WoTBindingFailureEventType (ns=2;i=64013) + +*Subtype of:* [WoTResourceEventType](#type-WoTResourceEventType) + +Raised when a form cannot be bound to its protocol binding (unknown binding, unsupported operation or a runtime binder error). The failing resource is the event source. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| BindingUri | String | Mandatory | WoTBindingFailureEventType | The binding URI that could not be bound. | +| Reason | String | Mandatory | WoTBindingFailureEventType | Human-readable binding failure reason. | + + + +#### WoTRefreshCompletedEventType (ns=2;i=64014) + +*Subtype of:* [BaseEventType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.4.2) + +Raised by the registry when a Refresh completes (including automatic refreshes). Carries the refresh summary and the committed generation. The registry object is the event source. + +| Field | DataType | ModellingRule | Declared in | Description | +|---|---|---|---|---| +| Summary | [WoTRefreshSummaryDataType](#type-WoTRefreshSummaryDataType) | Mandatory | WoTRefreshCompletedEventType | The refresh summary snapshot. | +| RequestId | String | Mandatory | WoTRefreshCompletedEventType | The caller-supplied request identifier echoed from the Refresh call. | +| Generation | UInt32 | Mandatory | WoTRefreshCompletedEventType | The committed generation. | + +### DataTypes + + + +#### WoTDocumentKindEnum (ns=2;i=64020) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The kind of WoT document a resource carries: a Thing Description (a concrete instance) or a Thing Model (a reusable type template). + +| Name | Value | Description | +|---|---|---| +| ThingDescription | 0 | A W3C WoT Thing Description (WoT-TD/1.1); projects to OPC UA instances. | +| ThingModel | 1 | A W3C WoT Thing Model (WoT-TM/1.1); projects to OPC UA types. | + + + +#### WoTLoadStateEnum (ns=2;i=64021) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The lifecycle state of a WoT document's derived projection in the AddressSpace. The registry file always remains stored; this enum reflects only the state of the code-behind projection. + +| Name | Value | Description | +|---|---|---| +| Unloaded | 0 | Stored but not projected into the AddressSpace. | +| Validating | 1 | Format and compatibility validation is in progress. | +| Loading | 2 | The projection is being materialized under a shadow generation. | +| Active | 3 | The projection is committed and serving as the active generation. | +| Failed | 4 | Validation or projection failed; the last valid projection (if any) stays active. | +| Superseded | 5 | A newer generation has replaced this one; awaiting the configured retirement policy. | +| Retiring | 6 | Graceful retirement is waiting for monitored items and requests to drain. | +| Retired | 7 | The projection has been removed from the AddressSpace. | + + + +#### WoTRefreshModeEnum (ns=2;i=64022) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +How a registry or document triggers refresh of its derived projection. + +| Name | Value | Description | +|---|---|---| +| Manual | 0 | Only an explicit Refresh Method call re-projects. | +| Periodic | 1 | The registry re-projects on a fixed interval (RefreshInterval). | +| EventDriven | 2 | The registry re-projects when a stored document changes (write/CloseAndUpdate). | +| Scheduled | 3 | The registry re-projects on an implementation-defined schedule. | + + + +#### WoTAtomicityEnum (ns=2;i=64023) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The commit granularity applied when a refresh projects one or more documents. + +| Name | Value | Description | +|---|---|---| +| PerResource | 0 | Each resource commits independently; a failure isolates to that resource. | +| PerGroup | 1 | All resources of a group commit together or not at all. | +| PerClosure | 2 | A document and its full dependency closure (DAG) commit atomically. | +| PerRegistry | 3 | All selected documents commit as a single all-or-nothing transaction. | + + + +#### WoTDeletePolicyEnum (ns=2;i=64024) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +How the registry treats dependents when a document version is unloaded or deleted. + +| Name | Value | Description | +|---|---|---| +| Reject | 0 | Reject the operation while any other loaded document still depends on it. | +| Retire | 1 | Retire the projection but keep the stored document for dependents to resolve. | +| Cascade | 2 | Unload dependents that resolve only through this document. | +| Force | 3 | Force-unload the projection even while dependents remain, marking them Failed. | + + + +#### WoTOutcomeEnum (ns=2;i=64025) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The outcome of a validation, projection or refresh operation on a document or the registry. + +| Name | Value | Description | +|---|---|---| +| Success | 0 | The operation completed and changed the projection. | +| Unchanged | 1 | The operation was idempotent; the content digest matched and nothing changed. | +| Warning | 2 | The operation completed with non-fatal warnings. | +| Skipped | 3 | The operation was not applicable and was skipped. | +| Rejected | 4 | The operation was rejected by policy (for example concurrency or delete policy). | +| Failed | 5 | The operation failed; the previous valid projection (if any) remains active. | + + + +#### WoTPhaseEnum (ns=2;i=64026) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +The processing phase a document reached, used to locate where an outcome was produced. + +| Name | Value | Description | +|---|---|---| +| Fetch | 0 | Reading document bytes and resolving registry-scoped context/schema references. | +| Parse | 1 | Parsing the JSON-LD document. | +| FormatValidation | 2 | Validating the document against its WoT-TD/WoT-TM format. | +| CompatibilityValidation | 3 | Validating the version against the resource compatibility policy. | +| DependencyResolution | 4 | Resolving the dependency closure (tm:extends, tm:ref, links rel=type). | +| Projection | 5 | Materializing types/instances into a shadow generation. | +| Activation | 6 | Committing the shadow generation as active. | +| Retirement | 7 | Applying the configured graceful or immediate retirement policy. | + + + +#### WoTBindingCapabilityEnum (ns=2;i=64027) + +*Subtype of:* [Enumeration](https://reference.opcfoundation.org/specs/OPC-10000-3/8.40) + +A single interaction operation a protocol binding supports, aligned with the WoT form op vocabulary. + +| Name | Value | Description | +|---|---|---| +| ReadProperty | 0 | Read a property affordance. | +| WriteProperty | 1 | Write a property affordance. | +| ObserveProperty | 2 | Observe (subscribe to) a property affordance. | +| InvokeAction | 3 | Invoke an action affordance. | +| SubscribeEvent | 4 | Subscribe to an event affordance. | +| UnsubscribeEvent | 5 | Unsubscribe from an event affordance. | + + + +#### WoTValidationOutcomeDataType (ns=2;i=64040) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable snapshot of a document's format and compatibility validation result. Read as a single Variant value; a new snapshot is produced on each validation and never mutated in place. + +| Field | DataType | Description | +|---|---|---| +| FormatValidated | Boolean | True if format validation was performed. | +| FormatOutcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | Outcome of format validation (WoT-TD/WoT-TM conformance). | +| FormatReason | String | Human-readable reason for the format outcome (empty on success). | +| CompatibilityValidated | Boolean | True if compatibility validation was performed. | +| CompatibilityOutcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | Outcome of compatibility validation against the resource policy. | +| CompatibilityReason | String | Human-readable reason for the compatibility outcome (empty on success). | +| CompatibilityPolicy | String | The compatibility policy in force (for example NONE, BACKWARD, FULL). | +| ValidatedAt | DateTime | UTC time the validation completed. | +| VocabularyVersion | String | The pinned WoT Binding JSON-LD vocabulary version used for validation. | + + + +#### WoTBindingCapabilityDataType (ns=2;i=64041) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable snapshot of a protocol binding's identity, version-pinned W3C document, maturity and supported operations. Held as an array element only for immutable snapshots; browseable binding objects (WoTBindingType) carry the live, per-field form. + +| Field | DataType | Description | +|---|---|---| +| BindingUri | String | The WoT protocol-binding vocabulary URI (for example the OPC UA, HTTP or Modbus binding). | +| Title | String | Human-readable binding title. | +| ProfileVersion | String | The version-pinned W3C binding document version this capability snapshot was built against. | +| DraftMaturity | String | The W3C maturity of the pinned binding document (for example WD, CR, PR, REC). | +| Capabilities | [WoTBindingCapabilityEnum](#type-WoTBindingCapabilityEnum)\[\] | The interaction operations this binding supports. | +| ContentTypes | String\[\] | The content types this binding produces/consumes. | + + + +#### WoTRefreshOptionsDataType (ns=2;i=64042) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +Immutable options controlling a single Refresh invocation. + +| Field | DataType | Description | +|---|---|---| +| Atomicity | [WoTAtomicityEnum](#type-WoTAtomicityEnum) | Commit granularity for this refresh. | +| Force | Boolean | Re-project even when the content digest is unchanged. | +| DryRun | Boolean | Validate and compute results without committing any projection change. | +| IncludeDependents | Boolean | Also refresh documents that depend on the selected documents. | +| DeletePolicy | [WoTDeletePolicyEnum](#type-WoTDeletePolicyEnum) | How to treat dependents when a selected document is unloaded/retired. | +| MaxParallelism | UInt32 | Maximum number of documents projected concurrently; 0 lets the server decide. | +| Timeout | Duration | Overall time budget for the refresh; 0 lets the server decide. | + + + +#### WoTResourceSelectorDataType (ns=2;i=64043) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable selector identifying which stored documents a Refresh applies to. An empty selector array selects the whole registry. + +| Field | DataType | Description | +|---|---|---| +| Kind | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | Restrict to Thing Descriptions or Thing Models; omit to select both. | +| GroupId | String | Restrict to a group by groupid; empty selects all groups. | +| ResourceId | String | Restrict to a resource by resourceid; empty selects all resources. | +| VersionId | String | Restrict to a version by versionid; empty selects the resource's default version. | +| Xid | String | Select a single entity by its xRegistry xid; overrides the other fields when set. | + + + +#### WoTResourceLoadResultDataType (ns=2;i=64044) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable per-resource result row of a Refresh. Never mutated; the array is a point-in-time snapshot for one generation. + +| Field | DataType | Description | +|---|---|---| +| Xid | String | The xRegistry xid of the affected resource/version. | +| GroupId | String | The groupid of the resource's group. | +| ResourceId | String | The resourceid of the affected resource. | +| VersionId | String | The versionid that was projected. | +| Kind | [WoTDocumentKindEnum](#type-WoTDocumentKindEnum) | Whether the document is a Thing Description or a Thing Model. | +| Outcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | The per-resource outcome. | +| Phase | [WoTPhaseEnum](#type-WoTPhaseEnum) | The phase the resource reached (the failing phase on failure). | +| LoadState | [WoTLoadStateEnum](#type-WoTLoadStateEnum) | The resulting load state of the projection. | +| Generation | UInt32 | The refresh generation this result belongs to. | +| MaterializedNodeCount | UInt32 | Number of AddressSpace nodes materialized for this resource. | +| RootNodeId | [NodeId](https://reference.opcfoundation.org/specs/OPC-10000-3/8.2.1) | The root node of the materialized projection, if any. | +| ContentDigest | ByteString | The content digest (hash) of the projected document bytes. | +| Message | String | Human-readable detail for the outcome. | + + + +#### WoTRefreshSummaryDataType (ns=2;i=64045) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable summary of one Refresh invocation, also carried by the WoTRefreshCompletedEventType and cached on the registry as LastRefreshSummary. + +| Field | DataType | Description | +|---|---|---| +| RequestId | String | The caller-supplied request identifier echoed back for correlation. | +| Generation | UInt32 | The committed refresh generation (0 on a dry run or full failure). | +| Outcome | [WoTOutcomeEnum](#type-WoTOutcomeEnum) | The overall outcome of the refresh. | +| Atomicity | [WoTAtomicityEnum](#type-WoTAtomicityEnum) | The commit granularity that was applied. | +| StartTime | DateTime | UTC start time of the refresh. | +| EndTime | DateTime | UTC end time of the refresh. | +| Total | UInt32 | Total number of resources considered. | +| Succeeded | UInt32 | Number of resources that changed successfully. | +| Unchanged | UInt32 | Number of resources that were idempotently unchanged. | +| Failed | UInt32 | Number of resources that failed. | +| Skipped | UInt32 | Number of resources skipped by selection or policy. | +| Retired | UInt32 | Number of superseded generations retired. | + + + +#### WoTDependencyDataType (ns=2;i=64046) + +*Subtype of:* [Structure](https://reference.opcfoundation.org/specs/OPC-10000-5/8.24) + +An immutable edge of the document dependency DAG, used to describe closures in results and diagnostics. + +| Field | DataType | Description | +|---|---|---| +| SourceXid | String | The xid of the dependent document. | +| TargetXid | String | The xid of the document depended upon (empty if unresolved). | +| TargetUri | String | The raw href/URI of the dependency as authored in the document. | +| RefType | String | The dependency kind (for example tm:extends, tm:ref, links.rel=type). | +| Resolved | Boolean | True if the dependency resolved to a stored document. | + +### Reference types + + + +| NodeId | BrowseName | InverseName | Subtype of | Description | +|---|---|---|---|---| +| ns=2;i=64060 | HasWoTProjection | WoTProjectionOf | [NonHierarchicalReferences](https://reference.opcfoundation.org/specs/OPC-10000-5/11.3) | Links a stored WoT document resource (source) to the root node of its derived AddressSpace projection (target). Used to correlate materialized nodes and their NodeVersion with the canonical document, and to find the document behind a projected node. | + +### Methods + +| Method | Owning type | Input arguments | Output arguments | +|---|---|---|---| +| Refresh | [WoTRegistryType](#type-WoTRegistryType) | Selection, Options, ExpectedGeneration, RequestId | Summary, Results, NewGeneration | +| Validate | [WoTDocumentType](#type-WoTDocumentType) | (none) | Outcome | +| SetEnabled | [WoTDocumentType](#type-WoTDocumentType) | Enabled, ExpectedEpoch | (none) | +| SetDefaultVersion | [WoTDocumentType](#type-WoTDocumentType) | VersionId, ExpectedEpoch | (none) | + +### Well-known instances + +| BrowseName | NodeId | TypeDefinition | Note | +|---|---|---|---| +| WoTRegistry | ns=2;i=64100 | [WoTRegistryType](#type-WoTRegistryType) | The server-wide WoT Connectivity 1.1 registry, a well-known component of the Server object. Its stored Thing Description / Thing Model files are canonical; the projected AddressSpace is derived. It is the notifier for the WoT resource lifecycle events raised by its groups and resources. | + +### Legacy model (OPC 10100-1 v1.02 — preserved, deprecated) + +The published OPC 10100-1 v1.02 WoT Connectivity model is incorporated into this combined NodeSet unchanged, at its exact published NodeIds (`1..172`) and NodeClasses (preserved from the pinned `legacy/WotConnection.csv`). Because the additive registry supersedes it, the whole management/upload surface carries `ReleaseStatus="Deprecated"` — it is deprecated, not removed, so existing 1.02 clients keep working. The `WoTAssetConnectionManagement` object remains at its published NodeId and callable. Method signatures are unchanged and are listed in §13.1. + + + + + + + +| NodeId | BrowseName | NodeClass | Subtype of | Release status | +|---|---|---|---|---| +| ns=2;i=1 | WoTAssetConnectionManagementType | ObjectType | [BaseObjectType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) | Deprecated | +| ns=2;i=42 | IWoTAssetType | ObjectType | [BaseInterfaceType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) | Deprecated | +| ns=2;i=105 | WoTAssetConfigurationType | ObjectType | [BaseInterfaceType](https://reference.opcfoundation.org/specs/OPC-10000-5/6.2) | Deprecated | +| ns=2;i=110 | WoTAssetFileType | ObjectType | [FileType](https://reference.opcfoundation.org/specs/OPC-10000-20/4.2) | Deprecated | +| ns=2;i=142 | HasWoTComponent | ReferenceType | [HasComponent](https://reference.opcfoundation.org/specs/OPC-10000-5/11.3) | Deprecated | + +| Well-known instance | NodeId | TypeDefinition | Release status | +|---|---|---|---| +| WoTAssetConnectionManagement | ns=2;i=31 | [WoTAssetConnectionManagementType](#type-WoTAssetConnectionManagementType) | Deprecated | diff --git a/wot-specs/WoT-Connectivity/tools/validate_local.py b/wot-specs/WoT-Connectivity/tools/validate_local.py new file mode 100644 index 0000000..f4970e2 --- /dev/null +++ b/wot-specs/WoT-Connectivity/tools/validate_local.py @@ -0,0 +1,448 @@ +#!/usr/bin/env python3 +"""Local structural validator for the WoT Connectivity 1.1 combined NodeSet + CSV + Annex A. + +Standard library only. Loads the local abstract xRegistry base NodeIds (the +RequiredModel this spec extends) and the pinned legacy NodeId table so the 1.02 +preservation can be proven. Base UA NodeIds are checked only when a local +validation aid table (tools/ref/UA.NodeIds.csv, gitignored) is present. + +Checks: + * XML well-formedness, unique own NodeIds, additive registry ids in the 64000+ + block, incorporated 1.02 legacy ids in the preserved 1..172 range. + * Every reference target resolves: own (ns=2), xRegistry base (ns=1), base UA. + * Types carry a HasSubtype inverse; instances/members carry a HasModellingRule + (except the well-known instances, encodings and well-known-parent members). + * UAObject/UAVariable carry a HasTypeDefinition. + * Structure DataTypes carry Default Binary + Default JSON encodings. + * Well-known Properties (InputArguments, OutputArguments, EnumStrings) carry a + namespace-0 BrowseName, not a namespace-2 (this spec's own) BrowseName. + * CSV <-> XML consistency (ids, classes, no orphans; reserved 'Unspecified' + legacy ids stay in the CSV without an XML node). + * Legacy preservation: the first 172 CSV rows match the pinned + legacy/WotConnection.csv byte-for-byte (every published NodeId and NodeClass + preserved); the required 1.02 symbols are present as nodes with the pinned id + and class; the management/upload surface carries ReleaseStatus="Deprecated"; + the well-known WoTAssetConnectionManagement (i=31) is under Objects, typed and + callable. + * The combined NodeSet uses one NamespaceUri (http://opcfoundation.org/UA/ + WoT-Con/) at model version 1.1.0. + * The well-known WoTRegistry instance is a component of the Server object + (i=2253), is an EventNotifier and is a HasNotifier target of the Server + (Server -> WoTRegistry notifier topology). + * The well-known WoTRegistry instance materializes a concrete Value for every + Mandatory member of WoTRegistryType, own (for example RefreshGeneration) + and inherited from the xRegistry RegistryType (RegistryId). + * The generated Annex A (tools/model-reference.md) is embedded verbatim in + OPC-UA-WoT-Connectivity.md (generated-annex equality). +""" +import os +import sys +import csv +import re +import xml.etree.ElementTree as ET + +HERE = os.path.dirname(os.path.abspath(__file__)) +GEN = os.path.dirname(HERE) +ROOT = os.path.dirname(os.path.dirname(GEN)) +REF = os.path.join(HERE, "ref") +NS = "{http://opcfoundation.org/UA/2011/03/UANodeSet.xsd}" +XML = os.path.join(GEN, "Opc.Ua.WoTCon.NodeSet2.xml") +CSVF = os.path.join(GEN, "Opc.Ua.WoTCon.NodeIds.csv") +SPEC = os.path.join(GEN, "OPC-UA-WoT-Connectivity.md") +MODELREF = os.path.join(HERE, "model-reference.md") +LEGACY_CSVF = os.path.join(GEN, "legacy", "WotConnection.csv") + +NAMESPACE = "http://opcfoundation.org/UA/WoT-Con/" +MODEL_VERSION = "1.1.0" + +XR_NS = 1 # required model: abstract xRegistry base +OWN_NS = 2 # this specification's own namespace (WoT-Con) +OWN_MIN = 64000 # additive registry block + +WELLKNOWN = {64100, 31} +SERVER = 2253 +OBJECTS = 85 + +# The 1.02 symbols that must be preserved (BrowseName-ish, id, NodeClass). +REQUIRED_LEGACY = { + 1: ("ObjectType", "WoTAssetConnectionManagementType"), + 31: ("Object", "WoTAssetConnectionManagement"), + 42: ("ObjectType", "IWoTAssetType"), + 105: ("ObjectType", "WoTAssetConfigurationType"), + 110: ("ObjectType", "WoTAssetFileType"), + 142: ("ReferenceType", "HasWoTComponent"), +} +# The 1.02 management/upload surface that must be machine-readably deprecated. +LEGACY_DEPRECATED_TYPES = {1, 42, 105, 110, 142, 31} + + +def load_ids(p): + s = set() + with open(p, encoding="utf-8") as f: + for row in csv.reader(f): + if len(row) >= 2 and row[1].strip().isdigit(): + s.add(int(row[1])) + return s + + +_ua_csv = os.path.join(REF, "UA.NodeIds.csv") +UA = load_ids(_ua_csv) if os.path.exists(_ua_csv) else None +# Common base/Part 5/Part 20 ids this spec references, in case the local UA table is minimal. +UA_EXTRA = {1, 3, 5, 6, 7, 9, 11, 12, 13, 15, 17, 18, 21, 22, 24, 29, 32, 35, 37, 38, 40, 41, 45, + 46, 47, 48, 58, 61, 63, 68, 76, 78, 80, 85, 95, 96, 256, 290, 291, 294, 296, 297, + 2004, 2041, 2253, 11508, 11510, 11575, 11616, 11715, 17602, 17603, 20998, 23751, 24263} + +# Pinned legacy NodeId table: the authoritative 1.02 preservation source. +LEGACY_ROWS = [] +if os.path.exists(LEGACY_CSVF): + with open(LEGACY_CSVF, encoding="utf-8") as f: + for r in csv.reader(f): + if len(r) == 3 and r[1].strip().isdigit(): + LEGACY_ROWS.append((r[0], int(r[1]), r[2])) +LEGACY_IDS = {sid for _, sid, _ in LEGACY_ROWS} +LEGACY_CONCRETE = {sid: cls for _, sid, cls in LEGACY_ROWS if cls != "Unspecified"} +LEGACY_RESERVED = {sid for _, sid, cls in LEGACY_ROWS if cls == "Unspecified"} + +_xr_csv = os.path.join(ROOT, "core-specs", "xregistry", "Opc.Ua.XRegistry.NodeIds.csv") +XR = load_ids(_xr_csv) if os.path.exists(_xr_csv) else None + +errors, warnings = [], [] +ALIAS = {} +tree = ET.parse(XML) +root = tree.getroot() +defined = {} +elems = [] + +NID_RE = re.compile(r"^(?:ns=(\d+);)?i=(\d+)$") + + +def parse_numeric_nodeid(t): + t = ALIAS.get(t, t) + m = NID_RE.match(t or "") + if not m: + return None + ns = int(m.group(1) or 0) + return ns, int(m.group(2)) + + +for el in root: + tag = el.tag.replace(NS, "") + if tag == "Aliases": + for a in el: + ALIAS[a.get("Alias")] = a.text + if not tag.startswith("UA"): + continue + nid = el.get("NodeId") + parsed = parse_numeric_nodeid(nid) + if parsed and parsed[0] == OWN_NS: + key = parsed[1] + if key in defined: + errors.append(f"dup NodeId ns={OWN_NS};i={key}") + defined[key] = (tag, el.get("BrowseName")) + elems.append((tag, el)) + + +def check(t, ctx): + parsed = parse_numeric_nodeid(t) + if parsed is None: + return + ns, v = parsed + if ns == OWN_NS: + if v in defined: + return + errors.append(f"{ctx}: ns={OWN_NS};i={v} not defined here") + return + if ns == XR_NS: + if XR is None or v in XR: + return + errors.append(f"{ctx}: ns={XR_NS};i={v} not defined in the xRegistry base model") + return + if v in UA_EXTRA: + return + if UA is None: + return + if v in UA: + return + errors.append(f"{ctx}: i={v} not defined here and not a known base/xRegistry id") + + +enc_types = set() # DataTypes that declare an encoding +for tag, el in elems: + bn = el.get("BrowseName") + nid = el.get("NodeId") + ctx = f"{tag} {bn} ({nid})" + parsed = parse_numeric_nodeid(nid) + if parsed and parsed[0] == OWN_NS and parsed[1] < OWN_MIN and parsed[1] not in LEGACY_IDS: + errors.append(f"{ctx}: own NodeId below reserved provisional block {OWN_MIN} and not a preserved 1.02 id") + if el.get("ParentNodeId"): + check(el.get("ParentNodeId"), ctx + " parent") + if el.get("DataType"): + check(el.get("DataType"), ctx + " datatype") + refs = el.find(NS + "References") + rl = [] + if refs is not None: + for r in refs: + rt = r.get("ReferenceType") + tgt = r.text + fwd = r.get("IsForward", "true") != "false" + rl.append((rt, tgt, fwd)) + check(rt, ctx + " reftype") + check(tgt, ctx + " ref") + reftypes = [rt for rt, _, _ in rl] + typedef = [t for rt, t, f in rl if rt == "HasTypeDefinition"] + is_enc = any(parse_numeric_nodeid(t) == (0, 76) for t in typedef) + # Track encodings for the DataType that owns them. + for rt, t, f in rl: + if rt == "HasEncoding" and not f: + p = parse_numeric_nodeid(t) + if p and p[0] == OWN_NS: + enc_types.add(p[1]) + if tag in ("UAObjectType", "UADataType", "UAVariableType", "UAReferenceType"): + if not any(rt == "HasSubtype" and not fwd for rt, _, fwd in rl): + errors.append(f"{ctx}: type without HasSubtype(inverse)") + if tag in ("UAVariable", "UAObject", "UAMethod") and el.get("ParentNodeId"): + p = parse_numeric_nodeid(el.get("ParentNodeId")) + wellknown_parent = p is not None and p[0] == 0 + own_id = parsed[1] if parsed and parsed[0] == OWN_NS else None + cat_el = el.find(NS + "Category") + is_instance = cat_el is not None and (cat_el.text or "").strip().endswith("Instances") + if "HasModellingRule" not in reftypes and not is_enc and not wellknown_parent: + if own_id not in WELLKNOWN and not is_instance: + warnings.append(f"{ctx}: instance/member without HasModellingRule") + if tag in ("UAVariable", "UAObject") and not typedef and not is_enc: + errors.append(f"{ctx}: missing HasTypeDefinition") + +# Structure DataTypes must declare both encodings. +for tag, el in elems: + if tag != "UADataType": + continue + parsed = parse_numeric_nodeid(el.get("NodeId")) + definition = el.find(NS + "Definition") + subtype = None + refs = el.find(NS + "References") + if refs is not None: + for r in refs: + if r.get("ReferenceType") == "HasSubtype" and r.get("IsForward") == "false": + subtype = parse_numeric_nodeid(r.text) + # Structures (subtype of i=22) require Default Binary + Default JSON encodings. + if subtype == (0, 22): + if parsed and parsed[1] not in enc_types: + errors.append(f"UADataType {el.get('BrowseName')}: Structure without HasEncoding objects") + +# Well-known Properties (InputArguments, OutputArguments, EnumStrings) must carry a +# namespace-0 BrowseName (Part 5/Part 6 well-known names), not this spec's own +# namespace-2 BrowseName prefix. +WELLKNOWN_NS0_PROPERTIES = {"InputArguments", "OutputArguments", "EnumStrings"} +for tag, el in elems: + bn = el.get("BrowseName") + if not bn: + continue + prefix, _, local = bn.rpartition(":") if ":" in bn else (None, None, bn) + if local in WELLKNOWN_NS0_PROPERTIES and prefix not in (None, "0"): + errors.append(f"{tag} {bn} ({el.get('NodeId')}): well-known Property must use the namespace-0 " + f"BrowseName '{local}', not '{bn}'") + +# CSV consistency +rows = [r for r in csv.reader(open(CSVF, encoding="utf-8")) if r] +csv_ids = {} +for r in rows: + if len(r) != 3: + errors.append(f"csv bad row {r}") + continue + if not r[1].isdigit(): + errors.append(f"csv nonnumeric id {r}") + continue + csv_ids[int(r[1])] = (r[2], r[0]) +for num, (tag, bn) in defined.items(): + if num not in csv_ids: + errors.append(f"ns={OWN_NS};i={num} {bn} missing from CSV") + elif csv_ids[num][0] != tag[2:]: + errors.append(f"class mismatch ns={OWN_NS};i={num}") +for cid in csv_ids: + if cid not in defined and csv_ids[cid][0] != "Unspecified": + errors.append(f"csv id {cid} not in XML") + +# --- Legacy OPC 10100-1 v1.02 preservation ------------------------------------- +# 1) The first 172 generated CSV rows must equal the pinned legacy table exactly +# (every published NodeId and NodeClass preserved, including reserved rows). +if not LEGACY_ROWS: + warnings.append("legacy/WotConnection.csv not found; skipping 1.02 preservation checks") +else: + gen_legacy = rows[:len(LEGACY_ROWS)] + pinned = [[s, str(i), c] for s, i, c in LEGACY_ROWS] + if gen_legacy != pinned: + errors.append("legacy preservation: generated CSV rows 1..172 do not match the pinned " + "legacy/WotConnection.csv (NodeId/NodeClass drift)") + # 2) Every concrete legacy id is present in the XML with the pinned NodeClass. + for sid, cls in LEGACY_CONCRETE.items(): + if sid not in defined: + errors.append(f"legacy preservation: 1.02 id {sid} ({cls}) missing from the combined NodeSet") + elif defined[sid][0][2:] != cls: + errors.append(f"legacy preservation: 1.02 id {sid} class {defined[sid][0][2:]} != pinned {cls}") + # 3) Reserved ids must NOT be emitted (they are burned, CSV-only). + for sid in LEGACY_RESERVED: + if sid in defined: + errors.append(f"legacy preservation: reserved (Unspecified) 1.02 id {sid} must not be emitted as a node") + # 4) Required 1.02 symbols present with the pinned id and class. + for sid, (cls, name) in REQUIRED_LEGACY.items(): + if sid not in defined: + errors.append(f"legacy preservation: required 1.02 symbol {name} (i={sid}) missing") + elif defined[sid][0][2:] != cls: + errors.append(f"legacy preservation: required 1.02 symbol {name} class mismatch") + +# --- Legacy deprecation is machine-readable (ReleaseStatus="Deprecated") -------- +el_by_id = {parse_numeric_nodeid(el.get("NodeId"))[1]: el for tag, el in elems + if parse_numeric_nodeid(el.get("NodeId")) and parse_numeric_nodeid(el.get("NodeId"))[0] == OWN_NS} +for sid in LEGACY_DEPRECATED_TYPES: + el = el_by_id.get(sid) + if el is None: + continue + if el.get("ReleaseStatus") != "Deprecated": + errors.append(f"legacy deprecation: 1.02 management/upload node i={sid} lacks ReleaseStatus=\"Deprecated\"") + +# --- Well-known WoTAssetConnectionManagement (i=31) is under Objects and callable +wacm = el_by_id.get(31) +if LEGACY_ROWS: + if wacm is None: + errors.append("legacy: well-known WoTAssetConnectionManagement (i=31) missing") + else: + if wacm.get("ParentNodeId") != f"i={OBJECTS}": + errors.append("legacy: WoTAssetConnectionManagement is not organized under the Objects folder (i=85)") + wrefs = wacm.find(NS + "References") + wrefs = list(wrefs) if wrefs is not None else [] + if not any(r.get("ReferenceType") == "HasTypeDefinition" + and parse_numeric_nodeid(r.text) == (OWN_NS, 1) for r in wrefs): + errors.append("legacy: WoTAssetConnectionManagement lacks HasTypeDefinition WoTAssetConnectionManagementType") + # It must expose the mandatory CreateAsset/DeleteAsset methods (callable). + method_children = {(e.get("BrowseName") or "").split(":")[-1] for t, e in elems + if e.get("ParentNodeId") == f"ns={OWN_NS};i=31" and t == "UAMethod"} + for m in ("CreateAsset", "DeleteAsset"): + if m not in method_children: + errors.append(f"legacy: WoTAssetConnectionManagement does not expose the callable {m} method") + +# --- Combined NodeSet declares one namespace at model version 1.1.0 ------------- +ns_uris = [u.text for u in root.iter(NS + "Uri")] +if NAMESPACE not in ns_uris: + errors.append(f"combined NodeSet does not declare the NamespaceUri {NAMESPACE}") +if f"{NAMESPACE}V2/" in ns_uris or any(u and u.endswith("/V2/") for u in ns_uris): + errors.append("combined NodeSet still declares a separate V2 namespace") +model = next((m for m in root.iter(NS + "Model") if m.get("ModelUri") == NAMESPACE), None) +if model is None: + errors.append(f"combined NodeSet has no ") +elif model.get("Version") != MODEL_VERSION: + errors.append(f"combined NodeSet model version {model.get('Version')} != {MODEL_VERSION}") + +# Well-known WoTRegistry instance topology. +registry = next((el for tag, el in elems if el.get("NodeId") == f"ns={OWN_NS};i=64100"), None) +if registry is None: + errors.append("WoTRegistry well-known instance ns=2;i=64100 missing") +else: + if registry.get("ParentNodeId") != f"i={SERVER}": + errors.append("WoTRegistry well-known instance is not parented by the Server object i=2253") + if registry.get("EventNotifier") not in ("1", "5", "7"): + errors.append("WoTRegistry well-known instance does not declare EventNotifier (SubscribeToEvents)") + rrefs = registry.find(NS + "References") + rrefs = list(rrefs) if rrefs is not None else [] + has_notifier = any(r.get("ReferenceType") == "HasNotifier" and r.get("IsForward") == "false" + and parse_numeric_nodeid(r.text) == (0, SERVER) for r in rrefs) + has_typedef = any(r.get("ReferenceType") == "HasTypeDefinition" + and parse_numeric_nodeid(r.text) == (OWN_NS, 64000) for r in rrefs) + if not has_notifier: + errors.append("WoTRegistry is not a HasNotifier target of the Server object (notifier topology)") + if not has_typedef: + errors.append("WoTRegistry does not have HasTypeDefinition WoTRegistryType") + +# Registry/document types generate the required events (notifier chain sources). +def refs_of(numeric_id): + el = next((e for t, e in elems if e.get("NodeId") == f"ns={OWN_NS};i={numeric_id}"), None) + out = [] + if el is not None: + rr = el.find(NS + "References") + for r in (list(rr) if rr is not None else []): + out.append((r.get("ReferenceType"), r.text, r.get("IsForward", "true") != "false")) + return out + +reg_events = {parse_numeric_nodeid(t)[1] for rt, t, f in refs_of(64000) + if rt == "GeneratesEvent" and parse_numeric_nodeid(t) and parse_numeric_nodeid(t)[0] == OWN_NS} +doc_events = {parse_numeric_nodeid(t)[1] for rt, t, f in refs_of(64003) + if rt == "GeneratesEvent" and parse_numeric_nodeid(t) and parse_numeric_nodeid(t)[0] == OWN_NS} +if 64014 not in reg_events: + errors.append("WoTRegistryType does not GeneratesEvent WoTRefreshCompletedEventType") +for ev in (64011, 64012, 64013): + if ev not in doc_events: + errors.append(f"WoTDocumentType does not GeneratesEvent ns=2;i={ev}") + +# Mandatory-instance completeness: a well-known instance must materialize a concrete Value +# for every Mandatory member of its type, own AND inherited, so that loading the NodeSet +# alone (with no server-side logic) yields a structurally complete instance. +def _local_bn(bn): + return bn.split(":", 1)[1] if bn and ":" in bn else bn + + +def _mandatory_members(el_list, parent_nodeid): + names = set() + for el in el_list: + if el.get("ParentNodeId") != parent_nodeid: + continue + refs = el.find(NS + "References") + if refs is None: + continue + if any(r.get("ReferenceType") == "HasModellingRule" and parse_numeric_nodeid(r.text) == (0, 78) + for r in refs): + names.add(_local_bn(el.get("BrowseName"))) + return names + + +own_mandatory = _mandatory_members([el for _, el in elems], f"ns={OWN_NS};i=64000") + +xr_mandatory = set() +_xr_xml = os.path.join(ROOT, "core-specs", "xregistry", "Opc.Ua.XRegistry.NodeSet2.xml") +if os.path.exists(_xr_xml): + xr_root = ET.parse(_xr_xml).getroot() + xr_elems = [e for e in xr_root if e.tag.replace(NS, "").startswith("UA")] + xr_mandatory = _mandatory_members(xr_elems, f"ns={XR_NS};i=63000") +else: + warnings.append("xRegistry NodeSet2.xml not found; skipping inherited-mandatory completeness check") + +expected_mandatory = own_mandatory | xr_mandatory +if registry is not None: + reg_children_by_name = {} + for _, el in elems: + if el.get("ParentNodeId") == f"ns={OWN_NS};i=64100": + reg_children_by_name[_local_bn(el.get("BrowseName"))] = el + for mname in sorted(expected_mandatory): + child = reg_children_by_name.get(mname) + if child is None: + errors.append(f"WoTRegistry well-known instance does not materialize Mandatory member '{mname}'") + continue + vel = child.find(NS + "Value") + if vel is None or (len(list(vel)) == 0 and not (vel.text or "").strip()): + errors.append(f"WoTRegistry well-known instance member '{mname}' has no materialized Value") + +# Generated-annex equality: model-reference.md is embedded verbatim in the spec. +annex_ok = None +if os.path.exists(MODELREF) and os.path.exists(SPEC): + ann = open(MODELREF, encoding="utf-8").read().replace("\r\n", "\n").strip() + spec = open(SPEC, encoding="utf-8").read().replace("\r\n", "\n") + if '' not in spec: + errors.append("spec is missing the Annex A marker") + annex_ok = False + elif ann not in spec: + errors.append("generated Annex A (model-reference.md) is not embedded verbatim in the spec") + annex_ok = False + else: + annex_ok = True +elif not os.path.exists(SPEC): + warnings.append("spec OPC-UA-WoT-Connectivity.md not found; skipping annex-embed check") + +print(f"XML nodes: {len(defined)} CSV rows: {len(rows)} " + f"base ids: {len(UA) if UA is not None else 'skipped (no local base table)'} " + f"xRegistry base ids: {len(XR) if XR is not None else 'skipped'} " + f"annex embedded: {annex_ok}") +print(f"ERRORS: {len(errors)}") +for e in errors[:60]: + print(" ERR", e) +print(f"WARNINGS: {len(warnings)}") +for w in warnings[:40]: + print(" WARN", w) +sys.exit(1 if errors else 0) diff --git a/wot-specs/extras/WoT-Binding/README.md b/wot-specs/extras/WoT-Binding/README.md new file mode 100644 index 0000000..2e6c6ca --- /dev/null +++ b/wot-specs/extras/WoT-Binding/README.md @@ -0,0 +1,7 @@ +# WoT Binding — extras (informative) + +Material that supports the [OPC UA — WoT Binding](../../WoT-Binding/OPC-UA-WoT-Binding.md) draft but is **not** part of the normative specification. Nothing here is normative; the specification is self-contained. + +## Contents + +- [`adr-to-uav-crosswalk.md`](adr-to-uav-crosswalk.md) — a design-input crosswalk mapping the neutral concepts of the two architecture decision records (ADR 0029, ADR 0032) that informed the model and platform vocabulary onto the `uav` terms. It was moved out of the specification body because the normative meaning of every term now lives in Section 6 (concept, usage, examples) and Section 7 (validation rules); the crosswalk is retained here only as informative design provenance. diff --git a/wot-specs/extras/WoT-Binding/adr-to-uav-crosswalk.md b/wot-specs/extras/WoT-Binding/adr-to-uav-crosswalk.md new file mode 100644 index 0000000..848ea2c --- /dev/null +++ b/wot-specs/extras/WoT-Binding/adr-to-uav-crosswalk.md @@ -0,0 +1,37 @@ +# ADR-to-`uav` semantic crosswalk (design-input, informative) + +> Informative background — **not** part of the normative [OPC UA — WoT Binding](../../WoT-Binding/OPC-UA-WoT-Binding.md) specification. The normative meaning of every `uav` model and platform term is defined by Section 6 (with per-term concept, usage, and examples) and its validation rules in Section 7 of that specification. This document is kept in `extras/` purely to record the design inputs and is linked only from README/extras documentation. + +The model and platform vocabulary of the WoT Binding was informed by two architecture decision records (**ADR 0029** and **ADR 0032**) used only as design inputs. This crosswalk records how the neutral concepts of those design inputs are realized by the `uav` terms. It uses no vendor prefix, namespace, or modelling-language name; only the neutral concept name and the `uav` term appear. **No** vocabulary, prefix, or namespace of those design inputs is reused in the specification. + +| Source ADR concept | `uav` term | Semantics realized | +| --- | --- | --- | +| Composite-model flag | `uav:isComposite` | The type is decomposed into named parts. | +| Event-affordance flag | `uav:isEvent` | The affordance projects an event definition. | +| Capability facet link | `rel: uav:capability` | An exposed capability or interface-like mix-in. | +| Component sub-model link | `rel: uav:componentModel` | An owned, contained sub-component model. | +| Plain relationship link | `rel: uav:reference` | A non-hierarchical, untyped relationship. | +| Typed relationship link | ReferenceType compact model name in `rel` + optional/required `uav:refId` | A relationship qualified semantically by NamespaceUri + BrowseName and definitively by NodeId when needed. | +| Parent (container) link | `rel: uav:componentOf` | The parent under which the instance is exposed (inverse `HasComponent`). | +| Relationship name | `uav:refName` | The name a relationship is exposed under. | +| Relationship identifier | `uav:refId` | The definitive ReferenceType ExpandedNodeId used when model-name lookup is unavailable or ambiguous. | +| Containment (child set) | `uav:contains` | The parts a composite directly contains. | +| Containment (parent) | `uav:containedIn` | The single composite that contains a part. | +| Co-typed / congruent definition | `uav:congruentType` | A structurally congruent shared definition. | +| Naming namespace | `uav:nameNamespace` | The absolute IRI naming namespace of local names. | +| Value scale factor | `uav:scaleFactor` | Linear factor, engineering = raw × factor. | +| Retained decimal places | `uav:decimalPlaces` | Fractional places kept after scaling. | +| Property group set | `uav:propertyGroups` | Named groups of properties. | +| Event group set | `uav:eventGroups` | Named groups of events. | +| Action group set | `uav:actionGroups` | Named groups of actions. | +| Group membership | `uav:memberOf` | The group a member belongs to. | +| Unit-carrying property locator | `uav:unitProperty` | JSON Pointer to the unit string property; quantity kind via QUDT. | +| Opaque annotation bag | `uav:metadata` | Verbatim implementation metadata. | +| Semantic identity | `uav:semanticId` | Absolute IRI semantic identifier. | +| Per-property configuration | `uav:propertyConfiguration` | Opaque property configuration. | +| Per-action configuration | `uav:actionConfiguration` | Opaque action configuration. | +| Per-event configuration | `uav:eventConfiguration` | Opaque event configuration. | +| Inherited-member inclusion | `uav:includeInherited` | Whether inherited members are in scope. | +| Open-content flag | `uav:additionalProperties` | Whether instances may carry undeclared members. | +| External schema pointer | `uav:externalSchema` | URI/path to a custom DataType or payload schema. | +| Instantiation rule | `uav:modellingRule` | Exactly one of `Mandatory`, `Optional`, `MandatoryPlaceholder`, `OptionalPlaceholder`. |