meshcore-cli 1.3.5__tar.gz → 1.3.11__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.5 → meshcore_cli-1.3.11}/PKG-INFO +3 -2
- {meshcore_cli-1.3.5 → meshcore_cli-1.3.11}/flake.nix +11 -8
- {meshcore_cli-1.3.5 → meshcore_cli-1.3.11}/pyproject.toml +6 -2
- {meshcore_cli-1.3.5 → meshcore_cli-1.3.11}/src/meshcore_cli/meshcore_cli.py +244 -72
- {meshcore_cli-1.3.5 → meshcore_cli-1.3.11}/.gitignore +0 -0
- {meshcore_cli-1.3.5 → meshcore_cli-1.3.11}/LICENSE +0 -0
- {meshcore_cli-1.3.5 → meshcore_cli-1.3.11}/README.md +0 -0
- {meshcore_cli-1.3.5 → meshcore_cli-1.3.11}/flake.lock +0 -0
- {meshcore_cli-1.3.5 → meshcore_cli-1.3.11}/src/meshcore_cli/__init__.py +0 -0
- {meshcore_cli-1.3.5 → meshcore_cli-1.3.11}/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.11
|
|
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,8 @@ 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:
|
|
13
|
+
Requires-Dist: bleak<2.0,>=0.22
|
|
14
|
+
Requires-Dist: meshcore>=2.2.3
|
|
14
15
|
Requires-Dist: prompt-toolkit>=3.0.50
|
|
15
16
|
Requires-Dist: pycryptodome
|
|
16
17
|
Requires-Dist: requests>=2.28.0
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
outputs =
|
|
7
|
+
outputs =
|
|
8
|
+
inputs:
|
|
8
9
|
inputs.flake-utils.lib.eachDefaultSystem (
|
|
9
|
-
system:
|
|
10
|
+
system:
|
|
11
|
+
let
|
|
10
12
|
pkgs = inputs.nixpkgs.legacyPackages.${system};
|
|
11
13
|
|
|
12
14
|
lib = pkgs.lib;
|
|
@@ -15,15 +17,15 @@
|
|
|
15
17
|
|
|
16
18
|
meshcore = python3Packages.buildPythonPackage rec {
|
|
17
19
|
pname = "meshcore";
|
|
18
|
-
version = "2.2.
|
|
20
|
+
version = "2.2.2";
|
|
19
21
|
pyproject = true;
|
|
20
22
|
|
|
21
23
|
src = python3Packages.fetchPypi {
|
|
22
24
|
inherit pname version;
|
|
23
|
-
sha256 = "sha256-
|
|
25
|
+
sha256 = "sha256-vn/vF4avMDwDLL0EMVrrMWkZrZ1GTiUxGyTBOtKvG1I=";
|
|
24
26
|
};
|
|
25
27
|
|
|
26
|
-
build-system = [python3Packages.hatchling];
|
|
28
|
+
build-system = [ python3Packages.hatchling ];
|
|
27
29
|
|
|
28
30
|
dependencies = [
|
|
29
31
|
python3Packages.bleak
|
|
@@ -31,12 +33,13 @@
|
|
|
31
33
|
python3Packages.pyserial-asyncio
|
|
32
34
|
];
|
|
33
35
|
|
|
34
|
-
pythonImportsCheck = ["meshcore"];
|
|
36
|
+
pythonImportsCheck = [ "meshcore" ];
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
pyproject = lib.importTOML ./pyproject.toml;
|
|
38
40
|
version = pyproject.project.version;
|
|
39
|
-
in
|
|
41
|
+
in
|
|
42
|
+
{
|
|
40
43
|
packages.meshcore-cli = python3Packages.buildPythonPackage {
|
|
41
44
|
pname = "meshcore-cli";
|
|
42
45
|
inherit version;
|
|
@@ -57,7 +60,7 @@
|
|
|
57
60
|
python3Packages.prompt_toolkit
|
|
58
61
|
python3Packages.pyserial
|
|
59
62
|
python3Packages.requests
|
|
60
|
-
|
|
63
|
+
python3Packages.pycryptodome
|
|
61
64
|
];
|
|
62
65
|
|
|
63
66
|
doCheck = false;
|
|
@@ -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.11"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
|
|
10
10
|
]
|
|
@@ -17,7 +17,11 @@ classifiers = [
|
|
|
17
17
|
]
|
|
18
18
|
license = "MIT"
|
|
19
19
|
license-files = ["LICEN[CS]E*"]
|
|
20
|
-
dependencies = [ "meshcore >= 2.2.
|
|
20
|
+
dependencies = [ "meshcore >= 2.2.3",
|
|
21
|
+
"bleak >= 0.22, <2.0",
|
|
22
|
+
"prompt_toolkit >= 3.0.50",
|
|
23
|
+
"requests >= 2.28.0",
|
|
24
|
+
"pycryptodome" ]
|
|
21
25
|
|
|
22
26
|
[project.urls]
|
|
23
27
|
Homepage = "https://github.com/fdlamotte/meshcore-cli"
|
|
@@ -10,7 +10,7 @@ import getopt, json, shlex, re
|
|
|
10
10
|
import logging
|
|
11
11
|
import requests
|
|
12
12
|
from bleak import BleakScanner, BleakClient
|
|
13
|
-
from bleak.exc import BleakError
|
|
13
|
+
from bleak.exc import BleakError, BleakDBusError
|
|
14
14
|
import serial.tools.list_ports
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
import traceback
|
|
@@ -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.11"
|
|
36
36
|
|
|
37
37
|
# default ble address is stored in a config file
|
|
38
38
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
|
@@ -130,10 +130,23 @@ async def process_event_message(mc, ev, json_output, end="\n", above=False):
|
|
|
130
130
|
await mc.ensure_contacts()
|
|
131
131
|
data = ev.payload
|
|
132
132
|
|
|
133
|
+
path_str = ""
|
|
134
|
+
|
|
135
|
+
if process_event_message.timestamp != "" and process_event_message.timestamp != "off":
|
|
136
|
+
ts = data["sender_timestamp"]
|
|
137
|
+
if process_event_message.timestamp == "on":
|
|
138
|
+
if (abs(time.time()-ts) < 86400):
|
|
139
|
+
fmt = "%H:%M"
|
|
140
|
+
else:
|
|
141
|
+
fmt = "%y-%m-%d %H:%M"
|
|
142
|
+
else:
|
|
143
|
+
fmt = process_event_message.timestamp
|
|
144
|
+
path_str += f'{datetime.datetime.fromtimestamp(ts).strftime(fmt)},'
|
|
145
|
+
|
|
133
146
|
if data['path_len'] == 255 :
|
|
134
|
-
path_str
|
|
147
|
+
path_str += "D"
|
|
135
148
|
else :
|
|
136
|
-
path_str
|
|
149
|
+
path_str += f"{data['path_len']}"
|
|
137
150
|
if "SNR" in data and process_event_message.print_snr:
|
|
138
151
|
path_str = path_str + f",{data['SNR']}dB"
|
|
139
152
|
|
|
@@ -206,6 +219,7 @@ async def process_event_message(mc, ev, json_output, end="\n", above=False):
|
|
|
206
219
|
process_event_message.print_snr=False
|
|
207
220
|
process_event_message.color=True
|
|
208
221
|
process_event_message.last_node=None
|
|
222
|
+
process_event_message.timestamp=""
|
|
209
223
|
|
|
210
224
|
async def handle_log_rx(event):
|
|
211
225
|
mc = handle_log_rx.mc
|
|
@@ -292,6 +306,57 @@ async def handle_log_rx(event):
|
|
|
292
306
|
else:
|
|
293
307
|
print(txt)
|
|
294
308
|
|
|
309
|
+
elif payload_type == 0x04: # Advert
|
|
310
|
+
if handle_log_rx.advert_echoes:
|
|
311
|
+
pk_buf = io.BytesIO(pkt_payload)
|
|
312
|
+
adv_key = pk_buf.read(32).hex()
|
|
313
|
+
adv_timestamp = int.from_bytes(pk_buf.read(4), "little", signed=False)
|
|
314
|
+
signature = pk_buf.read(64).hex()
|
|
315
|
+
flags = pk_buf.read(1)[0]
|
|
316
|
+
adv_type = flags & 0x0F
|
|
317
|
+
adv_lat = None
|
|
318
|
+
adv_lon = None
|
|
319
|
+
if flags & 0x10 > 0: #has location
|
|
320
|
+
adv_lat = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
|
|
321
|
+
adv_lon = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
|
|
322
|
+
if flags & 0x20 > 0: #has feature1
|
|
323
|
+
adv_feat1 = pk_buf.read(2).hex()
|
|
324
|
+
if flags & 0x40 > 0: #has feature2
|
|
325
|
+
adv_feat2 = pk_buf.read(2).hex()
|
|
326
|
+
if flags & 0x80 > 0: #has name
|
|
327
|
+
adv_name = pk_buf.read().decode("utf-8").strip("\x00")
|
|
328
|
+
|
|
329
|
+
if adv_name is None:
|
|
330
|
+
# try to get the name from the contact
|
|
331
|
+
ct = handle_log_rx.mc.get_contact_by_key_prefix(adv_key)
|
|
332
|
+
if ct is None:
|
|
333
|
+
adv_name = adv_key[0:12]
|
|
334
|
+
else:
|
|
335
|
+
adv_name = ct["adv_name"]
|
|
336
|
+
|
|
337
|
+
ts_str = ""
|
|
338
|
+
if process_event_message.timestamp != "" and process_event_message.timestamp != "off":
|
|
339
|
+
ts = adv_timestamp
|
|
340
|
+
if process_event_message.timestamp == "on":
|
|
341
|
+
if (abs(time.time()-ts) < 86400):
|
|
342
|
+
fmt = "%H:%M"
|
|
343
|
+
else:
|
|
344
|
+
fmt = "%y-%m-%d %H:%M"
|
|
345
|
+
else:
|
|
346
|
+
fmt = process_event_message.timestamp
|
|
347
|
+
ts_str = f' at {datetime.datetime.fromtimestamp(ts).strftime(fmt)}'
|
|
348
|
+
|
|
349
|
+
txt = f"{ANSI_LIGHT_GRAY}Advert for{ANSI_END} {adv_name}{ANSI_GREEN}/{CONTACT_TYPENAMES[adv_type]}{ts_str}{ANSI_END}"
|
|
350
|
+
if not adv_lat is None:
|
|
351
|
+
txt += f" {ANSI_LIGHT_GRAY}coords: {adv_lat},{adv_lon}"
|
|
352
|
+
txt += f" {ANSI_YELLOW}path: [{path}] {ANSI_LIGHT_GRAY}snr: {event.payload['snr']:.2f}dB{ANSI_END}"
|
|
353
|
+
|
|
354
|
+
if handle_message.above:
|
|
355
|
+
print_above(txt)
|
|
356
|
+
else:
|
|
357
|
+
print(txt)
|
|
358
|
+
|
|
359
|
+
|
|
295
360
|
if handle_log_rx.json_log_rx: # json mode ... raw dump
|
|
296
361
|
msg = json.dumps(event.payload)
|
|
297
362
|
if handle_message.above:
|
|
@@ -302,6 +367,7 @@ async def handle_log_rx(event):
|
|
|
302
367
|
|
|
303
368
|
handle_log_rx.json_log_rx = False
|
|
304
369
|
handle_log_rx.channel_echoes = False
|
|
370
|
+
handle_log_rx.advert_echoes = False
|
|
305
371
|
handle_log_rx.mc = None
|
|
306
372
|
handle_log_rx.echo_unk_chans=False
|
|
307
373
|
|
|
@@ -473,6 +539,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
473
539
|
|
|
474
540
|
completion_list = {
|
|
475
541
|
"to" : to_list,
|
|
542
|
+
"/to" : to_list,
|
|
476
543
|
"public" : None,
|
|
477
544
|
"chan" : None,
|
|
478
545
|
}
|
|
@@ -518,6 +585,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
518
585
|
"self_telemetry" : None,
|
|
519
586
|
"get_channel": None,
|
|
520
587
|
"set_channel": None,
|
|
588
|
+
"add_channel": None,
|
|
521
589
|
"get_channels": None,
|
|
522
590
|
"remove_channel": None,
|
|
523
591
|
"apply_to": None,
|
|
@@ -532,12 +600,15 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
532
600
|
"lat" : None,
|
|
533
601
|
"lon" : None,
|
|
534
602
|
"coords" : None,
|
|
603
|
+
"private_key": None,
|
|
535
604
|
"print_snr" : {"on":None, "off": None},
|
|
605
|
+
"print_timestamp" : {"on":None, "off": None, "%Y:%M":None},
|
|
536
606
|
"json_msgs" : {"on":None, "off": None},
|
|
537
607
|
"color" : {"on":None, "off":None},
|
|
538
608
|
"print_adverts" : {"on":None, "off":None},
|
|
539
609
|
"json_log_rx" : {"on":None, "off":None},
|
|
540
610
|
"channel_echoes" : {"on":None, "off":None},
|
|
611
|
+
"advert_echoes" : {"on":None, "off":None},
|
|
541
612
|
"echo_unk_chans" : {"on":None, "off":None},
|
|
542
613
|
"print_new_contacts" : {"on": None, "off":None},
|
|
543
614
|
"print_path_updates" : {"on":None,"off":None},
|
|
@@ -561,12 +632,15 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
561
632
|
"coords":None,
|
|
562
633
|
"lat":None,
|
|
563
634
|
"lon":None,
|
|
635
|
+
"private_key":None,
|
|
564
636
|
"print_snr":None,
|
|
637
|
+
"print_timestamp":None,
|
|
565
638
|
"json_msgs":None,
|
|
566
639
|
"color":None,
|
|
567
640
|
"print_adverts":None,
|
|
568
641
|
"json_log_rx":None,
|
|
569
642
|
"channel_echoes":None,
|
|
643
|
+
"advert_echoes":None,
|
|
570
644
|
"echo_unk_chans":None,
|
|
571
645
|
"print_path_updates":None,
|
|
572
646
|
"print_new_contacts":None,
|
|
@@ -594,6 +668,12 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
594
668
|
"?pending_contacts":None,
|
|
595
669
|
"?add_pending":None,
|
|
596
670
|
"?flush_pending":None,
|
|
671
|
+
"?get_channels":None,
|
|
672
|
+
"?set_channel":None,
|
|
673
|
+
"?get_channel":None,
|
|
674
|
+
"?set_channel":None,
|
|
675
|
+
"?add_channel":None,
|
|
676
|
+
"?remove_channel":None,
|
|
597
677
|
}
|
|
598
678
|
|
|
599
679
|
contact_completion_list = {
|
|
@@ -782,8 +862,6 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
782
862
|
|
|
783
863
|
await subscribe_to_msgs(mc, above=True)
|
|
784
864
|
|
|
785
|
-
handle_new_contact.print_new_contacts = True
|
|
786
|
-
|
|
787
865
|
try:
|
|
788
866
|
if os.path.isdir(MCCLI_CONFIG_DIR) :
|
|
789
867
|
our_history = FileHistory(MCCLI_HISTORY_FILE)
|
|
@@ -923,6 +1001,9 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
923
1001
|
except IndexError:
|
|
924
1002
|
print(scope)
|
|
925
1003
|
|
|
1004
|
+
elif line == "quit" or line == "q" or line == "/quit" or line == "/q" :
|
|
1005
|
+
break
|
|
1006
|
+
|
|
926
1007
|
elif contact is None and (line.startswith("apply_to ") or line.startswith("at ")) or\
|
|
927
1008
|
line.startswith("/apply_to ") or line.startswith("/at ") :
|
|
928
1009
|
try:
|
|
@@ -930,52 +1011,8 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
930
1011
|
except IndexError:
|
|
931
1012
|
logger.error(f"Error with apply_to command parameters")
|
|
932
1013
|
|
|
933
|
-
elif line.startswith("/")
|
|
934
|
-
|
|
935
|
-
if path.count("/") == 1:
|
|
936
|
-
args = line[1:].split(" ")
|
|
937
|
-
dest = args[0]
|
|
938
|
-
dest_scope = None
|
|
939
|
-
if "%" in dest :
|
|
940
|
-
dest_scope = dest.split("%")[-1]
|
|
941
|
-
dest = dest[:-len(dest_scope)-1]
|
|
942
|
-
await set_scope (mc, dest_scope)
|
|
943
|
-
tct = mc.get_contact_by_name(dest)
|
|
944
|
-
if len(args)>1 and not tct is None: # a contact, send a message
|
|
945
|
-
if tct["type"] == 1 or tct["type"] == 3: # client or room
|
|
946
|
-
last_ack = await msg_ack(mc, tct, line.split(" ", 1)[1])
|
|
947
|
-
else:
|
|
948
|
-
print("Can only send msg to chan, client or room")
|
|
949
|
-
else :
|
|
950
|
-
ch = await get_channel_by_name(mc, dest)
|
|
951
|
-
if len(args)>1 and not ch is None: # a channel, send message
|
|
952
|
-
await send_chan_msg(mc, ch["channel_idx"], line.split(" ", 1)[1])
|
|
953
|
-
else :
|
|
954
|
-
try :
|
|
955
|
-
await process_cmds(mc, shlex.split(line[1:]))
|
|
956
|
-
except ValueError:
|
|
957
|
-
logger.error(f"Error processing line{line[1:]}")
|
|
958
|
-
else:
|
|
959
|
-
cmdline = line[1:].split("/",1)[1]
|
|
960
|
-
contact_name = path[1:].split("/",1)[0]
|
|
961
|
-
dest_scope = None
|
|
962
|
-
if "%" in contact_name:
|
|
963
|
-
dest_scope = contact_name.split("%")[-1]
|
|
964
|
-
contact_name = contact_name[:-len(dest_scope)-1]
|
|
965
|
-
await set_scope (mc, dest_scope)
|
|
966
|
-
tct = mc.get_contact_by_name(contact_name)
|
|
967
|
-
if tct is None:
|
|
968
|
-
print(f"{contact_name} is not a contact")
|
|
969
|
-
else:
|
|
970
|
-
if not await process_contact_chat_line(mc, tct, cmdline):
|
|
971
|
-
if cmdline != "":
|
|
972
|
-
if tct["type"] == 1:
|
|
973
|
-
last_ack = await msg_ack(mc, tct, cmdline)
|
|
974
|
-
else :
|
|
975
|
-
await process_cmds(mc, ["cmd", tct["adv_name"], cmdline])
|
|
976
|
-
|
|
977
|
-
elif line.startswith("to ") : # dest
|
|
978
|
-
dest = line[3:]
|
|
1014
|
+
elif line.startswith("to ") or line.startswith("/to "): # dest
|
|
1015
|
+
dest = line.split(" ", 1)[1]
|
|
979
1016
|
if dest.startswith("\"") or dest.startswith("\'") : # if name starts with a quote
|
|
980
1017
|
dest = shlex.split(dest)[0] # use shlex.split to get contact name between quotes
|
|
981
1018
|
dest_scope = None
|
|
@@ -1021,14 +1058,55 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
1021
1058
|
if not dest_scope is None:
|
|
1022
1059
|
scope = await set_scope(mc, dest_scope)
|
|
1023
1060
|
|
|
1024
|
-
elif line == "to" :
|
|
1061
|
+
elif line == "to" or line == "/to" :
|
|
1025
1062
|
if contact is None :
|
|
1026
1063
|
print(mc.self_info['name'])
|
|
1027
1064
|
else:
|
|
1028
1065
|
print(contact["adv_name"])
|
|
1029
1066
|
|
|
1030
|
-
elif line
|
|
1031
|
-
|
|
1067
|
+
elif line.startswith("/") :
|
|
1068
|
+
path = line.split(" ", 1)[0]
|
|
1069
|
+
if path.count("/") == 1:
|
|
1070
|
+
args = line[1:].split(" ")
|
|
1071
|
+
dest = args[0]
|
|
1072
|
+
dest_scope = None
|
|
1073
|
+
if "%" in dest :
|
|
1074
|
+
dest_scope = dest.split("%")[-1]
|
|
1075
|
+
dest = dest[:-len(dest_scope)-1]
|
|
1076
|
+
await set_scope (mc, dest_scope)
|
|
1077
|
+
tct = mc.get_contact_by_name(dest)
|
|
1078
|
+
if len(args)>1 and not tct is None: # a contact, send a message
|
|
1079
|
+
if tct["type"] == 1 or tct["type"] == 3: # client or room
|
|
1080
|
+
last_ack = await msg_ack(mc, tct, line.split(" ", 1)[1])
|
|
1081
|
+
else:
|
|
1082
|
+
print("Can only send msg to chan, client or room")
|
|
1083
|
+
else :
|
|
1084
|
+
ch = await get_channel_by_name(mc, dest)
|
|
1085
|
+
if len(args)>1 and not ch is None: # a channel, send message
|
|
1086
|
+
await send_chan_msg(mc, ch["channel_idx"], line.split(" ", 1)[1])
|
|
1087
|
+
else :
|
|
1088
|
+
try :
|
|
1089
|
+
await process_cmds(mc, shlex.split(line[1:]))
|
|
1090
|
+
except ValueError:
|
|
1091
|
+
logger.error(f"Error processing line{line[1:]}")
|
|
1092
|
+
else:
|
|
1093
|
+
cmdline = line[1:].split("/",1)[1]
|
|
1094
|
+
contact_name = path[1:].split("/",1)[0]
|
|
1095
|
+
dest_scope = None
|
|
1096
|
+
if "%" in contact_name:
|
|
1097
|
+
dest_scope = contact_name.split("%")[-1]
|
|
1098
|
+
contact_name = contact_name[:-len(dest_scope)-1]
|
|
1099
|
+
await set_scope (mc, dest_scope)
|
|
1100
|
+
tct = mc.get_contact_by_name(contact_name)
|
|
1101
|
+
if tct is None:
|
|
1102
|
+
print(f"{contact_name} is not a contact")
|
|
1103
|
+
else:
|
|
1104
|
+
if not await process_contact_chat_line(mc, tct, cmdline):
|
|
1105
|
+
if cmdline != "":
|
|
1106
|
+
if tct["type"] == 1:
|
|
1107
|
+
last_ack = await msg_ack(mc, tct, cmdline)
|
|
1108
|
+
else :
|
|
1109
|
+
await process_cmds(mc, ["cmd", tct["adv_name"], cmdline])
|
|
1032
1110
|
|
|
1033
1111
|
# commands that take one parameter (don't need quotes)
|
|
1034
1112
|
elif line.startswith("public ") :
|
|
@@ -1527,8 +1605,11 @@ async def set_scope (mc, scope) :
|
|
|
1527
1605
|
return scope
|
|
1528
1606
|
|
|
1529
1607
|
res = await mc.commands.set_flood_scope(scope)
|
|
1530
|
-
if res is None
|
|
1531
|
-
|
|
1608
|
+
if res is None :
|
|
1609
|
+
return None
|
|
1610
|
+
|
|
1611
|
+
if res.type == EventType.ERROR:
|
|
1612
|
+
if "error_code" in res.payload and res.payload["error_code"] == 1: #unsupported
|
|
1532
1613
|
set_scope.has_scope = False
|
|
1533
1614
|
return None
|
|
1534
1615
|
|
|
@@ -1556,13 +1637,18 @@ async def get_channel (mc, chan) :
|
|
|
1556
1637
|
|
|
1557
1638
|
async def set_channel (mc, chan, name, key=None):
|
|
1558
1639
|
|
|
1559
|
-
if chan
|
|
1560
|
-
|
|
1640
|
+
if isinstance(chan, str):
|
|
1641
|
+
if chan.isnumeric():
|
|
1642
|
+
nb = int(chan)
|
|
1643
|
+
else:
|
|
1644
|
+
c = await get_channel_by_name(mc, chan)
|
|
1645
|
+
if c is None:
|
|
1646
|
+
return None
|
|
1647
|
+
nb = c['channel_idx']
|
|
1648
|
+
elif isinstance(chan, int):
|
|
1649
|
+
nb = chan
|
|
1561
1650
|
else:
|
|
1562
|
-
|
|
1563
|
-
if c is None:
|
|
1564
|
-
return None
|
|
1565
|
-
nb = c['channel_idx']
|
|
1651
|
+
return None
|
|
1566
1652
|
|
|
1567
1653
|
res = await mc.commands.set_channel(nb, name, key)
|
|
1568
1654
|
|
|
@@ -1874,6 +1960,10 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1874
1960
|
process_event_message.color = (cmds[2] == "on")
|
|
1875
1961
|
if json_output :
|
|
1876
1962
|
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1963
|
+
case "print_timestamp" :
|
|
1964
|
+
process_event_message.timestamp = cmds[2]
|
|
1965
|
+
if json_output :
|
|
1966
|
+
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1877
1967
|
case "print_snr" :
|
|
1878
1968
|
process_event_message.print_snr = (cmds[2] == "on")
|
|
1879
1969
|
if json_output :
|
|
@@ -1886,6 +1976,10 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1886
1976
|
handle_log_rx.channel_echoes = (cmds[2] == "on")
|
|
1887
1977
|
if json_output :
|
|
1888
1978
|
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1979
|
+
case "advert_echoes" :
|
|
1980
|
+
handle_log_rx.advert_echoes = (cmds[2] == "on")
|
|
1981
|
+
if json_output :
|
|
1982
|
+
print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
|
|
1889
1983
|
case "echo_unk_chans" :
|
|
1890
1984
|
handle_log_rx.echo_unk_chans = (cmds[2] == "on")
|
|
1891
1985
|
if json_output :
|
|
@@ -1983,6 +2077,16 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1983
2077
|
print(json.dumps(res.payload, indent=4))
|
|
1984
2078
|
else:
|
|
1985
2079
|
print("ok")
|
|
2080
|
+
case "private_key":
|
|
2081
|
+
params=bytes.fromhex(cmds[2])
|
|
2082
|
+
res = await mc.commands.import_private_key(params)
|
|
2083
|
+
logger.debug(res)
|
|
2084
|
+
if res.type == EventType.ERROR:
|
|
2085
|
+
print(f"Error: {res}")
|
|
2086
|
+
elif json_output :
|
|
2087
|
+
print(json.dumps(res.payload, indent=4))
|
|
2088
|
+
else:
|
|
2089
|
+
print("ok")
|
|
1986
2090
|
case "tuning":
|
|
1987
2091
|
params=cmds[2].commands.split(",")
|
|
1988
2092
|
res = await mc.commands.set_tuning(
|
|
@@ -2101,6 +2205,11 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2101
2205
|
print(json.dumps({"color" : process_event_message.color}))
|
|
2102
2206
|
else:
|
|
2103
2207
|
print(f"{'on' if process_event_message.color else 'off'}")
|
|
2208
|
+
case "print_timestamp":
|
|
2209
|
+
if json_output :
|
|
2210
|
+
print(json.dumps({"timestamp" : process_event_message.timestamp}))
|
|
2211
|
+
else:
|
|
2212
|
+
print(f"{process_event_message.timestamp}")
|
|
2104
2213
|
case "json_log_rx":
|
|
2105
2214
|
if json_output :
|
|
2106
2215
|
print(json.dumps({"json_log_rx" : handle_log_rx.json_log_rx}))
|
|
@@ -2111,6 +2220,11 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2111
2220
|
print(json.dumps({"channel_echoes" : handle_log_rx.channel_echoes}))
|
|
2112
2221
|
else:
|
|
2113
2222
|
print(f"{'on' if handle_log_rx.channel_echoes else 'off'}")
|
|
2223
|
+
case "advert_echoes":
|
|
2224
|
+
if json_output :
|
|
2225
|
+
print(json.dumps({"advert_echoes" : handle_log_rx.channel_echoes}))
|
|
2226
|
+
else:
|
|
2227
|
+
print(f"{'on' if handle_log_rx.advert_echoes else 'off'}")
|
|
2114
2228
|
case "echo_unk_chans":
|
|
2115
2229
|
if json_output :
|
|
2116
2230
|
print(json.dumps({"echo_unk_chans" : handle_log_rx.echo_unk_chans}))
|
|
@@ -2185,6 +2299,16 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2185
2299
|
print(json.dumps(res.payload, indent=4))
|
|
2186
2300
|
else:
|
|
2187
2301
|
print(f"Battery level : {res.payload['level']}")
|
|
2302
|
+
case "private_key":
|
|
2303
|
+
res = await mc.commands.export_private_key()
|
|
2304
|
+
logger.debug(res)
|
|
2305
|
+
if res.type == EventType.ERROR:
|
|
2306
|
+
print(f"Error exporting private key {res}")
|
|
2307
|
+
elif json_output :
|
|
2308
|
+
res.payload["private_key"] = res.payload["private_key"].hex()
|
|
2309
|
+
print(json.dumps(res.payload))
|
|
2310
|
+
else:
|
|
2311
|
+
print(f"Private key: {res.payload['private_key'].hex()}")
|
|
2188
2312
|
case "fstats" :
|
|
2189
2313
|
res = await mc.commands.get_bat()
|
|
2190
2314
|
logger.debug(res)
|
|
@@ -2313,11 +2437,17 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2313
2437
|
if res is None:
|
|
2314
2438
|
print("Error setting channel")
|
|
2315
2439
|
|
|
2316
|
-
case "
|
|
2317
|
-
argnum =
|
|
2318
|
-
|
|
2440
|
+
case "add_channel":
|
|
2441
|
+
argnum = 2
|
|
2442
|
+
if cmds[1].startswith("#") or len(cmds) == 2:
|
|
2443
|
+
argnum = 1
|
|
2444
|
+
res = await set_channel(mc, "", cmds[1])
|
|
2445
|
+
elif len(cmds[2]) != 32:
|
|
2446
|
+
res = None
|
|
2447
|
+
else:
|
|
2448
|
+
res = await set_channel(mc, "", cmds[1], bytes.fromhex(cmds[3]))
|
|
2319
2449
|
if res is None:
|
|
2320
|
-
print(
|
|
2450
|
+
print("Error adding channel")
|
|
2321
2451
|
|
|
2322
2452
|
case "remove_channel":
|
|
2323
2453
|
argnum = 1
|
|
@@ -2325,6 +2455,12 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2325
2455
|
if res is None:
|
|
2326
2456
|
print("Error deleting channel")
|
|
2327
2457
|
|
|
2458
|
+
case "scope":
|
|
2459
|
+
argnum = 1
|
|
2460
|
+
res = await set_scope(mc, cmds[1])
|
|
2461
|
+
if res is None:
|
|
2462
|
+
print(f"Error while setting scope")
|
|
2463
|
+
|
|
2328
2464
|
case "reboot" :
|
|
2329
2465
|
res = await mc.commands.reboot()
|
|
2330
2466
|
logger.debug(res)
|
|
@@ -3202,7 +3338,8 @@ def command_help():
|
|
|
3202
3338
|
msgs_subscribe : display msgs as they arrive ms
|
|
3203
3339
|
get_channels : prints all channel info
|
|
3204
3340
|
get_channel <n> : get info for channel (by number or name)
|
|
3205
|
-
set_channel n nm k
|
|
3341
|
+
set_channel n nm [k] : set channel info (nb, name, key)
|
|
3342
|
+
add_channel name [key] : add new channel with optional key
|
|
3206
3343
|
remove_channel <n> : remove channel (by number or name)
|
|
3207
3344
|
scope <s> : sets scope for flood messages
|
|
3208
3345
|
Management
|
|
@@ -3318,6 +3455,7 @@ def get_help_for (cmdname, context="line") :
|
|
|
3318
3455
|
lon : longitude
|
|
3319
3456
|
radio : radio parameters
|
|
3320
3457
|
tx : tx power
|
|
3458
|
+
private_key : private key of the node
|
|
3321
3459
|
print_snr : snr display in messages
|
|
3322
3460
|
print_adverts : display adverts as they come
|
|
3323
3461
|
print_new_contacts : display new pending contacts when available
|
|
@@ -3336,6 +3474,7 @@ def get_help_for (cmdname, context="line") :
|
|
|
3336
3474
|
name <name> : node name
|
|
3337
3475
|
lat <lat> : latitude
|
|
3338
3476
|
lon <lon> : longitude
|
|
3477
|
+
private_key : private key
|
|
3339
3478
|
coords <lat,lon> : coordinates
|
|
3340
3479
|
multi_ack <on/off> : multi-acks feature
|
|
3341
3480
|
telemetry_mode_base <mode> : set basic telemetry mode all/selected/off
|
|
@@ -3347,12 +3486,14 @@ def get_help_for (cmdname, context="line") :
|
|
|
3347
3486
|
- when on contacts must be added manually using add_pending
|
|
3348
3487
|
(pending contacts list is built by meshcli from adverts while connected)
|
|
3349
3488
|
display:
|
|
3489
|
+
print_timestamp <on/off/fmt>: toggle printing of timestamp, can be strftime format
|
|
3350
3490
|
print_snr <on/off> : toggle snr display in messages
|
|
3351
3491
|
print_adverts <on/off> : display adverts as they come
|
|
3352
3492
|
print_new_contacts <on/off> : display new pending contacts when available
|
|
3353
3493
|
print_path_updates <on/off> : display path updates as they come
|
|
3354
3494
|
json_log_rx <on/off> : logs packets incoming to device as json
|
|
3355
3495
|
channel_echoes <on/off> : print repeats for channel data
|
|
3496
|
+
advert_echoes <on/off> : print repeats for adverts
|
|
3356
3497
|
echo_unk_channels <on/off> : also dump unk channels (encrypted)
|
|
3357
3498
|
color <on/off> : color off should remove ANSI codes from output
|
|
3358
3499
|
meshcore-cli behaviour:
|
|
@@ -3397,6 +3538,30 @@ With growing number of users, it becomes necessary to manage contact list and on
|
|
|
3397
3538
|
This feature only really works in interactive mode.
|
|
3398
3539
|
|
|
3399
3540
|
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).
|
|
3541
|
+
""")
|
|
3542
|
+
|
|
3543
|
+
elif "channel" in cmdname:
|
|
3544
|
+
print("""Channel management
|
|
3545
|
+
|
|
3546
|
+
Channels are used to send messages to a group of people. This group of people share a common key, used to encrypt, identify and decrypt the messages that are sent flood over the network (possibly with a scope).
|
|
3547
|
+
|
|
3548
|
+
Channel commands are the following:
|
|
3549
|
+
- get_channels
|
|
3550
|
+
- get_channel chan
|
|
3551
|
+
- add_channel name [key]
|
|
3552
|
+
- set_channel chan name [key]
|
|
3553
|
+
- remove_channel chan
|
|
3554
|
+
|
|
3555
|
+
There is a fixed number of slots on companions to store channel messages, each channel has a number, a name and a key, the get_channels command lists theses slots.
|
|
3556
|
+
|
|
3557
|
+
You can also call get_channel (with number or name) to get information about one channel.
|
|
3558
|
+
|
|
3559
|
+
Adding a channel can be done using the set_channel command, taking as parameters the channel number, the name and the key. Key is optional, if not provided, it will be computed from the name.
|
|
3560
|
+
The add_channel command won't take a number as it will use first available slot.
|
|
3561
|
+
|
|
3562
|
+
There is a special case for auto channels, which starts with a #, these have always their key computed from the name (note that mccli does not lowercase and strip characters so you should be carefull when sharing when users of the android app or ripple).
|
|
3563
|
+
|
|
3564
|
+
To remove a channel, use remove_channel, either with channel name or number.
|
|
3400
3565
|
""")
|
|
3401
3566
|
|
|
3402
3567
|
else:
|
|
@@ -3473,7 +3638,7 @@ async def main(argv):
|
|
|
3473
3638
|
for d in devices :
|
|
3474
3639
|
if not d.name is None and d.name.startswith("MeshCore-"):
|
|
3475
3640
|
print(f" {d.address} {d.name}")
|
|
3476
|
-
except BleakError:
|
|
3641
|
+
except (BleakError, BleakDBusError):
|
|
3477
3642
|
print(" No BLE HW")
|
|
3478
3643
|
print("\nSerial ports:")
|
|
3479
3644
|
ports = serial.tools.list_ports.comports()
|
|
@@ -3488,7 +3653,7 @@ async def main(argv):
|
|
|
3488
3653
|
for d in devices:
|
|
3489
3654
|
if not d.name is None and d.name.startswith("MeshCore-"):
|
|
3490
3655
|
choices.append(({"type":"ble","device":d}, f"{d.address:<22} {d.name}"))
|
|
3491
|
-
except BleakError:
|
|
3656
|
+
except (BleakError, BleakDBusError):
|
|
3492
3657
|
logger.info("No BLE Device")
|
|
3493
3658
|
|
|
3494
3659
|
ports = serial.tools.list_ports.comports()
|
|
@@ -3537,7 +3702,14 @@ async def main(argv):
|
|
|
3537
3702
|
logger.info(f"Searching first MC BLE device")
|
|
3538
3703
|
else:
|
|
3539
3704
|
logger.info(f"Scanning BLE for device matching {address}")
|
|
3540
|
-
|
|
3705
|
+
try:
|
|
3706
|
+
devices = await BleakScanner.discover(timeout=timeout)
|
|
3707
|
+
except (BleakError, BleakDBusError):
|
|
3708
|
+
print("BLE connection asked (default behaviour), but no BLE HW found")
|
|
3709
|
+
print("Call meshcore-cli with -h for some more help (on commands)")
|
|
3710
|
+
command_usage()
|
|
3711
|
+
return
|
|
3712
|
+
|
|
3541
3713
|
found = False
|
|
3542
3714
|
for d in devices:
|
|
3543
3715
|
if not d.name is None and d.name.startswith("MeshCore-") and\
|
|
@@ -3559,7 +3731,7 @@ async def main(argv):
|
|
|
3559
3731
|
|
|
3560
3732
|
try :
|
|
3561
3733
|
mc = await MeshCore.create_ble(address=address, device=device, client=client, debug=debug, only_error=json_output, pin=pin)
|
|
3562
|
-
except BleakError :
|
|
3734
|
+
except (BleakError, BleakDBusError):
|
|
3563
3735
|
print("BLE connection asked (default behaviour), but no BLE HW found")
|
|
3564
3736
|
print("Call meshcore-cli with -h for some more help (on commands)")
|
|
3565
3737
|
command_usage()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|