meshcore-cli 0.3__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.
@@ -0,0 +1,171 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # PyPI configuration file
171
+ .pypirc
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 fdlamotte
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: meshcore-cli
3
+ Version: 0.3
4
+ Summary: Command line interface to meshcore companion radios
5
+ Project-URL: Homepage, https://github.com/fdlamotte/meshcore-cli
6
+ Project-URL: Issues, https://github.com/fdlamotte/meshcore-cli/issues
7
+ Author-email: Florent de Lamotte <florent@frizoncorrea.fr>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: meshcore
14
+ Description-Content-Type: text/markdown
15
+
16
+ # mccli
17
+
18
+ mccli.py : CLI interface to MeschCore companion app over BLE, TCP or Serial
19
+
20
+ ## Install
21
+
22
+ You should install (meshcore)[https://github.com/fdlamotte/meshcore_py] package first via pip.
23
+
24
+ <pre>
25
+ $ pip install meshcore
26
+ </pre>
27
+
28
+ Then you can put `mccli.py` program in your path.
29
+
30
+ ## Usage
31
+
32
+ <pre>
33
+ $ mccli.py &lt;args&gt; &lt;commands&gt;
34
+ </pre>
35
+
36
+ ### Arguments
37
+
38
+ Arguments mostly deals with ble connection
39
+
40
+ <pre>
41
+ -h : prints this help
42
+ -a &lt;address&gt; : specifies device address (can be a name)
43
+ -d &lt;name&gt; : filter meshcore devices with name or address
44
+ -t &lt;hostname&gt; : connects via tcp/ip
45
+ -p &lt;port&gt; : specifies tcp port (default 5000)
46
+ -s &lt;port&gt; : use serial port &lt;port&gt;
47
+ -b &lt;baudrate&gt; : specify baudrate
48
+ </pre>
49
+
50
+ ### Available Commands
51
+
52
+ Commands are given after arguments, they can be chained and some have shortcuts.
53
+
54
+ <pre>
55
+ infos : print informations about the node i
56
+ reboot : reboots node
57
+ send &lt;key&gt; &lt;msg&gt; : sends msg to node using pubkey[0:6]
58
+ sendto &lt;name&gt; &lt;msg&gt; : sends msg to node with given name
59
+ msg &lt;name&gt; &lt;msg&gt; : same as sendto m
60
+ wait_ack : wait an ack for last sent msg wa
61
+ recv : reads next msg r
62
+ sync_msgs : gets all unread msgs from the node sm
63
+ wait_msg : wait for a message and read it wm
64
+ advert : sends advert a
65
+ contacts : gets contact list lc
66
+ share_contact &lt;ct&gt; : share a contact with others sc
67
+ remove_contact &lt;ct&gt; : removes a contact from this node
68
+ reset_path &lt;ct&gt; : resets path to a contact to flood rp
69
+ change_path &lt;ct&gt; &lt;path&gt;: change the path to a contact cp
70
+ get_time : gets current time
71
+ set_time &lt;epoch&gt; : sets time to given epoch
72
+ sync_time : sync time with system
73
+ set_name &lt;name&gt; : sets node name
74
+ get_bat : gets battery level b
75
+ login &lt;name&gt; &lt;pwd&gt; : log into a node (rep) with given pwd l
76
+ wait_login : wait for login (timeouts after 5sec) wl
77
+ cmd &lt;name&gt; &lt;cmd&gt; : sends a command to a repeater (no ack) c
78
+ req_status &lt;name&gt; : requests status from a node rs
79
+ wait_status : wait and print reply ws
80
+ sleep &lt;secs&gt; : sleeps for a given amount of secs s
81
+ </pre>
82
+
83
+ ### Examples
84
+
85
+ <pre>
86
+ # gets info from first MC device it finds (was -s but now used for serial port)
87
+ $ ./mccli.py -d "" infos
88
+ Scanning for devices
89
+ Found device : F0:F5:BD:4F:9B:AD: MeshCore
90
+ Connexion started
91
+ {'adv_type': 1, 'public_key': '54c11cff0c2a861cfc5b0bd6e4b81cd5e6ca85e058bf53932d86c87dc7a20011', 'device_loc': '000000000000000000000000', 'radio_freq': 867500, 'radio_bw': 250000, 'radio_sf': 10, 'radio_cr': 5, 'name': 'toto'}
92
+ cmd ['infos'] processed ...
93
+
94
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD get_time
95
+ Connexion started
96
+ Current time : 2024-05-15 12:52:53 (1715770373)
97
+ cmd ['get_time'] processed ...
98
+
99
+ $ date
100
+ Tue Feb 4 12:55:05 CET 2025
101
+
102
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD sync_time get_time
103
+ Connexion started
104
+ True
105
+ cmd ['sync_time'] processed ...
106
+ Current time : 2025-02-04 12:55:24 (1738670124)
107
+ cmd ['get_time'] processed ...
108
+
109
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD contacts
110
+ Connexion started
111
+ {}
112
+ cmd ['contacts'] processed ...
113
+
114
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD sleep 10 contacts
115
+ Connexion started
116
+ Advertisment received
117
+ cmd ['sleep', '10'] processed ...
118
+ {
119
+ "flo2": {
120
+ "public_key": "d6e43f8e9ef26b801d6f5fee39f55ad6dfabfc939c84987256532d8b94aa25dd",
121
+ "type": 1,
122
+ "flags": 0,
123
+ "out_path_len": 255,
124
+ "out_path": "",
125
+ "adv_name": "flo2",
126
+ "last_advert": 1738670344,
127
+ "adv_lat": 0,
128
+ "adv_lon": 0,
129
+ "lastmod": 1738670354
130
+ }
131
+ }
132
+ cmd ['contacts'] processed ...
133
+
134
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD sendto flo2 "Hello flo2" sleep 10
135
+ Connexion started
136
+ {'type': 1, 'expected_ack': b'9\x05\x0c\x12', 'suggested_timeout': 3260}
137
+ cmd ['sendto', 'flo2', 'Hello flo2'] processed ...
138
+ Code path update
139
+ Received ACK
140
+ Msgs are waiting
141
+ cmd ['sleep', '10'] processed ...
142
+
143
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD recv
144
+ Connexion started
145
+ {'type': 'PRIV', 'pubkey_prefix': 'd6e43f8e9ef2', 'path_len': 255, 'txt_type': 0, 'sender_timestamp': 1738670421, 'text': 'hi'}
146
+ cmd ['recv'] processed ...
147
+
148
+ # logs into a repeater (HomeRep) and check time
149
+ $ ./mccli.py -d t1000 login HomeRep password
150
+ Scanning for devices
151
+ Found device : FB:F2:5C:40:4F:77: MeshCore-t1000
152
+ Connexion started
153
+ {'type': 0, 'expected_ack': b'\x82yU\x02', 'suggested_timeout': 4446}
154
+ cmd ['login', 'HomeRep', 'password'] processed ...
155
+
156
+ $ ./mccli.py cmd HomeRep clock wait_msg
157
+ Connexion started
158
+ {'type': 0, 'expected_ack': b'\x00\x00\x00\x00', 'suggested_timeout': 2724}
159
+ cmd ['cmd', 'HomeRep', 'clock'] processed ...
160
+ Msgs are waiting
161
+ {'type': 'PRIV', 'pubkey_prefix': '827955027cad', 'path_len': 255, 'txt_type': 1, 'sender_timestamp': 1741030036, 'text': '19:27 - 3/3/2025 UTC'}
162
+ cmd ['wait_msg'] processed ...
163
+ </pre>
164
+
@@ -0,0 +1,149 @@
1
+ # mccli
2
+
3
+ mccli.py : CLI interface to MeschCore companion app over BLE, TCP or Serial
4
+
5
+ ## Install
6
+
7
+ You should install (meshcore)[https://github.com/fdlamotte/meshcore_py] package first via pip.
8
+
9
+ <pre>
10
+ $ pip install meshcore
11
+ </pre>
12
+
13
+ Then you can put `mccli.py` program in your path.
14
+
15
+ ## Usage
16
+
17
+ <pre>
18
+ $ mccli.py &lt;args&gt; &lt;commands&gt;
19
+ </pre>
20
+
21
+ ### Arguments
22
+
23
+ Arguments mostly deals with ble connection
24
+
25
+ <pre>
26
+ -h : prints this help
27
+ -a &lt;address&gt; : specifies device address (can be a name)
28
+ -d &lt;name&gt; : filter meshcore devices with name or address
29
+ -t &lt;hostname&gt; : connects via tcp/ip
30
+ -p &lt;port&gt; : specifies tcp port (default 5000)
31
+ -s &lt;port&gt; : use serial port &lt;port&gt;
32
+ -b &lt;baudrate&gt; : specify baudrate
33
+ </pre>
34
+
35
+ ### Available Commands
36
+
37
+ Commands are given after arguments, they can be chained and some have shortcuts.
38
+
39
+ <pre>
40
+ infos : print informations about the node i
41
+ reboot : reboots node
42
+ send &lt;key&gt; &lt;msg&gt; : sends msg to node using pubkey[0:6]
43
+ sendto &lt;name&gt; &lt;msg&gt; : sends msg to node with given name
44
+ msg &lt;name&gt; &lt;msg&gt; : same as sendto m
45
+ wait_ack : wait an ack for last sent msg wa
46
+ recv : reads next msg r
47
+ sync_msgs : gets all unread msgs from the node sm
48
+ wait_msg : wait for a message and read it wm
49
+ advert : sends advert a
50
+ contacts : gets contact list lc
51
+ share_contact &lt;ct&gt; : share a contact with others sc
52
+ remove_contact &lt;ct&gt; : removes a contact from this node
53
+ reset_path &lt;ct&gt; : resets path to a contact to flood rp
54
+ change_path &lt;ct&gt; &lt;path&gt;: change the path to a contact cp
55
+ get_time : gets current time
56
+ set_time &lt;epoch&gt; : sets time to given epoch
57
+ sync_time : sync time with system
58
+ set_name &lt;name&gt; : sets node name
59
+ get_bat : gets battery level b
60
+ login &lt;name&gt; &lt;pwd&gt; : log into a node (rep) with given pwd l
61
+ wait_login : wait for login (timeouts after 5sec) wl
62
+ cmd &lt;name&gt; &lt;cmd&gt; : sends a command to a repeater (no ack) c
63
+ req_status &lt;name&gt; : requests status from a node rs
64
+ wait_status : wait and print reply ws
65
+ sleep &lt;secs&gt; : sleeps for a given amount of secs s
66
+ </pre>
67
+
68
+ ### Examples
69
+
70
+ <pre>
71
+ # gets info from first MC device it finds (was -s but now used for serial port)
72
+ $ ./mccli.py -d "" infos
73
+ Scanning for devices
74
+ Found device : F0:F5:BD:4F:9B:AD: MeshCore
75
+ Connexion started
76
+ {'adv_type': 1, 'public_key': '54c11cff0c2a861cfc5b0bd6e4b81cd5e6ca85e058bf53932d86c87dc7a20011', 'device_loc': '000000000000000000000000', 'radio_freq': 867500, 'radio_bw': 250000, 'radio_sf': 10, 'radio_cr': 5, 'name': 'toto'}
77
+ cmd ['infos'] processed ...
78
+
79
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD get_time
80
+ Connexion started
81
+ Current time : 2024-05-15 12:52:53 (1715770373)
82
+ cmd ['get_time'] processed ...
83
+
84
+ $ date
85
+ Tue Feb 4 12:55:05 CET 2025
86
+
87
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD sync_time get_time
88
+ Connexion started
89
+ True
90
+ cmd ['sync_time'] processed ...
91
+ Current time : 2025-02-04 12:55:24 (1738670124)
92
+ cmd ['get_time'] processed ...
93
+
94
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD contacts
95
+ Connexion started
96
+ {}
97
+ cmd ['contacts'] processed ...
98
+
99
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD sleep 10 contacts
100
+ Connexion started
101
+ Advertisment received
102
+ cmd ['sleep', '10'] processed ...
103
+ {
104
+ "flo2": {
105
+ "public_key": "d6e43f8e9ef26b801d6f5fee39f55ad6dfabfc939c84987256532d8b94aa25dd",
106
+ "type": 1,
107
+ "flags": 0,
108
+ "out_path_len": 255,
109
+ "out_path": "",
110
+ "adv_name": "flo2",
111
+ "last_advert": 1738670344,
112
+ "adv_lat": 0,
113
+ "adv_lon": 0,
114
+ "lastmod": 1738670354
115
+ }
116
+ }
117
+ cmd ['contacts'] processed ...
118
+
119
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD sendto flo2 "Hello flo2" sleep 10
120
+ Connexion started
121
+ {'type': 1, 'expected_ack': b'9\x05\x0c\x12', 'suggested_timeout': 3260}
122
+ cmd ['sendto', 'flo2', 'Hello flo2'] processed ...
123
+ Code path update
124
+ Received ACK
125
+ Msgs are waiting
126
+ cmd ['sleep', '10'] processed ...
127
+
128
+ $ ./mccli.py -a F0:F5:BD:4F:9B:AD recv
129
+ Connexion started
130
+ {'type': 'PRIV', 'pubkey_prefix': 'd6e43f8e9ef2', 'path_len': 255, 'txt_type': 0, 'sender_timestamp': 1738670421, 'text': 'hi'}
131
+ cmd ['recv'] processed ...
132
+
133
+ # logs into a repeater (HomeRep) and check time
134
+ $ ./mccli.py -d t1000 login HomeRep password
135
+ Scanning for devices
136
+ Found device : FB:F2:5C:40:4F:77: MeshCore-t1000
137
+ Connexion started
138
+ {'type': 0, 'expected_ack': b'\x82yU\x02', 'suggested_timeout': 4446}
139
+ cmd ['login', 'HomeRep', 'password'] processed ...
140
+
141
+ $ ./mccli.py cmd HomeRep clock wait_msg
142
+ Connexion started
143
+ {'type': 0, 'expected_ack': b'\x00\x00\x00\x00', 'suggested_timeout': 2724}
144
+ cmd ['cmd', 'HomeRep', 'clock'] processed ...
145
+ Msgs are waiting
146
+ {'type': 'PRIV', 'pubkey_prefix': '827955027cad', 'path_len': 255, 'txt_type': 1, 'sender_timestamp': 1741030036, 'text': '19:27 - 3/3/2025 UTC'}
147
+ cmd ['wait_msg'] processed ...
148
+ </pre>
149
+
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "meshcore-cli"
7
+ version = "0.3"
8
+ authors = [
9
+ { name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
10
+ ]
11
+ description = "Command line interface to meshcore companion radios"
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ license = "MIT"
19
+ license-files = ["LICEN[CS]E*"]
20
+ dependencies = [ "meshcore" ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/fdlamotte/meshcore-cli"
24
+ Issues = "https://github.com/fdlamotte/meshcore-cli/issues"
25
+
26
+ [project.scripts]
27
+ meshcli = "meshcore_cli.meshcore_cli:cli"
28
+ meshcore-cli = "meshcore_cli.meshcore_cli:cli"
File without changes
@@ -0,0 +1,3 @@
1
+ if __name__ == "__main__":
2
+ from meshcore_cli.meshcore_cli import cli
3
+ cli()
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/python
2
+ """
3
+ mccli.py : CLI interface to MeschCore BLE companion app
4
+ """
5
+ import asyncio
6
+ import os
7
+ import sys
8
+ import getopt
9
+ import json
10
+ import datetime
11
+ import time
12
+ from pathlib import Path
13
+
14
+ from meshcore import TCPConnection
15
+ from meshcore import BLEConnection
16
+ from meshcore import SerialConnection
17
+ from meshcore import printerr
18
+ from meshcore import MeshCore
19
+
20
+ # default address is stored in a config file
21
+ MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/mc-cli/"
22
+ MCCLI_ADDRESS = MCCLI_CONFIG_DIR + "default_address"
23
+
24
+ # Fallback address if config file not found
25
+ # if None or "" then a scan is performed
26
+ ADDRESS = ""
27
+
28
+ async def next_cmd(mc, cmds):
29
+ """ process next command """
30
+ argnum = 0
31
+ match cmds[0] :
32
+ case "q":
33
+ print(await mc.send_device_qeury())
34
+ case "get_time" | "clock" :
35
+ if len(cmds) > 1 and cmds[1] == "sync" :
36
+ argnum=1
37
+ print(await mc.set_time(int(time.time())))
38
+ else:
39
+ timestamp = await mc.get_time()
40
+ print('Current time :'
41
+ f' {datetime.datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")}'
42
+ f' ({timestamp})')
43
+ case "sync_time"|"clock sync"|"st":
44
+ print(await mc.set_time(int(time.time())))
45
+ case "set_time" :
46
+ argnum = 1
47
+ print(await mc.set_time(cmds[1]))
48
+ case "set_txpower"|"txp" :
49
+ argnum = 1
50
+ print(await mc.set_tx_power(cmds[1]))
51
+ case "set_radio"|"rad" :
52
+ argnum = 4
53
+ print(await mc.set_radio(cmds[1], cmds[2], cmds[3], cmds[4]))
54
+ case "set_name" :
55
+ argnum = 1
56
+ print(await mc.set_name(cmds[1]))
57
+ case "set":
58
+ argnum = 2
59
+ match cmds[1]:
60
+ case "pin":
61
+ print (await mc.set_devicepin(cmds[2]))
62
+ case "radio":
63
+ params=cmds[2].split(",")
64
+ print (await mc.set_radio(params[0], params[1], params[2], params[3]))
65
+ case "name":
66
+ print (await mc.set_name(cmds[2]))
67
+ case "tx":
68
+ print (await mc.set_tx_power(cmds[2]))
69
+ case "lat":
70
+ print (await mc.set_coords(\
71
+ float(cmds[2]),\
72
+ mc.self_infos['adv_lon']))
73
+ case "lon":
74
+ print (await mc.set_coords(\
75
+ mc.self_infos['adv_lat'],\
76
+ float(cmds[2])))
77
+ case "coords":
78
+ params=cmds[2].split(",")
79
+ print (await mc.set_coords(\
80
+ float(params[0]),\
81
+ float(params[1])))
82
+ case "set_tuning"|"tun" :
83
+ argnum = 2
84
+ print(await mc.set_tuning(cmds[1], cmds[2]))
85
+ case "get_bat" | "b":
86
+ print(await mc.get_bat())
87
+ case "reboot" :
88
+ print(await mc.reboot())
89
+ case "send" :
90
+ argnum = 2
91
+ print(await mc.send_msg(bytes.fromhex(cmds[1]), cmds[2]))
92
+ case "msg" | "sendto" | "m" | "{" : # sends to a contact from name
93
+ argnum = 2
94
+ await mc.ensure_contacts()
95
+ print(await mc.send_msg(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])[0:6],
96
+ cmds[2]))
97
+ case "chan_msg"|"ch" :
98
+ argnum = 2
99
+ print(await mc.send_chan_msg(cmds[1], cmds[2]))
100
+ case "def_chan_msg"|"def_chan"|"dch" : # default chan
101
+ argnum = 1
102
+ print(await mc.send_chan_msg(0, cmds[1]))
103
+ case "cmd" | "c" | "[" :
104
+ argnum = 2
105
+ await mc.ensure_contacts()
106
+ print(await mc.send_cmd(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])[0:6],
107
+ cmds[2]))
108
+ case "login" | "l" | "[[" :
109
+ argnum = 2
110
+ await mc.ensure_contacts()
111
+ print(await mc.send_login(bytes.fromhex(mc.contacts[cmds[1]]["public_key"]),
112
+ cmds[2]))
113
+ case "wait_login" | "wl" | "]]":
114
+ print(await mc.wait_login())
115
+ case "req_status" | "rs" :
116
+ argnum = 1
117
+ await mc.ensure_contacts()
118
+ print(await mc.send_statusreq(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
119
+ case "wait_status" | "ws" :
120
+ print(await mc.wait_status())
121
+ case "contacts" | "lc":
122
+ print(json.dumps(await mc.get_contacts(),indent=4))
123
+ case "change_path" | "cp":
124
+ argnum = 2
125
+ await mc.ensure_contacts()
126
+ await mc.set_out_path(mc.contacts[cmds[1]], cmds[2])
127
+ print(await mc.update_contact(mc.contacts[cmds[1]]))
128
+ case "reset_path" | "rp" :
129
+ argnum = 1
130
+ await mc.ensure_contacts()
131
+ print(await mc.reset_path(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
132
+ await mc.get_contacts()
133
+ case "share_contact" | "sc":
134
+ argnum = 1
135
+ await mc.ensure_contacts()
136
+ print(await mc.share_contact(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
137
+ case "export_contact"|"ec":
138
+ argnum = 1
139
+ await mc.ensure_contacts()
140
+ print(await mc.export_contact(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
141
+ case "export_myself"|"e":
142
+ print(await mc.export_contact())
143
+ case "remove_contact" :
144
+ argnum = 1
145
+ await mc.ensure_contacts()
146
+ print(await mc.remove_contact(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
147
+ case "recv" | "r" :
148
+ print(await mc.get_msg())
149
+ case "sync_msgs" | "sm":
150
+ res=True
151
+ while res:
152
+ res = await mc.get_msg()
153
+ print (res)
154
+ case "wait_msg" | "wm" :
155
+ await mc.wait_msg()
156
+ res = await mc.get_msg()
157
+ print (res)
158
+ case "trywait_msg" | "wmt" :
159
+ argnum = 1
160
+ if await mc.wait_msg(timeout=int(cmds[1])) :
161
+ print (await mc.get_msg())
162
+ case "wmt8"|"]":
163
+ if await mc.wait_msg(timeout=8) :
164
+ print (await mc.get_msg())
165
+ case "wait_ack" | "wa" | "}":
166
+ await mc.wait_ack()
167
+ case "infos" | "i" :
168
+ print(json.dumps(mc.self_info,indent=4))
169
+ case "advert" | "a":
170
+ print(await mc.send_advert())
171
+ case "sleep" | "s" :
172
+ argnum = 1
173
+ await asyncio.sleep(int(cmds[1]))
174
+
175
+ printerr (f"cmd {cmds[0:argnum+1]} processed ...")
176
+ return cmds[argnum+1:]
177
+
178
+ def usage () :
179
+ """ Prints some help """
180
+ print("""mccli.py : CLI interface to MeschCore BLE companion app
181
+
182
+ Usage : mccli.py <args> <commands>
183
+
184
+ Arguments :
185
+ -h : prints this help
186
+ -a <address> : specifies device address (can be a name)
187
+ -d <name> : filter meshcore devices with name or address
188
+ -t <hostname> : connects via tcp/ip
189
+ -p <port> : specifies tcp port (default 5000)
190
+ -s <port> : use serial port <port>
191
+ -b <baudrate> : specify baudrate
192
+
193
+ Available Commands and shorcuts (can be chained) :
194
+ infos : print informations about the node i
195
+ reboot : reboots node
196
+ send <key> <msg> : sends msg to node using pubkey[0:6]
197
+ sendto <name> <msg> : sends msg to node with given name
198
+ msg <name> <msg> : same as sendto m
199
+ wait_ack : wait an ack for last sent msg wa
200
+ recv : reads next msg r
201
+ sync_msgs : gets all unread msgs from the node sm
202
+ wait_msg : wait for a message and read it wm
203
+ advert : sends advert a
204
+ contacts : gets contact list lc
205
+ share_contact <ct> : share a contact with others sc
206
+ remove_contact <ct> : removes a contact from this node
207
+ reset_path <ct> : resets path to a contact to flood rp
208
+ change_path <ct> <path>: change the path to a contact cp
209
+ get_time : gets current time
210
+ set_time <epoch> : sets time to given epoch
211
+ sync_time : sync time with system
212
+ set_name <name> : sets node name
213
+ get_bat : gets battery level b
214
+ login <name> <pwd> : log into a node (rep) with given pwd l
215
+ wait_login : wait for login (timeouts after 5sec) wl
216
+ cmd <name> <cmd> : sends a command to a repeater (no ack) c
217
+ req_status <name> : requests status from a node rs
218
+ wait_status : wait and print reply ws
219
+ sleep <secs> : sleeps for a given amount of secs s""")
220
+
221
+ async def main(argv):
222
+ """ Do the job """
223
+ address = ADDRESS
224
+ port = 5000
225
+ hostname = None
226
+ serial_port = None
227
+ baudrate = 115200
228
+ # If there is an address in config file, use it by default
229
+ # unless an arg is explicitely given
230
+ if os.path.exists(MCCLI_ADDRESS) :
231
+ with open(MCCLI_ADDRESS, encoding="utf-8") as f :
232
+ address = f.readline().strip()
233
+
234
+ opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:")
235
+ for opt, arg in opts :
236
+ match opt:
237
+ case "-d" : # name specified on cmdline
238
+ address = arg
239
+ case "-a" : # address specified on cmdline
240
+ address = arg
241
+ case "-s" : # serial port
242
+ serial_port = arg
243
+ case "-b" :
244
+ baudrate = int(arg)
245
+ case "-t" :
246
+ hostname = arg
247
+ case "-p" :
248
+ port = int(arg)
249
+
250
+ if len(args) == 0 : # no args, no action
251
+ usage()
252
+ return
253
+
254
+ con = None
255
+ if not hostname is None : # connect via tcp
256
+ con = TCPConnection(hostname, port)
257
+ await con.connect()
258
+ elif not serial_port is None : # connect via serial port
259
+ con = SerialConnection(serial_port, baudrate)
260
+ await con.connect()
261
+ await asyncio.sleep(0.1)
262
+ else : #connect via ble
263
+ con = BLEConnection(address)
264
+ address = await con.connect()
265
+ if address is None or address == "" : # no device, no action
266
+ printerr ("No device found, exiting ...")
267
+ return
268
+
269
+ # Store device address in configuration
270
+ if os.path.isdir(MCCLI_CONFIG_DIR) :
271
+ with open(MCCLI_ADDRESS, "w", encoding="utf-8") as f :
272
+ f.write(address)
273
+
274
+ mc = MeshCore(con)
275
+ await mc.connect()
276
+
277
+ cmds = args
278
+ while len(cmds) > 0 :
279
+ cmds = await next_cmd(mc, cmds)
280
+
281
+ def cli():
282
+ asyncio.run(main(sys.argv[1:]))