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