meshcore-cli 1.2.0__py3-none-any.whl → 1.2.2__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 +120 -13
- {meshcore_cli-1.2.0.dist-info → meshcore_cli-1.2.2.dist-info}/METADATA +2 -1
- meshcore_cli-1.2.2.dist-info/RECORD +8 -0
- meshcore_cli-1.2.0.dist-info/RECORD +0 -8
- {meshcore_cli-1.2.0.dist-info → meshcore_cli-1.2.2.dist-info}/WHEEL +0 -0
- {meshcore_cli-1.2.0.dist-info → meshcore_cli-1.2.2.dist-info}/entry_points.txt +0 -0
- {meshcore_cli-1.2.0.dist-info → meshcore_cli-1.2.2.dist-info}/licenses/LICENSE +0 -0
meshcore_cli/meshcore_cli.py
CHANGED
|
@@ -24,13 +24,15 @@ from prompt_toolkit.key_binding import KeyBindings
|
|
|
24
24
|
from prompt_toolkit.shortcuts import radiolist_dialog
|
|
25
25
|
from prompt_toolkit.completion.word_completer import WordCompleter
|
|
26
26
|
from prompt_toolkit.document import Document
|
|
27
|
+
from hashlib import sha256
|
|
28
|
+
from Crypto.Cipher import AES
|
|
27
29
|
|
|
28
30
|
import re
|
|
29
31
|
|
|
30
32
|
from meshcore import MeshCore, EventType, logger
|
|
31
33
|
|
|
32
34
|
# Version
|
|
33
|
-
VERSION = "v1.2.
|
|
35
|
+
VERSION = "v1.2.2"
|
|
34
36
|
|
|
35
37
|
# default ble address is stored in a config file
|
|
36
38
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
|
@@ -193,6 +195,41 @@ process_event_message.print_snr=False
|
|
|
193
195
|
process_event_message.color=True
|
|
194
196
|
process_event_message.last_node=None
|
|
195
197
|
|
|
198
|
+
async def handle_log_rx(event):
|
|
199
|
+
mc = handle_log_rx.mc
|
|
200
|
+
if handle_log_rx.json_log_rx: # json mode ... raw dump
|
|
201
|
+
msg = json.dumps(event.payload)
|
|
202
|
+
if handle_message.above:
|
|
203
|
+
print_above(msg)
|
|
204
|
+
else :
|
|
205
|
+
print(msg)
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
pkt = bytes().fromhex(event.payload["payload"])
|
|
209
|
+
|
|
210
|
+
if handle_log_rx.log_channels:
|
|
211
|
+
if pkt[0] == 0x15:
|
|
212
|
+
path_len = pkt[1]
|
|
213
|
+
path = pkt[2:path_len+2].hex()
|
|
214
|
+
chan_hash = pkt[path_len+2:path_len+3].hex()
|
|
215
|
+
cipher_mac = int.from_bytes(pkt[path_len+3:path_len+5], byteorder="little")
|
|
216
|
+
msg = pkt[path_len+5:]
|
|
217
|
+
channel = await get_channel_by_hash(mc, chan_hash)
|
|
218
|
+
if channel is None :
|
|
219
|
+
chan_name = chan_hash
|
|
220
|
+
message = msg.hex()
|
|
221
|
+
else:
|
|
222
|
+
chan_name = channel["channel_name"]
|
|
223
|
+
aes_key = bytes.fromhex(channel["channel_secret"])
|
|
224
|
+
cipher = AES.new(aes_key, AES.MODE_ECB)
|
|
225
|
+
message = cipher.decrypt(msg)[5:].decode("utf-8").strip("\x00")
|
|
226
|
+
|
|
227
|
+
print_above(f"{ANSI_LIGHT_GRAY}{chan_name:>10} {ANSI_GREEN}{message[0:25]:25} {ANSI_LIGHT_GRAY}({event.payload['snr']:6,.2f},{event.payload['rssi']:4}){ANSI_YELLOW} [{path}]{ANSI_END}")
|
|
228
|
+
|
|
229
|
+
handle_log_rx.json_log_rx = False
|
|
230
|
+
handle_log_rx.log_channels = False
|
|
231
|
+
handle_log_rx.mc = None
|
|
232
|
+
|
|
196
233
|
async def handle_advert(event):
|
|
197
234
|
if not handle_advert.print_adverts:
|
|
198
235
|
return
|
|
@@ -320,7 +357,7 @@ class MyNestedCompleter(NestedCompleter):
|
|
|
320
357
|
opts = self.options.keys()
|
|
321
358
|
completer = WordCompleter(
|
|
322
359
|
opts, ignore_case=self.ignore_case,
|
|
323
|
-
pattern=re.compile(r"([a-zA-Z0-9_
|
|
360
|
+
pattern=re.compile(r"([a-zA-Z0-9_\\/\#]+|[^a-zA-Z0-9_\s\#]+)"))
|
|
324
361
|
yield from completer.get_completions(document, complete_event)
|
|
325
362
|
else: # normal behavior for remainder
|
|
326
363
|
yield from super().get_completions(document, complete_event)
|
|
@@ -417,6 +454,8 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
417
454
|
"color" : {"on":None, "off":None},
|
|
418
455
|
"print_name" : {"on":None, "off":None},
|
|
419
456
|
"print_adverts" : {"on":None, "off":None},
|
|
457
|
+
"json_log_rx" : {"on":None, "off":None},
|
|
458
|
+
"log_channels" : {"on":None, "off":None},
|
|
420
459
|
"print_new_contacts" : {"on": None, "off":None},
|
|
421
460
|
"print_path_updates" : {"on":None,"off":None},
|
|
422
461
|
"classic_prompt" : {"on" : None, "off":None},
|
|
@@ -444,6 +483,8 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
444
483
|
"color":None,
|
|
445
484
|
"print_name":None,
|
|
446
485
|
"print_adverts":None,
|
|
486
|
+
"json_log_rx":None,
|
|
487
|
+
"log_channels":None,
|
|
447
488
|
"print_path_updates":None,
|
|
448
489
|
"print_new_contacts":None,
|
|
449
490
|
"classic_prompt":None,
|
|
@@ -604,6 +645,14 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
604
645
|
|
|
605
646
|
completion_list.update(slash_contacts_completion_list)
|
|
606
647
|
|
|
648
|
+
slash_chan_completion_list = {}
|
|
649
|
+
if not channels is None:
|
|
650
|
+
for c in channels :
|
|
651
|
+
if c["channel_name"] != "":
|
|
652
|
+
slash_chan_completion_list["/" + c["channel_name"]] = None
|
|
653
|
+
|
|
654
|
+
completion_list.update(slash_chan_completion_list)
|
|
655
|
+
|
|
607
656
|
completion_list.update({
|
|
608
657
|
"script" : None,
|
|
609
658
|
"quit" : None
|
|
@@ -675,7 +724,7 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
675
724
|
if classic :
|
|
676
725
|
prompt = prompt + " > "
|
|
677
726
|
else :
|
|
678
|
-
prompt = prompt + "
|
|
727
|
+
prompt = prompt + f"{ANSI_NORMAL}🭬{ANSI_INVERT}"
|
|
679
728
|
|
|
680
729
|
if not contact is None :
|
|
681
730
|
if not last_ack:
|
|
@@ -696,7 +745,7 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
696
745
|
prompt = prompt + f"{ANSI_INVERT}"
|
|
697
746
|
|
|
698
747
|
if print_name and not classic :
|
|
699
|
-
prompt = prompt + "
|
|
748
|
+
prompt = prompt + f"{ANSI_NORMAL}🭨{ANSI_INVERT}"
|
|
700
749
|
|
|
701
750
|
prompt = prompt + f"{contact['adv_name']}"
|
|
702
751
|
if classic :
|
|
@@ -734,8 +783,19 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
734
783
|
elif line.startswith("/") :
|
|
735
784
|
path = line.split(" ", 1)[0]
|
|
736
785
|
if path.count("/") == 1:
|
|
737
|
-
args =
|
|
738
|
-
|
|
786
|
+
args = line[1:].split(" ")
|
|
787
|
+
tct = mc.get_contact_by_name(args[0])
|
|
788
|
+
if len(args)>1 and not tct is None: # a contact, send a message
|
|
789
|
+
if tct["type"] == 1 or tct["type"] == 3: # client or room
|
|
790
|
+
last_ack = await msg_ack(mc, tct, line.split(" ", 1)[1])
|
|
791
|
+
else:
|
|
792
|
+
print("Can only send msg to chan, client or room")
|
|
793
|
+
else :
|
|
794
|
+
ch = await get_channel_by_name(mc, args[0])
|
|
795
|
+
if len(args)>1 and not ch is None: # a channel, send message
|
|
796
|
+
await send_chan_msg(mc, ch["channel_idx"], line.split(" ", 1)[1])
|
|
797
|
+
else :
|
|
798
|
+
await process_cmds(mc, shlex.split(line[1:]))
|
|
739
799
|
else:
|
|
740
800
|
cmdline = line[1:].split("/",1)[1]
|
|
741
801
|
contact_name = path[1:].split("/",1)[0]
|
|
@@ -744,10 +804,11 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
744
804
|
print(f"{contact_name} is not a contact")
|
|
745
805
|
else:
|
|
746
806
|
if not await process_contact_chat_line(mc, tct, cmdline):
|
|
747
|
-
if
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
807
|
+
if cmdline != "":
|
|
808
|
+
if tct["type"] == 1:
|
|
809
|
+
last_ack = await msg_ack(mc, tct, cmdline)
|
|
810
|
+
else :
|
|
811
|
+
await process_cmds(mc, ["cmd", tct["adv_name"], cmdline])
|
|
751
812
|
|
|
752
813
|
elif line.startswith("to ") : # dest
|
|
753
814
|
dest = line[3:]
|
|
@@ -805,7 +866,7 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
805
866
|
if ln is None :
|
|
806
867
|
print("No received msg yet !")
|
|
807
868
|
elif ln["type"] == 0 :
|
|
808
|
-
await
|
|
869
|
+
await send_chan_msg(mc, ln["chan_nb"], line[1:])
|
|
809
870
|
else :
|
|
810
871
|
last_ack = await msg_ack(mc, ln, line[1:])
|
|
811
872
|
if last_ack == False :
|
|
@@ -837,7 +898,7 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
837
898
|
last_ack = await msg_ack(mc, contact, line)
|
|
838
899
|
|
|
839
900
|
elif contact["type"] == 0 : # channel, send msg to channel
|
|
840
|
-
await
|
|
901
|
+
await send_chan_msg(mc, contact["chan_nb"], line)
|
|
841
902
|
|
|
842
903
|
elif contact["type"] == 1 : # chat, send to recipient and wait ack
|
|
843
904
|
last_ack = await msg_ack(mc, contact, line)
|
|
@@ -970,7 +1031,18 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
970
1031
|
password_file = ""
|
|
971
1032
|
password = ""
|
|
972
1033
|
if os.path.isdir(MCCLI_CONFIG_DIR) :
|
|
1034
|
+
# if a password file exists with node name open it and destroy it
|
|
973
1035
|
password_file = MCCLI_CONFIG_DIR + contact['adv_name'] + ".pass"
|
|
1036
|
+
if os.path.exists(password_file) :
|
|
1037
|
+
with open(password_file, "r", encoding="utf-8") as f :
|
|
1038
|
+
password=f.readline().strip()
|
|
1039
|
+
os.remove(password_file)
|
|
1040
|
+
password_file = MCCLI_CONFIG_DIR + contact["public_key"] + ".pass"
|
|
1041
|
+
with open(password_file, "w", encoding="utf-8") as f :
|
|
1042
|
+
f.write(password)
|
|
1043
|
+
|
|
1044
|
+
# this is the new correct password file, using pubkey
|
|
1045
|
+
password_file = MCCLI_CONFIG_DIR + contact["public_key"] + ".pass"
|
|
974
1046
|
if os.path.exists(password_file) :
|
|
975
1047
|
with open(password_file, "r", encoding="utf-8") as f :
|
|
976
1048
|
password=f.readline().strip()
|
|
@@ -993,6 +1065,9 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
993
1065
|
|
|
994
1066
|
if line.startswith("forget_password") or line.startswith("fp"):
|
|
995
1067
|
password_file = MCCLI_CONFIG_DIR + contact['adv_name'] + ".pass"
|
|
1068
|
+
if os.path.exists(password_file):
|
|
1069
|
+
os.remove(password_file)
|
|
1070
|
+
password_file = MCCLI_CONFIG_DIR + contact['public_key'] + ".pass"
|
|
996
1071
|
if os.path.exists(password_file):
|
|
997
1072
|
os.remove(password_file)
|
|
998
1073
|
try:
|
|
@@ -1112,6 +1187,7 @@ async def set_channel (mc, chan, name, key=None):
|
|
|
1112
1187
|
return None
|
|
1113
1188
|
|
|
1114
1189
|
info = res.payload
|
|
1190
|
+
info["channel_hash"] = sha256(info["channel_secret"]).digest()[0:1].hex()
|
|
1115
1191
|
info["channel_secret"] = info["channel_secret"].hex()
|
|
1116
1192
|
|
|
1117
1193
|
if hasattr(mc,'channels') :
|
|
@@ -1129,6 +1205,16 @@ async def get_channel_by_name (mc, name):
|
|
|
1129
1205
|
|
|
1130
1206
|
return None
|
|
1131
1207
|
|
|
1208
|
+
async def get_channel_by_hash (mc, hash):
|
|
1209
|
+
if not hasattr(mc, 'channels') :
|
|
1210
|
+
await_get_channels(mc)
|
|
1211
|
+
|
|
1212
|
+
for c in mc.channels:
|
|
1213
|
+
if c['channel_hash'] == hash:
|
|
1214
|
+
return c
|
|
1215
|
+
|
|
1216
|
+
return None
|
|
1217
|
+
|
|
1132
1218
|
async def get_contacts (mc, anim=False, lastomod=0, timeout=5) :
|
|
1133
1219
|
if mc._contacts:
|
|
1134
1220
|
return
|
|
@@ -1200,6 +1286,7 @@ async def get_channels (mc, anim=False) :
|
|
|
1200
1286
|
if res.type == EventType.ERROR:
|
|
1201
1287
|
break
|
|
1202
1288
|
info = res.payload
|
|
1289
|
+
info["channel_hash"] = sha256(info["channel_secret"]).digest()[0:1].hex()
|
|
1203
1290
|
info["channel_secret"] = info["channel_secret"].hex()
|
|
1204
1291
|
mc.channels.append(info)
|
|
1205
1292
|
ch = ch + 1
|
|
@@ -1411,6 +1498,14 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1411
1498
|
process_event_message.print_snr = (cmds[2] == "on")
|
|
1412
1499
|
if json_output :
|
|
1413
1500
|
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1501
|
+
case "json_log_rx" :
|
|
1502
|
+
handle_log_rx.json_log_rx = (cmds[2] == "on")
|
|
1503
|
+
if json_output :
|
|
1504
|
+
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1505
|
+
case "log_channels" :
|
|
1506
|
+
handle_log_rx.log_channels = (cmds[2] == "on")
|
|
1507
|
+
if json_output :
|
|
1508
|
+
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1414
1509
|
case "print_adverts" :
|
|
1415
1510
|
handle_advert.print_adverts = (cmds[2] == "on")
|
|
1416
1511
|
if json_output :
|
|
@@ -1641,6 +1736,16 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1641
1736
|
print(json.dumps({"color" : process_event_message.color}))
|
|
1642
1737
|
else:
|
|
1643
1738
|
print(f"{'on' if process_event_message.color else 'off'}")
|
|
1739
|
+
case "json_log_rx":
|
|
1740
|
+
if json_output :
|
|
1741
|
+
print(json.dumps({"json_log_rx" : handle_log_rx.json_log_rx}))
|
|
1742
|
+
else:
|
|
1743
|
+
print(f"{'on' if handle_log_rx.json_log_rx else 'off'}")
|
|
1744
|
+
case "log_channels":
|
|
1745
|
+
if json_output :
|
|
1746
|
+
print(json.dumps({"log_channels" : handle_log_rx.log_channels}))
|
|
1747
|
+
else:
|
|
1748
|
+
print(f"{'on' if handle_log_rx.log_channels else 'off'}")
|
|
1644
1749
|
case "print_adverts":
|
|
1645
1750
|
if json_output :
|
|
1646
1751
|
print(json.dumps({"print_adverts" : handle_advert.print_adverts}))
|
|
@@ -2843,7 +2948,7 @@ async def main(argv):
|
|
|
2843
2948
|
mc = await MeshCore.create_ble(address=address, device=device, client=client, debug=debug, only_error=json_output, pin=pin)
|
|
2844
2949
|
except ConnectionError :
|
|
2845
2950
|
logger.info("Error while connecting, retrying once ...")
|
|
2846
|
-
if device is None
|
|
2951
|
+
if device is None and client is None: # Search for device
|
|
2847
2952
|
logger.info(f"Scanning BLE for device matching {address}")
|
|
2848
2953
|
devices = await BleakScanner.discover(timeout=timeout)
|
|
2849
2954
|
found = False
|
|
@@ -2881,10 +2986,12 @@ async def main(argv):
|
|
|
2881
2986
|
handle_message.mc = mc # connect meshcore to handle_message
|
|
2882
2987
|
handle_advert.mc = mc
|
|
2883
2988
|
handle_path_update.mc = mc
|
|
2989
|
+
handle_log_rx.mc = mc
|
|
2884
2990
|
|
|
2885
2991
|
mc.subscribe(EventType.ADVERTISEMENT, handle_advert)
|
|
2886
2992
|
mc.subscribe(EventType.PATH_UPDATE, handle_path_update)
|
|
2887
2993
|
mc.subscribe(EventType.NEW_CONTACT, handle_new_contact)
|
|
2994
|
+
mc.subscribe(EventType.RX_LOG_DATA, handle_log_rx)
|
|
2888
2995
|
|
|
2889
2996
|
mc.auto_update_contacts = True
|
|
2890
2997
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
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
|
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
13
|
Requires-Dist: meshcore>=2.1.19
|
|
14
14
|
Requires-Dist: prompt-toolkit>=3.0.50
|
|
15
|
+
Requires-Dist: pycryptodome
|
|
15
16
|
Requires-Dist: requests>=2.28.0
|
|
16
17
|
Description-Content-Type: text/markdown
|
|
17
18
|
|
|
@@ -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=1HmybWFe78tqsbD4zjxHq4aCzTrxd_s7MwtrlKTiQ6c,125901
|
|
4
|
+
meshcore_cli-1.2.2.dist-info/METADATA,sha256=VZygAExPzIUa2rPpHksZQHGxkj1awTiyHuSjpnKOOXk,11657
|
|
5
|
+
meshcore_cli-1.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
meshcore_cli-1.2.2.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
+
meshcore_cli-1.2.2.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
+
meshcore_cli-1.2.2.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=xvBECDCbnHtrK7VAEsQNHcYB1T9k3dQyJiAf_0VSct4,120884
|
|
4
|
-
meshcore_cli-1.2.0.dist-info/METADATA,sha256=OTBsXmevebzoVaxfVclgLgkkXWRYzQr5zySZUCAZd2k,11629
|
|
5
|
-
meshcore_cli-1.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
meshcore_cli-1.2.0.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
-
meshcore_cli-1.2.0.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
-
meshcore_cli-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|