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