meshcore-cli 1.2.4__py3-none-any.whl → 1.2.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 +217 -31
- {meshcore_cli-1.2.4.dist-info → meshcore_cli-1.2.6.dist-info}/METADATA +2 -2
- meshcore_cli-1.2.6.dist-info/RECORD +8 -0
- meshcore_cli-1.2.4.dist-info/RECORD +0 -8
- {meshcore_cli-1.2.4.dist-info → meshcore_cli-1.2.6.dist-info}/WHEEL +0 -0
- {meshcore_cli-1.2.4.dist-info → meshcore_cli-1.2.6.dist-info}/entry_points.txt +0 -0
- {meshcore_cli-1.2.4.dist-info → meshcore_cli-1.2.6.dist-info}/licenses/LICENSE +0 -0
meshcore_cli/meshcore_cli.py
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
|
-
import os, sys
|
|
7
|
+
import os, sys, io
|
|
8
8
|
import time, datetime
|
|
9
9
|
import getopt, json, shlex, re
|
|
10
10
|
import logging
|
|
@@ -33,7 +33,7 @@ import re
|
|
|
33
33
|
from meshcore import MeshCore, EventType, logger
|
|
34
34
|
|
|
35
35
|
# Version
|
|
36
|
-
VERSION = "v1.2.
|
|
36
|
+
VERSION = "v1.2.6"
|
|
37
37
|
|
|
38
38
|
# default ble address is stored in a config file
|
|
39
39
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
|
@@ -208,14 +208,19 @@ async def handle_log_rx(event):
|
|
|
208
208
|
return
|
|
209
209
|
|
|
210
210
|
pkt = bytes().fromhex(event.payload["payload"])
|
|
211
|
+
pbuf = io.BytesIO(pkt)
|
|
212
|
+
header = pbuf.read(1)[0]
|
|
213
|
+
|
|
214
|
+
if header & ~1 == 0x14: # flood msg / channel
|
|
215
|
+
if handle_log_rx.channel_echoes:
|
|
216
|
+
if header & 1 == 0: # has transport code
|
|
217
|
+
pbuf.read(4) # discard transport code
|
|
218
|
+
path_len = pbuf.read(1)[0]
|
|
219
|
+
path = pbuf.read(path_len).hex()
|
|
220
|
+
chan_hash = pbuf.read(1).hex()
|
|
221
|
+
cipher_mac = pbuf.read(2)
|
|
222
|
+
msg = pbuf.read() # until the end of buffer
|
|
211
223
|
|
|
212
|
-
if handle_log_rx.channel_echoes:
|
|
213
|
-
if pkt[0] == 0x15:
|
|
214
|
-
path_len = pkt[1]
|
|
215
|
-
path = pkt[2:path_len+2].hex()
|
|
216
|
-
chan_hash = pkt[path_len+2:path_len+3].hex()
|
|
217
|
-
cipher_mac = pkt[path_len+3:path_len+5]
|
|
218
|
-
msg = pkt[path_len+5:]
|
|
219
224
|
channel = None
|
|
220
225
|
for c in await get_channels(mc):
|
|
221
226
|
if c["channel_hash"] == chan_hash : # validate against MAC
|
|
@@ -225,28 +230,32 @@ async def handle_log_rx(event):
|
|
|
225
230
|
channel = c
|
|
226
231
|
break
|
|
227
232
|
|
|
233
|
+
chan_name = ""
|
|
234
|
+
|
|
228
235
|
if channel is None :
|
|
229
|
-
|
|
230
|
-
|
|
236
|
+
if handle_log_rx.echo_unk_chans:
|
|
237
|
+
chan_name = chan_hash
|
|
238
|
+
message = msg.hex()
|
|
231
239
|
else:
|
|
232
240
|
chan_name = channel["channel_name"]
|
|
233
241
|
aes_key = bytes.fromhex(channel["channel_secret"])
|
|
234
242
|
cipher = AES.new(aes_key, AES.MODE_ECB)
|
|
235
243
|
message = cipher.decrypt(msg)[5:].decode("utf-8").strip("\x00")
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
|
|
245
|
+
if chan_name != "" :
|
|
246
|
+
width = os.get_terminal_size().columns
|
|
247
|
+
cars = width - 13 - 2 * path_len - len(chan_name) - 1
|
|
248
|
+
dispmsg = message[0:cars]
|
|
249
|
+
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}"
|
|
250
|
+
if handle_message.above:
|
|
251
|
+
print_above(txt)
|
|
252
|
+
else:
|
|
253
|
+
print(txt)
|
|
254
|
+
|
|
247
255
|
handle_log_rx.json_log_rx = False
|
|
248
256
|
handle_log_rx.channel_echoes = False
|
|
249
257
|
handle_log_rx.mc = None
|
|
258
|
+
handle_log_rx.echo_unk_chans=False
|
|
250
259
|
|
|
251
260
|
async def handle_advert(event):
|
|
252
261
|
if not handle_advert.print_adverts:
|
|
@@ -319,7 +328,7 @@ async def log_message(mc, msg):
|
|
|
319
328
|
if msg["type"] == "PRIV" :
|
|
320
329
|
ct = mc.get_contact_by_key_prefix(msg['pubkey_prefix'])
|
|
321
330
|
if ct is None:
|
|
322
|
-
msg["name"] =
|
|
331
|
+
msg["name"] = msg["pubkey_prefix"]
|
|
323
332
|
else:
|
|
324
333
|
msg["name"] = ct["adv_name"]
|
|
325
334
|
elif msg["type"] == "CHAN" :
|
|
@@ -363,7 +372,7 @@ async def subscribe_to_msgs(mc, json_output=False, above=False):
|
|
|
363
372
|
class MyNestedCompleter(NestedCompleter):
|
|
364
373
|
def get_completions( self, document, complete_event):
|
|
365
374
|
txt = document.text_before_cursor.lstrip()
|
|
366
|
-
if not " " in txt:
|
|
375
|
+
if not " " in txt:
|
|
367
376
|
if txt != "" and txt[0] == "/" and txt.count("/") == 1:
|
|
368
377
|
opts = []
|
|
369
378
|
for k in self.options.keys():
|
|
@@ -396,6 +405,10 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
396
405
|
for c in it :
|
|
397
406
|
contact_list[c[1]['adv_name']] = None
|
|
398
407
|
|
|
408
|
+
pit = iter(pending.items())
|
|
409
|
+
for c in pit :
|
|
410
|
+
pending_list[c[1]['adv_name']] = None
|
|
411
|
+
|
|
399
412
|
pit = iter(pending.items())
|
|
400
413
|
for c in pit :
|
|
401
414
|
pending_list[c[1]['public_key']] = None
|
|
@@ -458,6 +471,8 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
458
471
|
"set_channel": None,
|
|
459
472
|
"get_channels": None,
|
|
460
473
|
"remove_channel": None,
|
|
474
|
+
"apply_to": None,
|
|
475
|
+
"at": None,
|
|
461
476
|
"set" : {
|
|
462
477
|
"name" : None,
|
|
463
478
|
"pin" : None,
|
|
@@ -474,6 +489,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
474
489
|
"print_adverts" : {"on":None, "off":None},
|
|
475
490
|
"json_log_rx" : {"on":None, "off":None},
|
|
476
491
|
"channel_echoes" : {"on":None, "off":None},
|
|
492
|
+
"echo_unk_chans" : {"on":None, "off":None},
|
|
477
493
|
"print_new_contacts" : {"on": None, "off":None},
|
|
478
494
|
"print_path_updates" : {"on":None,"off":None},
|
|
479
495
|
"classic_prompt" : {"on" : None, "off":None},
|
|
@@ -503,6 +519,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
503
519
|
"print_adverts":None,
|
|
504
520
|
"json_log_rx":None,
|
|
505
521
|
"channel_echoes":None,
|
|
522
|
+
"echo_unk_chans":None,
|
|
506
523
|
"print_path_updates":None,
|
|
507
524
|
"print_new_contacts":None,
|
|
508
525
|
"classic_prompt":None,
|
|
@@ -522,6 +539,8 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
522
539
|
|
|
523
540
|
contact_completion_list = {
|
|
524
541
|
"contact_info": None,
|
|
542
|
+
"contact_name": None,
|
|
543
|
+
"contact_lastmod": None,
|
|
525
544
|
"export_contact" : None,
|
|
526
545
|
"share_contact" : None,
|
|
527
546
|
"upload_contact" : None,
|
|
@@ -643,7 +662,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
643
662
|
slash_root_completion_list = {}
|
|
644
663
|
for k,v in root_completion_list.items():
|
|
645
664
|
slash_root_completion_list["/"+k]=v
|
|
646
|
-
|
|
665
|
+
|
|
647
666
|
completion_list.update(slash_root_completion_list)
|
|
648
667
|
|
|
649
668
|
slash_contacts_completion_list = {}
|
|
@@ -793,6 +812,9 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
793
812
|
if line == "" : # blank line
|
|
794
813
|
pass
|
|
795
814
|
|
|
815
|
+
elif line.startswith("?") :
|
|
816
|
+
get_help_for(line[1:], context="chat")
|
|
817
|
+
|
|
796
818
|
# raw meshcli command as on command line
|
|
797
819
|
elif line.startswith("$") :
|
|
798
820
|
try :
|
|
@@ -896,6 +918,13 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
896
918
|
if last_ack == False :
|
|
897
919
|
contact = ln
|
|
898
920
|
|
|
921
|
+
elif contact is None and\
|
|
922
|
+
(line.startswith("apply_to ") or line.startswith("at ")):
|
|
923
|
+
try:
|
|
924
|
+
await apply_command_to_contacts(mc, line.split(" ",2)[1], line.split(" ",2)[2])
|
|
925
|
+
except IndexError:
|
|
926
|
+
logger.error(f"Error with apply_to command parameters")
|
|
927
|
+
|
|
899
928
|
# commands are passed through if at root
|
|
900
929
|
elif contact is None or line.startswith(".") :
|
|
901
930
|
try:
|
|
@@ -957,6 +986,23 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
957
986
|
await process_cmds(mc, args)
|
|
958
987
|
return True
|
|
959
988
|
|
|
989
|
+
if line.startswith("contact_name") or line.startswith("cn"):
|
|
990
|
+
print(contact['adv_name'],end="")
|
|
991
|
+
if " " in line:
|
|
992
|
+
print(" ", end="", flush=True)
|
|
993
|
+
secline = line.split(" ", 1)[1]
|
|
994
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
995
|
+
else:
|
|
996
|
+
print("")
|
|
997
|
+
return True
|
|
998
|
+
|
|
999
|
+
if line == "contact_lastmod":
|
|
1000
|
+
timestamp = contact["lastmod"]
|
|
1001
|
+
print(f"{contact['adv_name']} updated"
|
|
1002
|
+
f" {datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d at %H:%M:%S')}"
|
|
1003
|
+
f" ({timestamp})")
|
|
1004
|
+
return True
|
|
1005
|
+
|
|
960
1006
|
# commands that take contact as second arg will be sent to recipient
|
|
961
1007
|
if line == "sc" or line == "share_contact" or\
|
|
962
1008
|
line == "ec" or line == "export_contact" or\
|
|
@@ -1044,7 +1090,7 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1044
1090
|
return True
|
|
1045
1091
|
|
|
1046
1092
|
# same but for commands with a parameter
|
|
1047
|
-
if line.startswith("cmd ") or\
|
|
1093
|
+
if line.startswith("cmd ") or line.startswith("msg ") or\
|
|
1048
1094
|
line.startswith("cp ") or line.startswith("change_path ") or\
|
|
1049
1095
|
line.startswith("cf ") or line.startswith("change_flags ") or\
|
|
1050
1096
|
line.startswith("req_binary ") or\
|
|
@@ -1076,7 +1122,7 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1076
1122
|
|
|
1077
1123
|
if password == "":
|
|
1078
1124
|
try:
|
|
1079
|
-
sess = PromptSession("Password: ", is_password=True)
|
|
1125
|
+
sess = PromptSession(f"Password for {contact['adv_name']}: ", is_password=True)
|
|
1080
1126
|
password = await sess.prompt_async()
|
|
1081
1127
|
except EOFError:
|
|
1082
1128
|
logger.info("Canceled")
|
|
@@ -1115,6 +1161,89 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1115
1161
|
|
|
1116
1162
|
return False
|
|
1117
1163
|
|
|
1164
|
+
async def apply_command_to_contacts(mc, contact_filter, line):
|
|
1165
|
+
upd_before = None
|
|
1166
|
+
upd_after = None
|
|
1167
|
+
contact_type = None
|
|
1168
|
+
min_hops = None
|
|
1169
|
+
max_hops = None
|
|
1170
|
+
|
|
1171
|
+
await mc.ensure_contacts()
|
|
1172
|
+
|
|
1173
|
+
filters = contact_filter.split(",")
|
|
1174
|
+
for f in filters:
|
|
1175
|
+
if f == "all":
|
|
1176
|
+
pass
|
|
1177
|
+
elif f[0] == "u": #updated
|
|
1178
|
+
val_str = f[2:]
|
|
1179
|
+
t = time.time()
|
|
1180
|
+
if val_str[-1] == "d": # value in days
|
|
1181
|
+
t = t - float(val_str[0:-1]) * 86400
|
|
1182
|
+
elif val_str[-1] == "h": # value in hours
|
|
1183
|
+
t = t - float(val_str[0:-1]) * 3600
|
|
1184
|
+
elif val_str[-1] == "m": # value in minutes
|
|
1185
|
+
t = t - float(val_str[0:-1]) * 60
|
|
1186
|
+
else:
|
|
1187
|
+
t = int(val_str)
|
|
1188
|
+
if f[1] == "<": #before
|
|
1189
|
+
upd_before = t
|
|
1190
|
+
elif f[1] == ">":
|
|
1191
|
+
upd_after = t
|
|
1192
|
+
else:
|
|
1193
|
+
logger.error(f"Time filter can only be < or >")
|
|
1194
|
+
return
|
|
1195
|
+
elif f[0] == "t": # type
|
|
1196
|
+
if f[1] == "=":
|
|
1197
|
+
contact_type = int(f[2:])
|
|
1198
|
+
else:
|
|
1199
|
+
logger.error(f"Type can only be equals to a value")
|
|
1200
|
+
return
|
|
1201
|
+
elif f[0] == "d": # direct
|
|
1202
|
+
min_hops=0
|
|
1203
|
+
elif f[0] == "f": # flood
|
|
1204
|
+
max_hops=-1
|
|
1205
|
+
elif f[0] == "h": # hop number
|
|
1206
|
+
if f[1] == ">":
|
|
1207
|
+
min_hops = int(f[2:])+1
|
|
1208
|
+
elif f[1] == "<":
|
|
1209
|
+
max_hops = int(f[2:])-1
|
|
1210
|
+
elif f[1] == "=":
|
|
1211
|
+
min_hops = int(f[2:])
|
|
1212
|
+
max_hops = int(f[2:])
|
|
1213
|
+
else:
|
|
1214
|
+
logger.error(f"Unknown filter {f}")
|
|
1215
|
+
return
|
|
1216
|
+
|
|
1217
|
+
for c in dict(mc._contacts).items():
|
|
1218
|
+
contact = c[1]
|
|
1219
|
+
if (contact_type is None or contact["type"] == contact_type) and\
|
|
1220
|
+
(upd_before is None or contact["lastmod"] < upd_before) and\
|
|
1221
|
+
(upd_after is None or contact["lastmod"] > upd_after) and\
|
|
1222
|
+
(min_hops is None or contact["out_path_len"] >= min_hops) and\
|
|
1223
|
+
(max_hops is None or contact["out_path_len"] <= max_hops):
|
|
1224
|
+
if await process_contact_chat_line(mc, contact, line):
|
|
1225
|
+
pass
|
|
1226
|
+
|
|
1227
|
+
elif line == "remove_contact":
|
|
1228
|
+
args = [line, contact['adv_name']]
|
|
1229
|
+
await process_cmds(mc, args)
|
|
1230
|
+
|
|
1231
|
+
elif line.startswith("send") or line.startswith("\"") :
|
|
1232
|
+
if line.startswith("send") :
|
|
1233
|
+
line = line[5:]
|
|
1234
|
+
if line.startswith("\"") :
|
|
1235
|
+
line = line[1:]
|
|
1236
|
+
await msg_ack(mc, contact, line)
|
|
1237
|
+
|
|
1238
|
+
elif contact["type"] == 2 or\
|
|
1239
|
+
contact["type"] == 3 or\
|
|
1240
|
+
contact["type"] == 4 : # repeater, room, sensor send cmd
|
|
1241
|
+
await process_cmds(mc, ["cmd", contact["adv_name"], line])
|
|
1242
|
+
# wait for a reply from cmd
|
|
1243
|
+
await mc.wait_for_event(EventType.MESSAGES_WAITING, timeout=7)
|
|
1244
|
+
|
|
1245
|
+
else:
|
|
1246
|
+
logger.error(f"Can't send {line} to {contact['adv_name']}")
|
|
1118
1247
|
|
|
1119
1248
|
async def send_cmd (mc, contact, cmd) :
|
|
1120
1249
|
res = await mc.commands.send_cmd(contact, cmd)
|
|
@@ -1386,6 +1515,11 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1386
1515
|
""" process next command """
|
|
1387
1516
|
try :
|
|
1388
1517
|
argnum = 0
|
|
1518
|
+
|
|
1519
|
+
if cmds[0].startswith("?") : # get some help
|
|
1520
|
+
get_help_for(cmds[0][1:], context="line")
|
|
1521
|
+
return cmds[argnum+1:]
|
|
1522
|
+
|
|
1389
1523
|
if cmds[0].startswith(".") : # override json_output
|
|
1390
1524
|
json_output = True
|
|
1391
1525
|
cmd = cmds[0][1:]
|
|
@@ -1476,6 +1610,10 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1476
1610
|
else:
|
|
1477
1611
|
print("Time set")
|
|
1478
1612
|
|
|
1613
|
+
case "apply_to"|"at":
|
|
1614
|
+
argnum = 2
|
|
1615
|
+
await apply_command_to_contacts(mc, cmds[1], cmds[2])
|
|
1616
|
+
|
|
1479
1617
|
case "set":
|
|
1480
1618
|
argnum = 2
|
|
1481
1619
|
match cmds[1]:
|
|
@@ -1524,6 +1662,10 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1524
1662
|
handle_log_rx.channel_echoes = (cmds[2] == "on")
|
|
1525
1663
|
if json_output :
|
|
1526
1664
|
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1665
|
+
case "echo_unk_chans" :
|
|
1666
|
+
handle_log_rx.echo_unk_chans = (cmds[2] == "on")
|
|
1667
|
+
if json_output :
|
|
1668
|
+
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1527
1669
|
case "print_adverts" :
|
|
1528
1670
|
handle_advert.print_adverts = (cmds[2] == "on")
|
|
1529
1671
|
if json_output :
|
|
@@ -1764,6 +1906,11 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1764
1906
|
print(json.dumps({"channel_echoes" : handle_log_rx.channel_echoes}))
|
|
1765
1907
|
else:
|
|
1766
1908
|
print(f"{'on' if handle_log_rx.channel_echoes else 'off'}")
|
|
1909
|
+
case "echo_unk_chans":
|
|
1910
|
+
if json_output :
|
|
1911
|
+
print(json.dumps({"echo_unk_chans" : handle_log_rx.echo_unk_chans}))
|
|
1912
|
+
else:
|
|
1913
|
+
print(f"{'on' if handle_log_rx.echo_unk_chans else 'off'}")
|
|
1767
1914
|
case "print_adverts":
|
|
1768
1915
|
if json_output :
|
|
1769
1916
|
print(json.dumps({"print_adverts" : handle_advert.print_adverts}))
|
|
@@ -1961,6 +2108,12 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1961
2108
|
if res is None:
|
|
1962
2109
|
print("Error setting channel")
|
|
1963
2110
|
|
|
2111
|
+
case "scope":
|
|
2112
|
+
argnum = 1
|
|
2113
|
+
res = await mc.commands.set_flood_scope(cmds[1])
|
|
2114
|
+
if res is None or res.type == EventType.ERROR:
|
|
2115
|
+
print(f"Error while setting scope")
|
|
2116
|
+
|
|
1964
2117
|
case "remove_channel":
|
|
1965
2118
|
argnum = 1
|
|
1966
2119
|
res = await set_channel(mc, cmds[1], "", bytes.fromhex(16*"00"))
|
|
@@ -2355,6 +2508,13 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2355
2508
|
case "add_pending":
|
|
2356
2509
|
argnum = 1
|
|
2357
2510
|
contact = mc.pop_pending_contact(cmds[1])
|
|
2511
|
+
if contact is None: # try to find by name
|
|
2512
|
+
key = None
|
|
2513
|
+
for c in mc.pending_contacts.items():
|
|
2514
|
+
if c[1]['adv_name'] == cmds[1]:
|
|
2515
|
+
key = c[1]['public_key']
|
|
2516
|
+
contact = mc.pop_pending_contact(key)
|
|
2517
|
+
break
|
|
2358
2518
|
if contact is None:
|
|
2359
2519
|
if json_output:
|
|
2360
2520
|
print(json.dumps({"error":"Contact does not exist"}))
|
|
@@ -2693,7 +2853,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2693
2853
|
await mc.ensure_contacts()
|
|
2694
2854
|
contact = mc.get_contact_by_name(cmds[0])
|
|
2695
2855
|
if contact is None:
|
|
2696
|
-
logger.error(f"Unknown command : {cmd},
|
|
2856
|
+
logger.error(f"Unknown command : {cmd}, {cmds} not executed ...")
|
|
2697
2857
|
return None
|
|
2698
2858
|
|
|
2699
2859
|
await interactive_loop(mc, to=contact)
|
|
@@ -2702,7 +2862,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2702
2862
|
return cmds[argnum+1:]
|
|
2703
2863
|
|
|
2704
2864
|
except IndexError:
|
|
2705
|
-
logger.error("Error in parameters
|
|
2865
|
+
logger.error("Error in parameters")
|
|
2706
2866
|
return None
|
|
2707
2867
|
except EOFError:
|
|
2708
2868
|
logger.error("Cancelled")
|
|
@@ -2737,7 +2897,8 @@ def version():
|
|
|
2737
2897
|
print (f"meshcore-cli: command line interface to MeshCore companion radios {VERSION}")
|
|
2738
2898
|
|
|
2739
2899
|
def command_help():
|
|
2740
|
-
print("""
|
|
2900
|
+
print(""" ?<cmd> may give you some more help about cmd
|
|
2901
|
+
General commands
|
|
2741
2902
|
chat : enter the chat (interactive) mode
|
|
2742
2903
|
chat_to <ct> : enter chat with contact to
|
|
2743
2904
|
script <filename> : execute commands in filename
|
|
@@ -2748,6 +2909,7 @@ def command_help():
|
|
|
2748
2909
|
reboot : reboots node
|
|
2749
2910
|
sleep <secs> : sleeps for a given amount of secs s
|
|
2750
2911
|
wait_key : wait until user presses <Enter> wk
|
|
2912
|
+
apply_to <scope> <cmds>: sends cmds to contacts matching scope at
|
|
2751
2913
|
Messenging
|
|
2752
2914
|
msg <name> <msg> : send message to node by name m {
|
|
2753
2915
|
wait_ack : wait an ack wa }
|
|
@@ -2787,7 +2949,7 @@ def command_help():
|
|
|
2787
2949
|
req_mma <ct> : requests min/max/avg for a sensor rm
|
|
2788
2950
|
req_acl <ct> : requests access control list for sensor
|
|
2789
2951
|
pending_contacts : show pending contacts
|
|
2790
|
-
add_pending <
|
|
2952
|
+
add_pending <pending> : manually add pending contact
|
|
2791
2953
|
flush_pending : flush pending contact list
|
|
2792
2954
|
Repeaters
|
|
2793
2955
|
login <name> <pwd> : log into a node (rep) with given pwd l
|
|
@@ -2822,6 +2984,30 @@ def usage () :
|
|
|
2822
2984
|
Available Commands and shorcuts (can be chained) :""")
|
|
2823
2985
|
command_help()
|
|
2824
2986
|
|
|
2987
|
+
def get_help_for (cmdname, context="line") :
|
|
2988
|
+
if cmdname == "apply_to" or cmdname == "at" :
|
|
2989
|
+
print("""apply_to <scope> <cmd> : applies cmd to contacts matching scope
|
|
2990
|
+
Scope acts like a filter with comma separated fields :
|
|
2991
|
+
- u, matches modification time < or > than a timestamp
|
|
2992
|
+
(can also be days hours or minutes ago if followed by d,h or m)
|
|
2993
|
+
- t, matches the type (1: client, 2: repeater, 3: room, 4: sensor)
|
|
2994
|
+
- h, matches number of hops
|
|
2995
|
+
- d, direct, similar to h>-1
|
|
2996
|
+
- f, flood, similar to h<0 or h=-1
|
|
2997
|
+
|
|
2998
|
+
Note: Some commands like contact_name (aka cn), reset_path (aka rp), forget_password (aka fp) can be chained.
|
|
2999
|
+
|
|
3000
|
+
Examples:
|
|
3001
|
+
# removes all clients that have not been updated in last 2 days
|
|
3002
|
+
at u<2d,t=1 remove_contact
|
|
3003
|
+
# gives traces to repeaters that have been updated in the last 24h and are direct
|
|
3004
|
+
at t=2,u>1d,d cn trace
|
|
3005
|
+
# tries to do flood login to all repeaters
|
|
3006
|
+
at t=2 rp login
|
|
3007
|
+
""")
|
|
3008
|
+
else:
|
|
3009
|
+
print(f"Sorry, no help yet for {cmdname}")
|
|
3010
|
+
|
|
2825
3011
|
async def main(argv):
|
|
2826
3012
|
""" Do the job """
|
|
2827
3013
|
json_output = JSON
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.6
|
|
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
|
|
@@ -10,7 +10,7 @@ License-File: LICENSE
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
|
-
Requires-Dist: meshcore>=2.1.
|
|
13
|
+
Requires-Dist: meshcore>=2.1.20
|
|
14
14
|
Requires-Dist: prompt-toolkit>=3.0.50
|
|
15
15
|
Requires-Dist: pycryptodome
|
|
16
16
|
Requires-Dist: requests>=2.28.0
|
|
@@ -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=HrfCnKDBvC-Lni4X4rWlSuBnadmRuVK2f5Z7HK0jHNo,134323
|
|
4
|
+
meshcore_cli-1.2.6.dist-info/METADATA,sha256=rsIFIQm9Ne8Ft-ax8PCGAnfpjymhCNjxVg5z4CRDF7s,11657
|
|
5
|
+
meshcore_cli-1.2.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
meshcore_cli-1.2.6.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
+
meshcore_cli-1.2.6.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
+
meshcore_cli-1.2.6.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=BJYQj2brE9tpWi-oDAtORCV8UDp9w1gFK3PJsjKJR-g,126909
|
|
4
|
-
meshcore_cli-1.2.4.dist-info/METADATA,sha256=B1yKgcSP5eRjaSlZyMvUHuZv-TkWlHCd2IGaUFOJ_vc,11657
|
|
5
|
-
meshcore_cli-1.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
meshcore_cli-1.2.4.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
-
meshcore_cli-1.2.4.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
-
meshcore_cli-1.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|