@vitormnm/node-red-simple-opcua 1.6.2 → 1.6.3
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/package.json
CHANGED
|
@@ -193,15 +193,18 @@ class OpcUaServerProcess {
|
|
|
193
193
|
const target = msg && msg.opcuaServerIo ? msg.opcuaServerIo : {};
|
|
194
194
|
const identifierType = this.resolveIdentifierType(target);
|
|
195
195
|
|
|
196
|
-
let result = {}
|
|
196
|
+
let result = {};
|
|
197
|
+
let readArrayResults = null;
|
|
197
198
|
|
|
198
199
|
if (Array.isArray(payload)) {
|
|
199
200
|
if (!payload.length) {
|
|
200
201
|
throw new Error("msg.payload array does not contain any items");
|
|
201
202
|
}
|
|
202
203
|
|
|
204
|
+
readArrayResults = payload.map((item) => this.readPayloadItem(identifierType, item));
|
|
205
|
+
|
|
203
206
|
result = {
|
|
204
|
-
payload:
|
|
207
|
+
payload: readArrayResults,
|
|
205
208
|
identifiers: payload.map((item) => this.resolvePayloadItemIdentifier(item))
|
|
206
209
|
};
|
|
207
210
|
} else if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
@@ -225,9 +228,18 @@ class OpcUaServerProcess {
|
|
|
225
228
|
};
|
|
226
229
|
} else {
|
|
227
230
|
const identifier = this.resolveIdentifier(target);
|
|
231
|
+
let directValue = null;
|
|
232
|
+
let directError = null;
|
|
233
|
+
try {
|
|
234
|
+
directValue = server.readValue(identifierType, identifier);
|
|
235
|
+
} catch (e) {
|
|
236
|
+
directValue = null;
|
|
237
|
+
directError = { identifier, message: e.message || String(e) };
|
|
238
|
+
}
|
|
228
239
|
result = {
|
|
229
|
-
payload:
|
|
230
|
-
identifiers: [identifier]
|
|
240
|
+
payload: directValue,
|
|
241
|
+
identifiers: [identifier],
|
|
242
|
+
directError
|
|
231
243
|
};
|
|
232
244
|
|
|
233
245
|
}
|
|
@@ -261,6 +273,35 @@ class OpcUaServerProcess {
|
|
|
261
273
|
nodeId: nodeId
|
|
262
274
|
});
|
|
263
275
|
|
|
276
|
+
// Emit a partialError for items that could not be read (array-of-objects mode only)
|
|
277
|
+
if (readArrayResults) {
|
|
278
|
+
const failed = readArrayResults
|
|
279
|
+
.filter(item => item && item.status !== "Good")
|
|
280
|
+
.map(item => ({ name: item.name, path: item.path, status: item.status }));
|
|
281
|
+
|
|
282
|
+
if (failed.length) {
|
|
283
|
+
process.send({
|
|
284
|
+
type: "partialError",
|
|
285
|
+
error: "Some tags could not be read: " + failed.map(f => f.path).join(", "),
|
|
286
|
+
failed: failed,
|
|
287
|
+
originalMsg: msg,
|
|
288
|
+
nodeId: nodeId
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Emit a partialError for a direct single-tag read failure
|
|
294
|
+
if (result.directError) {
|
|
295
|
+
const { identifier, message } = result.directError;
|
|
296
|
+
process.send({
|
|
297
|
+
type: "partialError",
|
|
298
|
+
error: "Some tags could not be read: " + identifier,
|
|
299
|
+
failed: [{ name: "", path: identifier, status: message }],
|
|
300
|
+
originalMsg: msg,
|
|
301
|
+
nodeId: nodeId
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
264
305
|
} catch (error) {
|
|
265
306
|
|
|
266
307
|
process.send({
|
|
@@ -335,6 +376,7 @@ class OpcUaServerProcess {
|
|
|
335
376
|
try {
|
|
336
377
|
let writtenPaths = null;
|
|
337
378
|
let payload = msg ? msg.payload : undefined;
|
|
379
|
+
let directError = null;
|
|
338
380
|
|
|
339
381
|
const target = msg && msg.opcuaServerIo ? msg.opcuaServerIo : {};
|
|
340
382
|
const identifierType = this.resolveIdentifierType(target);
|
|
@@ -365,14 +407,18 @@ class OpcUaServerProcess {
|
|
|
365
407
|
if (Buffer.isBuffer(payload) || payload instanceof Uint8Array) {
|
|
366
408
|
|
|
367
409
|
const identifier = this.resolveIdentifier(target);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
410
|
+
try {
|
|
411
|
+
this.node.writeValue(
|
|
412
|
+
identifierType,
|
|
413
|
+
identifier,
|
|
414
|
+
Buffer.isBuffer(payload)
|
|
415
|
+
? payload
|
|
416
|
+
: Buffer.from(payload)
|
|
417
|
+
);
|
|
418
|
+
} catch (e) {
|
|
419
|
+
directError = { identifier, message: e.message || String(e) };
|
|
420
|
+
msg.payload = null;
|
|
421
|
+
}
|
|
376
422
|
|
|
377
423
|
writtenPaths = [identifier];
|
|
378
424
|
}
|
|
@@ -384,14 +430,18 @@ class OpcUaServerProcess {
|
|
|
384
430
|
) {
|
|
385
431
|
|
|
386
432
|
const identifier = this.resolveIdentifier(target);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
433
|
+
try {
|
|
434
|
+
this.node.writeValue(
|
|
435
|
+
identifierType,
|
|
436
|
+
identifier,
|
|
437
|
+
isByteString
|
|
438
|
+
? Buffer.from(payload)
|
|
439
|
+
: payload
|
|
440
|
+
);
|
|
441
|
+
} catch (e) {
|
|
442
|
+
directError = { identifier, message: e.message || String(e) };
|
|
443
|
+
msg.payload = null;
|
|
444
|
+
}
|
|
395
445
|
|
|
396
446
|
writtenPaths = [identifier];
|
|
397
447
|
}
|
|
@@ -403,13 +453,13 @@ class OpcUaServerProcess {
|
|
|
403
453
|
throw new Error("msg.payload array does not contain any items");
|
|
404
454
|
}
|
|
405
455
|
|
|
406
|
-
payload.
|
|
407
|
-
this.writePayloadItem(identifierType, item)
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
writtenPaths = payload.map(item =>
|
|
411
|
-
this.resolvePayloadItemIdentifier(item)
|
|
456
|
+
const writeArrayResults = payload.map(item =>
|
|
457
|
+
this.writePayloadItem(identifierType, item)
|
|
412
458
|
);
|
|
459
|
+
|
|
460
|
+
msg.payload = writeArrayResults;
|
|
461
|
+
|
|
462
|
+
writtenPaths = writeArrayResults.map(item => item.path);
|
|
413
463
|
}
|
|
414
464
|
|
|
415
465
|
// Objeto { path: value }
|
|
@@ -429,11 +479,15 @@ class OpcUaServerProcess {
|
|
|
429
479
|
}
|
|
430
480
|
|
|
431
481
|
identifiers.forEach(identifier => {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
482
|
+
try {
|
|
483
|
+
this.node.writeValue(
|
|
484
|
+
identifierType,
|
|
485
|
+
identifier,
|
|
486
|
+
payload[identifier]
|
|
487
|
+
);
|
|
488
|
+
} catch (e) {
|
|
489
|
+
// suppress per-item errors; unknown paths become undefined
|
|
490
|
+
}
|
|
437
491
|
});
|
|
438
492
|
|
|
439
493
|
writtenPaths = identifiers;
|
|
@@ -443,12 +497,16 @@ class OpcUaServerProcess {
|
|
|
443
497
|
else {
|
|
444
498
|
|
|
445
499
|
const identifier = this.resolveIdentifier(target);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
500
|
+
try {
|
|
501
|
+
this.node.writeValue(
|
|
502
|
+
identifierType,
|
|
503
|
+
identifier,
|
|
504
|
+
payload
|
|
505
|
+
);
|
|
506
|
+
} catch (e) {
|
|
507
|
+
directError = { identifier, message: e.message || String(e) };
|
|
508
|
+
msg.payload = null;
|
|
509
|
+
}
|
|
452
510
|
|
|
453
511
|
writtenPaths = [identifier];
|
|
454
512
|
}
|
|
@@ -485,6 +543,35 @@ class OpcUaServerProcess {
|
|
|
485
543
|
nodeId
|
|
486
544
|
});
|
|
487
545
|
|
|
546
|
+
// Emit a partialError for items that could not be written (array-of-objects mode only)
|
|
547
|
+
if (Array.isArray(msg.payload) && msg.payload.length && msg.payload[0] && typeof msg.payload[0].status === "string") {
|
|
548
|
+
const failed = msg.payload
|
|
549
|
+
.filter(item => item.status !== "Good")
|
|
550
|
+
.map(item => ({ name: item.name, path: item.path, status: item.status }));
|
|
551
|
+
|
|
552
|
+
if (failed.length) {
|
|
553
|
+
process.send({
|
|
554
|
+
type: "partialError",
|
|
555
|
+
error: "Some tags could not be written: " + failed.map(f => f.path).join(", "),
|
|
556
|
+
failed: failed,
|
|
557
|
+
originalMsg: msg,
|
|
558
|
+
nodeId
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Emit a partialError for a direct single-tag write failure
|
|
564
|
+
if (directError) {
|
|
565
|
+
const { identifier, message } = directError;
|
|
566
|
+
process.send({
|
|
567
|
+
type: "partialError",
|
|
568
|
+
error: "Some tags could not be written: " + identifier,
|
|
569
|
+
failed: [{ name: "", path: identifier, status: message }],
|
|
570
|
+
originalMsg: msg,
|
|
571
|
+
nodeId
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
488
575
|
} catch (error) {
|
|
489
576
|
|
|
490
577
|
process.send({
|
|
@@ -541,20 +628,38 @@ class OpcUaServerProcess {
|
|
|
541
628
|
|
|
542
629
|
readPayloadItem(identifierType, item) {
|
|
543
630
|
const identifier = this.resolvePayloadItemIdentifier(item);
|
|
631
|
+
let value = null;
|
|
632
|
+
let status = "Good";
|
|
633
|
+
try {
|
|
634
|
+
value = this.node.readValue(identifierType, identifier);
|
|
635
|
+
} catch (e) {
|
|
636
|
+
value = null;
|
|
637
|
+
status = e.message || String(e);
|
|
638
|
+
}
|
|
544
639
|
return {
|
|
545
640
|
name: item.name,
|
|
546
641
|
path: identifier,
|
|
547
|
-
value
|
|
642
|
+
value,
|
|
643
|
+
status
|
|
548
644
|
};
|
|
549
645
|
}
|
|
550
646
|
|
|
551
647
|
writePayloadItem(identifierType, item) {
|
|
552
648
|
const identifier = this.resolvePayloadItemIdentifier(item);
|
|
553
|
-
|
|
649
|
+
let writtenValue = null;
|
|
650
|
+
let status = "Good";
|
|
651
|
+
try {
|
|
652
|
+
this.node.writeValue(identifierType, identifier, item.value);
|
|
653
|
+
writtenValue = item.value;
|
|
654
|
+
} catch (e) {
|
|
655
|
+
writtenValue = null;
|
|
656
|
+
status = e.message || String(e);
|
|
657
|
+
}
|
|
554
658
|
return {
|
|
555
659
|
name: item.name,
|
|
556
660
|
path: identifier,
|
|
557
|
-
value:
|
|
661
|
+
value: writtenValue,
|
|
662
|
+
status
|
|
558
663
|
};
|
|
559
664
|
}
|
|
560
665
|
|
|
@@ -152,6 +152,15 @@ module.exports = function (RED) {
|
|
|
152
152
|
node.error(msg.error);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
if (msg.type === "partialError") {
|
|
156
|
+
// Route failed items to catch node without changing the node status
|
|
157
|
+
const catchMsg = Object.assign({}, msg.originalMsg || {}, {
|
|
158
|
+
payload: msg.failed,
|
|
159
|
+
error: msg.error
|
|
160
|
+
});
|
|
161
|
+
node.error(msg.error, catchMsg);
|
|
162
|
+
}
|
|
163
|
+
|
|
155
164
|
if (msg.type === "sendMethod") {
|
|
156
165
|
|
|
157
166
|
node.status({
|
package/server/opcua-server.js
CHANGED
|
@@ -21,6 +21,7 @@ module.exports = function (RED) {
|
|
|
21
21
|
node.serverName = settings.serverName;
|
|
22
22
|
node.server = null;
|
|
23
23
|
node.namespace = null;
|
|
24
|
+
node.isClosing = false;
|
|
24
25
|
|
|
25
26
|
node.status({ fill: "yellow", shape: "ring", text: "initializing OPC UA server" });
|
|
26
27
|
|
|
@@ -44,12 +45,35 @@ module.exports = function (RED) {
|
|
|
44
45
|
|
|
45
46
|
registry.registerServerNames(node.serverName, node.serverName);
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
let crashHandled = false;
|
|
49
|
+
function handleUnexpectedExit(code, signal) {
|
|
50
|
+
if (node.isClosing || crashHandled) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
crashHandled = true;
|
|
54
|
+
|
|
55
|
+
const errorDetails = `OPC UA server child process exited unexpectedly with code ${code} and signal ${signal}`;
|
|
56
|
+
node.status({ fill: "red", shape: "dot", text: "Child process crashed" });
|
|
57
|
+
|
|
58
|
+
const catchMsg = {
|
|
59
|
+
topic: node.serverName,
|
|
60
|
+
payload: {
|
|
61
|
+
status: "error",
|
|
62
|
+
error: errorDetails
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
node.send(catchMsg);
|
|
66
|
+
node.error(errorDetails, catchMsg);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
child.on("exit", (code, signal) => {
|
|
48
70
|
registry.unregisterChild(node.serverName, child);
|
|
71
|
+
handleUnexpectedExit(code, signal);
|
|
49
72
|
});
|
|
50
73
|
|
|
51
|
-
child.on("close", () => {
|
|
74
|
+
child.on("close", (code, signal) => {
|
|
52
75
|
registry.unregisterChild(node.serverName, child);
|
|
76
|
+
handleUnexpectedExit(code, signal);
|
|
53
77
|
});
|
|
54
78
|
|
|
55
79
|
|
|
@@ -112,6 +136,7 @@ module.exports = function (RED) {
|
|
|
112
136
|
|
|
113
137
|
node.on("close", async function (removed, done) {
|
|
114
138
|
try {
|
|
139
|
+
node.isClosing = true;
|
|
115
140
|
registry.unregisterChild(node.serverName, child);
|
|
116
141
|
registry.unregisterServerNames(node.serverName);
|
|
117
142
|
child.kill();
|