meshcore-cli 1.3.0__tar.gz → 1.3.4__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.3.0 → meshcore_cli-1.3.4}/PKG-INFO +14 -2
- {meshcore_cli-1.3.0 → meshcore_cli-1.3.4}/README.md +12 -0
- {meshcore_cli-1.3.0 → meshcore_cli-1.3.4}/flake.nix +2 -2
- {meshcore_cli-1.3.0 → meshcore_cli-1.3.4}/pyproject.toml +2 -2
- {meshcore_cli-1.3.0 → meshcore_cli-1.3.4}/src/meshcore_cli/meshcore_cli.py +172 -57
- {meshcore_cli-1.3.0 → meshcore_cli-1.3.4}/.gitignore +0 -0
- {meshcore_cli-1.3.0 → meshcore_cli-1.3.4}/LICENSE +0 -0
- {meshcore_cli-1.3.0 → meshcore_cli-1.3.4}/flake.lock +0 -0
- {meshcore_cli-1.3.0 → meshcore_cli-1.3.4}/src/meshcore_cli/__init__.py +0 -0
- {meshcore_cli-1.3.0 → meshcore_cli-1.3.4}/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.3.
|
|
3
|
+
Version: 1.3.4
|
|
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.2.1
|
|
14
14
|
Requires-Dist: prompt-toolkit>=3.0.50
|
|
15
15
|
Requires-Dist: pycryptodome
|
|
16
16
|
Requires-Dist: requests>=2.28.0
|
|
@@ -198,6 +198,18 @@ f1down/#fdl|*> 8
|
|
|
198
198
|
#fdl f1down: 8 [2521] 1.00-109
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
### Contact management
|
|
202
|
+
|
|
203
|
+
To receive a message from another user, it is necessary to have its public key. This key is stored on a contact list in the device, and this list has a finite size (50 when meshcore started, now over 350 for most devices).
|
|
204
|
+
|
|
205
|
+
By default contacts are automatically added to the device contact list when an advertisement is received, so as soon as you receive an advert, you can talk with your buddy.
|
|
206
|
+
|
|
207
|
+
With growing number of users, it becomes necessary to manage contact list and one of the ways is to add contacts manually to the device. This is done by turning on `manual_add_contacts`. Once this option has been turned on, a pending list is built by meshcore-cli from the received adverts. You can view the list issuing a `pending_contacts` command, flush the list using `flush_pending` or add a contact from the list with `add_pending` followed by the key of the contact or its name (both will be auto-completed with tab).
|
|
208
|
+
|
|
209
|
+
This feature only really works in interactive mode.
|
|
210
|
+
|
|
211
|
+
Note: There is also an `auto_update_contacts` setting that has nothing to do with adding contacts, it permits to automatically sync contact lists between device and meshcore-cli (when there is an update in name, location or path).
|
|
212
|
+
|
|
201
213
|
### Issuing batch commands to contacts with apply to
|
|
202
214
|
|
|
203
215
|
`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.
|
|
@@ -180,6 +180,18 @@ f1down/#fdl|*> 8
|
|
|
180
180
|
#fdl f1down: 8 [2521] 1.00-109
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
+
### Contact management
|
|
184
|
+
|
|
185
|
+
To receive a message from another user, it is necessary to have its public key. This key is stored on a contact list in the device, and this list has a finite size (50 when meshcore started, now over 350 for most devices).
|
|
186
|
+
|
|
187
|
+
By default contacts are automatically added to the device contact list when an advertisement is received, so as soon as you receive an advert, you can talk with your buddy.
|
|
188
|
+
|
|
189
|
+
With growing number of users, it becomes necessary to manage contact list and one of the ways is to add contacts manually to the device. This is done by turning on `manual_add_contacts`. Once this option has been turned on, a pending list is built by meshcore-cli from the received adverts. You can view the list issuing a `pending_contacts` command, flush the list using `flush_pending` or add a contact from the list with `add_pending` followed by the key of the contact or its name (both will be auto-completed with tab).
|
|
190
|
+
|
|
191
|
+
This feature only really works in interactive mode.
|
|
192
|
+
|
|
193
|
+
Note: There is also an `auto_update_contacts` setting that has nothing to do with adding contacts, it permits to automatically sync contact lists between device and meshcore-cli (when there is an update in name, location or path).
|
|
194
|
+
|
|
183
195
|
### Issuing batch commands to contacts with apply to
|
|
184
196
|
|
|
185
197
|
`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.
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
meshcore = python3Packages.buildPythonPackage rec {
|
|
17
17
|
pname = "meshcore";
|
|
18
|
-
version = "2.1
|
|
18
|
+
version = "2.2.1";
|
|
19
19
|
pyproject = true;
|
|
20
20
|
|
|
21
21
|
src = python3Packages.fetchPypi {
|
|
22
22
|
inherit pname version;
|
|
23
|
-
sha256 = "sha256-
|
|
23
|
+
sha256 = "sha256-HpCbGG+ZQdVWIeE3mJFFQ7w5W+JjcNb+Tb53i9uT5CA=";
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
build-system = [python3Packages.hatchling];
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "meshcore-cli"
|
|
7
|
-
version = "1.3.
|
|
7
|
+
version = "1.3.4"
|
|
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.2.1", "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"
|
|
@@ -32,7 +32,7 @@ import re
|
|
|
32
32
|
from meshcore import MeshCore, EventType, logger
|
|
33
33
|
|
|
34
34
|
# Version
|
|
35
|
-
VERSION = "v1.3.
|
|
35
|
+
VERSION = "v1.3.4"
|
|
36
36
|
|
|
37
37
|
# default ble address is stored in a config file
|
|
38
38
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
|
@@ -40,6 +40,10 @@ MCCLI_ADDRESS = MCCLI_CONFIG_DIR + "default_address"
|
|
|
40
40
|
MCCLI_HISTORY_FILE = MCCLI_CONFIG_DIR + "history"
|
|
41
41
|
MCCLI_INIT_SCRIPT = MCCLI_CONFIG_DIR + "init"
|
|
42
42
|
|
|
43
|
+
PAYLOAD_TYPENAMES = ["REQ", "RESPONSE", "TEXT_MSG", "ACK", "ADVERT", "GRP_TXT", "GRP_DATA", "ANON_REQ", "PATH", "TRACE", "MULTIPART", "CONTROL"]
|
|
44
|
+
ROUTE_TYPENAMES = ["TC_FLOOD", "FLOOD", "DIRECT", "TC_DIRECT"]
|
|
45
|
+
CONTACT_TYPENAMES = ["NONE","CLI","REP","ROOM","SENS"]
|
|
46
|
+
|
|
43
47
|
# Fallback address if config file not found
|
|
44
48
|
# if None or "" then a scan is performed
|
|
45
49
|
ADDRESS = ""
|
|
@@ -203,9 +207,6 @@ process_event_message.print_snr=False
|
|
|
203
207
|
process_event_message.color=True
|
|
204
208
|
process_event_message.last_node=None
|
|
205
209
|
|
|
206
|
-
PAYLOAD_TYPENAMES = ["REQ", "RESPONSE", "TEXT_MSG", "ACK", "ADVERT", "GRP_TXT", "GRP_DATA", "ANON_REQ", "PATH", "TRACE", "MULTIPART", "CONTROL"]
|
|
207
|
-
ROUTE_TYPENAMES = ["TC_FLOOD", "FLOOD", "DIRECT", "TC_DIRECT"]
|
|
208
|
-
|
|
209
210
|
async def handle_log_rx(event):
|
|
210
211
|
mc = handle_log_rx.mc
|
|
211
212
|
|
|
@@ -284,7 +285,7 @@ async def handle_log_rx(event):
|
|
|
284
285
|
if chan_name != "" :
|
|
285
286
|
width = os.get_terminal_size().columns
|
|
286
287
|
cars = width - 13 - 2 * path_len - len(chan_name) - 1
|
|
287
|
-
dispmsg = message[0:cars]
|
|
288
|
+
dispmsg = message.replace("\n","")[0:cars]
|
|
288
289
|
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}"
|
|
289
290
|
if handle_message.above:
|
|
290
291
|
print_above(txt)
|
|
@@ -431,7 +432,7 @@ class MyNestedCompleter(NestedCompleter):
|
|
|
431
432
|
opts = self.options.keys()
|
|
432
433
|
completer = WordCompleter(
|
|
433
434
|
opts, ignore_case=self.ignore_case,
|
|
434
|
-
pattern=re.compile(r"([a-zA-Z0-9_
|
|
435
|
+
pattern=re.compile(r"([a-zA-Z0-9_\\/\#\?]+|[^a-zA-Z0-9_\s\#\?]+)"))
|
|
435
436
|
yield from completer.get_completions(document, complete_event)
|
|
436
437
|
else: # normal behavior for remainder
|
|
437
438
|
yield from super().get_completions(document, complete_event)
|
|
@@ -582,11 +583,24 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
582
583
|
"flood_after":None,
|
|
583
584
|
"custom":None,
|
|
584
585
|
},
|
|
586
|
+
"?get":None,
|
|
587
|
+
"?set":None,
|
|
588
|
+
"?scope":None,
|
|
589
|
+
"?contact_info":None,
|
|
590
|
+
"?apply_to":None,
|
|
591
|
+
"?at":None,
|
|
592
|
+
"?node_discover":None,
|
|
593
|
+
"?nd":None,
|
|
594
|
+
"?pending_contacts":None,
|
|
595
|
+
"?add_pending":None,
|
|
596
|
+
"?flush_pending":None,
|
|
585
597
|
}
|
|
586
598
|
|
|
587
599
|
contact_completion_list = {
|
|
588
600
|
"contact_info": None,
|
|
589
601
|
"contact_name": None,
|
|
602
|
+
"contact_key": None,
|
|
603
|
+
"contact_type": None,
|
|
590
604
|
"contact_lastmod": None,
|
|
591
605
|
"export_contact" : None,
|
|
592
606
|
"share_contact" : None,
|
|
@@ -749,9 +763,10 @@ make_completion_dict.custom_vars = {}
|
|
|
749
763
|
async def interactive_loop(mc, to=None) :
|
|
750
764
|
print("""Interactive mode, most commands from terminal chat should work.
|
|
751
765
|
Use \"to\" to select recipient, use Tab to complete name ...
|
|
752
|
-
|
|
766
|
+
Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
753
767
|
\"quit\", \"q\", CTRL+D will end interactive mode""")
|
|
754
768
|
|
|
769
|
+
|
|
755
770
|
contact = to
|
|
756
771
|
prev_contact = None
|
|
757
772
|
|
|
@@ -760,16 +775,16 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
760
775
|
|
|
761
776
|
await get_contacts(mc, anim=True)
|
|
762
777
|
await get_channels(mc, anim=True)
|
|
778
|
+
|
|
779
|
+
# Call sync_msg before going further so there is no issue when scrolling
|
|
780
|
+
# long list of msgs
|
|
781
|
+
await next_cmd(mc, ["sync_msgs"])
|
|
782
|
+
|
|
763
783
|
await subscribe_to_msgs(mc, above=True)
|
|
764
784
|
|
|
765
785
|
handle_new_contact.print_new_contacts = True
|
|
766
786
|
|
|
767
787
|
try:
|
|
768
|
-
while True: # purge msgs
|
|
769
|
-
res = await mc.commands.get_msg()
|
|
770
|
-
if res.type == EventType.NO_MORE_MSGS:
|
|
771
|
-
break
|
|
772
|
-
|
|
773
788
|
if os.path.isdir(MCCLI_CONFIG_DIR) :
|
|
774
789
|
our_history = FileHistory(MCCLI_HISTORY_FILE)
|
|
775
790
|
else:
|
|
@@ -1102,6 +1117,26 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1102
1117
|
await process_cmds(mc, args)
|
|
1103
1118
|
return True
|
|
1104
1119
|
|
|
1120
|
+
if line.startswith("contact_key") or line.startswith("ck"):
|
|
1121
|
+
print(contact['public_key'],end="")
|
|
1122
|
+
if " " in line:
|
|
1123
|
+
print(" ", end="", flush=True)
|
|
1124
|
+
secline = line.split(" ", 1)[1]
|
|
1125
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1126
|
+
else:
|
|
1127
|
+
print("")
|
|
1128
|
+
return True
|
|
1129
|
+
|
|
1130
|
+
if line.startswith("contact_type") or line.startswith("ct"):
|
|
1131
|
+
print(f"{CONTACT_TYPENAMES[contact['type']]:4}",end="")
|
|
1132
|
+
if " " in line:
|
|
1133
|
+
print(" ", end="", flush=True)
|
|
1134
|
+
secline = line.split(" ", 1)[1]
|
|
1135
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1136
|
+
else:
|
|
1137
|
+
print("")
|
|
1138
|
+
return True
|
|
1139
|
+
|
|
1105
1140
|
if line.startswith("contact_name") or line.startswith("cn"):
|
|
1106
1141
|
print(contact['adv_name'],end="")
|
|
1107
1142
|
if " " in line:
|
|
@@ -1112,7 +1147,22 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1112
1147
|
print("")
|
|
1113
1148
|
return True
|
|
1114
1149
|
|
|
1115
|
-
if line.startswith("
|
|
1150
|
+
if line.startswith("path") :
|
|
1151
|
+
if contact['out_path_len'] == -1:
|
|
1152
|
+
print("Flood", end="")
|
|
1153
|
+
elif contact['out_path_len'] == 0:
|
|
1154
|
+
print("0 hop", end="")
|
|
1155
|
+
else:
|
|
1156
|
+
print(contact['out_path'],end="")
|
|
1157
|
+
if " " in line:
|
|
1158
|
+
print(" ", end="", flush=True)
|
|
1159
|
+
secline = line.split(" ", 1)[1]
|
|
1160
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1161
|
+
else:
|
|
1162
|
+
print("")
|
|
1163
|
+
return True
|
|
1164
|
+
|
|
1165
|
+
if line.startswith("sleep ") or line.startswith("s "):
|
|
1116
1166
|
try:
|
|
1117
1167
|
sleeptime = int(line.split(" ",2)[1])
|
|
1118
1168
|
cmd_pos = 2
|
|
@@ -1143,20 +1193,24 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1143
1193
|
return True
|
|
1144
1194
|
|
|
1145
1195
|
# commands that take contact as second arg will be sent to recipient
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
line
|
|
1149
|
-
line
|
|
1150
|
-
line
|
|
1151
|
-
line
|
|
1152
|
-
line
|
|
1153
|
-
line
|
|
1154
|
-
line
|
|
1155
|
-
line
|
|
1156
|
-
line
|
|
1157
|
-
line
|
|
1158
|
-
|
|
1196
|
+
# and can be chained ...
|
|
1197
|
+
if line.startswith("sc") or line.startswith("share_contact") or\
|
|
1198
|
+
line.startswith("ec") or line.startswith("export_contact") or\
|
|
1199
|
+
line.startswith("uc") or line.startswith("upload_contact") or\
|
|
1200
|
+
line.startswith("rp") or line.startswith("reset_path") or\
|
|
1201
|
+
line.startswith("dp") or line.startswith("disc_path") or\
|
|
1202
|
+
line.startswith("contact_info") or line.startswith("ci") or\
|
|
1203
|
+
line.startswith("req_status") or line.startswith("rs") or\
|
|
1204
|
+
line.startswith("req_neighbours") or line.startswith("rn") or\
|
|
1205
|
+
line.startswith("req_telemetry") or line.startswith("rt") or\
|
|
1206
|
+
line.startswith("req_acl") or\
|
|
1207
|
+
line.startswith("path") or\
|
|
1208
|
+
line.startswith("logout") :
|
|
1209
|
+
args = [line.split()[0], contact['adv_name']]
|
|
1159
1210
|
await process_cmds(mc, args)
|
|
1211
|
+
if " " in line:
|
|
1212
|
+
secline = line.split(" ", 1)[1]
|
|
1213
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
1160
1214
|
return True
|
|
1161
1215
|
|
|
1162
1216
|
# special case for rp that can be chained from cmdline
|
|
@@ -1169,6 +1223,8 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1169
1223
|
|
|
1170
1224
|
if line.startswith("set timeout "):
|
|
1171
1225
|
cmds=line.split(" ")
|
|
1226
|
+
#args = ["contact_timeout", contact['adv_name'], cmds[2]]
|
|
1227
|
+
#await process_cmds(mc, args)
|
|
1172
1228
|
contact["timeout"] = float(cmds[2])
|
|
1173
1229
|
return True
|
|
1174
1230
|
|
|
@@ -1306,12 +1362,13 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1306
1362
|
|
|
1307
1363
|
return False
|
|
1308
1364
|
|
|
1309
|
-
async def apply_command_to_contacts(mc, contact_filter, line):
|
|
1365
|
+
async def apply_command_to_contacts(mc, contact_filter, line, json_output=False):
|
|
1310
1366
|
upd_before = None
|
|
1311
1367
|
upd_after = None
|
|
1312
1368
|
contact_type = None
|
|
1313
1369
|
min_hops = None
|
|
1314
1370
|
max_hops = None
|
|
1371
|
+
count = 0
|
|
1315
1372
|
|
|
1316
1373
|
await mc.ensure_contacts()
|
|
1317
1374
|
|
|
@@ -1366,6 +1423,9 @@ async def apply_command_to_contacts(mc, contact_filter, line):
|
|
|
1366
1423
|
(upd_after is None or contact["lastmod"] > upd_after) and\
|
|
1367
1424
|
(min_hops is None or contact["out_path_len"] >= min_hops) and\
|
|
1368
1425
|
(max_hops is None or contact["out_path_len"] <= max_hops):
|
|
1426
|
+
|
|
1427
|
+
count = count + 1
|
|
1428
|
+
|
|
1369
1429
|
if await process_contact_chat_line(mc, contact, line):
|
|
1370
1430
|
pass
|
|
1371
1431
|
|
|
@@ -1390,6 +1450,9 @@ async def apply_command_to_contacts(mc, contact_filter, line):
|
|
|
1390
1450
|
else:
|
|
1391
1451
|
logger.error(f"Can't send {line} to {contact['adv_name']}")
|
|
1392
1452
|
|
|
1453
|
+
if not json_output:
|
|
1454
|
+
print(f"> {count} matches in contacts")
|
|
1455
|
+
|
|
1393
1456
|
async def send_cmd (mc, contact, cmd) :
|
|
1394
1457
|
res = await mc.commands.send_cmd(contact, cmd)
|
|
1395
1458
|
if not res is None and not res.type == EventType.ERROR:
|
|
@@ -1431,7 +1494,8 @@ async def send_msg (mc, contact, msg) :
|
|
|
1431
1494
|
return res
|
|
1432
1495
|
|
|
1433
1496
|
async def msg_ack (mc, contact, msg) :
|
|
1434
|
-
timeout = 0 if not 'timeout' in contact
|
|
1497
|
+
timeout = 0 if not isinstance(contact, dict) or not 'timeout' in contact\
|
|
1498
|
+
else contact['timeout']
|
|
1435
1499
|
res = await mc.commands.send_msg_with_retry(contact, msg,
|
|
1436
1500
|
max_attempts=msg_ack.max_attempts,
|
|
1437
1501
|
flood_after=msg_ack.flood_after,
|
|
@@ -1780,7 +1844,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1780
1844
|
|
|
1781
1845
|
case "apply_to"|"at":
|
|
1782
1846
|
argnum = 2
|
|
1783
|
-
await apply_command_to_contacts(mc, cmds[1], cmds[2])
|
|
1847
|
+
await apply_command_to_contacts(mc, cmds[1], cmds[2], json_output=json_output)
|
|
1784
1848
|
|
|
1785
1849
|
case "set":
|
|
1786
1850
|
argnum = 2
|
|
@@ -2271,7 +2335,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2271
2335
|
argnum = 2
|
|
2272
2336
|
dest = None
|
|
2273
2337
|
|
|
2274
|
-
if len(cmds[1])
|
|
2338
|
+
if len(cmds[1]) >= 12: # possibly an hex prefix
|
|
2275
2339
|
try:
|
|
2276
2340
|
dest = bytes.fromhex(cmds[1])
|
|
2277
2341
|
except ValueError:
|
|
@@ -2532,20 +2596,16 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2532
2596
|
await mc.ensure_contacts()
|
|
2533
2597
|
print(f"Discovered {len(dn)} nodes:")
|
|
2534
2598
|
for n in dn:
|
|
2535
|
-
|
|
2536
|
-
|
|
2599
|
+
try :
|
|
2600
|
+
name = f"{n['pubkey'][0:2]} {mc.get_contact_by_key_prefix(n['pubkey'])['adv_name']}"
|
|
2601
|
+
except TypeError:
|
|
2537
2602
|
name = n["pubkey"][0:16]
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
type = "ROOM"
|
|
2545
|
-
elif n['node_type'] == 4:
|
|
2546
|
-
type = "SENS"
|
|
2547
|
-
|
|
2548
|
-
print(f" {name:16} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
|
|
2603
|
+
if n['node_type'] >= len(CONTACT_TYPENAMES):
|
|
2604
|
+
type = f"t:{n['node_type']}"
|
|
2605
|
+
else:
|
|
2606
|
+
type = CONTACT_TYPENAMES[n['node_type']]
|
|
2607
|
+
|
|
2608
|
+
print(f" {name:22} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
|
|
2549
2609
|
|
|
2550
2610
|
case "req_telemetry"|"rt" :
|
|
2551
2611
|
argnum = 1
|
|
@@ -2561,7 +2621,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2561
2621
|
else :
|
|
2562
2622
|
print(json.dumps({
|
|
2563
2623
|
"name": contact["adv_name"],
|
|
2564
|
-
"pubkey_pre": contact["public_key"][0:
|
|
2624
|
+
"pubkey_pre": contact["public_key"][0:16],
|
|
2565
2625
|
"lpp": res,
|
|
2566
2626
|
}, indent = 4))
|
|
2567
2627
|
|
|
@@ -2695,7 +2755,13 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2695
2755
|
print(json.dumps(res, indent=4))
|
|
2696
2756
|
else :
|
|
2697
2757
|
for c in res.items():
|
|
2698
|
-
|
|
2758
|
+
if c[1]['out_path_len'] == -1:
|
|
2759
|
+
path_str = "Flood"
|
|
2760
|
+
elif c[1]['out_path_len'] == 0:
|
|
2761
|
+
path_str = "0 hop"
|
|
2762
|
+
else:
|
|
2763
|
+
path_str = f"{c[1]['out_path']}"
|
|
2764
|
+
print(f"{c[1]['adv_name']:30} {CONTACT_TYPENAMES[c[1]['type']]:4} {c[1]['public_key'][:12]} {path_str}")
|
|
2699
2765
|
print(f"> {len(mc.contacts)} contacts in device")
|
|
2700
2766
|
|
|
2701
2767
|
case "reload_contacts" | "rc":
|
|
@@ -2763,7 +2829,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2763
2829
|
if (path_len == 0) :
|
|
2764
2830
|
print("0 hop")
|
|
2765
2831
|
elif (path_len == -1) :
|
|
2766
|
-
print("
|
|
2832
|
+
print("Flood")
|
|
2767
2833
|
else:
|
|
2768
2834
|
print(path)
|
|
2769
2835
|
|
|
@@ -2790,6 +2856,8 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2790
2856
|
print(f"Unknown contact {cmds[1]}")
|
|
2791
2857
|
else:
|
|
2792
2858
|
path = cmds[2].replace(",","") # we'll accept path with ,
|
|
2859
|
+
if path == "0":
|
|
2860
|
+
path = ""
|
|
2793
2861
|
try:
|
|
2794
2862
|
res = await mc.commands.change_contact_path(contact, path)
|
|
2795
2863
|
logger.debug(res)
|
|
@@ -3178,11 +3246,16 @@ def command_help():
|
|
|
3178
3246
|
def usage () :
|
|
3179
3247
|
""" Prints some help """
|
|
3180
3248
|
version()
|
|
3249
|
+
command_usage()
|
|
3250
|
+
print(" Available Commands and shorcuts (can be chained) :""")
|
|
3251
|
+
command_help()
|
|
3252
|
+
|
|
3253
|
+
def command_usage() :
|
|
3181
3254
|
print("""
|
|
3182
3255
|
Usage : meshcore-cli <args> <commands>
|
|
3183
3256
|
|
|
3184
3257
|
Arguments :
|
|
3185
|
-
-h : prints
|
|
3258
|
+
-h : prints help for arguments and commands
|
|
3186
3259
|
-v : prints version
|
|
3187
3260
|
-j : json output (disables init file)
|
|
3188
3261
|
-D : debug
|
|
@@ -3198,9 +3271,7 @@ def usage () :
|
|
|
3198
3271
|
-b <baudrate> : specify baudrate
|
|
3199
3272
|
-C : toggles classic mode for prompt
|
|
3200
3273
|
-c <on/off> : disables most of color output if off
|
|
3201
|
-
|
|
3202
|
-
Available Commands and shorcuts (can be chained) :""")
|
|
3203
|
-
command_help()
|
|
3274
|
+
""")
|
|
3204
3275
|
|
|
3205
3276
|
def get_help_for (cmdname, context="line") :
|
|
3206
3277
|
if cmdname == "apply_to" or cmdname == "at" :
|
|
@@ -3213,7 +3284,7 @@ def get_help_for (cmdname, context="line") :
|
|
|
3213
3284
|
- d, direct, similar to h>-1
|
|
3214
3285
|
- f, flood, similar to h<0 or h=-1
|
|
3215
3286
|
|
|
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 ...
|
|
3287
|
+
Note: Some commands like contact_name (aka cn), contact_key (aka ck), contact_type (aka ct), reset_path (aka rp), forget_password (aka fp) can be chained. There is also a sleep command taking an optional event. The sleep will be issued after the command, it helps limiting rate through repeaters ...
|
|
3217
3288
|
|
|
3218
3289
|
Examples:
|
|
3219
3290
|
# removes all clients that have not been updated in last 2 days
|
|
@@ -3252,7 +3323,8 @@ def get_help_for (cmdname, context="line") :
|
|
|
3252
3323
|
print_new_contacts : display new pending contacts when available
|
|
3253
3324
|
print_path_updates : display path updates as they come
|
|
3254
3325
|
custom : all custom variables in json format
|
|
3255
|
-
each custom var can also be get/set directly
|
|
3326
|
+
each custom var can also be get/set directly
|
|
3327
|
+
""")
|
|
3256
3328
|
|
|
3257
3329
|
elif cmdname == "set" :
|
|
3258
3330
|
print("""Available parameters :
|
|
@@ -3265,12 +3337,15 @@ def get_help_for (cmdname, context="line") :
|
|
|
3265
3337
|
lat <lat> : latitude
|
|
3266
3338
|
lon <lon> : longitude
|
|
3267
3339
|
coords <lat,lon> : coordinates
|
|
3268
|
-
auto_update_contacts <> : automatically updates contact list
|
|
3269
3340
|
multi_ack <on/off> : multi-acks feature
|
|
3270
3341
|
telemetry_mode_base <mode> : set basic telemetry mode all/selected/off
|
|
3271
3342
|
telemetry_mode_loc <mode> : set location telemetry mode all/selected/off
|
|
3272
3343
|
telemetry_mode_env <mode> : set env telemetry mode all/selected/off
|
|
3273
3344
|
advert_loc_policy <policy> : "share" means loc will be shared in adv
|
|
3345
|
+
manual_add_contacts <on/off>: let user manually add contacts to device
|
|
3346
|
+
- when off device automatically adds contacts from adverts
|
|
3347
|
+
- when on contacts must be added manually using add_pending
|
|
3348
|
+
(pending contacts list is built by meshcli from adverts while connected)
|
|
3274
3349
|
display:
|
|
3275
3350
|
print_snr <on/off> : toggle snr display in messages
|
|
3276
3351
|
print_adverts <on/off> : display adverts as they come
|
|
@@ -3280,12 +3355,14 @@ def get_help_for (cmdname, context="line") :
|
|
|
3280
3355
|
channel_echoes <on/off> : print repeats for channel data
|
|
3281
3356
|
echo_unk_channels <on/off> : also dump unk channels (encrypted)
|
|
3282
3357
|
color <on/off> : color off should remove ANSI codes from output
|
|
3283
|
-
|
|
3358
|
+
meshcore-cli behaviour:
|
|
3284
3359
|
classic_prompt <on/off> : activates less fancier prompt
|
|
3285
3360
|
arrow_head <string> : change arrow head in prompt
|
|
3286
3361
|
slash_start <string> : idem for slash start
|
|
3287
3362
|
slash_end <string> : slash end
|
|
3288
|
-
invert_slash <on/off> : apply color inversion to slash
|
|
3363
|
+
invert_slash <on/off> : apply color inversion to slash
|
|
3364
|
+
auto_update_contacts <on/of>: auto sync contact list with device
|
|
3365
|
+
""")
|
|
3289
3366
|
|
|
3290
3367
|
elif cmdname == "scope":
|
|
3291
3368
|
print("""scope <scope> : changes flood scope of the node
|
|
@@ -3296,7 +3373,31 @@ Managing Flood Scope in interactive mode
|
|
|
3296
3373
|
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
3374
|
When entering chat mode, scope will be reset to *, meaning classic flood.
|
|
3298
3375
|
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.
|
|
3376
|
+
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.
|
|
3377
|
+
""")
|
|
3378
|
+
|
|
3379
|
+
elif cmdname == "contact_info":
|
|
3380
|
+
print("""contact_info <ct> : displays contact info
|
|
3381
|
+
|
|
3382
|
+
in interactive mode, there are some lighter commands that can be chained to give more compact information
|
|
3383
|
+
- contact_name (cn)
|
|
3384
|
+
- contact_key (ck)
|
|
3385
|
+
- contact_type (ct)
|
|
3386
|
+
""")
|
|
3387
|
+
|
|
3388
|
+
elif cmdname == "pending_contacts" or cmdname == "flush_pending" or cmdname == "add_pending":
|
|
3389
|
+
print("""Contact management
|
|
3390
|
+
|
|
3391
|
+
To receive a message from another user, it is necessary to have its public key. This key is stored on a contact list in the device, and this list has a finite size (50 when meshcore started, now over 350 for most devices).
|
|
3392
|
+
|
|
3393
|
+
By default contacts are automatically added to the device contact list when an advertisement is received, so as soon as you receive an advert, you can talk with your buddy.
|
|
3394
|
+
|
|
3395
|
+
With growing number of users, it becomes necessary to manage contact list and one of the ways is to add contacts manually to the device. This is done by turning on manual_add_contacts. Once this option has been turned on, a pending list is built by meshcore-cli from the received adverts. You can view the list issuing a pending_contacts command, flush the list using flush_pending or add a contact from the list with add_pending followed by the key of the contact or its name (both will be auto-completed with tab).
|
|
3396
|
+
|
|
3397
|
+
This feature only really works in interactive mode.
|
|
3398
|
+
|
|
3399
|
+
Note: There is also an auto_update_contacts setting that has nothing to do with adding contacts, it permits to automatically sync contact lists between device and meshcore-cli (when there is an update in name, location or path).
|
|
3400
|
+
""")
|
|
3300
3401
|
|
|
3301
3402
|
else:
|
|
3302
3403
|
print(f"Sorry, no help yet for {cmdname}")
|
|
@@ -3313,13 +3414,19 @@ async def main(argv):
|
|
|
3313
3414
|
baudrate = 115200
|
|
3314
3415
|
timeout = 2
|
|
3315
3416
|
pin = None
|
|
3417
|
+
first_device = False
|
|
3316
3418
|
# If there is an address in config file, use it by default
|
|
3317
3419
|
# unless an arg is explicitely given
|
|
3318
3420
|
if os.path.exists(MCCLI_ADDRESS) :
|
|
3319
3421
|
with open(MCCLI_ADDRESS, encoding="utf-8") as f :
|
|
3320
3422
|
address = f.readline().strip()
|
|
3321
3423
|
|
|
3322
|
-
|
|
3424
|
+
try:
|
|
3425
|
+
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:C")
|
|
3426
|
+
except getopt.GetoptError:
|
|
3427
|
+
print("Unrecognized option, use -h to get more help")
|
|
3428
|
+
command_usage()
|
|
3429
|
+
return
|
|
3323
3430
|
for opt, arg in opts :
|
|
3324
3431
|
match opt:
|
|
3325
3432
|
case "-c" :
|
|
@@ -3356,6 +3463,7 @@ async def main(argv):
|
|
|
3356
3463
|
return
|
|
3357
3464
|
case "-f": # connect to first encountered device
|
|
3358
3465
|
address = ""
|
|
3466
|
+
first_device = True
|
|
3359
3467
|
case "-l" :
|
|
3360
3468
|
print("BLE devices:")
|
|
3361
3469
|
try :
|
|
@@ -3451,8 +3559,15 @@ async def main(argv):
|
|
|
3451
3559
|
|
|
3452
3560
|
try :
|
|
3453
3561
|
mc = await MeshCore.create_ble(address=address, device=device, client=client, debug=debug, only_error=json_output, pin=pin)
|
|
3562
|
+
except BleakError :
|
|
3563
|
+
print("BLE connection asked (default behaviour), but no BLE HW found")
|
|
3564
|
+
print("Call meshcore-cli with -h for some more help (on commands)")
|
|
3565
|
+
command_usage()
|
|
3566
|
+
return
|
|
3454
3567
|
except ConnectionError :
|
|
3455
3568
|
logger.info("Error while connecting, retrying once ...")
|
|
3569
|
+
if first_device :
|
|
3570
|
+
address = "" # reset address to change device if first_device was asked
|
|
3456
3571
|
if device is None and client is None: # Search for device
|
|
3457
3572
|
logger.info(f"Scanning BLE for device matching {address}")
|
|
3458
3573
|
devices = await BleakScanner.discover(timeout=timeout)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|