nep-cli 0.2.1 → 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/index.js CHANGED
@@ -18,8 +18,8 @@ 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
21
- const PORT_IMAGES = 50060;
22
- const PORT_JSON = 50080;
21
+ const PORT_SERVER = 50050;
22
+
23
23
 
24
24
 
25
25
  program
@@ -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 reply.send(processMsg(node_request, nodes_register, topic_register))
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,109 +121,173 @@ var onRegisteredTopic = function (node_request, topic_register, topic) {
120
121
  }
121
122
 
122
123
 
123
- var onResetTopic = function (node_request) {
124
-
125
- var topic = node_request["topic"]
126
- nep_configuration["current_port"] = Math.max(nep_configuration["current_port"], node_request["port"] + 2);
127
-
128
- if (node_request["socket"] === "publisher" || node_request["socket"] === "subscriber") {
129
- // Create new broker for many2many communication
130
- if (node_request["mode"] === "many2many") {
131
- restartBroker(node_request["topic"], node_request["port"]);
132
- }
133
-
134
- if ("msg_type" in node_request) {
135
- topic_register[topic] = { "port": node_request["port"], "socket": node_request["socket"], 'ip': nep_configuration["IP"], "mode": node_request["mode"], "msg_type": node_request["msg_type"] }
136
- }
137
- else {
138
- topic_register[topic] = { "port": node_request["port"], "socket": node_request["socket"], 'ip': nep_configuration["IP"], "mode": node_request["mode"], "msg_type": "json" }
139
- }
140
-
141
- topic_register[topic]["nodes"] = [];
142
- }
143
-
144
- }
124
+ var onNewTopic = async function (node_request, topic_register) {
125
+ // Assign new ports
126
+ nep_configuration["current_port"] += 2;
145
127
 
146
- var onNewTopic = function (node_request, topic_register) {
128
+ // Topic must be a string
129
+ var topic = String(node_request['topic']);
130
+ var msg = {};
147
131
 
148
- // Asign new ports
149
- nep_configuration["current_port"] = nep_configuration["current_port"] + 2
150
- // Topic must be an string
151
- topic = String(node_request['topic'])
152
- var msg = {}
153
132
  // If publisher or subscriber
154
133
  if (node_request["socket"] === "publisher" || node_request["socket"] === "subscriber") {
155
134
  // Create new broker for many2many communication
156
135
  if (node_request["mode"] === "many2many") {
157
- createBroker(topic);
158
- msg = m2mResponse(node_request, topic_register, topic);
136
+ await createBroker(topic);
137
+ msg = ManyToManyResponse(node_request, topic_register, topic);
159
138
  }
160
139
  }
161
- else if (node_request["socket"] === "surveyor" || node_request["socket"] === "respondent") {
162
- msg = surveyResponse(node_request, topic_register, topic)
163
- }
140
+
141
+ // If client or server
164
142
  else if (node_request["socket"] === "client" || node_request["socket"] === "server") {
165
- msg = csResponse(node_request, topic_register, topic)
143
+ msg = csResponse(node_request, topic_register, topic);
166
144
  }
145
+
167
146
  // Key for related nodes
168
- topic_register[topic]["nodes"] = {}
147
+ topic_register[topic]["nodes"] = {};
148
+
169
149
  // Set PID of node
170
150
  updatePID(node_request, topic_register, topic);
171
151
 
172
- if ("language" in node_request) {
173
-
174
- if (node_request["language"] == "C++") {
175
- msg.port = msg.port.toString();
176
- }
177
-
152
+ // If the language is C++, convert the port to a string
153
+ if (node_request["language"] === "C++") {
154
+ msg.port = msg.port.toString();
178
155
  }
179
156
 
180
- return msg
157
+ return msg;
181
158
  }
182
159
 
183
160
 
184
- var createBroker = function (topic) {
185
- try {
186
- var broker = new Broker(nep_configuration["IP"], nep_configuration["current_port"] + 1, nep_configuration["current_port"])
187
- //var brokersubex = new BrokerBridgeSUBEX(conf["IP"],IP_EXTERNAL, conf["current_port"] , conf["current_port"])
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;
188
168
 
189
- // Add broker defined to list of brokers
190
- nep_configuration["brokers"][topic] = broker
191
- } catch (error) {
192
- // console.log("NEP ERROR: ports " + String(nep_configuration["current_port"]) + " and " + String(nep_configuration["current_port"] + 1) + " not avaliable")
193
- nep_configuration["current_port"] = nep_configuration["current_port"] + 2
194
- var broker = new nep.Broker(nep_configuration["IP"], nep_configuration["current_port"] + 1, nep_configuration["current_port"])
195
- // Add broker defined to list of brokers
196
- nep_configuration["brokers"][topic] = broker
169
+ // Use ZMQ version 5
170
+ this.zmqc = require("zeromq/v5-compat");
197
171
  }
198
- }
199
172
 
200
- var restartBroker = function (topic, port) {
201
- try {
202
- var broker = new nep.Broker(nep_configuration["IP"], port + 1, port)
203
- //var brokersubex = new BrokerBridgeSUBEX(conf["IP"],IP_EXTERNAL, conf["current_port"] , conf["current_port"])
204
- // Add broker defined to list of brokers
205
- nep_configuration["brokers"][topic] = broker
206
- } catch (error) {
207
- console.log("NEP ERROR: ports " + String(port) + " and " + String(port + 1) + " not avaliable")
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
+ }
208
196
  }
209
- }
210
197
 
211
- var m2mResponse = function (node_request, topic_register, topic) {
212
- if ("msg_type" in node_request) {
213
- topic_register[topic] = { "port": nep_configuration["current_port"], "socket": node_request["socket"], 'ip': nep_configuration["IP"], "mode": node_request["mode"], "msg_type": node_request["msg_type"] }
214
- }
215
- else {
216
- topic_register[topic] = { "port": nep_configuration["current_port"], "socket": node_request["socket"], 'ip': nep_configuration["IP"], "mode": node_request["mode"], "msg_type": "json" }
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");
217
217
  }
218
- return { 'topic': topic, 'port': nep_configuration["current_port"], 'mode': node_request["mode"], 'ip': nep_configuration["IP"], 'socket': node_request["socket"], "state": "success" }
219
218
  }
220
219
 
221
- var surveyResponse = function (node_request, topic_register, topic) {
222
- topic_register[topic] = { "port": nep_configuration["current_port"], "socket": node_request["socket"], 'ip': nep_configuration["IP"] }
223
- return { 'topic': topic, 'port': nep_configuration["current_port"], 'ip': nep_configuration["IP"], 'socket': node_request["socket"], "state": "success" }
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
+ });
224
236
  }
225
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
+ };
288
+ }
289
+
290
+
226
291
  var csResponse = function (node_request, topic_register, topic) {
227
292
  topic_register[topic] = { "port": nep_configuration["current_port"], "socket": node_request["socket"], 'ip': nep_configuration["IP"], "msg_type": "json" }
228
293
  return { 'topic': topic, 'port': nep_configuration["current_port"], 'ip': nep_configuration["IP"], 'socket': node_request["socket"], "state": "success" }
@@ -239,27 +304,16 @@ var updatePID = function (node_request, topic_register, topic) {
239
304
  }
240
305
 
241
306
 
242
- var restartTopics = function (node_request) {
243
- console.log(node_request)
244
- // Get topic name
245
- var topic = String(node_request['topic'])
246
-
247
- console.log(" --- Reset topic : *" + topic + "* ---")
248
- // Create new broker
249
- onResetTopic(node_request);
250
- onUpdateTopicList(topic_register)
251
- }
252
-
253
- const processMsg = (json_msg, nodes_register, topic_register) => {
307
+ const processMsg = async (json_msg, nodes_register, topic_register) => {
254
308
  const node_request = JSON.parse(json_msg);
255
309
  const { node, socket, topic, pid } = node_request;
256
- console.log(`NEP: Node request - node: ${node}, socket: ${socket}, topic: ${topic}`);
310
+ console.log(`\n --- REQUEST: node: ${node}, socket: ${socket}, topic: ${topic} ---`);
257
311
 
258
312
  // Check node status
259
313
  if (node) {
260
314
  // Kill previous node with the same name
261
315
  if (node in nodes_register) {
262
- console.log(`--- Node *${node}* already defined ---`);
316
+ console.log(`NODE: *${node}* already registered`);
263
317
  const current_pid = nodes_register[node];
264
318
  if (pid !== current_pid) {
265
319
  if (!node.startsWith('nep-cli')) {
@@ -269,7 +323,7 @@ const processMsg = (json_msg, nodes_register, topic_register) => {
269
323
  }
270
324
  }
271
325
  } else {
272
- console.log(`--- New Node *${node}* ---`);
326
+ console.log(`NODE: *${node}* was registered`);
273
327
  }
274
328
  }
275
329
 
@@ -279,14 +333,14 @@ const processMsg = (json_msg, nodes_register, topic_register) => {
279
333
  // Check topic status
280
334
  const topicStr = String(topic);
281
335
  if (topicStr in topic_register) {
282
- console.log(`--- Topic *${topicStr}* already registered ---`);
336
+ console.log(`TOPIC: *${topicStr}* already registered ---`);
283
337
  // Send information of topic already defined
284
338
  const response = onRegisteredTopic(node_request, topic_register, topicStr);
285
339
  return JSON.stringify(response);
286
340
  } else {
287
- console.log(`--- New topic: *${topicStr}* ---`);
341
+ console.log(`TOPIC: *${topicStr}* was registered`);
288
342
  // Create new broker
289
- const response = onNewTopic(node_request, topic_register);
343
+ const response = await onNewTopic(node_request, topic_register);
290
344
  return JSON.stringify(response);
291
345
  }
292
346
  };
@@ -297,6 +351,10 @@ function startShowServer(port, topic, ip) {
297
351
  const io = socketIo(server);
298
352
  const path = require('path');
299
353
 
354
+ // Serve static files from node_modules
355
+ app.use(express.static(path.join(__dirname)));
356
+
357
+
300
358
  const node = new nep.Node("nep-cli-sub");
301
359
  const config = node.hybrid(ip)
302
360
 
@@ -330,6 +388,7 @@ function startJsonServer(port, topic, ip, msg_type = "json") {
330
388
  const server = http.createServer(app);
331
389
  const io = socketIo(server);
332
390
  const path = require('path');
391
+ app.use(express.static(path.join(__dirname)));
333
392
 
334
393
  const node = new nep.Node("nep-cli-sub");
335
394
  const config = node.hybrid(ip)
@@ -364,76 +423,83 @@ function startJsonServer(port, topic, ip, msg_type = "json") {
364
423
  }
365
424
 
366
425
 
367
- program
368
- .command('app')
369
- .description('Open NEP+ App')
370
- .action(() => {
371
- // Get the username of the logged-in user
372
- const username = os.userInfo().username;
426
+ const createRequester = (master_ip, port) => {
427
+ const requester = new zmq.Request();
428
+ requester.connect(`tcp://${master_ip}:${port}`);
429
+ return requester;
430
+ };
373
431
 
374
- // Specify the path to the executable
375
- const appFolderName = 'nepplus-app';
376
- const executableName = 'nepplus-app.exe';
377
- const executablePath = `C:\\Users\\${username}\\AppData\\Local\\Programs\\${appFolderName}\\${executableName}`;
432
+ const sendRequest = async (requester, msg) => {
433
+ const message = JSON.stringify(msg);
434
+ await requester.send(message);
435
+ const [result] = await requester.receive();
436
+ return JSON.parse(result.toString());
437
+ };
378
438
 
379
- // Run the executable with proper escaping
380
- exec(`"${executablePath}"`, (error, stdout, stderr) => {
381
- if (error) {
382
- console.error(`Error: ${error.message}`);
383
- return;
384
- }
385
- console.log(`stdout: ${stdout}`);
386
- console.error(`stderr: ${stderr}`);
439
+ const selectTopic = async (results, topic) => {
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);
452
+ }
453
+ };
454
+
455
+ const selectIndex = async (index, maxIndex = 9) => {
456
+ try {
457
+ const choices = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
458
+
459
+ const autoComplete = new AutoComplete({
460
+ name: 'index',
461
+ message: !index ? 'Select a index:' : 'Invalid index. Select a valid index:',
462
+ choices: choices,
387
463
  });
388
- });
464
+ index = await autoComplete.run();
389
465
 
466
+ return index;
467
+ } catch (error) {
468
+ process.exit(1);
469
+ }
470
+ };
390
471
 
391
- program
392
- .command('open [programName]')
393
- .description('Open a NEP+ GUI')
394
- .action(async (programName) => {
395
- const programs = ['cameras', 'hxri'];
472
+ const selectMsgType = async (allowedFormats, msg_type, topic) => {
473
+ try {
474
+ var msgType = topic.split('/').pop();
475
+ if (msgType && msgType !== topic) {
476
+ msg_type = msgType;
477
+ }
396
478
 
397
- // If a program name is provided as an argument, use it; otherwise, show autocomplete
398
- if (!programName) {
479
+ if (!msg_type || !allowedFormats.includes(msg_type)) {
399
480
  const autoComplete = new AutoComplete({
400
- name: 'program',
401
- message: 'Select a program:',
402
- choices: programs,
481
+ name: 'msg_type',
482
+ message: !msg_type ? 'Select a message type:' : `Select a valid format:`,
483
+ choices: allowedFormats,
403
484
  });
404
- programName = await autoComplete.run();
405
- } else if (!programs.includes(programName)) {
406
- const autoComplete = new AutoComplete({
407
- name: 'program',
408
- message: 'Invalid program name. Select a valid program:',
409
- choices: programs,
410
- });
411
- programName = await autoComplete.run();
485
+ msg_type = await autoComplete.run();
412
486
  }
487
+ return msg_type;
488
+ } catch (error) {
489
+ process.exit(1);
490
+ }
491
+ };
413
492
 
414
- // Get the username of the logged-in user
415
- const username = os.userInfo().username;
416
-
417
- // Specify the path to the executable
418
- const appFolderName = 'nepplus' + "-" + programName;
419
- const executableName = 'nepplus' + "-" + `${programName}.exe`;
420
- const executablePath = `C:\\Users\\${username}\\AppData\\Local\\Programs\\${appFolderName}\\${executableName}`;
421
-
422
- // Run the executable with proper escaping
423
- exec(`"${executablePath}"`, (error, stdout, stderr) => {
424
- if (error) {
425
- console.error(`Error: ${error.message}`);
426
- return;
427
- }
428
- console.log(`stdout: ${stdout}`);
429
- console.error(`stderr: ${stderr}`);
430
- });
431
- });
493
+ const openSub = (master_ip, topic, msg_type, callback) => {
494
+ const node = new nep.Node("nep-cli-sub");
495
+ const conf = node.hybrid(master_ip);
496
+ const sub = node.new_sub(topic, msg_type, callback, conf);
497
+ };
432
498
 
433
499
 
434
500
  program
435
501
  .command('master')
436
- .description('Start NEP master in localhost')
502
+ .description('Start master for managing NEP+ topics')
437
503
  .action(async () => {
438
504
  try {
439
505
  var node = new nep.Node("nep-cli");
@@ -452,7 +518,7 @@ program
452
518
 
453
519
  program
454
520
  .command('topics')
455
- .description('Get list of NEP+ topics')
521
+ .description('Displays the list of NEP+ topics')
456
522
  .action(async () => {
457
523
  const master_ip = "127.0.0.1";
458
524
  const TIMEOUT_DURATION = 5000; // Timeout duration in milliseconds
@@ -503,141 +569,118 @@ program
503
569
  await getTopics(); // Call the function to get topics
504
570
  });
505
571
 
506
- program
507
- .command('show <topic> <msg_type> [index]')
508
- .description('Show images published to a NEP+ topic in the default browser')
509
- .action(async (topic, msg_type, index = 0) => {
510
- const ip = "127.0.0.1";
511
- const topicid = `${topic}`;
512
- const port = PORT_IMAGES + parseInt(index);
513
-
514
- const allowedFormats = ["json", "images", "dictionary"];
515
- if (!allowedFormats.includes(msg_type)) {
516
- console.error(`Format '${msg_type}' no allowed. Allowed formats are: ${allowedFormats.join(', ')}`);
517
- return;
518
- }
572
+ program
573
+ .command('show [topic] [index]')
574
+ .description('Displays messages published to a specified NEP+ topic in the default browser. ' +
575
+ 'The [index] parameter specifies the index of the message to display and must be a number between 0 and 49. '
576
+ )
577
+ .action(async (topic, index) => {
578
+ const ip = "127.0.0.1";
519
579
 
520
- if(msg_type === "images"){
580
+ const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
581
+ const master_ip = "127.0.0.1";
582
+
583
+ const requester = createRequester(master_ip, PORT_MASTER_INFO);
584
+ const results = await sendRequest(requester, { "input": "topics" });
585
+
586
+ topic = await selectTopic(results, topic);
587
+ var msg_type = await selectMsgType(allowedFormats, msg_type, topic);
588
+ index = await selectIndex(index);
589
+
590
+ const topicid = `${index}: ${topic}`;
591
+ if (parseInt(index) < 50) {
592
+ const port = PORT_SERVER + parseInt(index);
593
+ console.log(PORT_SERVER)
594
+ console.log(index)
595
+ console.log(PORT_SERVER + parseInt(index))
596
+
597
+ if (msg_type === "images") {
521
598
  open(`http://localhost:${port}/?port=${port}&topic=${encodeURIComponent(topicid)}`);
522
599
  startShowServer(port, topic, ip);
523
600
  }
524
- else if (msg_type === "json" || msg_type === "dictionary")
525
- {
601
+ else if (msg_type === "json" || msg_type === "dictionary") {
526
602
  open(`http://localhost:${port}/?port=${port}&topic=${encodeURIComponent(topicid)}`);
527
603
  startJsonServer(port, topic, ip, msg_type);
528
604
  }
529
- });
530
605
 
531
- program
532
- .command('echo <topic> <msg_type>')
533
- .description('Subscribe to a NEP+ topic and display raw string messages')
534
- .action(async (topic, msg_type) => {
535
-
536
-
537
-
538
- const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
539
- if (!allowedFormats.includes(msg_type)) {
540
- console.error(`Format '${msg_type}' no allowed. Allowed formats are: ${allowedFormats.join(', ')}`);
541
- return;
606
+ }
607
+ else {
608
+ console.log("Index must be between 0 and 49");
542
609
  }
543
610
 
544
- var opensub = function (master_ip = "127.0.0.1", msg_type) {
545
- var callback = function (msg) {
546
- var date = new Date();
547
- var dateString = date.toISOString();
548
- console.log(dateString);
611
+ });
549
612
 
550
- if (msg.length > 10000) {
551
- console.log("the message is too long to be displayed");
552
- } else {
553
- console.log(msg);
554
- }
555
- };
556
613
 
557
- var node = new nep.Node("nep-cli-sub");
558
- var conf = node.hybrid(master_ip);
559
- sub = node.new_sub(topic, msg_type, callback, conf);
560
- };
614
+ program
615
+ .command('echo [topic]')
616
+ .description('Subscribe to a NEP+ topic and display raw string messages')
617
+ .action(async (topic, msg_type) => {
618
+ const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
619
+ const master_ip = "127.0.0.1";
561
620
 
621
+ const requester = createRequester(master_ip, PORT_MASTER_INFO);
622
+ const results = await sendRequest(requester, { "input": "topics" });
562
623
 
563
- var master_ip = "127.0.0.1";
564
- async function run() {
565
- var requester = new zmq.Request();
566
- requester.connect("tcp://" + master_ip + ":" + PORT_MASTER_INFO);
624
+ topic = await selectTopic(results, topic);
625
+ msg_type = await selectMsgType(allowedFormats, msg_type, topic);
567
626
 
568
- let msg = { "input": "topics" };
569
- var message = JSON.stringify(msg);
570
- await requester.send(message);
571
- const [result] = await requester.receive();
572
- var results = JSON.parse(result.toString());
627
+ const callback = (msg) => {
628
+ const date = new Date();
629
+ const dateString = date.toISOString();
630
+ console.log(dateString);
573
631
 
574
- if (results["state"] === "failure") {
575
- console.log("Topic is not registered");
632
+ if (msg.length > 10000) {
633
+ console.log("the message is too long to be displayed");
576
634
  } else {
577
- console.log("");
578
- if (results["input"].includes(topic)) {
579
- opensub(master_ip, msg_type);
580
- } else {
581
- console.log("Topic is not registered");
582
- }
635
+ console.log(msg);
583
636
  }
584
- }
637
+ };
585
638
 
586
- run();
639
+ openSub(master_ip, topic, msg_type, callback);
587
640
  });
588
641
 
589
642
  program
590
- .command('hz <topic> <msg_type>')
591
- .description('Monitor the publishing rate of a NEP+ topic in localhost')
643
+ .command('hz [topic]')
644
+ .description('Monitor the publishing rate of a NEP+ topic')
592
645
  .action(async (topic, msg_type) => {
593
- const interfaces = os.networkInterfaces();
594
- const master_ip = "127.0.0.1"; // Change to the appropriate master IP
646
+ const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
647
+ const master_ip = "127.0.0.1";
595
648
 
596
- const opensub = function () {
597
- const timestamps = []; // To store message timestamps
649
+ const requester = createRequester(master_ip, PORT_MASTER_INFO);
650
+ const results = await sendRequest(requester, { "input": "topics" });
651
+ const timestamps = []; // Store timestamps of received messages
598
652
 
599
- const callback = function (msg) {
600
- const date = new Date();
601
- const timestamp = date.getTime();
653
+ topic = await selectTopic(results, topic);
654
+ msg_type = await selectMsgType(allowedFormats, msg_type, topic);
602
655
 
603
- timestamps.push(timestamp);
656
+ const callback = function (msg) {
657
+ const date = new Date();
658
+ const timestamp = date.getTime();
604
659
 
605
- // Remove timestamps older than ten seconds
606
- const tenSecondsAgo = timestamp - 10000;
607
- while (timestamps[0] < tenSecondsAgo) {
608
- timestamps.shift();
609
- }
610
- };
660
+ timestamps.push(timestamp);
611
661
 
612
- const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
613
- if (!allowedFormats.includes(msg_type)) {
614
- console.error(`Format '${msg_type}' no allowed. Allowed formats are: ${allowedFormats.join(', ')}`);
615
- return;
662
+ // Remove timestamps older than ten seconds
663
+ const tenSecondsAgo = timestamp - 10000;
664
+ while (timestamps[0] < tenSecondsAgo) {
665
+ timestamps.shift();
616
666
  }
617
- const node = new nep.Node("nep-cli-sub");
618
- const conf = node.hybrid(master_ip);
619
-
620
- console.log("Topic:", topic)
621
- console.log("Message type:", msg_type)
667
+ };
622
668
 
623
- const sub = node.new_sub(topic, msg_type, callback, conf);
669
+ setInterval(() => {
670
+ // Calculate statistics for the last 10 seconds
671
+ const now = Date.now();
672
+ const tenSecondsAgo = now - 10000;
673
+ const recentTimestamps = timestamps.filter((ts) => ts > tenSecondsAgo);
674
+ const rate = recentTimestamps.length / 10; // Messages per second for a 10-second window
675
+
676
+ console.log("Average rate:", rate.toFixed(2), "Hz");
677
+ console.log("Min:", (10 / Math.max(rate, 0.1)).toFixed(2), "s");
678
+ console.log("Max:", (10 / Math.min(rate, 10)).toFixed(2), "s");
679
+ console.log("Std dev:", calculateStdDev(recentTimestamps, now).toFixed(2), "s");
680
+ console.log("Window:", recentTimestamps.length);
624
681
  console.log("");
682
+ }, 1000);
625
683
 
626
- setInterval(() => {
627
- // Calculate statistics for the last 10 seconds
628
- const now = Date.now();
629
- const tenSecondsAgo = now - 10000;
630
- const recentTimestamps = timestamps.filter((ts) => ts > tenSecondsAgo);
631
- const rate = recentTimestamps.length / 10; // Messages per second for a 10-second window
632
-
633
- console.log("Average rate:", rate.toFixed(2), "Hz");
634
- console.log("Min:", (10 / Math.max(rate, 0.1)).toFixed(2), "s");
635
- console.log("Max:", (10 / Math.min(rate, 10)).toFixed(2), "s");
636
- console.log("Std dev:", calculateStdDev(recentTimestamps, now).toFixed(2), "s");
637
- console.log("Window:", recentTimestamps.length);
638
- console.log("");
639
- }, 1000);
640
- };
641
684
 
642
685
  const calculateStdDev = (timestamps) => {
643
686
  if (timestamps.length < 2) {
@@ -666,33 +709,330 @@ program
666
709
  return stdDeviation;
667
710
  };
668
711
 
712
+ openSub(master_ip, topic, msg_type, callback);
669
713
 
670
- async function run() {
671
- const requester = new zmq.Request();
672
- requester.connect(`tcp://${master_ip}:${PORT_MASTER_INFO}`);
714
+ });
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";
673
752
 
674
- const msg = { "input": "topics" };
675
- const message = JSON.stringify(msg);
676
- await requester.send(message);
677
- const [result] = await requester.receive();
678
- const results = JSON.parse(result.toString());
753
+ const requester = createRequester(master_ip, PORT_MASTER_INFO);
754
+ const results = await sendRequest(requester, { "input": "topics" });
679
755
 
680
- if (results["state"] === "failure") {
681
- console.log("Topic is not registered, use *nep topics* to see the list of avaliable topics");
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');
682
768
  } else {
683
- console.log("");
684
- if (results["input"].includes(topic)) {
685
- opensub();
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);
686
950
  } else {
687
- console.log("Topic is not registered, use *nep topics* to see the list of avaliable topics");
951
+ process.exit();
688
952
  }
689
953
  }
690
- }
691
954
 
692
- run();
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
+ });
693
982
  });
694
983
 
695
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
+ });
1035
+
696
1036
  program
697
1037
  .command('docs')
698
1038
  .description('Open the NEP+ documentation in the default browser')