node-opcua-samples 2.56.3 → 2.57.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.
@@ -1,588 +1,588 @@
1
- #!/usr/bin / env node
2
- /* eslint-disable max-statements */
3
- /* eslint no-process-exit: 0 */
4
- "use strict";
5
- const path = require("path");
6
- const fs = require("fs");
7
- const assert = require("assert");
8
- const chalk = require("chalk");
9
- const yargs = require("yargs/yargs");
10
- const envPaths = require("env-paths");
11
-
12
- 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
26
- } = require("node-opcua");
27
-
28
- Error.stackTraceLimit = Infinity;
29
-
30
-
31
- const argv = yargs(process.argv)
32
- .wrap(132)
33
-
34
- .string("alternateHostname")
35
- .describe("alternateHostname")
36
-
37
- .number("port")
38
- .default("port", 26543)
39
-
40
- .number("maxAllowedSessionNumber")
41
- .describe("maxAllowedSessionNumber", "the maximum number of concurrent client session that the server will accept")
42
- .default("maxAllowedSessionNumber", 500)
43
-
44
- .number("maxAllowedSubscriptionNumber")
45
- .describe("maxAllowedSubscriptionNumber", "the maximum number of concurrent subscriptions")
46
-
47
- .boolean("silent")
48
- .default("silent", false)
49
- .describe("silent", "no trace")
50
-
51
-
52
- .string("alternateHostname")
53
- .default("alternateHostname", null)
54
-
55
- .number("keySize")
56
- .describe("keySize", "certificate keySize [1024|2048|3072|4096]")
57
- .default("keySize", 2048)
58
- .alias("k", "keySize")
59
-
60
- .string("applicationName")
61
- .describe("applicationName", "the application name")
62
- .default("applicationName", "NodeOPCUA-Server")
63
-
64
- .alias("a", "alternateHostname")
65
- .alias("m", "maxAllowedSessionNumber")
66
- .alias("n", "applicationName")
67
- .alias("p", "port")
68
-
69
- .help(true)
70
- .argv;
71
-
72
- const port = argv.port;
73
- const maxAllowedSessionNumber = argv.maxAllowedSessionNumber;
74
- const maxConnectionsPerEndpoint = maxAllowedSessionNumber;
75
- const maxAllowedSubscriptionNumber = argv.maxAllowedSubscriptionNumber || 50;
76
- OPCUAServer.MAX_SUBSCRIPTION = maxAllowedSubscriptionNumber;
77
-
78
-
79
- const os = require('os');
80
-
81
- 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;
103
- });
104
- });
105
- return ipAddresses;
106
- }
107
-
108
- const userManager = {
109
- isValidUser: function(userName, password) {
110
-
111
- if (userName === "user1" && password === "password1") {
112
- return true;
113
- }
114
- if (userName === "user2" && password === "password2") {
115
- return true;
116
- }
117
- return false;
118
- }
119
- };
120
-
121
- const keySize = argv.keySize;
122
-
123
- const productUri = argv.applicationName || "NodeOPCUASample-Simple-Server";
124
-
125
- const paths = envPaths(productUri);
126
-
127
- (async function main() {
128
-
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
- };
222
-
223
- process.title = "Node OPCUA Server on port : " + server_options.port;
224
- server_options.alternateHostname = argv.alternateHostname;
225
- const server = new OPCUAServer(server_options);
226
-
227
- const hostname = require("os").hostname();
228
-
229
- await server.initialize();
230
-
231
- function post_initialize() {
232
-
233
- const addressSpace = server.engine.addressSpace;
234
-
235
- build_address_space_for_conformance_testing(addressSpace);
236
-
237
- install_optional_cpu_and_memory_usage_node(server);
238
-
239
- addressSpace.installAlarmsAndConditionsService();
240
-
241
- const rootFolder = addressSpace.findNode("RootFolder");
242
- assert(rootFolder.browseName.toString() === "Root");
243
-
244
- const namespace = addressSpace.getOwnNamespace();
245
-
246
- const myDevices = namespace.addFolder(rootFolder.objects, { browseName: "MyDevices" });
247
-
248
-
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 })
262
- });
263
-
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
- }
296
- });
297
-
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)) });
306
- }
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
- /*
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);
369
- }
370
- }
371
- });
372
-
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 });
390
- }
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
- });
410
- }
411
- }
412
- });
413
-
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
- }
430
- });
431
-
432
-
433
- //------------------------------------------------------------------------------
434
- // Add a view
435
- //------------------------------------------------------------------------------
436
- const view = namespace.addView({
437
- organizedBy: rootFolder.views,
438
- browseName: "MyView"
439
- });
440
-
441
- view.addReference({
442
- referenceType: "Organizes",
443
- nodeId: node.nodeId
444
- });
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);
563
- });
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
- })();
1
+ #!/usr/bin / env node
2
+ /* eslint-disable max-statements */
3
+ /* eslint no-process-exit: 0 */
4
+ "use strict";
5
+ const path = require("path");
6
+ const fs = require("fs");
7
+ const assert = require("assert");
8
+ const chalk = require("chalk");
9
+ const yargs = require("yargs/yargs");
10
+ const envPaths = require("env-paths");
11
+
12
+ 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
26
+ } = require("node-opcua");
27
+
28
+ Error.stackTraceLimit = Infinity;
29
+
30
+
31
+ const argv = yargs(process.argv)
32
+ .wrap(132)
33
+
34
+ .string("alternateHostname")
35
+ .describe("alternateHostname")
36
+
37
+ .number("port")
38
+ .default("port", 26543)
39
+
40
+ .number("maxAllowedSessionNumber")
41
+ .describe("maxAllowedSessionNumber", "the maximum number of concurrent client session that the server will accept")
42
+ .default("maxAllowedSessionNumber", 500)
43
+
44
+ .number("maxAllowedSubscriptionNumber")
45
+ .describe("maxAllowedSubscriptionNumber", "the maximum number of concurrent subscriptions")
46
+
47
+ .boolean("silent")
48
+ .default("silent", false)
49
+ .describe("silent", "no trace")
50
+
51
+
52
+ .string("alternateHostname")
53
+ .default("alternateHostname", null)
54
+
55
+ .number("keySize")
56
+ .describe("keySize", "certificate keySize [1024|2048|3072|4096]")
57
+ .default("keySize", 2048)
58
+ .alias("k", "keySize")
59
+
60
+ .string("applicationName")
61
+ .describe("applicationName", "the application name")
62
+ .default("applicationName", "NodeOPCUA-Server")
63
+
64
+ .alias("a", "alternateHostname")
65
+ .alias("m", "maxAllowedSessionNumber")
66
+ .alias("n", "applicationName")
67
+ .alias("p", "port")
68
+
69
+ .help(true)
70
+ .argv;
71
+
72
+ const port = argv.port;
73
+ const maxAllowedSessionNumber = argv.maxAllowedSessionNumber;
74
+ const maxConnectionsPerEndpoint = maxAllowedSessionNumber;
75
+ const maxAllowedSubscriptionNumber = argv.maxAllowedSubscriptionNumber || 50;
76
+ OPCUAServer.MAX_SUBSCRIPTION = maxAllowedSubscriptionNumber;
77
+
78
+
79
+ const os = require('os');
80
+
81
+ 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;
103
+ });
104
+ });
105
+ return ipAddresses;
106
+ }
107
+
108
+ const userManager = {
109
+ isValidUser: function(userName, password) {
110
+
111
+ if (userName === "user1" && password === "password1") {
112
+ return true;
113
+ }
114
+ if (userName === "user2" && password === "password2") {
115
+ return true;
116
+ }
117
+ return false;
118
+ }
119
+ };
120
+
121
+ const keySize = argv.keySize;
122
+
123
+ const productUri = argv.applicationName || "NodeOPCUASample-Simple-Server";
124
+
125
+ const paths = envPaths(productUri);
126
+
127
+ (async function main() {
128
+
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
+ };
222
+
223
+ process.title = "Node OPCUA Server on port : " + server_options.port;
224
+ server_options.alternateHostname = argv.alternateHostname;
225
+ const server = new OPCUAServer(server_options);
226
+
227
+ const hostname = require("os").hostname();
228
+
229
+ await server.initialize();
230
+
231
+ function post_initialize() {
232
+
233
+ const addressSpace = server.engine.addressSpace;
234
+
235
+ build_address_space_for_conformance_testing(addressSpace);
236
+
237
+ install_optional_cpu_and_memory_usage_node(server);
238
+
239
+ addressSpace.installAlarmsAndConditionsService();
240
+
241
+ const rootFolder = addressSpace.findNode("RootFolder");
242
+ assert(rootFolder.browseName.toString() === "Root");
243
+
244
+ const namespace = addressSpace.getOwnNamespace();
245
+
246
+ const myDevices = namespace.addFolder(rootFolder.objects, { browseName: "MyDevices" });
247
+
248
+
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 })
262
+ });
263
+
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
+ }
296
+ });
297
+
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)) });
306
+ }
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
+ /*
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);
369
+ }
370
+ }
371
+ });
372
+
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 });
390
+ }
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
+ });
410
+ }
411
+ }
412
+ });
413
+
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
+ }
430
+ });
431
+
432
+
433
+ //------------------------------------------------------------------------------
434
+ // Add a view
435
+ //------------------------------------------------------------------------------
436
+ const view = namespace.addView({
437
+ organizedBy: rootFolder.views,
438
+ browseName: "MyView"
439
+ });
440
+
441
+ view.addReference({
442
+ referenceType: "Organizes",
443
+ nodeId: node.nodeId
444
+ });
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);
563
+ });
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
+ })();