@vitormnm/node-red-simple-opcua 1.4.1 → 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.
Files changed (32) hide show
  1. package/README.md +5 -0
  2. package/client/icons/opcua.svg +132 -132
  3. package/client/lib/opcua-client-browser.js +330 -330
  4. package/client/lib/opcua-client-method-service.js +88 -88
  5. package/client/lib/opcua-client-read-service.js +15 -15
  6. package/client/lib/opcua-client-subscription-id-service.js +24 -24
  7. package/client/lib/opcua-client-subscription-service.js +170 -170
  8. package/client/lib/opcua-client-write-service.js +146 -146
  9. package/client/opcua-client-config.html +80 -80
  10. package/client/opcua-client.html +140 -140
  11. package/client/opcua-client.js +1 -7
  12. package/client/view/opcua-client.js +1140 -1140
  13. package/icons/opcua.svg +132 -132
  14. package/icons/opcua2.svg +132 -132
  15. package/package.json +42 -42
  16. package/resources/bmc-button.svg +22 -0
  17. package/server/icons/opcua.svg +132 -132
  18. package/server/lib/opcua-address-space-alarm.js +341 -341
  19. package/server/lib/opcua-address-space-builder.js +1484 -1484
  20. package/server/lib/opcua-config.js +546 -546
  21. package/server/lib/opcua-constants.js +109 -109
  22. package/server/lib/opcua-server-events-child.js +139 -139
  23. package/server/lib/opcua-server-runtime-child.js +819 -819
  24. package/server/lib/opcua-server-runtime.js +311 -311
  25. package/server/lib/opcua-server-status-child.js +187 -187
  26. package/server/lib/server-node-utils.js +16 -16
  27. package/server/opcua-server-io.html +346 -346
  28. package/server/opcua-server-io.js +496 -496
  29. package/server/opcua-server-registry.js +270 -270
  30. package/server/opcua-server.css +265 -265
  31. package/server/opcua-server.html +1643 -1643
  32. package/client/testClient.json +0 -18
@@ -1,496 +1,496 @@
1
- "use strict";
2
-
3
- module.exports = function (RED) {
4
- const registry = require("./opcua-server-registry");
5
-
6
- RED.httpAdmin.get("/opcua-server-io/servers", RED.auth.needsPermission("flows.read"), function (req, res) {
7
- try {
8
- res.json(registry.listActiveServers());
9
-
10
- } catch (error) {
11
- res.status(500).json({ error: error.message });
12
- }
13
- });
14
-
15
-
16
- const path = require("path");
17
-
18
-
19
-
20
-
21
-
22
-
23
- function OpcUaServerIoNode(config) {
24
- RED.nodes.createNode(this, config);
25
- const node = this;
26
-
27
-
28
- node.name = (config.name || "").trim();
29
- node.serverRef = (config.serverRef || "").trim();
30
- node.mode = config.mode || "read";
31
- node.identifierType = config.identifierType === "nodeId" ? "nodeId" : "path";
32
- node.tagPath = (config.tagPath || "").trim();
33
- node.tagNodeId = (config.tagNodeId || "").trim();
34
- node.methodName = (config.methodName || "").trim();
35
- node.intervalMs = normalizeInterval(config.intervalMs);
36
- node._childListenerAttached = false;
37
- node._attachedChild = null;
38
- node._childListenerRetry = null;
39
-
40
- const handler = (msg) => {
41
- onMessage(msg, node);
42
- };
43
- node._messageHandler = handler;
44
-
45
- attachChildListener(node, handler);
46
- ensureChildListener(node, handler);
47
-
48
- if (node.mode === "method-input") {
49
- registerMethodInput(node);
50
- }
51
- if (node.mode === "events") {
52
- registerEvents(node, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
53
- }
54
- if (node.mode === "status") {
55
- requestSnapshot(node, null, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
56
- }
57
-
58
-
59
- node.on("input", async function (msg, send, done) {
60
- try {
61
- send = send || function () {
62
- node.send.apply(node, arguments);
63
- };
64
-
65
- if (node.mode === "read") {
66
- await handleRead(node, msg, send);
67
- }
68
-
69
- if (node.mode === "write") {
70
- await handleWrite(node, msg, send);
71
- }
72
-
73
- if (node.mode === "event") {
74
- await handleEvent(node, msg, send);
75
- }
76
-
77
- if (node.mode === "activeAlarms") {
78
- await handleActiveAlarms(node, msg, send);
79
- }
80
-
81
- if (node.mode === "method-output") {
82
- await handleMethodOutput(node, msg, send);
83
- done();
84
- return;
85
- }
86
- if (node.mode === "events") {
87
- await registerEvents(node, { waitForServer: true, timeoutMs: 5000 });
88
- }
89
- if (node.mode === "status") {
90
- await requestSnapshot(node, msg, { waitForServer: true, timeoutMs: 5000 });
91
- }
92
-
93
- done();
94
- } catch (error) {
95
- node.status({ fill: "red", shape: "ring", text: node.mode + " failed" });
96
- done(error);
97
- }
98
- });
99
-
100
- node.on("close", function () {
101
- if (node._childListenerRetry) {
102
- clearInterval(node._childListenerRetry);
103
- node._childListenerRetry = null;
104
- }
105
-
106
- detachChildListener(node, handler);
107
-
108
-
109
- if (node.mode === "method-input") {
110
- registry.unregisterMethodHandler(node.methodName);
111
- }
112
- if (node.mode === "events") {
113
- registry.unregisterAccessListener(node.id);
114
- }
115
- });
116
- }
117
-
118
- function restoreBuffers(value) {
119
- // Formato exato que o Node.js IPC gera ao serializar um Buffer
120
- if (
121
- value !== null &&
122
- typeof value === "object" &&
123
- value.type === "Buffer" &&
124
- Array.isArray(value.data)
125
- ) {
126
- return Buffer.from(value.data);
127
- }
128
-
129
- return value;
130
- }
131
-
132
- function onMessage(msg, node) {
133
- if (msg.nodeId == node.id) {
134
-
135
- if (msg.type === "status") {
136
-
137
- node.status(msg.data);
138
- }
139
-
140
- if (msg.type === "send") {
141
- const data = msg.data;
142
-
143
- if (data && data.payload !== undefined) {
144
- data.payload = restoreBuffers(data.payload);
145
- }
146
-
147
- node.send(data);
148
- }
149
-
150
- if (msg.type === "error") {
151
- node.status(msg.data);
152
- node.error(msg.error);
153
- }
154
-
155
- if (msg.type === "sendMethod") {
156
-
157
- node.status({
158
- fill: "blue",
159
- shape: "dot",
160
- text: "method called"
161
- });
162
-
163
- node.send({
164
- topic: msg.data.nodeId,
165
- payload: msg.data.inputArguments,
166
- opcua: {
167
- server: msg.data.serverName,
168
- method: msg.data.methodName,
169
- data: msg.data
170
- },
171
- _callId: msg.data.callId
172
- });
173
- }
174
-
175
- }
176
- }
177
-
178
-
179
- function childControl(node) {
180
-
181
- const child = registry.resolveChild(node.serverRef)
182
-
183
- child.on("message", (msg) => {
184
-
185
- if (msg.type === "sendMethod") {
186
-
187
- node.status({
188
- fill: "blue",
189
- shape: "dot",
190
- text: "method called"
191
- });
192
-
193
- node.send({
194
- topic: msg.data.nodeId,
195
- payload: msg.data.inputArguments,
196
- opcua: {
197
- server: msg.data.serverName,
198
- method: msg.data.methodName,
199
- data: msg.data
200
- },
201
- _callId: msg.data.callId
202
- });
203
- }
204
- });
205
-
206
-
207
- // if (child && !child._listenerAttached) {
208
- // child._listenerAttached = true;
209
-
210
- // }
211
- }
212
-
213
- async function registerMethodInput(node) {
214
- await sendToChild(node, {
215
- type: "registerMethodInput",
216
- node: node,
217
- nodeId: node.id
218
-
219
- }, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
220
- }
221
-
222
- async function registerEvents(node, options) {
223
- return sendToChild(node, {
224
- type: "eventsServer",
225
- node: node,
226
- nodeId: node.id
227
- }, options);
228
- }
229
-
230
- async function requestSnapshot(node, msg, options) {
231
- return sendToChild(node, {
232
- type: "buildServerSnapshot",
233
- node: node,
234
- msg: msg,
235
- nodeId: node.id
236
- }, options);
237
- }
238
-
239
-
240
- async function handleWrite(node, msg) {
241
-
242
- // let serverName = null
243
- // if (msg.opcua.server === undefined) {
244
- // serverName = node.serverRef
245
- // } else {
246
- // serverName = msg.opcua.server
247
- // }
248
-
249
- await sendToChild(node, {
250
- type: "writeTagServer",
251
- msg: buildIoMessage(node, msg),
252
- node: node.id,
253
- nodeId: node.id,
254
- }, { waitForServer: true, timeoutMs: 5000 });
255
- }
256
-
257
- async function handleEvent(node, msg) {
258
-
259
- // let serverName = null
260
- // if (msg.opcua.server === undefined) {
261
- // serverName = node.serverRef
262
- // } else {
263
- // serverName = msg.opcua.server
264
- // }
265
-
266
- await sendToChild(node, {
267
- type: "writeEventServer",
268
- msg: msg,
269
- node: node.id,
270
- nodeId: node.id,
271
- }, { waitForServer: true, timeoutMs: 5000 });
272
- }
273
-
274
- async function handleActiveAlarms(node, msg) {
275
-
276
- // let serverName = null
277
- // if (msg.opcua.server === undefined) {
278
- // serverName = node.serverRef
279
- // } else {
280
- // serverName = msg.opcua.server
281
- // }
282
-
283
- await sendToChild(node, {
284
- type: "readActiveAlarms",
285
- msg: msg,
286
- node: node.id,
287
- nodeId: node.id,
288
- }, { waitForServer: true, timeoutMs: 5000 });
289
- }
290
-
291
-
292
- async function handleRead(node, msg) {
293
- // const server = resolveRegisteredServer(node, msg, registry);
294
- msg.opcua = msg.opcua || {};
295
- // msg.opcua.server = server.name || server.serverName || server.id;
296
-
297
- await sendToChild(node, {
298
- type: "readTagServer",
299
- msg: buildIoMessage(node, msg),
300
- node: node.id,
301
- nodeId: node.id,
302
- }, { waitForServer: true, timeoutMs: 5000 });
303
- }
304
-
305
-
306
-
307
- async function handleMethodOutput(node, msg, send) {
308
- await sendToChild(node, {
309
- type: "handleMethodOutput",
310
- msg: msg,
311
- node: node.id,
312
- nodeId: node.id,
313
- }, { waitForServer: true, timeoutMs: 5000 });
314
- }
315
-
316
- function buildIoMessage(node, msg) {
317
- const nextMsg = Object.assign({}, msg);
318
- nextMsg.opcuaServerIo = {
319
- identifierType: node.identifierType === "nodeId" ? "nodeId" : "path",
320
- tagPath: String(node.tagPath || "").trim(),
321
- tagNodeId: String(node.tagNodeId || "").trim()
322
- };
323
- return nextMsg;
324
- }
325
-
326
- function attachChildListener(node, handler) {
327
- const child = registry.resolveChild(node.serverRef);
328
- if (!child) {
329
- detachChildListener(node, handler);
330
- node.status({ fill: "yellow", shape: "ring", text: "waiting for server" });
331
- return false;
332
- }
333
-
334
- if (node._attachedChild === child && node._childListenerAttached) {
335
- return true;
336
- }
337
-
338
- detachChildListener(node, handler);
339
- child.on("message", handler);
340
- child.once("close", function () {
341
- if (node._attachedChild === child) {
342
- node._attachedChild = null;
343
- node._childListenerAttached = false;
344
- ensureChildListener(node, handler);
345
- }
346
- });
347
- child.once("exit", function () {
348
- if (node._attachedChild === child) {
349
- node._attachedChild = null;
350
- node._childListenerAttached = false;
351
- ensureChildListener(node, handler);
352
- }
353
- });
354
- child.once("disconnect", function () {
355
- if (node._attachedChild === child) {
356
- node._attachedChild = null;
357
- node._childListenerAttached = false;
358
- ensureChildListener(node, handler);
359
- }
360
- });
361
- node._attachedChild = child;
362
- node._childListenerAttached = true;
363
-
364
- if (node.mode === "method-input") {
365
- registerMethodInput(node);
366
- }
367
-
368
- return true;
369
- }
370
-
371
- function ensureChildListener(node, handler) {
372
- if (node._childListenerAttached || node._childListenerRetry) {
373
- return;
374
- }
375
-
376
- node._childListenerRetry = setInterval(function () {
377
- if (attachChildListener(node, handler)) {
378
- clearInterval(node._childListenerRetry);
379
- node._childListenerRetry = null;
380
- }
381
- }, 1000);
382
- }
383
-
384
- function detachChildListener(node, handler) {
385
- const child = node._attachedChild;
386
- if (!child) {
387
- node._childListenerAttached = false;
388
- node._attachedChild = null;
389
- return;
390
- }
391
-
392
- child.removeListener("message", handler);
393
- node._childListenerAttached = false;
394
- node._attachedChild = null;
395
- }
396
-
397
- function delay(ms) {
398
- return new Promise(function (resolve) {
399
- setTimeout(resolve, ms);
400
- });
401
- }
402
-
403
- async function waitForChild(node, handler, timeoutMs) {
404
- var startedAt = Date.now();
405
- var timeout = timeoutMs || 5000;
406
-
407
- while ((Date.now() - startedAt) < timeout) {
408
- if (attachChildListener(node, handler)) {
409
- var child = registry.resolveChild(node.serverRef);
410
- if (child) {
411
- return child;
412
- }
413
- }
414
-
415
- await delay(100);
416
- }
417
-
418
- return null;
419
- }
420
-
421
- function normalizeInterval(value) {
422
- const interval = Number(value);
423
- if (!Number.isFinite(interval) || interval <= 0) {
424
- return 500;
425
- }
426
-
427
- return Math.trunc(interval);
428
- }
429
-
430
- async function sendToChild(node, payload, options) {
431
- options = options || {};
432
- var handler = options.handler || node._messageHandler;
433
- if (options.ensureListener !== false) {
434
- attachChildListener(node, handler);
435
- }
436
-
437
- var child = registry.resolveChild(node.serverRef);
438
-
439
- if (!child && options.waitForServer) {
440
- node.status({ fill: "yellow", shape: "ring", text: "waiting for server" });
441
- child = await waitForChild(node, handler, options.timeoutMs);
442
- }
443
-
444
- if (!child) {
445
- const error = new Error("OPC UA server child process is not available for serverRef: " + node.serverRef);
446
- node.status({ fill: "red", shape: "ring", text: "server unavailable" });
447
- if (options.throwOnError === false) {
448
- node.error(error.message);
449
- return false;
450
- }
451
- throw error;
452
- }
453
-
454
- try {
455
- child.send(payload);
456
- if (node.mode !== "method-input") {
457
- node.status({ fill: "green", shape: "dot", text: "connected" });
458
- }
459
- return true;
460
- } catch (error) {
461
- node.status({ fill: "red", shape: "ring", text: "ipc unavailable" });
462
- node._childListenerAttached = false;
463
- node._attachedChild = null;
464
- ensureChildListener(node, handler);
465
- if (options.throwOnError === false) {
466
- node.error(error.message);
467
- return false;
468
- }
469
- throw error;
470
- }
471
- }
472
-
473
-
474
-
475
-
476
- OpcUaServerIoNode.prototype.sendMethodCall = function (call) {
477
- const node = this;
478
-
479
- node.status({
480
- fill: "blue",
481
- shape: "dot",
482
- text: "method called"
483
- });
484
-
485
- node.send({
486
- payload: call.inputArguments,
487
- opcua: {
488
- server: call.serverName,
489
- method: call.methodName
490
- },
491
- _callId: call.callId
492
- });
493
- };
494
-
495
- RED.nodes.registerType("opcua-server-io", OpcUaServerIoNode);
496
- };
1
+ "use strict";
2
+
3
+ module.exports = function (RED) {
4
+ const registry = require("./opcua-server-registry");
5
+
6
+ RED.httpAdmin.get("/opcua-server-io/servers", RED.auth.needsPermission("flows.read"), function (req, res) {
7
+ try {
8
+ res.json(registry.listActiveServers());
9
+
10
+ } catch (error) {
11
+ res.status(500).json({ error: error.message });
12
+ }
13
+ });
14
+
15
+
16
+ const path = require("path");
17
+
18
+
19
+
20
+
21
+
22
+
23
+ function OpcUaServerIoNode(config) {
24
+ RED.nodes.createNode(this, config);
25
+ const node = this;
26
+
27
+
28
+ node.name = (config.name || "").trim();
29
+ node.serverRef = (config.serverRef || "").trim();
30
+ node.mode = config.mode || "read";
31
+ node.identifierType = config.identifierType === "nodeId" ? "nodeId" : "path";
32
+ node.tagPath = (config.tagPath || "").trim();
33
+ node.tagNodeId = (config.tagNodeId || "").trim();
34
+ node.methodName = (config.methodName || "").trim();
35
+ node.intervalMs = normalizeInterval(config.intervalMs);
36
+ node._childListenerAttached = false;
37
+ node._attachedChild = null;
38
+ node._childListenerRetry = null;
39
+
40
+ const handler = (msg) => {
41
+ onMessage(msg, node);
42
+ };
43
+ node._messageHandler = handler;
44
+
45
+ attachChildListener(node, handler);
46
+ ensureChildListener(node, handler);
47
+
48
+ if (node.mode === "method-input") {
49
+ registerMethodInput(node);
50
+ }
51
+ if (node.mode === "events") {
52
+ registerEvents(node, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
53
+ }
54
+ if (node.mode === "status") {
55
+ requestSnapshot(node, null, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
56
+ }
57
+
58
+
59
+ node.on("input", async function (msg, send, done) {
60
+ try {
61
+ send = send || function () {
62
+ node.send.apply(node, arguments);
63
+ };
64
+
65
+ if (node.mode === "read") {
66
+ await handleRead(node, msg, send);
67
+ }
68
+
69
+ if (node.mode === "write") {
70
+ await handleWrite(node, msg, send);
71
+ }
72
+
73
+ if (node.mode === "event") {
74
+ await handleEvent(node, msg, send);
75
+ }
76
+
77
+ if (node.mode === "activeAlarms") {
78
+ await handleActiveAlarms(node, msg, send);
79
+ }
80
+
81
+ if (node.mode === "method-output") {
82
+ await handleMethodOutput(node, msg, send);
83
+ done();
84
+ return;
85
+ }
86
+ if (node.mode === "events") {
87
+ await registerEvents(node, { waitForServer: true, timeoutMs: 5000 });
88
+ }
89
+ if (node.mode === "status") {
90
+ await requestSnapshot(node, msg, { waitForServer: true, timeoutMs: 5000 });
91
+ }
92
+
93
+ done();
94
+ } catch (error) {
95
+ node.status({ fill: "red", shape: "ring", text: node.mode + " failed" });
96
+ done(error);
97
+ }
98
+ });
99
+
100
+ node.on("close", function () {
101
+ if (node._childListenerRetry) {
102
+ clearInterval(node._childListenerRetry);
103
+ node._childListenerRetry = null;
104
+ }
105
+
106
+ detachChildListener(node, handler);
107
+
108
+
109
+ if (node.mode === "method-input") {
110
+ registry.unregisterMethodHandler(node.methodName);
111
+ }
112
+ if (node.mode === "events") {
113
+ registry.unregisterAccessListener(node.id);
114
+ }
115
+ });
116
+ }
117
+
118
+ function restoreBuffers(value) {
119
+ // Formato exato que o Node.js IPC gera ao serializar um Buffer
120
+ if (
121
+ value !== null &&
122
+ typeof value === "object" &&
123
+ value.type === "Buffer" &&
124
+ Array.isArray(value.data)
125
+ ) {
126
+ return Buffer.from(value.data);
127
+ }
128
+
129
+ return value;
130
+ }
131
+
132
+ function onMessage(msg, node) {
133
+ if (msg.nodeId == node.id) {
134
+
135
+ if (msg.type === "status") {
136
+
137
+ node.status(msg.data);
138
+ }
139
+
140
+ if (msg.type === "send") {
141
+ const data = msg.data;
142
+
143
+ if (data && data.payload !== undefined) {
144
+ data.payload = restoreBuffers(data.payload);
145
+ }
146
+
147
+ node.send(data);
148
+ }
149
+
150
+ if (msg.type === "error") {
151
+ node.status(msg.data);
152
+ node.error(msg.error);
153
+ }
154
+
155
+ if (msg.type === "sendMethod") {
156
+
157
+ node.status({
158
+ fill: "blue",
159
+ shape: "dot",
160
+ text: "method called"
161
+ });
162
+
163
+ node.send({
164
+ topic: msg.data.nodeId,
165
+ payload: msg.data.inputArguments,
166
+ opcua: {
167
+ server: msg.data.serverName,
168
+ method: msg.data.methodName,
169
+ data: msg.data
170
+ },
171
+ _callId: msg.data.callId
172
+ });
173
+ }
174
+
175
+ }
176
+ }
177
+
178
+
179
+ function childControl(node) {
180
+
181
+ const child = registry.resolveChild(node.serverRef)
182
+
183
+ child.on("message", (msg) => {
184
+
185
+ if (msg.type === "sendMethod") {
186
+
187
+ node.status({
188
+ fill: "blue",
189
+ shape: "dot",
190
+ text: "method called"
191
+ });
192
+
193
+ node.send({
194
+ topic: msg.data.nodeId,
195
+ payload: msg.data.inputArguments,
196
+ opcua: {
197
+ server: msg.data.serverName,
198
+ method: msg.data.methodName,
199
+ data: msg.data
200
+ },
201
+ _callId: msg.data.callId
202
+ });
203
+ }
204
+ });
205
+
206
+
207
+ // if (child && !child._listenerAttached) {
208
+ // child._listenerAttached = true;
209
+
210
+ // }
211
+ }
212
+
213
+ async function registerMethodInput(node) {
214
+ await sendToChild(node, {
215
+ type: "registerMethodInput",
216
+ node: node,
217
+ nodeId: node.id
218
+
219
+ }, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
220
+ }
221
+
222
+ async function registerEvents(node, options) {
223
+ return sendToChild(node, {
224
+ type: "eventsServer",
225
+ node: node,
226
+ nodeId: node.id
227
+ }, options);
228
+ }
229
+
230
+ async function requestSnapshot(node, msg, options) {
231
+ return sendToChild(node, {
232
+ type: "buildServerSnapshot",
233
+ node: node,
234
+ msg: msg,
235
+ nodeId: node.id
236
+ }, options);
237
+ }
238
+
239
+
240
+ async function handleWrite(node, msg) {
241
+
242
+ // let serverName = null
243
+ // if (msg.opcua.server === undefined) {
244
+ // serverName = node.serverRef
245
+ // } else {
246
+ // serverName = msg.opcua.server
247
+ // }
248
+
249
+ await sendToChild(node, {
250
+ type: "writeTagServer",
251
+ msg: buildIoMessage(node, msg),
252
+ node: node.id,
253
+ nodeId: node.id,
254
+ }, { waitForServer: true, timeoutMs: 5000 });
255
+ }
256
+
257
+ async function handleEvent(node, msg) {
258
+
259
+ // let serverName = null
260
+ // if (msg.opcua.server === undefined) {
261
+ // serverName = node.serverRef
262
+ // } else {
263
+ // serverName = msg.opcua.server
264
+ // }
265
+
266
+ await sendToChild(node, {
267
+ type: "writeEventServer",
268
+ msg: msg,
269
+ node: node.id,
270
+ nodeId: node.id,
271
+ }, { waitForServer: true, timeoutMs: 5000 });
272
+ }
273
+
274
+ async function handleActiveAlarms(node, msg) {
275
+
276
+ // let serverName = null
277
+ // if (msg.opcua.server === undefined) {
278
+ // serverName = node.serverRef
279
+ // } else {
280
+ // serverName = msg.opcua.server
281
+ // }
282
+
283
+ await sendToChild(node, {
284
+ type: "readActiveAlarms",
285
+ msg: msg,
286
+ node: node.id,
287
+ nodeId: node.id,
288
+ }, { waitForServer: true, timeoutMs: 5000 });
289
+ }
290
+
291
+
292
+ async function handleRead(node, msg) {
293
+ // const server = resolveRegisteredServer(node, msg, registry);
294
+ msg.opcua = msg.opcua || {};
295
+ // msg.opcua.server = server.name || server.serverName || server.id;
296
+
297
+ await sendToChild(node, {
298
+ type: "readTagServer",
299
+ msg: buildIoMessage(node, msg),
300
+ node: node.id,
301
+ nodeId: node.id,
302
+ }, { waitForServer: true, timeoutMs: 5000 });
303
+ }
304
+
305
+
306
+
307
+ async function handleMethodOutput(node, msg, send) {
308
+ await sendToChild(node, {
309
+ type: "handleMethodOutput",
310
+ msg: msg,
311
+ node: node.id,
312
+ nodeId: node.id,
313
+ }, { waitForServer: true, timeoutMs: 5000 });
314
+ }
315
+
316
+ function buildIoMessage(node, msg) {
317
+ const nextMsg = Object.assign({}, msg);
318
+ nextMsg.opcuaServerIo = {
319
+ identifierType: node.identifierType === "nodeId" ? "nodeId" : "path",
320
+ tagPath: String(node.tagPath || "").trim(),
321
+ tagNodeId: String(node.tagNodeId || "").trim()
322
+ };
323
+ return nextMsg;
324
+ }
325
+
326
+ function attachChildListener(node, handler) {
327
+ const child = registry.resolveChild(node.serverRef);
328
+ if (!child) {
329
+ detachChildListener(node, handler);
330
+ node.status({ fill: "yellow", shape: "ring", text: "waiting for server" });
331
+ return false;
332
+ }
333
+
334
+ if (node._attachedChild === child && node._childListenerAttached) {
335
+ return true;
336
+ }
337
+
338
+ detachChildListener(node, handler);
339
+ child.on("message", handler);
340
+ child.once("close", function () {
341
+ if (node._attachedChild === child) {
342
+ node._attachedChild = null;
343
+ node._childListenerAttached = false;
344
+ ensureChildListener(node, handler);
345
+ }
346
+ });
347
+ child.once("exit", function () {
348
+ if (node._attachedChild === child) {
349
+ node._attachedChild = null;
350
+ node._childListenerAttached = false;
351
+ ensureChildListener(node, handler);
352
+ }
353
+ });
354
+ child.once("disconnect", function () {
355
+ if (node._attachedChild === child) {
356
+ node._attachedChild = null;
357
+ node._childListenerAttached = false;
358
+ ensureChildListener(node, handler);
359
+ }
360
+ });
361
+ node._attachedChild = child;
362
+ node._childListenerAttached = true;
363
+
364
+ if (node.mode === "method-input") {
365
+ registerMethodInput(node);
366
+ }
367
+
368
+ return true;
369
+ }
370
+
371
+ function ensureChildListener(node, handler) {
372
+ if (node._childListenerAttached || node._childListenerRetry) {
373
+ return;
374
+ }
375
+
376
+ node._childListenerRetry = setInterval(function () {
377
+ if (attachChildListener(node, handler)) {
378
+ clearInterval(node._childListenerRetry);
379
+ node._childListenerRetry = null;
380
+ }
381
+ }, 1000);
382
+ }
383
+
384
+ function detachChildListener(node, handler) {
385
+ const child = node._attachedChild;
386
+ if (!child) {
387
+ node._childListenerAttached = false;
388
+ node._attachedChild = null;
389
+ return;
390
+ }
391
+
392
+ child.removeListener("message", handler);
393
+ node._childListenerAttached = false;
394
+ node._attachedChild = null;
395
+ }
396
+
397
+ function delay(ms) {
398
+ return new Promise(function (resolve) {
399
+ setTimeout(resolve, ms);
400
+ });
401
+ }
402
+
403
+ async function waitForChild(node, handler, timeoutMs) {
404
+ var startedAt = Date.now();
405
+ var timeout = timeoutMs || 5000;
406
+
407
+ while ((Date.now() - startedAt) < timeout) {
408
+ if (attachChildListener(node, handler)) {
409
+ var child = registry.resolveChild(node.serverRef);
410
+ if (child) {
411
+ return child;
412
+ }
413
+ }
414
+
415
+ await delay(100);
416
+ }
417
+
418
+ return null;
419
+ }
420
+
421
+ function normalizeInterval(value) {
422
+ const interval = Number(value);
423
+ if (!Number.isFinite(interval) || interval <= 0) {
424
+ return 500;
425
+ }
426
+
427
+ return Math.trunc(interval);
428
+ }
429
+
430
+ async function sendToChild(node, payload, options) {
431
+ options = options || {};
432
+ var handler = options.handler || node._messageHandler;
433
+ if (options.ensureListener !== false) {
434
+ attachChildListener(node, handler);
435
+ }
436
+
437
+ var child = registry.resolveChild(node.serverRef);
438
+
439
+ if (!child && options.waitForServer) {
440
+ node.status({ fill: "yellow", shape: "ring", text: "waiting for server" });
441
+ child = await waitForChild(node, handler, options.timeoutMs);
442
+ }
443
+
444
+ if (!child) {
445
+ const error = new Error("OPC UA server child process is not available for serverRef: " + node.serverRef);
446
+ node.status({ fill: "red", shape: "ring", text: "server unavailable" });
447
+ if (options.throwOnError === false) {
448
+ node.error(error.message);
449
+ return false;
450
+ }
451
+ throw error;
452
+ }
453
+
454
+ try {
455
+ child.send(payload);
456
+ if (node.mode !== "method-input") {
457
+ node.status({ fill: "green", shape: "dot", text: "connected" });
458
+ }
459
+ return true;
460
+ } catch (error) {
461
+ node.status({ fill: "red", shape: "ring", text: "ipc unavailable" });
462
+ node._childListenerAttached = false;
463
+ node._attachedChild = null;
464
+ ensureChildListener(node, handler);
465
+ if (options.throwOnError === false) {
466
+ node.error(error.message);
467
+ return false;
468
+ }
469
+ throw error;
470
+ }
471
+ }
472
+
473
+
474
+
475
+
476
+ OpcUaServerIoNode.prototype.sendMethodCall = function (call) {
477
+ const node = this;
478
+
479
+ node.status({
480
+ fill: "blue",
481
+ shape: "dot",
482
+ text: "method called"
483
+ });
484
+
485
+ node.send({
486
+ payload: call.inputArguments,
487
+ opcua: {
488
+ server: call.serverName,
489
+ method: call.methodName
490
+ },
491
+ _callId: call.callId
492
+ });
493
+ };
494
+
495
+ RED.nodes.registerType("opcua-server-io", OpcUaServerIoNode);
496
+ };