@vitormnm/node-red-simple-opcua 1.4.3 → 1.6.2
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/client/icons/opcua.svg +132 -132
- package/client/lib/opcua-client-browser.js +368 -330
- package/client/lib/opcua-client-method-service.js +88 -88
- package/client/lib/opcua-client-read-service.js +27 -15
- package/client/lib/opcua-client-subscription-id-service.js +24 -24
- package/client/lib/opcua-client-subscription-service.js +175 -170
- package/client/lib/opcua-client-write-service.js +146 -146
- package/client/opcua-client-config.html +80 -80
- package/client/opcua-client-utils.js +217 -22
- package/client/opcua-client.html +140 -140
- package/client/view/opcua-client.js +1144 -1140
- package/examples/flows_simple_opc.json +1 -2851
- package/icons/opcua.svg +132 -132
- package/icons/opcua2.svg +132 -132
- package/package.json +3 -3
- package/server/icons/opcua.svg +132 -132
- package/server/lib/opcua-address-space-alarm.js +341 -341
- package/server/lib/opcua-address-space-builder.js +1797 -1485
- package/server/lib/opcua-config.js +800 -546
- package/server/lib/opcua-constants.js +120 -109
- package/server/lib/opcua-server-events-child.js +139 -139
- package/server/lib/opcua-server-methods.js +2 -0
- package/server/lib/opcua-server-runtime-child.js +874 -819
- package/server/lib/opcua-server-runtime.js +385 -311
- package/server/lib/opcua-server-status-child.js +187 -187
- package/server/lib/server-node-utils.js +16 -16
- package/server/opcua-server-io.html +346 -346
- package/server/opcua-server-io.js +497 -496
- package/server/opcua-server-registry.js +270 -270
- package/server/opcua-server.css +265 -265
- package/server/opcua-server.html +158 -1643
- package/server/opcua-server.js +24 -13
- package/server/view/opcua-server.css +496 -0
- package/server/view/opcua-server.js +1585 -0
|
@@ -102,9 +102,69 @@ function coerceValue(value, typeName) {
|
|
|
102
102
|
case "UInt32":
|
|
103
103
|
return Number.parseInt(value, 10);
|
|
104
104
|
|
|
105
|
-
case "Int64":
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
case "Int64": {
|
|
106
|
+
const minVal = -9223372036854775808n;
|
|
107
|
+
const maxVal = 9223372036854775807n;
|
|
108
|
+
let bigintVal;
|
|
109
|
+
if (Array.isArray(value) && value.length === 2) {
|
|
110
|
+
const h = BigInt(value[0]);
|
|
111
|
+
const l = BigInt(value[1]);
|
|
112
|
+
const signMask = 1n << 31n;
|
|
113
|
+
const shiftHigh = 1n << 32n;
|
|
114
|
+
if ((h & signMask) === signMask) {
|
|
115
|
+
bigintVal = (h & ~signMask) * shiftHigh + l - 0x8000000000000000n;
|
|
116
|
+
} else {
|
|
117
|
+
bigintVal = h * shiftHigh + l;
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
try {
|
|
121
|
+
bigintVal = BigInt(value);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
const parsed = Number(value);
|
|
124
|
+
if (Number.isFinite(parsed)) {
|
|
125
|
+
bigintVal = BigInt(Math.trunc(parsed));
|
|
126
|
+
} else {
|
|
127
|
+
bigintVal = 0n;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (bigintVal < minVal) bigintVal = minVal;
|
|
132
|
+
else if (bigintVal > maxVal) bigintVal = maxVal;
|
|
133
|
+
|
|
134
|
+
const mask = 0xFFFFFFFFFFFFFFFFn;
|
|
135
|
+
const unsignedVal = bigintVal & mask;
|
|
136
|
+
const high = Number(unsignedVal >> 32n);
|
|
137
|
+
const low = Number(unsignedVal & 0xFFFFFFFFn);
|
|
138
|
+
return [high, low];
|
|
139
|
+
}
|
|
140
|
+
case "UInt64": {
|
|
141
|
+
const minVal = 0n;
|
|
142
|
+
const maxVal = 18446744073709551615n;
|
|
143
|
+
let bigintVal;
|
|
144
|
+
if (Array.isArray(value) && value.length === 2) {
|
|
145
|
+
const h = BigInt(value[0]);
|
|
146
|
+
const l = BigInt(value[1]);
|
|
147
|
+
const shiftHigh = 1n << 32n;
|
|
148
|
+
bigintVal = h * shiftHigh + l;
|
|
149
|
+
} else {
|
|
150
|
+
try {
|
|
151
|
+
bigintVal = BigInt(value);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
const parsed = Number(value);
|
|
154
|
+
if (Number.isFinite(parsed)) {
|
|
155
|
+
bigintVal = BigInt(Math.trunc(parsed));
|
|
156
|
+
} else {
|
|
157
|
+
bigintVal = 0n;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (bigintVal < minVal) bigintVal = minVal;
|
|
162
|
+
else if (bigintVal > maxVal) bigintVal = maxVal;
|
|
163
|
+
|
|
164
|
+
const high = Number(bigintVal >> 32n);
|
|
165
|
+
const low = Number(bigintVal & 0xFFFFFFFFn);
|
|
166
|
+
return [high, low];
|
|
167
|
+
}
|
|
108
168
|
|
|
109
169
|
case "Float":
|
|
110
170
|
case "Double":
|
|
@@ -218,12 +278,49 @@ function variantTypeToName(variant) {
|
|
|
218
278
|
return DataType[variant.dataType] || String(variant.dataType);
|
|
219
279
|
}
|
|
220
280
|
|
|
281
|
+
function decode64BitValue(value, isUnsigned) {
|
|
282
|
+
if (Array.isArray(value) && value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number") {
|
|
283
|
+
const h = BigInt(value[0]);
|
|
284
|
+
const l = BigInt(value[1]);
|
|
285
|
+
const shiftHigh = 1n << 32n;
|
|
286
|
+
let bigintVal;
|
|
287
|
+
if (!isUnsigned) {
|
|
288
|
+
const signMask = 1n << 31n;
|
|
289
|
+
if ((h & signMask) === signMask) {
|
|
290
|
+
bigintVal = (h & ~signMask) * shiftHigh + l - 0x8000000000000000n;
|
|
291
|
+
} else {
|
|
292
|
+
bigintVal = h * shiftHigh + l;
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
bigintVal = h * shiftHigh + l;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const num = Number(bigintVal);
|
|
299
|
+
return num;
|
|
300
|
+
}
|
|
301
|
+
return value;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function resolve64BitValue(value, isUnsigned) {
|
|
305
|
+
if (Array.isArray(value)) {
|
|
306
|
+
if (value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number") {
|
|
307
|
+
return decode64BitValue(value, isUnsigned);
|
|
308
|
+
}
|
|
309
|
+
return value.map((val) => resolve64BitValue(val, isUnsigned));
|
|
310
|
+
}
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
313
|
+
|
|
221
314
|
function dataValueToItemResult(item, dataValue) {
|
|
222
315
|
const variant = dataValue && dataValue.value ? dataValue.value : null;
|
|
316
|
+
let val = variant ? variant.value : null;
|
|
317
|
+
if (variant && (variant.dataType === DataType.Int64 || variant.dataType === DataType.UInt64)) {
|
|
318
|
+
val = resolve64BitValue(val, variant.dataType === DataType.UInt64);
|
|
319
|
+
}
|
|
223
320
|
return {
|
|
224
321
|
name: resolveName(item, resolveNodeId(item)),
|
|
225
322
|
nodeID: resolveNodeId(item),
|
|
226
|
-
value:
|
|
323
|
+
value: val,
|
|
227
324
|
type: variantTypeToName(variant),
|
|
228
325
|
status: statusCodeToString(dataValue && dataValue.statusCode),
|
|
229
326
|
sourceTimestamp: timestampToIso(dataValue && dataValue.sourceTimestamp),
|
|
@@ -242,18 +339,20 @@ async function dataValueToItemResultEvent(item, eventFields, session) {
|
|
|
242
339
|
|
|
243
340
|
const eventTypeName = dv.value.value.name;
|
|
244
341
|
|
|
342
|
+
|
|
343
|
+
|
|
245
344
|
return {
|
|
246
|
-
eventId: eventFields[0]
|
|
247
|
-
eventType: eventFields[1]
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
345
|
+
eventId: eventFields[0]?.value ?? null,
|
|
346
|
+
eventType: eventFields[1]?.value ?? null,
|
|
347
|
+
sourceNode: eventFields[2]?.value?.toString() ?? null,
|
|
348
|
+
sourceName: eventFields[3]?.value ?? null,
|
|
349
|
+
message: eventFields[4]?.value?.text ?? null,
|
|
350
|
+
severity: eventFields[5]?.value ?? null,
|
|
351
|
+
active: eventFields[6]?.value?.text ?? null, // Active / Inactive
|
|
352
|
+
AckedState: eventFields[7]?.value?.text ?? null, // Active / Inactive
|
|
353
|
+
ConfirmedState: eventFields[8]?.value?.text ?? null, // Active / Inactive
|
|
354
|
+
time: eventFields[9]?.value ?? null,
|
|
355
|
+
conditionId: eventFields[10]?.value?.toString() ?? null
|
|
257
356
|
};
|
|
258
357
|
}
|
|
259
358
|
|
|
@@ -270,13 +369,19 @@ function callResultToItemResult(item, callResult, argumentDefinition) {
|
|
|
270
369
|
name: resolveName(item, methodId),
|
|
271
370
|
nodeID: methodId,
|
|
272
371
|
status: statusCodeToString(callResult.statusCode),
|
|
273
|
-
outputs: outputArguments.map((variant, index) =>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
372
|
+
outputs: outputArguments.map((variant, index) => {
|
|
373
|
+
let val = variant ? variant.value : null;
|
|
374
|
+
if (variant && (variant.dataType === DataType.Int64 || variant.dataType === DataType.UInt64)) {
|
|
375
|
+
val = resolve64BitValue(val, variant.dataType === DataType.UInt64);
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
name: outputDefinitions[index] && outputDefinitions[index].name
|
|
379
|
+
? String(outputDefinitions[index].name)
|
|
380
|
+
: "output" + (index + 1),
|
|
381
|
+
type: variantTypeToName(variant),
|
|
382
|
+
value: val
|
|
383
|
+
};
|
|
384
|
+
})
|
|
280
385
|
};
|
|
281
386
|
}
|
|
282
387
|
|
|
@@ -363,6 +468,95 @@ async function getMethodArgumentDefinition(session, methodNodeId, cache) {
|
|
|
363
468
|
return definition;
|
|
364
469
|
}
|
|
365
470
|
|
|
471
|
+
async function enrichItemResultWithEnumeration(result, session, cache, nodeId) {
|
|
472
|
+
if (result.type !== "Int32" && result.type !== "Enumeration") {
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (typeof result.value !== "number" || !Number.isInteger(result.value)) {
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
try {
|
|
481
|
+
const cacheKeyType = "dt:" + nodeId;
|
|
482
|
+
let dtNodeId = cache ? cache.get(cacheKeyType) : undefined;
|
|
483
|
+
if (dtNodeId === undefined) {
|
|
484
|
+
const dv = await session.read({
|
|
485
|
+
nodeId: nodeId,
|
|
486
|
+
attributeId: AttributeIds.DataType
|
|
487
|
+
});
|
|
488
|
+
if (dv.statusCode.isGood()) {
|
|
489
|
+
dtNodeId = dv.value.value;
|
|
490
|
+
if (cache) cache.set(cacheKeyType, dtNodeId);
|
|
491
|
+
} else {
|
|
492
|
+
if (cache) cache.set(cacheKeyType, null);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (!dtNodeId) return result;
|
|
497
|
+
|
|
498
|
+
const cacheKeyStrings = "enumStrings:" + dtNodeId.toString();
|
|
499
|
+
let enumStrings = cache ? cache.get(cacheKeyStrings) : undefined;
|
|
500
|
+
|
|
501
|
+
if (enumStrings === undefined) {
|
|
502
|
+
const browseResult = await session.browse({
|
|
503
|
+
nodeId: dtNodeId,
|
|
504
|
+
referenceTypeId: "HasProperty",
|
|
505
|
+
browseDirection: BrowseDirection.Forward,
|
|
506
|
+
includeSubtypes: true,
|
|
507
|
+
resultMask: 63
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const enumStringsRef = browseResult.references ? browseResult.references.find(r => r.browseName.name === "EnumStrings") : null;
|
|
511
|
+
const enumValuesRef = browseResult.references ? browseResult.references.find(r => r.browseName.name === "EnumValues") : null;
|
|
512
|
+
|
|
513
|
+
if (enumStringsRef) {
|
|
514
|
+
const dataValue = await session.read({
|
|
515
|
+
nodeId: enumStringsRef.nodeId,
|
|
516
|
+
attributeId: AttributeIds.Value
|
|
517
|
+
});
|
|
518
|
+
if (dataValue.statusCode.isGood() && dataValue.value.value) {
|
|
519
|
+
enumStrings = dataValue.value.value.map(lt => lt.text);
|
|
520
|
+
if (cache) cache.set(cacheKeyStrings, enumStrings);
|
|
521
|
+
} else {
|
|
522
|
+
if (cache) cache.set(cacheKeyStrings, null);
|
|
523
|
+
}
|
|
524
|
+
} else if (enumValuesRef) {
|
|
525
|
+
const dataValue = await session.read({
|
|
526
|
+
nodeId: enumValuesRef.nodeId,
|
|
527
|
+
attributeId: AttributeIds.Value
|
|
528
|
+
});
|
|
529
|
+
if (dataValue.statusCode.isGood() && dataValue.value.value) {
|
|
530
|
+
const map = {};
|
|
531
|
+
dataValue.value.value.forEach(ev => {
|
|
532
|
+
let val;
|
|
533
|
+
if (Array.isArray(ev.value) && ev.value.length === 2) {
|
|
534
|
+
val = ev.value[1]; // low part of Int64
|
|
535
|
+
} else {
|
|
536
|
+
val = Number(ev.value);
|
|
537
|
+
}
|
|
538
|
+
map[val] = ev.displayName.text;
|
|
539
|
+
});
|
|
540
|
+
enumStrings = map;
|
|
541
|
+
if (cache) cache.set(cacheKeyStrings, enumStrings);
|
|
542
|
+
} else {
|
|
543
|
+
if (cache) cache.set(cacheKeyStrings, null);
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
if (cache) cache.set(cacheKeyStrings, null);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (enumStrings && enumStrings[result.value] !== undefined) {
|
|
551
|
+
result.valueEnumeration = enumStrings[result.value];
|
|
552
|
+
}
|
|
553
|
+
} catch (e) {
|
|
554
|
+
console.error("Error in enrichItemResultWithEnumeration:", e);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return result;
|
|
558
|
+
}
|
|
559
|
+
|
|
366
560
|
module.exports = {
|
|
367
561
|
AttributeIds,
|
|
368
562
|
coerceNodeId,
|
|
@@ -373,6 +567,7 @@ module.exports = {
|
|
|
373
567
|
callResultToItemResult,
|
|
374
568
|
dataValueToItemResult,
|
|
375
569
|
dataValueToItemResultEvent,
|
|
570
|
+
enrichItemResultWithEnumeration,
|
|
376
571
|
ensureArrayPayload,
|
|
377
572
|
getMethodArgumentDefinition,
|
|
378
573
|
inferTypeName,
|
package/client/opcua-client.html
CHANGED
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
<link rel="stylesheet" type="text/css" href="opcua-client-resource/style.css">
|
|
2
|
-
|
|
3
|
-
<script type="text/javascript" src="opcua-client-resource/script.js"></script>
|
|
4
|
-
|
|
5
|
-
<script type="text/html" data-template-name="opcua-client">
|
|
6
|
-
<div class="form-row">
|
|
7
|
-
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
8
|
-
<input type="text" id="node-input-name" placeholder="opcua-client">
|
|
9
|
-
</div>
|
|
10
|
-
<div class="form-row">
|
|
11
|
-
<label for="node-input-connection"><i class="fa fa-server"></i> Connection</label>
|
|
12
|
-
<input type="text" id="node-input-connection">
|
|
13
|
-
</div>
|
|
14
|
-
<div class="form-row">
|
|
15
|
-
<label for="node-input-mode"><i class="fa fa-exchange"></i> Mode</label>
|
|
16
|
-
<select id="node-input-mode">
|
|
17
|
-
<option value="read">Read</option>
|
|
18
|
-
<option value="write">Write</option>
|
|
19
|
-
<option value="browse">Browse</option>
|
|
20
|
-
<option value="method">Method</option>
|
|
21
|
-
<option value="getSubscriptionId">getSubscriptionId</option>
|
|
22
|
-
<option value="subscription">Subscription</option>
|
|
23
|
-
<option value="events">Events</option>
|
|
24
|
-
</select>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<div class="form-row opcua-client-selection-row">
|
|
28
|
-
<label for="node-input-selectedItems"><i class="fa fa-code"></i> JSON</label>
|
|
29
|
-
<input type="text" id="node-input-selectedItems" class="opcua-client-json" style="width: 70%;">
|
|
30
|
-
</div>
|
|
31
|
-
<div id="node-input-browse-modal" class="opcua-tree-modal" style="display:none;">
|
|
32
|
-
<div class="opcua-tree-modal__dialog">
|
|
33
|
-
<div class="opcua-tree-modal__header">
|
|
34
|
-
<div class="opcua-tree-modal__title"><i class="fa fa-sitemap"></i> OPC UA Browse Tree</div>
|
|
35
|
-
<a href="#" id="node-input-close-browse-modal" class="editor-button editor-button-small"><i class="fa fa-times"></i> Close</a>
|
|
36
|
-
</div>
|
|
37
|
-
<div class="opcua-tree-modal__toolbar">
|
|
38
|
-
<a href="#" id="node-input-browse-root" class="editor-button editor-button-small"><i class="fa fa-search"></i> Browse Root</a>
|
|
39
|
-
<div class="opcua-tree-search">
|
|
40
|
-
<i class="fa fa-search"></i>
|
|
41
|
-
<input type="text" id="node-input-browse-search" placeholder="Search for items already explored">
|
|
42
|
-
<a href="#" id="node-input-browse-search-clear" class="editor-button editor-button-small" style="display:none;"><i class="fa fa-times"></i></a>
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
<div class="opcua-tree-modal__body">
|
|
46
|
-
<div id="node-input-browse-tree" class="opcua-tree-editor"></div>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
<div id="node-input-browse-context-menu" class="opcua-tree-context-menu" style="display:none;">
|
|
50
|
-
<a href="#" id="node-input-browse-context-refresh"><i class="fa fa-refresh"></i> Refresh node</a>
|
|
51
|
-
<a href="#" id="node-input-browse-context-copy-nodeid"><i class="fa fa-copy"></i> Copy NodeID</a>
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
|
|
55
|
-
<div class="form-row opcua-client-selection-row">
|
|
56
|
-
<label style="width:auto;"><i class="fa fa-sitemap"></i> Browse Tree</label>
|
|
57
|
-
<a href="#" id="node-input-open-browse-modal" class="editor-button">
|
|
58
|
-
<i class="fa fa-expand"></i> Open browse tree
|
|
59
|
-
</a>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
<div class="form-row opcua-client-subscription-row">
|
|
63
|
-
<label for="node-input-samplingInterval"><i class="fa fa-clock-o"></i> Sampling</label>
|
|
64
|
-
<input type="text" id="node-input-samplingInterval" placeholder="250">
|
|
65
|
-
</div>
|
|
66
|
-
<div class="form-row opcua-client-subscription-row">
|
|
67
|
-
<label for="node-input-publishingInterval"><i class="fa fa-clock-o"></i> Publishing</label>
|
|
68
|
-
<input type="text" id="node-input-publishingInterval" placeholder="250">
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
<div class="form-row opcua-client-selection-row" style="margin-bottom:0;">
|
|
72
|
-
<label style="width:auto;"><i class="fa fa-tags"></i> Selected items</label>
|
|
73
|
-
</div>
|
|
74
|
-
<div class="form-row opcua-client-selection-row">
|
|
75
|
-
<label style="width:auto;"></label>
|
|
76
|
-
<div id="node-input-selected-tags" class="opcua-client-tag-box"></div>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
</script>
|
|
80
|
-
|
|
81
|
-
<script type="text/html" data-help-name="opcua-client">
|
|
82
|
-
<p>Unified OPC UA client node for read, write, browse and subscription using a shared client configuration node.</p>
|
|
83
|
-
|
|
84
|
-
<h3>Modes</h3>
|
|
85
|
-
<p><b>Read</b>: reads one or more variable values.</p>
|
|
86
|
-
<p><b>Write</b>: writes one or more variable values.</p>
|
|
87
|
-
<p><b>Browse</b>: browses one or more OPC UA nodes and returns their children.</p>
|
|
88
|
-
<p><b>Method</b>: calls one or more OPC UA methods.</p>
|
|
89
|
-
<p><b>Subscription</b>: subscribes to one or more variable values and emits one message per change.</p>
|
|
90
|
-
|
|
91
|
-
<h3>Read</h3>
|
|
92
|
-
<p><b>Input</b> <code>msg.payload</code>:</p>
|
|
93
|
-
<pre><code>[
|
|
94
|
-
{
|
|
95
|
-
"name": "status",
|
|
96
|
-
"nodeID": "ns=2;s=Factory.Line1.Motor1.status"
|
|
97
|
-
}
|
|
98
|
-
]</code></pre>
|
|
99
|
-
<p>If <code>msg.payload</code> is not an array and <code>NodeId</code> is configured, the node reads that configured node.</p>
|
|
100
|
-
|
|
101
|
-
<h3>Write</h3>
|
|
102
|
-
<p><b>Input</b> <code>msg.payload</code>:</p>
|
|
103
|
-
<pre><code>[
|
|
104
|
-
{
|
|
105
|
-
"name": "speed",
|
|
106
|
-
"nodeID": "ns=2;s=Factory.Line1.Motor1.speed",
|
|
107
|
-
"value": 25.5,
|
|
108
|
-
"type": "Float"
|
|
109
|
-
}
|
|
110
|
-
]</code></pre>
|
|
111
|
-
|
|
112
|
-
<h3>Browse</h3>
|
|
113
|
-
<p>If <code>msg.payload</code> is not an array and <code>NodeId</code> is configured, the node browses that configured node.</p>
|
|
114
|
-
<p>If <code>msg.payload = []</code>, the node browses the OPC UA <code>RootFolder</code>.</p>
|
|
115
|
-
|
|
116
|
-
<h3>Method</h3>
|
|
117
|
-
<p><code>nodeID</code> and <code>objectId</code> are accepted explicitly. For backward compatibility, <code>nodeID</code> is also accepted as the method id.</p>
|
|
118
|
-
<p><b>Input</b> <code>msg.payload</code>:</p>
|
|
119
|
-
<pre><code>[
|
|
120
|
-
{
|
|
121
|
-
"name": "method1",
|
|
122
|
-
"nodeID": "ns=2;s=motor.method1",
|
|
123
|
-
"inputs": [
|
|
124
|
-
{
|
|
125
|
-
"name": "input1",
|
|
126
|
-
"type": "Int32",
|
|
127
|
-
"value": 1
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
"name": "input2",
|
|
131
|
-
"type": "Int32",
|
|
132
|
-
"value": 1
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
]</code></pre>
|
|
139
|
-
<h3>Subscription</h3>
|
|
140
|
-
<p>In subscription mode the node emits one message per value change.</p>
|
|
1
|
+
<link rel="stylesheet" type="text/css" href="opcua-client-resource/style.css">
|
|
2
|
+
|
|
3
|
+
<script type="text/javascript" src="opcua-client-resource/script.js"></script>
|
|
4
|
+
|
|
5
|
+
<script type="text/html" data-template-name="opcua-client">
|
|
6
|
+
<div class="form-row">
|
|
7
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
8
|
+
<input type="text" id="node-input-name" placeholder="opcua-client">
|
|
9
|
+
</div>
|
|
10
|
+
<div class="form-row">
|
|
11
|
+
<label for="node-input-connection"><i class="fa fa-server"></i> Connection</label>
|
|
12
|
+
<input type="text" id="node-input-connection">
|
|
13
|
+
</div>
|
|
14
|
+
<div class="form-row">
|
|
15
|
+
<label for="node-input-mode"><i class="fa fa-exchange"></i> Mode</label>
|
|
16
|
+
<select id="node-input-mode">
|
|
17
|
+
<option value="read">Read</option>
|
|
18
|
+
<option value="write">Write</option>
|
|
19
|
+
<option value="browse">Browse</option>
|
|
20
|
+
<option value="method">Method</option>
|
|
21
|
+
<option value="getSubscriptionId">getSubscriptionId</option>
|
|
22
|
+
<option value="subscription">Subscription</option>
|
|
23
|
+
<option value="events">Events</option>
|
|
24
|
+
</select>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="form-row opcua-client-selection-row">
|
|
28
|
+
<label for="node-input-selectedItems"><i class="fa fa-code"></i> JSON</label>
|
|
29
|
+
<input type="text" id="node-input-selectedItems" class="opcua-client-json" style="width: 70%;">
|
|
30
|
+
</div>
|
|
31
|
+
<div id="node-input-browse-modal" class="opcua-tree-modal" style="display:none;">
|
|
32
|
+
<div class="opcua-tree-modal__dialog">
|
|
33
|
+
<div class="opcua-tree-modal__header">
|
|
34
|
+
<div class="opcua-tree-modal__title"><i class="fa fa-sitemap"></i> OPC UA Browse Tree</div>
|
|
35
|
+
<a href="#" id="node-input-close-browse-modal" class="editor-button editor-button-small"><i class="fa fa-times"></i> Close</a>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="opcua-tree-modal__toolbar">
|
|
38
|
+
<a href="#" id="node-input-browse-root" class="editor-button editor-button-small"><i class="fa fa-search"></i> Browse Root</a>
|
|
39
|
+
<div class="opcua-tree-search">
|
|
40
|
+
<i class="fa fa-search"></i>
|
|
41
|
+
<input type="text" id="node-input-browse-search" placeholder="Search for items already explored">
|
|
42
|
+
<a href="#" id="node-input-browse-search-clear" class="editor-button editor-button-small" style="display:none;"><i class="fa fa-times"></i></a>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="opcua-tree-modal__body">
|
|
46
|
+
<div id="node-input-browse-tree" class="opcua-tree-editor"></div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div id="node-input-browse-context-menu" class="opcua-tree-context-menu" style="display:none;">
|
|
50
|
+
<a href="#" id="node-input-browse-context-refresh"><i class="fa fa-refresh"></i> Refresh node</a>
|
|
51
|
+
<a href="#" id="node-input-browse-context-copy-nodeid"><i class="fa fa-copy"></i> Copy NodeID</a>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="form-row opcua-client-selection-row">
|
|
56
|
+
<label style="width:auto;"><i class="fa fa-sitemap"></i> Browse Tree</label>
|
|
57
|
+
<a href="#" id="node-input-open-browse-modal" class="editor-button">
|
|
58
|
+
<i class="fa fa-expand"></i> Open browse tree
|
|
59
|
+
</a>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div class="form-row opcua-client-subscription-row">
|
|
63
|
+
<label for="node-input-samplingInterval"><i class="fa fa-clock-o"></i> Sampling</label>
|
|
64
|
+
<input type="text" id="node-input-samplingInterval" placeholder="250">
|
|
65
|
+
</div>
|
|
66
|
+
<div class="form-row opcua-client-subscription-row">
|
|
67
|
+
<label for="node-input-publishingInterval"><i class="fa fa-clock-o"></i> Publishing</label>
|
|
68
|
+
<input type="text" id="node-input-publishingInterval" placeholder="250">
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="form-row opcua-client-selection-row" style="margin-bottom:0;">
|
|
72
|
+
<label style="width:auto;"><i class="fa fa-tags"></i> Selected items</label>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="form-row opcua-client-selection-row">
|
|
75
|
+
<label style="width:auto;"></label>
|
|
76
|
+
<div id="node-input-selected-tags" class="opcua-client-tag-box"></div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<script type="text/html" data-help-name="opcua-client">
|
|
82
|
+
<p>Unified OPC UA client node for read, write, browse and subscription using a shared client configuration node.</p>
|
|
83
|
+
|
|
84
|
+
<h3>Modes</h3>
|
|
85
|
+
<p><b>Read</b>: reads one or more variable values.</p>
|
|
86
|
+
<p><b>Write</b>: writes one or more variable values.</p>
|
|
87
|
+
<p><b>Browse</b>: browses one or more OPC UA nodes and returns their children.</p>
|
|
88
|
+
<p><b>Method</b>: calls one or more OPC UA methods.</p>
|
|
89
|
+
<p><b>Subscription</b>: subscribes to one or more variable values and emits one message per change.</p>
|
|
90
|
+
|
|
91
|
+
<h3>Read</h3>
|
|
92
|
+
<p><b>Input</b> <code>msg.payload</code>:</p>
|
|
93
|
+
<pre><code>[
|
|
94
|
+
{
|
|
95
|
+
"name": "status",
|
|
96
|
+
"nodeID": "ns=2;s=Factory.Line1.Motor1.status"
|
|
97
|
+
}
|
|
98
|
+
]</code></pre>
|
|
99
|
+
<p>If <code>msg.payload</code> is not an array and <code>NodeId</code> is configured, the node reads that configured node.</p>
|
|
100
|
+
|
|
101
|
+
<h3>Write</h3>
|
|
102
|
+
<p><b>Input</b> <code>msg.payload</code>:</p>
|
|
103
|
+
<pre><code>[
|
|
104
|
+
{
|
|
105
|
+
"name": "speed",
|
|
106
|
+
"nodeID": "ns=2;s=Factory.Line1.Motor1.speed",
|
|
107
|
+
"value": 25.5,
|
|
108
|
+
"type": "Float"
|
|
109
|
+
}
|
|
110
|
+
]</code></pre>
|
|
111
|
+
|
|
112
|
+
<h3>Browse</h3>
|
|
113
|
+
<p>If <code>msg.payload</code> is not an array and <code>NodeId</code> is configured, the node browses that configured node.</p>
|
|
114
|
+
<p>If <code>msg.payload = []</code>, the node browses the OPC UA <code>RootFolder</code>.</p>
|
|
115
|
+
|
|
116
|
+
<h3>Method</h3>
|
|
117
|
+
<p><code>nodeID</code> and <code>objectId</code> are accepted explicitly. For backward compatibility, <code>nodeID</code> is also accepted as the method id.</p>
|
|
118
|
+
<p><b>Input</b> <code>msg.payload</code>:</p>
|
|
119
|
+
<pre><code>[
|
|
120
|
+
{
|
|
121
|
+
"name": "method1",
|
|
122
|
+
"nodeID": "ns=2;s=motor.method1",
|
|
123
|
+
"inputs": [
|
|
124
|
+
{
|
|
125
|
+
"name": "input1",
|
|
126
|
+
"type": "Int32",
|
|
127
|
+
"value": 1
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "input2",
|
|
131
|
+
"type": "Int32",
|
|
132
|
+
"value": 1
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
]</code></pre>
|
|
139
|
+
<h3>Subscription</h3>
|
|
140
|
+
<p>In subscription mode the node emits one message per value change.</p>
|
|
141
141
|
</script>
|