node-opcua-uanodeset-rdf 2.182.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +294 -0
- package/VOCABULARY.md +186 -0
- package/dist/address_space.d.ts +16 -0
- package/dist/address_space.js +30 -0
- package/dist/address_space.js.map +1 -0
- package/dist/from_address_space.d.ts +15 -0
- package/dist/from_address_space.js +110 -0
- package/dist/from_address_space.js.map +1 -0
- package/dist/from_session.d.ts +28 -0
- package/dist/from_session.js +293 -0
- package/dist/from_session.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +79 -0
- package/dist/model.js +15 -0
- package/dist/model.js.map +1 -0
- package/dist/session.d.ts +27 -0
- package/dist/session.js +41 -0
- package/dist/session.js.map +1 -0
- package/dist/to_jsonld.d.ts +31 -0
- package/dist/to_jsonld.js +372 -0
- package/dist/to_jsonld.js.map +1 -0
- package/package.json +70 -0
- package/source/address_space.ts +25 -0
- package/source/from_address_space.ts +116 -0
- package/source/from_session.ts +327 -0
- package/source/index.ts +31 -0
- package/source/model.ts +88 -0
- package/source/session.ts +35 -0
- package/source/to_jsonld.ts +385 -0
package/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# node-opcua-uanodeset-rdf
|
|
2
|
+
|
|
3
|
+
Export an OPC UA information model as **JSON-LD**, so it can be queried with SPARQL, reasoned
|
|
4
|
+
over with an OWL toolchain, or joined to data that was never OPC UA in the first place.
|
|
5
|
+
|
|
6
|
+
see http://node-opcua.github.io/
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install node-opcua-uanodeset-rdf
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
An address space is already a graph: nodes, typed edges, subtype chains. What it lacks is a way
|
|
15
|
+
to ask graph questions of it. `HasComponent` is a traversal in an SDK, but a *predicate* in RDF,
|
|
16
|
+
and once the model is in a triple store you can ask things the OPC UA services have no method
|
|
17
|
+
for:
|
|
18
|
+
|
|
19
|
+
> every DataType in this model with a field whose own DataType is not defined by any model I
|
|
20
|
+
> imported
|
|
21
|
+
|
|
22
|
+
> the transitive closure of `HasSubtype` under `DeviceType`, across DI and a vendor companion
|
|
23
|
+
> specification loaded side by side
|
|
24
|
+
|
|
25
|
+
> which nodes changed between two published versions of a companion specification
|
|
26
|
+
|
|
27
|
+
That is the job. Get the model once, export it, and let a graph database answer.
|
|
28
|
+
|
|
29
|
+
## What comes out
|
|
30
|
+
|
|
31
|
+
```jsonc
|
|
32
|
+
{
|
|
33
|
+
"@context": { /* prefixes, property terms, and per-document predicates */ },
|
|
34
|
+
"@graph": [ /* the ontology node, then one entry per node */ ],
|
|
35
|
+
"@included": [ /* OWL property declarations, and foreign attachments */ ]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The first `@graph` entry describes the model itself, as both a `uardf:UANodeSet` and an
|
|
40
|
+
`owl:Ontology`. Every entry after it is a node:
|
|
41
|
+
|
|
42
|
+
```jsonc
|
|
43
|
+
{
|
|
44
|
+
"@id": "di:DeviceType",
|
|
45
|
+
"@type": ["uardf:UAObjectType", "owl:Class"],
|
|
46
|
+
"nodeId": "nsu=http://opcfoundation.org/UA/DI/;i=1002",
|
|
47
|
+
"browseName": "nsu=http://opcfoundation.org/UA/DI/;DeviceType",
|
|
48
|
+
"name": "DeviceType",
|
|
49
|
+
"subClassOf": "di:ComponentType",
|
|
50
|
+
"di:ParameterSet": "di:bnN1PWh0dHA6..."
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Three ideas carry most of the mapping:
|
|
55
|
+
|
|
56
|
+
- **Types get readable IRIs, instances get stable ones.** An ObjectType, VariableType,
|
|
57
|
+
ReferenceType or DataType is `<prefix>:<BrowseName>`; everything else is
|
|
58
|
+
`<prefix>:<base64url(canonical NodeId)>`, unpadded. So `di:DeviceType` is legible in a query
|
|
59
|
+
and an instance IRI survives a renaming.
|
|
60
|
+
- **Three references become fields rather than edges.** `HasTypeDefinition` becomes part of
|
|
61
|
+
`@type` (a Property is `["uardf:UAVariable", "opcua:PropertyType"]`), `HasSubtype` becomes
|
|
62
|
+
`subClassOf`, and `HasModellingRule` becomes `modellingRule`. That is what makes the OWL
|
|
63
|
+
reasoning work: a subtype chain is a class hierarchy.
|
|
64
|
+
- **`@included` carries the schema.** One `owl:ObjectProperty` declaration per reference type the
|
|
65
|
+
model defines, with its inverse and its `subPropertyOf`, so a reasoner knows what the
|
|
66
|
+
predicates mean.
|
|
67
|
+
|
|
68
|
+
The legacy OPC Binary machinery is dropped: every node whose TypeDefinition is
|
|
69
|
+
`DataTypeDictionaryType` or `DataTypeDescriptionType`, and everything they own. The structure
|
|
70
|
+
DataTypes are kept, with their `DataTypeDefinition`. On the DI nodeset that is 14 nodes of 447.
|
|
71
|
+
|
|
72
|
+
[`VOCABULARY.md`](./VOCABULARY.md) is the full account: prefixes, IRI minting, the ontology node,
|
|
73
|
+
type composition, the reference rules, the three kinds of `@included` entry.
|
|
74
|
+
|
|
75
|
+
## Which entry point
|
|
76
|
+
|
|
77
|
+
That document can be produced two ways, and each has an entry point of its own so that neither
|
|
78
|
+
drags in what only the other needs.
|
|
79
|
+
|
|
80
|
+
| | [`/address-space`](#node-opcua-uanodeset-rdfaddress-space) | [`/session`](#node-opcua-uanodeset-rdfsession) |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| you have | the NodeSet2 document | an `OPCUAClient` or a `PseudoSession` |
|
|
83
|
+
| covers | every node of the namespace | every node reachable by browsing |
|
|
84
|
+
| `symbolicName` | yes | no |
|
|
85
|
+
| `owl:imports` | yes | no |
|
|
86
|
+
|
|
87
|
+
Prefer `/address-space` when you have the document. Reach for `/session` when you do not, which
|
|
88
|
+
for a vendor's own companion specification is often the only option there is.
|
|
89
|
+
|
|
90
|
+
The root module re-exports both, for callers that want both.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
# `node-opcua-uanodeset-rdf/address-space`
|
|
95
|
+
|
|
96
|
+
The complete source. An address space retains the NodeSet2 metadata that OPC UA has no attribute
|
|
97
|
+
for, so an export taken this way is the reference the other is judged against.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { writeFile } from "node:fs/promises";
|
|
101
|
+
import { AddressSpace } from "node-opcua-address-space";
|
|
102
|
+
import { generateAddressSpace } from "node-opcua-address-space/nodeJS";
|
|
103
|
+
import { nodesets } from "node-opcua-nodesets";
|
|
104
|
+
import { addressSpaceToJsonLdText } from "node-opcua-uanodeset-rdf/address-space";
|
|
105
|
+
|
|
106
|
+
const addressSpace = AddressSpace.create();
|
|
107
|
+
await generateAddressSpace(addressSpace, [nodesets.standard, nodesets.di]);
|
|
108
|
+
|
|
109
|
+
// with no modelUri, the last namespace loaded is the one exported
|
|
110
|
+
await writeFile("Opc.Ua.Di.jsonld", addressSpaceToJsonLdText(addressSpace), "utf8");
|
|
111
|
+
|
|
112
|
+
addressSpace.dispose();
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A whole address space is needed rather than the one namespace, and that is not an implementation
|
|
116
|
+
detail: a type's IRI is its BrowseName, including for types belonging to the models this one
|
|
117
|
+
merely imports, so the exporter has to resolve NodeIds it never read.
|
|
118
|
+
|
|
119
|
+
Two fields of the ontology node are written in the source document's `Models` element and are not
|
|
120
|
+
retained by the loader. Supply them if you want them in the output:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { addressSpaceToJsonLd } from "node-opcua-uanodeset-rdf/address-space";
|
|
124
|
+
|
|
125
|
+
const doc = addressSpaceToJsonLd(addressSpace, {
|
|
126
|
+
modelUri: "http://opcfoundation.org/UA/DI/",
|
|
127
|
+
model: { modelVersion: "1.5.0", xmlSchemaUri: "http://opcfoundation.org/UA/DI/Types.xsd" }
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| export | what it is |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `addressSpaceToJsonLdText(addressSpace, options?)` | one namespace as a JSON-LD document, serialised |
|
|
134
|
+
| `addressSpaceToJsonLd(addressSpace, options?)` | the same as a plain object, to hand to a JSON-LD library |
|
|
135
|
+
| `addressSpaceToRdfModel(addressSpace, modelUri?)` | the collected model, to inspect or amend before mapping it |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
# `node-opcua-uanodeset-rdf/session`
|
|
140
|
+
|
|
141
|
+
Nothing reachable from this entry point mentions `node-opcua-address-space`, at the type level or
|
|
142
|
+
the runtime one, so a client application uses it without loading the address-space machinery it
|
|
143
|
+
has no other use for.
|
|
144
|
+
|
|
145
|
+
## With an `OPCUAClient`
|
|
146
|
+
|
|
147
|
+
This is the case a nodeset file cannot serve: a device implementing a companion specification you
|
|
148
|
+
do not have, exported straight into a triple store.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { writeFile } from "node:fs/promises";
|
|
152
|
+
import { OPCUAClient } from "node-opcua-client";
|
|
153
|
+
import { sessionToJsonLdText } from "node-opcua-uanodeset-rdf/session";
|
|
154
|
+
|
|
155
|
+
await OPCUAClient.create({ endpointMustExist: false }).withSessionAsync(
|
|
156
|
+
"opc.tcp://localhost:26543",
|
|
157
|
+
async (session) => {
|
|
158
|
+
const jsonld = await sessionToJsonLdText(session, {
|
|
159
|
+
modelUri: "http://acme.com/UA/Widget/"
|
|
160
|
+
});
|
|
161
|
+
await writeFile("Widget.jsonld", jsonld, "utf8");
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## With a `PseudoSession`
|
|
167
|
+
|
|
168
|
+
`PseudoSession` puts the session interface in front of an address space you already hold. No
|
|
169
|
+
server, no socket, no endpoint.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { AddressSpace, PseudoSession } from "node-opcua-address-space";
|
|
173
|
+
import { generateAddressSpace } from "node-opcua-address-space/nodeJS";
|
|
174
|
+
import { nodesets } from "node-opcua-nodesets";
|
|
175
|
+
import { sessionToJsonLdText } from "node-opcua-uanodeset-rdf/session";
|
|
176
|
+
|
|
177
|
+
const addressSpace = AddressSpace.create();
|
|
178
|
+
await generateAddressSpace(addressSpace, [nodesets.standard, nodesets.di]);
|
|
179
|
+
|
|
180
|
+
const jsonld = await sessionToJsonLdText(new PseudoSession(addressSpace), {
|
|
181
|
+
modelUri: "http://opcfoundation.org/UA/DI/"
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
addressSpace.dispose();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
It is also how this entry point is tested. Both paths then see the same model, so every
|
|
188
|
+
difference between the two documents is something OPC UA cannot say about a node rather than
|
|
189
|
+
something the collector got wrong.
|
|
190
|
+
|
|
191
|
+
## What a session has to provide
|
|
192
|
+
|
|
193
|
+
`RdfSession` is not a session type. It is the three services collecting a model actually calls:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
type RdfSession = IBasicSessionBrowseAsyncMultiple &
|
|
197
|
+
IBasicSessionBrowseNextAsyncMultiple &
|
|
198
|
+
IBasicSessionReadAsyncMultiple;
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Browse, follow the continuation point a browse hands back, read attributes. `create`, `close`,
|
|
202
|
+
subscriptions and the rest of a session's surface are deliberately outside it, so a caller may
|
|
203
|
+
pass something much smaller than a session.
|
|
204
|
+
|
|
205
|
+
Browse is chunked by the server's own `MaxNodesPerBrowse` and `MaxBrowseContinuationPoints`,
|
|
206
|
+
which `browseAll` reads for itself. Attribute reads use a fixed modest batch: honouring a
|
|
207
|
+
server's `MaxNodesPerRead` belongs in a client that specialises in it, not in an exporter.
|
|
208
|
+
|
|
209
|
+
## What a session cannot tell you
|
|
210
|
+
|
|
211
|
+
Three differences from the address-space export, all structural rather than unfinished work, each
|
|
212
|
+
measured by exporting one DI address space both ways.
|
|
213
|
+
|
|
214
|
+
- **No service lists the nodes of a namespace.** Browse walks references; nothing enumerates. So
|
|
215
|
+
the model is whatever a crawl from Root reaches, and a node nothing points at is invisible. On
|
|
216
|
+
DI that is exactly nine: the well-known function-group Objects `Configuration`, `Tuning`,
|
|
217
|
+
`Maintenance`, `Diagnostics`, `Statistics`, `Status`, `Operational`, `OperationCounters` and
|
|
218
|
+
`Identification`, which carry no inverse reference of any type.
|
|
219
|
+
- **`SymbolicName` is not an attribute.** It is NodeSet2 metadata, so `uardf:symbolicName` is
|
|
220
|
+
absent from every entry.
|
|
221
|
+
- **`NamespaceMetadataType` states no required models.** It carries `NamespaceUri`,
|
|
222
|
+
`NamespaceVersion` and `NamespacePublicationDate` and no list of imports, so the ontology node
|
|
223
|
+
has `version` and `publicationDate` but no `requiredModels` and no `owl:imports`.
|
|
224
|
+
|
|
225
|
+
Everything else agrees, node for node and predicate for predicate: the same `@id`, `@type`,
|
|
226
|
+
`dataType`, `value` and children, the same `@context` terms, the same `@included` entries.
|
|
227
|
+
|
|
228
|
+
| export | what it is |
|
|
229
|
+
|---|---|
|
|
230
|
+
| `sessionToJsonLdText(session, options?)` | one namespace as a JSON-LD document, serialised |
|
|
231
|
+
| `sessionToJsonLd(session, options?)` | the same as a plain object |
|
|
232
|
+
| `sessionToRdfModel(session, modelUri?)` | the collected model, to inspect or amend before mapping it |
|
|
233
|
+
| `RdfSession` | the intersection above |
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Shared API
|
|
238
|
+
|
|
239
|
+
Both entry points also export:
|
|
240
|
+
|
|
241
|
+
| export | what it is |
|
|
242
|
+
|---|---|
|
|
243
|
+
| `JsonLdOptions` | `{ modelUri?, model?: { xmlSchemaUri?, modelVersion? } }` |
|
|
244
|
+
| `modelToJsonLd(model, options?)` | the mapping itself, for a model collected some third way |
|
|
245
|
+
| `RdfModel`, `RdfNode`, `RdfReference`, `RdfTarget` | the collected model's types |
|
|
246
|
+
| `UARDF` | `http://opcfoundation.org/rdf/uacore#`, the vocabulary namespace |
|
|
247
|
+
| `OPCUA_NAMESPACE` | `http://opcfoundation.org/UA/` |
|
|
248
|
+
| `prefixOfNamespace(uri)` | the prefix a namespace URI gets: its last non-empty path segment, lowercased |
|
|
249
|
+
|
|
250
|
+
Collecting a model and mapping it are separate on purpose. `modelToJsonLd` is synchronous and
|
|
251
|
+
reads nothing but an `RdfModel`, which is why the session collector could be added without
|
|
252
|
+
touching a line of the vocabulary, and why a third source would need no more than a function
|
|
253
|
+
returning an `RdfModel`.
|
|
254
|
+
|
|
255
|
+
## Status
|
|
256
|
+
|
|
257
|
+
**This is an export, not a serialisation format.** There is no reader, and it never registers
|
|
258
|
+
with the address-space loader. To read or write a JSON nodeset you want
|
|
259
|
+
[`node-opcua-uanodeset-json`](../node-opcua-uanodeset-json), which implements the normative
|
|
260
|
+
Annex I formats. The two answer different questions: Annex I is how a model is exchanged, this is
|
|
261
|
+
how a model is queried.
|
|
262
|
+
|
|
263
|
+
**The vocabulary is not in the specification.** OPC 10000-6 Annex I runs I.1 to I.24 and has no
|
|
264
|
+
RDF section. This vocabulary belongs to the OPC Foundation's `UA-NodeSetTool`, whose own
|
|
265
|
+
`rdf_prototype.md` no longer describes what it emits, so its output *is* the specification and
|
|
266
|
+
`VOCABULARY.md` is a reverse-engineered account of it.
|
|
267
|
+
|
|
268
|
+
**Parity** with that tool, measured on `Opc.Ua.Di.NodeSet2`:
|
|
269
|
+
|
|
270
|
+
| | reference tool | this package |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `@graph` entries | 434 | **434** |
|
|
273
|
+
| `@included` entries | 172 | 173 |
|
|
274
|
+
| `@context` terms | 239 | 220 |
|
|
275
|
+
| entries byte-identical | n/a | 66 of 434 |
|
|
276
|
+
|
|
277
|
+
Graph membership matches exactly, so a query over either document sees the same nodes. It is not
|
|
278
|
+
yet byte-identical, and with no reader there is no round trip, no fixpoint and no digest
|
|
279
|
+
equivalence to lean on: a diff against the reference implementation is the only oracle. The gap
|
|
280
|
+
is one undocumented rule, whether a child is written under a BrowseName-derived predicate or
|
|
281
|
+
under the generic reference type. Three hypotheses were tested and all three failed, so the
|
|
282
|
+
question is recorded in `VOCABULARY.md` rather than answered with a rule that merely fits one
|
|
283
|
+
nodeset.
|
|
284
|
+
|
|
285
|
+
## On the name
|
|
286
|
+
|
|
287
|
+
`node-opcua-nodeset-*` is a reserved prefix. The nodeset code generator sweeps every folder
|
|
288
|
+
matching it and rewrites `packages/tsconfig.json` from what it finds, so a hand-written package
|
|
289
|
+
sitting there breaks `generate:nodesets`. Hence `uanodeset`, matching
|
|
290
|
+
`node-opcua-uanodeset-json`.
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
MIT
|
package/VOCABULARY.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# The OPC UA JSON-LD vocabulary
|
|
2
|
+
|
|
3
|
+
This is a **reverse-engineered** description of the JSON-LD an OPC UA information model is
|
|
4
|
+
exported as by `OPCF-Members/UA-NodeSetTool`. It is written down here because no accurate
|
|
5
|
+
description existed: the tool's own `rdf_prototype.md` describes an earlier shape, and the
|
|
6
|
+
differences are not small.
|
|
7
|
+
|
|
8
|
+
Nothing here is normative. **OPC 10000-6 Annex I has no RDF section** — the annex runs I.1 to
|
|
9
|
+
I.24 and stops at `DataTypeField`. This vocabulary is the reference implementation's own.
|
|
10
|
+
|
|
11
|
+
Everything below was established by generating output for real nodesets and reading it. Where a
|
|
12
|
+
rule could not be established, it says so rather than guessing.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Document shape
|
|
17
|
+
|
|
18
|
+
```jsonc
|
|
19
|
+
{
|
|
20
|
+
"@context": { /* prefixes, property terms, and per-document predicates */ },
|
|
21
|
+
"@graph": [ /* the ontology node, then one entry per node */ ],
|
|
22
|
+
"@included": [ /* OWL property declarations, and foreign attachments */ ]
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
There is no root `@id` and no root `@type`. (`rdf_prototype.md` describes both, plus `modelUri`,
|
|
27
|
+
`version` and `requiredModels` at the root, and nodes flagged `isNormative` / `isExternal`. None
|
|
28
|
+
of that is emitted.)
|
|
29
|
+
|
|
30
|
+
## Prefixes
|
|
31
|
+
|
|
32
|
+
| prefix | expands to |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `opcua` | `http://opcfoundation.org/UA/` |
|
|
35
|
+
| `uardf` | `http://opcfoundation.org/rdf/uacore#` |
|
|
36
|
+
| `owl` | `http://www.w3.org/2002/07/owl#` |
|
|
37
|
+
| `rdfs` | `http://www.w3.org/2000/01/rdf-schema#` |
|
|
38
|
+
| `xsd` | `http://www.w3.org/2001/XMLSchema#` |
|
|
39
|
+
| *model* | the exported model's namespace URI |
|
|
40
|
+
|
|
41
|
+
The model's prefix is its namespace URI's **last non-empty path segment, lowercased** —
|
|
42
|
+
`http://opcfoundation.org/UA/DI/` becomes `di`.
|
|
43
|
+
|
|
44
|
+
Note the vocabulary prefix is `uardf`, **not** `opcua` as `rdf_prototype.md` states. Only the
|
|
45
|
+
reference types and BrowseName-derived predicates live under `opcua:`.
|
|
46
|
+
|
|
47
|
+
## Node identity
|
|
48
|
+
|
|
49
|
+
| node kind | IRI |
|
|
50
|
+
|---|---|
|
|
51
|
+
| ObjectType, VariableType, ReferenceType, DataType | `<prefix>:<BrowseName>` |
|
|
52
|
+
| everything else | `<prefix>:<base64url(canonical NodeId)>` |
|
|
53
|
+
|
|
54
|
+
The canonical NodeId is the Annex I form (`i=85`, or `nsu=<uri>;i=85`), base64url encoded
|
|
55
|
+
**without padding**. So `nsu=http://opcfoundation.org/UA/DI/;i=6450` becomes
|
|
56
|
+
`di:bnN1PWh0dHA6Ly9vcGNmb3VuZGF0aW9uLm9yZy9VQS9ESS87aT02NDUw`.
|
|
57
|
+
|
|
58
|
+
## The ontology node
|
|
59
|
+
|
|
60
|
+
The first entry of `@graph` describes the model itself:
|
|
61
|
+
|
|
62
|
+
```jsonc
|
|
63
|
+
{
|
|
64
|
+
"@id": "http://opcfoundation.org/UA/DI/",
|
|
65
|
+
"@type": ["uardf:UANodeSet", "owl:Ontology"],
|
|
66
|
+
"modelUri": "...", "xmlSchemaUri": "...",
|
|
67
|
+
"version": "1.05.0", "modelVersion": "1.5.0",
|
|
68
|
+
"owl:versionInfo": "1.5.0",
|
|
69
|
+
"owl:versionIRI": { "@id": "<modelUri><modelVersion>" },
|
|
70
|
+
"publicationDate": "2025-11-15T00:00:00Z",
|
|
71
|
+
"requiredModels": [ { "modelUri": "...", "version": "...", "publicationDate": "..." } ],
|
|
72
|
+
"owl:imports": [ { "@id": "http://opcfoundation.org/UA/" } ]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`xmlSchemaUri` and `modelVersion` are written in the source document's `Models` element and are
|
|
77
|
+
**not retained by node-opcua's address space**, so this package takes them through
|
|
78
|
+
`JsonLdOptions.model` rather than inventing them.
|
|
79
|
+
|
|
80
|
+
## Node entries
|
|
81
|
+
|
|
82
|
+
Every node carries `@id`, `@type`, `nodeId`, `namespaceUri`, `browseName` and `name`.
|
|
83
|
+
`browseName` is the Annex I QualifiedName form (bare in namespace 0, `nsu=<uri>;<name>`
|
|
84
|
+
otherwise); `name` is the bare BrowseName.
|
|
85
|
+
|
|
86
|
+
`@type` is `uardf:UAObject`, `uardf:UAVariable`, `uardf:UAMethod`, `uardf:UAObjectType`,
|
|
87
|
+
`uardf:UAVariableType`, `uardf:UAReferenceType`, `uardf:UADataType` or `uardf:UAView`, plus:
|
|
88
|
+
|
|
89
|
+
- `owl:Class` for the four type NodeClasses;
|
|
90
|
+
- the **TypeDefinition's IRI** for instances, so a Property is `["uardf:UAVariable",
|
|
91
|
+
"opcua:PropertyType"]`.
|
|
92
|
+
|
|
93
|
+
Other fields: `subClassOf` (from the inverse `HasSubtype`), `parent`, `symbolicName`,
|
|
94
|
+
`description` as a language map (`{"@none": "..."}` when no locale), `dataType`, `valueRank`
|
|
95
|
+
(omitted when scalar), `arrayDimensions`, `definition`, and `value` as a raw Part 6 1.05 JSON
|
|
96
|
+
Variant carried as a `@json` literal.
|
|
97
|
+
|
|
98
|
+
## References
|
|
99
|
+
|
|
100
|
+
`HasTypeDefinition`, `HasModellingRule` and `HasSubtype` are never written as references:
|
|
101
|
+
the first two become `@type` and `modellingRule`, the third becomes `subClassOf`.
|
|
102
|
+
|
|
103
|
+
Everything else is written one of two ways:
|
|
104
|
+
|
|
105
|
+
- **as a predicate named after the child's BrowseName** — `di:ParameterSet`, `opcua:EnumStrings`;
|
|
106
|
+
- **as a predicate named after the reference type** — `opcua:HasComponent`, whose value is an
|
|
107
|
+
array when several children share it.
|
|
108
|
+
|
|
109
|
+
**The rule choosing between the two is not established.** Observed on the DI nodeset:
|
|
110
|
+
`IdleToPreparing` and `ParameterSet` get BrowseName predicates; `Idle`, `Error`, `Installing`
|
|
111
|
+
and `Abort` do not, and appear under `opcua:HasComponent` instead. It is not name uniqueness
|
|
112
|
+
(`PercentComplete` occurs twice and *does* get one; `Error` occurs once and does not), and it is
|
|
113
|
+
not the NodeClass or the reference type (States and Transitions are both Objects reached by
|
|
114
|
+
`HasComponent`, and they differ). **This is the open question to put to the maintainer.**
|
|
115
|
+
|
|
116
|
+
A BrowseName that names a placeholder (`<ParameterIdentifier>`) never becomes a predicate.
|
|
117
|
+
|
|
118
|
+
## `@included`
|
|
119
|
+
|
|
120
|
+
Three kinds of entry:
|
|
121
|
+
|
|
122
|
+
1. **ReferenceType declarations**, one per reference type the model defines:
|
|
123
|
+
`{"@id", "@type": "owl:ObjectProperty", "label", "symmetric", "inverseOf", "inverseName",
|
|
124
|
+
"subPropertyOf"}`. A symmetric type is its own inverse.
|
|
125
|
+
2. **Predicate declarations**, one per BrowseName-derived predicate, minimal:
|
|
126
|
+
`{"@id", "@type": "owl:ObjectProperty", "subPropertyOf": "<the reference type reaching it>"}`.
|
|
127
|
+
3. **Foreign attachments**: a node in *another* model that this model hangs children on, written
|
|
128
|
+
as `{"@id": "<that node>", "<predicate>": "<child>"}` with no `@type`. It cannot go in
|
|
129
|
+
`@graph`, because the document does not define that node.
|
|
130
|
+
|
|
131
|
+
## What is dropped
|
|
132
|
+
|
|
133
|
+
The legacy OPC Binary machinery: every node whose TypeDefinition is `DataTypeDictionaryType`
|
|
134
|
+
(`i=72`) or `DataTypeDescriptionType` (`i=69`), **and everything those nodes own**. The structure
|
|
135
|
+
DataTypes themselves are kept, with their `DataTypeDefinition`; only the encoding dictionaries and
|
|
136
|
+
description blobs go. On the DI nodeset that is 14 nodes of 448.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Parity status of this implementation
|
|
141
|
+
|
|
142
|
+
Measured against the reference implementation for `Opc.Ua.Di.NodeSet2`:
|
|
143
|
+
|
|
144
|
+
| | reference | this package |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `@graph` entries | 434 | **434** |
|
|
147
|
+
| `@included` entries | 172 | 173 |
|
|
148
|
+
| `@context` terms | 239 | 220 |
|
|
149
|
+
| entries byte-identical | — | 66 of 434 |
|
|
150
|
+
|
|
151
|
+
`@graph` membership matches exactly. The remaining differences are concentrated in which
|
|
152
|
+
predicate a child is written under (the open question above) and in the reference-type aliases
|
|
153
|
+
that `@context` needs so the `subPropertyOf` values in `@included` resolve.
|
|
154
|
+
|
|
155
|
+
**This exporter is not yet a drop-in replacement**, and the numbers above are the honest state of
|
|
156
|
+
it rather than an aspiration.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## What a session cannot tell you
|
|
161
|
+
|
|
162
|
+
The same document can be produced from a live session
|
|
163
|
+
(`node-opcua-uanodeset-rdf/session`) rather than a loaded address space. That is the only way to
|
|
164
|
+
export a server whose nodeset file you do not have, and it is not the equal of the address-space
|
|
165
|
+
path. Three differences are structural, not defects waiting to be fixed, and each was measured on
|
|
166
|
+
DI by exporting the same address space both ways.
|
|
167
|
+
|
|
168
|
+
**1. There is no service that lists the nodes of a namespace.** Browse walks references; nothing
|
|
169
|
+
enumerates. So a session-sourced model is whatever a crawl from Root reaches, and a node nothing
|
|
170
|
+
points at is invisible. On DI that is nine nodes: the well-known function-group Objects
|
|
171
|
+
`Configuration`, `Tuning`, `Maintenance`, `Diagnostics`, `Statistics`, `Status`, `Operational`,
|
|
172
|
+
`OperationCounters` and `Identification`. They carry no inverse reference of any type, so no crawl
|
|
173
|
+
from any root reaches them. Every other node of the namespace is reached, and each is written with
|
|
174
|
+
the same `@id`, `@type`, `dataType`, `value` and predicates as the address-space export gives it.
|
|
175
|
+
|
|
176
|
+
**2. `SymbolicName` is not an attribute.** It is NodeSet2 metadata, so `uardf:symbolicName` is
|
|
177
|
+
absent from every entry of a session-sourced document.
|
|
178
|
+
|
|
179
|
+
**3. `NamespaceMetadataType` states no required models.** It carries `NamespaceUri`,
|
|
180
|
+
`NamespaceVersion` and `NamespacePublicationDate`, and no list of imports. So the ontology node of
|
|
181
|
+
a session-sourced document has `version` and `publicationDate` but no `requiredModels` and no
|
|
182
|
+
`owl:imports`, which is what a reasoner uses to pull the imported models in. A caller that knows
|
|
183
|
+
the imports can still supply `xmlSchemaUri` and `modelVersion` through `JsonLdOptions.model`.
|
|
184
|
+
|
|
185
|
+
Everything else agrees: the `@context` terms, the `@included` entries, the drop rule for the
|
|
186
|
+
legacy OPC Binary machinery, and every predicate on every node reached.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The address-space entry point: `node-opcua-uanodeset-rdf/address-space`.
|
|
3
|
+
*
|
|
4
|
+
* This is the complete source and the one to prefer where the model has been loaded from its
|
|
5
|
+
* NodeSet2 document: an address space retains the metadata OPC UA has no attribute for, so an
|
|
6
|
+
* export taken this way is the reference a session-sourced one is judged against.
|
|
7
|
+
*/
|
|
8
|
+
import type { IAddressSpace } from "node-opcua-address-space";
|
|
9
|
+
import { type JsonLdOptions } from "./to_jsonld.js";
|
|
10
|
+
export { addressSpaceToRdfModel } from "./from_address_space.js";
|
|
11
|
+
export type { RdfModel, RdfModelReference, RdfNode, RdfReference, RdfTarget } from "./model.js";
|
|
12
|
+
export { type JsonLdOptions, modelToJsonLd, OPCUA_NAMESPACE, prefixOfNamespace, UARDF } from "./to_jsonld.js";
|
|
13
|
+
/** a namespace of an address space as a JSON-LD document */
|
|
14
|
+
export declare function addressSpaceToJsonLd(addressSpace: IAddressSpace, options?: JsonLdOptions): Record<string, unknown>;
|
|
15
|
+
/** a namespace of an address space as a JSON-LD document, serialised */
|
|
16
|
+
export declare function addressSpaceToJsonLdText(addressSpace: IAddressSpace, options?: JsonLdOptions): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The address-space entry point: `node-opcua-uanodeset-rdf/address-space`.
|
|
4
|
+
*
|
|
5
|
+
* This is the complete source and the one to prefer where the model has been loaded from its
|
|
6
|
+
* NodeSet2 document: an address space retains the metadata OPC UA has no attribute for, so an
|
|
7
|
+
* export taken this way is the reference a session-sourced one is judged against.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.UARDF = exports.prefixOfNamespace = exports.OPCUA_NAMESPACE = exports.modelToJsonLd = exports.addressSpaceToRdfModel = void 0;
|
|
11
|
+
exports.addressSpaceToJsonLd = addressSpaceToJsonLd;
|
|
12
|
+
exports.addressSpaceToJsonLdText = addressSpaceToJsonLdText;
|
|
13
|
+
const from_address_space_js_1 = require("./from_address_space.js");
|
|
14
|
+
const to_jsonld_js_1 = require("./to_jsonld.js");
|
|
15
|
+
var from_address_space_js_2 = require("./from_address_space.js");
|
|
16
|
+
Object.defineProperty(exports, "addressSpaceToRdfModel", { enumerable: true, get: function () { return from_address_space_js_2.addressSpaceToRdfModel; } });
|
|
17
|
+
var to_jsonld_js_2 = require("./to_jsonld.js");
|
|
18
|
+
Object.defineProperty(exports, "modelToJsonLd", { enumerable: true, get: function () { return to_jsonld_js_2.modelToJsonLd; } });
|
|
19
|
+
Object.defineProperty(exports, "OPCUA_NAMESPACE", { enumerable: true, get: function () { return to_jsonld_js_2.OPCUA_NAMESPACE; } });
|
|
20
|
+
Object.defineProperty(exports, "prefixOfNamespace", { enumerable: true, get: function () { return to_jsonld_js_2.prefixOfNamespace; } });
|
|
21
|
+
Object.defineProperty(exports, "UARDF", { enumerable: true, get: function () { return to_jsonld_js_2.UARDF; } });
|
|
22
|
+
/** a namespace of an address space as a JSON-LD document */
|
|
23
|
+
function addressSpaceToJsonLd(addressSpace, options = {}) {
|
|
24
|
+
return (0, to_jsonld_js_1.modelToJsonLd)((0, from_address_space_js_1.addressSpaceToRdfModel)(addressSpace, options.modelUri), options);
|
|
25
|
+
}
|
|
26
|
+
/** a namespace of an address space as a JSON-LD document, serialised */
|
|
27
|
+
function addressSpaceToJsonLdText(addressSpace, options = {}) {
|
|
28
|
+
return `${JSON.stringify(addressSpaceToJsonLd(addressSpace, options), null, 2)}\n`;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=address_space.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address_space.js","sourceRoot":"","sources":["../source/address_space.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAWH,oDAEC;AAGD,4DAEC;AAfD,mEAAiE;AACjE,iDAAmE;AAEnE,iEAAiE;AAAxD,+HAAA,sBAAsB,OAAA;AAE/B,+CAA8G;AAAjF,6GAAA,aAAa,OAAA;AAAE,+GAAA,eAAe,OAAA;AAAE,iHAAA,iBAAiB,OAAA;AAAE,qGAAA,KAAK,OAAA;AAErF,4DAA4D;AAC5D,SAAgB,oBAAoB,CAAC,YAA2B,EAAE,UAAyB,EAAE;IACzF,OAAO,IAAA,4BAAa,EAAC,IAAA,8CAAsB,EAAC,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;AAC1F,CAAC;AAED,wEAAwE;AACxE,SAAgB,wBAAwB,CAAC,YAA2B,EAAE,UAAyB,EAAE;IAC7F,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AACvF,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collecting an `RdfModel` from a loaded address space.
|
|
3
|
+
*
|
|
4
|
+
* This is the complete source: an address space retains the NodeSet2 metadata that OPC UA has no
|
|
5
|
+
* attribute for, so an export taken this way is the reference against which a session-sourced one
|
|
6
|
+
* is judged.
|
|
7
|
+
*
|
|
8
|
+
* Foreign nodes are converted lazily. The mapper names supertypes, reference types and type
|
|
9
|
+
* definitions belonging to models this one only imports, and there is no point walking all of
|
|
10
|
+
* `Opc.Ua` to find the handful it actually asks for.
|
|
11
|
+
*/
|
|
12
|
+
import type { IAddressSpace } from "node-opcua-address-space";
|
|
13
|
+
import type { RdfModel } from "./model.js";
|
|
14
|
+
/** the model a namespace of an address space describes */
|
|
15
|
+
export declare function addressSpaceToRdfModel(addressSpace: IAddressSpace, modelUri?: string): RdfModel;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Collecting an `RdfModel` from a loaded address space.
|
|
4
|
+
*
|
|
5
|
+
* This is the complete source: an address space retains the NodeSet2 metadata that OPC UA has no
|
|
6
|
+
* attribute for, so an export taken this way is the reference against which a session-sourced one
|
|
7
|
+
* is judged.
|
|
8
|
+
*
|
|
9
|
+
* Foreign nodes are converted lazily. The mapper names supertypes, reference types and type
|
|
10
|
+
* definitions belonging to models this one only imports, and there is no point walking all of
|
|
11
|
+
* `Opc.Ua` to find the handful it actually asks for.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.addressSpaceToRdfModel = addressSpaceToRdfModel;
|
|
15
|
+
const node_opcua_data_model_1 = require("node-opcua-data-model");
|
|
16
|
+
/**
|
|
17
|
+
* one node.
|
|
18
|
+
*
|
|
19
|
+
* `full` is false for a node the exporter only has to name. Skipping the value read there is not
|
|
20
|
+
* an optimisation for its own sake: reading a value can be expensive, or fail, on a node this
|
|
21
|
+
* document never describes.
|
|
22
|
+
*/
|
|
23
|
+
function toRdfNode(node, full) {
|
|
24
|
+
const out = {
|
|
25
|
+
nodeId: node.nodeId,
|
|
26
|
+
nodeClass: node.nodeClass,
|
|
27
|
+
browseName: node.browseName,
|
|
28
|
+
references: node.allReferences().map((reference) => ({
|
|
29
|
+
referenceTypeId: reference.referenceType,
|
|
30
|
+
isForward: reference.isForward,
|
|
31
|
+
targetId: reference.nodeId
|
|
32
|
+
}))
|
|
33
|
+
};
|
|
34
|
+
const typeDefinition = node.typeDefinitionObj?.nodeId;
|
|
35
|
+
if (typeDefinition)
|
|
36
|
+
out.typeDefinition = typeDefinition;
|
|
37
|
+
const supertype = node.subtypeOfObj?.nodeId;
|
|
38
|
+
if (supertype)
|
|
39
|
+
out.subtypeOf = supertype;
|
|
40
|
+
if (node.description?.text)
|
|
41
|
+
out.description = node.description;
|
|
42
|
+
if (node.nodeClass === node_opcua_data_model_1.NodeClass.ReferenceType) {
|
|
43
|
+
const referenceType = node;
|
|
44
|
+
out.symmetric = !!referenceType.symmetric;
|
|
45
|
+
if (referenceType.inverseName)
|
|
46
|
+
out.inverseName = referenceType.inverseName;
|
|
47
|
+
}
|
|
48
|
+
if (!full)
|
|
49
|
+
return out;
|
|
50
|
+
const symbolicName = node.symbolicName;
|
|
51
|
+
if (symbolicName)
|
|
52
|
+
out.symbolicName = symbolicName;
|
|
53
|
+
if (node.nodeClass === node_opcua_data_model_1.NodeClass.Variable || node.nodeClass === node_opcua_data_model_1.NodeClass.VariableType) {
|
|
54
|
+
const variable = node;
|
|
55
|
+
if (variable.dataType)
|
|
56
|
+
out.dataType = variable.dataType;
|
|
57
|
+
if (variable.valueRank !== undefined)
|
|
58
|
+
out.valueRank = variable.valueRank;
|
|
59
|
+
if (variable.arrayDimensions)
|
|
60
|
+
out.arrayDimensions = variable.arrayDimensions;
|
|
61
|
+
try {
|
|
62
|
+
const dataValue = variable.readValue();
|
|
63
|
+
if (dataValue?.value)
|
|
64
|
+
out.value = dataValue.value;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// a value that cannot be read contributes none, which is not an error for an export
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
/** the model a namespace of an address space describes */
|
|
73
|
+
function addressSpaceToRdfModel(addressSpace, modelUri) {
|
|
74
|
+
const namespaces = addressSpace.getNamespaceArray();
|
|
75
|
+
const target = modelUri ? namespaces.find((n) => n.namespaceUri === modelUri) : namespaces[namespaces.length - 1];
|
|
76
|
+
if (!target) {
|
|
77
|
+
throw new Error(`the address space holds no namespace ${modelUri}`);
|
|
78
|
+
}
|
|
79
|
+
const cache = new Map();
|
|
80
|
+
const nodes = [];
|
|
81
|
+
for (const node of namespaces[target.index].nodeIterator()) {
|
|
82
|
+
const converted = toRdfNode(node, true);
|
|
83
|
+
cache.set(node.nodeId.toString(), converted);
|
|
84
|
+
nodes.push(converted);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
namespaceUris: namespaces.map((n) => n.namespaceUri),
|
|
88
|
+
target: {
|
|
89
|
+
index: target.index,
|
|
90
|
+
namespaceUri: target.namespaceUri,
|
|
91
|
+
version: target.version,
|
|
92
|
+
publicationDate: target.publicationDate,
|
|
93
|
+
requiredModels: target.getRequiredModels?.()
|
|
94
|
+
},
|
|
95
|
+
nodes,
|
|
96
|
+
resolve(nodeId) {
|
|
97
|
+
const key = nodeId.toString();
|
|
98
|
+
const known = cache.get(key);
|
|
99
|
+
if (known)
|
|
100
|
+
return known;
|
|
101
|
+
const found = addressSpace.findNode(nodeId);
|
|
102
|
+
if (!found)
|
|
103
|
+
return undefined;
|
|
104
|
+
const converted = toRdfNode(found, false);
|
|
105
|
+
cache.set(key, converted);
|
|
106
|
+
return converted;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=from_address_space.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from_address_space.js","sourceRoot":"","sources":["../source/from_address_space.ts"],"names":[],"mappings":"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"}
|