nep-cli 0.2.2 → 0.2.3

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