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