meshcore-cli 1.3.0__py3-none-any.whl → 1.3.1__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 +112 -39
- {meshcore_cli-1.3.0.dist-info → meshcore_cli-1.3.1.dist-info}/METADATA +1 -1
- meshcore_cli-1.3.1.dist-info/RECORD +8 -0
- meshcore_cli-1.3.0.dist-info/RECORD +0 -8
- {meshcore_cli-1.3.0.dist-info → meshcore_cli-1.3.1.dist-info}/WHEEL +0 -0
- {meshcore_cli-1.3.0.dist-info → meshcore_cli-1.3.1.dist-info}/entry_points.txt +0 -0
- {meshcore_cli-1.3.0.dist-info → meshcore_cli-1.3.1.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.3.
|
|
35
|
+
VERSION = "v1.3.1"
|
|
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 = ""
|
|
@@ -203,9 +207,6 @@ process_event_message.print_snr=False
|
|
|
203
207
|
process_event_message.color=True
|
|
204
208
|
process_event_message.last_node=None
|
|
205
209
|
|
|
206
|
-
PAYLOAD_TYPENAMES = ["REQ", "RESPONSE", "TEXT_MSG", "ACK", "ADVERT", "GRP_TXT", "GRP_DATA", "ANON_REQ", "PATH", "TRACE", "MULTIPART", "CONTROL"]
|
|
207
|
-
ROUTE_TYPENAMES = ["TC_FLOOD", "FLOOD", "DIRECT", "TC_DIRECT"]
|
|
208
|
-
|
|
209
210
|
async def handle_log_rx(event):
|
|
210
211
|
mc = handle_log_rx.mc
|
|
211
212
|
|
|
@@ -431,7 +432,7 @@ class MyNestedCompleter(NestedCompleter):
|
|
|
431
432
|
opts = self.options.keys()
|
|
432
433
|
completer = WordCompleter(
|
|
433
434
|
opts, ignore_case=self.ignore_case,
|
|
434
|
-
pattern=re.compile(r"([a-zA-Z0-9_
|
|
435
|
+
pattern=re.compile(r"([a-zA-Z0-9_\\/\#\?]+|[^a-zA-Z0-9_\s\#\?]+)"))
|
|
435
436
|
yield from completer.get_completions(document, complete_event)
|
|
436
437
|
else: # normal behavior for remainder
|
|
437
438
|
yield from super().get_completions(document, complete_event)
|
|
@@ -582,11 +583,21 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
582
583
|
"flood_after":None,
|
|
583
584
|
"custom":None,
|
|
584
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,
|
|
585
594
|
}
|
|
586
595
|
|
|
587
596
|
contact_completion_list = {
|
|
588
597
|
"contact_info": None,
|
|
589
598
|
"contact_name": None,
|
|
599
|
+
"contact_key": None,
|
|
600
|
+
"contact_type": None,
|
|
590
601
|
"contact_lastmod": None,
|
|
591
602
|
"export_contact" : None,
|
|
592
603
|
"share_contact" : None,
|
|
@@ -749,7 +760,7 @@ make_completion_dict.custom_vars = {}
|
|
|
749
760
|
async def interactive_loop(mc, to=None) :
|
|
750
761
|
print("""Interactive mode, most commands from terminal chat should work.
|
|
751
762
|
Use \"to\" to select recipient, use Tab to complete name ...
|
|
752
|
-
|
|
763
|
+
Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
753
764
|
\"quit\", \"q\", CTRL+D will end interactive mode""")
|
|
754
765
|
|
|
755
766
|
contact = to
|
|
@@ -1102,6 +1113,26 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1102
1113
|
await process_cmds(mc, args)
|
|
1103
1114
|
return True
|
|
1104
1115
|
|
|
1116
|
+
if line.startswith("contact_key") or line.startswith("ck"):
|
|
1117
|
+
print(contact['public_key'],end="")
|
|
1118
|
+
if " " in line:
|
|
1119
|
+
print(" ", end="", flush=True)
|
|
1120
|
+
secline = line.split(" ", 1)[1]
|
|
1121
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1122
|
+
else:
|
|
1123
|
+
print("")
|
|
1124
|
+
return True
|
|
1125
|
+
|
|
1126
|
+
if line.startswith("contact_type") or line.startswith("ct"):
|
|
1127
|
+
print(f"{CONTACT_TYPENAMES[contact['type']]:4}",end="")
|
|
1128
|
+
if " " in line:
|
|
1129
|
+
print(" ", end="", flush=True)
|
|
1130
|
+
secline = line.split(" ", 1)[1]
|
|
1131
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1132
|
+
else:
|
|
1133
|
+
print("")
|
|
1134
|
+
return True
|
|
1135
|
+
|
|
1105
1136
|
if line.startswith("contact_name") or line.startswith("cn"):
|
|
1106
1137
|
print(contact['adv_name'],end="")
|
|
1107
1138
|
if " " in line:
|
|
@@ -1112,6 +1143,21 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1112
1143
|
print("")
|
|
1113
1144
|
return True
|
|
1114
1145
|
|
|
1146
|
+
if line.startswith("path") :
|
|
1147
|
+
if contact['out_path_len'] == -1:
|
|
1148
|
+
print("Flood", end="")
|
|
1149
|
+
elif contact['out_path_len'] == 0:
|
|
1150
|
+
print("0 hop", end="")
|
|
1151
|
+
else:
|
|
1152
|
+
print(contact['out_path'],end="")
|
|
1153
|
+
if " " in line:
|
|
1154
|
+
print(" ", end="", flush=True)
|
|
1155
|
+
secline = line.split(" ", 1)[1]
|
|
1156
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1157
|
+
else:
|
|
1158
|
+
print("")
|
|
1159
|
+
return True
|
|
1160
|
+
|
|
1115
1161
|
if line.startswith("sleep") or line.startswith("s"):
|
|
1116
1162
|
try:
|
|
1117
1163
|
sleeptime = int(line.split(" ",2)[1])
|
|
@@ -1143,20 +1189,24 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1143
1189
|
return True
|
|
1144
1190
|
|
|
1145
1191
|
# commands that take contact as second arg will be sent to recipient
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
line
|
|
1149
|
-
line
|
|
1150
|
-
line
|
|
1151
|
-
line
|
|
1152
|
-
line
|
|
1153
|
-
line
|
|
1154
|
-
line
|
|
1155
|
-
line
|
|
1156
|
-
line
|
|
1157
|
-
line
|
|
1158
|
-
|
|
1192
|
+
# and can be chained ...
|
|
1193
|
+
if line.startswith("sc") or line.startswith("share_contact") or\
|
|
1194
|
+
line.startswith("ec") or line.startswith("export_contact") or\
|
|
1195
|
+
line.startswith("uc") or line.startswith("upload_contact") or\
|
|
1196
|
+
line.startswith("rp") or line.startswith("reset_path") or\
|
|
1197
|
+
line.startswith("dp") or line.startswith("disc_path") or\
|
|
1198
|
+
line.startswith("contact_info") or line.startswith("ci") or\
|
|
1199
|
+
line.startswith("req_status") or line.startswith("rs") or\
|
|
1200
|
+
line.startswith("req_neighbours") or line.startswith("rn") or\
|
|
1201
|
+
line.startswith("req_telemetry") or line.startswith("rt") or\
|
|
1202
|
+
line.startswith("req_acl") or\
|
|
1203
|
+
line.startswith("path") or\
|
|
1204
|
+
line.startswith("logout") :
|
|
1205
|
+
args = [line.split()[0], contact['adv_name']]
|
|
1159
1206
|
await process_cmds(mc, args)
|
|
1207
|
+
if " " in line:
|
|
1208
|
+
secline = line.split(" ", 1)[1]
|
|
1209
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1160
1210
|
return True
|
|
1161
1211
|
|
|
1162
1212
|
# special case for rp that can be chained from cmdline
|
|
@@ -1306,12 +1356,13 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1306
1356
|
|
|
1307
1357
|
return False
|
|
1308
1358
|
|
|
1309
|
-
async def apply_command_to_contacts(mc, contact_filter, line):
|
|
1359
|
+
async def apply_command_to_contacts(mc, contact_filter, line, json_output=False):
|
|
1310
1360
|
upd_before = None
|
|
1311
1361
|
upd_after = None
|
|
1312
1362
|
contact_type = None
|
|
1313
1363
|
min_hops = None
|
|
1314
1364
|
max_hops = None
|
|
1365
|
+
count = 0
|
|
1315
1366
|
|
|
1316
1367
|
await mc.ensure_contacts()
|
|
1317
1368
|
|
|
@@ -1366,6 +1417,9 @@ async def apply_command_to_contacts(mc, contact_filter, line):
|
|
|
1366
1417
|
(upd_after is None or contact["lastmod"] > upd_after) and\
|
|
1367
1418
|
(min_hops is None or contact["out_path_len"] >= min_hops) and\
|
|
1368
1419
|
(max_hops is None or contact["out_path_len"] <= max_hops):
|
|
1420
|
+
|
|
1421
|
+
count = count + 1
|
|
1422
|
+
|
|
1369
1423
|
if await process_contact_chat_line(mc, contact, line):
|
|
1370
1424
|
pass
|
|
1371
1425
|
|
|
@@ -1390,6 +1444,9 @@ async def apply_command_to_contacts(mc, contact_filter, line):
|
|
|
1390
1444
|
else:
|
|
1391
1445
|
logger.error(f"Can't send {line} to {contact['adv_name']}")
|
|
1392
1446
|
|
|
1447
|
+
if not json_output:
|
|
1448
|
+
print(f"> {count} matches in contacts")
|
|
1449
|
+
|
|
1393
1450
|
async def send_cmd (mc, contact, cmd) :
|
|
1394
1451
|
res = await mc.commands.send_cmd(contact, cmd)
|
|
1395
1452
|
if not res is None and not res.type == EventType.ERROR:
|
|
@@ -1780,7 +1837,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1780
1837
|
|
|
1781
1838
|
case "apply_to"|"at":
|
|
1782
1839
|
argnum = 2
|
|
1783
|
-
await apply_command_to_contacts(mc, cmds[1], cmds[2])
|
|
1840
|
+
await apply_command_to_contacts(mc, cmds[1], cmds[2], json_output=json_output)
|
|
1784
1841
|
|
|
1785
1842
|
case "set":
|
|
1786
1843
|
argnum = 2
|
|
@@ -2532,18 +2589,14 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2532
2589
|
await mc.ensure_contacts()
|
|
2533
2590
|
print(f"Discovered {len(dn)} nodes:")
|
|
2534
2591
|
for n in dn:
|
|
2535
|
-
|
|
2536
|
-
|
|
2592
|
+
try :
|
|
2593
|
+
name = f"{n['pubkey'][0:2]} {mc.get_contact_by_key_prefix(n['pubkey'])['adv_name']}"
|
|
2594
|
+
except TypeError:
|
|
2537
2595
|
name = n["pubkey"][0:16]
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
type = "REP"
|
|
2543
|
-
elif n['node_type'] == 3:
|
|
2544
|
-
type = "ROOM"
|
|
2545
|
-
elif n['node_type'] == 4:
|
|
2546
|
-
type = "SENS"
|
|
2596
|
+
if n['node_type'] >= len(CONTACT_TYPENAMES):
|
|
2597
|
+
type = f"t:{n['node_type']}"
|
|
2598
|
+
else:
|
|
2599
|
+
type = CONTACT_TYPENAMES[n['node_type']]
|
|
2547
2600
|
|
|
2548
2601
|
print(f" {name:16} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
|
|
2549
2602
|
|
|
@@ -2561,7 +2614,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2561
2614
|
else :
|
|
2562
2615
|
print(json.dumps({
|
|
2563
2616
|
"name": contact["adv_name"],
|
|
2564
|
-
"pubkey_pre": contact["public_key"][0:
|
|
2617
|
+
"pubkey_pre": contact["public_key"][0:16],
|
|
2565
2618
|
"lpp": res,
|
|
2566
2619
|
}, indent = 4))
|
|
2567
2620
|
|
|
@@ -2695,7 +2748,13 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2695
2748
|
print(json.dumps(res, indent=4))
|
|
2696
2749
|
else :
|
|
2697
2750
|
for c in res.items():
|
|
2698
|
-
|
|
2751
|
+
if c[1]['out_path_len'] == -1:
|
|
2752
|
+
path_str = "Flood"
|
|
2753
|
+
elif c[1]['out_path_len'] == 0:
|
|
2754
|
+
path_str = "0 hop"
|
|
2755
|
+
else:
|
|
2756
|
+
path_str = f"{c[1]['out_path']}"
|
|
2757
|
+
print(f"{c[1]['adv_name']:30} {CONTACT_TYPENAMES[c[1]['type']]:4} {c[1]['public_key'][:12]} {path_str}")
|
|
2699
2758
|
print(f"> {len(mc.contacts)} contacts in device")
|
|
2700
2759
|
|
|
2701
2760
|
case "reload_contacts" | "rc":
|
|
@@ -2763,7 +2822,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2763
2822
|
if (path_len == 0) :
|
|
2764
2823
|
print("0 hop")
|
|
2765
2824
|
elif (path_len == -1) :
|
|
2766
|
-
print("
|
|
2825
|
+
print("Flood")
|
|
2767
2826
|
else:
|
|
2768
2827
|
print(path)
|
|
2769
2828
|
|
|
@@ -2790,6 +2849,8 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2790
2849
|
print(f"Unknown contact {cmds[1]}")
|
|
2791
2850
|
else:
|
|
2792
2851
|
path = cmds[2].replace(",","") # we'll accept path with ,
|
|
2852
|
+
if path == "0":
|
|
2853
|
+
path = ""
|
|
2793
2854
|
try:
|
|
2794
2855
|
res = await mc.commands.change_contact_path(contact, path)
|
|
2795
2856
|
logger.debug(res)
|
|
@@ -3213,7 +3274,7 @@ def get_help_for (cmdname, context="line") :
|
|
|
3213
3274
|
- d, direct, similar to h>-1
|
|
3214
3275
|
- f, flood, similar to h<0 or h=-1
|
|
3215
3276
|
|
|
3216
|
-
Note: Some commands like contact_name (aka cn), 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 ...
|
|
3277
|
+
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 ...
|
|
3217
3278
|
|
|
3218
3279
|
Examples:
|
|
3219
3280
|
# removes all clients that have not been updated in last 2 days
|
|
@@ -3252,7 +3313,8 @@ def get_help_for (cmdname, context="line") :
|
|
|
3252
3313
|
print_new_contacts : display new pending contacts when available
|
|
3253
3314
|
print_path_updates : display path updates as they come
|
|
3254
3315
|
custom : all custom variables in json format
|
|
3255
|
-
each custom var can also be get/set directly
|
|
3316
|
+
each custom var can also be get/set directly
|
|
3317
|
+
""")
|
|
3256
3318
|
|
|
3257
3319
|
elif cmdname == "set" :
|
|
3258
3320
|
print("""Available parameters :
|
|
@@ -3285,7 +3347,8 @@ def get_help_for (cmdname, context="line") :
|
|
|
3285
3347
|
arrow_head <string> : change arrow head in prompt
|
|
3286
3348
|
slash_start <string> : idem for slash start
|
|
3287
3349
|
slash_end <string> : slash end
|
|
3288
|
-
invert_slash <on/off> : apply color inversion to slash
|
|
3350
|
+
invert_slash <on/off> : apply color inversion to slash
|
|
3351
|
+
""")
|
|
3289
3352
|
|
|
3290
3353
|
elif cmdname == "scope":
|
|
3291
3354
|
print("""scope <scope> : changes flood scope of the node
|
|
@@ -3296,7 +3359,17 @@ Managing Flood Scope in interactive mode
|
|
|
3296
3359
|
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.
|
|
3297
3360
|
When entering chat mode, scope will be reset to *, meaning classic flood.
|
|
3298
3361
|
You can switch scope using the scope command, or postfixing the to command with %<scope>.
|
|
3299
|
-
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.
|
|
3362
|
+
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.
|
|
3363
|
+
""")
|
|
3364
|
+
|
|
3365
|
+
elif cmdname == "contact_info":
|
|
3366
|
+
print("""contact_info <ct> : displays contact info
|
|
3367
|
+
|
|
3368
|
+
in interactive mode, there are some lighter commands that can be chained to give more compact information
|
|
3369
|
+
- contact_name (cn)
|
|
3370
|
+
- contact_key (ck)
|
|
3371
|
+
- contact_type (ct)
|
|
3372
|
+
""")
|
|
3300
3373
|
|
|
3301
3374
|
else:
|
|
3302
3375
|
print(f"Sorry, no help yet for {cmdname}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore-cli
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.1
|
|
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
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
meshcore_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
meshcore_cli/__main__.py,sha256=PfYgibmu2LEtC-OV7L1UgmvV3swJ5rQ4bbXHlwUFlgE,83
|
|
3
|
+
meshcore_cli/meshcore_cli.py,sha256=ZuBGD-H_nXLmHSP3ffat8ZtKNfieKbLmL3TQgv8B4_I,149253
|
|
4
|
+
meshcore_cli-1.3.1.dist-info/METADATA,sha256=k3NMG9h1qPemlm8if4A_7JD7yH6Wi8wzhABH_0ijtJo,15873
|
|
5
|
+
meshcore_cli-1.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
meshcore_cli-1.3.1.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
+
meshcore_cli-1.3.1.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
+
meshcore_cli-1.3.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
meshcore_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
meshcore_cli/__main__.py,sha256=PfYgibmu2LEtC-OV7L1UgmvV3swJ5rQ4bbXHlwUFlgE,83
|
|
3
|
-
meshcore_cli/meshcore_cli.py,sha256=C6dZGtfqIEIsEaoVBNOmr4xmoy0sMr-gZOTqZ0prxd0,146609
|
|
4
|
-
meshcore_cli-1.3.0.dist-info/METADATA,sha256=Fgv1cb5iij3ExDitwB8vRvP7GuxSOPqkv5nxnyATMRA,15873
|
|
5
|
-
meshcore_cli-1.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
meshcore_cli-1.3.0.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
-
meshcore_cli-1.3.0.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
-
meshcore_cli-1.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|