meshcore-cli 1.2.12__py3-none-any.whl → 1.3.6__py3-none-any.whl
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/meshcore_cli.py +383 -189
- meshcore_cli-1.3.6.dist-info/METADATA +349 -0
- meshcore_cli-1.3.6.dist-info/RECORD +8 -0
- meshcore_cli-1.2.12.dist-info/METADATA +0 -267
- meshcore_cli-1.2.12.dist-info/RECORD +0 -8
- {meshcore_cli-1.2.12.dist-info → meshcore_cli-1.3.6.dist-info}/WHEEL +0 -0
- {meshcore_cli-1.2.12.dist-info → meshcore_cli-1.3.6.dist-info}/entry_points.txt +0 -0
- {meshcore_cli-1.2.12.dist-info → meshcore_cli-1.3.6.dist-info}/licenses/LICENSE +0 -0
meshcore_cli/meshcore_cli.py
CHANGED
|
@@ -32,7 +32,7 @@ import re
|
|
|
32
32
|
from meshcore import MeshCore, EventType, logger
|
|
33
33
|
|
|
34
34
|
# Version
|
|
35
|
-
VERSION = "v1.
|
|
35
|
+
VERSION = "v1.3.6"
|
|
36
36
|
|
|
37
37
|
# default ble address is stored in a config file
|
|
38
38
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
|
@@ -40,6 +40,10 @@ MCCLI_ADDRESS = MCCLI_CONFIG_DIR + "default_address"
|
|
|
40
40
|
MCCLI_HISTORY_FILE = MCCLI_CONFIG_DIR + "history"
|
|
41
41
|
MCCLI_INIT_SCRIPT = MCCLI_CONFIG_DIR + "init"
|
|
42
42
|
|
|
43
|
+
PAYLOAD_TYPENAMES = ["REQ", "RESPONSE", "TEXT_MSG", "ACK", "ADVERT", "GRP_TXT", "GRP_DATA", "ANON_REQ", "PATH", "TRACE", "MULTIPART", "CONTROL"]
|
|
44
|
+
ROUTE_TYPENAMES = ["TC_FLOOD", "FLOOD", "DIRECT", "TC_DIRECT"]
|
|
45
|
+
CONTACT_TYPENAMES = ["NONE","CLI","REP","ROOM","SENS"]
|
|
46
|
+
|
|
43
47
|
# Fallback address if config file not found
|
|
44
48
|
# if None or "" then a scan is performed
|
|
45
49
|
ADDRESS = ""
|
|
@@ -76,13 +80,11 @@ ANSI_YELLOW = "\033[0;33m"
|
|
|
76
80
|
ANSI_BYELLOW = "\033[1;33m"
|
|
77
81
|
|
|
78
82
|
#Unicode chars
|
|
79
|
-
# some possible symbols for prompts
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
ARROW_TAIL = ""
|
|
85
|
-
ARROW_HEAD = " "
|
|
83
|
+
# some possible symbols for prompts 🭬🬛🬗🭬🬛🬃🬗🭬🬛🬃🬗🬏🭀🭋🭨🮋
|
|
84
|
+
ARROW_HEAD = ""
|
|
85
|
+
SLASH_END = ""
|
|
86
|
+
SLASH_START = ""
|
|
87
|
+
INVERT_SLASH = False
|
|
86
88
|
|
|
87
89
|
def escape_ansi(line):
|
|
88
90
|
ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
|
|
@@ -207,27 +209,57 @@ process_event_message.last_node=None
|
|
|
207
209
|
|
|
208
210
|
async def handle_log_rx(event):
|
|
209
211
|
mc = handle_log_rx.mc
|
|
210
|
-
if handle_log_rx.json_log_rx: # json mode ... raw dump
|
|
211
|
-
msg = json.dumps(event.payload)
|
|
212
|
-
if handle_message.above:
|
|
213
|
-
print_above(msg)
|
|
214
|
-
else :
|
|
215
|
-
print(msg)
|
|
216
|
-
return
|
|
217
212
|
|
|
218
213
|
pkt = bytes().fromhex(event.payload["payload"])
|
|
219
214
|
pbuf = io.BytesIO(pkt)
|
|
220
215
|
header = pbuf.read(1)[0]
|
|
216
|
+
route_type = header & 0x03
|
|
217
|
+
payload_type = (header & 0x3c) >> 2
|
|
218
|
+
payload_ver = (header & 0xc0) >> 6
|
|
219
|
+
|
|
220
|
+
transport_code = None
|
|
221
|
+
if route_type == 0x00 or route_type == 0x03: # has transport code
|
|
222
|
+
transport_code = pbuf.read(4) # discard transport code
|
|
221
223
|
|
|
222
|
-
|
|
224
|
+
path_len = pbuf.read(1)[0]
|
|
225
|
+
path = pbuf.read(path_len).hex() # Beware of traces where pathes are mixed
|
|
226
|
+
|
|
227
|
+
try :
|
|
228
|
+
route_typename = ROUTE_TYPENAMES[route_type]
|
|
229
|
+
except IndexError:
|
|
230
|
+
logger.debug(f"Unknown route type {route_type}")
|
|
231
|
+
route_typename = "UNK"
|
|
232
|
+
|
|
233
|
+
try :
|
|
234
|
+
payload_typename = PAYLOAD_TYPENAMES[payload_type]
|
|
235
|
+
except IndexError:
|
|
236
|
+
logger.debug(f"Unknown payload type {payload_type}")
|
|
237
|
+
payload_typename = "UNK"
|
|
238
|
+
|
|
239
|
+
pkt_payload = pbuf.read()
|
|
240
|
+
|
|
241
|
+
event.payload["header"] = header
|
|
242
|
+
event.payload["route_type"] = route_type
|
|
243
|
+
event.payload["route_typename"] = route_typename
|
|
244
|
+
event.payload["payload_type"] = payload_type
|
|
245
|
+
event.payload["payload_typename"]= payload_typename
|
|
246
|
+
|
|
247
|
+
event.payload["payload_ver"] = payload_ver
|
|
248
|
+
|
|
249
|
+
if not transport_code is None:
|
|
250
|
+
event.payload["transport_code"] = transport_code.hex()
|
|
251
|
+
|
|
252
|
+
event.payload["path_len"] = path_len
|
|
253
|
+
event.payload["path"] = path
|
|
254
|
+
|
|
255
|
+
event.payload["pkt_payload"] = pkt_payload.hex()
|
|
256
|
+
|
|
257
|
+
if payload_type == 0x05: # flood msg / channel
|
|
223
258
|
if handle_log_rx.channel_echoes:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
chan_hash = pbuf.read(1).hex()
|
|
229
|
-
cipher_mac = pbuf.read(2)
|
|
230
|
-
msg = pbuf.read() # until the end of buffer
|
|
259
|
+
pk_buf = io.BytesIO(pkt_payload)
|
|
260
|
+
chan_hash = pk_buf.read(1).hex()
|
|
261
|
+
cipher_mac = pk_buf.read(2)
|
|
262
|
+
msg = pk_buf.read() # until the end of buffer
|
|
231
263
|
|
|
232
264
|
channel = None
|
|
233
265
|
for c in await get_channels(mc):
|
|
@@ -253,13 +285,21 @@ async def handle_log_rx(event):
|
|
|
253
285
|
if chan_name != "" :
|
|
254
286
|
width = os.get_terminal_size().columns
|
|
255
287
|
cars = width - 13 - 2 * path_len - len(chan_name) - 1
|
|
256
|
-
dispmsg = message[0:cars]
|
|
288
|
+
dispmsg = message.replace("\n","")[0:cars]
|
|
257
289
|
txt = f"{ANSI_LIGHT_GRAY}{chan_name} {ANSI_DGREEN}{dispmsg+(cars-len(dispmsg))*' '} {ANSI_YELLOW}[{path}]{ANSI_LIGHT_GRAY}{event.payload['snr']:6,.2f}{event.payload['rssi']:4}{ANSI_END}"
|
|
258
290
|
if handle_message.above:
|
|
259
291
|
print_above(txt)
|
|
260
292
|
else:
|
|
261
293
|
print(txt)
|
|
262
294
|
|
|
295
|
+
if handle_log_rx.json_log_rx: # json mode ... raw dump
|
|
296
|
+
msg = json.dumps(event.payload)
|
|
297
|
+
if handle_message.above:
|
|
298
|
+
print_above(msg)
|
|
299
|
+
else :
|
|
300
|
+
print(msg)
|
|
301
|
+
|
|
302
|
+
|
|
263
303
|
handle_log_rx.json_log_rx = False
|
|
264
304
|
handle_log_rx.channel_echoes = False
|
|
265
305
|
handle_log_rx.mc = None
|
|
@@ -392,7 +432,7 @@ class MyNestedCompleter(NestedCompleter):
|
|
|
392
432
|
opts = self.options.keys()
|
|
393
433
|
completer = WordCompleter(
|
|
394
434
|
opts, ignore_case=self.ignore_case,
|
|
395
|
-
pattern=re.compile(r"([a-zA-Z0-9_
|
|
435
|
+
pattern=re.compile(r"([a-zA-Z0-9_\\/\#\?]+|[^a-zA-Z0-9_\s\#\?]+)"))
|
|
396
436
|
yield from completer.get_completions(document, complete_event)
|
|
397
437
|
else: # normal behavior for remainder
|
|
398
438
|
yield from super().get_completions(document, complete_event)
|
|
@@ -470,7 +510,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
470
510
|
"login" : contact_list,
|
|
471
511
|
"cmd" : contact_list,
|
|
472
512
|
"req_status" : contact_list,
|
|
473
|
-
"
|
|
513
|
+
"req_neighbours": contact_list,
|
|
474
514
|
"logout" : contact_list,
|
|
475
515
|
"req_telemetry" : contact_list,
|
|
476
516
|
"req_binary" : contact_list,
|
|
@@ -495,7 +535,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
495
535
|
"print_snr" : {"on":None, "off": None},
|
|
496
536
|
"json_msgs" : {"on":None, "off": None},
|
|
497
537
|
"color" : {"on":None, "off":None},
|
|
498
|
-
"print_name" : {"on":None, "off":None},
|
|
499
538
|
"print_adverts" : {"on":None, "off":None},
|
|
500
539
|
"json_log_rx" : {"on":None, "off":None},
|
|
501
540
|
"channel_echoes" : {"on":None, "off":None},
|
|
@@ -525,7 +564,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
525
564
|
"print_snr":None,
|
|
526
565
|
"json_msgs":None,
|
|
527
566
|
"color":None,
|
|
528
|
-
"print_name":None,
|
|
529
567
|
"print_adverts":None,
|
|
530
568
|
"json_log_rx":None,
|
|
531
569
|
"channel_echoes":None,
|
|
@@ -545,11 +583,24 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
545
583
|
"flood_after":None,
|
|
546
584
|
"custom":None,
|
|
547
585
|
},
|
|
586
|
+
"?get":None,
|
|
587
|
+
"?set":None,
|
|
588
|
+
"?scope":None,
|
|
589
|
+
"?contact_info":None,
|
|
590
|
+
"?apply_to":None,
|
|
591
|
+
"?at":None,
|
|
592
|
+
"?node_discover":None,
|
|
593
|
+
"?nd":None,
|
|
594
|
+
"?pending_contacts":None,
|
|
595
|
+
"?add_pending":None,
|
|
596
|
+
"?flush_pending":None,
|
|
548
597
|
}
|
|
549
598
|
|
|
550
599
|
contact_completion_list = {
|
|
551
600
|
"contact_info": None,
|
|
552
601
|
"contact_name": None,
|
|
602
|
+
"contact_key": None,
|
|
603
|
+
"contact_type": None,
|
|
553
604
|
"contact_lastmod": None,
|
|
554
605
|
"export_contact" : None,
|
|
555
606
|
"share_contact" : None,
|
|
@@ -577,7 +628,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
577
628
|
"login" : None,
|
|
578
629
|
"logout" : None,
|
|
579
630
|
"req_status" : None,
|
|
580
|
-
"req_bstatus" : None,
|
|
581
631
|
"req_neighbours": None,
|
|
582
632
|
"cmd" : None,
|
|
583
633
|
"ver" : None,
|
|
@@ -713,9 +763,10 @@ make_completion_dict.custom_vars = {}
|
|
|
713
763
|
async def interactive_loop(mc, to=None) :
|
|
714
764
|
print("""Interactive mode, most commands from terminal chat should work.
|
|
715
765
|
Use \"to\" to select recipient, use Tab to complete name ...
|
|
716
|
-
|
|
766
|
+
Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
717
767
|
\"quit\", \"q\", CTRL+D will end interactive mode""")
|
|
718
768
|
|
|
769
|
+
|
|
719
770
|
contact = to
|
|
720
771
|
prev_contact = None
|
|
721
772
|
|
|
@@ -724,16 +775,16 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
724
775
|
|
|
725
776
|
await get_contacts(mc, anim=True)
|
|
726
777
|
await get_channels(mc, anim=True)
|
|
778
|
+
|
|
779
|
+
# Call sync_msg before going further so there is no issue when scrolling
|
|
780
|
+
# long list of msgs
|
|
781
|
+
await next_cmd(mc, ["sync_msgs"])
|
|
782
|
+
|
|
727
783
|
await subscribe_to_msgs(mc, above=True)
|
|
728
784
|
|
|
729
785
|
handle_new_contact.print_new_contacts = True
|
|
730
786
|
|
|
731
787
|
try:
|
|
732
|
-
while True: # purge msgs
|
|
733
|
-
res = await mc.commands.get_msg()
|
|
734
|
-
if res.type == EventType.NO_MORE_MSGS:
|
|
735
|
-
break
|
|
736
|
-
|
|
737
788
|
if os.path.isdir(MCCLI_CONFIG_DIR) :
|
|
738
789
|
our_history = FileHistory(MCCLI_HISTORY_FILE)
|
|
739
790
|
else:
|
|
@@ -765,26 +816,32 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
765
816
|
|
|
766
817
|
color = process_event_message.color
|
|
767
818
|
classic = interactive_loop.classic or not color
|
|
768
|
-
print_name = interactive_loop.print_name
|
|
769
819
|
|
|
770
820
|
if classic:
|
|
771
821
|
prompt = ""
|
|
772
822
|
else:
|
|
773
823
|
prompt = f"{ANSI_INVERT}"
|
|
774
824
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
825
|
+
prompt = prompt + f"{ANSI_BGRAY}"
|
|
826
|
+
prompt = prompt + f"{mc.self_info['name']}"
|
|
827
|
+
if contact is None: # display scope
|
|
828
|
+
if not scope is None:
|
|
829
|
+
prompt = prompt + f"|{scope}"
|
|
830
|
+
|
|
831
|
+
if contact is None :
|
|
782
832
|
if classic :
|
|
783
|
-
prompt = prompt + ">
|
|
833
|
+
prompt = prompt + ">"
|
|
784
834
|
else :
|
|
785
|
-
prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}
|
|
786
|
-
|
|
787
|
-
|
|
835
|
+
prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}"
|
|
836
|
+
else:
|
|
837
|
+
if classic :
|
|
838
|
+
prompt = prompt + "/"
|
|
839
|
+
else :
|
|
840
|
+
if INVERT_SLASH:
|
|
841
|
+
prompt = prompt + f"{ANSI_INVERT}"
|
|
842
|
+
else:
|
|
843
|
+
prompt = prompt + f"{ANSI_NORMAL}"
|
|
844
|
+
prompt = prompt + f"{SLASH_START}"
|
|
788
845
|
if not last_ack:
|
|
789
846
|
prompt = prompt + f"{ANSI_BRED}"
|
|
790
847
|
if classic :
|
|
@@ -800,11 +857,9 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
800
857
|
else :
|
|
801
858
|
prompt = prompt + f"{ANSI_BBLUE}"
|
|
802
859
|
if not classic:
|
|
860
|
+
prompt = prompt + f"{SLASH_END}"
|
|
803
861
|
prompt = prompt + f"{ANSI_INVERT}"
|
|
804
862
|
|
|
805
|
-
if print_name and not classic :
|
|
806
|
-
prompt = prompt + f"{ANSI_NORMAL}{ARROW_TAIL}{ANSI_INVERT}"
|
|
807
|
-
|
|
808
863
|
prompt = prompt + f"{contact['adv_name']}"
|
|
809
864
|
if contact["type"] == 0 or contact["out_path_len"]==-1:
|
|
810
865
|
if scope is None:
|
|
@@ -818,14 +873,15 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
818
873
|
prompt = prompt + "|" + contact["out_path"]
|
|
819
874
|
|
|
820
875
|
if classic :
|
|
821
|
-
prompt = prompt + f"{ANSI_NORMAL}>
|
|
876
|
+
prompt = prompt + f"{ANSI_NORMAL}>"
|
|
822
877
|
else:
|
|
823
878
|
prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}"
|
|
824
879
|
|
|
825
880
|
prompt = prompt + f"{ANSI_END}"
|
|
826
881
|
|
|
827
|
-
|
|
828
|
-
|
|
882
|
+
prompt = prompt + " "
|
|
883
|
+
if not color :
|
|
884
|
+
prompt=escape_ansi(prompt)
|
|
829
885
|
|
|
830
886
|
session.app.ttimeoutlen = 0.2
|
|
831
887
|
session.app.timeoutlen = 0.2
|
|
@@ -1036,8 +1092,10 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
1036
1092
|
except asyncio.CancelledError:
|
|
1037
1093
|
# Handle task cancellation from KeyboardInterrupt in asyncio.run()
|
|
1038
1094
|
print("Exiting cli")
|
|
1039
|
-
|
|
1040
|
-
interactive_loop.
|
|
1095
|
+
if platform.system() == "Darwin" or platform.system() == "Windows":
|
|
1096
|
+
interactive_loop.classic = True
|
|
1097
|
+
else:
|
|
1098
|
+
interactive_loop.classic = False
|
|
1041
1099
|
|
|
1042
1100
|
async def process_contact_chat_line(mc, contact, line):
|
|
1043
1101
|
if contact["type"] == 0:
|
|
@@ -1059,6 +1117,26 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1059
1117
|
await process_cmds(mc, args)
|
|
1060
1118
|
return True
|
|
1061
1119
|
|
|
1120
|
+
if line.startswith("contact_key") or line.startswith("ck"):
|
|
1121
|
+
print(contact['public_key'],end="")
|
|
1122
|
+
if " " in line:
|
|
1123
|
+
print(" ", end="", flush=True)
|
|
1124
|
+
secline = line.split(" ", 1)[1]
|
|
1125
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1126
|
+
else:
|
|
1127
|
+
print("")
|
|
1128
|
+
return True
|
|
1129
|
+
|
|
1130
|
+
if line.startswith("contact_type") or line.startswith("ct"):
|
|
1131
|
+
print(f"{CONTACT_TYPENAMES[contact['type']]:4}",end="")
|
|
1132
|
+
if " " in line:
|
|
1133
|
+
print(" ", end="", flush=True)
|
|
1134
|
+
secline = line.split(" ", 1)[1]
|
|
1135
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1136
|
+
else:
|
|
1137
|
+
print("")
|
|
1138
|
+
return True
|
|
1139
|
+
|
|
1062
1140
|
if line.startswith("contact_name") or line.startswith("cn"):
|
|
1063
1141
|
print(contact['adv_name'],end="")
|
|
1064
1142
|
if " " in line:
|
|
@@ -1069,6 +1147,44 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1069
1147
|
print("")
|
|
1070
1148
|
return True
|
|
1071
1149
|
|
|
1150
|
+
if line.startswith("path") :
|
|
1151
|
+
if contact['out_path_len'] == -1:
|
|
1152
|
+
print("Flood", end="")
|
|
1153
|
+
elif contact['out_path_len'] == 0:
|
|
1154
|
+
print("0 hop", end="")
|
|
1155
|
+
else:
|
|
1156
|
+
print(contact['out_path'],end="")
|
|
1157
|
+
if " " in line:
|
|
1158
|
+
print(" ", end="", flush=True)
|
|
1159
|
+
secline = line.split(" ", 1)[1]
|
|
1160
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1161
|
+
else:
|
|
1162
|
+
print("")
|
|
1163
|
+
return True
|
|
1164
|
+
|
|
1165
|
+
if line.startswith("sleep ") or line.startswith("s "):
|
|
1166
|
+
try:
|
|
1167
|
+
sleeptime = int(line.split(" ",2)[1])
|
|
1168
|
+
cmd_pos = 2
|
|
1169
|
+
except IndexError: # nothing arg after sleep
|
|
1170
|
+
sleeptime = 1
|
|
1171
|
+
cmd_pos = 0
|
|
1172
|
+
except ValueError:
|
|
1173
|
+
sleeptime = 1
|
|
1174
|
+
cmd_pos = 1
|
|
1175
|
+
|
|
1176
|
+
try:
|
|
1177
|
+
if cmd_pos > 0:
|
|
1178
|
+
secline = line.split(" ",cmd_pos)[cmd_pos]
|
|
1179
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1180
|
+
except IndexError:
|
|
1181
|
+
pass
|
|
1182
|
+
|
|
1183
|
+
# will sleep after executed command if there is a command
|
|
1184
|
+
await asyncio.sleep(sleeptime)
|
|
1185
|
+
|
|
1186
|
+
return True
|
|
1187
|
+
|
|
1072
1188
|
if line == "contact_lastmod":
|
|
1073
1189
|
timestamp = contact["lastmod"]
|
|
1074
1190
|
print(f"{contact['adv_name']} updated"
|
|
@@ -1077,21 +1193,24 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1077
1193
|
return True
|
|
1078
1194
|
|
|
1079
1195
|
# commands that take contact as second arg will be sent to recipient
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
line
|
|
1083
|
-
line
|
|
1084
|
-
line
|
|
1085
|
-
line
|
|
1086
|
-
line
|
|
1087
|
-
line
|
|
1088
|
-
line
|
|
1089
|
-
line
|
|
1090
|
-
line
|
|
1091
|
-
line
|
|
1092
|
-
line
|
|
1093
|
-
args = [line, contact['adv_name']]
|
|
1196
|
+
# and can be chained ...
|
|
1197
|
+
if line.startswith("sc") or line.startswith("share_contact") or\
|
|
1198
|
+
line.startswith("ec") or line.startswith("export_contact") or\
|
|
1199
|
+
line.startswith("uc") or line.startswith("upload_contact") or\
|
|
1200
|
+
line.startswith("rp") or line.startswith("reset_path") or\
|
|
1201
|
+
line.startswith("dp") or line.startswith("disc_path") or\
|
|
1202
|
+
line.startswith("contact_info") or line.startswith("ci") or\
|
|
1203
|
+
line.startswith("req_status") or line.startswith("rs") or\
|
|
1204
|
+
line.startswith("req_neighbours") or line.startswith("rn") or\
|
|
1205
|
+
line.startswith("req_telemetry") or line.startswith("rt") or\
|
|
1206
|
+
line.startswith("req_acl") or\
|
|
1207
|
+
line.startswith("path") or\
|
|
1208
|
+
line.startswith("logout") :
|
|
1209
|
+
args = [line.split()[0], contact['adv_name']]
|
|
1094
1210
|
await process_cmds(mc, args)
|
|
1211
|
+
if " " in line:
|
|
1212
|
+
secline = line.split(" ", 1)[1]
|
|
1213
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1095
1214
|
return True
|
|
1096
1215
|
|
|
1097
1216
|
# special case for rp that can be chained from cmdline
|
|
@@ -1104,6 +1223,8 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1104
1223
|
|
|
1105
1224
|
if line.startswith("set timeout "):
|
|
1106
1225
|
cmds=line.split(" ")
|
|
1226
|
+
#args = ["contact_timeout", contact['adv_name'], cmds[2]]
|
|
1227
|
+
#await process_cmds(mc, args)
|
|
1107
1228
|
contact["timeout"] = float(cmds[2])
|
|
1108
1229
|
return True
|
|
1109
1230
|
|
|
@@ -1241,12 +1362,13 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1241
1362
|
|
|
1242
1363
|
return False
|
|
1243
1364
|
|
|
1244
|
-
async def apply_command_to_contacts(mc, contact_filter, line):
|
|
1365
|
+
async def apply_command_to_contacts(mc, contact_filter, line, json_output=False):
|
|
1245
1366
|
upd_before = None
|
|
1246
1367
|
upd_after = None
|
|
1247
1368
|
contact_type = None
|
|
1248
1369
|
min_hops = None
|
|
1249
1370
|
max_hops = None
|
|
1371
|
+
count = 0
|
|
1250
1372
|
|
|
1251
1373
|
await mc.ensure_contacts()
|
|
1252
1374
|
|
|
@@ -1301,6 +1423,9 @@ async def apply_command_to_contacts(mc, contact_filter, line):
|
|
|
1301
1423
|
(upd_after is None or contact["lastmod"] > upd_after) and\
|
|
1302
1424
|
(min_hops is None or contact["out_path_len"] >= min_hops) and\
|
|
1303
1425
|
(max_hops is None or contact["out_path_len"] <= max_hops):
|
|
1426
|
+
|
|
1427
|
+
count = count + 1
|
|
1428
|
+
|
|
1304
1429
|
if await process_contact_chat_line(mc, contact, line):
|
|
1305
1430
|
pass
|
|
1306
1431
|
|
|
@@ -1325,6 +1450,9 @@ async def apply_command_to_contacts(mc, contact_filter, line):
|
|
|
1325
1450
|
else:
|
|
1326
1451
|
logger.error(f"Can't send {line} to {contact['adv_name']}")
|
|
1327
1452
|
|
|
1453
|
+
if not json_output:
|
|
1454
|
+
print(f"> {count} matches in contacts")
|
|
1455
|
+
|
|
1328
1456
|
async def send_cmd (mc, contact, cmd) :
|
|
1329
1457
|
res = await mc.commands.send_cmd(contact, cmd)
|
|
1330
1458
|
if not res is None and not res.type == EventType.ERROR:
|
|
@@ -1366,7 +1494,8 @@ async def send_msg (mc, contact, msg) :
|
|
|
1366
1494
|
return res
|
|
1367
1495
|
|
|
1368
1496
|
async def msg_ack (mc, contact, msg) :
|
|
1369
|
-
timeout = 0 if not 'timeout' in contact
|
|
1497
|
+
timeout = 0 if not isinstance(contact, dict) or not 'timeout' in contact\
|
|
1498
|
+
else contact['timeout']
|
|
1370
1499
|
res = await mc.commands.send_msg_with_retry(contact, msg,
|
|
1371
1500
|
max_attempts=msg_ack.max_attempts,
|
|
1372
1501
|
flood_after=msg_ack.flood_after,
|
|
@@ -1615,7 +1744,7 @@ async def print_disc_trace_to (mc, contact):
|
|
|
1615
1744
|
|
|
1616
1745
|
async def next_cmd(mc, cmds, json_output=False):
|
|
1617
1746
|
""" process next command """
|
|
1618
|
-
global
|
|
1747
|
+
global ARROW_HEAD, SLASH_START, SLASH_END, INVERT_SLASH
|
|
1619
1748
|
try :
|
|
1620
1749
|
argnum = 0
|
|
1621
1750
|
|
|
@@ -1715,44 +1844,32 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1715
1844
|
|
|
1716
1845
|
case "apply_to"|"at":
|
|
1717
1846
|
argnum = 2
|
|
1718
|
-
await apply_command_to_contacts(mc, cmds[1], cmds[2])
|
|
1847
|
+
await apply_command_to_contacts(mc, cmds[1], cmds[2], json_output=json_output)
|
|
1719
1848
|
|
|
1720
1849
|
case "set":
|
|
1721
1850
|
argnum = 2
|
|
1722
1851
|
match cmds[1]:
|
|
1723
1852
|
case "help" :
|
|
1724
1853
|
argnum = 1
|
|
1725
|
-
|
|
1726
|
-
pin <pin> : ble pin
|
|
1727
|
-
radio <freq,bw,sf,cr> : radio params
|
|
1728
|
-
tuning <rx_dly,af> : tuning params
|
|
1729
|
-
tx <dbm> : tx power
|
|
1730
|
-
name <name> : node name
|
|
1731
|
-
lat <lat> : latitude
|
|
1732
|
-
lon <lon> : longitude
|
|
1733
|
-
coords <lat,lon> : coordinates
|
|
1734
|
-
print_snr <on/off> : toggle snr display in messages
|
|
1735
|
-
print_adverts <on/off> : display adverts as they come
|
|
1736
|
-
print_new_contacts <on/off> : display new pending contacts when available
|
|
1737
|
-
print_path_updates <on/off> : display path updates as they come""")
|
|
1854
|
+
get_help_for("set")
|
|
1738
1855
|
case "max_flood_attempts":
|
|
1739
1856
|
msg_ack.max_flood_attempts=int(cmds[2])
|
|
1740
1857
|
case "max_attempts":
|
|
1741
1858
|
msg_ack.max_attempts=int(cmds[2])
|
|
1742
1859
|
case "flood_after":
|
|
1743
1860
|
msg_ack.flood_after=int(cmds[2])
|
|
1744
|
-
case "print_name":
|
|
1745
|
-
interactive_loop.print_name = (cmds[2] == "on")
|
|
1746
|
-
if json_output :
|
|
1747
|
-
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1748
1861
|
case "classic_prompt":
|
|
1749
1862
|
interactive_loop.classic = (cmds[2] == "on")
|
|
1750
1863
|
if json_output :
|
|
1751
1864
|
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1752
|
-
case "arrow_tail":
|
|
1753
|
-
ARROW_TAIL = cmds[2]
|
|
1754
1865
|
case "arrow_head":
|
|
1755
1866
|
ARROW_HEAD = cmds[2]
|
|
1867
|
+
case "slash_start":
|
|
1868
|
+
SLASH_START = cmds[2]
|
|
1869
|
+
case "slash_end":
|
|
1870
|
+
SLASH_END = cmds[2]
|
|
1871
|
+
case "invert_slash":
|
|
1872
|
+
INVERT_SLASH = cmds[2] == "on"
|
|
1756
1873
|
case "color" :
|
|
1757
1874
|
process_event_message.color = (cmds[2] == "on")
|
|
1758
1875
|
if json_output :
|
|
@@ -1958,21 +2075,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1958
2075
|
argnum = 1
|
|
1959
2076
|
match cmds[1]:
|
|
1960
2077
|
case "help":
|
|
1961
|
-
|
|
1962
|
-
name : node name
|
|
1963
|
-
bat : battery level in mV
|
|
1964
|
-
fstats : fs statistics
|
|
1965
|
-
coords : adv coordinates
|
|
1966
|
-
lat : latitude
|
|
1967
|
-
lon : longitude
|
|
1968
|
-
radio : radio parameters
|
|
1969
|
-
tx : tx power
|
|
1970
|
-
print_snr : snr display in messages
|
|
1971
|
-
print_adverts : display adverts as they come
|
|
1972
|
-
print_new_contacts : display new pending contacts when available
|
|
1973
|
-
print_path_updates : display path updates as they come
|
|
1974
|
-
custom : all custom variables in json format
|
|
1975
|
-
each custom var can also be get/set directly""")
|
|
2078
|
+
get_help_for("get")
|
|
1976
2079
|
case "max_flood_attempts":
|
|
1977
2080
|
if json_output :
|
|
1978
2081
|
print(json.dumps({"max_flood_attempts" : msg_ack.max_flood_attempts}))
|
|
@@ -1983,11 +2086,6 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1983
2086
|
print(json.dumps({"flood_after" : msg_ack.flood_after}))
|
|
1984
2087
|
else:
|
|
1985
2088
|
print(f"flood_after: {msg_ack.flood_after}")
|
|
1986
|
-
case "print_name":
|
|
1987
|
-
if json_output :
|
|
1988
|
-
print(json.dumps({"print_name" : interactive_loop.print_name}))
|
|
1989
|
-
else:
|
|
1990
|
-
print(f"{'on' if interactive_loop.print_name else 'off'}")
|
|
1991
2089
|
case "classic_prompt":
|
|
1992
2090
|
if json_output :
|
|
1993
2091
|
print(json.dumps({"classic_prompt" : interactive_loop.classic}))
|
|
@@ -2237,7 +2335,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2237
2335
|
argnum = 2
|
|
2238
2336
|
dest = None
|
|
2239
2337
|
|
|
2240
|
-
if len(cmds[1])
|
|
2338
|
+
if len(cmds[1]) >= 12: # possibly an hex prefix
|
|
2241
2339
|
try:
|
|
2242
2340
|
dest = bytes.fromhex(cmds[1])
|
|
2243
2341
|
except ValueError:
|
|
@@ -2344,12 +2442,12 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2344
2442
|
else :
|
|
2345
2443
|
color = process_event_message.color
|
|
2346
2444
|
classic = interactive_loop.classic or not color
|
|
2347
|
-
print("
|
|
2445
|
+
print(" ", end="")
|
|
2348
2446
|
for t in ev.payload["path"]:
|
|
2349
2447
|
if classic :
|
|
2350
2448
|
print("→",end="")
|
|
2351
2449
|
else:
|
|
2352
|
-
print(f"
|
|
2450
|
+
print(f"{ANSI_INVERT}", end="")
|
|
2353
2451
|
snr = t['snr']
|
|
2354
2452
|
if color:
|
|
2355
2453
|
if snr >= 10 :
|
|
@@ -2368,7 +2466,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2368
2466
|
if "hash" in t:
|
|
2369
2467
|
print(f"[{t['hash']}]",end="")
|
|
2370
2468
|
else:
|
|
2371
|
-
print(
|
|
2469
|
+
print()
|
|
2372
2470
|
|
|
2373
2471
|
case "login" | "l" :
|
|
2374
2472
|
argnum = 2
|
|
@@ -2428,46 +2526,6 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2428
2526
|
contact = mc.get_contact_by_name(cmds[1])
|
|
2429
2527
|
contact["timeout"] = float(cmds[2])
|
|
2430
2528
|
|
|
2431
|
-
case "req_status" | "rs" :
|
|
2432
|
-
argnum = 1
|
|
2433
|
-
await mc.ensure_contacts()
|
|
2434
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2435
|
-
res = await mc.commands.send_statusreq(contact)
|
|
2436
|
-
logger.debug(res)
|
|
2437
|
-
if res.type == EventType.ERROR:
|
|
2438
|
-
print(f"Error while requesting status: {res}")
|
|
2439
|
-
else :
|
|
2440
|
-
timeout = res.payload["suggested_timeout"]/800 if not "timeout" in contact or contact['timeout']==0 else contact["timeout"]
|
|
2441
|
-
res = await mc.wait_for_event(EventType.STATUS_RESPONSE, timeout=timeout)
|
|
2442
|
-
logger.debug(res)
|
|
2443
|
-
if res is None:
|
|
2444
|
-
if json_output :
|
|
2445
|
-
print(json.dumps({"error" : "Timeout waiting status"}))
|
|
2446
|
-
else:
|
|
2447
|
-
print("Timeout waiting status")
|
|
2448
|
-
else :
|
|
2449
|
-
print(json.dumps(res.payload, indent=4))
|
|
2450
|
-
|
|
2451
|
-
case "req_telemetry" | "rt" :
|
|
2452
|
-
argnum = 1
|
|
2453
|
-
await mc.ensure_contacts()
|
|
2454
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2455
|
-
res = await mc.commands.send_telemetry_req(contact)
|
|
2456
|
-
logger.debug(res)
|
|
2457
|
-
if res.type == EventType.ERROR:
|
|
2458
|
-
print(f"Error while requesting telemetry")
|
|
2459
|
-
else:
|
|
2460
|
-
timeout = res.payload["suggested_timeout"]/800 if not "timeout" in contact or contact['timeout']==0 else contact["timeout"]
|
|
2461
|
-
res = await mc.wait_for_event(EventType.TELEMETRY_RESPONSE, timeout=timeout)
|
|
2462
|
-
logger.debug(res)
|
|
2463
|
-
if res is None:
|
|
2464
|
-
if json_output :
|
|
2465
|
-
print(json.dumps({"error" : "Timeout waiting telemetry"}))
|
|
2466
|
-
else:
|
|
2467
|
-
print("Timeout waiting telemetry")
|
|
2468
|
-
else :
|
|
2469
|
-
print(json.dumps(res.payload, indent=4))
|
|
2470
|
-
|
|
2471
2529
|
case "disc_path" | "dp" :
|
|
2472
2530
|
argnum = 1
|
|
2473
2531
|
await mc.ensure_contacts()
|
|
@@ -2538,22 +2596,18 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2538
2596
|
await mc.ensure_contacts()
|
|
2539
2597
|
print(f"Discovered {len(dn)} nodes:")
|
|
2540
2598
|
for n in dn:
|
|
2541
|
-
|
|
2542
|
-
|
|
2599
|
+
try :
|
|
2600
|
+
name = f"{n['pubkey'][0:2]} {mc.get_contact_by_key_prefix(n['pubkey'])['adv_name']}"
|
|
2601
|
+
except TypeError:
|
|
2543
2602
|
name = n["pubkey"][0:16]
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
type = "SENS"
|
|
2553
|
-
|
|
2554
|
-
print(f" {name:16} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
|
|
2555
|
-
|
|
2556
|
-
case "req_btelemetry"|"rbt" :
|
|
2603
|
+
if n['node_type'] >= len(CONTACT_TYPENAMES):
|
|
2604
|
+
type = f"t:{n['node_type']}"
|
|
2605
|
+
else:
|
|
2606
|
+
type = CONTACT_TYPENAMES[n['node_type']]
|
|
2607
|
+
|
|
2608
|
+
print(f" {name:22} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
|
|
2609
|
+
|
|
2610
|
+
case "req_telemetry"|"rt" :
|
|
2557
2611
|
argnum = 1
|
|
2558
2612
|
await mc.ensure_contacts()
|
|
2559
2613
|
contact = mc.get_contact_by_name(cmds[1])
|
|
@@ -2565,9 +2619,13 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2565
2619
|
else:
|
|
2566
2620
|
print("Error getting data")
|
|
2567
2621
|
else :
|
|
2568
|
-
print(json.dumps(
|
|
2622
|
+
print(json.dumps({
|
|
2623
|
+
"name": contact["adv_name"],
|
|
2624
|
+
"pubkey_pre": contact["public_key"][0:16],
|
|
2625
|
+
"lpp": res,
|
|
2626
|
+
}, indent = 4))
|
|
2569
2627
|
|
|
2570
|
-
case "
|
|
2628
|
+
case "req_status"|"rs" :
|
|
2571
2629
|
argnum = 1
|
|
2572
2630
|
await mc.ensure_contacts()
|
|
2573
2631
|
contact = mc.get_contact_by_name(cmds[1])
|
|
@@ -2651,15 +2709,30 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2651
2709
|
if json_output:
|
|
2652
2710
|
print(json.dumps(res, indent=4))
|
|
2653
2711
|
else:
|
|
2712
|
+
width = os.get_terminal_size().columns
|
|
2654
2713
|
print(f"Got {res['results_count']} neighbours out of {res['neighbours_count']} from {contact['adv_name']}:")
|
|
2655
2714
|
for n in res['neighbours']:
|
|
2656
2715
|
ct = mc.get_contact_by_key_prefix(n["pubkey"])
|
|
2657
|
-
if ct :
|
|
2716
|
+
if ct and width > 60 :
|
|
2658
2717
|
name = f"[{n['pubkey'][0:8]}] {ct['adv_name']}"
|
|
2718
|
+
name = f"{name:30}"
|
|
2719
|
+
elif ct :
|
|
2720
|
+
name = f"{ct['adv_name']}"
|
|
2721
|
+
name = f"{name:20}"
|
|
2659
2722
|
else:
|
|
2660
2723
|
name = f"[{n['pubkey']}]"
|
|
2661
2724
|
|
|
2662
|
-
|
|
2725
|
+
t_s = n['secs_ago']
|
|
2726
|
+
time_ago = f"{t_s}s"
|
|
2727
|
+
if t_s / 86400 >= 1 : # result in days
|
|
2728
|
+
time_ago = f"{int(t_s/86400)}d ago{f' ({time_ago})' if width > 62 else ''}"
|
|
2729
|
+
elif t_s / 3600 >= 1 : # result in days
|
|
2730
|
+
time_ago = f"{int(t_s/3600)}h ago{f' ({time_ago})' if width > 62 else ''}"
|
|
2731
|
+
elif t_s / 60 >= 1 : # result in min
|
|
2732
|
+
time_ago = f"{int(t_s/60)}m ago{f' ({time_ago})' if width > 62 else ''}"
|
|
2733
|
+
|
|
2734
|
+
|
|
2735
|
+
print(f" {name} {time_ago}, {n['snr']}dB{' SNR' if width > 66 else ''}")
|
|
2663
2736
|
|
|
2664
2737
|
case "req_binary" :
|
|
2665
2738
|
argnum = 2
|
|
@@ -2682,7 +2755,13 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2682
2755
|
print(json.dumps(res, indent=4))
|
|
2683
2756
|
else :
|
|
2684
2757
|
for c in res.items():
|
|
2685
|
-
|
|
2758
|
+
if c[1]['out_path_len'] == -1:
|
|
2759
|
+
path_str = "Flood"
|
|
2760
|
+
elif c[1]['out_path_len'] == 0:
|
|
2761
|
+
path_str = "0 hop"
|
|
2762
|
+
else:
|
|
2763
|
+
path_str = f"{c[1]['out_path']}"
|
|
2764
|
+
print(f"{c[1]['adv_name']:30} {CONTACT_TYPENAMES[c[1]['type']]:4} {c[1]['public_key'][:12]} {path_str}")
|
|
2686
2765
|
print(f"> {len(mc.contacts)} contacts in device")
|
|
2687
2766
|
|
|
2688
2767
|
case "reload_contacts" | "rc":
|
|
@@ -2750,7 +2829,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2750
2829
|
if (path_len == 0) :
|
|
2751
2830
|
print("0 hop")
|
|
2752
2831
|
elif (path_len == -1) :
|
|
2753
|
-
print("
|
|
2832
|
+
print("Flood")
|
|
2754
2833
|
else:
|
|
2755
2834
|
print(path)
|
|
2756
2835
|
|
|
@@ -2777,6 +2856,8 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2777
2856
|
print(f"Unknown contact {cmds[1]}")
|
|
2778
2857
|
else:
|
|
2779
2858
|
path = cmds[2].replace(",","") # we'll accept path with ,
|
|
2859
|
+
if path == "0":
|
|
2860
|
+
path = ""
|
|
2780
2861
|
try:
|
|
2781
2862
|
res = await mc.commands.change_contact_path(contact, path)
|
|
2782
2863
|
logger.debug(res)
|
|
@@ -3109,8 +3190,8 @@ def command_help():
|
|
|
3109
3190
|
reboot : reboots node
|
|
3110
3191
|
sleep <secs> : sleeps for a given amount of secs s
|
|
3111
3192
|
wait_key : wait until user presses <Enter> wk
|
|
3112
|
-
apply_to <
|
|
3113
|
-
|
|
3193
|
+
apply_to <f> <cmds> : sends cmds to contacts matching f at
|
|
3194
|
+
Messaging
|
|
3114
3195
|
msg <name> <msg> : send message to node by name m {
|
|
3115
3196
|
wait_ack : wait an ack wa }
|
|
3116
3197
|
chan <nb> <msg> : send message to channel number <nb> ch
|
|
@@ -3123,6 +3204,7 @@ def command_help():
|
|
|
3123
3204
|
get_channel <n> : get info for channel (by number or name)
|
|
3124
3205
|
set_channel n nm k : set channel info (nb, name, key)
|
|
3125
3206
|
remove_channel <n> : remove channel (by number or name)
|
|
3207
|
+
scope <s> : sets scope for flood messages
|
|
3126
3208
|
Management
|
|
3127
3209
|
advert : sends advert a
|
|
3128
3210
|
floodadv : flood advert
|
|
@@ -3164,17 +3246,21 @@ def command_help():
|
|
|
3164
3246
|
def usage () :
|
|
3165
3247
|
""" Prints some help """
|
|
3166
3248
|
version()
|
|
3249
|
+
command_usage()
|
|
3250
|
+
print(" Available Commands and shorcuts (can be chained) :""")
|
|
3251
|
+
command_help()
|
|
3252
|
+
|
|
3253
|
+
def command_usage() :
|
|
3167
3254
|
print("""
|
|
3168
3255
|
Usage : meshcore-cli <args> <commands>
|
|
3169
3256
|
|
|
3170
3257
|
Arguments :
|
|
3171
|
-
-h : prints
|
|
3258
|
+
-h : prints help for arguments and commands
|
|
3172
3259
|
-v : prints version
|
|
3173
3260
|
-j : json output (disables init file)
|
|
3174
3261
|
-D : debug
|
|
3175
3262
|
-S : scan for devices and show a selector
|
|
3176
3263
|
-l : list available ble/serial devices and exit
|
|
3177
|
-
-c <on/off> : disables most of color output if off
|
|
3178
3264
|
-T <timeout> : timeout for the ble scan (-S and -l) default 2s
|
|
3179
3265
|
-a <address> : specifies device address (can be a name)
|
|
3180
3266
|
-d <name> : filter meshcore devices with name or address
|
|
@@ -3183,14 +3269,14 @@ def usage () :
|
|
|
3183
3269
|
-p <port> : specifies tcp port (default 5000)
|
|
3184
3270
|
-s <port> : use serial port <port>
|
|
3185
3271
|
-b <baudrate> : specify baudrate
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3272
|
+
-C : toggles classic mode for prompt
|
|
3273
|
+
-c <on/off> : disables most of color output if off
|
|
3274
|
+
""")
|
|
3189
3275
|
|
|
3190
3276
|
def get_help_for (cmdname, context="line") :
|
|
3191
3277
|
if cmdname == "apply_to" or cmdname == "at" :
|
|
3192
|
-
print("""apply_to <
|
|
3193
|
-
|
|
3278
|
+
print("""apply_to <f> <cmd> : applies cmd to contacts matching filter <f>
|
|
3279
|
+
Filter is constructed with comma separated fields :
|
|
3194
3280
|
- u, matches modification time < or > than a timestamp
|
|
3195
3281
|
(can also be days hours or minutes ago if followed by d,h or m)
|
|
3196
3282
|
- t, matches the type (1: client, 2: repeater, 3: room, 4: sensor)
|
|
@@ -3198,7 +3284,7 @@ def get_help_for (cmdname, context="line") :
|
|
|
3198
3284
|
- d, direct, similar to h>-1
|
|
3199
3285
|
- f, flood, similar to h<0 or h=-1
|
|
3200
3286
|
|
|
3201
|
-
Note: Some commands like contact_name (aka cn), reset_path (aka rp), forget_password (aka fp) can be chained.
|
|
3287
|
+
Note: Some commands like contact_name (aka cn), contact_key (aka ck), contact_type (aka ct), reset_path (aka rp), forget_password (aka fp) can be chained. There is also a sleep command taking an optional event. The sleep will be issued after the command, it helps limiting rate through repeaters ...
|
|
3202
3288
|
|
|
3203
3289
|
Examples:
|
|
3204
3290
|
# removes all clients that have not been updated in last 2 days
|
|
@@ -3209,7 +3295,7 @@ def get_help_for (cmdname, context="line") :
|
|
|
3209
3295
|
at t=2 rp login
|
|
3210
3296
|
""")
|
|
3211
3297
|
|
|
3212
|
-
|
|
3298
|
+
elif cmdname == "node_discover" or cmdname == "nd" :
|
|
3213
3299
|
print("""node_discover <filter> : discovers 0-hop nodes and displays signal info
|
|
3214
3300
|
|
|
3215
3301
|
filter can be "all" for all types or nodes or a comma separated list consisting of :
|
|
@@ -3221,6 +3307,98 @@ def get_help_for (cmdname, context="line") :
|
|
|
3221
3307
|
nd can be used with no filter parameter ... !!! BEWARE WITH CHAINING !!!
|
|
3222
3308
|
""")
|
|
3223
3309
|
|
|
3310
|
+
elif cmdname == "get" :
|
|
3311
|
+
print("""Gets parameters from node
|
|
3312
|
+
Please see also help for set command, which is more up to date ...
|
|
3313
|
+
name : node name
|
|
3314
|
+
bat : battery level in mV
|
|
3315
|
+
fstats : fs statistics
|
|
3316
|
+
coords : adv coordinates
|
|
3317
|
+
lat : latitude
|
|
3318
|
+
lon : longitude
|
|
3319
|
+
radio : radio parameters
|
|
3320
|
+
tx : tx power
|
|
3321
|
+
print_snr : snr display in messages
|
|
3322
|
+
print_adverts : display adverts as they come
|
|
3323
|
+
print_new_contacts : display new pending contacts when available
|
|
3324
|
+
print_path_updates : display path updates as they come
|
|
3325
|
+
custom : all custom variables in json format
|
|
3326
|
+
each custom var can also be get/set directly
|
|
3327
|
+
""")
|
|
3328
|
+
|
|
3329
|
+
elif cmdname == "set" :
|
|
3330
|
+
print("""Available parameters :
|
|
3331
|
+
device:
|
|
3332
|
+
pin <pin> : ble pin
|
|
3333
|
+
radio <freq,bw,sf,cr> : radio params
|
|
3334
|
+
tuning <rx_dly,af> : tuning params
|
|
3335
|
+
tx <dbm> : tx power
|
|
3336
|
+
name <name> : node name
|
|
3337
|
+
lat <lat> : latitude
|
|
3338
|
+
lon <lon> : longitude
|
|
3339
|
+
coords <lat,lon> : coordinates
|
|
3340
|
+
multi_ack <on/off> : multi-acks feature
|
|
3341
|
+
telemetry_mode_base <mode> : set basic telemetry mode all/selected/off
|
|
3342
|
+
telemetry_mode_loc <mode> : set location telemetry mode all/selected/off
|
|
3343
|
+
telemetry_mode_env <mode> : set env telemetry mode all/selected/off
|
|
3344
|
+
advert_loc_policy <policy> : "share" means loc will be shared in adv
|
|
3345
|
+
manual_add_contacts <on/off>: let user manually add contacts to device
|
|
3346
|
+
- when off device automatically adds contacts from adverts
|
|
3347
|
+
- when on contacts must be added manually using add_pending
|
|
3348
|
+
(pending contacts list is built by meshcli from adverts while connected)
|
|
3349
|
+
display:
|
|
3350
|
+
print_snr <on/off> : toggle snr display in messages
|
|
3351
|
+
print_adverts <on/off> : display adverts as they come
|
|
3352
|
+
print_new_contacts <on/off> : display new pending contacts when available
|
|
3353
|
+
print_path_updates <on/off> : display path updates as they come
|
|
3354
|
+
json_log_rx <on/off> : logs packets incoming to device as json
|
|
3355
|
+
channel_echoes <on/off> : print repeats for channel data
|
|
3356
|
+
echo_unk_channels <on/off> : also dump unk channels (encrypted)
|
|
3357
|
+
color <on/off> : color off should remove ANSI codes from output
|
|
3358
|
+
meshcore-cli behaviour:
|
|
3359
|
+
classic_prompt <on/off> : activates less fancier prompt
|
|
3360
|
+
arrow_head <string> : change arrow head in prompt
|
|
3361
|
+
slash_start <string> : idem for slash start
|
|
3362
|
+
slash_end <string> : slash end
|
|
3363
|
+
invert_slash <on/off> : apply color inversion to slash
|
|
3364
|
+
auto_update_contacts <on/of>: auto sync contact list with device
|
|
3365
|
+
""")
|
|
3366
|
+
|
|
3367
|
+
elif cmdname == "scope":
|
|
3368
|
+
print("""scope <scope> : changes flood scope of the node
|
|
3369
|
+
|
|
3370
|
+
The scope command can be used from command line or interactive mode to set the region in which flood packets will be transmitted.
|
|
3371
|
+
|
|
3372
|
+
Managing Flood Scope in interactive mode
|
|
3373
|
+
Flood scope has recently been introduced in meshcore (from v1.10.0). It limits the scope of packets to regions, using transport codes in the frame.
|
|
3374
|
+
When entering chat mode, scope will be reset to *, meaning classic flood.
|
|
3375
|
+
You can switch scope using the scope command, or postfixing the to command with %<scope>.
|
|
3376
|
+
Scope can also be applied to a command using % before the scope name. For instance login%#Morbihan will limit diffusion of the login command (which is usually sent flood to get the path to a repeater) to the #Morbihan region.
|
|
3377
|
+
""")
|
|
3378
|
+
|
|
3379
|
+
elif cmdname == "contact_info":
|
|
3380
|
+
print("""contact_info <ct> : displays contact info
|
|
3381
|
+
|
|
3382
|
+
in interactive mode, there are some lighter commands that can be chained to give more compact information
|
|
3383
|
+
- contact_name (cn)
|
|
3384
|
+
- contact_key (ck)
|
|
3385
|
+
- contact_type (ct)
|
|
3386
|
+
""")
|
|
3387
|
+
|
|
3388
|
+
elif cmdname == "pending_contacts" or cmdname == "flush_pending" or cmdname == "add_pending":
|
|
3389
|
+
print("""Contact management
|
|
3390
|
+
|
|
3391
|
+
To receive a message from another user, it is necessary to have its public key. This key is stored on a contact list in the device, and this list has a finite size (50 when meshcore started, now over 350 for most devices).
|
|
3392
|
+
|
|
3393
|
+
By default contacts are automatically added to the device contact list when an advertisement is received, so as soon as you receive an advert, you can talk with your buddy.
|
|
3394
|
+
|
|
3395
|
+
With growing number of users, it becomes necessary to manage contact list and one of the ways is to add contacts manually to the device. This is done by turning on manual_add_contacts. Once this option has been turned on, a pending list is built by meshcore-cli from the received adverts. You can view the list issuing a pending_contacts command, flush the list using flush_pending or add a contact from the list with add_pending followed by the key of the contact or its name (both will be auto-completed with tab).
|
|
3396
|
+
|
|
3397
|
+
This feature only really works in interactive mode.
|
|
3398
|
+
|
|
3399
|
+
Note: There is also an auto_update_contacts setting that has nothing to do with adding contacts, it permits to automatically sync contact lists between device and meshcore-cli (when there is an update in name, location or path).
|
|
3400
|
+
""")
|
|
3401
|
+
|
|
3224
3402
|
else:
|
|
3225
3403
|
print(f"Sorry, no help yet for {cmdname}")
|
|
3226
3404
|
|
|
@@ -3236,18 +3414,26 @@ async def main(argv):
|
|
|
3236
3414
|
baudrate = 115200
|
|
3237
3415
|
timeout = 2
|
|
3238
3416
|
pin = None
|
|
3417
|
+
first_device = False
|
|
3239
3418
|
# If there is an address in config file, use it by default
|
|
3240
3419
|
# unless an arg is explicitely given
|
|
3241
3420
|
if os.path.exists(MCCLI_ADDRESS) :
|
|
3242
3421
|
with open(MCCLI_ADDRESS, encoding="utf-8") as f :
|
|
3243
3422
|
address = f.readline().strip()
|
|
3244
3423
|
|
|
3245
|
-
|
|
3424
|
+
try:
|
|
3425
|
+
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:C")
|
|
3426
|
+
except getopt.GetoptError:
|
|
3427
|
+
print("Unrecognized option, use -h to get more help")
|
|
3428
|
+
command_usage()
|
|
3429
|
+
return
|
|
3246
3430
|
for opt, arg in opts :
|
|
3247
3431
|
match opt:
|
|
3248
3432
|
case "-c" :
|
|
3249
3433
|
if arg == "off":
|
|
3250
3434
|
process_event_message.color = False
|
|
3435
|
+
case "-C":
|
|
3436
|
+
interactive_loop.classic = not interactive_loop.classic
|
|
3251
3437
|
case "-d" : # name specified on cmdline
|
|
3252
3438
|
address = arg
|
|
3253
3439
|
case "-a" : # address specified on cmdline
|
|
@@ -3277,6 +3463,7 @@ async def main(argv):
|
|
|
3277
3463
|
return
|
|
3278
3464
|
case "-f": # connect to first encountered device
|
|
3279
3465
|
address = ""
|
|
3466
|
+
first_device = True
|
|
3280
3467
|
case "-l" :
|
|
3281
3468
|
print("BLE devices:")
|
|
3282
3469
|
try :
|
|
@@ -3372,8 +3559,15 @@ async def main(argv):
|
|
|
3372
3559
|
|
|
3373
3560
|
try :
|
|
3374
3561
|
mc = await MeshCore.create_ble(address=address, device=device, client=client, debug=debug, only_error=json_output, pin=pin)
|
|
3562
|
+
except BleakError :
|
|
3563
|
+
print("BLE connection asked (default behaviour), but no BLE HW found")
|
|
3564
|
+
print("Call meshcore-cli with -h for some more help (on commands)")
|
|
3565
|
+
command_usage()
|
|
3566
|
+
return
|
|
3375
3567
|
except ConnectionError :
|
|
3376
3568
|
logger.info("Error while connecting, retrying once ...")
|
|
3569
|
+
if first_device :
|
|
3570
|
+
address = "" # reset address to change device if first_device was asked
|
|
3377
3571
|
if device is None and client is None: # Search for device
|
|
3378
3572
|
logger.info(f"Scanning BLE for device matching {address}")
|
|
3379
3573
|
devices = await BleakScanner.discover(timeout=timeout)
|