meshcore-cli 1.1.41__tar.gz → 1.2.0__tar.gz
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.
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/PKG-INFO +1 -1
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/pyproject.toml +1 -1
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/src/meshcore_cli/meshcore_cli.py +499 -329
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/.gitignore +0 -0
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/LICENSE +0 -0
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/README.md +0 -0
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/flake.lock +0 -0
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/flake.nix +0 -0
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/src/meshcore_cli/__init__.py +0 -0
- {meshcore_cli-1.1.41 → meshcore_cli-1.2.0}/src/meshcore_cli/__main__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Command line interface to meshcore companion radios
|
|
5
5
|
Project-URL: Homepage, https://github.com/fdlamotte/meshcore-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/fdlamotte/meshcore-cli/issues
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/python
|
|
2
|
-
"""
|
|
2
|
+
"""
|
|
3
3
|
mccli.py : CLI interface to MeschCore BLE companion app
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import asyncio
|
|
6
7
|
import os, sys
|
|
7
8
|
import time, datetime
|
|
@@ -16,15 +17,20 @@ import traceback
|
|
|
16
17
|
from prompt_toolkit.shortcuts import PromptSession
|
|
17
18
|
from prompt_toolkit.shortcuts import CompleteStyle
|
|
18
19
|
from prompt_toolkit.completion import NestedCompleter
|
|
20
|
+
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
|
|
19
21
|
from prompt_toolkit.history import FileHistory
|
|
20
22
|
from prompt_toolkit.formatted_text import ANSI
|
|
21
23
|
from prompt_toolkit.key_binding import KeyBindings
|
|
22
24
|
from prompt_toolkit.shortcuts import radiolist_dialog
|
|
25
|
+
from prompt_toolkit.completion.word_completer import WordCompleter
|
|
26
|
+
from prompt_toolkit.document import Document
|
|
27
|
+
|
|
28
|
+
import re
|
|
23
29
|
|
|
24
30
|
from meshcore import MeshCore, EventType, logger
|
|
25
31
|
|
|
26
32
|
# Version
|
|
27
|
-
VERSION = "v1.
|
|
33
|
+
VERSION = "v1.2.0"
|
|
28
34
|
|
|
29
35
|
# default ble address is stored in a config file
|
|
30
36
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
|
@@ -297,6 +303,29 @@ async def subscribe_to_msgs(mc, json_output=False, above=False):
|
|
|
297
303
|
CS = mc.subscribe(EventType.CHANNEL_MSG_RECV, handle_message)
|
|
298
304
|
await mc.start_auto_message_fetching()
|
|
299
305
|
|
|
306
|
+
# redefine get_completion to let user put symbols in first item
|
|
307
|
+
# and handle navigating in path ...
|
|
308
|
+
class MyNestedCompleter(NestedCompleter):
|
|
309
|
+
def get_completions( self, document, complete_event):
|
|
310
|
+
txt = document.text_before_cursor.lstrip()
|
|
311
|
+
if not " " in txt:
|
|
312
|
+
if txt != "" and txt[0] == "/" and txt.count("/") == 1:
|
|
313
|
+
opts = []
|
|
314
|
+
for k in self.options.keys():
|
|
315
|
+
if k[0] == "/" :
|
|
316
|
+
v = "/" + k.split("/")[1] #+ ("/" if k.count("/") == 2 else "")
|
|
317
|
+
if v not in opts:
|
|
318
|
+
opts.append(v)
|
|
319
|
+
else:
|
|
320
|
+
opts = self.options.keys()
|
|
321
|
+
completer = WordCompleter(
|
|
322
|
+
opts, ignore_case=self.ignore_case,
|
|
323
|
+
pattern=re.compile(r"([a-zA-Z0-9_\\/]+|[^a-zA-Z0-9_\s]+)"))
|
|
324
|
+
yield from completer.get_completions(document, complete_event)
|
|
325
|
+
else: # normal behavior for remainder
|
|
326
|
+
yield from super().get_completions(document, complete_event)
|
|
327
|
+
|
|
328
|
+
|
|
300
329
|
def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
301
330
|
contact_list = {}
|
|
302
331
|
pending_list = {}
|
|
@@ -307,7 +336,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
307
336
|
if not process_event_message.last_node is None:
|
|
308
337
|
to_list["!"] = None
|
|
309
338
|
to_list[".."] = None
|
|
310
|
-
to_list["public"] = None
|
|
311
339
|
|
|
312
340
|
it = iter(contacts.items())
|
|
313
341
|
for c in it :
|
|
@@ -333,229 +361,248 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
333
361
|
"chan" : None,
|
|
334
362
|
}
|
|
335
363
|
|
|
364
|
+
root_completion_list = {
|
|
365
|
+
"ver" : None,
|
|
366
|
+
"infos" : None,
|
|
367
|
+
"advert" : None,
|
|
368
|
+
"floodadv" : None,
|
|
369
|
+
"msg" : contact_list,
|
|
370
|
+
"wait_ack" : None,
|
|
371
|
+
"time" : None,
|
|
372
|
+
"clock" : {"sync" : None},
|
|
373
|
+
"reboot" : None,
|
|
374
|
+
"card" : None,
|
|
375
|
+
"upload_card" : None,
|
|
376
|
+
"contacts": None,
|
|
377
|
+
"pending_contacts": None,
|
|
378
|
+
"add_pending": pending_list,
|
|
379
|
+
"flush_pending": None,
|
|
380
|
+
"contact_info": contact_list,
|
|
381
|
+
"export_contact" : contact_list,
|
|
382
|
+
"upload_contact" : contact_list,
|
|
383
|
+
"share_contact" : contact_list,
|
|
384
|
+
"path": contact_list,
|
|
385
|
+
"disc_path" : contact_list,
|
|
386
|
+
"trace" : None,
|
|
387
|
+
"reset_path" : contact_list,
|
|
388
|
+
"change_path" : contact_list,
|
|
389
|
+
"change_flags" : contact_list,
|
|
390
|
+
"remove_contact" : contact_list,
|
|
391
|
+
"import_contact" : {"meshcore://":None},
|
|
392
|
+
"reload_contacts" : None,
|
|
393
|
+
"login" : contact_list,
|
|
394
|
+
"cmd" : contact_list,
|
|
395
|
+
"req_status" : contact_list,
|
|
396
|
+
"req_bstatus" : contact_list,
|
|
397
|
+
"logout" : contact_list,
|
|
398
|
+
"req_telemetry" : contact_list,
|
|
399
|
+
"req_binary" : contact_list,
|
|
400
|
+
"req_mma" : contact_list,
|
|
401
|
+
"self_telemetry" : None,
|
|
402
|
+
"get_channel": None,
|
|
403
|
+
"set_channel": None,
|
|
404
|
+
"get_channels": None,
|
|
405
|
+
"remove_channel": None,
|
|
406
|
+
"set" : {
|
|
407
|
+
"name" : None,
|
|
408
|
+
"pin" : None,
|
|
409
|
+
"radio" : {",,,":None, "f,bw,sf,cr":None},
|
|
410
|
+
"tx" : None,
|
|
411
|
+
"tuning" : {",", "af,tx_d"},
|
|
412
|
+
"lat" : None,
|
|
413
|
+
"lon" : None,
|
|
414
|
+
"coords" : None,
|
|
415
|
+
"print_snr" : {"on":None, "off": None},
|
|
416
|
+
"json_msgs" : {"on":None, "off": None},
|
|
417
|
+
"color" : {"on":None, "off":None},
|
|
418
|
+
"print_name" : {"on":None, "off":None},
|
|
419
|
+
"print_adverts" : {"on":None, "off":None},
|
|
420
|
+
"print_new_contacts" : {"on": None, "off":None},
|
|
421
|
+
"print_path_updates" : {"on":None,"off":None},
|
|
422
|
+
"classic_prompt" : {"on" : None, "off":None},
|
|
423
|
+
"manual_add_contacts" : {"on" : None, "off":None},
|
|
424
|
+
"telemetry_mode_base" : {"always" : None, "device":None, "never":None},
|
|
425
|
+
"telemetry_mode_loc" : {"always" : None, "device":None, "never":None},
|
|
426
|
+
"telemetry_mode_env" : {"always" : None, "device":None, "never":None},
|
|
427
|
+
"advert_loc_policy" : {"none" : None, "share" : None},
|
|
428
|
+
"auto_update_contacts" : {"on":None, "off":None},
|
|
429
|
+
"multi_acks" : {"on": None, "off":None},
|
|
430
|
+
"max_attempts" : None,
|
|
431
|
+
"max_flood_attempts" : None,
|
|
432
|
+
"flood_after" : None,
|
|
433
|
+
},
|
|
434
|
+
"get" : {"name":None,
|
|
435
|
+
"bat":None,
|
|
436
|
+
"fstats": None,
|
|
437
|
+
"radio":None,
|
|
438
|
+
"tx":None,
|
|
439
|
+
"coords":None,
|
|
440
|
+
"lat":None,
|
|
441
|
+
"lon":None,
|
|
442
|
+
"print_snr":None,
|
|
443
|
+
"json_msgs":None,
|
|
444
|
+
"color":None,
|
|
445
|
+
"print_name":None,
|
|
446
|
+
"print_adverts":None,
|
|
447
|
+
"print_path_updates":None,
|
|
448
|
+
"print_new_contacts":None,
|
|
449
|
+
"classic_prompt":None,
|
|
450
|
+
"manual_add_contacts":None,
|
|
451
|
+
"telemetry_mode_base":None,
|
|
452
|
+
"telemetry_mode_loc":None,
|
|
453
|
+
"telemetry_mode_env":None,
|
|
454
|
+
"advert_loc_policy":None,
|
|
455
|
+
"auto_update_contacts":None,
|
|
456
|
+
"multi_acks":None,
|
|
457
|
+
"max_attempts":None,
|
|
458
|
+
"max_flood_attempts":None,
|
|
459
|
+
"flood_after":None,
|
|
460
|
+
"custom":None,
|
|
461
|
+
},
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
contact_completion_list = {
|
|
465
|
+
"contact_info": None,
|
|
466
|
+
"export_contact" : None,
|
|
467
|
+
"share_contact" : None,
|
|
468
|
+
"upload_contact" : None,
|
|
469
|
+
"path": None,
|
|
470
|
+
"disc_path": None,
|
|
471
|
+
"trace": None,
|
|
472
|
+
"dtrace": None,
|
|
473
|
+
"reset_path" : None,
|
|
474
|
+
"change_path" : None,
|
|
475
|
+
"change_flags" : None,
|
|
476
|
+
"req_telemetry" : None,
|
|
477
|
+
"req_binary" : None,
|
|
478
|
+
"forget_password" : None,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
client_completion_list = dict(contact_completion_list)
|
|
482
|
+
client_completion_list.update({
|
|
483
|
+
"get" : { "timeout":None, },
|
|
484
|
+
"set" : { "timeout":None, },
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
repeater_completion_list = dict(contact_completion_list)
|
|
488
|
+
repeater_completion_list.update({
|
|
489
|
+
"login" : None,
|
|
490
|
+
"logout" : None,
|
|
491
|
+
"req_status" : None,
|
|
492
|
+
"req_bstatus" : None,
|
|
493
|
+
"cmd" : None,
|
|
494
|
+
"ver" : None,
|
|
495
|
+
"advert" : None,
|
|
496
|
+
"time" : None,
|
|
497
|
+
"clock" : {"sync" : None},
|
|
498
|
+
"reboot" : None,
|
|
499
|
+
"start ota" : None,
|
|
500
|
+
"password" : None,
|
|
501
|
+
"neighbors" : None,
|
|
502
|
+
"req_acl":None,
|
|
503
|
+
"setperm":contact_list,
|
|
504
|
+
"gps" : {"on":None,"off":None,"sync":None,"setloc":None,
|
|
505
|
+
"advert" : {"none": None, "share": None, "prefs": None},
|
|
506
|
+
},
|
|
507
|
+
"sensor": {"list": None, "set": {"gps": None}, "get": {"gps": None}},
|
|
508
|
+
"get" : {"name" : None,
|
|
509
|
+
"role":None,
|
|
510
|
+
"radio" : None,
|
|
511
|
+
"freq":None,
|
|
512
|
+
"tx":None,
|
|
513
|
+
"af" : None,
|
|
514
|
+
"repeat" : None,
|
|
515
|
+
"allow.read.only" : None,
|
|
516
|
+
"flood.advert.interval" : None,
|
|
517
|
+
"flood.max":None,
|
|
518
|
+
"advert.interval" : None,
|
|
519
|
+
"guest.password" : None,
|
|
520
|
+
"rxdelay": None,
|
|
521
|
+
"txdelay": None,
|
|
522
|
+
"direct.tx_delay":None,
|
|
523
|
+
"public.key":None,
|
|
524
|
+
"lat" : None,
|
|
525
|
+
"lon" : None,
|
|
526
|
+
"telemetry" : None,
|
|
527
|
+
"status" : None,
|
|
528
|
+
"timeout" : None,
|
|
529
|
+
"acl":None,
|
|
530
|
+
"bridge.enabled":None,
|
|
531
|
+
"bridge.delay":None,
|
|
532
|
+
"bridge.source":None,
|
|
533
|
+
"bridge.baud":None,
|
|
534
|
+
"bridge.secret":None,
|
|
535
|
+
"bridge.type":None,
|
|
536
|
+
},
|
|
537
|
+
"set" : {"name" : None,
|
|
538
|
+
"radio" : {",,,":None, "f,bw,sf,cr": None},
|
|
539
|
+
"freq" : None,
|
|
540
|
+
"tx" : None,
|
|
541
|
+
"af": None,
|
|
542
|
+
"repeat" : {"on": None, "off": None},
|
|
543
|
+
"flood.advert.interval" : None,
|
|
544
|
+
"flood.max" : None,
|
|
545
|
+
"advert.interval" : None,
|
|
546
|
+
"guest.password" : None,
|
|
547
|
+
"allow.read.only" : {"on": None, "off": None},
|
|
548
|
+
"rxdelay" : None,
|
|
549
|
+
"txdelay": None,
|
|
550
|
+
"direct.txdelay" : None,
|
|
551
|
+
"lat" : None,
|
|
552
|
+
"lon" : None,
|
|
553
|
+
"timeout" : None,
|
|
554
|
+
"perm":contact_list,
|
|
555
|
+
"bridge.enabled":{"on": None, "off": None},
|
|
556
|
+
"bridge.delay":None,
|
|
557
|
+
"bridge.source":None,
|
|
558
|
+
"bridge.baud":None,
|
|
559
|
+
"bridge.secret":None,
|
|
560
|
+
},
|
|
561
|
+
"erase": None,
|
|
562
|
+
"log" : {"start" : None, "stop" : None, "erase" : None}
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
sensor_completion_list = dict(repeater_completion_list)
|
|
566
|
+
sensor_completion_list.update({"req_mma":{"begin end":None}})
|
|
567
|
+
sensor_completion_list["get"].update({ "mma":None, })
|
|
568
|
+
|
|
336
569
|
if to is None :
|
|
337
|
-
completion_list.update(
|
|
338
|
-
"ver" : None,
|
|
339
|
-
"infos" : None,
|
|
340
|
-
"advert" : None,
|
|
341
|
-
"floodadv" : None,
|
|
342
|
-
"msg" : contact_list,
|
|
343
|
-
"wait_ack" : None,
|
|
344
|
-
"time" : None,
|
|
345
|
-
"clock" : {"sync" : None},
|
|
346
|
-
"reboot" : None,
|
|
347
|
-
"card" : None,
|
|
348
|
-
"upload_card" : None,
|
|
349
|
-
"contacts": None,
|
|
350
|
-
"pending_contacts": None,
|
|
351
|
-
"add_pending": pending_list,
|
|
352
|
-
"flush_pending": None,
|
|
353
|
-
"contact_info": contact_list,
|
|
354
|
-
"export_contact" : contact_list,
|
|
355
|
-
"upload_contact" : contact_list,
|
|
356
|
-
"share_contact" : contact_list,
|
|
357
|
-
"path": contact_list,
|
|
358
|
-
"disc_path" : contact_list,
|
|
359
|
-
"trace" : None,
|
|
360
|
-
"reset_path" : contact_list,
|
|
361
|
-
"change_path" : contact_list,
|
|
362
|
-
"change_flags" : contact_list,
|
|
363
|
-
"remove_contact" : contact_list,
|
|
364
|
-
"import_contact" : {"meshcore://":None},
|
|
365
|
-
"reload_contacts" : None,
|
|
366
|
-
"login" : contact_list,
|
|
367
|
-
"cmd" : contact_list,
|
|
368
|
-
"req_status" : contact_list,
|
|
369
|
-
"req_bstatus" : contact_list,
|
|
370
|
-
"logout" : contact_list,
|
|
371
|
-
"req_telemetry" : contact_list,
|
|
372
|
-
"req_binary" : contact_list,
|
|
373
|
-
"req_mma" : contact_list,
|
|
374
|
-
"self_telemetry" : None,
|
|
375
|
-
"get_channel": None,
|
|
376
|
-
"set_channel": None,
|
|
377
|
-
"get_channels": None,
|
|
378
|
-
"remove_channel": None,
|
|
379
|
-
"set" : {
|
|
380
|
-
"name" : None,
|
|
381
|
-
"pin" : None,
|
|
382
|
-
"radio" : {",,,":None, "f,bw,sf,cr":None},
|
|
383
|
-
"tx" : None,
|
|
384
|
-
"tuning" : {",", "af,tx_d"},
|
|
385
|
-
"lat" : None,
|
|
386
|
-
"lon" : None,
|
|
387
|
-
"coords" : None,
|
|
388
|
-
"print_snr" : {"on":None, "off": None},
|
|
389
|
-
"json_msgs" : {"on":None, "off": None},
|
|
390
|
-
"color" : {"on":None, "off":None},
|
|
391
|
-
"print_name" : {"on":None, "off":None},
|
|
392
|
-
"print_adverts" : {"on":None, "off":None},
|
|
393
|
-
"print_new_contacts" : {"on": None, "off":None},
|
|
394
|
-
"print_path_updates" : {"on":None,"off":None},
|
|
395
|
-
"classic_prompt" : {"on" : None, "off":None},
|
|
396
|
-
"manual_add_contacts" : {"on" : None, "off":None},
|
|
397
|
-
"telemetry_mode_base" : {"always" : None, "device":None, "never":None},
|
|
398
|
-
"telemetry_mode_loc" : {"always" : None, "device":None, "never":None},
|
|
399
|
-
"telemetry_mode_env" : {"always" : None, "device":None, "never":None},
|
|
400
|
-
"advert_loc_policy" : {"none" : None, "share" : None},
|
|
401
|
-
"auto_update_contacts" : {"on":None, "off":None},
|
|
402
|
-
"multi_acks" : {"on": None, "off":None},
|
|
403
|
-
"max_attempts" : None,
|
|
404
|
-
"max_flood_attempts" : None,
|
|
405
|
-
"flood_after" : None,
|
|
406
|
-
},
|
|
407
|
-
"get" : {"name":None,
|
|
408
|
-
"bat":None,
|
|
409
|
-
"fstats": None,
|
|
410
|
-
"radio":None,
|
|
411
|
-
"tx":None,
|
|
412
|
-
"coords":None,
|
|
413
|
-
"lat":None,
|
|
414
|
-
"lon":None,
|
|
415
|
-
"print_snr":None,
|
|
416
|
-
"json_msgs":None,
|
|
417
|
-
"color":None,
|
|
418
|
-
"print_name":None,
|
|
419
|
-
"print_adverts":None,
|
|
420
|
-
"print_path_updates":None,
|
|
421
|
-
"print_new_contacts":None,
|
|
422
|
-
"classic_prompt":None,
|
|
423
|
-
"manual_add_contacts":None,
|
|
424
|
-
"telemetry_mode_base":None,
|
|
425
|
-
"telemetry_mode_loc":None,
|
|
426
|
-
"telemetry_mode_env":None,
|
|
427
|
-
"advert_loc_policy":None,
|
|
428
|
-
"auto_update_contacts":None,
|
|
429
|
-
"multi_acks":None,
|
|
430
|
-
"max_attempts":None,
|
|
431
|
-
"max_flood_attempts":None,
|
|
432
|
-
"flood_after":None,
|
|
433
|
-
"custom":None,
|
|
434
|
-
},
|
|
435
|
-
})
|
|
570
|
+
completion_list.update(dict(root_completion_list))
|
|
436
571
|
completion_list["set"].update(make_completion_dict.custom_vars)
|
|
437
572
|
completion_list["get"].update(make_completion_dict.custom_vars)
|
|
438
573
|
else :
|
|
439
574
|
completion_list.update({
|
|
440
575
|
"send" : None,
|
|
441
576
|
})
|
|
577
|
+
if to['type'] == 1 :
|
|
578
|
+
completion_list.update(client_completion_list)
|
|
579
|
+
if to['type'] == 2 or to['type'] == 3 : # repeaters and room servers
|
|
580
|
+
completion_list.update(repeater_completion_list)
|
|
581
|
+
if (to['type'] == 4) : #specific to sensors
|
|
582
|
+
completion_list.update(sensor_completion_list)
|
|
442
583
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
"upload_contact" : None,
|
|
449
|
-
"path": None,
|
|
450
|
-
"disc_path": None,
|
|
451
|
-
"trace": None,
|
|
452
|
-
"dtrace": None,
|
|
453
|
-
"reset_path" : None,
|
|
454
|
-
"change_path" : None,
|
|
455
|
-
"change_flags" : None,
|
|
456
|
-
"req_telemetry" : None,
|
|
457
|
-
"req_binary" : None,
|
|
458
|
-
})
|
|
584
|
+
slash_root_completion_list = {}
|
|
585
|
+
for k,v in root_completion_list.items():
|
|
586
|
+
slash_root_completion_list["/"+k]=v
|
|
587
|
+
|
|
588
|
+
completion_list.update(slash_root_completion_list)
|
|
459
589
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if to['type'] > 1 : # repeaters and room servers
|
|
471
|
-
completion_list.update({
|
|
472
|
-
"login" : None,
|
|
473
|
-
"logout" : None,
|
|
474
|
-
"req_status" : None,
|
|
475
|
-
"req_bstatus" : None,
|
|
476
|
-
"cmd" : None,
|
|
477
|
-
"ver" : None,
|
|
478
|
-
"advert" : None,
|
|
479
|
-
"time" : None,
|
|
480
|
-
"clock" : {"sync" : None},
|
|
481
|
-
"reboot" : None,
|
|
482
|
-
"start ota" : None,
|
|
483
|
-
"password" : None,
|
|
484
|
-
"neighbors" : None,
|
|
485
|
-
"req_acl":None,
|
|
486
|
-
"setperm":contact_list,
|
|
487
|
-
"gps" : {"on":None,"off":None,"sync":None,"setloc":None,
|
|
488
|
-
"advert" : {"none": None, "share": None, "prefs": None},
|
|
489
|
-
},
|
|
490
|
-
"sensor": {"list": None, "set": {"gps": None}, "get": {"gps": None}},
|
|
491
|
-
"get" : {"name" : None,
|
|
492
|
-
"role":None,
|
|
493
|
-
"radio" : None,
|
|
494
|
-
"freq":None,
|
|
495
|
-
"tx":None,
|
|
496
|
-
"af" : None,
|
|
497
|
-
"repeat" : None,
|
|
498
|
-
"allow.read.only" : None,
|
|
499
|
-
"flood.advert.interval" : None,
|
|
500
|
-
"flood.max":None,
|
|
501
|
-
"advert.interval" : None,
|
|
502
|
-
"guest.password" : None,
|
|
503
|
-
"rxdelay": None,
|
|
504
|
-
"txdelay": None,
|
|
505
|
-
"direct.tx_delay":None,
|
|
506
|
-
"public.key":None,
|
|
507
|
-
"lat" : None,
|
|
508
|
-
"lon" : None,
|
|
509
|
-
"telemetry" : None,
|
|
510
|
-
"status" : None,
|
|
511
|
-
"timeout" : None,
|
|
512
|
-
"acl":None,
|
|
513
|
-
"bridge.enabled":None,
|
|
514
|
-
"bridge.delay":None,
|
|
515
|
-
"bridge.source":None,
|
|
516
|
-
"bridge.baud":None,
|
|
517
|
-
"bridge.secret":None,
|
|
518
|
-
"bridge.type":None,
|
|
519
|
-
},
|
|
520
|
-
"set" : {"name" : None,
|
|
521
|
-
"radio" : {",,,":None, "f,bw,sf,cr": None},
|
|
522
|
-
"freq" : None,
|
|
523
|
-
"tx" : None,
|
|
524
|
-
"af": None,
|
|
525
|
-
"repeat" : {"on": None, "off": None},
|
|
526
|
-
"flood.advert.interval" : None,
|
|
527
|
-
"flood.max" : None,
|
|
528
|
-
"advert.interval" : None,
|
|
529
|
-
"guest.password" : None,
|
|
530
|
-
"allow.read.only" : {"on": None, "off": None},
|
|
531
|
-
"rxdelay" : None,
|
|
532
|
-
"txdelay": None,
|
|
533
|
-
"direct.txdelay" : None,
|
|
534
|
-
"lat" : None,
|
|
535
|
-
"lon" : None,
|
|
536
|
-
"timeout" : None,
|
|
537
|
-
"perm":contact_list,
|
|
538
|
-
"bridge.enabled":{"on": None, "off": None},
|
|
539
|
-
"bridge.delay":None,
|
|
540
|
-
"bridge.source":None,
|
|
541
|
-
"bridge.baud":None,
|
|
542
|
-
"bridge.secret":None,
|
|
543
|
-
},
|
|
544
|
-
"erase": None,
|
|
545
|
-
"log" : {"start" : None, "stop" : None, "erase" : None}
|
|
546
|
-
})
|
|
590
|
+
slash_contacts_completion_list = {}
|
|
591
|
+
for k,v in contacts.items():
|
|
592
|
+
d={}
|
|
593
|
+
if v["type"] == 1:
|
|
594
|
+
l = client_completion_list
|
|
595
|
+
elif v["type"] == 2 or v["type"] == 3:
|
|
596
|
+
l = repeater_completion_list
|
|
597
|
+
elif v["type"] == 4:
|
|
598
|
+
l = sensor_completion_list
|
|
547
599
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
"req_mma":{"begin end":None},
|
|
551
|
-
})
|
|
600
|
+
for kk, vv in l.items():
|
|
601
|
+
d["/" + v["adv_name"] + "/" + kk] = vv
|
|
552
602
|
|
|
553
|
-
|
|
554
|
-
"mma":None,
|
|
555
|
-
})
|
|
603
|
+
slash_contacts_completion_list.update(d)
|
|
556
604
|
|
|
557
|
-
|
|
558
|
-
})
|
|
605
|
+
completion_list.update(slash_contacts_completion_list)
|
|
559
606
|
|
|
560
607
|
completion_list.update({
|
|
561
608
|
"script" : None,
|
|
@@ -574,7 +621,6 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
574
621
|
contact = to
|
|
575
622
|
prev_contact = None
|
|
576
623
|
|
|
577
|
-
# await get_contacts(mc, anim=True)
|
|
578
624
|
await get_contacts(mc, anim=True)
|
|
579
625
|
await get_channels(mc, anim=True)
|
|
580
626
|
await subscribe_to_msgs(mc, above=True)
|
|
@@ -666,7 +712,7 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
666
712
|
session.app.ttimeoutlen = 0.2
|
|
667
713
|
session.app.timeoutlen = 0.2
|
|
668
714
|
|
|
669
|
-
completer =
|
|
715
|
+
completer = MyNestedCompleter.from_nested_dict(
|
|
670
716
|
make_completion_dict(mc.contacts,
|
|
671
717
|
mc.pending_contacts,
|
|
672
718
|
to=contact,
|
|
@@ -685,6 +731,24 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
685
731
|
args = shlex.split(line[1:])
|
|
686
732
|
await process_cmds(mc, args)
|
|
687
733
|
|
|
734
|
+
elif line.startswith("/") :
|
|
735
|
+
path = line.split(" ", 1)[0]
|
|
736
|
+
if path.count("/") == 1:
|
|
737
|
+
args = shlex.split(line[1:])
|
|
738
|
+
await process_cmds(mc, args)
|
|
739
|
+
else:
|
|
740
|
+
cmdline = line[1:].split("/",1)[1]
|
|
741
|
+
contact_name = path[1:].split("/",1)[0]
|
|
742
|
+
tct = mc.get_contact_by_name(contact_name)
|
|
743
|
+
if tct is None:
|
|
744
|
+
print(f"{contact_name} is not a contact")
|
|
745
|
+
else:
|
|
746
|
+
if not await process_contact_chat_line(mc, tct, cmdline):
|
|
747
|
+
if tct["type"] == 1:
|
|
748
|
+
last_ack = await msg_ack(mc, tct, cmdline)
|
|
749
|
+
else :
|
|
750
|
+
await process_cmds(mc, ["cmd", tct["adv_name"], cmdline])
|
|
751
|
+
|
|
688
752
|
elif line.startswith("to ") : # dest
|
|
689
753
|
dest = line[3:]
|
|
690
754
|
if dest.startswith("\"") or dest.startswith("\'") : # if name starts with a quote
|
|
@@ -707,7 +771,7 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
707
771
|
elif dest == "!" :
|
|
708
772
|
nc = process_event_message.last_node
|
|
709
773
|
else :
|
|
710
|
-
chan = await get_channel_by_name(mc, dest)
|
|
774
|
+
chan = await get_channel_by_name(mc, dest)
|
|
711
775
|
if chan is None :
|
|
712
776
|
print(f"Contact '{dest}' not found in contacts.")
|
|
713
777
|
nc = contact
|
|
@@ -752,104 +816,8 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
752
816
|
args = shlex.split(line)
|
|
753
817
|
await process_cmds(mc, args)
|
|
754
818
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
line == "ec" or line == "export_contact" or\
|
|
758
|
-
line == "uc" or line == "upload_contact" or\
|
|
759
|
-
line == "rp" or line == "reset_path" or\
|
|
760
|
-
line == "dp" or line == "disc_path" or\
|
|
761
|
-
line == "contact_info" or line == "ci" or\
|
|
762
|
-
line == "req_status" or line == "rs" or\
|
|
763
|
-
line == "req_bstatus" or line == "rbs" or\
|
|
764
|
-
line == "req_telemetry" or line == "rt" or\
|
|
765
|
-
line == "req_acl" or\
|
|
766
|
-
line == "path" or\
|
|
767
|
-
line == "logout" ) :
|
|
768
|
-
args = [line, contact['adv_name']]
|
|
769
|
-
await process_cmds(mc, args)
|
|
770
|
-
|
|
771
|
-
elif contact["type"] > 0 and line.startswith("set timeout "):
|
|
772
|
-
cmds=line.split(" ")
|
|
773
|
-
contact["timeout"] = float(cmds[2])
|
|
774
|
-
|
|
775
|
-
elif contact["type"] > 0 and line == "get timeout":
|
|
776
|
-
print(f"timeout: {0 if not 'timeout' in contact else contact['timeout']}")
|
|
777
|
-
|
|
778
|
-
elif contact["type"] == 4 and\
|
|
779
|
-
(line.startswith("get mma ")) or\
|
|
780
|
-
contact["type"] > 1 and\
|
|
781
|
-
(line.startswith("get telemetry") or line.startswith("get status") or line.startswith("get acl")):
|
|
782
|
-
cmds = line.split(" ")
|
|
783
|
-
args = [f"req_{cmds[1]}", contact['adv_name']]
|
|
784
|
-
if len(cmds) > 2 :
|
|
785
|
-
args = args + cmds[2:]
|
|
786
|
-
if line.startswith("get mma ") and len(args) < 4:
|
|
787
|
-
args.append("0")
|
|
788
|
-
await process_cmds(mc, args)
|
|
789
|
-
|
|
790
|
-
# special treatment for setperm to support contact name as param
|
|
791
|
-
elif contact["type"] > 1 and\
|
|
792
|
-
(line.startswith("setperm ") or line.startswith("set perm ")):
|
|
793
|
-
try:
|
|
794
|
-
cmds = shlex.split(line)
|
|
795
|
-
off = 1 if line.startswith("set perm") else 0
|
|
796
|
-
name = cmds[1 + off]
|
|
797
|
-
perm_string = cmds[2 + off]
|
|
798
|
-
if (perm_string.startswith("0x")):
|
|
799
|
-
perm = int(perm_string,0)
|
|
800
|
-
elif (perm_string.startswith("#")):
|
|
801
|
-
perm = int(perm_string[1:])
|
|
802
|
-
else:
|
|
803
|
-
perm = int(perm_string,16)
|
|
804
|
-
ct=mc.get_contact_by_name(name)
|
|
805
|
-
if ct is None:
|
|
806
|
-
ct=mc.get_contact_by_key_prefix(name)
|
|
807
|
-
if ct is None:
|
|
808
|
-
if name == "self" or mc.self_info["public_key"].startswith(name):
|
|
809
|
-
key = mc.self_info["public_key"]
|
|
810
|
-
else:
|
|
811
|
-
key = name
|
|
812
|
-
else:
|
|
813
|
-
key=ct["public_key"]
|
|
814
|
-
newline=f"setperm {key} {perm}"
|
|
815
|
-
await process_cmds(mc, ["cmd", contact["adv_name"], newline])
|
|
816
|
-
except IndexError:
|
|
817
|
-
print("Wrong number of parameters")
|
|
818
|
-
|
|
819
|
-
# trace called on a contact
|
|
820
|
-
elif contact["type"] > 0 and (
|
|
821
|
-
line == "trace" or line == "tr") :
|
|
822
|
-
await print_trace_to(mc, contact)
|
|
823
|
-
|
|
824
|
-
elif contact["type"] > 0 and (
|
|
825
|
-
line == "dtrace" or line == "dt") :
|
|
826
|
-
await print_disc_trace_to(mc, contact)
|
|
827
|
-
|
|
828
|
-
# same but for commands with a parameter
|
|
829
|
-
elif contact["type"] > 0 and (line.startswith("cmd ") or\
|
|
830
|
-
line.startswith("cp ") or line.startswith("change_path ") or\
|
|
831
|
-
line.startswith("cf ") or line.startswith("change_flags ") or\
|
|
832
|
-
line.startswith("req_binary ") or\
|
|
833
|
-
line.startswith("login ")) :
|
|
834
|
-
cmds = line.split(" ", 1)
|
|
835
|
-
args = [cmds[0], contact['adv_name'], cmds[1]]
|
|
836
|
-
await process_cmds(mc, args)
|
|
837
|
-
|
|
838
|
-
elif contact["type"] == 4 and \
|
|
839
|
-
(line.startswith("req_mma ") or line.startswith('rm ')) :
|
|
840
|
-
cmds = line.split(" ")
|
|
841
|
-
if len(cmds) < 3 :
|
|
842
|
-
cmds.append("0")
|
|
843
|
-
args = [cmds[0], contact['adv_name'], cmds[1], cmds[2]]
|
|
844
|
-
await process_cmds(mc, args)
|
|
845
|
-
|
|
846
|
-
elif line.startswith(":") : # : will send a command to current recipient
|
|
847
|
-
args=["cmd", contact['adv_name'], line[1:]]
|
|
848
|
-
await process_cmds(mc, args)
|
|
849
|
-
|
|
850
|
-
elif line == "reset path" : # reset path for compat with terminal chat
|
|
851
|
-
args = ["reset_path", contact['adv_name']]
|
|
852
|
-
await process_cmds(mc, args)
|
|
819
|
+
elif await process_contact_chat_line(mc, contact, line):
|
|
820
|
+
pass
|
|
853
821
|
|
|
854
822
|
elif line == "list" : # list command from chat displays contacts on a line
|
|
855
823
|
it = iter(mc.contacts.items())
|
|
@@ -887,6 +855,165 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
887
855
|
interactive_loop.classic = False
|
|
888
856
|
interactive_loop.print_name = True
|
|
889
857
|
|
|
858
|
+
async def process_contact_chat_line(mc, contact, line):
|
|
859
|
+
if contact["type"] == 0:
|
|
860
|
+
return False
|
|
861
|
+
|
|
862
|
+
if line.startswith(":") : # : will send a command to current recipient
|
|
863
|
+
args=["cmd", contact['adv_name'], line[1:]]
|
|
864
|
+
await process_cmds(mc, args)
|
|
865
|
+
return True
|
|
866
|
+
|
|
867
|
+
if line == "reset path" : # reset path for compat with terminal chat
|
|
868
|
+
args = ["reset_path", contact['adv_name']]
|
|
869
|
+
await process_cmds(mc, args)
|
|
870
|
+
return True
|
|
871
|
+
|
|
872
|
+
# commands that take contact as second arg will be sent to recipient
|
|
873
|
+
if line == "sc" or line == "share_contact" or\
|
|
874
|
+
line == "ec" or line == "export_contact" or\
|
|
875
|
+
line == "uc" or line == "upload_contact" or\
|
|
876
|
+
line == "rp" or line == "reset_path" or\
|
|
877
|
+
line == "dp" or line == "disc_path" or\
|
|
878
|
+
line == "contact_info" or line == "ci" or\
|
|
879
|
+
line == "req_status" or line == "rs" or\
|
|
880
|
+
line == "req_bstatus" or line == "rbs" or\
|
|
881
|
+
line == "req_telemetry" or line == "rt" or\
|
|
882
|
+
line == "req_acl" or\
|
|
883
|
+
line == "path" or\
|
|
884
|
+
line == "logout" :
|
|
885
|
+
args = [line, contact['adv_name']]
|
|
886
|
+
await process_cmds(mc, args)
|
|
887
|
+
return True
|
|
888
|
+
|
|
889
|
+
# special case for rp that can be chained from cmdline
|
|
890
|
+
if line.startswith("rp ") or line.startswith("reset_path ") :
|
|
891
|
+
args = ["rp", contact['adv_name']]
|
|
892
|
+
await process_cmds(mc, args)
|
|
893
|
+
secline = line.split(" ", 1)[1]
|
|
894
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
895
|
+
return True
|
|
896
|
+
|
|
897
|
+
if line.startswith("set timeout "):
|
|
898
|
+
cmds=line.split(" ")
|
|
899
|
+
contact["timeout"] = float(cmds[2])
|
|
900
|
+
return True
|
|
901
|
+
|
|
902
|
+
if line == "get timeout":
|
|
903
|
+
print(f"timeout: {0 if not 'timeout' in contact else contact['timeout']}")
|
|
904
|
+
return True
|
|
905
|
+
|
|
906
|
+
if contact["type"] == 4 and\
|
|
907
|
+
(line.startswith("get mma ")) or\
|
|
908
|
+
contact["type"] > 1 and\
|
|
909
|
+
(line.startswith("get telemetry") or line.startswith("get status") or line.startswith("get acl")):
|
|
910
|
+
cmds = line.split(" ")
|
|
911
|
+
args = [f"req_{cmds[1]}", contact['adv_name']]
|
|
912
|
+
if len(cmds) > 2 :
|
|
913
|
+
args = args + cmds[2:]
|
|
914
|
+
if line.startswith("get mma ") and len(args) < 4:
|
|
915
|
+
args.append("0")
|
|
916
|
+
await process_cmds(mc, args)
|
|
917
|
+
return True
|
|
918
|
+
|
|
919
|
+
# special treatment for setperm to support contact name as param
|
|
920
|
+
if contact["type"] > 1 and\
|
|
921
|
+
(line.startswith("setperm ") or line.startswith("set perm ")):
|
|
922
|
+
try:
|
|
923
|
+
cmds = shlex.split(line)
|
|
924
|
+
off = 1 if line.startswith("set perm") else 0
|
|
925
|
+
name = cmds[1 + off]
|
|
926
|
+
perm_string = cmds[2 + off]
|
|
927
|
+
if (perm_string.startswith("0x")):
|
|
928
|
+
perm = int(perm_string,0)
|
|
929
|
+
elif (perm_string.startswith("#")):
|
|
930
|
+
perm = int(perm_string[1:])
|
|
931
|
+
else:
|
|
932
|
+
perm = int(perm_string,16)
|
|
933
|
+
ct=mc.get_contact_by_name(name)
|
|
934
|
+
if ct is None:
|
|
935
|
+
ct=mc.get_contact_by_key_prefix(name)
|
|
936
|
+
if ct is None:
|
|
937
|
+
if name == "self" or mc.self_info["public_key"].startswith(name):
|
|
938
|
+
key = mc.self_info["public_key"]
|
|
939
|
+
else:
|
|
940
|
+
key = name
|
|
941
|
+
else:
|
|
942
|
+
key=ct["public_key"]
|
|
943
|
+
newline=f"setperm {key} {perm}"
|
|
944
|
+
await process_cmds(mc, ["cmd", contact["adv_name"], newline])
|
|
945
|
+
except IndexError:
|
|
946
|
+
print("Wrong number of parameters")
|
|
947
|
+
return True
|
|
948
|
+
|
|
949
|
+
# trace called on a contact
|
|
950
|
+
if line == "trace" or line == "tr" :
|
|
951
|
+
await print_trace_to(mc, contact)
|
|
952
|
+
return True
|
|
953
|
+
|
|
954
|
+
if line == "dtrace" or line == "dt" :
|
|
955
|
+
await print_disc_trace_to(mc, contact)
|
|
956
|
+
return True
|
|
957
|
+
|
|
958
|
+
# same but for commands with a parameter
|
|
959
|
+
if line.startswith("cmd ") or\
|
|
960
|
+
line.startswith("cp ") or line.startswith("change_path ") or\
|
|
961
|
+
line.startswith("cf ") or line.startswith("change_flags ") or\
|
|
962
|
+
line.startswith("req_binary ") or\
|
|
963
|
+
line.startswith("login ") :
|
|
964
|
+
cmds = line.split(" ", 1)
|
|
965
|
+
args = [cmds[0], contact['adv_name'], cmds[1]]
|
|
966
|
+
await process_cmds(mc, args)
|
|
967
|
+
return True
|
|
968
|
+
|
|
969
|
+
if line == "login": # use stored password or prompt for it
|
|
970
|
+
password_file = ""
|
|
971
|
+
password = ""
|
|
972
|
+
if os.path.isdir(MCCLI_CONFIG_DIR) :
|
|
973
|
+
password_file = MCCLI_CONFIG_DIR + contact['adv_name'] + ".pass"
|
|
974
|
+
if os.path.exists(password_file) :
|
|
975
|
+
with open(password_file, "r", encoding="utf-8") as f :
|
|
976
|
+
password=f.readline().strip()
|
|
977
|
+
|
|
978
|
+
if password == "":
|
|
979
|
+
try:
|
|
980
|
+
sess = PromptSession("Password: ", is_password=True)
|
|
981
|
+
password = await sess.prompt_async()
|
|
982
|
+
except EOFError:
|
|
983
|
+
logger.info("Canceled")
|
|
984
|
+
return True
|
|
985
|
+
|
|
986
|
+
if password_file != "":
|
|
987
|
+
with open(password_file, "w", encoding="utf-8") as f :
|
|
988
|
+
f.write(password)
|
|
989
|
+
|
|
990
|
+
args = ["login", contact['adv_name'], password]
|
|
991
|
+
await process_cmds(mc, args)
|
|
992
|
+
return True
|
|
993
|
+
|
|
994
|
+
if line.startswith("forget_password") or line.startswith("fp"):
|
|
995
|
+
password_file = MCCLI_CONFIG_DIR + contact['adv_name'] + ".pass"
|
|
996
|
+
if os.path.exists(password_file):
|
|
997
|
+
os.remove(password_file)
|
|
998
|
+
try:
|
|
999
|
+
secline = line.split(" ", 1)[1]
|
|
1000
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1001
|
+
except IndexError:
|
|
1002
|
+
pass
|
|
1003
|
+
return True
|
|
1004
|
+
|
|
1005
|
+
if contact["type"] == 4 and \
|
|
1006
|
+
(line.startswith("req_mma ") or line.startswith('rm ')) :
|
|
1007
|
+
cmds = line.split(" ")
|
|
1008
|
+
if len(cmds) < 3 :
|
|
1009
|
+
cmds.append("0")
|
|
1010
|
+
args = [cmds[0], contact['adv_name'], cmds[1], cmds[2]]
|
|
1011
|
+
await process_cmds(mc, args)
|
|
1012
|
+
return True
|
|
1013
|
+
|
|
1014
|
+
return False
|
|
1015
|
+
|
|
1016
|
+
|
|
890
1017
|
async def send_cmd (mc, contact, cmd) :
|
|
891
1018
|
res = await mc.commands.send_cmd(contact, cmd)
|
|
892
1019
|
if not res is None and not res.type == EventType.ERROR:
|
|
@@ -910,7 +1037,7 @@ async def send_chan_msg(mc, nb, msg):
|
|
|
910
1037
|
sent["text"] = msg
|
|
911
1038
|
sent["txt_type"] = 0
|
|
912
1039
|
sent["name"] = mc.self_info['name']
|
|
913
|
-
await log_message(mc, sent)
|
|
1040
|
+
await log_message(mc, sent)
|
|
914
1041
|
return res
|
|
915
1042
|
|
|
916
1043
|
async def send_msg (mc, contact, msg) :
|
|
@@ -1003,6 +1130,9 @@ async def get_channel_by_name (mc, name):
|
|
|
1003
1130
|
return None
|
|
1004
1131
|
|
|
1005
1132
|
async def get_contacts (mc, anim=False, lastomod=0, timeout=5) :
|
|
1133
|
+
if mc._contacts:
|
|
1134
|
+
return
|
|
1135
|
+
|
|
1006
1136
|
if anim:
|
|
1007
1137
|
print("Fetching contacts ", end="", flush=True)
|
|
1008
1138
|
|
|
@@ -1021,7 +1151,7 @@ async def get_contacts (mc, anim=False, lastomod=0, timeout=5) :
|
|
|
1021
1151
|
done, pending = await asyncio.wait(
|
|
1022
1152
|
futures, timeout=timeout, return_when=asyncio.FIRST_COMPLETED
|
|
1023
1153
|
)
|
|
1024
|
-
|
|
1154
|
+
|
|
1025
1155
|
# Check if any future completed successfully
|
|
1026
1156
|
if len(done) == 0:
|
|
1027
1157
|
logger.debug("Timeout while getting contacts")
|
|
@@ -1041,7 +1171,7 @@ async def get_contacts (mc, anim=False, lastomod=0, timeout=5) :
|
|
|
1041
1171
|
if anim:
|
|
1042
1172
|
if event.type == EventType.CONTACTS:
|
|
1043
1173
|
print ((len(event.payload)-contact_nb)*"." + " Done")
|
|
1044
|
-
else :
|
|
1174
|
+
else :
|
|
1045
1175
|
print(" Error")
|
|
1046
1176
|
for future in pending:
|
|
1047
1177
|
future.cancel()
|
|
@@ -1703,7 +1833,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1703
1833
|
res = await set_channel(mc, cmds[1], cmds[2])
|
|
1704
1834
|
elif len(cmds[3]) != 32:
|
|
1705
1835
|
res = None
|
|
1706
|
-
else:
|
|
1836
|
+
else:
|
|
1707
1837
|
res = await set_channel(mc, cmds[1], cmds[2], bytes.fromhex(cmds[3]))
|
|
1708
1838
|
if res is None:
|
|
1709
1839
|
print("Error setting channel")
|
|
@@ -1723,8 +1853,8 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1723
1853
|
case "msg" | "m" | "{" : # sends to a contact from name
|
|
1724
1854
|
argnum = 2
|
|
1725
1855
|
dest = None
|
|
1726
|
-
|
|
1727
|
-
if len(cmds[1]) == 12: # possibly an hex prefix
|
|
1856
|
+
|
|
1857
|
+
if len(cmds[1]) == 12: # possibly an hex prefix
|
|
1728
1858
|
try:
|
|
1729
1859
|
dest = bytes.fromhex(cmds[1])
|
|
1730
1860
|
except ValueError:
|
|
@@ -1801,11 +1931,18 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1801
1931
|
|
|
1802
1932
|
case "trace" | "tr":
|
|
1803
1933
|
argnum = 1
|
|
1804
|
-
|
|
1934
|
+
path = cmds[1]
|
|
1935
|
+
plen = int(len(path)/2)
|
|
1936
|
+
if plen > 1 and path.count(",") == 0:
|
|
1937
|
+
path = cmds[1][0:2]
|
|
1938
|
+
for i in range(1, plen):
|
|
1939
|
+
path = path + "," + cmds[1][2*i:2*i+2]
|
|
1940
|
+
|
|
1941
|
+
res = await mc.commands.send_trace(path=path)
|
|
1805
1942
|
if res and res.type != EventType.ERROR:
|
|
1806
1943
|
tag= int.from_bytes(res.payload['expected_ack'], byteorder="little")
|
|
1807
1944
|
timeout = res.payload["suggested_timeout"] / 1000 * 1.2
|
|
1808
|
-
ev = await mc.wait_for_event(EventType.TRACE_DATA,
|
|
1945
|
+
ev = await mc.wait_for_event(EventType.TRACE_DATA,
|
|
1809
1946
|
attribute_filters={"tag": tag},
|
|
1810
1947
|
timeout=timeout)
|
|
1811
1948
|
if ev is None:
|
|
@@ -1857,7 +1994,12 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1857
1994
|
else:
|
|
1858
1995
|
print(f"Unknown contact {cmds[1]}")
|
|
1859
1996
|
else:
|
|
1860
|
-
|
|
1997
|
+
password = cmds[2]
|
|
1998
|
+
if password == "$":
|
|
1999
|
+
sess = PromptSession("Password: ", is_password=True)
|
|
2000
|
+
password = await sess.prompt_async()
|
|
2001
|
+
|
|
2002
|
+
res = await mc.commands.send_login(contact, password)
|
|
1861
2003
|
logger.debug(res)
|
|
1862
2004
|
if res.type == EventType.ERROR:
|
|
1863
2005
|
if json_output :
|
|
@@ -1939,7 +2081,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1939
2081
|
print("Timeout waiting telemetry")
|
|
1940
2082
|
else :
|
|
1941
2083
|
print(json.dumps(res.payload, indent=4))
|
|
1942
|
-
|
|
2084
|
+
|
|
1943
2085
|
case "disc_path" | "dp" :
|
|
1944
2086
|
argnum = 1
|
|
1945
2087
|
await mc.ensure_contacts()
|
|
@@ -2439,6 +2581,9 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2439
2581
|
except IndexError:
|
|
2440
2582
|
logger.error("Error in parameters, returning")
|
|
2441
2583
|
return None
|
|
2584
|
+
except EOFError:
|
|
2585
|
+
logger.error("Cancelled")
|
|
2586
|
+
return None
|
|
2442
2587
|
|
|
2443
2588
|
async def process_cmds (mc, args, json_output=False) :
|
|
2444
2589
|
cmds = args
|
|
@@ -2569,7 +2714,7 @@ async def main(argv):
|
|
|
2569
2714
|
with open(MCCLI_ADDRESS, encoding="utf-8") as f :
|
|
2570
2715
|
address = f.readline().strip()
|
|
2571
2716
|
|
|
2572
|
-
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:
|
|
2717
|
+
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:P")
|
|
2573
2718
|
for opt, arg in opts :
|
|
2574
2719
|
match opt:
|
|
2575
2720
|
case "-d" : # name specified on cmdline
|
|
@@ -2599,6 +2744,8 @@ async def main(argv):
|
|
|
2599
2744
|
case "-v":
|
|
2600
2745
|
version()
|
|
2601
2746
|
return
|
|
2747
|
+
case "-f": # connect to first encountered device
|
|
2748
|
+
address = ""
|
|
2602
2749
|
case "-l" :
|
|
2603
2750
|
print("BLE devices:")
|
|
2604
2751
|
try :
|
|
@@ -2609,7 +2756,7 @@ async def main(argv):
|
|
|
2609
2756
|
if not d.name is None and d.name.startswith("MeshCore-"):
|
|
2610
2757
|
print(f" {d.address} {d.name}")
|
|
2611
2758
|
except BleakError:
|
|
2612
|
-
print(" No BLE HW")
|
|
2759
|
+
print(" No BLE HW")
|
|
2613
2760
|
print("\nSerial ports:")
|
|
2614
2761
|
ports = serial.tools.list_ports.comports()
|
|
2615
2762
|
for port, desc, hwid in sorted(ports):
|
|
@@ -2650,7 +2797,7 @@ async def main(argv):
|
|
|
2650
2797
|
else:
|
|
2651
2798
|
logger.error("Invalid choice")
|
|
2652
2799
|
return
|
|
2653
|
-
|
|
2800
|
+
|
|
2654
2801
|
if (debug==True):
|
|
2655
2802
|
logger.setLevel(logging.DEBUG)
|
|
2656
2803
|
elif (json_output) :
|
|
@@ -2668,7 +2815,10 @@ async def main(argv):
|
|
|
2668
2815
|
elif address and len(address) == 36 and len(address.split("-")) == 5:
|
|
2669
2816
|
client = BleakClient(address) # mac uses uuid, we'll pass a client
|
|
2670
2817
|
else:
|
|
2671
|
-
|
|
2818
|
+
if address == "":
|
|
2819
|
+
logger.info(f"Searching first MC BLE device")
|
|
2820
|
+
else:
|
|
2821
|
+
logger.info(f"Scanning BLE for device matching {address}")
|
|
2672
2822
|
devices = await BleakScanner.discover(timeout=timeout)
|
|
2673
2823
|
found = False
|
|
2674
2824
|
for d in devices:
|
|
@@ -2693,6 +2843,26 @@ async def main(argv):
|
|
|
2693
2843
|
mc = await MeshCore.create_ble(address=address, device=device, client=client, debug=debug, only_error=json_output, pin=pin)
|
|
2694
2844
|
except ConnectionError :
|
|
2695
2845
|
logger.info("Error while connecting, retrying once ...")
|
|
2846
|
+
if device is None or client is None: # Search for device
|
|
2847
|
+
logger.info(f"Scanning BLE for device matching {address}")
|
|
2848
|
+
devices = await BleakScanner.discover(timeout=timeout)
|
|
2849
|
+
found = False
|
|
2850
|
+
for d in devices:
|
|
2851
|
+
if not d.name is None and d.name.startswith("MeshCore-") and\
|
|
2852
|
+
(address is None or address in d.name) :
|
|
2853
|
+
address=d.address
|
|
2854
|
+
device=d
|
|
2855
|
+
logger.info(f"Found device {d.name} {d.address}")
|
|
2856
|
+
found = True
|
|
2857
|
+
break
|
|
2858
|
+
elif d.address == address : # on a mac, address is an uuid
|
|
2859
|
+
device = d
|
|
2860
|
+
logger.info(f"Found device {d.name} {d.address}")
|
|
2861
|
+
found = True
|
|
2862
|
+
break
|
|
2863
|
+
if not found :
|
|
2864
|
+
logger.info(f"Couldn't find device {address}")
|
|
2865
|
+
return
|
|
2696
2866
|
try :
|
|
2697
2867
|
mc = await MeshCore.create_ble(address=address, device=device, client=client, debug=debug, only_error=json_output, pin=pin)
|
|
2698
2868
|
except ConnectionError :
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|