@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.
- package/client/lib/opcua-client-browser.js +291 -0
- package/client/lib/opcua-client-read-service.js +16 -0
- package/client/lib/opcua-client-subscription-id-service.js +25 -0
- package/client/lib/opcua-client-subscription-service.js +171 -0
- package/client/lib/opcua-client-write-service.js +53 -0
- package/client/opcua-client-config.html +80 -0
- package/client/opcua-client-config.js +159 -0
- package/client/opcua-client-utils.js +320 -0
- package/client/opcua-client.html +1225 -0
- package/client/opcua-client.js +380 -0
- package/object.json +65 -0
- package/package.json +1 -5
- package/server/lib/opcua-address-space-alarm.js +341 -0
- package/server/lib/opcua-address-space-builder.js +1456 -0
- package/server/lib/opcua-config.js +546 -0
- package/server/lib/opcua-constants.js +109 -0
- package/server/lib/opcua-server-events-child.js +140 -0
- package/server/lib/opcua-server-methods.js +198 -0
- package/server/lib/opcua-server-runtime-child.js +729 -0
- package/server/lib/opcua-server-runtime.js +311 -0
- package/server/lib/opcua-server-status-child.js +188 -0
- package/server/lib/server-node-utils.js +16 -0
- package/server/opcua-server-io.html +347 -0
- package/server/opcua-server-io.js +463 -0
- package/server/opcua-server-registry.js +270 -0
- package/server/opcua-server.css +265 -0
- package/server/opcua-server.html +1548 -0
|
@@ -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
|
+
};
|