meshcore-cli 1.2.11__tar.gz → 1.2.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/PKG-INFO +2 -2
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/pyproject.toml +2 -2
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/src/meshcore_cli/meshcore_cli.py +74 -89
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/.gitignore +0 -0
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/LICENSE +0 -0
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/PXL_20251107_184348511.jpg +0 -0
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/README.md +0 -0
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/flake.lock +0 -0
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/flake.nix +0 -0
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/src/meshcore_cli/__init__.py +0 -0
- {meshcore_cli-1.2.11 → meshcore_cli-1.2.13}/src/meshcore_cli/__main__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.13
|
|
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.24
|
|
14
14
|
Requires-Dist: prompt-toolkit>=3.0.50
|
|
15
15
|
Requires-Dist: pycryptodome
|
|
16
16
|
Requires-Dist: requests>=2.28.0
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "meshcore-cli"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.13"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
|
|
10
10
|
]
|
|
@@ -17,7 +17,7 @@ classifiers = [
|
|
|
17
17
|
]
|
|
18
18
|
license = "MIT"
|
|
19
19
|
license-files = ["LICEN[CS]E*"]
|
|
20
|
-
dependencies = [ "meshcore >= 2.1.
|
|
20
|
+
dependencies = [ "meshcore >= 2.1.24", "prompt_toolkit >= 3.0.50", "requests >= 2.28.0", "pycryptodome" ]
|
|
21
21
|
|
|
22
22
|
[project.urls]
|
|
23
23
|
Homepage = "https://github.com/fdlamotte/meshcore-cli"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
|
-
import os, sys, io
|
|
7
|
+
import os, sys, io
|
|
8
8
|
import time, datetime
|
|
9
9
|
import getopt, json, shlex, re
|
|
10
10
|
import logging
|
|
@@ -32,7 +32,7 @@ import re
|
|
|
32
32
|
from meshcore import MeshCore, EventType, logger
|
|
33
33
|
|
|
34
34
|
# Version
|
|
35
|
-
VERSION = "v1.2.
|
|
35
|
+
VERSION = "v1.2.13"
|
|
36
36
|
|
|
37
37
|
# default ble address is stored in a config file
|
|
38
38
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
|
@@ -76,13 +76,11 @@ ANSI_YELLOW = "\033[0;33m"
|
|
|
76
76
|
ANSI_BYELLOW = "\033[1;33m"
|
|
77
77
|
|
|
78
78
|
#Unicode chars
|
|
79
|
-
# some possible symbols for prompts
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
ARROW_TAIL = ""
|
|
85
|
-
ARROW_HEAD = " "
|
|
79
|
+
# some possible symbols for prompts 🭬🬛🬗🭬🬛🬃🬗🭬🬛🬃🬗🬏🭀🭋🭨🮋
|
|
80
|
+
ARROW_HEAD = ""
|
|
81
|
+
SLASH_END = ""
|
|
82
|
+
SLASH_START = ""
|
|
83
|
+
INVERT_SLASH = False
|
|
86
84
|
|
|
87
85
|
def escape_ansi(line):
|
|
88
86
|
ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
|
|
@@ -470,7 +468,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
470
468
|
"login" : contact_list,
|
|
471
469
|
"cmd" : contact_list,
|
|
472
470
|
"req_status" : contact_list,
|
|
473
|
-
"
|
|
471
|
+
"req_neighbours": contact_list,
|
|
474
472
|
"logout" : contact_list,
|
|
475
473
|
"req_telemetry" : contact_list,
|
|
476
474
|
"req_binary" : contact_list,
|
|
@@ -495,7 +493,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
495
493
|
"print_snr" : {"on":None, "off": None},
|
|
496
494
|
"json_msgs" : {"on":None, "off": None},
|
|
497
495
|
"color" : {"on":None, "off":None},
|
|
498
|
-
"print_name" : {"on":None, "off":None},
|
|
499
496
|
"print_adverts" : {"on":None, "off":None},
|
|
500
497
|
"json_log_rx" : {"on":None, "off":None},
|
|
501
498
|
"channel_echoes" : {"on":None, "off":None},
|
|
@@ -525,7 +522,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
525
522
|
"print_snr":None,
|
|
526
523
|
"json_msgs":None,
|
|
527
524
|
"color":None,
|
|
528
|
-
"print_name":None,
|
|
529
525
|
"print_adverts":None,
|
|
530
526
|
"json_log_rx":None,
|
|
531
527
|
"channel_echoes":None,
|
|
@@ -577,7 +573,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
577
573
|
"login" : None,
|
|
578
574
|
"logout" : None,
|
|
579
575
|
"req_status" : None,
|
|
580
|
-
"
|
|
576
|
+
"req_neighbours": None,
|
|
581
577
|
"cmd" : None,
|
|
582
578
|
"ver" : None,
|
|
583
579
|
"advert" : None,
|
|
@@ -764,26 +760,32 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
764
760
|
|
|
765
761
|
color = process_event_message.color
|
|
766
762
|
classic = interactive_loop.classic or not color
|
|
767
|
-
print_name = interactive_loop.print_name
|
|
768
763
|
|
|
769
764
|
if classic:
|
|
770
765
|
prompt = ""
|
|
771
766
|
else:
|
|
772
767
|
prompt = f"{ANSI_INVERT}"
|
|
773
768
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
769
|
+
prompt = prompt + f"{ANSI_BGRAY}"
|
|
770
|
+
prompt = prompt + f"{mc.self_info['name']}"
|
|
771
|
+
if contact is None: # display scope
|
|
772
|
+
if not scope is None:
|
|
773
|
+
prompt = prompt + f"|{scope}"
|
|
774
|
+
|
|
775
|
+
if contact is None :
|
|
781
776
|
if classic :
|
|
782
|
-
prompt = prompt + ">
|
|
777
|
+
prompt = prompt + ">"
|
|
783
778
|
else :
|
|
784
|
-
prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}
|
|
785
|
-
|
|
786
|
-
|
|
779
|
+
prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}"
|
|
780
|
+
else:
|
|
781
|
+
if classic :
|
|
782
|
+
prompt = prompt + "/"
|
|
783
|
+
else :
|
|
784
|
+
if INVERT_SLASH:
|
|
785
|
+
prompt = prompt + f"{ANSI_INVERT}"
|
|
786
|
+
else:
|
|
787
|
+
prompt = prompt + f"{ANSI_NORMAL}"
|
|
788
|
+
prompt = prompt + f"{SLASH_START}"
|
|
787
789
|
if not last_ack:
|
|
788
790
|
prompt = prompt + f"{ANSI_BRED}"
|
|
789
791
|
if classic :
|
|
@@ -799,11 +801,9 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
799
801
|
else :
|
|
800
802
|
prompt = prompt + f"{ANSI_BBLUE}"
|
|
801
803
|
if not classic:
|
|
804
|
+
prompt = prompt + f"{SLASH_END}"
|
|
802
805
|
prompt = prompt + f"{ANSI_INVERT}"
|
|
803
806
|
|
|
804
|
-
if print_name and not classic :
|
|
805
|
-
prompt = prompt + f"{ANSI_NORMAL}{ARROW_TAIL}{ANSI_INVERT}"
|
|
806
|
-
|
|
807
807
|
prompt = prompt + f"{contact['adv_name']}"
|
|
808
808
|
if contact["type"] == 0 or contact["out_path_len"]==-1:
|
|
809
809
|
if scope is None:
|
|
@@ -817,14 +817,15 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
817
817
|
prompt = prompt + "|" + contact["out_path"]
|
|
818
818
|
|
|
819
819
|
if classic :
|
|
820
|
-
prompt = prompt + f"{ANSI_NORMAL}>
|
|
820
|
+
prompt = prompt + f"{ANSI_NORMAL}>"
|
|
821
821
|
else:
|
|
822
822
|
prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}"
|
|
823
823
|
|
|
824
824
|
prompt = prompt + f"{ANSI_END}"
|
|
825
825
|
|
|
826
|
-
|
|
827
|
-
|
|
826
|
+
prompt = prompt + " "
|
|
827
|
+
if not color :
|
|
828
|
+
prompt=escape_ansi(prompt)
|
|
828
829
|
|
|
829
830
|
session.app.ttimeoutlen = 0.2
|
|
830
831
|
session.app.timeoutlen = 0.2
|
|
@@ -1036,7 +1037,6 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
1036
1037
|
# Handle task cancellation from KeyboardInterrupt in asyncio.run()
|
|
1037
1038
|
print("Exiting cli")
|
|
1038
1039
|
interactive_loop.classic = False
|
|
1039
|
-
interactive_loop.print_name = True
|
|
1040
1040
|
|
|
1041
1041
|
async def process_contact_chat_line(mc, contact, line):
|
|
1042
1042
|
if contact["type"] == 0:
|
|
@@ -1083,7 +1083,7 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1083
1083
|
line == "dp" or line == "disc_path" or\
|
|
1084
1084
|
line == "contact_info" or line == "ci" or\
|
|
1085
1085
|
line == "req_status" or line == "rs" or\
|
|
1086
|
-
line == "
|
|
1086
|
+
line == "req_neighbours" or line == "rn" or\
|
|
1087
1087
|
line == "req_telemetry" or line == "rt" or\
|
|
1088
1088
|
line == "req_acl" or\
|
|
1089
1089
|
line == "path" or\
|
|
@@ -1613,7 +1613,7 @@ async def print_disc_trace_to (mc, contact):
|
|
|
1613
1613
|
|
|
1614
1614
|
async def next_cmd(mc, cmds, json_output=False):
|
|
1615
1615
|
""" process next command """
|
|
1616
|
-
global
|
|
1616
|
+
global ARROW_HEAD, SLASH_START, SLASH_END, INVERT_SLASH
|
|
1617
1617
|
try :
|
|
1618
1618
|
argnum = 0
|
|
1619
1619
|
|
|
@@ -1739,18 +1739,18 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1739
1739
|
msg_ack.max_attempts=int(cmds[2])
|
|
1740
1740
|
case "flood_after":
|
|
1741
1741
|
msg_ack.flood_after=int(cmds[2])
|
|
1742
|
-
case "print_name":
|
|
1743
|
-
interactive_loop.print_name = (cmds[2] == "on")
|
|
1744
|
-
if json_output :
|
|
1745
|
-
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1746
1742
|
case "classic_prompt":
|
|
1747
1743
|
interactive_loop.classic = (cmds[2] == "on")
|
|
1748
1744
|
if json_output :
|
|
1749
1745
|
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1750
|
-
case "arrow_tail":
|
|
1751
|
-
ARROW_TAIL = cmds[2]
|
|
1752
1746
|
case "arrow_head":
|
|
1753
1747
|
ARROW_HEAD = cmds[2]
|
|
1748
|
+
case "slash_start":
|
|
1749
|
+
SLASH_START = cmds[2]
|
|
1750
|
+
case "slash_end":
|
|
1751
|
+
SLASH_END = cmds[2]
|
|
1752
|
+
case "invert_slash":
|
|
1753
|
+
INVERT_SLASH = cmds[2] == "on"
|
|
1754
1754
|
case "color" :
|
|
1755
1755
|
process_event_message.color = (cmds[2] == "on")
|
|
1756
1756
|
if json_output :
|
|
@@ -1981,11 +1981,6 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1981
1981
|
print(json.dumps({"flood_after" : msg_ack.flood_after}))
|
|
1982
1982
|
else:
|
|
1983
1983
|
print(f"flood_after: {msg_ack.flood_after}")
|
|
1984
|
-
case "print_name":
|
|
1985
|
-
if json_output :
|
|
1986
|
-
print(json.dumps({"print_name" : interactive_loop.print_name}))
|
|
1987
|
-
else:
|
|
1988
|
-
print(f"{'on' if interactive_loop.print_name else 'off'}")
|
|
1989
1984
|
case "classic_prompt":
|
|
1990
1985
|
if json_output :
|
|
1991
1986
|
print(json.dumps({"classic_prompt" : interactive_loop.classic}))
|
|
@@ -2360,7 +2355,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2360
2355
|
if classic :
|
|
2361
2356
|
print("→",end="")
|
|
2362
2357
|
else :
|
|
2363
|
-
print(f"{ANSI_NORMAL}{ARROW_HEAD}",end="")
|
|
2358
|
+
print(f"{ANSI_NORMAL}{ARROW_HEAD} ",end="")
|
|
2364
2359
|
if color:
|
|
2365
2360
|
print(ANSI_END, end="")
|
|
2366
2361
|
if "hash" in t:
|
|
@@ -2426,46 +2421,6 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2426
2421
|
contact = mc.get_contact_by_name(cmds[1])
|
|
2427
2422
|
contact["timeout"] = float(cmds[2])
|
|
2428
2423
|
|
|
2429
|
-
case "req_status" | "rs" :
|
|
2430
|
-
argnum = 1
|
|
2431
|
-
await mc.ensure_contacts()
|
|
2432
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2433
|
-
res = await mc.commands.send_statusreq(contact)
|
|
2434
|
-
logger.debug(res)
|
|
2435
|
-
if res.type == EventType.ERROR:
|
|
2436
|
-
print(f"Error while requesting status: {res}")
|
|
2437
|
-
else :
|
|
2438
|
-
timeout = res.payload["suggested_timeout"]/800 if not "timeout" in contact or contact['timeout']==0 else contact["timeout"]
|
|
2439
|
-
res = await mc.wait_for_event(EventType.STATUS_RESPONSE, timeout=timeout)
|
|
2440
|
-
logger.debug(res)
|
|
2441
|
-
if res is None:
|
|
2442
|
-
if json_output :
|
|
2443
|
-
print(json.dumps({"error" : "Timeout waiting status"}))
|
|
2444
|
-
else:
|
|
2445
|
-
print("Timeout waiting status")
|
|
2446
|
-
else :
|
|
2447
|
-
print(json.dumps(res.payload, indent=4))
|
|
2448
|
-
|
|
2449
|
-
case "req_telemetry" | "rt" :
|
|
2450
|
-
argnum = 1
|
|
2451
|
-
await mc.ensure_contacts()
|
|
2452
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2453
|
-
res = await mc.commands.send_telemetry_req(contact)
|
|
2454
|
-
logger.debug(res)
|
|
2455
|
-
if res.type == EventType.ERROR:
|
|
2456
|
-
print(f"Error while requesting telemetry")
|
|
2457
|
-
else:
|
|
2458
|
-
timeout = res.payload["suggested_timeout"]/800 if not "timeout" in contact or contact['timeout']==0 else contact["timeout"]
|
|
2459
|
-
res = await mc.wait_for_event(EventType.TELEMETRY_RESPONSE, timeout=timeout)
|
|
2460
|
-
logger.debug(res)
|
|
2461
|
-
if res is None:
|
|
2462
|
-
if json_output :
|
|
2463
|
-
print(json.dumps({"error" : "Timeout waiting telemetry"}))
|
|
2464
|
-
else:
|
|
2465
|
-
print("Timeout waiting telemetry")
|
|
2466
|
-
else :
|
|
2467
|
-
print(json.dumps(res.payload, indent=4))
|
|
2468
|
-
|
|
2469
2424
|
case "disc_path" | "dp" :
|
|
2470
2425
|
argnum = 1
|
|
2471
2426
|
await mc.ensure_contacts()
|
|
@@ -2551,7 +2506,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2551
2506
|
|
|
2552
2507
|
print(f" {name:16} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
|
|
2553
2508
|
|
|
2554
|
-
case "
|
|
2509
|
+
case "req_telemetry"|"rt" :
|
|
2555
2510
|
argnum = 1
|
|
2556
2511
|
await mc.ensure_contacts()
|
|
2557
2512
|
contact = mc.get_contact_by_name(cmds[1])
|
|
@@ -2563,9 +2518,13 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2563
2518
|
else:
|
|
2564
2519
|
print("Error getting data")
|
|
2565
2520
|
else :
|
|
2566
|
-
print(json.dumps(
|
|
2521
|
+
print(json.dumps({
|
|
2522
|
+
"name": contact["adv_name"],
|
|
2523
|
+
"pubkey_pre": contact["public_key"][0:12],
|
|
2524
|
+
"lpp": res,
|
|
2525
|
+
}, indent = 4))
|
|
2567
2526
|
|
|
2568
|
-
case "
|
|
2527
|
+
case "req_status"|"rs" :
|
|
2569
2528
|
argnum = 1
|
|
2570
2529
|
await mc.ensure_contacts()
|
|
2571
2530
|
contact = mc.get_contact_by_name(cmds[1])
|
|
@@ -2634,6 +2593,31 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2634
2593
|
name = f"{ct['adv_name']:<20} [{e['key']}]"
|
|
2635
2594
|
print(f"{name:{' '}<35}: {e['perm']:02x}")
|
|
2636
2595
|
|
|
2596
|
+
case "req_neighbours"|"rn" :
|
|
2597
|
+
argnum = 1
|
|
2598
|
+
await mc.ensure_contacts()
|
|
2599
|
+
contact = mc.get_contact_by_name(cmds[1])
|
|
2600
|
+
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2601
|
+
res = await mc.commands.fetch_all_neighbours(contact, timeout=timeout)
|
|
2602
|
+
if res is None :
|
|
2603
|
+
if json_output :
|
|
2604
|
+
print(json.dumps({"error" : "Getting data"}))
|
|
2605
|
+
else:
|
|
2606
|
+
print("Error getting data")
|
|
2607
|
+
else :
|
|
2608
|
+
if json_output:
|
|
2609
|
+
print(json.dumps(res, indent=4))
|
|
2610
|
+
else:
|
|
2611
|
+
print(f"Got {res['results_count']} neighbours out of {res['neighbours_count']} from {contact['adv_name']}:")
|
|
2612
|
+
for n in res['neighbours']:
|
|
2613
|
+
ct = mc.get_contact_by_key_prefix(n["pubkey"])
|
|
2614
|
+
if ct :
|
|
2615
|
+
name = f"[{n['pubkey'][0:8]}] {ct['adv_name']}"
|
|
2616
|
+
else:
|
|
2617
|
+
name = f"[{n['pubkey']}]"
|
|
2618
|
+
|
|
2619
|
+
print(f" {name:30} last viewed {n['secs_ago']} sec ago at {n['snr']} ")
|
|
2620
|
+
|
|
2637
2621
|
case "req_binary" :
|
|
2638
2622
|
argnum = 2
|
|
2639
2623
|
await mc.ensure_contacts()
|
|
@@ -3131,6 +3115,7 @@ def command_help():
|
|
|
3131
3115
|
cmd <name> <cmd> : sends a command to a repeater (no ack) c [
|
|
3132
3116
|
wmt8 : wait for a msg (reply) with a timeout ]
|
|
3133
3117
|
req_status <name> : requests status from a node rs
|
|
3118
|
+
req_neighbours <name> : requests for neighbours in binary form rn
|
|
3134
3119
|
trace <path> : run a trace, path is comma separated""")
|
|
3135
3120
|
|
|
3136
3121
|
def usage () :
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|