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/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 reply.send(processMsg(node_request, nodes_register, topic_register))
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
- var onNewTopic = function (node_request, topic_register) {
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 = m2mResponse(node_request, topic_register, topic);
137
+ await createBroker(topic);
138
+ msg = ManyToManyResponse(node_request, topic_register, topic);
137
139
  }
138
140
  }
139
- else if (node_request["socket"] === "surveyor" || node_request["socket"] === "respondent") {
140
- msg = surveyResponse(node_request, topic_register, topic)
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
- if ("language" in node_request) {
151
-
152
- if (node_request["language"] == "C++") {
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
- var createBroker = function (topic) {
163
- try {
164
- var broker = new Broker(nep_configuration["IP"], nep_configuration["current_port"] + 1, nep_configuration["current_port"])
165
- //var brokersubex = new BrokerBridgeSUBEX(conf["IP"],IP_EXTERNAL, conf["current_port"] , conf["current_port"])
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
- // Add broker defined to list of brokers
168
- nep_configuration["brokers"][topic] = broker
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
- var m2mResponse = function (node_request, topic_register, topic) {
180
- if ("msg_type" in node_request) {
181
- 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"] }
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
- else {
184
- topic_register[topic] = { "port": nep_configuration["current_port"], "socket": node_request["socket"], 'ip': nep_configuration["IP"], "mode": node_request["mode"], "msg_type": "json" }
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
- var surveyResponse = function (node_request, topic_register, topic) {
190
- topic_register[topic] = { "port": nep_configuration["current_port"], "socket": node_request["socket"], 'ip': nep_configuration["IP"] }
191
- return { 'topic': topic, 'port': nep_configuration["current_port"], 'ip': nep_configuration["IP"], 'socket': node_request["socket"], "state": "success" }
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(`NEP: Node request - node: ${node}, socket: ${socket}, topic: ${topic}`);
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(`--- Node *${node}* already defined ---`);
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(`--- New Node *${node}* ---`);
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(`--- Topic *${topicStr}* already registered ---`);
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(`--- New topic: *${topicStr}* ---`);
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
- if (!topic || !results["input"].includes(topic)) {
344
- const autoComplete = new AutoComplete({
345
- name: 'topic',
346
- message: !topic ? 'Select a topic:' : 'Invalid topic. Select a valid topic:',
347
- choices: results["input"],
348
- });
349
- topic = await autoComplete.run();
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
- const choices = ["0","1","2","3","4","5","6","7","8","9"];
457
+ try {
458
+ const choices = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
356
459
 
357
- const autoComplete = new AutoComplete({
358
- name: 'index',
359
- message: !index ? 'Select a index:' : 'Invalid index. Select a valid index:',
360
- choices: choices,
361
- });
362
- index = await autoComplete.run();
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
- return index;
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
- var msgType = topic.split('/').pop();
369
- if (msgType && msgType !== topic) {
370
- msg_type = msgType;
371
- }
474
+ try {
475
+ var msgType = topic.split('/').pop();
476
+ if (msgType && msgType !== topic) {
477
+ msg_type = msgType;
478
+ }
372
479
 
373
- if (!msg_type || !allowedFormats.includes(msg_type)) {
374
- const autoComplete = new AutoComplete({
375
- name: 'msg_type',
376
- message: !msg_type ? 'Select a message type:' : `Select a valid format:`,
377
- choices: allowedFormats,
378
- });
379
- msg_type = await autoComplete.run();
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('Fetches and displays the list of NEP+ topics from the NEP master')
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
- 'The [index] parameter specifies the index of the message to display and must be a number between 0 and 49. '
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')