node-opcua-samples 2.66.3 → 2.68.1

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.
@@ -4,70 +4,70 @@
4
4
  "use strict";
5
5
  const path = require("path");
6
6
  const fs = require("fs");
7
+ const os = require("os");
7
8
  const assert = require("assert");
8
9
  const chalk = require("chalk");
9
10
  const yargs = require("yargs/yargs");
10
11
  const envPaths = require("env-paths");
11
12
 
12
13
  const {
13
- OPCUAServer,
14
- OPCUACertificateManager,
15
- Variant,
16
- DataType,
17
- VariantArrayType,
18
- DataValue,
19
- standardUnits,
20
- makeApplicationUrn,
21
- nodesets,
22
- install_optional_cpu_and_memory_usage_node,
23
- build_address_space_for_conformance_testing,
24
- RegisterServerMethod,
25
- extractFullyQualifiedDomainName
14
+ OPCUAServer,
15
+ OPCUACertificateManager,
16
+ Variant,
17
+ DataType,
18
+ VariantArrayType,
19
+ DataValue,
20
+ standardUnits,
21
+ makeApplicationUrn,
22
+ nodesets,
23
+ install_optional_cpu_and_memory_usage_node,
24
+ build_address_space_for_conformance_testing,
25
+ RegisterServerMethod,
26
+ extractFullyQualifiedDomainName,
27
+ makeRoles,
28
+ WellKnownRoles
26
29
  } = require("node-opcua");
27
30
 
28
31
  Error.stackTraceLimit = Infinity;
29
32
 
30
-
31
33
  const argv = yargs(process.argv)
32
- .wrap(132)
33
-
34
- .string("alternateHostname")
35
- .describe("alternateHostname")
34
+ .wrap(132)
36
35
 
37
- .number("port")
38
- .default("port", 26543)
36
+ .string("alternateHostname")
37
+ .describe("alternateHostname")
39
38
 
40
- .number("maxAllowedSessionNumber")
41
- .describe("maxAllowedSessionNumber", "the maximum number of concurrent client session that the server will accept")
42
- .default("maxAllowedSessionNumber", 500)
39
+ .number("port")
40
+ .default("port", 26543)
43
41
 
44
- .number("maxAllowedSubscriptionNumber")
45
- .describe("maxAllowedSubscriptionNumber", "the maximum number of concurrent subscriptions")
42
+ .number("maxAllowedSessionNumber")
43
+ .describe("maxAllowedSessionNumber", "the maximum number of concurrent client session that the server will accept")
44
+ .default("maxAllowedSessionNumber", 500)
46
45
 
47
- .boolean("silent")
48
- .default("silent", false)
49
- .describe("silent", "no trace")
46
+ .number("maxAllowedSubscriptionNumber")
47
+ .describe("maxAllowedSubscriptionNumber", "the maximum number of concurrent subscriptions")
50
48
 
49
+ .boolean("silent")
50
+ .default("silent", false)
51
+ .describe("silent", "no trace")
51
52
 
52
- .string("alternateHostname")
53
- .default("alternateHostname", null)
53
+ .string("alternateHostname")
54
+ .default("alternateHostname", null)
54
55
 
55
- .number("keySize")
56
- .describe("keySize", "certificate keySize [1024|2048|3072|4096]")
57
- .default("keySize", 2048)
58
- .alias("k", "keySize")
56
+ .number("keySize")
57
+ .describe("keySize", "certificate keySize [1024|2048|3072|4096]")
58
+ .default("keySize", 2048)
59
+ .alias("k", "keySize")
59
60
 
60
- .string("applicationName")
61
- .describe("applicationName", "the application name")
62
- .default("applicationName", "NodeOPCUA-Server")
61
+ .string("applicationName")
62
+ .describe("applicationName", "the application name")
63
+ .default("applicationName", "NodeOPCUA-Server")
63
64
 
64
- .alias("a", "alternateHostname")
65
- .alias("m", "maxAllowedSessionNumber")
66
- .alias("n", "applicationName")
67
- .alias("p", "port")
65
+ .alias("a", "alternateHostname")
66
+ .alias("m", "maxAllowedSessionNumber")
67
+ .alias("n", "applicationName")
68
+ .alias("p", "port")
68
69
 
69
- .help(true)
70
- .argv;
70
+ .help(true).argv;
71
71
 
72
72
  const port = argv.port;
73
73
  const maxAllowedSessionNumber = argv.maxAllowedSessionNumber;
@@ -75,47 +75,60 @@ const maxConnectionsPerEndpoint = maxAllowedSessionNumber;
75
75
  const maxAllowedSubscriptionNumber = argv.maxAllowedSubscriptionNumber || 50;
76
76
  OPCUAServer.MAX_SUBSCRIPTION = maxAllowedSubscriptionNumber;
77
77
 
78
-
79
- const os = require('os');
80
-
81
78
  async function getIpAddresses() {
82
-
83
- const ipAddresses = [];
84
- const interfaces = os.networkInterfaces();
85
- Object.keys(interfaces).forEach(function(interfaceName) {
86
- let alias = 0;
87
-
88
- interfaces[interfaceName].forEach(function(iface) {
89
- if ('IPv4' !== iface.family || iface.internal !== false) {
90
- // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
91
- return;
92
- }
93
- if (alias >= 1) {
94
- // this single interface has multiple ipv4 addresses
95
- console.log(interfaceName + ':' + alias, iface.address);
96
- ipAddresses.push(iface.address);
97
- } else {
98
- // this interface has only one ipv4 address
99
- console.log(interfaceName, iface.address);
100
- ipAddresses.push(iface.address);
101
- }
102
- ++alias;
79
+ const ipAddresses = [];
80
+ const interfaces = os.networkInterfaces();
81
+ Object.keys(interfaces).forEach(function (interfaceName) {
82
+ let alias = 0;
83
+
84
+ interfaces[interfaceName].forEach(function (iface) {
85
+ if ("IPv4" !== iface.family || iface.internal !== false) {
86
+ // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
87
+ return;
88
+ }
89
+ if (alias >= 1) {
90
+ // this single interface has multiple ipv4 addresses
91
+ console.log(interfaceName + ":" + alias, iface.address);
92
+ ipAddresses.push(iface.address);
93
+ } else {
94
+ // this interface has only one ipv4 address
95
+ console.log(interfaceName, iface.address);
96
+ ipAddresses.push(iface.address);
97
+ }
98
+ ++alias;
99
+ });
103
100
  });
104
- });
105
- return ipAddresses;
101
+ return ipAddresses;
106
102
  }
107
103
 
108
- const userManager = {
109
- isValidUser: function(userName, password) {
104
+ const users = [
105
+ {
106
+ username: "user1",
107
+ password: "password1",
108
+ role: makeRoles([WellKnownRoles.AuthenticatedUser, WellKnownRoles.ConfigureAdmin])
109
+ },
110
+ { username: "user2", password: "password2", role: makeRoles([WellKnownRoles.AuthenticatedUser, WellKnownRoles.Operator]) }
111
+ ];
110
112
 
111
- if (userName === "user1" && password === "password1") {
112
- return true;
113
- }
114
- if (userName === "user2" && password === "password2") {
115
- return true;
113
+ const userManager = {
114
+ isValidUser(username, password) {
115
+ const uIndex = users.findIndex((x) => x.username === username);
116
+ if (uIndex < 0) {
117
+ return false;
118
+ }
119
+ if (users[uIndex].password !== password) {
120
+ return false;
121
+ }
122
+ return true;
123
+ },
124
+ getUserRoles(username) {
125
+ const uIndex = users.findIndex((x) => x.username === username);
126
+ if (uIndex < 0) {
127
+ return [];
128
+ }
129
+ const userRole = users[uIndex].role;
130
+ return userRole;
116
131
  }
117
- return false;
118
- }
119
132
  };
120
133
 
121
134
  const keySize = argv.keySize;
@@ -125,464 +138,462 @@ const productUri = argv.applicationName || "NodeOPCUASample-Simple-Server";
125
138
  const paths = envPaths(productUri);
126
139
 
127
140
  (async function main() {
141
+ const fqdn = await extractFullyQualifiedDomainName();
142
+ console.log("FQDN = ", fqdn);
143
+
144
+ const applicationUri = makeApplicationUrn(fqdn, productUri);
145
+ // -----------------------------------------------
146
+ const configFolder = paths.config;
147
+ const pkiFolder = path.join(configFolder, "PKI");
148
+ const userPkiFolder = path.join(configFolder, "UserPKI");
149
+
150
+ const userCertificateManager = new OPCUACertificateManager({
151
+ automaticallyAcceptUnknownCertificate: true,
152
+ name: "UserPKI",
153
+ rootFolder: userPkiFolder
154
+ });
155
+ await userCertificateManager.initialize();
128
156
 
129
- const fqdn = await extractFullyQualifiedDomainName();
130
- console.log("FQDN = ", fqdn);
131
-
132
- const applicationUri = makeApplicationUrn(fqdn, productUri);
133
- // -----------------------------------------------
134
- const configFolder = paths.config;
135
- const pkiFolder = path.join(configFolder, "PKI");
136
- const userPkiFolder = path.join(configFolder, "UserPKI");
137
-
138
- const userCertificateManager = new OPCUACertificateManager({
139
- automaticallyAcceptUnknownCertificate: true,
140
- name: "UserPKI",
141
- rootFolder: userPkiFolder,
142
- });
143
- await userCertificateManager.initialize();
144
-
145
- const serverCertificateManager = new OPCUACertificateManager({
146
- automaticallyAcceptUnknownCertificate: true,
147
- name: "PKI",
148
- rootFolder: pkiFolder,
149
- });
150
-
151
- await serverCertificateManager.initialize();
152
-
153
- const certificateFile = path.join(pkiFolder, `server_certificate1.pem`);
154
- if (!fs.existsSync(certificateFile)) {
155
-
156
- console.log("Creating self-signed certificate");
157
-
158
- await serverCertificateManager.createSelfSignedCertificate({
159
- applicationUri: applicationUri,
160
- dns: argv.alternateHostname ? [argv.alternateHostname, fqdn] : [fqdn],
161
- ip: await getIpAddresses(),
162
- outputFile: certificateFile,
163
- subject: "/CN=Sterfive/DC=Test",
164
- startDate: new Date(),
165
- validity: 365 * 10,
166
- })
167
- }
168
- assert(fs.existsSync(certificateFile));
169
- // ------------------------------------------------------------------
170
-
171
- const server_options = {
172
-
173
- serverCertificateManager,
174
- certificateFile,
175
-
176
- userCertificateManager,
177
-
178
-
179
- port,
180
-
181
- maxAllowedSessionNumber: maxAllowedSessionNumber,
182
- maxConnectionsPerEndpoint: maxConnectionsPerEndpoint,
183
-
184
- nodeset_filename: [
185
- nodesets.standard,
186
- nodesets.di
187
- ],
188
-
189
- serverInfo: {
190
- applicationName: { text: "NodeOPCUA", locale: "en" },
191
- applicationUri: applicationUri,
192
- gatewayServerUri: null,
193
- productUri: productUri,
194
- discoveryProfileUri: null,
195
- discoveryUrls: []
196
- },
197
- buildInfo: {
198
- buildNumber: "1234"
199
- },
200
- serverCapabilities: {
201
- maxBrowseContinuationPoints: 10,
202
- maxHistoryContinuationPoints: 10,
203
- // maxInactiveLockTime
204
- operationLimits: {
205
- maxNodesPerRead: 1000,
206
- maxNodesPerWrite: 1000,
207
- maxNodesPerHistoryReadData: 100,
208
- maxNodesPerBrowse: 1000,
209
- maxNodesPerMethodCall: 200,
210
- }
211
- },
212
- userManager: userManager,
213
-
214
-
215
- isAuditing: false,
216
- //xx registerServerMethod: RegisterServerMethod.HIDDEN,
217
- //xx registerServerMethod: RegisterServerMethod.MDNS,
218
- registerServerMethod: RegisterServerMethod.LDS,
219
- discoveryServerEndpointUrl: "opc.tcp://localhost:4840",
220
-
221
- };
157
+ const serverCertificateManager = new OPCUACertificateManager({
158
+ automaticallyAcceptUnknownCertificate: true,
159
+ name: "PKI",
160
+ rootFolder: pkiFolder
161
+ });
222
162
 
223
- process.title = "Node OPCUA Server on port : " + server_options.port;
224
- server_options.alternateHostname = argv.alternateHostname;
225
- const server = new OPCUAServer(server_options);
163
+ await serverCertificateManager.initialize();
164
+
165
+ const certificateFile = path.join(pkiFolder, `server_certificate1.pem`);
166
+ if (!fs.existsSync(certificateFile)) {
167
+ console.log("Creating self-signed certificate");
168
+
169
+ await serverCertificateManager.createSelfSignedCertificate({
170
+ applicationUri: applicationUri,
171
+ dns: argv.alternateHostname ? [argv.alternateHostname, fqdn] : [fqdn],
172
+ ip: await getIpAddresses(),
173
+ outputFile: certificateFile,
174
+ subject: "/CN=Sterfive/DC=Test",
175
+ startDate: new Date(),
176
+ validity: 365 * 10
177
+ });
178
+ }
179
+ assert(fs.existsSync(certificateFile));
180
+ // ------------------------------------------------------------------
181
+
182
+ const server_options = {
183
+ serverCertificateManager,
184
+ certificateFile,
185
+
186
+ userCertificateManager,
187
+
188
+ port,
189
+
190
+ maxAllowedSessionNumber: maxAllowedSessionNumber,
191
+ maxConnectionsPerEndpoint: maxConnectionsPerEndpoint,
192
+
193
+ nodeset_filename: [nodesets.standard, nodesets.di],
194
+
195
+ serverInfo: {
196
+ applicationName: { text: "NodeOPCUA", locale: "en" },
197
+ applicationUri: applicationUri,
198
+ gatewayServerUri: null,
199
+ productUri: productUri,
200
+ discoveryProfileUri: null,
201
+ discoveryUrls: []
202
+ },
203
+ buildInfo: {
204
+ buildNumber: "1234"
205
+ },
206
+ serverCapabilities: {
207
+ maxBrowseContinuationPoints: 10,
208
+ maxHistoryContinuationPoints: 10,
209
+ // maxInactiveLockTime
210
+ operationLimits: {
211
+ maxNodesPerRead: 1000,
212
+ maxNodesPerWrite: 1000,
213
+ maxNodesPerHistoryReadData: 100,
214
+ maxNodesPerBrowse: 1000,
215
+ maxNodesPerMethodCall: 200
216
+ }
217
+ },
218
+ userManager: userManager,
219
+
220
+ isAuditing: false,
221
+ //xx registerServerMethod: RegisterServerMethod.HIDDEN,
222
+ //xx registerServerMethod: RegisterServerMethod.MDNS,
223
+ registerServerMethod: RegisterServerMethod.LDS,
224
+ discoveryServerEndpointUrl: "opc.tcp://localhost:4840"
225
+ };
226
+
227
+ process.title = "Node OPCUA Server on port : " + server_options.port;
228
+ server_options.alternateHostname = argv.alternateHostname;
229
+ const server = new OPCUAServer(server_options);
230
+
231
+ const hostname = require("os").hostname();
232
+
233
+ await server.initialize();
234
+
235
+ function post_initialize() {
236
+ const addressSpace = server.engine.addressSpace;
237
+
238
+ build_address_space_for_conformance_testing(addressSpace);
239
+
240
+ install_optional_cpu_and_memory_usage_node(server);
241
+
242
+ addressSpace.installAlarmsAndConditionsService();
243
+
244
+ const rootFolder = addressSpace.findNode("RootFolder");
245
+ assert(rootFolder.browseName.toString() === "Root");
246
+
247
+ const namespace = addressSpace.getOwnNamespace();
248
+
249
+ const myDevices = namespace.addFolder(rootFolder.objects, { browseName: "MyDevices" });
250
+
251
+ /*
252
+ * variation 0:
253
+ * ------------
254
+ *
255
+ * Add a variable in folder using a raw Variant.
256
+ * Use this variation when the variable has to be read or written by the OPCUA clients
257
+ */
258
+ const variable0 = namespace.addVariable({
259
+ organizedBy: myDevices,
260
+ browseName: "FanSpeed",
261
+ nodeId: "ns=1;s=FanSpeed",
262
+ dataType: "Double",
263
+ value: new Variant({ dataType: DataType.Double, value: 1000.0 })
264
+ });
265
+
266
+ setInterval(function () {
267
+ const fluctuation = Math.random() * 100 - 50;
268
+ variable0.setValueFromSource(new Variant({ dataType: DataType.Double, value: 1000.0 + fluctuation }));
269
+ }, 10);
270
+
271
+ /*
272
+ * variation 1:
273
+ * ------------
274
+ *
275
+ * Add a variable in folder using a single get function which returns the up to date variable value in Variant.
276
+ * The server will set the timestamps automatically for us.
277
+ * Use this variation when the variable value is controlled by the getter function
278
+ * Avoid using this variation if the variable has to be made writable, as the server will call the getter
279
+ * function prior to returning its value upon client read requests.
280
+ */
281
+ namespace.addVariable({
282
+ organizedBy: myDevices,
283
+ browseName: "PumpSpeed",
284
+ nodeId: "ns=1;s=PumpSpeed",
285
+ dataType: "Double",
286
+ value: {
287
+ /**
288
+ * returns the current value as a Variant
289
+ * @method get
290
+ * @return {Variant}
291
+ */
292
+ get: function () {
293
+ const pump_speed = 200 + 100 * Math.sin(Date.now() / 10000);
294
+ return new Variant({ dataType: DataType.Double, value: pump_speed });
295
+ }
296
+ }
297
+ });
298
+
299
+ namespace.addVariable({
300
+ organizedBy: myDevices,
301
+ browseName: "SomeDate",
302
+ nodeId: "ns=1;s=SomeDate",
303
+ dataType: "DateTime",
304
+ value: {
305
+ get: function () {
306
+ return new Variant({ dataType: DataType.DateTime, value: new Date(Date.UTC(2016, 9, 13, 8, 40, 0)) });
307
+ }
308
+ }
309
+ });
310
+
311
+ /*
312
+ * variation 2:
313
+ * ------------
314
+ *
315
+ * Add a variable in folder. This variable gets its value and source timestamps from the provided function.
316
+ * The value and source timestamps are held in a external object.
317
+ * The value and source timestamps are updated on a regular basis using a timer function.
318
+ */
319
+ const external_value_with_sourceTimestamp = new DataValue({
320
+ value: new Variant({ dataType: DataType.Double, value: 10.0 }),
321
+ sourceTimestamp: null,
322
+ sourcePicoseconds: 0
323
+ });
324
+ setInterval(function () {
325
+ external_value_with_sourceTimestamp.value.value = Math.random();
326
+ external_value_with_sourceTimestamp.sourceTimestamp = new Date();
327
+ }, 1000);
328
+
329
+ namespace.addVariable({
330
+ organizedBy: myDevices,
331
+ browseName: "Pressure",
332
+ nodeId: "ns=1;s=Pressure",
333
+ dataType: "Double",
334
+ value: {
335
+ timestamped_get: function () {
336
+ return external_value_with_sourceTimestamp;
337
+ }
338
+ }
339
+ });
340
+
341
+ /*
342
+ * variation 3:
343
+ * ------------
344
+ *
345
+ * Add a variable in a folder. This variable gets its value and source timestamps from the provided
346
+ * asynchronous function.
347
+ * The asynchronous function is called only when needed by the opcua Server read services and monitored item services
348
+ *
349
+ */
226
350
 
227
- const hostname = require("os").hostname();
351
+ namespace.addVariable({
352
+ organizedBy: myDevices,
353
+ browseName: "Temperature",
354
+ nodeId: "s=Temperature",
355
+ dataType: "Double",
356
+
357
+ value: {
358
+ refreshFunc: function (callback) {
359
+ const temperature = 20 + 10 * Math.sin(Date.now() / 10000);
360
+ const value = new Variant({ dataType: DataType.Double, value: temperature });
361
+ const sourceTimestamp = new Date();
362
+
363
+ // simulate a asynchronous behaviour
364
+ setTimeout(function () {
365
+ callback(null, new DataValue({ value: value, sourceTimestamp: sourceTimestamp }));
366
+ }, 100);
367
+ }
368
+ }
369
+ });
370
+
371
+ // UAAnalogItem
372
+ // add a UAAnalogItem
373
+ const node = namespace.addAnalogDataItem({
374
+ organizedBy: myDevices,
375
+
376
+ nodeId: "s=TemperatureAnalogItem",
377
+ browseName: "TemperatureAnalogItem",
378
+ definition: "(tempA -25) + tempB",
379
+ valuePrecision: 0.5,
380
+ engineeringUnitsRange: { low: 100, high: 200 },
381
+ instrumentRange: { low: -100, high: +200 },
382
+ engineeringUnits: standardUnits.degree_celsius,
383
+ dataType: "Double",
384
+ value: {
385
+ get: function () {
386
+ return new Variant({ dataType: DataType.Double, value: Math.random() + 19.0 });
387
+ }
388
+ }
389
+ });
390
+
391
+ const m3x3 = namespace.addVariable({
392
+ organizedBy: addressSpace.rootFolder.objects,
393
+ nodeId: "s=Matrix",
394
+ browseName: "Matrix",
395
+ dataType: "Double",
396
+ valueRank: 2,
397
+ arrayDimensions: [3, 3],
398
+ value: {
399
+ get: function () {
400
+ return new Variant({
401
+ dataType: DataType.Double,
402
+ arrayType: VariantArrayType.Matrix,
403
+ dimensions: [3, 3],
404
+ value: [1, 2, 3, 4, 5, 6, 7, 8, 9]
405
+ });
406
+ }
407
+ }
408
+ });
409
+
410
+ const xyz = namespace.addVariable({
411
+ organizedBy: addressSpace.rootFolder.objects,
412
+ nodeId: "s=Position",
413
+ browseName: "Position",
414
+ dataType: "Double",
415
+ valueRank: 1,
416
+ arrayDimensions: null,
417
+ value: {
418
+ get: function () {
419
+ return new Variant({
420
+ dataType: DataType.Double,
421
+ arrayType: VariantArrayType.Array,
422
+ value: [1, 2, 3, 4]
423
+ });
424
+ }
425
+ }
426
+ });
427
+
428
+ //------------------------------------------------------------------------------
429
+ // Add a view
430
+ //------------------------------------------------------------------------------
431
+ const view = namespace.addView({
432
+ organizedBy: rootFolder.views,
433
+ browseName: "MyView"
434
+ });
435
+
436
+ view.addReference({
437
+ referenceType: "Organizes",
438
+ nodeId: node.nodeId
439
+ });
440
+ }
228
441
 
229
- await server.initialize();
442
+ post_initialize();
230
443
 
231
- function post_initialize() {
444
+ function dumpObject(node) {
445
+ function w(str, width) {
446
+ return ("" + str).padEnd(width).substring(0, width);
447
+ }
448
+ return Object.entries(node)
449
+ .map((key, value) => " " + w(key, 30) + " : " + (value === null ? null : value.toString()))
450
+ .join("\n");
451
+ }
232
452
 
233
- const addressSpace = server.engine.addressSpace;
453
+ console.log(chalk.yellow(" server PID :"), process.pid);
454
+ console.log(chalk.yellow(" silent :"), argv.silent);
234
455
 
235
- build_address_space_for_conformance_testing(addressSpace);
456
+ await server.start();
236
457
 
237
- install_optional_cpu_and_memory_usage_node(server);
458
+ console.log(chalk.yellow("\nregistering server to :") + server.discoveryServerEndpointUrl);
238
459
 
239
- addressSpace.installAlarmsAndConditionsService();
460
+ const endpointUrl = server.getEndpointUrl();
240
461
 
241
- const rootFolder = addressSpace.findNode("RootFolder");
242
- assert(rootFolder.browseName.toString() === "Root");
462
+ console.log(chalk.yellow(" server on port :"), server.endpoints[0].port.toString());
463
+ console.log(chalk.yellow(" endpointUrl :"), endpointUrl);
243
464
 
244
- const namespace = addressSpace.getOwnNamespace();
465
+ console.log(chalk.yellow(" serverInfo :"));
466
+ console.log(dumpObject(server.serverInfo));
467
+ console.log(chalk.yellow(" buildInfo :"));
468
+ console.log(dumpObject(server.engine.buildInfo));
245
469
 
246
- const myDevices = namespace.addFolder(rootFolder.objects, { browseName: "MyDevices" });
470
+ console.log(chalk.yellow(" Certificate rejected folder "), server.serverCertificateManager.rejectedFolder);
471
+ console.log(chalk.yellow(" Certificate trusted folder "), server.serverCertificateManager.trustedFolder);
472
+ console.log(chalk.yellow(" Server private key "), server.serverCertificateManager.privateKey);
473
+ console.log(chalk.yellow(" Server public key "), server.certificateFile);
474
+ console.log(chalk.yellow(" X509 User rejected folder "), server.userCertificateManager.trustedFolder);
475
+ console.log(chalk.yellow(" X509 User trusted folder "), server.userCertificateManager.rejectedFolder);
247
476
 
477
+ console.log(chalk.yellow("\n server now waiting for connections. CTRL+C to stop"));
248
478
 
249
- /*
250
- * variation 0:
251
- * ------------
252
- *
253
- * Add a variable in folder using a raw Variant.
254
- * Use this variation when the variable has to be read or written by the OPCUA clients
255
- */
256
- const variable0 = namespace.addVariable({
257
- organizedBy: myDevices,
258
- browseName: "FanSpeed",
259
- nodeId: "ns=1;s=FanSpeed",
260
- dataType: "Double",
261
- value: new Variant({ dataType: DataType.Double, value: 1000.0 })
479
+ if (argv.silent) {
480
+ console.log(" silent");
481
+ console.log = function () {
482
+ /** */
483
+ };
484
+ }
485
+ // console.log = function(){};
486
+
487
+ server.on("create_session", function (session) {
488
+ console.log(" SESSION CREATED");
489
+ console.log(chalk.cyan(" client application URI: "), session.clientDescription.applicationUri);
490
+ console.log(chalk.cyan(" client product URI: "), session.clientDescription.productUri);
491
+ console.log(chalk.cyan(" client application name: "), session.clientDescription.applicationName.toString());
492
+ console.log(chalk.cyan(" client application type: "), session.clientDescription.applicationType.toString());
493
+ console.log(chalk.cyan(" session name: "), session.sessionName ? session.sessionName.toString() : "<null>");
494
+ console.log(chalk.cyan(" session timeout: "), session.sessionTimeout);
495
+ console.log(chalk.cyan(" session id: "), session.sessionId);
262
496
  });
263
497
 
264
- setInterval(function() {
265
- const fluctuation = Math.random() * 100 - 50;
266
- variable0.setValueFromSource(new Variant({ dataType: DataType.Double, value: 1000.0 + fluctuation }));
267
- }, 10);
268
-
269
-
270
- /*
271
- * variation 1:
272
- * ------------
273
- *
274
- * Add a variable in folder using a single get function which returns the up to date variable value in Variant.
275
- * The server will set the timestamps automatically for us.
276
- * Use this variation when the variable value is controlled by the getter function
277
- * Avoid using this variation if the variable has to be made writable, as the server will call the getter
278
- * function prior to returning its value upon client read requests.
279
- */
280
- namespace.addVariable({
281
- organizedBy: myDevices,
282
- browseName: "PumpSpeed",
283
- nodeId: "ns=1;s=PumpSpeed",
284
- dataType: "Double",
285
- value: {
286
- /**
287
- * returns the current value as a Variant
288
- * @method get
289
- * @return {Variant}
290
- */
291
- get: function() {
292
- const pump_speed = 200 + 100 * Math.sin(Date.now() / 10000);
293
- return new Variant({ dataType: DataType.Double, value: pump_speed });
294
- }
295
- }
498
+ server.on("session_closed", function (session, reason) {
499
+ console.log(" SESSION CLOSED :", reason);
500
+ console.log(chalk.cyan(" session name: "), session.sessionName ? session.sessionName.toString() : "<null>");
296
501
  });
297
502
 
298
- namespace.addVariable({
299
- organizedBy: myDevices,
300
- browseName: "SomeDate",
301
- nodeId: "ns=1;s=SomeDate",
302
- dataType: "DateTime",
303
- value: {
304
- get: function() {
305
- return new Variant({ dataType: DataType.DateTime, value: new Date(Date.UTC(2016, 9, 13, 8, 40, 0)) });
503
+ function w(s, w) {
504
+ return (" " + s).padStart(w, "0");
505
+ }
506
+ function t(d) {
507
+ return w(d.getHours(), 2) + ":" + w(d.getMinutes(), 2) + ":" + w(d.getSeconds(), 2) + ":" + w(d.getMilliseconds(), 3);
508
+ }
509
+ function indent(str, nb) {
510
+ const spacer = " ".slice(0, nb);
511
+ return str
512
+ .split("\n")
513
+ .map(function (s) {
514
+ return spacer + s;
515
+ })
516
+ .join("\n");
517
+ }
518
+ function isIn(obj, arr) {
519
+ try {
520
+ return arr.findIndex((a) => a === obj.constructor.name.replace(/Response|Request/, "")) >= 0;
521
+ } catch (err) {
522
+ return true;
306
523
  }
307
- }
308
- });
309
-
524
+ }
310
525
 
311
- /*
312
- * variation 2:
313
- * ------------
314
- *
315
- * Add a variable in folder. This variable gets its value and source timestamps from the provided function.
316
- * The value and source timestamps are held in a external object.
317
- * The value and source timestamps are updated on a regular basis using a timer function.
318
- */
319
- const external_value_with_sourceTimestamp = new DataValue({
320
- value: new Variant({ dataType: DataType.Double, value: 10.0 }),
321
- sourceTimestamp: null,
322
- sourcePicoseconds: 0
323
- });
324
- setInterval(function() {
325
- external_value_with_sourceTimestamp.value.value = Math.random();
326
- external_value_with_sourceTimestamp.sourceTimestamp = new Date();
327
- }, 1000);
328
-
329
- namespace.addVariable({
330
- organizedBy: myDevices,
331
- browseName: "Pressure",
332
- nodeId: "ns=1;s=Pressure",
333
- dataType: "Double",
334
- value: {
335
- timestamped_get: function() {
336
- return external_value_with_sourceTimestamp;
526
+ const servicesToTrace = ["Publish", "TransferSubscriptions", "Republish", "CreateSubscription", "CreateMonitoredItems"];
527
+ server.on("response", function (response) {
528
+ if (argv.silent) {
529
+ return;
337
530
  }
338
- }
339
- });
340
-
341
-
342
- /*
343
- * variation 3:
344
- * ------------
345
- *
346
- * Add a variable in a folder. This variable gets its value and source timestamps from the provided
347
- * asynchronous function.
348
- * The asynchronous function is called only when needed by the opcua Server read services and monitored item services
349
- *
350
- */
351
-
352
- namespace.addVariable({
353
- organizedBy: myDevices,
354
- browseName: "Temperature",
355
- nodeId: "s=Temperature",
356
- dataType: "Double",
357
-
358
- value: {
359
- refreshFunc: function(callback) {
360
-
361
- const temperature = 20 + 10 * Math.sin(Date.now() / 10000);
362
- const value = new Variant({ dataType: DataType.Double, value: temperature });
363
- const sourceTimestamp = new Date();
364
-
365
- // simulate a asynchronous behaviour
366
- setTimeout(function() {
367
- callback(null, new DataValue({ value: value, sourceTimestamp: sourceTimestamp }));
368
- }, 100);
531
+ if (isIn(response, servicesToTrace)) {
532
+ console.log(
533
+ t(response.responseHeader.timestamp),
534
+ response.responseHeader.requestHandle,
535
+ response.schema.name.padEnd(30, " "),
536
+ " status = ",
537
+ response.responseHeader.serviceResult.toString()
538
+ );
539
+ console.log(response.constructor.name, response.toString());
369
540
  }
370
- }
371
541
  });
372
542
 
373
- // UAAnalogItem
374
- // add a UAAnalogItem
375
- const node = namespace.addAnalogDataItem({
376
-
377
- organizedBy: myDevices,
378
-
379
- nodeId: "s=TemperatureAnalogItem",
380
- browseName: "TemperatureAnalogItem",
381
- definition: "(tempA -25) + tempB",
382
- valuePrecision: 0.5,
383
- engineeringUnitsRange: { low: 100, high: 200 },
384
- instrumentRange: { low: -100, high: +200 },
385
- engineeringUnits: standardUnits.degree_celsius,
386
- dataType: "Double",
387
- value: {
388
- get: function() {
389
- return new Variant({ dataType: DataType.Double, value: Math.random() + 19.0 });
543
+ server.on("request", function (request, channel) {
544
+ if (argv.silent) {
545
+ return;
390
546
  }
391
- }
392
- });
393
-
394
-
395
- const m3x3 = namespace.addVariable({
396
- organizedBy: addressSpace.rootFolder.objects,
397
- nodeId: "s=Matrix",
398
- browseName: "Matrix",
399
- dataType: "Double",
400
- valueRank: 2,
401
- arrayDimensions: [3, 3],
402
- value: {
403
- get: function() {
404
- return new Variant({
405
- dataType: DataType.Double,
406
- arrayType: VariantArrayType.Matrix,
407
- dimensions: [3, 3],
408
- value: [1, 2, 3, 4, 5, 6, 7, 8, 9]
409
- });
547
+ if (isIn(request, servicesToTrace)) {
548
+ console.log(
549
+ t(request.requestHeader.timestamp),
550
+ request.requestHeader.requestHandle,
551
+ request.schema.name.padEnd(30, " "),
552
+ " ID =",
553
+ channel.channelId.toString()
554
+ );
555
+ console.log(request.constructor.name, request.toString());
410
556
  }
411
- }
412
557
  });
413
558
 
414
- const xyz = namespace.addVariable({
415
- organizedBy: addressSpace.rootFolder.objects,
416
- nodeId: "s=Position",
417
- browseName: "Position",
418
- dataType: "Double",
419
- valueRank: 1,
420
- arrayDimensions: null,
421
- value: {
422
- get: function() {
423
- return new Variant({
424
- dataType: DataType.Double,
425
- arrayType: VariantArrayType.Array,
426
- value: [1, 2, 3, 4]
427
- });
428
- }
429
- }
559
+ process.once("SIGINT", function () {
560
+ // only work on linux apparently
561
+ console.error(chalk.red.bold(" Received server interruption from user "));
562
+ console.error(chalk.red.bold(" shutting down ..."));
563
+ server.shutdown(1000, function () {
564
+ console.error(chalk.red.bold(" shutting down completed "));
565
+ console.error(chalk.red.bold(" done "));
566
+ console.error("");
567
+ process.exit(-1);
568
+ });
430
569
  });
431
570
 
432
-
433
- //------------------------------------------------------------------------------
434
- // Add a view
435
- //------------------------------------------------------------------------------
436
- const view = namespace.addView({
437
- organizedBy: rootFolder.views,
438
- browseName: "MyView"
571
+ server.on("serverRegistered", () => {
572
+ console.log("server has been registered");
439
573
  });
440
-
441
- view.addReference({
442
- referenceType: "Organizes",
443
- nodeId: node.nodeId
574
+ server.on("serverUnregistered", () => {
575
+ console.log("server has been unregistered");
444
576
  });
445
-
446
- }
447
-
448
- post_initialize();
449
-
450
-
451
- function dumpObject(node) {
452
- function w(str, width) {
453
- const tmp = str + " ";
454
- return tmp.substr(0, width);
455
- }
456
- return Object.entries(node).map((key, value) =>
457
- " " + w(key, 30) + " : " + ((value === null) ? null : value.toString())
458
- ).join("\n");
459
- }
460
-
461
-
462
- console.log(chalk.yellow(" server PID :"), process.pid);
463
- console.log(chalk.yellow(" silent :"), argv.silent);
464
-
465
-
466
- await server.start();
467
-
468
- console.log(chalk.yellow("\nregistering server to :") + server.discoveryServerEndpointUrl);
469
-
470
- const endpointUrl = server.getEndpointUrl();
471
-
472
- console.log(chalk.yellow(" server on port :"), server.endpoints[0].port.toString());
473
- console.log(chalk.yellow(" endpointUrl :"), endpointUrl);
474
-
475
- console.log(chalk.yellow(" serverInfo :"));
476
- console.log(dumpObject(server.serverInfo));
477
- console.log(chalk.yellow(" buildInfo :"));
478
- console.log(dumpObject(server.engine.buildInfo));
479
-
480
- console.log(chalk.yellow(" Certificate rejected folder "), server.serverCertificateManager.rejectedFolder);
481
- console.log(chalk.yellow(" Certificate trusted folder "), server.serverCertificateManager.trustedFolder);
482
- console.log(chalk.yellow(" Server private key "), server.serverCertificateManager.privateKey);
483
- console.log(chalk.yellow(" Server public key "), server.certificateFile);
484
- console.log(chalk.yellow(" X509 User rejected folder "), server.userCertificateManager.trustedFolder);
485
- console.log(chalk.yellow(" X509 User trusted folder "), server.userCertificateManager.rejectedFolder);
486
-
487
-
488
-
489
- console.log(chalk.yellow("\n server now waiting for connections. CTRL+C to stop"));
490
-
491
- if (argv.silent) {
492
- console.log(" silent");
493
- console.log = function() { };
494
- }
495
- // console.log = function(){};
496
-
497
- server.on("create_session", function(session) {
498
- console.log(" SESSION CREATED");
499
- console.log(chalk.cyan(" client application URI: "), session.clientDescription.applicationUri);
500
- console.log(chalk.cyan(" client product URI: "), session.clientDescription.productUri);
501
- console.log(chalk.cyan(" client application name: "), session.clientDescription.applicationName.toString());
502
- console.log(chalk.cyan(" client application type: "), session.clientDescription.applicationType.toString());
503
- console.log(chalk.cyan(" session name: "), session.sessionName ? session.sessionName.toString() : "<null>");
504
- console.log(chalk.cyan(" session timeout: "), session.sessionTimeout);
505
- console.log(chalk.cyan(" session id: "), session.sessionId);
506
- });
507
-
508
- server.on("session_closed", function(session, reason) {
509
- console.log(" SESSION CLOSED :", reason);
510
- console.log(chalk.cyan(" session name: "), session.sessionName ? session.sessionName.toString() : "<null>");
511
- });
512
-
513
- function w(s, w) {
514
- return ("000" + s).substr(-w);
515
- }
516
- function t(d) {
517
- return w(d.getHours(), 2) + ":" + w(d.getMinutes(), 2) + ":" + w(d.getSeconds(), 2) + ":" + w(d.getMilliseconds(), 3);
518
- }
519
- function indent(str, nb) {
520
- const spacer = " ".slice(0, nb);
521
- return str.split("\n").map(function(s) {
522
- return spacer + s;
523
- }).join("\n");
524
- }
525
- function isIn(obj, arr) {
526
- try {
527
- return arr.findIndex((a) => a === obj.constructor.name.replace(/Response|Request/, "")) >= 0;
528
-
529
- } catch (err) {
530
- return true;
531
- }
532
- }
533
-
534
- const servicesToTrace = ["Publish", "TransferSubscriptions", "Republish", "CreateSubscription", "CreateMonitoredItems"];
535
- server.on("response", function(response) {
536
-
537
- if (argv.silent) { return; }
538
- if (isIn(response, servicesToTrace)) {
539
- console.log(t(response.responseHeader.timestamp), response.responseHeader.requestHandle,
540
- response.schema.name.padEnd(30, " "), " status = ", response.responseHeader.serviceResult.toString());
541
- console.log(response.constructor.name, response.toString());
542
- }
543
- });
544
-
545
- server.on("request", function(request, channel) {
546
- if (argv.silent) { return; }
547
- if (isIn(request, servicesToTrace)) {
548
- console.log(t(request.requestHeader.timestamp), request.requestHeader.requestHandle,
549
- request.schema.name.padEnd(30, " "), " ID =", channel.channelId.toString());
550
- console.log(request.constructor.name, request.toString());
551
- }
552
- });
553
-
554
- process.on("SIGINT", function() {
555
- // only work on linux apparently
556
- console.error(chalk.red.bold(" Received server interruption from user "));
557
- console.error(chalk.red.bold(" shutting down ..."));
558
- server.shutdown(1000, function() {
559
- console.error(chalk.red.bold(" shutting down completed "));
560
- console.error(chalk.red.bold(" done "));
561
- console.error("");
562
- process.exit(-1);
577
+ server.on("serverRegistrationRenewed", () => {
578
+ console.log("server registration has been renewed");
579
+ });
580
+ server.on("serverRegistrationPending", () => {
581
+ console.log("server registration is still pending (is Local Discovery Server up and running ?)");
582
+ });
583
+ server.on("newChannel", (channel) => {
584
+ console.log(
585
+ chalk.bgYellow("Client connected with address = "),
586
+ channel.remoteAddress,
587
+ " port = ",
588
+ channel.remotePort,
589
+ "timeout=",
590
+ channel.timeout
591
+ );
592
+ });
593
+ server.on("closeChannel", (channel) => {
594
+ console.log(chalk.bgCyan("Client disconnected with address = "), channel.remoteAddress, " port = ", channel.remotePort);
595
+ if (global.gc) {
596
+ global.gc();
597
+ }
563
598
  });
564
- });
565
-
566
- server.on("serverRegistered", () => {
567
- console.log("server has been registered");
568
- });
569
- server.on("serverUnregistered", () => {
570
- console.log("server has been unregistered");
571
- });
572
- server.on("serverRegistrationRenewed", () => {
573
- console.log("server registration has been renewed");
574
- });
575
- server.on("serverRegistrationPending", () => {
576
- console.log("server registration is still pending (is Local Discovery Server up and running ?)");
577
- });
578
- server.on("newChannel", (channel) => {
579
- console.log(chalk.bgYellow("Client connected with address = "), channel.remoteAddress, " port = ", channel.remotePort, "timeout=", channel.timeout);
580
- });
581
- server.on("closeChannel", (channel) => {
582
- console.log(chalk.bgCyan("Client disconnected with address = "), channel.remoteAddress, " port = ", channel.remotePort);
583
- if (global.gc) {
584
- global.gc();
585
- }
586
- });
587
-
588
599
  })();