@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitormnm/node-red-simple-opcua",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -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: payload.map((item) => this.readPayloadItem(identifierType, item)),
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: server.readValue(identifierType, identifier),
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
- this.node.writeValue(
370
- identifierType,
371
- identifier,
372
- Buffer.isBuffer(payload)
373
- ? payload
374
- : Buffer.from(payload)
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
- this.node.writeValue(
389
- identifierType,
390
- identifier,
391
- isByteString
392
- ? Buffer.from(payload)
393
- : payload
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.forEach(item => {
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
- this.node.writeValue(
433
- identifierType,
434
- identifier,
435
- payload[identifier]
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
- this.node.writeValue(
448
- identifierType,
449
- identifier,
450
- payload
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: this.node.readValue(identifierType, identifier)
642
+ value,
643
+ status
548
644
  };
549
645
  }
550
646
 
551
647
  writePayloadItem(identifierType, item) {
552
648
  const identifier = this.resolvePayloadItemIdentifier(item);
553
- this.node.writeValue(identifierType, identifier, item.value);
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: item.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({
@@ -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
- child.on("exit", () => {
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();