node-opcua-samples 2.56.3 → 2.60.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 +7 -7
|
@@ -1,758 +1,758 @@
|
|
|
1
|
-
/* eslint-disable no-case-declarations */
|
|
2
|
-
#!/usr/bin/env node
|
|
3
|
-
/* eslint no-process-exit: 0 */
|
|
4
|
-
"use strict";
|
|
5
|
-
const chalk = require("chalk");
|
|
6
|
-
const treeify = require("treeify");
|
|
7
|
-
const sprintf = require("sprintf-js").sprintf;
|
|
8
|
-
const util = require("util");
|
|
9
|
-
const fs = require("fs");
|
|
10
|
-
const path = require("path");
|
|
11
|
-
const _ = require("underscore");
|
|
12
|
-
|
|
13
|
-
const opcua = require("node-opcua");
|
|
14
|
-
const UAProxyManager = opcua.UAProxyManager;
|
|
15
|
-
const DataType = opcua.DataType;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const utils = opcua.utils;
|
|
20
|
-
|
|
21
|
-
const { assert } = require("node-opcua-assert");
|
|
22
|
-
|
|
23
|
-
console.log(" Version ", opcua.version);
|
|
24
|
-
|
|
25
|
-
const sessionTimeout = 2 * 60 * 1000; // 2 minutes
|
|
26
|
-
|
|
27
|
-
const client = opcua.OPCUAClient.create({
|
|
28
|
-
requestedSessionTimeout: sessionTimeout,
|
|
29
|
-
keepSessionAlive: true
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
let the_session = null;
|
|
33
|
-
let proxyManager = null;
|
|
34
|
-
|
|
35
|
-
let crawler = null;
|
|
36
|
-
let dumpPacket = false;
|
|
37
|
-
const dumpMessageChunk = false;
|
|
38
|
-
let endpoints_history = [];
|
|
39
|
-
|
|
40
|
-
const endpoints_history_file = path.join(__dirname, ".history_endpoints");
|
|
41
|
-
|
|
42
|
-
let curNode = null;
|
|
43
|
-
let curNodeCompletion = [];
|
|
44
|
-
|
|
45
|
-
function save_endpoint_history(callback) {
|
|
46
|
-
if (endpoints_history.length > 0) {
|
|
47
|
-
fs.writeFileSync(endpoints_history_file, endpoints_history.join("\n"), "ascii");
|
|
48
|
-
}
|
|
49
|
-
if (callback) {
|
|
50
|
-
callback();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function add_endpoint_to_history(endpoint) {
|
|
54
|
-
if (endpoints_history.indexOf(endpoint) >= 0) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
endpoints_history.push(endpoint);
|
|
58
|
-
save_endpoint_history();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let lines = [];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (fs.existsSync(endpoints_history_file)) {
|
|
66
|
-
lines = fs.readFileSync(endpoints_history_file, "ascii");
|
|
67
|
-
endpoints_history = lines.split(/\r\n|\n/);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const history_file = path.join(__dirname, ".history");
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
function completer(line, callback) {
|
|
74
|
-
|
|
75
|
-
let completions, hits;
|
|
76
|
-
|
|
77
|
-
if ((line.trim() === "") && curNode) {
|
|
78
|
-
// console.log(" completions ",completions);
|
|
79
|
-
let c = [".."].concat(curNodeCompletion);
|
|
80
|
-
if (curNodeCompletion.length === 1) {
|
|
81
|
-
c = curNodeCompletion;
|
|
82
|
-
}
|
|
83
|
-
return callback(null, [c, line]);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if ("open".match(new RegExp("^" + line.trim()))) {
|
|
87
|
-
completions = ["open localhost:port"];
|
|
88
|
-
return callback(null, [completions, line]);
|
|
89
|
-
|
|
90
|
-
} else {
|
|
91
|
-
if (the_session === null) {
|
|
92
|
-
if (client._secureChannel) {
|
|
93
|
-
completions = "createSession cs getEndpoints gep quit".split(" ");
|
|
94
|
-
} else {
|
|
95
|
-
completions = "open quit".split(" ");
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
completions = "browse read readall crawl closeSession disconnect quit getEndpoints".split(" ");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
assert(completions.length >= 0);
|
|
102
|
-
hits = completions.filter(function(c) {
|
|
103
|
-
return c.indexOf(line) === 0;
|
|
104
|
-
});
|
|
105
|
-
return callback(null, [hits.length ? hits : completions, line]);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const rl = readline.createInterface({
|
|
110
|
-
input: process.stdin,
|
|
111
|
-
output: process.stdout,
|
|
112
|
-
completer: completer
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
let the_prompt = chalk.cyan(">");
|
|
116
|
-
rl.setPrompt(the_prompt);
|
|
117
|
-
rl.prompt();
|
|
118
|
-
|
|
119
|
-
function save_history(callback) {
|
|
120
|
-
const history_uniq = _.uniq(rl.history);
|
|
121
|
-
fs.writeFileSync(history_file, history_uniq.join("\n"), "ascii");
|
|
122
|
-
callback();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function w(str, width) {
|
|
126
|
-
return (str + " ").substr(0, width);
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* @method toDate
|
|
130
|
-
* @param str
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* toDate("now");
|
|
134
|
-
* toDate("13:00"); => today at 13:00
|
|
135
|
-
* toDate("1 hour ago"); => today one our ago....
|
|
136
|
-
*
|
|
137
|
-
* @return {Date}
|
|
138
|
-
*/
|
|
139
|
-
function toDate(str) {
|
|
140
|
-
console.log(" parsing : '" + str + "'");
|
|
141
|
-
const now = new Date();
|
|
142
|
-
if (!str) {
|
|
143
|
-
return now;
|
|
144
|
-
}
|
|
145
|
-
if (str.toLowerCase() === "now") {
|
|
146
|
-
return now;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// check if provided date is <HH>:<MM>
|
|
150
|
-
|
|
151
|
-
const t = /([0-9]{1,2}):([0-9]{1,2})/;
|
|
152
|
-
const tt = str.match(t);
|
|
153
|
-
if (tt) {
|
|
154
|
-
// HH:MM of current date
|
|
155
|
-
const year = now.getFullYear();
|
|
156
|
-
const month = now.getMonth(); // 0 : jan , 1: feb etc ...
|
|
157
|
-
const day = now.getDate();
|
|
158
|
-
const hours = parseInt(tt[1], 10);
|
|
159
|
-
const minutes = parseInt(tt[2], 10);
|
|
160
|
-
const seconds = 0;
|
|
161
|
-
const date = new Date(year, month, day, hours, minutes, seconds);
|
|
162
|
-
return date;
|
|
163
|
-
}
|
|
164
|
-
// check if provided date looks like "3 hours ago" | "1 day ago" etc...
|
|
165
|
-
const r = /([0-9]*)(day|days|d|hours|hour|h|minutes|minutes|m)\s?((ago)?)/;
|
|
166
|
-
const m = str.match(r);
|
|
167
|
-
if (m) {
|
|
168
|
-
let tvalue = parseInt(m[1], 10);
|
|
169
|
-
switch (m[2][0]) {
|
|
170
|
-
case "d":
|
|
171
|
-
tvalue *= 24 * 3600;
|
|
172
|
-
break;
|
|
173
|
-
case "h":
|
|
174
|
-
tvalue *= 3600;
|
|
175
|
-
break;
|
|
176
|
-
case "m":
|
|
177
|
-
tvalue *= 60;
|
|
178
|
-
break;
|
|
179
|
-
default:
|
|
180
|
-
throw new Error(" invalidate date");
|
|
181
|
-
}
|
|
182
|
-
return new Date(now - tvalue * 1000);
|
|
183
|
-
} else {
|
|
184
|
-
return new Date(str);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
function log() {
|
|
189
|
-
rl.pause();
|
|
190
|
-
rl.clearLine(process.stdout);
|
|
191
|
-
const str = _.map(arguments).join(" ");
|
|
192
|
-
process.stdout.write(str);
|
|
193
|
-
rl.resume();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
let rootFolder = null;
|
|
197
|
-
|
|
198
|
-
let nodePath = [];
|
|
199
|
-
let nodePathName = [];
|
|
200
|
-
const lowerFirstLetter = opcua.utils.lowerFirstLetter;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
function setCurrentNode(node) {
|
|
204
|
-
|
|
205
|
-
curNode = node;
|
|
206
|
-
const curNodeBrowseName = lowerFirstLetter(curNode.browseName.name.toString());
|
|
207
|
-
nodePathName.push(curNodeBrowseName);
|
|
208
|
-
nodePath.push(node);
|
|
209
|
-
curNodeCompletion = node.$components.map(function(c) {
|
|
210
|
-
if (!c.browseName) {
|
|
211
|
-
return "???";
|
|
212
|
-
}
|
|
213
|
-
return lowerFirstLetter(c.browseName.name.toString());
|
|
214
|
-
});
|
|
215
|
-
the_prompt = chalk.yellow(nodePathName.join(".")) + ">";
|
|
216
|
-
rl.setPrompt(the_prompt);
|
|
217
|
-
}
|
|
218
|
-
function setRootNode(node) {
|
|
219
|
-
nodePath = [];
|
|
220
|
-
nodePathName = [];
|
|
221
|
-
setCurrentNode(node);
|
|
222
|
-
}
|
|
223
|
-
function moveToChild(browseName) {
|
|
224
|
-
|
|
225
|
-
if (browseName === "..") {
|
|
226
|
-
nodePathName.pop();
|
|
227
|
-
curNode = nodePath.splice(-1, 1)[0];
|
|
228
|
-
the_prompt = chalk.yellow(nodePathName.join(".")) + ">";
|
|
229
|
-
rl.setPrompt(the_prompt);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const child = curNode[browseName];
|
|
233
|
-
if (!child) {
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
setCurrentNode(child);
|
|
237
|
-
}
|
|
238
|
-
function get_root_folder(callback) {
|
|
239
|
-
|
|
240
|
-
if (!rootFolder) {
|
|
241
|
-
|
|
242
|
-
rl.pause();
|
|
243
|
-
proxyManager.getObject(opcua.makeNodeId(opcua.ObjectIds.RootFolder), function(err, data) {
|
|
244
|
-
|
|
245
|
-
if (!err) {
|
|
246
|
-
rootFolder = data;
|
|
247
|
-
assert(rootFolder, "expecting rootFolder");
|
|
248
|
-
setRootNode(rootFolder);
|
|
249
|
-
rl.resume();
|
|
250
|
-
}
|
|
251
|
-
callback();
|
|
252
|
-
});
|
|
253
|
-
} else {
|
|
254
|
-
setCurrentNode(rootFolder);
|
|
255
|
-
callback();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
client.on("send_chunk", function(message_chunk) {
|
|
261
|
-
if (dumpMessageChunk) {
|
|
262
|
-
process.stdout.write(">> " + message_chunk.length + "\r");
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
client.on("receive_chunk", function(message_chunk) {
|
|
267
|
-
if (dumpMessageChunk) {
|
|
268
|
-
process.stdout.write("<< " + message_chunk.length + "\r");
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
client.on("send_request", function(message) {
|
|
273
|
-
if (dumpPacket) {
|
|
274
|
-
log(chalk.red(" sending request"));
|
|
275
|
-
opcua.analyze_object_binary_encoding(message);
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
client.on("receive_response", function(message) {
|
|
280
|
-
if (dumpPacket) {
|
|
281
|
-
assert(message);
|
|
282
|
-
log(chalk.cyan.bold(" receive response"));
|
|
283
|
-
opcua.analyze_object_binary_encoding(message);
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
function dumpNodeResult(node) {
|
|
289
|
-
const str = sprintf(" %-30s%s%s", node.browseName.name, (node.isForward ? "->" : "<-"), node.nodeId.displayText());
|
|
290
|
-
log(str);
|
|
291
|
-
}
|
|
292
|
-
function colorize(value) {
|
|
293
|
-
return chalk.yellow.bold("" + value);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (rl.history) {
|
|
298
|
-
|
|
299
|
-
if (fs.existsSync(history_file)) {
|
|
300
|
-
lines = fs.readFileSync(history_file, "ascii");
|
|
301
|
-
lines = lines.split(/\r\n|\n/);
|
|
302
|
-
}
|
|
303
|
-
if (lines.length === 0) {
|
|
304
|
-
let hostname = require("os").hostname();
|
|
305
|
-
hostname = hostname.toLowerCase();
|
|
306
|
-
rl.history.push("open opc.tcp://opcua.demo-this.com:51210/UA/SampleServer");
|
|
307
|
-
rl.history.push("open opc.tcp://" + hostname + ":51210/UA/SampleServer");
|
|
308
|
-
rl.history.push("open opc.tcp://" + hostname + ":4841");
|
|
309
|
-
rl.history.push("open opc.tcp://" + "localhost" + ":51210/UA/SampleServer");
|
|
310
|
-
rl.history.push("open opc.tcp://" + hostname + ":6543/UA/SampleServer");
|
|
311
|
-
rl.history.push("open opc.tcp://" + hostname + ":53530/OPCUA/SimulationServer");
|
|
312
|
-
rl.history.push("b ObjectsFolder");
|
|
313
|
-
rl.history.push("r ns=2;s=Furnace_1.Temperature");
|
|
314
|
-
} else {
|
|
315
|
-
rl.history = rl.history.concat(lines);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
process.on("uncaughtException", function(e) {
|
|
320
|
-
util.puts(e.stack.red);
|
|
321
|
-
rl.prompt();
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
function apply_command(cmd, func, callback) {
|
|
328
|
-
callback = callback || function() { };
|
|
329
|
-
rl.pause();
|
|
330
|
-
func(function(err) {
|
|
331
|
-
callback();
|
|
332
|
-
rl.resume();
|
|
333
|
-
rl.prompt(the_prompt);
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function apply_on_valid_session(cmd, func, callback) {
|
|
338
|
-
|
|
339
|
-
assert(typeof func === "function");
|
|
340
|
-
assert(func.length === 2);
|
|
341
|
-
|
|
342
|
-
if (the_session) {
|
|
343
|
-
apply_command(cmd, function(callback) {
|
|
344
|
-
func(the_session, callback);
|
|
345
|
-
});
|
|
346
|
-
} else {
|
|
347
|
-
log("command : ", chalk.yellow(cmd), " requires a valid session , use createSession first");
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function dump_dataValues(nodesToRead, dataValues) {
|
|
352
|
-
for (let i = 0; i < dataValues.length; i++) {
|
|
353
|
-
const dataValue = dataValues[i];
|
|
354
|
-
log(" Node : ", chalk.cyan.bold((nodesToRead[i].nodeId.toString())), nodesToRead[i].attributeId.toString());
|
|
355
|
-
if (dataValue.value) {
|
|
356
|
-
log(" type : ", colorize(DataType[dataValue.value.dataType]));
|
|
357
|
-
log(" value: ", colorize(dataValue.value.value));
|
|
358
|
-
} else {
|
|
359
|
-
log(" value: <null>");
|
|
360
|
-
}
|
|
361
|
-
log(" statusCode: 0x", dataValue.statusCode.toString(16));
|
|
362
|
-
log(" sourceTimestamp: ", dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function dump_historyDataValues(nodeToRead, startDate, endDate, historyReadResult) {
|
|
367
|
-
|
|
368
|
-
log(" Node : ", chalk.cyan.bold((nodeToRead.nodeId.toString())), nodeToRead.attributeId.toString());
|
|
369
|
-
log(" startDate : ", startDate);
|
|
370
|
-
log(" endDate : ", endDate);
|
|
371
|
-
if (historyReadResult.statusCode !== opcua.StatusCodes.Good) {
|
|
372
|
-
log(" error ", historyReadResult.statusCode.toString());
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
log("historyReadResult = ", historyReadResult.toString());
|
|
377
|
-
|
|
378
|
-
const dataValues = historyReadResult.historyData.dataValues;
|
|
379
|
-
log(" Length = ", dataValues.length);
|
|
380
|
-
|
|
381
|
-
if (!dataValues || dataValues.length === 0) {
|
|
382
|
-
log(" No Data");
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
if (dataValues.length > 0 && dataValues[0].value) {
|
|
386
|
-
log(" type : ", colorize(DataType[dataValues[0].value.dataType]));
|
|
387
|
-
}
|
|
388
|
-
for (let i = 0; i < dataValues.length; i++) {
|
|
389
|
-
const dataValue = dataValues[i];
|
|
390
|
-
if (dataValue.value) {
|
|
391
|
-
log(
|
|
392
|
-
dataValue.sourceTimestamp,
|
|
393
|
-
w(dataValue.sourcePicoseconds, 4),
|
|
394
|
-
colorize(w(dataValue.value.value, 15)),
|
|
395
|
-
w(dataValue.statusCode.toString(16), 16));
|
|
396
|
-
} else {
|
|
397
|
-
log(" value: <null>", dataValue.toString());
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function open_session(callback) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (the_session !== null) {
|
|
406
|
-
log(" a session exists already ! use closeSession First");
|
|
407
|
-
return callback();
|
|
408
|
-
|
|
409
|
-
} else {
|
|
410
|
-
|
|
411
|
-
client.requestedSessionTimeout = sessionTimeout;
|
|
412
|
-
client.createSession(function(err, session) {
|
|
413
|
-
if (err) {
|
|
414
|
-
log(chalk.red("Error : "), err);
|
|
415
|
-
} else {
|
|
416
|
-
|
|
417
|
-
the_session = session;
|
|
418
|
-
log("session created ", session.sessionId.toString());
|
|
419
|
-
proxyManager = new UAProxyManager(the_session);
|
|
420
|
-
|
|
421
|
-
the_prompt = chalk.cyan("session:") + chalk.yellow(the_session.sessionId.toString()) + chalk.cyan(">");
|
|
422
|
-
rl.setPrompt(the_prompt);
|
|
423
|
-
|
|
424
|
-
assert(!crawler);
|
|
425
|
-
|
|
426
|
-
rl.prompt(the_prompt);
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
callback();
|
|
430
|
-
});
|
|
431
|
-
client.on("close", function() {
|
|
432
|
-
log(chalk.red(" Server has disconnected "));
|
|
433
|
-
the_session = null;
|
|
434
|
-
crawler = null;
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function close_session(outer_callback) {
|
|
440
|
-
apply_on_valid_session("closeSession", function(session, inner_callback) {
|
|
441
|
-
session.close(function(err) {
|
|
442
|
-
the_session = null;
|
|
443
|
-
crawler = null;
|
|
444
|
-
if (!outer_callback) {
|
|
445
|
-
inner_callback(err);
|
|
446
|
-
} else {
|
|
447
|
-
assert(typeof outer_callback === "function");
|
|
448
|
-
outer_callback(inner_callback);
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function set_debug(flag) {
|
|
455
|
-
if (flag) {
|
|
456
|
-
dumpPacket = true;
|
|
457
|
-
process.env.DEBUG = "ALL";
|
|
458
|
-
log(" Debug is ON");
|
|
459
|
-
} else {
|
|
460
|
-
dumpPacket = true;
|
|
461
|
-
delete process.env.DEBUG;
|
|
462
|
-
log(" Debug is OFF");
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function process_line(line) {
|
|
467
|
-
let nodes;
|
|
468
|
-
const args = line.trim().split(/ +/);
|
|
469
|
-
const cmd = args[0];
|
|
470
|
-
|
|
471
|
-
if (curNode) {
|
|
472
|
-
moveToChild(cmd);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
switch (cmd) {
|
|
476
|
-
case "debug":
|
|
477
|
-
const flag = (!args[1]) ? true : (["ON", "TRUE", "1"].indexOf(args[1].toUpperCase()) >= 0);
|
|
478
|
-
set_debug(flag);
|
|
479
|
-
break;
|
|
480
|
-
case "open":
|
|
481
|
-
let endpointUrl = args[1];
|
|
482
|
-
if (!endpointUrl.match(/^opc.tcp:\/\//)) {
|
|
483
|
-
endpointUrl = "opc.tcp://" + endpointUrl;
|
|
484
|
-
}
|
|
485
|
-
const p = opcua.parseEndpointUrl(endpointUrl);
|
|
486
|
-
const hostname = p.hostname;
|
|
487
|
-
const port = p.port;
|
|
488
|
-
log(" open url : ", endpointUrl);
|
|
489
|
-
log(" hostname : ", chalk.yellow(hostname || "<null>"));
|
|
490
|
-
log(" port : ", chalk.yellow(port.toString()));
|
|
491
|
-
|
|
492
|
-
apply_command(cmd, function(callback) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
client.connect(endpointUrl, function(err) {
|
|
496
|
-
if (err) {
|
|
497
|
-
log("client connected err=", err);
|
|
498
|
-
} else {
|
|
499
|
-
log("client connected : ", chalk.green("OK"));
|
|
500
|
-
|
|
501
|
-
add_endpoint_to_history(endpointUrl);
|
|
502
|
-
|
|
503
|
-
save_history(function() {
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
callback(err);
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
break;
|
|
510
|
-
|
|
511
|
-
case "fs":
|
|
512
|
-
case "FindServers":
|
|
513
|
-
apply_command(cmd, function(callback) {
|
|
514
|
-
client.findServers({}, function(err, data) {
|
|
515
|
-
const { servers, endpoints } = data;
|
|
516
|
-
if (err) {
|
|
517
|
-
log(err.message);
|
|
518
|
-
}
|
|
519
|
-
log(treeify.asTree(servers, true));
|
|
520
|
-
callback(err);
|
|
521
|
-
});
|
|
522
|
-
});
|
|
523
|
-
break;
|
|
524
|
-
case "gep":
|
|
525
|
-
case "getEndpoints":
|
|
526
|
-
apply_command(cmd, function(callback) {
|
|
527
|
-
client.getEndpoints(function(err, endpoints) {
|
|
528
|
-
if (err) {
|
|
529
|
-
log(err.message);
|
|
530
|
-
}
|
|
531
|
-
endpoints = utils.replaceBufferWithHexDump(endpoints);
|
|
532
|
-
log(treeify.asTree(endpoints, true));
|
|
533
|
-
callback(err);
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
break;
|
|
537
|
-
|
|
538
|
-
case "createSession":
|
|
539
|
-
case "cs":
|
|
540
|
-
apply_command(cmd, open_session);
|
|
541
|
-
break;
|
|
542
|
-
|
|
543
|
-
case "closeSession":
|
|
544
|
-
close_session(function() { });
|
|
545
|
-
break;
|
|
546
|
-
|
|
547
|
-
case "disconnect":
|
|
548
|
-
if (the_session) {
|
|
549
|
-
close_session(function(callback) {
|
|
550
|
-
client.disconnect(function() {
|
|
551
|
-
rl.write("client disconnected");
|
|
552
|
-
callback();
|
|
553
|
-
});
|
|
554
|
-
});
|
|
555
|
-
} else {
|
|
556
|
-
client.disconnect(function() {
|
|
557
|
-
rl.write("client disconnected");
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
break;
|
|
561
|
-
|
|
562
|
-
case "b":
|
|
563
|
-
case "browse":
|
|
564
|
-
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
565
|
-
|
|
566
|
-
nodes = [args[1]];
|
|
567
|
-
|
|
568
|
-
the_session.browse(nodes, function(err, nodeResults) {
|
|
569
|
-
|
|
570
|
-
if (err) {
|
|
571
|
-
log(err);
|
|
572
|
-
log(nodeResults);
|
|
573
|
-
} else {
|
|
574
|
-
|
|
575
|
-
save_history(function() {
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
for (let i = 0; i < nodeResults.length; i++) {
|
|
579
|
-
log("Node: ", nodes[i]);
|
|
580
|
-
log(" StatusCode =", nodeResults[i].statusCode.toString(16));
|
|
581
|
-
nodeResults[i].references.forEach(dumpNodeResult);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
callback();
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
break;
|
|
590
|
-
|
|
591
|
-
case "rootFolder":
|
|
592
|
-
|
|
593
|
-
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
594
|
-
|
|
595
|
-
get_root_folder(callback);
|
|
596
|
-
});
|
|
597
|
-
break;
|
|
598
|
-
|
|
599
|
-
case "hr":
|
|
600
|
-
case "readHistoryValue":
|
|
601
|
-
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
602
|
-
|
|
603
|
-
// example:
|
|
604
|
-
// hr ns=2;s=Demo.History.DoubleWithHistory 13:45 13:59
|
|
605
|
-
nodes = [args[1]];
|
|
606
|
-
|
|
607
|
-
let startTime = toDate(args[2]);// "2015-06-10T09:00:00.000Z"
|
|
608
|
-
let endTime = toDate(args[3]); // "2015-06-10T09:01:00.000Z"
|
|
609
|
-
if (startTime > endTime) {
|
|
610
|
-
const tmp = endTime; endTime = startTime; startTime = tmp;
|
|
611
|
-
}
|
|
612
|
-
nodes = nodes.map(opcua.coerceNodeId);
|
|
613
|
-
|
|
614
|
-
the_session.readHistoryValue(nodes, startTime, endTime, function(err, historyReadResults) {
|
|
615
|
-
if (err) {
|
|
616
|
-
log(err);
|
|
617
|
-
log(historyReadResults.toString());
|
|
618
|
-
} else {
|
|
619
|
-
save_history(function() { });
|
|
620
|
-
assert(historyReadResults.length === 1);
|
|
621
|
-
dump_historyDataValues({
|
|
622
|
-
nodeId: nodes[0],
|
|
623
|
-
attributeId: 13
|
|
624
|
-
}, startTime, endTime, historyReadResults[0]);
|
|
625
|
-
}
|
|
626
|
-
callback();
|
|
627
|
-
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
});
|
|
631
|
-
break;
|
|
632
|
-
case "r":
|
|
633
|
-
case "read":
|
|
634
|
-
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
635
|
-
|
|
636
|
-
nodes = [args[1]];
|
|
637
|
-
nodes = nodes.map(opcua.coerceNodeId);
|
|
638
|
-
|
|
639
|
-
the_session.readVariableValue(nodes, function(err, dataValues) {
|
|
640
|
-
if (err) {
|
|
641
|
-
log(err);
|
|
642
|
-
log(dataValues);
|
|
643
|
-
} else {
|
|
644
|
-
save_history(function() {
|
|
645
|
-
});
|
|
646
|
-
dump_dataValues([{
|
|
647
|
-
nodeId: nodes[0],
|
|
648
|
-
attributeId: 13
|
|
649
|
-
}], dataValues);
|
|
650
|
-
}
|
|
651
|
-
callback();
|
|
652
|
-
});
|
|
653
|
-
});
|
|
654
|
-
break;
|
|
655
|
-
case "ra":
|
|
656
|
-
case "readall":
|
|
657
|
-
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
658
|
-
const node = args[1];
|
|
659
|
-
|
|
660
|
-
the_session.readAllAttributes(node, function(err, result/*,diagnosticInfos*/) {
|
|
661
|
-
if (!err) {
|
|
662
|
-
save_history(function() {
|
|
663
|
-
});
|
|
664
|
-
console.log(result);
|
|
665
|
-
//xx dump_dataValues(nodesToRead, dataValues);
|
|
666
|
-
}
|
|
667
|
-
callback();
|
|
668
|
-
});
|
|
669
|
-
});
|
|
670
|
-
break;
|
|
671
|
-
|
|
672
|
-
case "tb":
|
|
673
|
-
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
674
|
-
|
|
675
|
-
const path = args[1];
|
|
676
|
-
the_session.translateBrowsePath(path, function(err, results) {
|
|
677
|
-
if (err) {
|
|
678
|
-
log(err.message);
|
|
679
|
-
}
|
|
680
|
-
log(" Path ", path, " is ");
|
|
681
|
-
log(util.inspect(results, { colors: true, depth: 100 }));
|
|
682
|
-
|
|
683
|
-
callback();
|
|
684
|
-
});
|
|
685
|
-
});
|
|
686
|
-
break;
|
|
687
|
-
case "crawl":
|
|
688
|
-
{
|
|
689
|
-
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
690
|
-
|
|
691
|
-
if (!crawler) {
|
|
692
|
-
crawler = new opcua.NodeCrawler(the_session);
|
|
693
|
-
crawler.on("browsed", function(element) {
|
|
694
|
-
// log("->",element.browseName.name,element.nodeId.toString());
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const nodeId = args[1] || "ObjectsFolder";
|
|
700
|
-
log("now crawling " + chalk.yellow(nodeId) + " ...please wait...");
|
|
701
|
-
crawler.read(nodeId, function(err, obj) {
|
|
702
|
-
if (!err) {
|
|
703
|
-
log(" crawling done ");
|
|
704
|
-
// todo : treeify.asTree performance is *very* slow on large object, replace with better implementation
|
|
705
|
-
//xx log(treeify.asTree(obj, true));
|
|
706
|
-
treeify.asLines(obj, true, true, function(line) {
|
|
707
|
-
log(line);
|
|
708
|
-
});
|
|
709
|
-
} else {
|
|
710
|
-
log("err ", err.message);
|
|
711
|
-
}
|
|
712
|
-
crawler = null;
|
|
713
|
-
callback();
|
|
714
|
-
});
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
break;
|
|
718
|
-
|
|
719
|
-
case ".info":
|
|
720
|
-
|
|
721
|
-
log(" bytesRead ", client.bytesRead, " bytes");
|
|
722
|
-
log(" bytesWritten ", client.bytesWritten, " bytes");
|
|
723
|
-
log("transactionsPerformed ", client.transactionsPerformed, "");
|
|
724
|
-
// -----------------------------------------------------------------------
|
|
725
|
-
// number of subscriptions
|
|
726
|
-
// -----------------------------------------------------------------------
|
|
727
|
-
// number of monitored items by subscription
|
|
728
|
-
|
|
729
|
-
break;
|
|
730
|
-
case ".quit":
|
|
731
|
-
process.exit(0);
|
|
732
|
-
break;
|
|
733
|
-
default:
|
|
734
|
-
if (cmd.trim().length > 0) {
|
|
735
|
-
log("Say what? I might have heard `" + cmd.trim() + "`");
|
|
736
|
-
}
|
|
737
|
-
break;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
rl.on("line", function(line) {
|
|
742
|
-
|
|
743
|
-
try {
|
|
744
|
-
process_line(line);
|
|
745
|
-
rl.prompt();
|
|
746
|
-
}
|
|
747
|
-
catch (err) {
|
|
748
|
-
log(chalk.red("------------------------------------------------"));
|
|
749
|
-
log(chalk.bgRed.yellow.bold(err.message));
|
|
750
|
-
log(err.stack);
|
|
751
|
-
log(chalk.red("------------------------------------------------"));
|
|
752
|
-
rl.resume();
|
|
753
|
-
}
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1
|
+
/* eslint-disable no-case-declarations */
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
/* eslint no-process-exit: 0 */
|
|
4
|
+
"use strict";
|
|
5
|
+
const chalk = require("chalk");
|
|
6
|
+
const treeify = require("treeify");
|
|
7
|
+
const sprintf = require("sprintf-js").sprintf;
|
|
8
|
+
const util = require("util");
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const _ = require("underscore");
|
|
12
|
+
|
|
13
|
+
const opcua = require("node-opcua");
|
|
14
|
+
const UAProxyManager = opcua.UAProxyManager;
|
|
15
|
+
const DataType = opcua.DataType;
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const utils = opcua.utils;
|
|
20
|
+
|
|
21
|
+
const { assert } = require("node-opcua-assert");
|
|
22
|
+
|
|
23
|
+
console.log(" Version ", opcua.version);
|
|
24
|
+
|
|
25
|
+
const sessionTimeout = 2 * 60 * 1000; // 2 minutes
|
|
26
|
+
|
|
27
|
+
const client = opcua.OPCUAClient.create({
|
|
28
|
+
requestedSessionTimeout: sessionTimeout,
|
|
29
|
+
keepSessionAlive: true
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
let the_session = null;
|
|
33
|
+
let proxyManager = null;
|
|
34
|
+
|
|
35
|
+
let crawler = null;
|
|
36
|
+
let dumpPacket = false;
|
|
37
|
+
const dumpMessageChunk = false;
|
|
38
|
+
let endpoints_history = [];
|
|
39
|
+
|
|
40
|
+
const endpoints_history_file = path.join(__dirname, ".history_endpoints");
|
|
41
|
+
|
|
42
|
+
let curNode = null;
|
|
43
|
+
let curNodeCompletion = [];
|
|
44
|
+
|
|
45
|
+
function save_endpoint_history(callback) {
|
|
46
|
+
if (endpoints_history.length > 0) {
|
|
47
|
+
fs.writeFileSync(endpoints_history_file, endpoints_history.join("\n"), "ascii");
|
|
48
|
+
}
|
|
49
|
+
if (callback) {
|
|
50
|
+
callback();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function add_endpoint_to_history(endpoint) {
|
|
54
|
+
if (endpoints_history.indexOf(endpoint) >= 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
endpoints_history.push(endpoint);
|
|
58
|
+
save_endpoint_history();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let lines = [];
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if (fs.existsSync(endpoints_history_file)) {
|
|
66
|
+
lines = fs.readFileSync(endpoints_history_file, "ascii");
|
|
67
|
+
endpoints_history = lines.split(/\r\n|\n/);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const history_file = path.join(__dirname, ".history");
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
function completer(line, callback) {
|
|
74
|
+
|
|
75
|
+
let completions, hits;
|
|
76
|
+
|
|
77
|
+
if ((line.trim() === "") && curNode) {
|
|
78
|
+
// console.log(" completions ",completions);
|
|
79
|
+
let c = [".."].concat(curNodeCompletion);
|
|
80
|
+
if (curNodeCompletion.length === 1) {
|
|
81
|
+
c = curNodeCompletion;
|
|
82
|
+
}
|
|
83
|
+
return callback(null, [c, line]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ("open".match(new RegExp("^" + line.trim()))) {
|
|
87
|
+
completions = ["open localhost:port"];
|
|
88
|
+
return callback(null, [completions, line]);
|
|
89
|
+
|
|
90
|
+
} else {
|
|
91
|
+
if (the_session === null) {
|
|
92
|
+
if (client._secureChannel) {
|
|
93
|
+
completions = "createSession cs getEndpoints gep quit".split(" ");
|
|
94
|
+
} else {
|
|
95
|
+
completions = "open quit".split(" ");
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
completions = "browse read readall crawl closeSession disconnect quit getEndpoints".split(" ");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
assert(completions.length >= 0);
|
|
102
|
+
hits = completions.filter(function(c) {
|
|
103
|
+
return c.indexOf(line) === 0;
|
|
104
|
+
});
|
|
105
|
+
return callback(null, [hits.length ? hits : completions, line]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
const rl = readline.createInterface({
|
|
110
|
+
input: process.stdin,
|
|
111
|
+
output: process.stdout,
|
|
112
|
+
completer: completer
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
let the_prompt = chalk.cyan(">");
|
|
116
|
+
rl.setPrompt(the_prompt);
|
|
117
|
+
rl.prompt();
|
|
118
|
+
|
|
119
|
+
function save_history(callback) {
|
|
120
|
+
const history_uniq = _.uniq(rl.history);
|
|
121
|
+
fs.writeFileSync(history_file, history_uniq.join("\n"), "ascii");
|
|
122
|
+
callback();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function w(str, width) {
|
|
126
|
+
return (str + " ").substr(0, width);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* @method toDate
|
|
130
|
+
* @param str
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* toDate("now");
|
|
134
|
+
* toDate("13:00"); => today at 13:00
|
|
135
|
+
* toDate("1 hour ago"); => today one our ago....
|
|
136
|
+
*
|
|
137
|
+
* @return {Date}
|
|
138
|
+
*/
|
|
139
|
+
function toDate(str) {
|
|
140
|
+
console.log(" parsing : '" + str + "'");
|
|
141
|
+
const now = new Date();
|
|
142
|
+
if (!str) {
|
|
143
|
+
return now;
|
|
144
|
+
}
|
|
145
|
+
if (str.toLowerCase() === "now") {
|
|
146
|
+
return now;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// check if provided date is <HH>:<MM>
|
|
150
|
+
|
|
151
|
+
const t = /([0-9]{1,2}):([0-9]{1,2})/;
|
|
152
|
+
const tt = str.match(t);
|
|
153
|
+
if (tt) {
|
|
154
|
+
// HH:MM of current date
|
|
155
|
+
const year = now.getFullYear();
|
|
156
|
+
const month = now.getMonth(); // 0 : jan , 1: feb etc ...
|
|
157
|
+
const day = now.getDate();
|
|
158
|
+
const hours = parseInt(tt[1], 10);
|
|
159
|
+
const minutes = parseInt(tt[2], 10);
|
|
160
|
+
const seconds = 0;
|
|
161
|
+
const date = new Date(year, month, day, hours, minutes, seconds);
|
|
162
|
+
return date;
|
|
163
|
+
}
|
|
164
|
+
// check if provided date looks like "3 hours ago" | "1 day ago" etc...
|
|
165
|
+
const r = /([0-9]*)(day|days|d|hours|hour|h|minutes|minutes|m)\s?((ago)?)/;
|
|
166
|
+
const m = str.match(r);
|
|
167
|
+
if (m) {
|
|
168
|
+
let tvalue = parseInt(m[1], 10);
|
|
169
|
+
switch (m[2][0]) {
|
|
170
|
+
case "d":
|
|
171
|
+
tvalue *= 24 * 3600;
|
|
172
|
+
break;
|
|
173
|
+
case "h":
|
|
174
|
+
tvalue *= 3600;
|
|
175
|
+
break;
|
|
176
|
+
case "m":
|
|
177
|
+
tvalue *= 60;
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
throw new Error(" invalidate date");
|
|
181
|
+
}
|
|
182
|
+
return new Date(now - tvalue * 1000);
|
|
183
|
+
} else {
|
|
184
|
+
return new Date(str);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
}
|
|
188
|
+
function log() {
|
|
189
|
+
rl.pause();
|
|
190
|
+
rl.clearLine(process.stdout);
|
|
191
|
+
const str = _.map(arguments).join(" ");
|
|
192
|
+
process.stdout.write(str);
|
|
193
|
+
rl.resume();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let rootFolder = null;
|
|
197
|
+
|
|
198
|
+
let nodePath = [];
|
|
199
|
+
let nodePathName = [];
|
|
200
|
+
const lowerFirstLetter = opcua.utils.lowerFirstLetter;
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
function setCurrentNode(node) {
|
|
204
|
+
|
|
205
|
+
curNode = node;
|
|
206
|
+
const curNodeBrowseName = lowerFirstLetter(curNode.browseName.name.toString());
|
|
207
|
+
nodePathName.push(curNodeBrowseName);
|
|
208
|
+
nodePath.push(node);
|
|
209
|
+
curNodeCompletion = node.$components.map(function(c) {
|
|
210
|
+
if (!c.browseName) {
|
|
211
|
+
return "???";
|
|
212
|
+
}
|
|
213
|
+
return lowerFirstLetter(c.browseName.name.toString());
|
|
214
|
+
});
|
|
215
|
+
the_prompt = chalk.yellow(nodePathName.join(".")) + ">";
|
|
216
|
+
rl.setPrompt(the_prompt);
|
|
217
|
+
}
|
|
218
|
+
function setRootNode(node) {
|
|
219
|
+
nodePath = [];
|
|
220
|
+
nodePathName = [];
|
|
221
|
+
setCurrentNode(node);
|
|
222
|
+
}
|
|
223
|
+
function moveToChild(browseName) {
|
|
224
|
+
|
|
225
|
+
if (browseName === "..") {
|
|
226
|
+
nodePathName.pop();
|
|
227
|
+
curNode = nodePath.splice(-1, 1)[0];
|
|
228
|
+
the_prompt = chalk.yellow(nodePathName.join(".")) + ">";
|
|
229
|
+
rl.setPrompt(the_prompt);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const child = curNode[browseName];
|
|
233
|
+
if (!child) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
setCurrentNode(child);
|
|
237
|
+
}
|
|
238
|
+
function get_root_folder(callback) {
|
|
239
|
+
|
|
240
|
+
if (!rootFolder) {
|
|
241
|
+
|
|
242
|
+
rl.pause();
|
|
243
|
+
proxyManager.getObject(opcua.makeNodeId(opcua.ObjectIds.RootFolder), function(err, data) {
|
|
244
|
+
|
|
245
|
+
if (!err) {
|
|
246
|
+
rootFolder = data;
|
|
247
|
+
assert(rootFolder, "expecting rootFolder");
|
|
248
|
+
setRootNode(rootFolder);
|
|
249
|
+
rl.resume();
|
|
250
|
+
}
|
|
251
|
+
callback();
|
|
252
|
+
});
|
|
253
|
+
} else {
|
|
254
|
+
setCurrentNode(rootFolder);
|
|
255
|
+
callback();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
client.on("send_chunk", function(message_chunk) {
|
|
261
|
+
if (dumpMessageChunk) {
|
|
262
|
+
process.stdout.write(">> " + message_chunk.length + "\r");
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
client.on("receive_chunk", function(message_chunk) {
|
|
267
|
+
if (dumpMessageChunk) {
|
|
268
|
+
process.stdout.write("<< " + message_chunk.length + "\r");
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
client.on("send_request", function(message) {
|
|
273
|
+
if (dumpPacket) {
|
|
274
|
+
log(chalk.red(" sending request"));
|
|
275
|
+
opcua.analyze_object_binary_encoding(message);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
client.on("receive_response", function(message) {
|
|
280
|
+
if (dumpPacket) {
|
|
281
|
+
assert(message);
|
|
282
|
+
log(chalk.cyan.bold(" receive response"));
|
|
283
|
+
opcua.analyze_object_binary_encoding(message);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
function dumpNodeResult(node) {
|
|
289
|
+
const str = sprintf(" %-30s%s%s", node.browseName.name, (node.isForward ? "->" : "<-"), node.nodeId.displayText());
|
|
290
|
+
log(str);
|
|
291
|
+
}
|
|
292
|
+
function colorize(value) {
|
|
293
|
+
return chalk.yellow.bold("" + value);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
if (rl.history) {
|
|
298
|
+
|
|
299
|
+
if (fs.existsSync(history_file)) {
|
|
300
|
+
lines = fs.readFileSync(history_file, "ascii");
|
|
301
|
+
lines = lines.split(/\r\n|\n/);
|
|
302
|
+
}
|
|
303
|
+
if (lines.length === 0) {
|
|
304
|
+
let hostname = require("os").hostname();
|
|
305
|
+
hostname = hostname.toLowerCase();
|
|
306
|
+
rl.history.push("open opc.tcp://opcua.demo-this.com:51210/UA/SampleServer");
|
|
307
|
+
rl.history.push("open opc.tcp://" + hostname + ":51210/UA/SampleServer");
|
|
308
|
+
rl.history.push("open opc.tcp://" + hostname + ":4841");
|
|
309
|
+
rl.history.push("open opc.tcp://" + "localhost" + ":51210/UA/SampleServer");
|
|
310
|
+
rl.history.push("open opc.tcp://" + hostname + ":6543/UA/SampleServer");
|
|
311
|
+
rl.history.push("open opc.tcp://" + hostname + ":53530/OPCUA/SimulationServer");
|
|
312
|
+
rl.history.push("b ObjectsFolder");
|
|
313
|
+
rl.history.push("r ns=2;s=Furnace_1.Temperature");
|
|
314
|
+
} else {
|
|
315
|
+
rl.history = rl.history.concat(lines);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
process.on("uncaughtException", function(e) {
|
|
320
|
+
util.puts(e.stack.red);
|
|
321
|
+
rl.prompt();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
function apply_command(cmd, func, callback) {
|
|
328
|
+
callback = callback || function() { };
|
|
329
|
+
rl.pause();
|
|
330
|
+
func(function(err) {
|
|
331
|
+
callback();
|
|
332
|
+
rl.resume();
|
|
333
|
+
rl.prompt(the_prompt);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function apply_on_valid_session(cmd, func, callback) {
|
|
338
|
+
|
|
339
|
+
assert(typeof func === "function");
|
|
340
|
+
assert(func.length === 2);
|
|
341
|
+
|
|
342
|
+
if (the_session) {
|
|
343
|
+
apply_command(cmd, function(callback) {
|
|
344
|
+
func(the_session, callback);
|
|
345
|
+
});
|
|
346
|
+
} else {
|
|
347
|
+
log("command : ", chalk.yellow(cmd), " requires a valid session , use createSession first");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function dump_dataValues(nodesToRead, dataValues) {
|
|
352
|
+
for (let i = 0; i < dataValues.length; i++) {
|
|
353
|
+
const dataValue = dataValues[i];
|
|
354
|
+
log(" Node : ", chalk.cyan.bold((nodesToRead[i].nodeId.toString())), nodesToRead[i].attributeId.toString());
|
|
355
|
+
if (dataValue.value) {
|
|
356
|
+
log(" type : ", colorize(DataType[dataValue.value.dataType]));
|
|
357
|
+
log(" value: ", colorize(dataValue.value.value));
|
|
358
|
+
} else {
|
|
359
|
+
log(" value: <null>");
|
|
360
|
+
}
|
|
361
|
+
log(" statusCode: 0x", dataValue.statusCode.toString(16));
|
|
362
|
+
log(" sourceTimestamp: ", dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function dump_historyDataValues(nodeToRead, startDate, endDate, historyReadResult) {
|
|
367
|
+
|
|
368
|
+
log(" Node : ", chalk.cyan.bold((nodeToRead.nodeId.toString())), nodeToRead.attributeId.toString());
|
|
369
|
+
log(" startDate : ", startDate);
|
|
370
|
+
log(" endDate : ", endDate);
|
|
371
|
+
if (historyReadResult.statusCode !== opcua.StatusCodes.Good) {
|
|
372
|
+
log(" error ", historyReadResult.statusCode.toString());
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
log("historyReadResult = ", historyReadResult.toString());
|
|
377
|
+
|
|
378
|
+
const dataValues = historyReadResult.historyData.dataValues;
|
|
379
|
+
log(" Length = ", dataValues.length);
|
|
380
|
+
|
|
381
|
+
if (!dataValues || dataValues.length === 0) {
|
|
382
|
+
log(" No Data");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (dataValues.length > 0 && dataValues[0].value) {
|
|
386
|
+
log(" type : ", colorize(DataType[dataValues[0].value.dataType]));
|
|
387
|
+
}
|
|
388
|
+
for (let i = 0; i < dataValues.length; i++) {
|
|
389
|
+
const dataValue = dataValues[i];
|
|
390
|
+
if (dataValue.value) {
|
|
391
|
+
log(
|
|
392
|
+
dataValue.sourceTimestamp,
|
|
393
|
+
w(dataValue.sourcePicoseconds, 4),
|
|
394
|
+
colorize(w(dataValue.value.value, 15)),
|
|
395
|
+
w(dataValue.statusCode.toString(16), 16));
|
|
396
|
+
} else {
|
|
397
|
+
log(" value: <null>", dataValue.toString());
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function open_session(callback) {
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
if (the_session !== null) {
|
|
406
|
+
log(" a session exists already ! use closeSession First");
|
|
407
|
+
return callback();
|
|
408
|
+
|
|
409
|
+
} else {
|
|
410
|
+
|
|
411
|
+
client.requestedSessionTimeout = sessionTimeout;
|
|
412
|
+
client.createSession(function(err, session) {
|
|
413
|
+
if (err) {
|
|
414
|
+
log(chalk.red("Error : "), err);
|
|
415
|
+
} else {
|
|
416
|
+
|
|
417
|
+
the_session = session;
|
|
418
|
+
log("session created ", session.sessionId.toString());
|
|
419
|
+
proxyManager = new UAProxyManager(the_session);
|
|
420
|
+
|
|
421
|
+
the_prompt = chalk.cyan("session:") + chalk.yellow(the_session.sessionId.toString()) + chalk.cyan(">");
|
|
422
|
+
rl.setPrompt(the_prompt);
|
|
423
|
+
|
|
424
|
+
assert(!crawler);
|
|
425
|
+
|
|
426
|
+
rl.prompt(the_prompt);
|
|
427
|
+
|
|
428
|
+
}
|
|
429
|
+
callback();
|
|
430
|
+
});
|
|
431
|
+
client.on("close", function() {
|
|
432
|
+
log(chalk.red(" Server has disconnected "));
|
|
433
|
+
the_session = null;
|
|
434
|
+
crawler = null;
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function close_session(outer_callback) {
|
|
440
|
+
apply_on_valid_session("closeSession", function(session, inner_callback) {
|
|
441
|
+
session.close(function(err) {
|
|
442
|
+
the_session = null;
|
|
443
|
+
crawler = null;
|
|
444
|
+
if (!outer_callback) {
|
|
445
|
+
inner_callback(err);
|
|
446
|
+
} else {
|
|
447
|
+
assert(typeof outer_callback === "function");
|
|
448
|
+
outer_callback(inner_callback);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function set_debug(flag) {
|
|
455
|
+
if (flag) {
|
|
456
|
+
dumpPacket = true;
|
|
457
|
+
process.env.DEBUG = "ALL";
|
|
458
|
+
log(" Debug is ON");
|
|
459
|
+
} else {
|
|
460
|
+
dumpPacket = true;
|
|
461
|
+
delete process.env.DEBUG;
|
|
462
|
+
log(" Debug is OFF");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function process_line(line) {
|
|
467
|
+
let nodes;
|
|
468
|
+
const args = line.trim().split(/ +/);
|
|
469
|
+
const cmd = args[0];
|
|
470
|
+
|
|
471
|
+
if (curNode) {
|
|
472
|
+
moveToChild(cmd);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
switch (cmd) {
|
|
476
|
+
case "debug":
|
|
477
|
+
const flag = (!args[1]) ? true : (["ON", "TRUE", "1"].indexOf(args[1].toUpperCase()) >= 0);
|
|
478
|
+
set_debug(flag);
|
|
479
|
+
break;
|
|
480
|
+
case "open":
|
|
481
|
+
let endpointUrl = args[1];
|
|
482
|
+
if (!endpointUrl.match(/^opc.tcp:\/\//)) {
|
|
483
|
+
endpointUrl = "opc.tcp://" + endpointUrl;
|
|
484
|
+
}
|
|
485
|
+
const p = opcua.parseEndpointUrl(endpointUrl);
|
|
486
|
+
const hostname = p.hostname;
|
|
487
|
+
const port = p.port;
|
|
488
|
+
log(" open url : ", endpointUrl);
|
|
489
|
+
log(" hostname : ", chalk.yellow(hostname || "<null>"));
|
|
490
|
+
log(" port : ", chalk.yellow(port.toString()));
|
|
491
|
+
|
|
492
|
+
apply_command(cmd, function(callback) {
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
client.connect(endpointUrl, function(err) {
|
|
496
|
+
if (err) {
|
|
497
|
+
log("client connected err=", err);
|
|
498
|
+
} else {
|
|
499
|
+
log("client connected : ", chalk.green("OK"));
|
|
500
|
+
|
|
501
|
+
add_endpoint_to_history(endpointUrl);
|
|
502
|
+
|
|
503
|
+
save_history(function() {
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
callback(err);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
break;
|
|
510
|
+
|
|
511
|
+
case "fs":
|
|
512
|
+
case "FindServers":
|
|
513
|
+
apply_command(cmd, function(callback) {
|
|
514
|
+
client.findServers({}, function(err, data) {
|
|
515
|
+
const { servers, endpoints } = data;
|
|
516
|
+
if (err) {
|
|
517
|
+
log(err.message);
|
|
518
|
+
}
|
|
519
|
+
log(treeify.asTree(servers, true));
|
|
520
|
+
callback(err);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
break;
|
|
524
|
+
case "gep":
|
|
525
|
+
case "getEndpoints":
|
|
526
|
+
apply_command(cmd, function(callback) {
|
|
527
|
+
client.getEndpoints(function(err, endpoints) {
|
|
528
|
+
if (err) {
|
|
529
|
+
log(err.message);
|
|
530
|
+
}
|
|
531
|
+
endpoints = utils.replaceBufferWithHexDump(endpoints);
|
|
532
|
+
log(treeify.asTree(endpoints, true));
|
|
533
|
+
callback(err);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
break;
|
|
537
|
+
|
|
538
|
+
case "createSession":
|
|
539
|
+
case "cs":
|
|
540
|
+
apply_command(cmd, open_session);
|
|
541
|
+
break;
|
|
542
|
+
|
|
543
|
+
case "closeSession":
|
|
544
|
+
close_session(function() { });
|
|
545
|
+
break;
|
|
546
|
+
|
|
547
|
+
case "disconnect":
|
|
548
|
+
if (the_session) {
|
|
549
|
+
close_session(function(callback) {
|
|
550
|
+
client.disconnect(function() {
|
|
551
|
+
rl.write("client disconnected");
|
|
552
|
+
callback();
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
} else {
|
|
556
|
+
client.disconnect(function() {
|
|
557
|
+
rl.write("client disconnected");
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
|
|
562
|
+
case "b":
|
|
563
|
+
case "browse":
|
|
564
|
+
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
565
|
+
|
|
566
|
+
nodes = [args[1]];
|
|
567
|
+
|
|
568
|
+
the_session.browse(nodes, function(err, nodeResults) {
|
|
569
|
+
|
|
570
|
+
if (err) {
|
|
571
|
+
log(err);
|
|
572
|
+
log(nodeResults);
|
|
573
|
+
} else {
|
|
574
|
+
|
|
575
|
+
save_history(function() {
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
for (let i = 0; i < nodeResults.length; i++) {
|
|
579
|
+
log("Node: ", nodes[i]);
|
|
580
|
+
log(" StatusCode =", nodeResults[i].statusCode.toString(16));
|
|
581
|
+
nodeResults[i].references.forEach(dumpNodeResult);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
callback();
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
break;
|
|
590
|
+
|
|
591
|
+
case "rootFolder":
|
|
592
|
+
|
|
593
|
+
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
594
|
+
|
|
595
|
+
get_root_folder(callback);
|
|
596
|
+
});
|
|
597
|
+
break;
|
|
598
|
+
|
|
599
|
+
case "hr":
|
|
600
|
+
case "readHistoryValue":
|
|
601
|
+
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
602
|
+
|
|
603
|
+
// example:
|
|
604
|
+
// hr ns=2;s=Demo.History.DoubleWithHistory 13:45 13:59
|
|
605
|
+
nodes = [args[1]];
|
|
606
|
+
|
|
607
|
+
let startTime = toDate(args[2]);// "2015-06-10T09:00:00.000Z"
|
|
608
|
+
let endTime = toDate(args[3]); // "2015-06-10T09:01:00.000Z"
|
|
609
|
+
if (startTime > endTime) {
|
|
610
|
+
const tmp = endTime; endTime = startTime; startTime = tmp;
|
|
611
|
+
}
|
|
612
|
+
nodes = nodes.map(opcua.coerceNodeId);
|
|
613
|
+
|
|
614
|
+
the_session.readHistoryValue(nodes, startTime, endTime, function(err, historyReadResults) {
|
|
615
|
+
if (err) {
|
|
616
|
+
log(err);
|
|
617
|
+
log(historyReadResults.toString());
|
|
618
|
+
} else {
|
|
619
|
+
save_history(function() { });
|
|
620
|
+
assert(historyReadResults.length === 1);
|
|
621
|
+
dump_historyDataValues({
|
|
622
|
+
nodeId: nodes[0],
|
|
623
|
+
attributeId: 13
|
|
624
|
+
}, startTime, endTime, historyReadResults[0]);
|
|
625
|
+
}
|
|
626
|
+
callback();
|
|
627
|
+
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
});
|
|
631
|
+
break;
|
|
632
|
+
case "r":
|
|
633
|
+
case "read":
|
|
634
|
+
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
635
|
+
|
|
636
|
+
nodes = [args[1]];
|
|
637
|
+
nodes = nodes.map(opcua.coerceNodeId);
|
|
638
|
+
|
|
639
|
+
the_session.readVariableValue(nodes, function(err, dataValues) {
|
|
640
|
+
if (err) {
|
|
641
|
+
log(err);
|
|
642
|
+
log(dataValues);
|
|
643
|
+
} else {
|
|
644
|
+
save_history(function() {
|
|
645
|
+
});
|
|
646
|
+
dump_dataValues([{
|
|
647
|
+
nodeId: nodes[0],
|
|
648
|
+
attributeId: 13
|
|
649
|
+
}], dataValues);
|
|
650
|
+
}
|
|
651
|
+
callback();
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
break;
|
|
655
|
+
case "ra":
|
|
656
|
+
case "readall":
|
|
657
|
+
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
658
|
+
const node = args[1];
|
|
659
|
+
|
|
660
|
+
the_session.readAllAttributes(node, function(err, result/*,diagnosticInfos*/) {
|
|
661
|
+
if (!err) {
|
|
662
|
+
save_history(function() {
|
|
663
|
+
});
|
|
664
|
+
console.log(result);
|
|
665
|
+
//xx dump_dataValues(nodesToRead, dataValues);
|
|
666
|
+
}
|
|
667
|
+
callback();
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
break;
|
|
671
|
+
|
|
672
|
+
case "tb":
|
|
673
|
+
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
674
|
+
|
|
675
|
+
const path = args[1];
|
|
676
|
+
the_session.translateBrowsePath(path, function(err, results) {
|
|
677
|
+
if (err) {
|
|
678
|
+
log(err.message);
|
|
679
|
+
}
|
|
680
|
+
log(" Path ", path, " is ");
|
|
681
|
+
log(util.inspect(results, { colors: true, depth: 100 }));
|
|
682
|
+
|
|
683
|
+
callback();
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
break;
|
|
687
|
+
case "crawl":
|
|
688
|
+
{
|
|
689
|
+
apply_on_valid_session(cmd, function(the_session, callback) {
|
|
690
|
+
|
|
691
|
+
if (!crawler) {
|
|
692
|
+
crawler = new opcua.NodeCrawler(the_session);
|
|
693
|
+
crawler.on("browsed", function(element) {
|
|
694
|
+
// log("->",element.browseName.name,element.nodeId.toString());
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const nodeId = args[1] || "ObjectsFolder";
|
|
700
|
+
log("now crawling " + chalk.yellow(nodeId) + " ...please wait...");
|
|
701
|
+
crawler.read(nodeId, function(err, obj) {
|
|
702
|
+
if (!err) {
|
|
703
|
+
log(" crawling done ");
|
|
704
|
+
// todo : treeify.asTree performance is *very* slow on large object, replace with better implementation
|
|
705
|
+
//xx log(treeify.asTree(obj, true));
|
|
706
|
+
treeify.asLines(obj, true, true, function(line) {
|
|
707
|
+
log(line);
|
|
708
|
+
});
|
|
709
|
+
} else {
|
|
710
|
+
log("err ", err.message);
|
|
711
|
+
}
|
|
712
|
+
crawler = null;
|
|
713
|
+
callback();
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
break;
|
|
718
|
+
|
|
719
|
+
case ".info":
|
|
720
|
+
|
|
721
|
+
log(" bytesRead ", client.bytesRead, " bytes");
|
|
722
|
+
log(" bytesWritten ", client.bytesWritten, " bytes");
|
|
723
|
+
log("transactionsPerformed ", client.transactionsPerformed, "");
|
|
724
|
+
// -----------------------------------------------------------------------
|
|
725
|
+
// number of subscriptions
|
|
726
|
+
// -----------------------------------------------------------------------
|
|
727
|
+
// number of monitored items by subscription
|
|
728
|
+
|
|
729
|
+
break;
|
|
730
|
+
case ".quit":
|
|
731
|
+
process.exit(0);
|
|
732
|
+
break;
|
|
733
|
+
default:
|
|
734
|
+
if (cmd.trim().length > 0) {
|
|
735
|
+
log("Say what? I might have heard `" + cmd.trim() + "`");
|
|
736
|
+
}
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
rl.on("line", function(line) {
|
|
742
|
+
|
|
743
|
+
try {
|
|
744
|
+
process_line(line);
|
|
745
|
+
rl.prompt();
|
|
746
|
+
}
|
|
747
|
+
catch (err) {
|
|
748
|
+
log(chalk.red("------------------------------------------------"));
|
|
749
|
+
log(chalk.bgRed.yellow.bold(err.message));
|
|
750
|
+
log(err.stack);
|
|
751
|
+
log(chalk.red("------------------------------------------------"));
|
|
752
|
+
rl.resume();
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
|