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.
- package/LICENSE +20 -20
- package/bin/createOPCUACertificate.cmd +5 -5
- package/bin/create_certificates.js +2 -2
- package/bin/crypto_create_CA.js +2 -2
- package/bin/demo_server_with_alarm.js +51 -51
- package/bin/di_server.js +318 -318
- package/bin/findServersOnNetwork.js +32 -32
- package/bin/interactive_client.js +758 -758
- package/bin/machineryServer.js +83 -83
- package/bin/more.js +40 -40
- package/bin/node-opcua.js +2 -2
- package/bin/opcua_interceptor.js +135 -135
- package/bin/simple_client.js +830 -830
- package/bin/simple_server.js +588 -588
- package/package.json +4 -4
package/bin/simple_server.js
CHANGED
|
@@ -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
|
+
})();
|