@vitormnm/node-red-simple-opcua 1.4.2 → 1.4.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.
@@ -1,820 +1,820 @@
1
- "use strict";
2
-
3
-
4
-
5
- const {
6
- OPCUAServer,
7
- UserTokenType,
8
- buildApplicationUri
9
- } = require("./opcua-constants");
10
-
11
- const { OpcUaAddressSpaceBuilder } = require("./opcua-address-space-builder");
12
- const { OpcUaServerRuntime } = require("./opcua-server-runtime");
13
- const { OpcUaServerConfigParser } = require("./opcua-config");
14
- const { resolveRegisteredServer } = require("./server-node-utils");
15
- const { OpcUaServerStatusNode } = require("./opcua-server-status-child")
16
- const { eventsServer } = require("./opcua-server-events-child")
17
- const registry = require("../opcua-server-registry");
18
-
19
- /**
20
- * Classe responsável por gerenciar TODO o ciclo de vida do servidor OPC UA
21
- */
22
- class OpcUaServerProcess {
23
- constructor() {
24
- this.node = {};
25
- this.runtime = null;
26
- this.parser = new OpcUaServerConfigParser(this.node);
27
- this.lifecyclePromise = null;
28
- this.isRunning = false;
29
- }
30
-
31
- /**
32
- * Cria e inicia o servidor
33
- */
34
- async create(settings, nodeId) {
35
- if (this.isRunning) {
36
- throw new Error("Server already running");
37
- }
38
-
39
- this.node.name = settings.name;
40
- this.node.serverName = settings.serverName;
41
- this.node.server = null;
42
- this.node.namespace = null;
43
-
44
- this.runtime = new OpcUaServerRuntime({
45
- node: this.node,
46
- registry,
47
- settings
48
- });
49
-
50
- this.node.runtime = this.runtime;
51
-
52
- this.node.readValueByPath = (path) => {
53
- return this.runtime.readValueByPath(path);
54
- };
55
-
56
- this.node.readValueByNodeId = (nodeId) => {
57
- return this.runtime.readValueByNodeId(nodeId);
58
- };
59
-
60
- this.node.readValue = (identifierType, identifier) => {
61
- return this.runtime.readValue(identifierType, identifier);
62
- };
63
-
64
- this.node.writeEventByPath = (valuePayload) => {
65
- return this.runtime.writeEventByPath(valuePayload);
66
- };
67
-
68
- this.node.writeValueByPath = (path, value) => {
69
- return this.runtime.writeValueByPath(path, value);
70
- };
71
-
72
- this.node.writeValueByNodeId = (nodeId, value) => {
73
- return this.runtime.writeValueByNodeId(nodeId, value);
74
- };
75
-
76
- this.node.writeValue = (identifierType, identifier, value) => {
77
- return this.runtime.writeValue(identifierType, identifier, value);
78
- };
79
-
80
- try {
81
- this.lifecyclePromise = this.runtime.start();
82
- await this.lifecyclePromise;
83
-
84
- this.node.server = this.runtime.server;
85
- this.node.namespace = this.runtime.namespace;
86
-
87
- this.isRunning = true;
88
-
89
- const endpointUrl = this.runtime.getEndpointUrl();
90
-
91
- process.send({
92
- type: "status",
93
- data: {
94
- fill: "green",
95
- shape: "dot",
96
- text: endpointUrl
97
- ? "running " + endpointUrl
98
- : "running"
99
- },
100
- nodeId: nodeId
101
- });
102
-
103
-
104
- } catch (error) {
105
- this.isRunning = false;
106
- console.error("Failed to start OPC UA server:", error);
107
-
108
- process.send({
109
- type: "error",
110
- data: "Failed to start OPC UA server: " + error.message
111
- });
112
-
113
- process.send({
114
- type: "status",
115
- data: {
116
- fill: "red",
117
- shape: "dot",
118
- text: "Failed to start OPC UA server"
119
- },
120
- nodeId: nodeId
121
- });
122
- }
123
- }
124
-
125
- /**
126
- * Para o servidor
127
- */
128
- async stop(nodeId) {
129
- try {
130
- if (!this.runtime) return;
131
-
132
- if (this.lifecyclePromise) {
133
- await this.lifecyclePromise.catch(() => { });
134
- this.lifecyclePromise = null;
135
- }
136
-
137
- await this.runtime.stop();
138
-
139
- this.node.server = null;
140
- this.node.namespace = null;
141
- this.isRunning = false;
142
-
143
- process.send({
144
- type: "status",
145
- data: {
146
- fill: "red",
147
- shape: "ring",
148
- text: "stopped"
149
- },
150
- nodeId: nodeId
151
- });
152
-
153
-
154
- } catch (error) {
155
- console.error("Failed to stop OPC UA server:", error);
156
-
157
- process.send({
158
- type: "error",
159
- data: "Failed to stop OPC UA server: " + error.message
160
- });
161
- }
162
- }
163
-
164
- readFromPayload(msg, nodeId) {
165
-
166
- try {
167
-
168
- const server = this.node.runtime
169
- const payload = msg ? msg.payload : undefined;
170
- const target = msg && msg.opcuaServerIo ? msg.opcuaServerIo : {};
171
- const identifierType = this.resolveIdentifierType(target);
172
-
173
- let result = {}
174
-
175
- if (Array.isArray(payload)) {
176
- if (!payload.length) {
177
- throw new Error("msg.payload array does not contain any items");
178
- }
179
-
180
- result = {
181
- payload: payload.map((item) => this.readPayloadItem(identifierType, item)),
182
- identifiers: payload.map((item) => this.resolvePayloadItemIdentifier(item))
183
- };
184
- } else if (payload && typeof payload === "object" && !Array.isArray(payload)) {
185
- const identifiers = Object.keys(payload);
186
- if (!identifiers.length) {
187
- throw new Error("msg.payload object does not contain any " + this.getIdentifierLabel(identifierType));
188
- }
189
-
190
- const resultPayload = {};
191
- identifiers.forEach((identifier) => {
192
- try {
193
- resultPayload[identifier] = server.readValue(identifierType, identifier);
194
- } catch (error) {
195
- resultPayload[identifier] = undefined;
196
- }
197
- });
198
-
199
- result = {
200
- payload: resultPayload,
201
- identifiers: identifiers
202
- };
203
- } else {
204
- const identifier = this.resolveIdentifier(target);
205
- result = {
206
- payload: server.readValue(identifierType, identifier),
207
- identifiers: [identifier]
208
- };
209
-
210
- }
211
-
212
-
213
- msg.opcua = msg.opcua || {};
214
- msg.payload = result.payload;
215
- this.assignReadMetadata(msg, identifierType, result.identifiers);
216
-
217
-
218
-
219
- if (result.identifiers.length === 1) {
220
- msg.topic = result.identifiers[0];
221
- }
222
-
223
-
224
-
225
- process.send({
226
- type: "send",
227
- data: msg,
228
- nodeId: nodeId
229
- });
230
-
231
- process.send({
232
- type: "status",
233
- data: {
234
- fill: "green",
235
- shape: "dot",
236
- text: result.identifiers.length > 1 ? "read " + result.identifiers.length + " tags" : "read " + result.identifiers[0]
237
- },
238
- nodeId: nodeId
239
- });
240
-
241
- } catch (error) {
242
-
243
- process.send({
244
- type: "error",
245
- data: { fill: "red", shape: "ring", text: "failed read" },
246
- error: error.message,
247
- nodeId: nodeId
248
- });
249
- }
250
-
251
- }
252
-
253
- writeEventFromPayload(msg, nodeId) {
254
- try {
255
- var writtenPaths = null
256
- const payload = msg ? msg.payload : undefined;
257
-
258
- writtenPaths = payload
259
-
260
- if (payload && Array.isArray(payload)) {
261
-
262
-
263
-
264
- payload.forEach((valuePayload) => {
265
- this.node.writeEventByPath(valuePayload);
266
- });
267
-
268
-
269
- } else if (payload && typeof payload === "object" && !Array.isArray(payload)) {
270
-
271
-
272
- this.node.writeEventByPath(payload);
273
-
274
-
275
-
276
- }
277
-
278
- process.send({
279
- type: "send",
280
- data: msg,
281
- nodeId: nodeId
282
- });
283
-
284
- process.send({
285
- type: "status",
286
- data: {
287
- fill: "green",
288
- shape: "dot",
289
- text: writtenPaths.length > 1 ? "write " + writtenPaths.length + " events" : "Event " + writtenPaths.nodePath
290
- },
291
- nodeId: nodeId
292
- });
293
-
294
-
295
- } catch (error) {
296
-
297
- process.send({
298
- type: "error",
299
- data: { fill: "red", shape: "ring", text: "failed write" },
300
- error: error.message,
301
- nodeId: nodeId
302
- });
303
- }
304
-
305
-
306
-
307
- //return [path];
308
- }
309
-
310
-
311
- writeFromPayload(msg, nodeId) {
312
- try {
313
- let writtenPaths = null;
314
- let payload = msg ? msg.payload : undefined;
315
-
316
- const target = msg && msg.opcuaServerIo ? msg.opcuaServerIo : {};
317
- const identifierType = this.resolveIdentifierType(target);
318
-
319
- // Buffer serializado pelo IPC
320
- if (
321
- payload &&
322
- typeof payload === "object" &&
323
- payload.type === "Buffer" &&
324
- Array.isArray(payload.data)
325
- ) {
326
- payload = Buffer.from(payload.data);
327
- }
328
-
329
- const dataType =
330
- target.dataType ||
331
- target.type ||
332
- target.builtInType ||
333
- "";
334
-
335
- const isByteString =
336
- typeof dataType === "string" &&
337
- dataType.toLowerCase() === "bytestring";
338
-
339
-
340
-
341
- // Buffer ou Uint8Array
342
- if (Buffer.isBuffer(payload) || payload instanceof Uint8Array) {
343
-
344
- const identifier = this.resolveIdentifier(target);
345
-
346
- this.node.writeValue(
347
- identifierType,
348
- identifier,
349
- Buffer.isBuffer(payload)
350
- ? payload
351
- : Buffer.from(payload)
352
- );
353
-
354
- writtenPaths = [identifier];
355
- }
356
-
357
- // Array de números
358
- else if (
359
- Array.isArray(payload) &&
360
- payload.every(item => typeof item === "number")
361
- ) {
362
-
363
- const identifier = this.resolveIdentifier(target);
364
-
365
- this.node.writeValue(
366
- identifierType,
367
- identifier,
368
- isByteString
369
- ? Buffer.from(payload)
370
- : payload
371
- );
372
-
373
- writtenPaths = [identifier];
374
- }
375
-
376
- // Array de objetos
377
- else if (Array.isArray(payload)) {
378
-
379
- if (!payload.length) {
380
- throw new Error("msg.payload array does not contain any items");
381
- }
382
-
383
- payload.forEach(item => {
384
- this.writePayloadItem(identifierType, item);
385
- });
386
-
387
- writtenPaths = payload.map(item =>
388
- this.resolvePayloadItemIdentifier(item)
389
- );
390
- }
391
-
392
- // Objeto { path: value }
393
- else if (
394
- payload &&
395
- typeof payload === "object" &&
396
- !Array.isArray(payload)
397
- ) {
398
-
399
- const identifiers = Object.keys(payload);
400
-
401
- if (!identifiers.length) {
402
- throw new Error(
403
- "msg.payload object does not contain any " +
404
- this.getIdentifierLabel(identifierType)
405
- );
406
- }
407
-
408
- identifiers.forEach(identifier => {
409
- this.node.writeValue(
410
- identifierType,
411
- identifier,
412
- payload[identifier]
413
- );
414
- });
415
-
416
- writtenPaths = identifiers;
417
- }
418
-
419
- // Valor simples
420
- else {
421
-
422
- const identifier = this.resolveIdentifier(target);
423
-
424
- this.node.writeValue(
425
- identifierType,
426
- identifier,
427
- payload
428
- );
429
-
430
- writtenPaths = [identifier];
431
- }
432
-
433
- msg.opcua = msg.opcua || {};
434
-
435
- this.assignWriteMetadata(
436
- msg,
437
- identifierType,
438
- writtenPaths
439
- );
440
-
441
-
442
- if (writtenPaths.length === 1) {
443
- msg.topic = writtenPaths[0];
444
- }
445
-
446
- process.send({
447
- type: "send",
448
- data: msg,
449
- nodeId
450
- });
451
-
452
- process.send({
453
- type: "status",
454
- data: {
455
- fill: "green",
456
- shape: "dot",
457
- text:
458
- writtenPaths.length > 1
459
- ? `write ${writtenPaths.length} tags`
460
- : `write ${writtenPaths[0]}`
461
- },
462
- nodeId
463
- });
464
-
465
- } catch (error) {
466
-
467
- process.send({
468
- type: "error",
469
- data: {
470
- fill: "red",
471
- shape: "ring",
472
- text: "failed write"
473
- },
474
- error: error.message,
475
- nodeId
476
- });
477
- }
478
- }
479
-
480
- resolveIdentifierType(target) {
481
- return target && target.identifierType === "nodeId" ? "nodeId" : "path";
482
- }
483
-
484
- resolveIdentifier(target) {
485
- const identifierType = this.resolveIdentifierType(target);
486
- if (identifierType === "nodeId") {
487
- const nodeId = String(target.tagNodeId || "").trim();
488
- if (!nodeId) {
489
- throw new Error("No tag nodeId configured");
490
- }
491
- return nodeId;
492
- }
493
-
494
- const path = String(target.tagPath || "").trim();
495
- if (!path) {
496
- throw new Error("No tag path configured");
497
- }
498
-
499
- return path;
500
- }
501
-
502
- getIdentifierLabel(identifierType) {
503
- return identifierType === "nodeId" ? "nodeIds" : "tag paths";
504
- }
505
-
506
- resolvePayloadItemIdentifier(item) {
507
- if (!item || typeof item !== "object" || Array.isArray(item)) {
508
- throw new Error("Each payload item must be an object");
509
- }
510
-
511
- const identifier = String(item.path || "").trim();
512
- if (!identifier) {
513
- throw new Error("Each payload item must contain a path");
514
- }
515
-
516
- return identifier;
517
- }
518
-
519
- readPayloadItem(identifierType, item) {
520
- const identifier = this.resolvePayloadItemIdentifier(item);
521
- return {
522
- name: item.name,
523
- path: identifier,
524
- value: this.node.readValue(identifierType, identifier)
525
- };
526
- }
527
-
528
- writePayloadItem(identifierType, item) {
529
- const identifier = this.resolvePayloadItemIdentifier(item);
530
- this.node.writeValue(identifierType, identifier, item.value);
531
- return {
532
- name: item.name,
533
- path: identifier,
534
- value: item.value
535
- };
536
- }
537
-
538
- assignReadMetadata(msg, identifierType, identifiers) {
539
- msg.opcua.identifierType = identifierType;
540
- if (identifierType === "nodeId") {
541
- msg.opcua.readNodeIds = identifiers;
542
- delete msg.opcua.readPaths;
543
- if (identifiers.length === 1) {
544
- msg.opcua.tagNodeId = identifiers[0];
545
- delete msg.opcua.tagPath;
546
- }
547
- return;
548
- }
549
-
550
- msg.opcua.readPaths = identifiers;
551
- delete msg.opcua.readNodeIds;
552
- if (identifiers.length === 1) {
553
- msg.opcua.tagPath = identifiers[0];
554
- delete msg.opcua.tagNodeId;
555
- }
556
- }
557
-
558
- assignWriteMetadata(msg, identifierType, identifiers) {
559
- msg.opcua.identifierType = identifierType;
560
- if (identifierType === "nodeId") {
561
- msg.opcua.writtenNodeIds = identifiers;
562
- delete msg.opcua.writtenPaths;
563
- if (identifiers.length === 1) {
564
- msg.opcua.tagNodeId = identifiers[0];
565
- delete msg.opcua.tagPath;
566
- }
567
- return;
568
- }
569
-
570
- msg.opcua.writtenPaths = identifiers;
571
- delete msg.opcua.writtenNodeIds;
572
- if (identifiers.length === 1) {
573
- msg.opcua.tagPath = identifiers[0];
574
- delete msg.opcua.tagNodeId;
575
- }
576
- }
577
-
578
- /**
579
- * Atualiza a árvore do servidor
580
- */
581
- async update(payload, nodeId) {
582
- try {
583
-
584
-
585
- process.send({
586
- type: "status",
587
- data: {
588
- fill: "yellow",
589
- shape: "dot",
590
- text: "updating items"
591
- },
592
- nodeId: nodeId
593
- });
594
-
595
- await this.ensureReady();
596
-
597
- const nextTree = this.parser.normalizeTreeConfig(payload);
598
-
599
- await this.runtime.updateTree(nextTree);
600
-
601
- const endpointUrl = await this.runtime.getEndpointUrl();
602
-
603
-
604
- process.send({
605
- type: "status",
606
- data: {
607
- fill: "green",
608
- shape: "dot",
609
- text: endpointUrl
610
- ? "running " + endpointUrl
611
- : "running"
612
- },
613
- nodeId: nodeId
614
- });
615
-
616
-
617
- } catch (error) {
618
-
619
-
620
- process.send({
621
- type: "errorUpdateServer",
622
- data: error.message,
623
- nodeId: nodeId
624
- });
625
-
626
- process.send({
627
- type: "status",
628
- data: {
629
- fill: "red",
630
- shape: "dot",
631
- text: "error update"
632
- },
633
- nodeId: nodeId
634
- });
635
- }
636
- }
637
-
638
-
639
- /**
640
- * metodos opc ua
641
- */
642
- registerMethodInput(methodName, nodeId) {
643
-
644
- if (methodName) {
645
-
646
- registry.registerMethodHandler(methodName, nodeId);
647
- } else {
648
-
649
-
650
- }
651
-
652
-
653
- }
654
-
655
-
656
- handleMethodOutput(msg, nodeId) {
657
-
658
- resolveRegisteredServer(this.node, msg, registry);
659
-
660
- if (!msg._callId) {
661
- throw new Error("Missing _callId for OPC UA method response");
662
- }
663
-
664
- registry.resolveMethodCall(msg._callId, msg.payload);
665
-
666
-
667
- process.send({
668
- type: "status",
669
- data: {
670
- fill: "green",
671
- shape: "dot",
672
- text: "response sent"
673
- },
674
- nodeId: nodeId
675
- });
676
-
677
-
678
- }
679
-
680
- /**
681
- * Garante que o servidor está pronto
682
- */
683
- async ensureReady() {
684
- if (!this.runtime) {
685
- throw new Error("Server not created");
686
- }
687
-
688
- if (this.lifecyclePromise) {
689
- await this.lifecyclePromise;
690
- }
691
-
692
- this.runtime.ensureReady();
693
- }
694
-
695
-
696
- readActiveAlarms(msg, nodeId) {
697
- try {
698
- var result = registry.getActiveAlarms(this.node)
699
- const msg2 = {
700
- payload: result
701
- }
702
-
703
- process.send({
704
- type: "send",
705
- data: msg2,
706
- nodeId: nodeId
707
- });
708
-
709
- process.send({
710
- type: "status",
711
- data: {
712
- fill: "green",
713
- shape: "dot",
714
- text: result.paths.length > 1 ? "read " + result.paths.length + " tags" : "read " + result.paths[0]
715
- },
716
- nodeId: nodeId
717
- });
718
- } catch {
719
-
720
- }
721
- }
722
-
723
- }
724
-
725
- /**
726
- * Instância única do processo
727
- */
728
- const serverProcess = new OpcUaServerProcess();
729
-
730
- /**
731
- * Comunicação com processo pai (Node-RED)
732
- */
733
- process.on("message", async (msg) => {
734
- try {
735
- switch (msg.type) {
736
- case "createServer":
737
- await serverProcess.create(msg.settings, msg.nodeId);
738
- break;
739
-
740
- case "stopServer":
741
-
742
- await serverProcess.stop(msg.nodeId);
743
- break;
744
-
745
- case "updateServer":
746
- await serverProcess.update(msg.msg.payload, msg.nodeId);
747
- break;
748
-
749
- case "writeTagServer":
750
-
751
- serverProcess.writeFromPayload(msg.msg, msg.nodeId)
752
- break;
753
-
754
- case "writeEventServer":
755
-
756
- serverProcess.writeEventFromPayload(msg.msg, msg.nodeId)
757
- break;
758
-
759
-
760
-
761
- case "readTagServer":
762
-
763
- serverProcess.readFromPayload(msg.msg, msg.nodeId)
764
- break;
765
-
766
- case "registerMethodInput":
767
- serverProcess.registerMethodInput(msg.node.methodName, msg.nodeId)
768
- break;
769
-
770
- case "handleMethodOutput":
771
- serverProcess.handleMethodOutput(msg.msg, msg.nodeId)
772
- break;
773
-
774
- case "buildServerSnapshot":
775
- OpcUaServerStatusNode(serverProcess.node, msg.msg, msg.nodeId)
776
- break
777
- case "eventsServer":
778
- eventsServer(serverProcess.node, msg.node, msg.nodeId)
779
- break;
780
- case "readActiveAlarms":
781
- serverProcess.readActiveAlarms(msg.msg, msg.nodeId)
782
- break;
783
-
784
-
785
-
786
- default:
787
- console.warn("Unknown message type2:", msg.type);
788
- }
789
- } catch (error) {
790
- console.error("Process message error:", error);
791
-
792
- process.send({
793
- type: "error",
794
- data: error.message
795
- });
796
- }
797
- });
798
-
799
- /**
800
- * Segurança: captura erros não tratados
801
- */
802
- process.on("uncaughtException", (err) => {
803
- console.error("Uncaught Exception:", err);
804
-
805
- process.send({
806
- type: "error",
807
- data: "Uncaught Exception: " + err.message,
808
- nodeId: nodeId
809
- });
810
- });
811
-
812
- process.on("unhandledRejection", (reason) => {
813
- console.error("Unhandled Rejection:", reason);
814
-
815
- process.send({
816
- type: "error",
817
- data: "Unhandled Rejection: " + (reason?.message || reason),
818
- nodeId: nodeId
819
- });
1
+ "use strict";
2
+
3
+
4
+
5
+ const {
6
+ OPCUAServer,
7
+ UserTokenType,
8
+ buildApplicationUri
9
+ } = require("./opcua-constants");
10
+
11
+ const { OpcUaAddressSpaceBuilder } = require("./opcua-address-space-builder");
12
+ const { OpcUaServerRuntime } = require("./opcua-server-runtime");
13
+ const { OpcUaServerConfigParser } = require("./opcua-config");
14
+ const { resolveRegisteredServer } = require("./server-node-utils");
15
+ const { OpcUaServerStatusNode } = require("./opcua-server-status-child")
16
+ const { eventsServer } = require("./opcua-server-events-child")
17
+ const registry = require("../opcua-server-registry");
18
+
19
+ /**
20
+ * Classe responsável por gerenciar TODO o ciclo de vida do servidor OPC UA
21
+ */
22
+ class OpcUaServerProcess {
23
+ constructor() {
24
+ this.node = {};
25
+ this.runtime = null;
26
+ this.parser = new OpcUaServerConfigParser(this.node);
27
+ this.lifecyclePromise = null;
28
+ this.isRunning = false;
29
+ }
30
+
31
+ /**
32
+ * Cria e inicia o servidor
33
+ */
34
+ async create(settings, nodeId) {
35
+ if (this.isRunning) {
36
+ throw new Error("Server already running");
37
+ }
38
+
39
+ this.node.name = settings.name;
40
+ this.node.serverName = settings.serverName;
41
+ this.node.server = null;
42
+ this.node.namespace = null;
43
+
44
+ this.runtime = new OpcUaServerRuntime({
45
+ node: this.node,
46
+ registry,
47
+ settings
48
+ });
49
+
50
+ this.node.runtime = this.runtime;
51
+
52
+ this.node.readValueByPath = (path) => {
53
+ return this.runtime.readValueByPath(path);
54
+ };
55
+
56
+ this.node.readValueByNodeId = (nodeId) => {
57
+ return this.runtime.readValueByNodeId(nodeId);
58
+ };
59
+
60
+ this.node.readValue = (identifierType, identifier) => {
61
+ return this.runtime.readValue(identifierType, identifier);
62
+ };
63
+
64
+ this.node.writeEventByPath = (valuePayload) => {
65
+ return this.runtime.writeEventByPath(valuePayload);
66
+ };
67
+
68
+ this.node.writeValueByPath = (path, value) => {
69
+ return this.runtime.writeValueByPath(path, value);
70
+ };
71
+
72
+ this.node.writeValueByNodeId = (nodeId, value) => {
73
+ return this.runtime.writeValueByNodeId(nodeId, value);
74
+ };
75
+
76
+ this.node.writeValue = (identifierType, identifier, value) => {
77
+ return this.runtime.writeValue(identifierType, identifier, value);
78
+ };
79
+
80
+ try {
81
+ this.lifecyclePromise = this.runtime.start();
82
+ await this.lifecyclePromise;
83
+
84
+ this.node.server = this.runtime.server;
85
+ this.node.namespace = this.runtime.namespace;
86
+
87
+ this.isRunning = true;
88
+
89
+ const endpointUrl = this.runtime.getEndpointUrl();
90
+
91
+ process.send({
92
+ type: "status",
93
+ data: {
94
+ fill: "green",
95
+ shape: "dot",
96
+ text: endpointUrl
97
+ ? "running " + endpointUrl
98
+ : "running"
99
+ },
100
+ nodeId: nodeId
101
+ });
102
+
103
+
104
+ } catch (error) {
105
+ this.isRunning = false;
106
+ console.error("Failed to start OPC UA server:", error);
107
+
108
+ process.send({
109
+ type: "error",
110
+ data: "Failed to start OPC UA server: " + error.message
111
+ });
112
+
113
+ process.send({
114
+ type: "status",
115
+ data: {
116
+ fill: "red",
117
+ shape: "dot",
118
+ text: "Failed to start OPC UA server"
119
+ },
120
+ nodeId: nodeId
121
+ });
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Para o servidor
127
+ */
128
+ async stop(nodeId) {
129
+ try {
130
+ if (!this.runtime) return;
131
+
132
+ if (this.lifecyclePromise) {
133
+ await this.lifecyclePromise.catch(() => { });
134
+ this.lifecyclePromise = null;
135
+ }
136
+
137
+ await this.runtime.stop();
138
+
139
+ this.node.server = null;
140
+ this.node.namespace = null;
141
+ this.isRunning = false;
142
+
143
+ process.send({
144
+ type: "status",
145
+ data: {
146
+ fill: "red",
147
+ shape: "ring",
148
+ text: "stopped"
149
+ },
150
+ nodeId: nodeId
151
+ });
152
+
153
+
154
+ } catch (error) {
155
+ console.error("Failed to stop OPC UA server:", error);
156
+
157
+ process.send({
158
+ type: "error",
159
+ data: "Failed to stop OPC UA server: " + error.message
160
+ });
161
+ }
162
+ }
163
+
164
+ readFromPayload(msg, nodeId) {
165
+
166
+ try {
167
+
168
+ const server = this.node.runtime
169
+ const payload = msg ? msg.payload : undefined;
170
+ const target = msg && msg.opcuaServerIo ? msg.opcuaServerIo : {};
171
+ const identifierType = this.resolveIdentifierType(target);
172
+
173
+ let result = {}
174
+
175
+ if (Array.isArray(payload)) {
176
+ if (!payload.length) {
177
+ throw new Error("msg.payload array does not contain any items");
178
+ }
179
+
180
+ result = {
181
+ payload: payload.map((item) => this.readPayloadItem(identifierType, item)),
182
+ identifiers: payload.map((item) => this.resolvePayloadItemIdentifier(item))
183
+ };
184
+ } else if (payload && typeof payload === "object" && !Array.isArray(payload)) {
185
+ const identifiers = Object.keys(payload);
186
+ if (!identifiers.length) {
187
+ throw new Error("msg.payload object does not contain any " + this.getIdentifierLabel(identifierType));
188
+ }
189
+
190
+ const resultPayload = {};
191
+ identifiers.forEach((identifier) => {
192
+ try {
193
+ resultPayload[identifier] = server.readValue(identifierType, identifier);
194
+ } catch (error) {
195
+ resultPayload[identifier] = undefined;
196
+ }
197
+ });
198
+
199
+ result = {
200
+ payload: resultPayload,
201
+ identifiers: identifiers
202
+ };
203
+ } else {
204
+ const identifier = this.resolveIdentifier(target);
205
+ result = {
206
+ payload: server.readValue(identifierType, identifier),
207
+ identifiers: [identifier]
208
+ };
209
+
210
+ }
211
+
212
+
213
+ msg.opcua = msg.opcua || {};
214
+ msg.payload = result.payload;
215
+ this.assignReadMetadata(msg, identifierType, result.identifiers);
216
+
217
+
218
+
219
+ if (result.identifiers.length === 1) {
220
+ msg.topic = result.identifiers[0];
221
+ }
222
+
223
+
224
+
225
+ process.send({
226
+ type: "send",
227
+ data: msg,
228
+ nodeId: nodeId
229
+ });
230
+
231
+ process.send({
232
+ type: "status",
233
+ data: {
234
+ fill: "green",
235
+ shape: "dot",
236
+ text: result.identifiers.length > 1 ? "read " + result.identifiers.length + " tags" : "read " + result.identifiers[0]
237
+ },
238
+ nodeId: nodeId
239
+ });
240
+
241
+ } catch (error) {
242
+
243
+ process.send({
244
+ type: "error",
245
+ data: { fill: "red", shape: "ring", text: "failed read" },
246
+ error: error.message,
247
+ nodeId: nodeId
248
+ });
249
+ }
250
+
251
+ }
252
+
253
+ writeEventFromPayload(msg, nodeId) {
254
+ try {
255
+ var writtenPaths = null
256
+ const payload = msg ? msg.payload : undefined;
257
+
258
+ writtenPaths = payload
259
+
260
+ if (payload && Array.isArray(payload)) {
261
+
262
+
263
+
264
+ payload.forEach((valuePayload) => {
265
+ this.node.writeEventByPath(valuePayload);
266
+ });
267
+
268
+
269
+ } else if (payload && typeof payload === "object" && !Array.isArray(payload)) {
270
+
271
+
272
+ this.node.writeEventByPath(payload);
273
+
274
+
275
+
276
+ }
277
+
278
+ process.send({
279
+ type: "send",
280
+ data: msg,
281
+ nodeId: nodeId
282
+ });
283
+
284
+ process.send({
285
+ type: "status",
286
+ data: {
287
+ fill: "green",
288
+ shape: "dot",
289
+ text: writtenPaths.length > 1 ? "write " + writtenPaths.length + " events" : "Event " + writtenPaths.nodePath
290
+ },
291
+ nodeId: nodeId
292
+ });
293
+
294
+
295
+ } catch (error) {
296
+
297
+ process.send({
298
+ type: "error",
299
+ data: { fill: "red", shape: "ring", text: "failed write" },
300
+ error: error.message,
301
+ nodeId: nodeId
302
+ });
303
+ }
304
+
305
+
306
+
307
+ //return [path];
308
+ }
309
+
310
+
311
+ writeFromPayload(msg, nodeId) {
312
+ try {
313
+ let writtenPaths = null;
314
+ let payload = msg ? msg.payload : undefined;
315
+
316
+ const target = msg && msg.opcuaServerIo ? msg.opcuaServerIo : {};
317
+ const identifierType = this.resolveIdentifierType(target);
318
+
319
+ // Buffer serializado pelo IPC
320
+ if (
321
+ payload &&
322
+ typeof payload === "object" &&
323
+ payload.type === "Buffer" &&
324
+ Array.isArray(payload.data)
325
+ ) {
326
+ payload = Buffer.from(payload.data);
327
+ }
328
+
329
+ const dataType =
330
+ target.dataType ||
331
+ target.type ||
332
+ target.builtInType ||
333
+ "";
334
+
335
+ const isByteString =
336
+ typeof dataType === "string" &&
337
+ dataType.toLowerCase() === "bytestring";
338
+
339
+
340
+
341
+ // Buffer ou Uint8Array
342
+ if (Buffer.isBuffer(payload) || payload instanceof Uint8Array) {
343
+
344
+ const identifier = this.resolveIdentifier(target);
345
+
346
+ this.node.writeValue(
347
+ identifierType,
348
+ identifier,
349
+ Buffer.isBuffer(payload)
350
+ ? payload
351
+ : Buffer.from(payload)
352
+ );
353
+
354
+ writtenPaths = [identifier];
355
+ }
356
+
357
+ // Array de números
358
+ else if (
359
+ Array.isArray(payload) &&
360
+ payload.every(item => typeof item === "number")
361
+ ) {
362
+
363
+ const identifier = this.resolveIdentifier(target);
364
+
365
+ this.node.writeValue(
366
+ identifierType,
367
+ identifier,
368
+ isByteString
369
+ ? Buffer.from(payload)
370
+ : payload
371
+ );
372
+
373
+ writtenPaths = [identifier];
374
+ }
375
+
376
+ // Array de objetos
377
+ else if (Array.isArray(payload)) {
378
+
379
+ if (!payload.length) {
380
+ throw new Error("msg.payload array does not contain any items");
381
+ }
382
+
383
+ payload.forEach(item => {
384
+ this.writePayloadItem(identifierType, item);
385
+ });
386
+
387
+ writtenPaths = payload.map(item =>
388
+ this.resolvePayloadItemIdentifier(item)
389
+ );
390
+ }
391
+
392
+ // Objeto { path: value }
393
+ else if (
394
+ payload &&
395
+ typeof payload === "object" &&
396
+ !Array.isArray(payload)
397
+ ) {
398
+
399
+ const identifiers = Object.keys(payload);
400
+
401
+ if (!identifiers.length) {
402
+ throw new Error(
403
+ "msg.payload object does not contain any " +
404
+ this.getIdentifierLabel(identifierType)
405
+ );
406
+ }
407
+
408
+ identifiers.forEach(identifier => {
409
+ this.node.writeValue(
410
+ identifierType,
411
+ identifier,
412
+ payload[identifier]
413
+ );
414
+ });
415
+
416
+ writtenPaths = identifiers;
417
+ }
418
+
419
+ // Valor simples
420
+ else {
421
+
422
+ const identifier = this.resolveIdentifier(target);
423
+
424
+ this.node.writeValue(
425
+ identifierType,
426
+ identifier,
427
+ payload
428
+ );
429
+
430
+ writtenPaths = [identifier];
431
+ }
432
+
433
+ msg.opcua = msg.opcua || {};
434
+
435
+ this.assignWriteMetadata(
436
+ msg,
437
+ identifierType,
438
+ writtenPaths
439
+ );
440
+
441
+
442
+ if (writtenPaths.length === 1) {
443
+ msg.topic = writtenPaths[0];
444
+ }
445
+
446
+ process.send({
447
+ type: "send",
448
+ data: msg,
449
+ nodeId
450
+ });
451
+
452
+ process.send({
453
+ type: "status",
454
+ data: {
455
+ fill: "green",
456
+ shape: "dot",
457
+ text:
458
+ writtenPaths.length > 1
459
+ ? `write ${writtenPaths.length} tags`
460
+ : `write ${writtenPaths[0]}`
461
+ },
462
+ nodeId
463
+ });
464
+
465
+ } catch (error) {
466
+
467
+ process.send({
468
+ type: "error",
469
+ data: {
470
+ fill: "red",
471
+ shape: "ring",
472
+ text: "failed write"
473
+ },
474
+ error: error.message,
475
+ nodeId
476
+ });
477
+ }
478
+ }
479
+
480
+ resolveIdentifierType(target) {
481
+ return target && target.identifierType === "nodeId" ? "nodeId" : "path";
482
+ }
483
+
484
+ resolveIdentifier(target) {
485
+ const identifierType = this.resolveIdentifierType(target);
486
+ if (identifierType === "nodeId") {
487
+ const nodeId = String(target.tagNodeId || "").trim();
488
+ if (!nodeId) {
489
+ throw new Error("No tag nodeId configured");
490
+ }
491
+ return nodeId;
492
+ }
493
+
494
+ const path = String(target.tagPath || "").trim();
495
+ if (!path) {
496
+ throw new Error("No tag path configured");
497
+ }
498
+
499
+ return path;
500
+ }
501
+
502
+ getIdentifierLabel(identifierType) {
503
+ return identifierType === "nodeId" ? "nodeIds" : "tag paths";
504
+ }
505
+
506
+ resolvePayloadItemIdentifier(item) {
507
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
508
+ throw new Error("Each payload item must be an object");
509
+ }
510
+
511
+ const identifier = String(item.path || "").trim();
512
+ if (!identifier) {
513
+ throw new Error("Each payload item must contain a path");
514
+ }
515
+
516
+ return identifier;
517
+ }
518
+
519
+ readPayloadItem(identifierType, item) {
520
+ const identifier = this.resolvePayloadItemIdentifier(item);
521
+ return {
522
+ name: item.name,
523
+ path: identifier,
524
+ value: this.node.readValue(identifierType, identifier)
525
+ };
526
+ }
527
+
528
+ writePayloadItem(identifierType, item) {
529
+ const identifier = this.resolvePayloadItemIdentifier(item);
530
+ this.node.writeValue(identifierType, identifier, item.value);
531
+ return {
532
+ name: item.name,
533
+ path: identifier,
534
+ value: item.value
535
+ };
536
+ }
537
+
538
+ assignReadMetadata(msg, identifierType, identifiers) {
539
+ msg.opcua.identifierType = identifierType;
540
+ if (identifierType === "nodeId") {
541
+ msg.opcua.readNodeIds = identifiers;
542
+ delete msg.opcua.readPaths;
543
+ if (identifiers.length === 1) {
544
+ msg.opcua.tagNodeId = identifiers[0];
545
+ delete msg.opcua.tagPath;
546
+ }
547
+ return;
548
+ }
549
+
550
+ msg.opcua.readPaths = identifiers;
551
+ delete msg.opcua.readNodeIds;
552
+ if (identifiers.length === 1) {
553
+ msg.opcua.tagPath = identifiers[0];
554
+ delete msg.opcua.tagNodeId;
555
+ }
556
+ }
557
+
558
+ assignWriteMetadata(msg, identifierType, identifiers) {
559
+ msg.opcua.identifierType = identifierType;
560
+ if (identifierType === "nodeId") {
561
+ msg.opcua.writtenNodeIds = identifiers;
562
+ delete msg.opcua.writtenPaths;
563
+ if (identifiers.length === 1) {
564
+ msg.opcua.tagNodeId = identifiers[0];
565
+ delete msg.opcua.tagPath;
566
+ }
567
+ return;
568
+ }
569
+
570
+ msg.opcua.writtenPaths = identifiers;
571
+ delete msg.opcua.writtenNodeIds;
572
+ if (identifiers.length === 1) {
573
+ msg.opcua.tagPath = identifiers[0];
574
+ delete msg.opcua.tagNodeId;
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Atualiza a árvore do servidor
580
+ */
581
+ async update(payload, nodeId) {
582
+ try {
583
+
584
+
585
+ process.send({
586
+ type: "status",
587
+ data: {
588
+ fill: "yellow",
589
+ shape: "dot",
590
+ text: "updating items"
591
+ },
592
+ nodeId: nodeId
593
+ });
594
+
595
+ await this.ensureReady();
596
+
597
+ const nextTree = this.parser.normalizeTreeConfig(payload);
598
+
599
+ await this.runtime.updateTree(nextTree);
600
+
601
+ const endpointUrl = await this.runtime.getEndpointUrl();
602
+
603
+
604
+ process.send({
605
+ type: "status",
606
+ data: {
607
+ fill: "green",
608
+ shape: "dot",
609
+ text: endpointUrl
610
+ ? "running " + endpointUrl
611
+ : "running"
612
+ },
613
+ nodeId: nodeId
614
+ });
615
+
616
+
617
+ } catch (error) {
618
+
619
+
620
+ process.send({
621
+ type: "errorUpdateServer",
622
+ data: error.message,
623
+ nodeId: nodeId
624
+ });
625
+
626
+ process.send({
627
+ type: "status",
628
+ data: {
629
+ fill: "red",
630
+ shape: "dot",
631
+ text: "error update"
632
+ },
633
+ nodeId: nodeId
634
+ });
635
+ }
636
+ }
637
+
638
+
639
+ /**
640
+ * metodos opc ua
641
+ */
642
+ registerMethodInput(methodName, nodeId) {
643
+
644
+ if (methodName) {
645
+
646
+ registry.registerMethodHandler(methodName, nodeId);
647
+ } else {
648
+
649
+
650
+ }
651
+
652
+
653
+ }
654
+
655
+
656
+ handleMethodOutput(msg, nodeId) {
657
+
658
+ resolveRegisteredServer(this.node, msg, registry);
659
+
660
+ if (!msg._callId) {
661
+ throw new Error("Missing _callId for OPC UA method response");
662
+ }
663
+
664
+ registry.resolveMethodCall(msg._callId, msg.payload);
665
+
666
+
667
+ process.send({
668
+ type: "status",
669
+ data: {
670
+ fill: "green",
671
+ shape: "dot",
672
+ text: "response sent"
673
+ },
674
+ nodeId: nodeId
675
+ });
676
+
677
+
678
+ }
679
+
680
+ /**
681
+ * Garante que o servidor está pronto
682
+ */
683
+ async ensureReady() {
684
+ if (!this.runtime) {
685
+ throw new Error("Server not created");
686
+ }
687
+
688
+ if (this.lifecyclePromise) {
689
+ await this.lifecyclePromise;
690
+ }
691
+
692
+ this.runtime.ensureReady();
693
+ }
694
+
695
+
696
+ readActiveAlarms(msg, nodeId) {
697
+ try {
698
+ var result = registry.getActiveAlarms(this.node)
699
+ const msg2 = {
700
+ payload: result
701
+ }
702
+
703
+ process.send({
704
+ type: "send",
705
+ data: msg2,
706
+ nodeId: nodeId
707
+ });
708
+
709
+ process.send({
710
+ type: "status",
711
+ data: {
712
+ fill: "green",
713
+ shape: "dot",
714
+ text: result.paths.length > 1 ? "read " + result.paths.length + " tags" : "read " + result.paths[0]
715
+ },
716
+ nodeId: nodeId
717
+ });
718
+ } catch {
719
+
720
+ }
721
+ }
722
+
723
+ }
724
+
725
+ /**
726
+ * Instância única do processo
727
+ */
728
+ const serverProcess = new OpcUaServerProcess();
729
+
730
+ /**
731
+ * Comunicação com processo pai (Node-RED)
732
+ */
733
+ process.on("message", async (msg) => {
734
+ try {
735
+ switch (msg.type) {
736
+ case "createServer":
737
+ await serverProcess.create(msg.settings, msg.nodeId);
738
+ break;
739
+
740
+ case "stopServer":
741
+
742
+ await serverProcess.stop(msg.nodeId);
743
+ break;
744
+
745
+ case "updateServer":
746
+ await serverProcess.update(msg.msg.payload, msg.nodeId);
747
+ break;
748
+
749
+ case "writeTagServer":
750
+
751
+ serverProcess.writeFromPayload(msg.msg, msg.nodeId)
752
+ break;
753
+
754
+ case "writeEventServer":
755
+
756
+ serverProcess.writeEventFromPayload(msg.msg, msg.nodeId)
757
+ break;
758
+
759
+
760
+
761
+ case "readTagServer":
762
+
763
+ serverProcess.readFromPayload(msg.msg, msg.nodeId)
764
+ break;
765
+
766
+ case "registerMethodInput":
767
+ serverProcess.registerMethodInput(msg.node.methodName, msg.nodeId)
768
+ break;
769
+
770
+ case "handleMethodOutput":
771
+ serverProcess.handleMethodOutput(msg.msg, msg.nodeId)
772
+ break;
773
+
774
+ case "buildServerSnapshot":
775
+ OpcUaServerStatusNode(serverProcess.node, msg.msg, msg.nodeId)
776
+ break
777
+ case "eventsServer":
778
+ eventsServer(serverProcess.node, msg.node, msg.nodeId)
779
+ break;
780
+ case "readActiveAlarms":
781
+ serverProcess.readActiveAlarms(msg.msg, msg.nodeId)
782
+ break;
783
+
784
+
785
+
786
+ default:
787
+ console.warn("Unknown message type2:", msg.type);
788
+ }
789
+ } catch (error) {
790
+ console.error("Process message error:", error);
791
+
792
+ process.send({
793
+ type: "error",
794
+ data: error.message
795
+ });
796
+ }
797
+ });
798
+
799
+ /**
800
+ * Segurança: captura erros não tratados
801
+ */
802
+ process.on("uncaughtException", (err) => {
803
+ console.error("Uncaught Exception:", err);
804
+
805
+ process.send({
806
+ type: "error",
807
+ data: "Uncaught Exception: " + err.message,
808
+ nodeId: nodeId
809
+ });
810
+ });
811
+
812
+ process.on("unhandledRejection", (reason) => {
813
+ console.error("Unhandled Rejection:", reason);
814
+
815
+ process.send({
816
+ type: "error",
817
+ data: "Unhandled Rejection: " + (reason?.message || reason),
818
+ nodeId: nodeId
819
+ });
820
820
  });