@t0a5tbr0t/node-red-contrib-opcua 0.1.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 (82) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +20 -0
  3. package/Readme.md +239 -0
  4. package/images/PROSYS-OPC-UA-EXAMPLE2.png +0 -0
  5. package/images/nodeopcua64.png +0 -0
  6. package/images/opcuanode.png +0 -0
  7. package/images/opcuanodeLogo64.png +0 -0
  8. package/opcua/101-opcuaitem.html +106 -0
  9. package/opcua/101-opcuaitem.js +82 -0
  10. package/opcua/102-opcuaclient.html +274 -0
  11. package/opcua/102-opcuaclient.js +2741 -0
  12. package/opcua/103-opcuabrowser.html +71 -0
  13. package/opcua/103-opcuabrowser.js +230 -0
  14. package/opcua/104-opcuaserver.html +261 -0
  15. package/opcua/104-opcuaserver.js +1844 -0
  16. package/opcua/105-opcuaendpoint.html +224 -0
  17. package/opcua/105-opcuaendpoint.js +64 -0
  18. package/opcua/106-opcuaevent.html +110 -0
  19. package/opcua/106-opcuaevent.js +50 -0
  20. package/opcua/107-opcuamethod.html +244 -0
  21. package/opcua/107-opcuamethod.js +521 -0
  22. package/opcua/108-opcuarights.html +223 -0
  23. package/opcua/108-opcuarights.js +156 -0
  24. package/opcua/109-opcuadiscovery.html +51 -0
  25. package/opcua/109-opcuadiscovery.js +180 -0
  26. package/opcua/dump_certificates.js +55 -0
  27. package/opcua/icons/opcuanode.png +0 -0
  28. package/opcua/icons/opcuanodeLogo.png +0 -0
  29. package/opcua/opcua-basics.js +1061 -0
  30. package/opcua/public/vendor/opc-foundation/binary/OPC.ISA95.Types.bsd.xml +141 -0
  31. package/opcua/public/vendor/opc-foundation/binary/Opc.Ua.Adi.Types.bsd.xml +76 -0
  32. package/opcua/public/vendor/opc-foundation/binary/Opc.Ua.Di.Types.bsd.xml +11 -0
  33. package/opcua/public/vendor/opc-foundation/binary/Opc.Ua.Gds.Types.bsd.xml +25 -0
  34. package/opcua/public/vendor/opc-foundation/binary/Opc.Ua.Types.bsd.xml +2391 -0
  35. package/opcua/public/vendor/opc-foundation/csv/AttributeIds.csv +22 -0
  36. package/opcua/public/vendor/opc-foundation/csv/NodeIds.csv +5746 -0
  37. package/opcua/public/vendor/opc-foundation/csv/Opc.Ua.StatusCodes.csv +227 -0
  38. package/opcua/public/vendor/opc-foundation/csv/OpcUaDiModel.csv +1523 -0
  39. package/opcua/public/vendor/opc-foundation/csv/OpcUaGdsModel.csv +400 -0
  40. package/opcua/public/vendor/opc-foundation/csv/ServerCapabilities.csv +13 -0
  41. package/opcua/public/vendor/opc-foundation/csv/ServerCapabilityIdentifiers.csv +13 -0
  42. package/opcua/public/vendor/opc-foundation/csv/StatusCode.csv +227 -0
  43. package/opcua/public/vendor/opc-foundation/csv/StatusCodes.csv +227 -0
  44. package/opcua/public/vendor/opc-foundation/schema/OPC.ISA95.Types.xsd +163 -0
  45. package/opcua/public/vendor/opc-foundation/schema/OPCBinarySchema.xsd +119 -0
  46. package/opcua/public/vendor/opc-foundation/schema/Opc.Ua.Types.xsd +3938 -0
  47. package/opcua/public/vendor/opc-foundation/schema/SecuredApplication.xsd +106 -0
  48. package/opcua/public/vendor/opc-foundation/schema/UANodeSet.xsd +420 -0
  49. package/opcua/public/vendor/opc-foundation/xml/OPC.ISA95.NodeSet2.Nov52013.xml +3293 -0
  50. package/opcua/public/vendor/opc-foundation/xml/Opc.ISA95.NodeSet2.xml +3589 -0
  51. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Adi.NodeSet2.xml +8301 -0
  52. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Adi.Types.bsd.xml +76 -0
  53. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Adi.Types.xsd +157 -0
  54. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.AutoID.NodeSet2.xml +6062 -0
  55. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Di.NodeSet2.xml +5066 -0
  56. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Di.PredefinedNodes.xml +11495 -0
  57. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Di.Types.bsd +86 -0
  58. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Di.Types.bsd.xml +11 -0
  59. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Di.Types.xsd +155 -0
  60. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Gds.NodeSet.xml +15226 -0
  61. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Gds.NodeSet2.xml +6214 -0
  62. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Gds.Types.bsd.xml +25 -0
  63. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.Gds.Types.xsd +30 -0
  64. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet-2.xml +134881 -0
  65. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet-3.xml +134881 -0
  66. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet.xml +134881 -0
  67. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part10.xml +832 -0
  68. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part11.xml +793 -0
  69. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part12.xml +3877 -0
  70. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part13.xml +344 -0
  71. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part3.xml +1103 -0
  72. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part4.xml +3091 -0
  73. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part5.xml +17092 -0
  74. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part8.xml +525 -0
  75. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part9.xml +2049 -0
  76. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.Part999.xml +134 -0
  77. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.xml +67169 -0
  78. package/opcua/public/vendor/opc-foundation/xml/Opc.Ua.PredefinedNodes.xml +64500 -0
  79. package/opcua/public/vendor/opc-foundation/xml/OpcUaDiModel.xml +1239 -0
  80. package/opcua/utils.js +55 -0
  81. package/package.json +68 -0
  82. package/users.json +22 -0
@@ -0,0 +1,521 @@
1
+ /**
2
+
3
+ Copyright 2020 Valmet Automation Inc.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+
17
+ **/
18
+
19
+ 'use strict'
20
+
21
+ /**
22
+ * OPC UA node representation for Node-RED OPC UA method call.
23
+ *
24
+ * @param RED
25
+ */
26
+ module.exports = function (RED) {
27
+ var chalk = require("chalk");
28
+ var opcua = require('node-opcua');
29
+ var uaclient = require('node-opcua-client');
30
+ const {stringify} = require('flatted');
31
+ // const dataTypeFactory = require("node-opcua-factory");
32
+ // const ScanData = getOrCreateConstructor("ScanData", dataTypeFactory);
33
+ var opcuaBasics = require('./opcua-basics');
34
+ const { createClientCertificateManager } = require("./utils");
35
+ function OPCUAMethodNode(n) {
36
+ RED.nodes.createNode(this, n)
37
+ this.objectId = n.objectId;
38
+ this.methodId = n.methodId;
39
+ this.name = n.name;
40
+ this.inputArguments = n.inputArguments;
41
+ this.outputArguments = n.outputArguments;
42
+
43
+ var node = this;
44
+ var opcuaEndpoint = RED.nodes.getNode(n.endpoint);
45
+ const cmdQueue = []; // queue msgs which can currently not be handled because session is not established, yet and currentStatus is 'connecting'
46
+ var currentStatus = ''; // the status value set set by node.status(). Didn't find a way to read it back.
47
+ node.outputArguments = [];
48
+
49
+ function set_node_status_to(statusValue, message = "") {
50
+ verbose_log(chalk.yellow("Client status: ") + chalk.cyan(statusValue));
51
+ var statusParameter = opcuaBasics.get_node_status(statusValue);
52
+ currentStatus = statusValue;
53
+ node.status({
54
+ fill: statusParameter.fill,
55
+ shape: statusParameter.shape,
56
+ text: (statusParameter.status + " " + message).trim()
57
+ });
58
+ }
59
+
60
+ if (n.arg0type === undefined || n.arg0type === "" || n.arg0value === "") {
61
+ // Do nothing
62
+ } else if (n.arg0type === "Boolean") {
63
+ if (n.arg0value === "True") {
64
+ node.inputArguments.push({dataType: n.arg0type, value: true});
65
+ }
66
+ else {
67
+ node.inputArguments.push({dataType: n.arg0type, value: false});
68
+ }
69
+ } else if (n.arg0type === "DateTime") {
70
+ node.inputArguments.push({dataType: n.arg0type, value: new Date(n.arg0value)});
71
+ } else if (n.arg0type === "NodeId") {
72
+ node.inputArguments.push({dataType: n.arg0type, value: opcua.coerceNodeId(n.arg0value)});
73
+ } else if (n.arg0type === "ExtensionObject") {
74
+ node.inputArguments.push({dataType: n.arg0type, typeid: n.arg0typeid, value: JSON.parse(n.arg0value)});
75
+ } else if (n.arg0type === "String") {
76
+ node.inputArguments.push({dataType: n.arg0type, value: n.arg0value});
77
+ } else if (n.out0type === "LocalizedText") {
78
+ // ValueRank="1" ArrayDimensions="1"
79
+ node.inputArguments.push({dataType: n.arg0type, valueRank: 1, arrayDimensions: 1, value: [n.arg0value]});
80
+ } else if (n.arg0type === "ScanData") {
81
+ node.inputArguments.push({dataType: n.arg0type, value: new ScanData(n.arg0value)});
82
+ }
83
+ else if (n.arg0type === "Double" || n.arg0type === "Float" ) {
84
+ node.inputArguments.push({dataType: n.arg0type, value: parseFloat(n.arg0value)});
85
+ } else {
86
+ node.inputArguments.push({dataType: n.arg0type, value: parseInt(n.arg0value)});
87
+ }
88
+
89
+ if (n.arg1type === undefined || n.arg1ype === "" || n.arg1value === "") {
90
+ // Do nothing
91
+ } else if (n.arg1type === "Boolean") {
92
+ if (n.arg1value === "True") {
93
+ node.inputArguments.push({dataType: n.arg1type, value: true});
94
+ }
95
+ else {
96
+ node.inputArguments.push({dataType: n.arg1type, value: false});
97
+ }
98
+ } else if (n.arg1type === "DateTime") {
99
+ node.inputArguments.push({dataType: n.arg1type, value: new Date(n.arg1value)});
100
+ } else if (n.arg1type === "NodeId") {
101
+ node.inputArguments.push({dataType: n.arg1type, value: opcua.coerceNodeId(n.arg1value)});
102
+ } else if (n.arg1type === "ExtensionObject") {
103
+ node.inputArguments.push({dataType: n.arg1type, typeid: n.arg1typeid, value: JSON.parse(n.arg1value)});
104
+ } else if (n.arg1type === "String") {
105
+ node.inputArguments.push({dataType: n.arg1type, value: n.arg1value});
106
+ } else if (n.arg1type === "LocalizedText") {
107
+ node.inputArguments.push({dataType: n.arg1type, valueRank: 1, arrayDimensions: 1, value: [n.arg1value]});
108
+ }else if (n.arg1type === "Double" || n.arg1type === "Float" ) {
109
+ node.inputArguments.push({dataType: n.arg1type, value: parseFloat(n.arg1value)});
110
+ } else {
111
+ node.inputArguments.push({dataType: n.arg1type, value: parseInt(n.arg1value)});
112
+ }
113
+
114
+ if (n.arg2type === undefined || n.arg2type === "" || n.arg2value === "") {
115
+ // Do nothing
116
+ } else if (n.arg2type === "Boolean") {
117
+ if (n.arg2value === "True") {
118
+ node.inputArguments.push({dataType: n.arg2type, value: true});
119
+ }
120
+ else {
121
+ node.inputArguments.push({dataType: n.arg2type, value: false});
122
+ }
123
+ } else if (n.arg2type === "DateTime") {
124
+ node.inputArguments.push({dataType: n.arg2type, value: new Date(n.arg2value)});
125
+ } else if (n.arg2type === "NodeId") {
126
+ node.inputArguments.push({dataType: n.arg2type, value: opcua.coerceNodeId(n.arg2value)});
127
+ } else if (n.arg2type === "ExtensionObject") {
128
+ node.inputArguments.push({dataType: n.arg2type, typeid: n.arg0typeid, value: JSON.parse(n.arg2value)});
129
+ } else if (n.arg2type === "String") {
130
+ node.inputArguments.push({dataType: n.arg2type, value: n.arg2value});
131
+ } else if (n.arg2type === "LocalizedText") {
132
+ node.inputArguments.push({dataType: n.arg2type, valueRank: 1, arrayDimensions: 1, value: [n.arg2value]});
133
+ }else if (n.arg2type === "Double" || n.arg2type === "Float" ) {
134
+ node.inputArguments.push({dataType: n.arg2type, value: parseFloat(n.arg2value)});
135
+ } else {
136
+ node.inputArguments.push({dataType: n.arg2type, value: parseInt(n.arg2value)});
137
+ }
138
+
139
+ if (n.out0type === undefined || n.out0type === "") {
140
+ // Do nothing
141
+ } else if (n.out0type === "Boolean") {
142
+ if (n.out0value === "True") {
143
+ node.outputArguments.push({dataType: n.out0type, value: true});
144
+ }
145
+ else {
146
+ node.outputArguments.push({dataType: n.out0type, value: false});
147
+ }
148
+ } else if (n.out0type === "DateTime") {
149
+ node.outputArguments.push({dataType: n.out0type, value: new Date(n.out0value)});
150
+ } else if (n.out0type === "NodeId") {
151
+ node.outputArguments.push({dataType: n.out0type, value: opcua.coerceNodeId(n.out0value)});
152
+ } else if (n.out0type === "ExtensionObject") {
153
+ node.outputArguments.push({dataType: n.out0type, typeid: n.out0typeid, value: JSON.parse(n.out0value)});
154
+ } else if (n.out0type === "String") {
155
+ node.outputArguments.push({dataType: n.out0type, value: n.out0value});
156
+ } else if (n.out0type === "LocalizedText") {
157
+ node.outputArguments.push({dataType: n.out0type, value: n.out0value});
158
+ }
159
+ else if (n.out0type === "ScanData") {
160
+ node.outputArguments.push({dataType: n.out0type, value: new ScanData(n.out0value)});
161
+ }
162
+ else if (n.out0type === "Double" || n.out0type === "Float" ) {
163
+ node.outputArguments.push({dataType: n.out0type, value: parseFloat(n.out0value)});
164
+ } else {
165
+ node.outputArguments.push({dataType: n.out0type, value: parseInt(n.out0value)});
166
+ }
167
+
168
+ var connectionOption = {};
169
+ var userIdentity = {};
170
+
171
+ if (opcuaEndpoint.securityPolicy) {
172
+ connectionOption.securityPolicy = opcua.SecurityPolicy[opcuaEndpoint.securityPolicy];
173
+ } else {
174
+ connectionOption.securityPolicy = opcua.SecurityPolicy.None;
175
+ }
176
+ if (opcuaEndpoint.securityMode) {
177
+ connectionOption.securityMode = opcua.MessageSecurityMode[opcuaEndpoint.securityMode];
178
+ } else {
179
+ connectionOption.securityPolicy = opcua.MessageSecurityMode.None;
180
+ }
181
+ connectionOption.endpointMustExist = false;
182
+ connectionOption.defaultSecureTokenLifetime = 40000 * 5;
183
+ connectionOption.connectionStrategy = {
184
+ maxRetry: 10512000, // Limited to max 10 ~5min // 10512000, // 10 years should be enough. No infinite parameter for backoff.
185
+ initialDelay: 5000, // 5s
186
+ maxDelay: 30 * 1000 // 30s
187
+ };
188
+ connectionOption.keepSessionAlive = false; // true;
189
+
190
+ connectionOption.clientCertificateManager = createClientCertificateManager();
191
+
192
+ if (opcuaEndpoint.login) {
193
+ userIdentity.userName = opcuaEndpoint.credentials.user;
194
+ userIdentity.password = opcuaEndpoint.credentials.password;
195
+ userIdentity.type = uaclient.UserTokenType.UserName; // New TypeScript API parameter
196
+ }
197
+ node.debug(chalk.yellow("Input arguments: ") + chalk.cyan(JSON.stringify(node.inputArguments)));
198
+
199
+ const backoff = function (attempt, delay) {
200
+ var msg = {};
201
+ msg.error = {};
202
+ msg.error.message = "reconnect";
203
+ msg.error.source = this;
204
+ // node.error("reconnect", msg);
205
+ verbose_log(chalk.red("reconnect") + chalk.cyan(msg));
206
+ set_node_status_to("reconnect", "attempt #" + attempt + " retry in " + delay / 1000.0 + " sec");
207
+ };
208
+
209
+ const reconnection = function () {
210
+ set_node_status_to("reconnect", "starting...");
211
+ };
212
+
213
+ const reestablish = function () {
214
+ set_node_status_to("connected", "re-established");
215
+ };
216
+
217
+ function create_opcua_client() {
218
+ if (node.client) {
219
+ node.client = null;
220
+ }
221
+ try {
222
+ if (opcuaEndpoint.endpoint.indexOf("opc.tcp://0.0.0.0") == 0) {
223
+ node_error("No client");
224
+ set_node_status_to("no client");
225
+ return;
226
+ }
227
+ // Normal client
228
+ verbose_log(chalk.green("1) CREATE CLIENT: ") + chalk.cyan(JSON.stringify(connectionOption).substring(0,75) + "..."));
229
+ node.client = opcua.OPCUAClient.create(connectionOption);
230
+ node.client.on("connection_reestablished", reestablish);
231
+ node.client.on("backoff", backoff);
232
+ node.client.on("start_reconnection", reconnection);
233
+ client.clientCertificateManager = connectionOption.clientCertificateManager; // FIX connection issue
234
+ set_node_status_to("create client");
235
+ }
236
+ catch(err) {
237
+ node_error("Cannot create client, check connection options: " + stringify(connectionOption));
238
+ set_node_status_to("error", "Cannot create client, check connection options: " + stringify(connectionOption));
239
+ }
240
+ }
241
+
242
+ create_opcua_client();
243
+
244
+ try {
245
+ node.client = opcua.OPCUAClient.create(connectionOption);
246
+ client.clientCertificateManager = connectionOption.clientCertificateManager; // FIX connection issue
247
+ node.client.clientCertificateManager.initialize();
248
+ }
249
+ catch (error1) {
250
+ set_node_status_to("invalid certificate");
251
+ let msg = {};
252
+ msg.error = {};
253
+ msg.error.message = "Certificate error: " + error1.message;
254
+ msg.error.source = this;
255
+ node.error("Certificate error", msg);
256
+ }
257
+ node.debug(chalk.yellow("Trusted folder: ") + chalk.cyan(node.client?.clientCertificateManager?.trustedFolder));
258
+ node.debug(chalk.yellow("Rejected folder: ") + chalk.cyan(node.client?.clientCertificateManager?.rejectedFolder));
259
+ node.debug(chalk.yellow("Crl folder: ") + chalk.cyan(node.client?.clientCertificateManager?.crlFolder));
260
+ node.debug(chalk.yellow("Issuers Cert folder: ") + chalk.cyan(node.client?.clientCertificateManager?.issuersCertFolder));
261
+ node.debug(chalk.yellow("Issuers Crl folder: ") + chalk.cyan(node.client?.clientCertificateManager?.issuersCrlFolder));
262
+
263
+ set_node_status_to("initialized");
264
+
265
+ async function methodNodeProcess(url, message, callback) {
266
+ try {
267
+
268
+ const statuses = ['initialized', 'method executed'];
269
+
270
+ verbose_log(chalk.yellow("Queued Message: ") + chalk.cyan(JSON.stringify(message)));
271
+ cmdQueue.push(message);
272
+
273
+ if (statuses.includes(currentStatus)) {
274
+ set_node_status_to("executing method")
275
+ // if Node Re-connecting or busy, methods should only be queued
276
+ // If it's ready:
277
+
278
+ // step 1 : connect client
279
+ await connect_opcua_client(url);
280
+ node.log("start method client on " + opcuaEndpoint.endpoint);
281
+
282
+ // step 2 : createSession
283
+ node.session = await node.client.createSession(userIdentity);
284
+ verbose_log(chalk.yellow("start session on ") + chalk.cyan(opcuaEndpoint.endpoint));
285
+ set_node_status_to("session active");
286
+
287
+ // step 3: call method
288
+ for (const cmd of cmdQueue) {
289
+ verbose_log(chalk.yellow("Call method: ") + chalk.cyan(JSON.stringify(cmd)));
290
+ var result = await callMethod(cmd);
291
+ if (result.statusCode !== opcua.StatusCodes.Good) {
292
+ verbose_warn(chalk.red("Could not run method: ") + chalk.cyan(result));
293
+ }
294
+ }
295
+
296
+ cmdQueue.length = 0;
297
+ set_node_status_to("method executed");
298
+
299
+ // step 4: close session & disconnect client
300
+ if (node.session) {
301
+ await node.session.close();
302
+ verbose_log(chalk.yellow("Session closed"));
303
+ node.session = null;
304
+ await node.client.disconnect();
305
+ }
306
+ }
307
+ } catch (err) {
308
+ var msg = {};
309
+ msg.error = {};
310
+ msg.error.message = "Cannot connect to " + JSON.stringify(opcuaEndpoint);
311
+ msg.error.source = this;
312
+ node.error("Cannot connect to ", msg);
313
+ callback(err);
314
+ }
315
+ }
316
+
317
+ async function connect_opcua_client(url) {
318
+ set_node_status_to("connecting")
319
+ await node.client.connect(url);
320
+ }
321
+
322
+ function close_opcua_client(message, error) {
323
+ if (node.client) {
324
+ node.client.removeListener("connection_reestablished", reestablish);
325
+ node.client.removeListener("backoff", backoff);
326
+ node.client.removeListener("start_reconnection", reconnection);
327
+ try {
328
+ if(!node.client.isReconnecting){
329
+ node.client.disconnect(function () {
330
+ node.client = null;
331
+ verbose_log(chalk.yellow("Client disconnected!"));
332
+ if (error === 0) {
333
+ set_node_status_to("closed");
334
+ }
335
+ else {
336
+ set_node_errorstatus_to(message, error)
337
+ node.error("Client disconnected & closed: " + message + " error: " + error.toString());
338
+ }
339
+ });
340
+ }
341
+ else {
342
+ node.client = null;
343
+ set_node_status_to("closed");
344
+ }
345
+ }
346
+ catch (err) {
347
+ node_error("Error on disconnect: " + stringify(err));
348
+ }
349
+ }
350
+ }
351
+
352
+ function node_error(err) {
353
+ // console.error(chalk.red("Client node error on node: " + node.name + "; error: " + stringify(err)));
354
+ var msg = {};
355
+ msg.error = {};
356
+ msg.error.message = "Client node error: " + stringify(err);
357
+ msg.error.source = this;
358
+ node.error("Method node error on node: ", msg);
359
+ }
360
+
361
+ function verbose_warn(logMessage) {
362
+ //if (RED.settings.verbose) {
363
+ node.warn((node.name) ? node.name + ': ' + logMessage : 'OpcUaMethodNode: ' + logMessage);
364
+ //}
365
+ }
366
+
367
+ function verbose_log(logMessage) {
368
+ //if (RED.settings.verbose) {
369
+ // node.log(logMessage);
370
+ node.debug(logMessage);
371
+ //}
372
+ }
373
+
374
+ async function callMethod(msg) {
375
+ if (msg.methodId && msg.inputArguments) {
376
+ verbose_log(chalk.yellow("Calling method: ") + chalk.cyan(JSON.stringify(msg.methodId)));
377
+ verbose_log(chalk.yellow("InputArguments: ") + chalk.cyan(JSON.stringify(msg.inputArguments)));
378
+ verbose_log(chalk.yellow("OutputArguments: ") + chalk.cyan(JSON.stringify(msg.outputArguments)));
379
+
380
+ try {
381
+ var i = 0;
382
+ var arg;
383
+ while (i < msg.inputArguments.length) {
384
+ arg = msg.inputArguments[i];
385
+ if (arg.dataType === "NodeId") {
386
+ arg.value = opcua.coerceNodeId(arg.value);
387
+ }
388
+ if (arg.dataType === "ExtensionObject") {
389
+ var extensionobject = null;
390
+ if (arg.typeid) {
391
+ extensionobject = await node.session.constructExtensionObject(opcua.coerceNodeId(arg.typeid), arg.value); // TODO make while loop to enable await
392
+ }
393
+ verbose_log(chalk.yellow("ExtensionObject=") + chalk.cyan(stringify(extensionobject)));
394
+ arg.value = extensionobject;
395
+ }
396
+ i++;
397
+ }
398
+ } catch (err) {
399
+ var msg = {};
400
+ msg.error = {};
401
+ msg.error.message = "Invalid NodeId: " + err;
402
+ msg.error.source = this;
403
+ node.error("Invalid NodeId: ", msg);
404
+ return opcua.StatusCodes.BadNodeIdUnknown;
405
+ }
406
+ verbose_log(chalk.yellow("Updated InputArguments: ") + chalk.cyan(JSON.stringify(msg.inputArguments)));
407
+ var callMethodRequest;
408
+ var diagInfo;
409
+ try {
410
+ set_node_status_to("call method");
411
+ callMethodRequest = new opcua.CallMethodRequest({
412
+ objectId: opcua.coerceNodeId(msg.objectId),
413
+ methodId: opcua.coerceNodeId(msg.methodId),
414
+ inputArgumentDiagnosticInfos: diagInfo,
415
+ inputArguments: msg.inputArguments,
416
+ outputArguments: msg.outputArguments
417
+ });
418
+ } catch (err) {
419
+ set_node_status_to("error: " + err);
420
+ var msg = {};
421
+ msg.error = {};
422
+ msg.error.message = "Build method request failed, error: " + err;
423
+ msg.error.source = this;
424
+ node.error("Build method request failed, error: ", msg);
425
+ }
426
+
427
+ verbose_log(chalk.yellow("Call request: ") + chalk.cyan(callMethodRequest.toString()));
428
+ verbose_log(chalk.yellow("Calling: ") + chalk.cyan(callMethodRequest));
429
+ try {
430
+ let result = await node.session.call(callMethodRequest);
431
+ if (diagInfo) {
432
+ verbose_log(chalk.red("Diagn. info: ") + chalk.cyan(JSON.stringify(diagInfo)));
433
+ }
434
+ verbose_log(chalk.yellow("Output args: ") + JSON.stringify(msg.outputArguments));
435
+ verbose_log(chalk.yellow("Results: ") + JSON.stringify(result));
436
+ msg.result = result;
437
+ if (result && result.statusCode === opcua.StatusCodes.Good) {
438
+ var i = 0;
439
+ msg.output = result.outputArguments; // Original outputArguments
440
+ msg.payload = []; // Store values back to array
441
+ if (result.outputArguments.length == 1) {
442
+ verbose_log(chalk.yellow("Value: ") + chalk.cyan(result.outputArguments[i].value));
443
+ msg.payload = result.outputArguments[0].value; // Return only if one output argument
444
+ } else {
445
+ while (result.outputArguments.length > i) {
446
+ verbose_log(chalk.yellow("Value[") + chalk.cyan(i) + chalk.yellow("]: ") + chalk.cyan(result.outputArguments[i].toString()));
447
+ msg.payload.push(result.outputArguments[i].value); // Just copy result value to payload[] array, actual value needed mostly
448
+ i++;
449
+ }
450
+ }
451
+ } else {
452
+ set_node_status_to("error: " + result.statusCode.description)
453
+ node.error("Execute method result, error:" + result.statusCode.description);
454
+ return result;
455
+ }
456
+ set_node_status_to("method executed");
457
+ node.send(msg);
458
+ return result; // opcua.StatusCodes.Good;
459
+ } catch (err) {
460
+ set_node_status_to("Method execution error: " + err);
461
+ var msg = {};
462
+ msg.error = {};
463
+ msg.error.message = "Method execution error: " + err;
464
+ msg.error.source = this;
465
+ node.error("Method execution error: ", msg);
466
+ return opcua.StatusCodes.BadMethodInvalid;
467
+ }
468
+ }
469
+ }
470
+
471
+ node.on("input", function (msg) {
472
+ var message = {}
473
+
474
+ message.objectId = msg.objectId || node.objectId;
475
+ message.methodId = msg.methodId || node.methodId;
476
+ message.methodType = msg.methodType || node.methodType;
477
+ message.inputArguments = msg.inputArguments || node.inputArguments;
478
+ message.outputArguments = msg.outputArguments || node.outputArguments;
479
+
480
+ if (!message.objectId) {
481
+ verbose_warn("No objectId for Method");
482
+ return;
483
+ }
484
+ if (!message.methodId) {
485
+ verbose_warn("No method for Method");
486
+ return;
487
+ }
488
+ if (!message.inputArguments) {
489
+ verbose_warn(chalk.yellow("No Input Arguments for Method"));
490
+ return
491
+ }
492
+
493
+ methodNodeProcess(opcuaEndpoint.endpoint, message, function (err) {
494
+ if (err) {
495
+ node_error(err);
496
+ node.status({
497
+ fill: "red",
498
+ shape: "dot",
499
+ text: "Error: " + err.toString()
500
+ });
501
+ }
502
+ });
503
+ });
504
+
505
+ node.on("close", async function (done) {
506
+ if (node.session) {
507
+ await node.session.close();
508
+ node.session = null;
509
+ close_opcua_client("closed", 0);
510
+ done();
511
+ } else {
512
+ node.session = null
513
+ close_opcua_client("closed", 0);
514
+ done();
515
+ }
516
+ });
517
+
518
+ }
519
+
520
+ RED.nodes.registerType("OpcUa-Method", OPCUAMethodNode);
521
+ }