meshcore-cli 1.2.11__py3-none-any.whl → 1.3.0__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 +249 -142
- {meshcore_cli-1.2.11.dist-info → meshcore_cli-1.3.0.dist-info}/METADATA +115 -46
- meshcore_cli-1.3.0.dist-info/RECORD +8 -0
- meshcore_cli-1.2.11.dist-info/RECORD +0 -8
- {meshcore_cli-1.2.11.dist-info → meshcore_cli-1.3.0.dist-info}/WHEEL +0 -0
- {meshcore_cli-1.2.11.dist-info → meshcore_cli-1.3.0.dist-info}/entry_points.txt +0 -0
- {meshcore_cli-1.2.11.dist-info → meshcore_cli-1.3.0.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.0"
|
|
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-?]*[ -/]*[@-~]')
|
|
@@ -205,29 +203,62 @@ process_event_message.print_snr=False
|
|
|
205
203
|
process_event_message.color=True
|
|
206
204
|
process_event_message.last_node=None
|
|
207
205
|
|
|
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
|
+
|
|
208
209
|
async def handle_log_rx(event):
|
|
209
210
|
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
211
|
|
|
218
212
|
pkt = bytes().fromhex(event.payload["payload"])
|
|
219
213
|
pbuf = io.BytesIO(pkt)
|
|
220
214
|
header = pbuf.read(1)[0]
|
|
215
|
+
route_type = header & 0x03
|
|
216
|
+
payload_type = (header & 0x3c) >> 2
|
|
217
|
+
payload_ver = (header & 0xc0) >> 6
|
|
218
|
+
|
|
219
|
+
transport_code = None
|
|
220
|
+
if route_type == 0x00 or route_type == 0x03: # has transport code
|
|
221
|
+
transport_code = pbuf.read(4) # discard transport code
|
|
222
|
+
|
|
223
|
+
path_len = pbuf.read(1)[0]
|
|
224
|
+
path = pbuf.read(path_len).hex() # Beware of traces where pathes are mixed
|
|
225
|
+
|
|
226
|
+
try :
|
|
227
|
+
route_typename = ROUTE_TYPENAMES[route_type]
|
|
228
|
+
except IndexError:
|
|
229
|
+
logger.debug(f"Unknown route type {route_type}")
|
|
230
|
+
route_typename = "UNK"
|
|
231
|
+
|
|
232
|
+
try :
|
|
233
|
+
payload_typename = PAYLOAD_TYPENAMES[payload_type]
|
|
234
|
+
except IndexError:
|
|
235
|
+
logger.debug(f"Unknown payload type {payload_type}")
|
|
236
|
+
payload_typename = "UNK"
|
|
237
|
+
|
|
238
|
+
pkt_payload = pbuf.read()
|
|
239
|
+
|
|
240
|
+
event.payload["header"] = header
|
|
241
|
+
event.payload["route_type"] = route_type
|
|
242
|
+
event.payload["route_typename"] = route_typename
|
|
243
|
+
event.payload["payload_type"] = payload_type
|
|
244
|
+
event.payload["payload_typename"]= payload_typename
|
|
221
245
|
|
|
222
|
-
|
|
246
|
+
event.payload["payload_ver"] = payload_ver
|
|
247
|
+
|
|
248
|
+
if not transport_code is None:
|
|
249
|
+
event.payload["transport_code"] = transport_code.hex()
|
|
250
|
+
|
|
251
|
+
event.payload["path_len"] = path_len
|
|
252
|
+
event.payload["path"] = path
|
|
253
|
+
|
|
254
|
+
event.payload["pkt_payload"] = pkt_payload.hex()
|
|
255
|
+
|
|
256
|
+
if payload_type == 0x05: # flood msg / channel
|
|
223
257
|
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
|
|
258
|
+
pk_buf = io.BytesIO(pkt_payload)
|
|
259
|
+
chan_hash = pk_buf.read(1).hex()
|
|
260
|
+
cipher_mac = pk_buf.read(2)
|
|
261
|
+
msg = pk_buf.read() # until the end of buffer
|
|
231
262
|
|
|
232
263
|
channel = None
|
|
233
264
|
for c in await get_channels(mc):
|
|
@@ -260,6 +291,14 @@ async def handle_log_rx(event):
|
|
|
260
291
|
else:
|
|
261
292
|
print(txt)
|
|
262
293
|
|
|
294
|
+
if handle_log_rx.json_log_rx: # json mode ... raw dump
|
|
295
|
+
msg = json.dumps(event.payload)
|
|
296
|
+
if handle_message.above:
|
|
297
|
+
print_above(msg)
|
|
298
|
+
else :
|
|
299
|
+
print(msg)
|
|
300
|
+
|
|
301
|
+
|
|
263
302
|
handle_log_rx.json_log_rx = False
|
|
264
303
|
handle_log_rx.channel_echoes = False
|
|
265
304
|
handle_log_rx.mc = None
|
|
@@ -470,7 +509,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
470
509
|
"login" : contact_list,
|
|
471
510
|
"cmd" : contact_list,
|
|
472
511
|
"req_status" : contact_list,
|
|
473
|
-
"
|
|
512
|
+
"req_neighbours": contact_list,
|
|
474
513
|
"logout" : contact_list,
|
|
475
514
|
"req_telemetry" : contact_list,
|
|
476
515
|
"req_binary" : contact_list,
|
|
@@ -495,7 +534,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
495
534
|
"print_snr" : {"on":None, "off": None},
|
|
496
535
|
"json_msgs" : {"on":None, "off": None},
|
|
497
536
|
"color" : {"on":None, "off":None},
|
|
498
|
-
"print_name" : {"on":None, "off":None},
|
|
499
537
|
"print_adverts" : {"on":None, "off":None},
|
|
500
538
|
"json_log_rx" : {"on":None, "off":None},
|
|
501
539
|
"channel_echoes" : {"on":None, "off":None},
|
|
@@ -525,7 +563,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
525
563
|
"print_snr":None,
|
|
526
564
|
"json_msgs":None,
|
|
527
565
|
"color":None,
|
|
528
|
-
"print_name":None,
|
|
529
566
|
"print_adverts":None,
|
|
530
567
|
"json_log_rx":None,
|
|
531
568
|
"channel_echoes":None,
|
|
@@ -577,7 +614,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
577
614
|
"login" : None,
|
|
578
615
|
"logout" : None,
|
|
579
616
|
"req_status" : None,
|
|
580
|
-
"
|
|
617
|
+
"req_neighbours": None,
|
|
581
618
|
"cmd" : None,
|
|
582
619
|
"ver" : None,
|
|
583
620
|
"advert" : None,
|
|
@@ -764,26 +801,32 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
764
801
|
|
|
765
802
|
color = process_event_message.color
|
|
766
803
|
classic = interactive_loop.classic or not color
|
|
767
|
-
print_name = interactive_loop.print_name
|
|
768
804
|
|
|
769
805
|
if classic:
|
|
770
806
|
prompt = ""
|
|
771
807
|
else:
|
|
772
808
|
prompt = f"{ANSI_INVERT}"
|
|
773
809
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
810
|
+
prompt = prompt + f"{ANSI_BGRAY}"
|
|
811
|
+
prompt = prompt + f"{mc.self_info['name']}"
|
|
812
|
+
if contact is None: # display scope
|
|
813
|
+
if not scope is None:
|
|
814
|
+
prompt = prompt + f"|{scope}"
|
|
815
|
+
|
|
816
|
+
if contact is None :
|
|
781
817
|
if classic :
|
|
782
|
-
prompt = prompt + ">
|
|
818
|
+
prompt = prompt + ">"
|
|
783
819
|
else :
|
|
784
|
-
prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}
|
|
785
|
-
|
|
786
|
-
|
|
820
|
+
prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}"
|
|
821
|
+
else:
|
|
822
|
+
if classic :
|
|
823
|
+
prompt = prompt + "/"
|
|
824
|
+
else :
|
|
825
|
+
if INVERT_SLASH:
|
|
826
|
+
prompt = prompt + f"{ANSI_INVERT}"
|
|
827
|
+
else:
|
|
828
|
+
prompt = prompt + f"{ANSI_NORMAL}"
|
|
829
|
+
prompt = prompt + f"{SLASH_START}"
|
|
787
830
|
if not last_ack:
|
|
788
831
|
prompt = prompt + f"{ANSI_BRED}"
|
|
789
832
|
if classic :
|
|
@@ -799,11 +842,9 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
799
842
|
else :
|
|
800
843
|
prompt = prompt + f"{ANSI_BBLUE}"
|
|
801
844
|
if not classic:
|
|
845
|
+
prompt = prompt + f"{SLASH_END}"
|
|
802
846
|
prompt = prompt + f"{ANSI_INVERT}"
|
|
803
847
|
|
|
804
|
-
if print_name and not classic :
|
|
805
|
-
prompt = prompt + f"{ANSI_NORMAL}{ARROW_TAIL}{ANSI_INVERT}"
|
|
806
|
-
|
|
807
848
|
prompt = prompt + f"{contact['adv_name']}"
|
|
808
849
|
if contact["type"] == 0 or contact["out_path_len"]==-1:
|
|
809
850
|
if scope is None:
|
|
@@ -817,14 +858,15 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
817
858
|
prompt = prompt + "|" + contact["out_path"]
|
|
818
859
|
|
|
819
860
|
if classic :
|
|
820
|
-
prompt = prompt + f"{ANSI_NORMAL}>
|
|
861
|
+
prompt = prompt + f"{ANSI_NORMAL}>"
|
|
821
862
|
else:
|
|
822
863
|
prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}"
|
|
823
864
|
|
|
824
865
|
prompt = prompt + f"{ANSI_END}"
|
|
825
866
|
|
|
826
|
-
|
|
827
|
-
|
|
867
|
+
prompt = prompt + " "
|
|
868
|
+
if not color :
|
|
869
|
+
prompt=escape_ansi(prompt)
|
|
828
870
|
|
|
829
871
|
session.app.ttimeoutlen = 0.2
|
|
830
872
|
session.app.timeoutlen = 0.2
|
|
@@ -1035,8 +1077,10 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
1035
1077
|
except asyncio.CancelledError:
|
|
1036
1078
|
# Handle task cancellation from KeyboardInterrupt in asyncio.run()
|
|
1037
1079
|
print("Exiting cli")
|
|
1038
|
-
|
|
1039
|
-
interactive_loop.
|
|
1080
|
+
if platform.system() == "Darwin" or platform.system() == "Windows":
|
|
1081
|
+
interactive_loop.classic = True
|
|
1082
|
+
else:
|
|
1083
|
+
interactive_loop.classic = False
|
|
1040
1084
|
|
|
1041
1085
|
async def process_contact_chat_line(mc, contact, line):
|
|
1042
1086
|
if contact["type"] == 0:
|
|
@@ -1068,6 +1112,29 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1068
1112
|
print("")
|
|
1069
1113
|
return True
|
|
1070
1114
|
|
|
1115
|
+
if line.startswith("sleep") or line.startswith("s"):
|
|
1116
|
+
try:
|
|
1117
|
+
sleeptime = int(line.split(" ",2)[1])
|
|
1118
|
+
cmd_pos = 2
|
|
1119
|
+
except IndexError: # nothing arg after sleep
|
|
1120
|
+
sleeptime = 1
|
|
1121
|
+
cmd_pos = 0
|
|
1122
|
+
except ValueError:
|
|
1123
|
+
sleeptime = 1
|
|
1124
|
+
cmd_pos = 1
|
|
1125
|
+
|
|
1126
|
+
try:
|
|
1127
|
+
if cmd_pos > 0:
|
|
1128
|
+
secline = line.split(" ",cmd_pos)[cmd_pos]
|
|
1129
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1130
|
+
except IndexError:
|
|
1131
|
+
pass
|
|
1132
|
+
|
|
1133
|
+
# will sleep after executed command if there is a command
|
|
1134
|
+
await asyncio.sleep(sleeptime)
|
|
1135
|
+
|
|
1136
|
+
return True
|
|
1137
|
+
|
|
1071
1138
|
if line == "contact_lastmod":
|
|
1072
1139
|
timestamp = contact["lastmod"]
|
|
1073
1140
|
print(f"{contact['adv_name']} updated"
|
|
@@ -1083,7 +1150,7 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1083
1150
|
line == "dp" or line == "disc_path" or\
|
|
1084
1151
|
line == "contact_info" or line == "ci" or\
|
|
1085
1152
|
line == "req_status" or line == "rs" or\
|
|
1086
|
-
line == "
|
|
1153
|
+
line == "req_neighbours" or line == "rn" or\
|
|
1087
1154
|
line == "req_telemetry" or line == "rt" or\
|
|
1088
1155
|
line == "req_acl" or\
|
|
1089
1156
|
line == "path" or\
|
|
@@ -1613,7 +1680,7 @@ async def print_disc_trace_to (mc, contact):
|
|
|
1613
1680
|
|
|
1614
1681
|
async def next_cmd(mc, cmds, json_output=False):
|
|
1615
1682
|
""" process next command """
|
|
1616
|
-
global
|
|
1683
|
+
global ARROW_HEAD, SLASH_START, SLASH_END, INVERT_SLASH
|
|
1617
1684
|
try :
|
|
1618
1685
|
argnum = 0
|
|
1619
1686
|
|
|
@@ -1720,37 +1787,25 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1720
1787
|
match cmds[1]:
|
|
1721
1788
|
case "help" :
|
|
1722
1789
|
argnum = 1
|
|
1723
|
-
|
|
1724
|
-
pin <pin> : ble pin
|
|
1725
|
-
radio <freq,bw,sf,cr> : radio params
|
|
1726
|
-
tuning <rx_dly,af> : tuning params
|
|
1727
|
-
tx <dbm> : tx power
|
|
1728
|
-
name <name> : node name
|
|
1729
|
-
lat <lat> : latitude
|
|
1730
|
-
lon <lon> : longitude
|
|
1731
|
-
coords <lat,lon> : coordinates
|
|
1732
|
-
print_snr <on/off> : toggle snr display in messages
|
|
1733
|
-
print_adverts <on/off> : display adverts as they come
|
|
1734
|
-
print_new_contacts <on/off> : display new pending contacts when available
|
|
1735
|
-
print_path_updates <on/off> : display path updates as they come""")
|
|
1790
|
+
get_help_for("set")
|
|
1736
1791
|
case "max_flood_attempts":
|
|
1737
1792
|
msg_ack.max_flood_attempts=int(cmds[2])
|
|
1738
1793
|
case "max_attempts":
|
|
1739
1794
|
msg_ack.max_attempts=int(cmds[2])
|
|
1740
1795
|
case "flood_after":
|
|
1741
1796
|
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
1797
|
case "classic_prompt":
|
|
1747
1798
|
interactive_loop.classic = (cmds[2] == "on")
|
|
1748
1799
|
if json_output :
|
|
1749
1800
|
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1750
|
-
case "arrow_tail":
|
|
1751
|
-
ARROW_TAIL = cmds[2]
|
|
1752
1801
|
case "arrow_head":
|
|
1753
1802
|
ARROW_HEAD = cmds[2]
|
|
1803
|
+
case "slash_start":
|
|
1804
|
+
SLASH_START = cmds[2]
|
|
1805
|
+
case "slash_end":
|
|
1806
|
+
SLASH_END = cmds[2]
|
|
1807
|
+
case "invert_slash":
|
|
1808
|
+
INVERT_SLASH = cmds[2] == "on"
|
|
1754
1809
|
case "color" :
|
|
1755
1810
|
process_event_message.color = (cmds[2] == "on")
|
|
1756
1811
|
if json_output :
|
|
@@ -1956,21 +2011,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1956
2011
|
argnum = 1
|
|
1957
2012
|
match cmds[1]:
|
|
1958
2013
|
case "help":
|
|
1959
|
-
|
|
1960
|
-
name : node name
|
|
1961
|
-
bat : battery level in mV
|
|
1962
|
-
fstats : fs statistics
|
|
1963
|
-
coords : adv coordinates
|
|
1964
|
-
lat : latitude
|
|
1965
|
-
lon : longitude
|
|
1966
|
-
radio : radio parameters
|
|
1967
|
-
tx : tx power
|
|
1968
|
-
print_snr : snr display in messages
|
|
1969
|
-
print_adverts : display adverts as they come
|
|
1970
|
-
print_new_contacts : display new pending contacts when available
|
|
1971
|
-
print_path_updates : display path updates as they come
|
|
1972
|
-
custom : all custom variables in json format
|
|
1973
|
-
each custom var can also be get/set directly""")
|
|
2014
|
+
get_help_for("get")
|
|
1974
2015
|
case "max_flood_attempts":
|
|
1975
2016
|
if json_output :
|
|
1976
2017
|
print(json.dumps({"max_flood_attempts" : msg_ack.max_flood_attempts}))
|
|
@@ -1981,11 +2022,6 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1981
2022
|
print(json.dumps({"flood_after" : msg_ack.flood_after}))
|
|
1982
2023
|
else:
|
|
1983
2024
|
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
2025
|
case "classic_prompt":
|
|
1990
2026
|
if json_output :
|
|
1991
2027
|
print(json.dumps({"classic_prompt" : interactive_loop.classic}))
|
|
@@ -2342,12 +2378,12 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2342
2378
|
else :
|
|
2343
2379
|
color = process_event_message.color
|
|
2344
2380
|
classic = interactive_loop.classic or not color
|
|
2345
|
-
print("
|
|
2381
|
+
print(" ", end="")
|
|
2346
2382
|
for t in ev.payload["path"]:
|
|
2347
2383
|
if classic :
|
|
2348
2384
|
print("→",end="")
|
|
2349
2385
|
else:
|
|
2350
|
-
print(f"
|
|
2386
|
+
print(f"{ANSI_INVERT}", end="")
|
|
2351
2387
|
snr = t['snr']
|
|
2352
2388
|
if color:
|
|
2353
2389
|
if snr >= 10 :
|
|
@@ -2366,7 +2402,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2366
2402
|
if "hash" in t:
|
|
2367
2403
|
print(f"[{t['hash']}]",end="")
|
|
2368
2404
|
else:
|
|
2369
|
-
print(
|
|
2405
|
+
print()
|
|
2370
2406
|
|
|
2371
2407
|
case "login" | "l" :
|
|
2372
2408
|
argnum = 2
|
|
@@ -2426,46 +2462,6 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2426
2462
|
contact = mc.get_contact_by_name(cmds[1])
|
|
2427
2463
|
contact["timeout"] = float(cmds[2])
|
|
2428
2464
|
|
|
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
2465
|
case "disc_path" | "dp" :
|
|
2470
2466
|
argnum = 1
|
|
2471
2467
|
await mc.ensure_contacts()
|
|
@@ -2551,7 +2547,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2551
2547
|
|
|
2552
2548
|
print(f" {name:16} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
|
|
2553
2549
|
|
|
2554
|
-
case "
|
|
2550
|
+
case "req_telemetry"|"rt" :
|
|
2555
2551
|
argnum = 1
|
|
2556
2552
|
await mc.ensure_contacts()
|
|
2557
2553
|
contact = mc.get_contact_by_name(cmds[1])
|
|
@@ -2563,9 +2559,13 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2563
2559
|
else:
|
|
2564
2560
|
print("Error getting data")
|
|
2565
2561
|
else :
|
|
2566
|
-
print(json.dumps(
|
|
2562
|
+
print(json.dumps({
|
|
2563
|
+
"name": contact["adv_name"],
|
|
2564
|
+
"pubkey_pre": contact["public_key"][0:12],
|
|
2565
|
+
"lpp": res,
|
|
2566
|
+
}, indent = 4))
|
|
2567
2567
|
|
|
2568
|
-
case "
|
|
2568
|
+
case "req_status"|"rs" :
|
|
2569
2569
|
argnum = 1
|
|
2570
2570
|
await mc.ensure_contacts()
|
|
2571
2571
|
contact = mc.get_contact_by_name(cmds[1])
|
|
@@ -2634,6 +2634,46 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2634
2634
|
name = f"{ct['adv_name']:<20} [{e['key']}]"
|
|
2635
2635
|
print(f"{name:{' '}<35}: {e['perm']:02x}")
|
|
2636
2636
|
|
|
2637
|
+
case "req_neighbours"|"rn" :
|
|
2638
|
+
argnum = 1
|
|
2639
|
+
await mc.ensure_contacts()
|
|
2640
|
+
contact = mc.get_contact_by_name(cmds[1])
|
|
2641
|
+
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2642
|
+
res = await mc.commands.fetch_all_neighbours(contact, timeout=timeout)
|
|
2643
|
+
if res is None :
|
|
2644
|
+
if json_output :
|
|
2645
|
+
print(json.dumps({"error" : "Getting data"}))
|
|
2646
|
+
else:
|
|
2647
|
+
print("Error getting data")
|
|
2648
|
+
else :
|
|
2649
|
+
if json_output:
|
|
2650
|
+
print(json.dumps(res, indent=4))
|
|
2651
|
+
else:
|
|
2652
|
+
width = os.get_terminal_size().columns
|
|
2653
|
+
print(f"Got {res['results_count']} neighbours out of {res['neighbours_count']} from {contact['adv_name']}:")
|
|
2654
|
+
for n in res['neighbours']:
|
|
2655
|
+
ct = mc.get_contact_by_key_prefix(n["pubkey"])
|
|
2656
|
+
if ct and width > 60 :
|
|
2657
|
+
name = f"[{n['pubkey'][0:8]}] {ct['adv_name']}"
|
|
2658
|
+
name = f"{name:30}"
|
|
2659
|
+
elif ct :
|
|
2660
|
+
name = f"{ct['adv_name']}"
|
|
2661
|
+
name = f"{name:20}"
|
|
2662
|
+
else:
|
|
2663
|
+
name = f"[{n['pubkey']}]"
|
|
2664
|
+
|
|
2665
|
+
t_s = n['secs_ago']
|
|
2666
|
+
time_ago = f"{t_s}s"
|
|
2667
|
+
if t_s / 86400 >= 1 : # result in days
|
|
2668
|
+
time_ago = f"{int(t_s/86400)}d ago{f' ({time_ago})' if width > 62 else ''}"
|
|
2669
|
+
elif t_s / 3600 >= 1 : # result in days
|
|
2670
|
+
time_ago = f"{int(t_s/3600)}h ago{f' ({time_ago})' if width > 62 else ''}"
|
|
2671
|
+
elif t_s / 60 >= 1 : # result in min
|
|
2672
|
+
time_ago = f"{int(t_s/60)}m ago{f' ({time_ago})' if width > 62 else ''}"
|
|
2673
|
+
|
|
2674
|
+
|
|
2675
|
+
print(f" {name} {time_ago}, {n['snr']}dB{' SNR' if width > 66 else ''}")
|
|
2676
|
+
|
|
2637
2677
|
case "req_binary" :
|
|
2638
2678
|
argnum = 2
|
|
2639
2679
|
await mc.ensure_contacts()
|
|
@@ -3082,8 +3122,8 @@ def command_help():
|
|
|
3082
3122
|
reboot : reboots node
|
|
3083
3123
|
sleep <secs> : sleeps for a given amount of secs s
|
|
3084
3124
|
wait_key : wait until user presses <Enter> wk
|
|
3085
|
-
apply_to <
|
|
3086
|
-
|
|
3125
|
+
apply_to <f> <cmds> : sends cmds to contacts matching f at
|
|
3126
|
+
Messaging
|
|
3087
3127
|
msg <name> <msg> : send message to node by name m {
|
|
3088
3128
|
wait_ack : wait an ack wa }
|
|
3089
3129
|
chan <nb> <msg> : send message to channel number <nb> ch
|
|
@@ -3096,6 +3136,7 @@ def command_help():
|
|
|
3096
3136
|
get_channel <n> : get info for channel (by number or name)
|
|
3097
3137
|
set_channel n nm k : set channel info (nb, name, key)
|
|
3098
3138
|
remove_channel <n> : remove channel (by number or name)
|
|
3139
|
+
scope <s> : sets scope for flood messages
|
|
3099
3140
|
Management
|
|
3100
3141
|
advert : sends advert a
|
|
3101
3142
|
floodadv : flood advert
|
|
@@ -3131,6 +3172,7 @@ def command_help():
|
|
|
3131
3172
|
cmd <name> <cmd> : sends a command to a repeater (no ack) c [
|
|
3132
3173
|
wmt8 : wait for a msg (reply) with a timeout ]
|
|
3133
3174
|
req_status <name> : requests status from a node rs
|
|
3175
|
+
req_neighbours <name> : requests for neighbours in binary form rn
|
|
3134
3176
|
trace <path> : run a trace, path is comma separated""")
|
|
3135
3177
|
|
|
3136
3178
|
def usage () :
|
|
@@ -3146,7 +3188,6 @@ def usage () :
|
|
|
3146
3188
|
-D : debug
|
|
3147
3189
|
-S : scan for devices and show a selector
|
|
3148
3190
|
-l : list available ble/serial devices and exit
|
|
3149
|
-
-c <on/off> : disables most of color output if off
|
|
3150
3191
|
-T <timeout> : timeout for the ble scan (-S and -l) default 2s
|
|
3151
3192
|
-a <address> : specifies device address (can be a name)
|
|
3152
3193
|
-d <name> : filter meshcore devices with name or address
|
|
@@ -3155,14 +3196,16 @@ def usage () :
|
|
|
3155
3196
|
-p <port> : specifies tcp port (default 5000)
|
|
3156
3197
|
-s <port> : use serial port <port>
|
|
3157
3198
|
-b <baudrate> : specify baudrate
|
|
3199
|
+
-C : toggles classic mode for prompt
|
|
3200
|
+
-c <on/off> : disables most of color output if off
|
|
3158
3201
|
|
|
3159
3202
|
Available Commands and shorcuts (can be chained) :""")
|
|
3160
3203
|
command_help()
|
|
3161
3204
|
|
|
3162
3205
|
def get_help_for (cmdname, context="line") :
|
|
3163
3206
|
if cmdname == "apply_to" or cmdname == "at" :
|
|
3164
|
-
print("""apply_to <
|
|
3165
|
-
|
|
3207
|
+
print("""apply_to <f> <cmd> : applies cmd to contacts matching filter <f>
|
|
3208
|
+
Filter is constructed with comma separated fields :
|
|
3166
3209
|
- u, matches modification time < or > than a timestamp
|
|
3167
3210
|
(can also be days hours or minutes ago if followed by d,h or m)
|
|
3168
3211
|
- t, matches the type (1: client, 2: repeater, 3: room, 4: sensor)
|
|
@@ -3170,7 +3213,7 @@ def get_help_for (cmdname, context="line") :
|
|
|
3170
3213
|
- d, direct, similar to h>-1
|
|
3171
3214
|
- f, flood, similar to h<0 or h=-1
|
|
3172
3215
|
|
|
3173
|
-
Note: Some commands like contact_name (aka cn), reset_path (aka rp), forget_password (aka fp) can be chained.
|
|
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 ...
|
|
3174
3217
|
|
|
3175
3218
|
Examples:
|
|
3176
3219
|
# removes all clients that have not been updated in last 2 days
|
|
@@ -3181,7 +3224,7 @@ def get_help_for (cmdname, context="line") :
|
|
|
3181
3224
|
at t=2 rp login
|
|
3182
3225
|
""")
|
|
3183
3226
|
|
|
3184
|
-
|
|
3227
|
+
elif cmdname == "node_discover" or cmdname == "nd" :
|
|
3185
3228
|
print("""node_discover <filter> : discovers 0-hop nodes and displays signal info
|
|
3186
3229
|
|
|
3187
3230
|
filter can be "all" for all types or nodes or a comma separated list consisting of :
|
|
@@ -3193,6 +3236,68 @@ def get_help_for (cmdname, context="line") :
|
|
|
3193
3236
|
nd can be used with no filter parameter ... !!! BEWARE WITH CHAINING !!!
|
|
3194
3237
|
""")
|
|
3195
3238
|
|
|
3239
|
+
elif cmdname == "get" :
|
|
3240
|
+
print("""Gets parameters from node
|
|
3241
|
+
Please see also help for set command, which is more up to date ...
|
|
3242
|
+
name : node name
|
|
3243
|
+
bat : battery level in mV
|
|
3244
|
+
fstats : fs statistics
|
|
3245
|
+
coords : adv coordinates
|
|
3246
|
+
lat : latitude
|
|
3247
|
+
lon : longitude
|
|
3248
|
+
radio : radio parameters
|
|
3249
|
+
tx : tx power
|
|
3250
|
+
print_snr : snr display in messages
|
|
3251
|
+
print_adverts : display adverts as they come
|
|
3252
|
+
print_new_contacts : display new pending contacts when available
|
|
3253
|
+
print_path_updates : display path updates as they come
|
|
3254
|
+
custom : all custom variables in json format
|
|
3255
|
+
each custom var can also be get/set directly""")
|
|
3256
|
+
|
|
3257
|
+
elif cmdname == "set" :
|
|
3258
|
+
print("""Available parameters :
|
|
3259
|
+
device:
|
|
3260
|
+
pin <pin> : ble pin
|
|
3261
|
+
radio <freq,bw,sf,cr> : radio params
|
|
3262
|
+
tuning <rx_dly,af> : tuning params
|
|
3263
|
+
tx <dbm> : tx power
|
|
3264
|
+
name <name> : node name
|
|
3265
|
+
lat <lat> : latitude
|
|
3266
|
+
lon <lon> : longitude
|
|
3267
|
+
coords <lat,lon> : coordinates
|
|
3268
|
+
auto_update_contacts <> : automatically updates contact list
|
|
3269
|
+
multi_ack <on/off> : multi-acks feature
|
|
3270
|
+
telemetry_mode_base <mode> : set basic telemetry mode all/selected/off
|
|
3271
|
+
telemetry_mode_loc <mode> : set location telemetry mode all/selected/off
|
|
3272
|
+
telemetry_mode_env <mode> : set env telemetry mode all/selected/off
|
|
3273
|
+
advert_loc_policy <policy> : "share" means loc will be shared in adv
|
|
3274
|
+
display:
|
|
3275
|
+
print_snr <on/off> : toggle snr display in messages
|
|
3276
|
+
print_adverts <on/off> : display adverts as they come
|
|
3277
|
+
print_new_contacts <on/off> : display new pending contacts when available
|
|
3278
|
+
print_path_updates <on/off> : display path updates as they come
|
|
3279
|
+
json_log_rx <on/off> : logs packets incoming to device as json
|
|
3280
|
+
channel_echoes <on/off> : print repeats for channel data
|
|
3281
|
+
echo_unk_channels <on/off> : also dump unk channels (encrypted)
|
|
3282
|
+
color <on/off> : color off should remove ANSI codes from output
|
|
3283
|
+
prompt:
|
|
3284
|
+
classic_prompt <on/off> : activates less fancier prompt
|
|
3285
|
+
arrow_head <string> : change arrow head in prompt
|
|
3286
|
+
slash_start <string> : idem for slash start
|
|
3287
|
+
slash_end <string> : slash end
|
|
3288
|
+
invert_slash <on/off> : apply color inversion to slash """)
|
|
3289
|
+
|
|
3290
|
+
elif cmdname == "scope":
|
|
3291
|
+
print("""scope <scope> : changes flood scope of the node
|
|
3292
|
+
|
|
3293
|
+
The scope command can be used from command line or interactive mode to set the region in which flood packets will be transmitted.
|
|
3294
|
+
|
|
3295
|
+
Managing Flood Scope in interactive mode
|
|
3296
|
+
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
|
+
When entering chat mode, scope will be reset to *, meaning classic flood.
|
|
3298
|
+
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.""")
|
|
3300
|
+
|
|
3196
3301
|
else:
|
|
3197
3302
|
print(f"Sorry, no help yet for {cmdname}")
|
|
3198
3303
|
|
|
@@ -3214,12 +3319,14 @@ async def main(argv):
|
|
|
3214
3319
|
with open(MCCLI_ADDRESS, encoding="utf-8") as f :
|
|
3215
3320
|
address = f.readline().strip()
|
|
3216
3321
|
|
|
3217
|
-
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:")
|
|
3322
|
+
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:C")
|
|
3218
3323
|
for opt, arg in opts :
|
|
3219
3324
|
match opt:
|
|
3220
3325
|
case "-c" :
|
|
3221
3326
|
if arg == "off":
|
|
3222
3327
|
process_event_message.color = False
|
|
3328
|
+
case "-C":
|
|
3329
|
+
interactive_loop.classic = not interactive_loop.classic
|
|
3223
3330
|
case "-d" : # name specified on cmdline
|
|
3224
3331
|
address = arg
|
|
3225
3332
|
case "-a" : # address specified on cmdline
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
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
|
|
@@ -58,22 +58,25 @@ Init files can also be defined for a given device, meshcore-cli will look for `&
|
|
|
58
58
|
|
|
59
59
|
### Arguments
|
|
60
60
|
|
|
61
|
-
Arguments mostly deals with
|
|
61
|
+
Arguments mostly deals with connection to the node
|
|
62
62
|
|
|
63
63
|
<pre>
|
|
64
64
|
-h : prints this help
|
|
65
65
|
-v : prints version
|
|
66
66
|
-j : json output (disables init file)
|
|
67
67
|
-D : debug
|
|
68
|
-
-S :
|
|
69
|
-
-l : list available ble devices and exit
|
|
70
|
-
-T <timeout
|
|
71
|
-
-a <address
|
|
72
|
-
-d <name
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
68
|
+
-S : scan for devices and show a selector
|
|
69
|
+
-l : list available ble/serial devices and exit
|
|
70
|
+
-T <timeout> : timeout for the ble scan (-S and -l) default 2s
|
|
71
|
+
-a <address> : specifies device address (can be a name)
|
|
72
|
+
-d <name> : filter meshcore devices with name or address
|
|
73
|
+
-P : forces pairing via the OS
|
|
74
|
+
-t <hostname> : connects via tcp/ip
|
|
75
|
+
-p <port> : specifies tcp port (default 5000)
|
|
76
|
+
-s <port> : use serial port <port>
|
|
77
|
+
-b <baudrate> : specify baudrate
|
|
78
|
+
-C : toggles classic mode for prompt
|
|
79
|
+
-c <on/off> : disables most of color output if off
|
|
77
80
|
</pre>
|
|
78
81
|
|
|
79
82
|
### Available Commands
|
|
@@ -81,60 +84,70 @@ Arguments mostly deals with ble connection
|
|
|
81
84
|
Commands are given after arguments, they can be chained and some have shortcuts. Also prefixing a command with a dot `.` will force it to output json instead of synthetic result.
|
|
82
85
|
|
|
83
86
|
<pre>
|
|
87
|
+
?<cmd> may give you some more help about cmd
|
|
84
88
|
General commands
|
|
85
89
|
chat : enter the chat (interactive) mode
|
|
86
|
-
chat_to <ct
|
|
87
|
-
script <filename
|
|
90
|
+
chat_to <ct> : enter chat with contact to
|
|
91
|
+
script <filename> : execute commands in filename
|
|
88
92
|
infos : print informations about the node i
|
|
89
93
|
self_telemetry : print own telemtry t
|
|
90
94
|
card : export this node URI e
|
|
91
95
|
ver : firmware version v
|
|
92
96
|
reboot : reboots node
|
|
93
|
-
sleep <secs
|
|
94
|
-
wait_key : wait until user presses <Enter
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
sleep <secs> : sleeps for a given amount of secs s
|
|
98
|
+
wait_key : wait until user presses <Enter> wk
|
|
99
|
+
apply_to <f> <cmds> : sends cmds to contacts matching f at
|
|
100
|
+
Messaging
|
|
101
|
+
msg <name> <msg> : send message to node by name m {
|
|
97
102
|
wait_ack : wait an ack wa }
|
|
98
|
-
chan <nb
|
|
99
|
-
public <msg
|
|
103
|
+
chan <nb> <msg> : send message to channel number <nb> ch
|
|
104
|
+
public <msg> : send message to public channel (0) dch
|
|
100
105
|
recv : reads next msg r
|
|
101
106
|
wait_msg : wait for a message and read it wm
|
|
102
107
|
sync_msgs : gets all unread msgs from the node sm
|
|
103
108
|
msgs_subscribe : display msgs as they arrive ms
|
|
104
|
-
|
|
109
|
+
get_channels : prints all channel info
|
|
110
|
+
get_channel <n> : get info for channel (by number or name)
|
|
105
111
|
set_channel n nm k : set channel info (nb, name, key)
|
|
112
|
+
remove_channel <n> : remove channel (by number or name)
|
|
113
|
+
scope <s> : sets node's flood scope
|
|
106
114
|
Management
|
|
107
115
|
advert : sends advert a
|
|
108
116
|
floodadv : flood advert
|
|
109
|
-
get <param
|
|
110
|
-
set <param
|
|
111
|
-
time <epoch
|
|
117
|
+
get <param> : gets a param, \"get help\" for more
|
|
118
|
+
set <param> <value> : sets a param, \"set help\" for more
|
|
119
|
+
time <epoch> : sets time to given epoch
|
|
112
120
|
clock : get current time
|
|
113
121
|
clock sync : sync device clock st
|
|
122
|
+
node_discover <filter> : discovers nodes based on their type nd
|
|
114
123
|
Contacts
|
|
115
124
|
contacts / list : gets contact list lc
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
reload_contacts : force reloading all contacts rc
|
|
126
|
+
contact_info <ct> : prints information for contact ct ci
|
|
127
|
+
contact_timeout <ct> v : sets temp default timeout for contact
|
|
128
|
+
share_contact <ct> : share a contact with others sc
|
|
129
|
+
export_contact <ct> : get a contact's URI ec
|
|
130
|
+
import_contact <URI> : import a contact from its URI ic
|
|
131
|
+
remove_contact <ct> : removes a contact from this node
|
|
132
|
+
path <ct> : diplays path for a contact
|
|
133
|
+
disc_path <ct> : discover new path and display dp
|
|
134
|
+
reset_path <ct> : resets path to a contact to flood rp
|
|
135
|
+
change_path <ct> <pth> : change the path to a contact cp
|
|
136
|
+
change_flags <ct> <f> : change contact flags (tel_l|tel_a|star)cf
|
|
137
|
+
req_telemetry <ct> : prints telemetry data as json rt
|
|
138
|
+
req_mma <ct> : requests min/max/avg for a sensor rm
|
|
139
|
+
req_acl <ct> : requests access control list for sensor
|
|
129
140
|
pending_contacts : show pending contacts
|
|
130
|
-
add_pending <
|
|
131
|
-
flush_pending : flush pending contact
|
|
141
|
+
add_pending <pending> : manually add pending contact
|
|
142
|
+
flush_pending : flush pending contact list
|
|
132
143
|
Repeaters
|
|
133
|
-
login <name
|
|
134
|
-
logout <name
|
|
135
|
-
cmd <name
|
|
144
|
+
login <name> <pwd> : log into a node (rep) with given pwd l
|
|
145
|
+
logout <name> : log out of a repeater
|
|
146
|
+
cmd <name> <cmd> : sends a command to a repeater (no ack) c [
|
|
136
147
|
wmt8 : wait for a msg (reply) with a timeout ]
|
|
137
|
-
req_status <name
|
|
148
|
+
req_status <name> : requests status from a node rs
|
|
149
|
+
req_neighbours <name> : requests for neighbours in binary form rn
|
|
150
|
+
trace <path> : run a trace, path is comma separated
|
|
138
151
|
</pre>
|
|
139
152
|
|
|
140
153
|
### Interactive Mode
|
|
@@ -147,14 +160,70 @@ You'll get a prompt with the name of your node. From here you can type meshcore-
|
|
|
147
160
|
|
|
148
161
|
The `to` command is specific to chat mode, it lets you enter the recipient for next command. By default you're on your node but you can enter other nodes or public rooms. Here are some examples :
|
|
149
162
|
|
|
150
|
-
- `to <
|
|
163
|
+
- `to <dest>` : will enter dest (node or channel)
|
|
151
164
|
- `to /`, `to ~` : will go to the root (your node)
|
|
152
165
|
- `to ..` : will go to the last node (it will switch between the two last nodes, this is just a 1-depth history)
|
|
153
166
|
- `to !` : will switch to the node you received last message from
|
|
154
167
|
|
|
155
|
-
When you are
|
|
168
|
+
When you are in a node, the behaviour will depend on the node type, if you're on a chat node, it will send messages by default and you can chat. On a repeater or a room server, it will send commands (autocompletion has been set to comply with the CommonCli class of meshcore). To send a message through a room you'll have to prefix the message with a quote or use the send command.
|
|
156
169
|
|
|
157
|
-
|
|
170
|
+
The `/` character is used to bypass the node you have currently selected using `to`:
|
|
171
|
+
- `/<cmd>` issues cmd command on the root
|
|
172
|
+
- `/<node>/<cmd>` will send cmd to selected node
|
|
173
|
+
- `/<dest> <msg>` will send msg to dest (channel or node)
|
|
174
|
+
|
|
175
|
+
#### Flood Scope in interactive mode
|
|
176
|
+
|
|
177
|
+
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.
|
|
178
|
+
|
|
179
|
+
When entering chat mode, scope will be reset to `*`, meaning classic flood.
|
|
180
|
+
|
|
181
|
+
You can switch scope using the `scope` command, or postfixing the `to` command with `%<scope>`.
|
|
182
|
+
|
|
183
|
+
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.
|
|
184
|
+
|
|
185
|
+
#### Channel echoes
|
|
186
|
+
|
|
187
|
+
It's sometimes interesting to know the path taken by a message received from a channel or which repeaters have repeated a sent message.
|
|
188
|
+
|
|
189
|
+
The app give you the information by listening `rx_log` from the device, when obtained the information is attached to the message and can be read.
|
|
190
|
+
|
|
191
|
+
In meshcore-cli I went lower-level by implementing channel echoes. When activated (with `/set channel_echoes on`), all the channel messages will be printed on the terminal along with the SNR and path taken. When sending a message, you'll have all the repeats from 0-hop repeaters as echoes, and when a message is received, you should see information about the received message, but also all the instances of the same message that might have reached you from another path.
|
|
192
|
+
|
|
193
|
+
In the example below, a msg has been sent between two repeaters, 21 and 25. 25 repeated the message and 21 the repeat and both echoes came back to the node with different SNRs.
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
f1down/#fdl|*> 8
|
|
197
|
+
#fdl f1down: 8 [25] -4.75-112
|
|
198
|
+
#fdl f1down: 8 [2521] 1.00-109
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Issuing batch commands to contacts with apply to
|
|
202
|
+
|
|
203
|
+
`apply_to <f> <cmd>` : applies cmd to contacts matching filter `<f>` it can be used to apply the same command to a pool of repeaters, or remove some contacts matching a condition.
|
|
204
|
+
|
|
205
|
+
Filter is constructed with comma separated fields :
|
|
206
|
+
|
|
207
|
+
- `u`, matches modification time `<` or `>` than a timestamp (can also be days hours or minutes ago if followed by `d`,`h` or `m`)
|
|
208
|
+
- `t`, matches the type (1: client, 2: repeater, 3: room, 4: sensor)
|
|
209
|
+
- `h`, matches number of hops
|
|
210
|
+
- `d`, direct, similar to `h>-1`
|
|
211
|
+
- `f`, flood, similar to `h<0` or `h=-1`
|
|
212
|
+
|
|
213
|
+
Commands should be written as if in interactive mode, if writing from the commandline don't forget to use commas to clearly delimit fields.
|
|
214
|
+
|
|
215
|
+
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 time parameter. The sleep will be issued after the command, it helps limiting rate through repeaters ...
|
|
216
|
+
|
|
217
|
+
#### Examples
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
# removes all clients that have not been updated in last 2 days
|
|
221
|
+
at u<2d,t=1 remove_contact
|
|
222
|
+
# gives traces to repeaters that have been updated in the last 24h and are direct
|
|
223
|
+
at t=2,u>1d,d cn trace
|
|
224
|
+
# tries to do flood login to all repeaters
|
|
225
|
+
at t=2 rp login
|
|
226
|
+
```
|
|
158
227
|
|
|
159
228
|
## Examples
|
|
160
229
|
|
|
@@ -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=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,,
|
|
@@ -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=3_pEZET6KLP327yh99zKmRx8a_sfgyx4mVLnT430HP4,142190
|
|
4
|
-
meshcore_cli-1.2.11.dist-info/METADATA,sha256=bQTAr3A4YUZ3igdHJD7SgQp3DAPnN20TIVUlD6IKLS0,11658
|
|
5
|
-
meshcore_cli-1.2.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
meshcore_cli-1.2.11.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
-
meshcore_cli-1.2.11.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
-
meshcore_cli-1.2.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|