nep-cli 0.2.2 → 0.2.3
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/bin/assets/vuetify/json/attributes.json +9157 -9157
- package/bin/assets/vuetify/json/tags.json +3062 -3062
- package/bin/assets/vuetify/json/web-types.json +27670 -27670
- package/bin/assets/vuetify/vuetify.css +24875 -24875
- package/bin/assets/vuetify/vuetify.js +41072 -41072
- package/bin/assets/vuetify/vuetify.min.css +7 -7
- package/bin/assets/vuetify/vuetify.min.js +5 -5
- package/bin/index.js +513 -200
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -78,7 +78,8 @@ class MasterLocal {
|
|
|
78
78
|
const reply = new zmq.Reply
|
|
79
79
|
await reply.bind(address)
|
|
80
80
|
for await (const [node_request] of reply) {
|
|
81
|
-
await
|
|
81
|
+
var msg = await processMsg(node_request.toString(), nodes_register, topic_register)
|
|
82
|
+
await reply.send(msg)
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
run()
|
|
@@ -120,77 +121,173 @@ var onRegisteredTopic = function (node_request, topic_register, topic) {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
|
|
124
|
+
var onNewTopic = async function (node_request, topic_register) {
|
|
125
|
+
// Assign new ports
|
|
126
|
+
nep_configuration["current_port"] += 2;
|
|
123
127
|
|
|
124
|
-
|
|
128
|
+
// Topic must be a string
|
|
129
|
+
var topic = String(node_request['topic']);
|
|
130
|
+
var msg = {};
|
|
125
131
|
|
|
126
|
-
// Asign new ports
|
|
127
|
-
nep_configuration["current_port"] = nep_configuration["current_port"] + 2
|
|
128
|
-
// Topic must be an string
|
|
129
|
-
topic = String(node_request['topic'])
|
|
130
|
-
var msg = {}
|
|
131
132
|
// If publisher or subscriber
|
|
132
133
|
if (node_request["socket"] === "publisher" || node_request["socket"] === "subscriber") {
|
|
133
134
|
// Create new broker for many2many communication
|
|
134
135
|
if (node_request["mode"] === "many2many") {
|
|
135
|
-
createBroker(topic);
|
|
136
|
-
msg =
|
|
136
|
+
await createBroker(topic);
|
|
137
|
+
msg = ManyToManyResponse(node_request, topic_register, topic);
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
140
|
+
|
|
141
|
+
// If client or server
|
|
142
142
|
else if (node_request["socket"] === "client" || node_request["socket"] === "server") {
|
|
143
|
-
msg = csResponse(node_request, topic_register, topic)
|
|
143
|
+
msg = csResponse(node_request, topic_register, topic);
|
|
144
144
|
}
|
|
145
|
+
|
|
145
146
|
// Key for related nodes
|
|
146
|
-
topic_register[topic]["nodes"] = {}
|
|
147
|
+
topic_register[topic]["nodes"] = {};
|
|
148
|
+
|
|
147
149
|
// Set PID of node
|
|
148
150
|
updatePID(node_request, topic_register, topic);
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
msg.port = msg.port.toString();
|
|
154
|
-
}
|
|
155
|
-
|
|
152
|
+
// If the language is C++, convert the port to a string
|
|
153
|
+
if (node_request["language"] === "C++") {
|
|
154
|
+
msg.port = msg.port.toString();
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
return msg
|
|
157
|
+
return msg;
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
class Broker {
|
|
162
|
+
constructor(IP, PORT_XPUB, PORT_XSUB, topic) {
|
|
163
|
+
this.pubListener = "tcp://" + IP + ":" + String(PORT_XPUB);
|
|
164
|
+
this.subListener = "tcp://" + IP + ":" + String(PORT_XSUB);
|
|
165
|
+
this.hwm = 1000;
|
|
166
|
+
this.verbose = 0;
|
|
167
|
+
this.topic = topic;
|
|
166
168
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
} catch (error) {
|
|
170
|
-
// console.log("NEP ERROR: ports " + String(nep_configuration["current_port"]) + " and " + String(nep_configuration["current_port"] + 1) + " not avaliable")
|
|
171
|
-
nep_configuration["current_port"] = nep_configuration["current_port"] + 2
|
|
172
|
-
var broker = new nep.Broker(nep_configuration["IP"], nep_configuration["current_port"] + 1, nep_configuration["current_port"])
|
|
173
|
-
// Add broker defined to list of brokers
|
|
174
|
-
nep_configuration["brokers"][topic] = broker
|
|
169
|
+
// Use ZMQ version 5
|
|
170
|
+
this.zmqc = require("zeromq/v5-compat");
|
|
175
171
|
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
172
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
bindSockets() {
|
|
174
|
+
try {
|
|
175
|
+
// The xsub listener is where pubs connect to
|
|
176
|
+
this.subSock = this.zmqc.socket('xsub');
|
|
177
|
+
this.subSock.bind(this.subListener);
|
|
178
|
+
|
|
179
|
+
// The xpub listener is where subs connect to
|
|
180
|
+
this.pubSock = this.zmqc.socket('xpub');
|
|
181
|
+
this.pubSock.setsockopt(this.zmqc.ZMQ_SNDHWM, this.hwm);
|
|
182
|
+
|
|
183
|
+
// By default xpub only signals new subscriptions
|
|
184
|
+
// Settings it to verbose = 1 , will signal on every new subscribe
|
|
185
|
+
this.pubSock.setsockopt(this.zmqc.ZMQ_XPUB_VERBOSE, this.verbose);
|
|
186
|
+
this.pubSock.bind(this.pubListener);
|
|
187
|
+
return true;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (error.code === 'EADDRINUSE') {
|
|
190
|
+
throw new Error(`Port ${this.pubListener.split(':')[2]} or ${this.subListener.split(':')[2]} is in use.`);
|
|
191
|
+
} else {
|
|
192
|
+
console.error(error);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
182
196
|
}
|
|
183
|
-
|
|
184
|
-
|
|
197
|
+
|
|
198
|
+
start() {
|
|
199
|
+
// When we receive data on subSock , it means someone is publishing
|
|
200
|
+
this.subSock.on('message', (data) => {
|
|
201
|
+
// We just relay it to the pubSock, so subscribers can receive it
|
|
202
|
+
this.pubSock.send(data);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// When Pubsock receives a message , it's subscribe requests
|
|
206
|
+
this.pubSock.on('message', (data) => {
|
|
207
|
+
// The data is a slow Buffer
|
|
208
|
+
// The first byte is the subscribe (1) /unsubscribe flag (0)
|
|
209
|
+
var type = data[0] === 0 ? 'unsubscribe' : 'subscribe';
|
|
210
|
+
// The channel name is the rest of the buffer
|
|
211
|
+
var channel = data.slice(1).toString();
|
|
212
|
+
console.log(type + ' :' + this.topic);
|
|
213
|
+
// We send it to subSock, so it knows to what channels to listen to
|
|
214
|
+
this.subSock.send(data);
|
|
215
|
+
});
|
|
216
|
+
console.log("BROKER: " + "XPUB: " + String(this.pubListener) + " - bind , XSUB: " + String(this.subListener) + " - bind");
|
|
185
217
|
}
|
|
186
|
-
return { 'topic': topic, 'port': nep_configuration["current_port"], 'mode': node_request["mode"], 'ip': nep_configuration["IP"], 'socket': node_request["socket"], "state": "success" }
|
|
187
218
|
}
|
|
188
219
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
function checkPort(port) {
|
|
221
|
+
return new Promise((resolve) => {
|
|
222
|
+
const sock = new zmq.Publisher;
|
|
223
|
+
sock.bind(`tcp://127.0.0.1:${port}`)
|
|
224
|
+
.then(() => {
|
|
225
|
+
sock.unbind(`tcp://127.0.0.1:${port}`);
|
|
226
|
+
resolve(false); // Puerto disponible
|
|
227
|
+
})
|
|
228
|
+
.catch((err) => {
|
|
229
|
+
if (err.code === 'EADDRINUSE') {
|
|
230
|
+
resolve(true); // Puerto en uso
|
|
231
|
+
} else {
|
|
232
|
+
console.error(err);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
var createBroker = async function (topic) {
|
|
239
|
+
var bindResult = false;
|
|
240
|
+
do {
|
|
241
|
+
var portInUse = await checkPort(nep_configuration["current_port"]);
|
|
242
|
+
var nextPortInUse = await checkPort(nep_configuration["current_port"] + 1);
|
|
243
|
+
if (portInUse || nextPortInUse) {
|
|
244
|
+
console.log(`LOG: Port ${nep_configuration["current_port"]} in use: ${portInUse}, Port ${nep_configuration["current_port"] + 1} in use: ${nextPortInUse}`);
|
|
245
|
+
nep_configuration["current_port"] += 2;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
var broker = new Broker(nep_configuration["IP"], nep_configuration["current_port"] + 1, nep_configuration["current_port"], topic);
|
|
250
|
+
bindResult = broker.bindSockets();
|
|
251
|
+
if (bindResult) {
|
|
252
|
+
broker.start();
|
|
253
|
+
nep_configuration["brokers"][topic] = broker;
|
|
254
|
+
}
|
|
255
|
+
} while (!bindResult);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Generate a response for many-to-many communication.
|
|
261
|
+
* @param {Object} node_request - The request from the node.
|
|
262
|
+
* @param {Object} topic_register - The register of topics.
|
|
263
|
+
* @param {string} topic - The topic of the request.
|
|
264
|
+
* @return {Object} The response to be sent back to the node.
|
|
265
|
+
*/
|
|
266
|
+
var ManyToManyResponse = function (node_request, topic_register, topic) {
|
|
267
|
+
// If the request contains a message type, use it. Otherwise, default to "json".
|
|
268
|
+
var msg_type = "msg_type" in node_request ? node_request["msg_type"] : "json";
|
|
269
|
+
|
|
270
|
+
// Register the topic with the current port, socket, IP, mode, and message type.
|
|
271
|
+
topic_register[topic] = {
|
|
272
|
+
"port": nep_configuration["current_port"],
|
|
273
|
+
"socket": node_request["socket"],
|
|
274
|
+
'ip': nep_configuration["IP"],
|
|
275
|
+
"mode": node_request["mode"],
|
|
276
|
+
"msg_type": msg_type
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Return the response with the topic, port, mode, IP, socket, and a success state.
|
|
280
|
+
return {
|
|
281
|
+
'topic': topic,
|
|
282
|
+
'port': nep_configuration["current_port"],
|
|
283
|
+
'mode': node_request["mode"],
|
|
284
|
+
'ip': nep_configuration["IP"],
|
|
285
|
+
'socket': node_request["socket"],
|
|
286
|
+
"state": "success"
|
|
287
|
+
};
|
|
192
288
|
}
|
|
193
289
|
|
|
290
|
+
|
|
194
291
|
var csResponse = function (node_request, topic_register, topic) {
|
|
195
292
|
topic_register[topic] = { "port": nep_configuration["current_port"], "socket": node_request["socket"], 'ip': nep_configuration["IP"], "msg_type": "json" }
|
|
196
293
|
return { 'topic': topic, 'port': nep_configuration["current_port"], 'ip': nep_configuration["IP"], 'socket': node_request["socket"], "state": "success" }
|
|
@@ -207,16 +304,16 @@ var updatePID = function (node_request, topic_register, topic) {
|
|
|
207
304
|
}
|
|
208
305
|
|
|
209
306
|
|
|
210
|
-
const processMsg = (json_msg, nodes_register, topic_register) => {
|
|
307
|
+
const processMsg = async (json_msg, nodes_register, topic_register) => {
|
|
211
308
|
const node_request = JSON.parse(json_msg);
|
|
212
309
|
const { node, socket, topic, pid } = node_request;
|
|
213
|
-
console.log(
|
|
310
|
+
console.log(`\n --- REQUEST: node: ${node}, socket: ${socket}, topic: ${topic} ---`);
|
|
214
311
|
|
|
215
312
|
// Check node status
|
|
216
313
|
if (node) {
|
|
217
314
|
// Kill previous node with the same name
|
|
218
315
|
if (node in nodes_register) {
|
|
219
|
-
console.log(
|
|
316
|
+
console.log(`NODE: *${node}* already registered`);
|
|
220
317
|
const current_pid = nodes_register[node];
|
|
221
318
|
if (pid !== current_pid) {
|
|
222
319
|
if (!node.startsWith('nep-cli')) {
|
|
@@ -226,7 +323,7 @@ const processMsg = (json_msg, nodes_register, topic_register) => {
|
|
|
226
323
|
}
|
|
227
324
|
}
|
|
228
325
|
} else {
|
|
229
|
-
console.log(
|
|
326
|
+
console.log(`NODE: *${node}* was registered`);
|
|
230
327
|
}
|
|
231
328
|
}
|
|
232
329
|
|
|
@@ -236,14 +333,14 @@ const processMsg = (json_msg, nodes_register, topic_register) => {
|
|
|
236
333
|
// Check topic status
|
|
237
334
|
const topicStr = String(topic);
|
|
238
335
|
if (topicStr in topic_register) {
|
|
239
|
-
console.log(
|
|
336
|
+
console.log(`TOPIC: *${topicStr}* already registered ---`);
|
|
240
337
|
// Send information of topic already defined
|
|
241
338
|
const response = onRegisteredTopic(node_request, topic_register, topicStr);
|
|
242
339
|
return JSON.stringify(response);
|
|
243
340
|
} else {
|
|
244
|
-
console.log(
|
|
341
|
+
console.log(`TOPIC: *${topicStr}* was registered`);
|
|
245
342
|
// Create new broker
|
|
246
|
-
const response = onNewTopic(node_request, topic_register);
|
|
343
|
+
const response = await onNewTopic(node_request, topic_register);
|
|
247
344
|
return JSON.stringify(response);
|
|
248
345
|
}
|
|
249
346
|
};
|
|
@@ -340,45 +437,57 @@ const sendRequest = async (requester, msg) => {
|
|
|
340
437
|
};
|
|
341
438
|
|
|
342
439
|
const selectTopic = async (results, topic) => {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
440
|
+
try {
|
|
441
|
+
if (!topic || !results["input"].includes(topic)) {
|
|
442
|
+
const autoComplete = new AutoComplete({
|
|
443
|
+
name: 'topic',
|
|
444
|
+
message: !topic ? 'Select a topic:' : 'Invalid topic. Select a valid topic:',
|
|
445
|
+
choices: results["input"],
|
|
446
|
+
});
|
|
447
|
+
topic = await autoComplete.run();
|
|
448
|
+
}
|
|
449
|
+
return topic;
|
|
450
|
+
} catch (error) {
|
|
451
|
+
process.exit(1);
|
|
350
452
|
}
|
|
351
|
-
return topic;
|
|
352
453
|
};
|
|
353
454
|
|
|
354
455
|
const selectIndex = async (index, maxIndex = 9) => {
|
|
355
|
-
|
|
456
|
+
try {
|
|
457
|
+
const choices = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
|
356
458
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
459
|
+
const autoComplete = new AutoComplete({
|
|
460
|
+
name: 'index',
|
|
461
|
+
message: !index ? 'Select a index:' : 'Invalid index. Select a valid index:',
|
|
462
|
+
choices: choices,
|
|
463
|
+
});
|
|
464
|
+
index = await autoComplete.run();
|
|
363
465
|
|
|
364
|
-
|
|
466
|
+
return index;
|
|
467
|
+
} catch (error) {
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
365
470
|
};
|
|
366
471
|
|
|
367
472
|
const selectMsgType = async (allowedFormats, msg_type, topic) => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
473
|
+
try {
|
|
474
|
+
var msgType = topic.split('/').pop();
|
|
475
|
+
if (msgType && msgType !== topic) {
|
|
476
|
+
msg_type = msgType;
|
|
477
|
+
}
|
|
372
478
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
479
|
+
if (!msg_type || !allowedFormats.includes(msg_type)) {
|
|
480
|
+
const autoComplete = new AutoComplete({
|
|
481
|
+
name: 'msg_type',
|
|
482
|
+
message: !msg_type ? 'Select a message type:' : `Select a valid format:`,
|
|
483
|
+
choices: allowedFormats,
|
|
484
|
+
});
|
|
485
|
+
msg_type = await autoComplete.run();
|
|
486
|
+
}
|
|
487
|
+
return msg_type;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
process.exit(1);
|
|
380
490
|
}
|
|
381
|
-
return msg_type;
|
|
382
491
|
};
|
|
383
492
|
|
|
384
493
|
const openSub = (master_ip, topic, msg_type, callback) => {
|
|
@@ -388,123 +497,9 @@ const openSub = (master_ip, topic, msg_type, callback) => {
|
|
|
388
497
|
};
|
|
389
498
|
|
|
390
499
|
|
|
391
|
-
program
|
|
392
|
-
.command('app')
|
|
393
|
-
.description('Open NEP+ App')
|
|
394
|
-
.action(() => {
|
|
395
|
-
// Get the username of the logged-in user
|
|
396
|
-
const username = os.userInfo().username;
|
|
397
|
-
|
|
398
|
-
// Specify the path to the executable
|
|
399
|
-
const appFolderName = 'nepplus-app';
|
|
400
|
-
const executableName = 'nepplus-app.exe';
|
|
401
|
-
const executablePath = `C:\\Users\\${username}\\AppData\\Local\\Programs\\${appFolderName}\\${executableName}`;
|
|
402
|
-
|
|
403
|
-
// Run the executable with proper escaping
|
|
404
|
-
exec(`"${executablePath}"`, (error, stdout, stderr) => {
|
|
405
|
-
if (error) {
|
|
406
|
-
console.error(`Error: ${error.message}`);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
console.log(`stdout: ${stdout}`);
|
|
410
|
-
console.error(`stderr: ${stderr}`);
|
|
411
|
-
});
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
program
|
|
416
|
-
.command('publish <topic> <message>')
|
|
417
|
-
.description('Publish a message to a NEP+ topic')
|
|
418
|
-
.action((topic, message) => {
|
|
419
|
-
|
|
420
|
-
var openpub = function (master_ip = "127.0.0.1") {
|
|
421
|
-
|
|
422
|
-
var pubFunction = function () {
|
|
423
|
-
|
|
424
|
-
var msg = message.replace(/'/g, '"')
|
|
425
|
-
console.log(JSON.parse(msg))
|
|
426
|
-
pub.publish(JSON.parse(msg))
|
|
427
|
-
console.log("Message published")
|
|
428
|
-
|
|
429
|
-
process.exit(1)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
var node = new nep.Node("nep-cli-pub")
|
|
434
|
-
var pub = node.new_pub(topic, "json")
|
|
435
|
-
setTimeout(pubFunction, 1000)
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
async function run() {
|
|
439
|
-
var requester = new zmq.Request;
|
|
440
|
-
var master_ip = "127.0.0.1"
|
|
441
|
-
|
|
442
|
-
requester.connect("tcp://" + master_ip + ":" + PORT_MASTER_INFO);
|
|
443
|
-
|
|
444
|
-
let msg = { "input": "topics" }
|
|
445
|
-
var message = JSON.stringify(msg);
|
|
446
|
-
await requester.send(message)
|
|
447
|
-
const [result] = await requester.receive()
|
|
448
|
-
var results = JSON.parse(result.toString())
|
|
449
|
-
//console.log(results);
|
|
450
|
-
if (results["input"].includes(topic)) {
|
|
451
|
-
console.log("")
|
|
452
|
-
openpub(master_ip)
|
|
453
|
-
}
|
|
454
|
-
else {
|
|
455
|
-
console.log("Topic is not registered");
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
run()
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
program
|
|
463
|
-
.command('open [programName]')
|
|
464
|
-
.description('Open a NEP+ GUI')
|
|
465
|
-
.action(async (programName) => {
|
|
466
|
-
const programs = ['cameras', 'hxri'];
|
|
467
|
-
|
|
468
|
-
// If a program name is provided as an argument, use it; otherwise, show autocomplete
|
|
469
|
-
if (!programName) {
|
|
470
|
-
const autoComplete = new AutoComplete({
|
|
471
|
-
name: 'program',
|
|
472
|
-
message: 'Select a program:',
|
|
473
|
-
choices: programs,
|
|
474
|
-
});
|
|
475
|
-
programName = await autoComplete.run();
|
|
476
|
-
} else if (!programs.includes(programName)) {
|
|
477
|
-
const autoComplete = new AutoComplete({
|
|
478
|
-
name: 'program',
|
|
479
|
-
message: 'Invalid program name. Select a valid program:',
|
|
480
|
-
choices: programs,
|
|
481
|
-
});
|
|
482
|
-
programName = await autoComplete.run();
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Get the username of the logged-in user
|
|
486
|
-
const username = os.userInfo().username;
|
|
487
|
-
|
|
488
|
-
// Specify the path to the executable
|
|
489
|
-
const appFolderName = 'nepplus' + "-" + programName;
|
|
490
|
-
const executableName = 'nepplus' + "-" + `${programName}.exe`;
|
|
491
|
-
const executablePath = `C:\\Users\\${username}\\AppData\\Local\\Programs\\${appFolderName}\\${executableName}`;
|
|
492
|
-
|
|
493
|
-
// Run the executable with proper escaping
|
|
494
|
-
exec(`"${executablePath}"`, (error, stdout, stderr) => {
|
|
495
|
-
if (error) {
|
|
496
|
-
console.error(`Error: ${error.message}`);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
console.log(`stdout: ${stdout}`);
|
|
500
|
-
console.error(`stderr: ${stderr}`);
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
|
|
505
500
|
program
|
|
506
501
|
.command('master')
|
|
507
|
-
.description('
|
|
502
|
+
.description('Start master for managing NEP+ topics')
|
|
508
503
|
.action(async () => {
|
|
509
504
|
try {
|
|
510
505
|
var node = new nep.Node("nep-cli");
|
|
@@ -577,8 +572,8 @@ program
|
|
|
577
572
|
program
|
|
578
573
|
.command('show [topic] [index]')
|
|
579
574
|
.description('Displays messages published to a specified NEP+ topic in the default browser. ' +
|
|
580
|
-
|
|
581
|
-
|
|
575
|
+
'The [index] parameter specifies the index of the message to display and must be a number between 0 and 49. '
|
|
576
|
+
)
|
|
582
577
|
.action(async (topic, index) => {
|
|
583
578
|
const ip = "127.0.0.1";
|
|
584
579
|
|
|
@@ -593,13 +588,12 @@ program
|
|
|
593
588
|
index = await selectIndex(index);
|
|
594
589
|
|
|
595
590
|
const topicid = `${index}: ${topic}`;
|
|
596
|
-
if(parseInt(index) < 50)
|
|
597
|
-
{
|
|
591
|
+
if (parseInt(index) < 50) {
|
|
598
592
|
const port = PORT_SERVER + parseInt(index);
|
|
599
593
|
console.log(PORT_SERVER)
|
|
600
594
|
console.log(index)
|
|
601
595
|
console.log(PORT_SERVER + parseInt(index))
|
|
602
|
-
|
|
596
|
+
|
|
603
597
|
if (msg_type === "images") {
|
|
604
598
|
open(`http://localhost:${port}/?port=${port}&topic=${encodeURIComponent(topicid)}`);
|
|
605
599
|
startShowServer(port, topic, ip);
|
|
@@ -613,7 +607,7 @@ program
|
|
|
613
607
|
else {
|
|
614
608
|
console.log("Index must be between 0 and 49");
|
|
615
609
|
}
|
|
616
|
-
|
|
610
|
+
|
|
617
611
|
});
|
|
618
612
|
|
|
619
613
|
|
|
@@ -719,6 +713,325 @@ program
|
|
|
719
713
|
|
|
720
714
|
});
|
|
721
715
|
|
|
716
|
+
program
|
|
717
|
+
.command('delay [topic]')
|
|
718
|
+
.description('Monitor the delay between messages of a NEP+ topic')
|
|
719
|
+
.action(async (topic, msg_type) => {
|
|
720
|
+
const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
|
|
721
|
+
const master_ip = "127.0.0.1";
|
|
722
|
+
|
|
723
|
+
const requester = createRequester(master_ip, PORT_MASTER_INFO);
|
|
724
|
+
const results = await sendRequest(requester, { "input": "topics" });
|
|
725
|
+
|
|
726
|
+
topic = await selectTopic(results, topic);
|
|
727
|
+
msg_type = await selectMsgType(allowedFormats, msg_type, topic);
|
|
728
|
+
|
|
729
|
+
let lastTimestamp = null; // Store timestamp of the last received message
|
|
730
|
+
|
|
731
|
+
const callback = function (msg) {
|
|
732
|
+
const date = new Date();
|
|
733
|
+
const timestamp = date.getTime();
|
|
734
|
+
|
|
735
|
+
if (lastTimestamp !== null) {
|
|
736
|
+
const delay = timestamp - lastTimestamp;
|
|
737
|
+
console.log("Delay:", delay.toFixed(2), "ms");
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
lastTimestamp = timestamp;
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
openSub(master_ip, topic, msg_type, callback);
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
program
|
|
747
|
+
.command('bw [topic]')
|
|
748
|
+
.description('Monitor the bandwidth used by a NEP+ topic')
|
|
749
|
+
.action(async (topic, msg_type) => {
|
|
750
|
+
const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
|
|
751
|
+
const master_ip = "127.0.0.1";
|
|
752
|
+
|
|
753
|
+
const requester = createRequester(master_ip, PORT_MASTER_INFO);
|
|
754
|
+
const results = await sendRequest(requester, { "input": "topics" });
|
|
755
|
+
|
|
756
|
+
topic = await selectTopic(results, topic);
|
|
757
|
+
msg_type = await selectMsgType(allowedFormats, msg_type, topic);
|
|
758
|
+
|
|
759
|
+
let totalBytes = 0; // Store total bytes received
|
|
760
|
+
|
|
761
|
+
const callback = function (msg) {
|
|
762
|
+
let strMsg;
|
|
763
|
+
|
|
764
|
+
if (typeof msg === 'string') {
|
|
765
|
+
strMsg = msg;
|
|
766
|
+
} else if (Buffer.isBuffer(msg)) {
|
|
767
|
+
strMsg = msg.toString('utf8');
|
|
768
|
+
} else {
|
|
769
|
+
// Convert JSON to string
|
|
770
|
+
strMsg = JSON.stringify(msg);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const bytes = Buffer.byteLength(strMsg, 'utf8');
|
|
774
|
+
totalBytes += bytes;
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
setInterval(() => {
|
|
778
|
+
console.log("Bandwidth:", (totalBytes / (1024 * 1024)).toFixed(2), "MB");
|
|
779
|
+
totalBytes = 0; // Reset total bytes for the next interval
|
|
780
|
+
}, 1000);
|
|
781
|
+
|
|
782
|
+
openSub(master_ip, topic, msg_type, callback);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
program
|
|
787
|
+
.command('publish <topic> <message>')
|
|
788
|
+
.description('Publish a message to a NEP+ topic (only JSON messages are supported)')
|
|
789
|
+
.action((topic, message) => {
|
|
790
|
+
|
|
791
|
+
var openpub = function (master_ip = "127.0.0.1") {
|
|
792
|
+
|
|
793
|
+
var pubFunction = function () {
|
|
794
|
+
|
|
795
|
+
var msg = message.replace(/'/g, '"')
|
|
796
|
+
console.log(JSON.parse(msg))
|
|
797
|
+
pub.publish(JSON.parse(msg))
|
|
798
|
+
console.log("Message published")
|
|
799
|
+
|
|
800
|
+
process.exit(1)
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
var node = new nep.Node("nep-cli-pub")
|
|
805
|
+
var pub = node.new_pub(topic, "json")
|
|
806
|
+
setTimeout(pubFunction, 1000)
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
async function run() {
|
|
810
|
+
var requester = new zmq.Request;
|
|
811
|
+
var master_ip = "127.0.0.1"
|
|
812
|
+
|
|
813
|
+
requester.connect("tcp://" + master_ip + ":" + PORT_MASTER_INFO);
|
|
814
|
+
|
|
815
|
+
let msg = { "input": "topics" }
|
|
816
|
+
var message = JSON.stringify(msg);
|
|
817
|
+
await requester.send(message)
|
|
818
|
+
const [result] = await requester.receive()
|
|
819
|
+
var results = JSON.parse(result.toString())
|
|
820
|
+
//console.log(results);
|
|
821
|
+
if (results["input"].includes(topic)) {
|
|
822
|
+
console.log("")
|
|
823
|
+
openpub(master_ip)
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
console.log("Topic is not registered");
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
run()
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
const { Input } = require('enquirer');
|
|
834
|
+
|
|
835
|
+
const selectTimeout = async (defaultTimeout = 1000) => {
|
|
836
|
+
try {
|
|
837
|
+
const prompt = new Input({
|
|
838
|
+
name: 'timeout',
|
|
839
|
+
message: 'Enter the timeout in milliseconds:',
|
|
840
|
+
initial: defaultTimeout,
|
|
841
|
+
validate: value => /^\d+$/.test(value) ? true : 'Timeout must be a number'
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
const timeout = await prompt.run();
|
|
845
|
+
return Number(timeout);
|
|
846
|
+
} catch (error) {
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
program
|
|
852
|
+
.command('record [topic] [msg_type] [timeout]')
|
|
853
|
+
.description('Record messages from a NEP+ topic to a file')
|
|
854
|
+
.action(async (topic, msg_type, timeout = 60000) => {
|
|
855
|
+
const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
|
|
856
|
+
const master_ip = "127.0.0.1";
|
|
857
|
+
|
|
858
|
+
const requester = createRequester(master_ip, PORT_MASTER_INFO);
|
|
859
|
+
const results = await sendRequest(requester, { "input": "topics" });
|
|
860
|
+
|
|
861
|
+
topic = await selectTopic(results, topic);
|
|
862
|
+
msg_type = await selectMsgType(allowedFormats, msg_type, topic);
|
|
863
|
+
timeout = await selectTimeout(timeout);
|
|
864
|
+
|
|
865
|
+
const date = new Date();
|
|
866
|
+
const timestamp = date.toISOString().replace(/:/g, '-');
|
|
867
|
+
const writeStream = fs.createWriteStream(`${topic.replace('/', '_')}_bag_${timestamp}.json`);
|
|
868
|
+
|
|
869
|
+
let lastTimestamp = null;
|
|
870
|
+
|
|
871
|
+
const callback = function (msg) {
|
|
872
|
+
const timestamp = Date.now();
|
|
873
|
+
const delay = lastTimestamp !== null ? timestamp - lastTimestamp : 0;
|
|
874
|
+
lastTimestamp = timestamp;
|
|
875
|
+
|
|
876
|
+
const record = { msg, delay };
|
|
877
|
+
writeStream.write(JSON.stringify(record) + '\n');
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
setTimeout(() => {
|
|
882
|
+
writeStream.end();
|
|
883
|
+
console.log('Recording stopped and file saved.');
|
|
884
|
+
process.exit();
|
|
885
|
+
}, timeout);
|
|
886
|
+
|
|
887
|
+
openSub(master_ip, topic, msg_type, callback);
|
|
888
|
+
setTimeout(() => {
|
|
889
|
+
console.log('\nRecording has started. Press Ctrl+C to stop and save the file');
|
|
890
|
+
}, 1000);
|
|
891
|
+
|
|
892
|
+
process.on('SIGINT', () => {
|
|
893
|
+
writeStream.end();
|
|
894
|
+
console.log('Recording stopped and file saved.');
|
|
895
|
+
process.exit();
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
const readline = require('readline');
|
|
903
|
+
const e = require('express');
|
|
904
|
+
|
|
905
|
+
program
|
|
906
|
+
.command('play <bagfile> <topic>')
|
|
907
|
+
.description('Play messages from a bag file to a NEP+ topic')
|
|
908
|
+
.action(async (bagfile, topic) => {
|
|
909
|
+
const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
|
|
910
|
+
const master_ip = "127.0.0.1";
|
|
911
|
+
|
|
912
|
+
const requester = createRequester(master_ip, PORT_MASTER_INFO);
|
|
913
|
+
const results = await sendRequest(requester, { "input": "topics" });
|
|
914
|
+
|
|
915
|
+
topic = await selectTopic(results, topic);
|
|
916
|
+
|
|
917
|
+
const node = new nep.Node("nep-cli-pub");
|
|
918
|
+
const pub = node.new_pub(topic, "json");
|
|
919
|
+
|
|
920
|
+
const readInterface = readline.createInterface({
|
|
921
|
+
input: fs.createReadStream(bagfile),
|
|
922
|
+
console: false
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
let records = [];
|
|
926
|
+
|
|
927
|
+
readInterface.on('line', function (line) {
|
|
928
|
+
const record = JSON.parse(line);
|
|
929
|
+
records.push(record);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
readInterface.on('close', function () {
|
|
933
|
+
let index = 0;
|
|
934
|
+
|
|
935
|
+
setTimeout(() => {
|
|
936
|
+
console.log("\nPublishing messages from bag file. Press Ctrl+C to stop.");
|
|
937
|
+
}, 500);
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
function publishNext() {
|
|
941
|
+
if (index < records.length) {
|
|
942
|
+
const record = records[index];
|
|
943
|
+
pub.publish(record.msg);
|
|
944
|
+
index++;
|
|
945
|
+
let delay = 0;
|
|
946
|
+
if (index < records.length) {
|
|
947
|
+
delay = records[index].delay;
|
|
948
|
+
}
|
|
949
|
+
setTimeout(publishNext, delay);
|
|
950
|
+
} else {
|
|
951
|
+
process.exit();
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
publishNext();
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
program
|
|
962
|
+
.command('app')
|
|
963
|
+
.description('Open NEP+ App (only if installed)')
|
|
964
|
+
.action(() => {
|
|
965
|
+
// Get the username of the logged-in user
|
|
966
|
+
const username = os.userInfo().username;
|
|
967
|
+
|
|
968
|
+
// Specify the path to the executable
|
|
969
|
+
const appFolderName = 'nepplus-app';
|
|
970
|
+
const executableName = 'nepplus-app.exe';
|
|
971
|
+
const executablePath = `C:\\Users\\${username}\\AppData\\Local\\Programs\\${appFolderName}\\${executableName}`;
|
|
972
|
+
|
|
973
|
+
// Run the executable with proper escaping
|
|
974
|
+
exec(`"${executablePath}"`, (error, stdout, stderr) => {
|
|
975
|
+
if (error) {
|
|
976
|
+
console.error(`Error: ${error.message}`);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
console.log(`stdout: ${stdout}`);
|
|
980
|
+
console.error(`stderr: ${stderr}`);
|
|
981
|
+
});
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
program
|
|
986
|
+
.command('open [programName]')
|
|
987
|
+
.description('Open a NEP+ GUIs (only if installed)')
|
|
988
|
+
.action(async (programName) => {
|
|
989
|
+
const programs = ['cameras', 'hxri'];
|
|
990
|
+
|
|
991
|
+
// If a program name is provided as an argument, use it; otherwise, show autocomplete
|
|
992
|
+
if (!programName) {
|
|
993
|
+
try {
|
|
994
|
+
const autoComplete = new AutoComplete({
|
|
995
|
+
name: 'program',
|
|
996
|
+
message: 'Select a program:',
|
|
997
|
+
choices: programs,
|
|
998
|
+
});
|
|
999
|
+
programName = await autoComplete.run();
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
process.exit(1);
|
|
1002
|
+
}
|
|
1003
|
+
} else if (!programs.includes(programName)) {
|
|
1004
|
+
|
|
1005
|
+
try {
|
|
1006
|
+
const autoComplete = new AutoComplete({
|
|
1007
|
+
name: 'program',
|
|
1008
|
+
message: 'Invalid program name. Select a valid program:',
|
|
1009
|
+
choices: programs,
|
|
1010
|
+
});
|
|
1011
|
+
programName = await autoComplete.run();
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
process.exit(1);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Get the username of the logged-in user
|
|
1018
|
+
const username = os.userInfo().username;
|
|
1019
|
+
|
|
1020
|
+
// Specify the path to the executable
|
|
1021
|
+
const appFolderName = 'nepplus' + "-" + programName;
|
|
1022
|
+
const executableName = 'nepplus' + "-" + `${programName}.exe`;
|
|
1023
|
+
const executablePath = `C:\\Users\\${username}\\AppData\\Local\\Programs\\${appFolderName}\\${executableName}`;
|
|
1024
|
+
|
|
1025
|
+
// Run the executable with proper escaping
|
|
1026
|
+
exec(`"${executablePath}"`, (error, stdout, stderr) => {
|
|
1027
|
+
if (error) {
|
|
1028
|
+
console.error(`Error: ${error.message}`);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
console.log(`stdout: ${stdout}`);
|
|
1032
|
+
console.error(`stderr: ${stderr}`);
|
|
1033
|
+
});
|
|
1034
|
+
});
|
|
722
1035
|
|
|
723
1036
|
program
|
|
724
1037
|
.command('docs')
|