mmrelay 1.1.3__tar.gz → 1.1.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.
Potentially problematic release.
This version of mmrelay might be problematic. Click here for more details.
- {mmrelay-1.1.3/src/mmrelay.egg-info → mmrelay-1.1.4}/PKG-INFO +3 -3
- {mmrelay-1.1.3 → mmrelay-1.1.4}/requirements.txt +8 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/setup.py +2 -2
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/__init__.py +1 -1
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/cli.py +124 -64
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/config.py +63 -36
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/config_checker.py +41 -12
- mmrelay-1.1.4/src/mmrelay/constants/__init__.py +54 -0
- mmrelay-1.1.4/src/mmrelay/constants/app.py +17 -0
- mmrelay-1.1.4/src/mmrelay/constants/config.py +73 -0
- mmrelay-1.1.4/src/mmrelay/constants/database.py +22 -0
- mmrelay-1.1.4/src/mmrelay/constants/formats.py +20 -0
- mmrelay-1.1.4/src/mmrelay/constants/messages.py +36 -0
- mmrelay-1.1.4/src/mmrelay/constants/network.py +35 -0
- mmrelay-1.1.4/src/mmrelay/constants/queue.py +17 -0
- mmrelay-1.1.4/src/mmrelay/db_utils.py +510 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/log_utils.py +38 -14
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/main.py +5 -4
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/matrix_utils.py +43 -53
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/meshtastic_utils.py +203 -99
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/message_queue.py +17 -17
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugin_loader.py +54 -51
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/base_plugin.py +58 -11
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/drop_plugin.py +13 -5
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/mesh_relay_plugin.py +7 -10
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/weather_plugin.py +10 -1
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/setup_utils.py +67 -30
- {mmrelay-1.1.3 → mmrelay-1.1.4/src/mmrelay.egg-info}/PKG-INFO +3 -3
- mmrelay-1.1.4/src/mmrelay.egg-info/SOURCES.txt +87 -0
- mmrelay-1.1.4/tests/test_async_patterns.py +690 -0
- mmrelay-1.1.4/tests/test_base_plugin.py +596 -0
- mmrelay-1.1.4/tests/test_cli.py +577 -0
- mmrelay-1.1.4/tests/test_cli_edge_cases.py +352 -0
- mmrelay-1.1.4/tests/test_config.py +360 -0
- mmrelay-1.1.4/tests/test_config_checker.py +596 -0
- mmrelay-1.1.4/tests/test_config_edge_cases.py +308 -0
- mmrelay-1.1.4/tests/test_constants.py +267 -0
- mmrelay-1.1.4/tests/test_db_utils.py +376 -0
- mmrelay-1.1.4/tests/test_db_utils_edge_cases.py +385 -0
- mmrelay-1.1.4/tests/test_debug_plugin.py +235 -0
- mmrelay-1.1.4/tests/test_drop_plugin.py +502 -0
- mmrelay-1.1.4/tests/test_error_boundaries.py +695 -0
- mmrelay-1.1.4/tests/test_health_plugin.py +281 -0
- mmrelay-1.1.4/tests/test_help_plugin.py +364 -0
- mmrelay-1.1.4/tests/test_imports.py +61 -0
- mmrelay-1.1.4/tests/test_integration_scenarios.py +865 -0
- mmrelay-1.1.4/tests/test_log_utils.py +402 -0
- mmrelay-1.1.4/tests/test_main.py +750 -0
- mmrelay-1.1.4/tests/test_map_plugin.py +690 -0
- mmrelay-1.1.4/tests/test_matrix_utils.py +1173 -0
- mmrelay-1.1.4/tests/test_matrix_utils_edge_cases.py +313 -0
- mmrelay-1.1.4/tests/test_mesh_relay_plugin.py +527 -0
- mmrelay-1.1.4/tests/test_meshtastic_utils.py +838 -0
- mmrelay-1.1.4/tests/test_meshtastic_utils_edge_cases.py +448 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/tests/test_message_queue.py +28 -18
- mmrelay-1.1.4/tests/test_message_queue_edge_cases.py +505 -0
- mmrelay-1.1.4/tests/test_network_reliability.py +426 -0
- mmrelay-1.1.4/tests/test_nodes_plugin.py +382 -0
- mmrelay-1.1.4/tests/test_performance_stress.py +857 -0
- mmrelay-1.1.4/tests/test_ping_plugin.py +533 -0
- mmrelay-1.1.4/tests/test_plugin_loader.py +558 -0
- mmrelay-1.1.4/tests/test_plugin_loader_edge_cases.py +460 -0
- mmrelay-1.1.4/tests/test_setup_utils.py +489 -0
- mmrelay-1.1.4/tests/test_setup_utils_edge_cases.py +421 -0
- mmrelay-1.1.4/tests/test_telemetry_plugin.py +506 -0
- mmrelay-1.1.4/tests/test_weather_plugin.py +526 -0
- mmrelay-1.1.3/src/mmrelay/db_utils.py +0 -361
- mmrelay-1.1.3/src/mmrelay.egg-info/SOURCES.txt +0 -44
- mmrelay-1.1.3/tests/test_detection_sensor.py +0 -156
- {mmrelay-1.1.3 → mmrelay-1.1.4}/LICENSE +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/MANIFEST.in +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/README.md +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/pyproject.toml +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/setup.cfg +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/__init__.py +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/debug_plugin.py +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/health_plugin.py +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/help_plugin.py +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/map_plugin.py +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/nodes_plugin.py +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/ping_plugin.py +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/plugins/telemetry_plugin.py +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/tools/__init__.py +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/tools/mmrelay.service +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/tools/sample-docker-compose.yaml +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/tools/sample.env +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay/tools/sample_config.yaml +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay.egg-info/dependency_links.txt +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay.egg-info/entry_points.txt +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay.egg-info/requires.txt +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/src/mmrelay.egg-info/top_level.txt +0 -0
- {mmrelay-1.1.3 → mmrelay-1.1.4}/tests/test_prefix_customization.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mmrelay
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.4
|
|
4
4
|
Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
|
|
5
|
-
Home-page: https://github.com/
|
|
5
|
+
Home-page: https://github.com/jeremiah-k/meshtastic-matrix-relay
|
|
6
6
|
Author: Geoff Whittington, Jeremiah K., and contributors
|
|
7
7
|
Author-email: jeremiahk@gmx.com
|
|
8
|
-
Project-URL: Bug Tracker, https://github.com/
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/jeremiah-k/meshtastic-matrix-relay/issues
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
@@ -8,5 +8,13 @@ haversine==2.9.0
|
|
|
8
8
|
schedule==1.2.2
|
|
9
9
|
platformdirs==4.3.8
|
|
10
10
|
py-staticmaps>=0.4.0
|
|
11
|
+
psutil>=5.8.0
|
|
11
12
|
rich==14.1.0
|
|
12
13
|
setuptools==80.9.0
|
|
14
|
+
|
|
15
|
+
# Testing and coverage
|
|
16
|
+
coverage==7.6.9
|
|
17
|
+
pytest==8.3.4
|
|
18
|
+
pytest-cov==6.0.0
|
|
19
|
+
pytest-asyncio==0.25.0
|
|
20
|
+
pytest-env==1.1.5
|
|
@@ -27,9 +27,9 @@ setup(
|
|
|
27
27
|
description="Bridge between Meshtastic mesh networks and Matrix chat rooms",
|
|
28
28
|
long_description=long_description,
|
|
29
29
|
long_description_content_type="text/markdown",
|
|
30
|
-
url="https://github.com/
|
|
30
|
+
url="https://github.com/jeremiah-k/meshtastic-matrix-relay",
|
|
31
31
|
project_urls={
|
|
32
|
-
"Bug Tracker": "https://github.com/
|
|
32
|
+
"Bug Tracker": "https://github.com/jeremiah-k/meshtastic-matrix-relay/issues"
|
|
33
33
|
},
|
|
34
34
|
classifiers=[
|
|
35
35
|
"Programming Language :: Python :: 3",
|
|
@@ -12,15 +12,36 @@ from yaml.loader import SafeLoader
|
|
|
12
12
|
|
|
13
13
|
# Import version from package
|
|
14
14
|
from mmrelay import __version__
|
|
15
|
+
from mmrelay.config import get_config_paths
|
|
16
|
+
from mmrelay.constants.app import WINDOWS_PLATFORM
|
|
17
|
+
from mmrelay.constants.config import (
|
|
18
|
+
CONFIG_KEY_ACCESS_TOKEN,
|
|
19
|
+
CONFIG_KEY_BOT_USER_ID,
|
|
20
|
+
CONFIG_KEY_HOMESERVER,
|
|
21
|
+
CONFIG_SECTION_MATRIX,
|
|
22
|
+
CONFIG_SECTION_MESHTASTIC,
|
|
23
|
+
)
|
|
24
|
+
from mmrelay.constants.network import (
|
|
25
|
+
CONFIG_KEY_BLE_ADDRESS,
|
|
26
|
+
CONFIG_KEY_CONNECTION_TYPE,
|
|
27
|
+
CONFIG_KEY_HOST,
|
|
28
|
+
CONFIG_KEY_SERIAL_PORT,
|
|
29
|
+
CONNECTION_TYPE_BLE,
|
|
30
|
+
CONNECTION_TYPE_NETWORK,
|
|
31
|
+
CONNECTION_TYPE_SERIAL,
|
|
32
|
+
CONNECTION_TYPE_TCP,
|
|
33
|
+
)
|
|
15
34
|
from mmrelay.tools import get_sample_config_path
|
|
16
35
|
|
|
17
36
|
|
|
18
37
|
def parse_arguments():
|
|
19
38
|
"""
|
|
20
|
-
Parse command-line arguments.
|
|
39
|
+
Parse and validate command-line arguments for the Meshtastic Matrix Relay CLI.
|
|
40
|
+
|
|
41
|
+
Supports options for specifying configuration file, data directory, logging preferences, version display, sample configuration generation, service installation, and configuration validation. On Windows, also accepts a deprecated positional argument for the config file path with a warning. Ignores unknown arguments outside of test environments and warns if any are present.
|
|
21
42
|
|
|
22
43
|
Returns:
|
|
23
|
-
argparse.Namespace:
|
|
44
|
+
argparse.Namespace: Parsed command-line arguments.
|
|
24
45
|
"""
|
|
25
46
|
parser = argparse.ArgumentParser(
|
|
26
47
|
description="Meshtastic Matrix Relay - Bridge between Meshtastic and Matrix"
|
|
@@ -61,16 +82,20 @@ def parse_arguments():
|
|
|
61
82
|
|
|
62
83
|
# Windows-specific handling for backward compatibility
|
|
63
84
|
# On Windows, add a positional argument for the config file path
|
|
64
|
-
if sys.platform ==
|
|
85
|
+
if sys.platform == WINDOWS_PLATFORM:
|
|
65
86
|
parser.add_argument(
|
|
66
87
|
"config_path", nargs="?", help=argparse.SUPPRESS, default=None
|
|
67
88
|
)
|
|
68
89
|
|
|
69
|
-
|
|
90
|
+
# Use parse_known_args to handle unknown arguments gracefully (e.g., pytest args)
|
|
91
|
+
args, unknown = parser.parse_known_args()
|
|
92
|
+
# If there are unknown arguments and we're not in a test environment, warn about them
|
|
93
|
+
if unknown and not any("pytest" in arg or "test" in arg for arg in sys.argv):
|
|
94
|
+
print(f"Warning: Unknown arguments ignored: {unknown}")
|
|
70
95
|
|
|
71
96
|
# If on Windows and a positional config path is provided but --config is not, use the positional one
|
|
72
97
|
if (
|
|
73
|
-
sys.platform ==
|
|
98
|
+
sys.platform == WINDOWS_PLATFORM
|
|
74
99
|
and hasattr(args, "config_path")
|
|
75
100
|
and args.config_path
|
|
76
101
|
and not args.config
|
|
@@ -105,15 +130,16 @@ def print_version():
|
|
|
105
130
|
|
|
106
131
|
def check_config(args=None):
|
|
107
132
|
"""
|
|
108
|
-
|
|
133
|
+
Validates the application's configuration file for required structure and fields.
|
|
109
134
|
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
If a configuration file is found, checks for the presence and correctness of required sections and keys, including Matrix and Meshtastic settings, and validates the format of matrix rooms. Prints errors or warnings for missing or deprecated fields. Returns True if the configuration is valid, otherwise False.
|
|
136
|
+
|
|
137
|
+
Parameters:
|
|
138
|
+
args: Parsed command-line arguments. If None, arguments are parsed internally.
|
|
112
139
|
|
|
113
140
|
Returns:
|
|
114
|
-
bool: True if the configuration is valid, False otherwise.
|
|
141
|
+
bool: True if the configuration file is valid, False otherwise.
|
|
115
142
|
"""
|
|
116
|
-
from mmrelay.config import get_config_paths
|
|
117
143
|
|
|
118
144
|
# If args is None, parse them now
|
|
119
145
|
if args is None:
|
|
@@ -137,12 +163,16 @@ def check_config(args=None):
|
|
|
137
163
|
return False
|
|
138
164
|
|
|
139
165
|
# Check matrix section
|
|
140
|
-
if
|
|
166
|
+
if CONFIG_SECTION_MATRIX not in config:
|
|
141
167
|
print("Error: Missing 'matrix' section in config")
|
|
142
168
|
return False
|
|
143
169
|
|
|
144
|
-
matrix_section = config[
|
|
145
|
-
required_matrix_fields = [
|
|
170
|
+
matrix_section = config[CONFIG_SECTION_MATRIX]
|
|
171
|
+
required_matrix_fields = [
|
|
172
|
+
CONFIG_KEY_HOMESERVER,
|
|
173
|
+
CONFIG_KEY_ACCESS_TOKEN,
|
|
174
|
+
CONFIG_KEY_BOT_USER_ID,
|
|
175
|
+
]
|
|
146
176
|
missing_matrix_fields = [
|
|
147
177
|
field
|
|
148
178
|
for field in required_matrix_fields
|
|
@@ -178,47 +208,55 @@ def check_config(args=None):
|
|
|
178
208
|
return False
|
|
179
209
|
|
|
180
210
|
# Check meshtastic section
|
|
181
|
-
if
|
|
211
|
+
if CONFIG_SECTION_MESHTASTIC not in config:
|
|
182
212
|
print("Error: Missing 'meshtastic' section in config")
|
|
183
213
|
return False
|
|
184
214
|
|
|
185
|
-
meshtastic_section = config[
|
|
215
|
+
meshtastic_section = config[CONFIG_SECTION_MESHTASTIC]
|
|
186
216
|
if "connection_type" not in meshtastic_section:
|
|
187
217
|
print("Error: Missing 'connection_type' in 'meshtastic' section")
|
|
188
218
|
return False
|
|
189
219
|
|
|
190
|
-
connection_type = meshtastic_section[
|
|
191
|
-
if connection_type not in [
|
|
220
|
+
connection_type = meshtastic_section[CONFIG_KEY_CONNECTION_TYPE]
|
|
221
|
+
if connection_type not in [
|
|
222
|
+
CONNECTION_TYPE_TCP,
|
|
223
|
+
CONNECTION_TYPE_SERIAL,
|
|
224
|
+
CONNECTION_TYPE_BLE,
|
|
225
|
+
CONNECTION_TYPE_NETWORK,
|
|
226
|
+
]:
|
|
192
227
|
print(
|
|
193
|
-
f"Error: Invalid 'connection_type': {connection_type}. Must be '
|
|
228
|
+
f"Error: Invalid 'connection_type': {connection_type}. Must be '{CONNECTION_TYPE_TCP}', '{CONNECTION_TYPE_SERIAL}', or '{CONNECTION_TYPE_BLE}'"
|
|
194
229
|
)
|
|
195
230
|
return False
|
|
196
231
|
|
|
197
232
|
# Check for deprecated connection_type
|
|
198
|
-
if connection_type ==
|
|
233
|
+
if connection_type == CONNECTION_TYPE_NETWORK:
|
|
199
234
|
print(
|
|
200
235
|
"\nWarning: 'network' connection_type is deprecated. Please use 'tcp' instead."
|
|
201
236
|
)
|
|
202
237
|
print(
|
|
203
|
-
"
|
|
238
|
+
"This option still works but may be removed in future versions.\n"
|
|
204
239
|
)
|
|
205
240
|
|
|
206
241
|
# Check connection-specific fields
|
|
207
242
|
if (
|
|
208
|
-
connection_type ==
|
|
209
|
-
and
|
|
243
|
+
connection_type == CONNECTION_TYPE_SERIAL
|
|
244
|
+
and CONFIG_KEY_SERIAL_PORT not in meshtastic_section
|
|
210
245
|
):
|
|
211
246
|
print("Error: Missing 'serial_port' for 'serial' connection type")
|
|
212
247
|
return False
|
|
213
248
|
|
|
214
249
|
if (
|
|
215
|
-
connection_type in [
|
|
216
|
-
and
|
|
250
|
+
connection_type in [CONNECTION_TYPE_TCP, CONNECTION_TYPE_NETWORK]
|
|
251
|
+
and CONFIG_KEY_HOST not in meshtastic_section
|
|
217
252
|
):
|
|
218
253
|
print("Error: Missing 'host' for 'tcp' connection type")
|
|
219
254
|
return False
|
|
220
255
|
|
|
221
|
-
if
|
|
256
|
+
if (
|
|
257
|
+
connection_type == CONNECTION_TYPE_BLE
|
|
258
|
+
and CONFIG_KEY_BLE_ADDRESS not in meshtastic_section
|
|
259
|
+
):
|
|
222
260
|
print("Error: Missing 'ble_address' for 'ble' connection type")
|
|
223
261
|
return False
|
|
224
262
|
|
|
@@ -228,7 +266,7 @@ def check_config(args=None):
|
|
|
228
266
|
"\nWarning: 'db' section is deprecated. Please use 'database' instead."
|
|
229
267
|
)
|
|
230
268
|
print(
|
|
231
|
-
"
|
|
269
|
+
"This option still works but may be removed in future versions.\n"
|
|
232
270
|
)
|
|
233
271
|
|
|
234
272
|
print("Configuration file is valid!")
|
|
@@ -248,36 +286,50 @@ def check_config(args=None):
|
|
|
248
286
|
|
|
249
287
|
|
|
250
288
|
def main():
|
|
251
|
-
"""
|
|
289
|
+
"""
|
|
290
|
+
Runs the Meshtastic Matrix Relay CLI, handling argument parsing, command execution, and error reporting.
|
|
252
291
|
|
|
253
292
|
Returns:
|
|
254
|
-
int: Exit code (0
|
|
293
|
+
int: Exit code indicating success (0) or failure (non-zero).
|
|
255
294
|
"""
|
|
256
|
-
|
|
295
|
+
try:
|
|
296
|
+
args = parse_arguments()
|
|
257
297
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
298
|
+
# Handle --check-config
|
|
299
|
+
if args.check_config:
|
|
300
|
+
return 0 if check_config(args) else 1
|
|
261
301
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
302
|
+
# Handle --install-service
|
|
303
|
+
if args.install_service:
|
|
304
|
+
try:
|
|
305
|
+
from mmrelay.setup_utils import install_service
|
|
265
306
|
|
|
266
|
-
|
|
307
|
+
return 0 if install_service() else 1
|
|
308
|
+
except ImportError as e:
|
|
309
|
+
print(f"Error importing setup utilities: {e}")
|
|
310
|
+
return 1
|
|
267
311
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
312
|
+
# Handle --generate-config
|
|
313
|
+
if args.generate_config:
|
|
314
|
+
return 0 if generate_sample_config() else 1
|
|
271
315
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
316
|
+
# Handle --version
|
|
317
|
+
if args.version:
|
|
318
|
+
print_version()
|
|
319
|
+
return 0
|
|
276
320
|
|
|
277
|
-
|
|
278
|
-
|
|
321
|
+
# If no command was specified, run the main functionality
|
|
322
|
+
try:
|
|
323
|
+
from mmrelay.main import run_main
|
|
279
324
|
|
|
280
|
-
|
|
325
|
+
return run_main(args)
|
|
326
|
+
except ImportError as e:
|
|
327
|
+
print(f"Error importing main module: {e}")
|
|
328
|
+
return 1
|
|
329
|
+
|
|
330
|
+
except Exception as e:
|
|
331
|
+
print(f"Unexpected error: {e}")
|
|
332
|
+
return 1
|
|
281
333
|
|
|
282
334
|
|
|
283
335
|
if __name__ == "__main__":
|
|
@@ -332,16 +384,17 @@ def handle_cli_commands(args):
|
|
|
332
384
|
|
|
333
385
|
|
|
334
386
|
def generate_sample_config():
|
|
335
|
-
"""
|
|
387
|
+
"""
|
|
388
|
+
Generate a sample configuration file (`config.yaml`) in the default location if one does not already exist.
|
|
389
|
+
|
|
390
|
+
Attempts to copy a sample config from various sources, handling directory creation and file system errors gracefully. Prints informative messages on success or failure.
|
|
336
391
|
|
|
337
392
|
Returns:
|
|
338
|
-
bool: True if the config was generated successfully, False otherwise.
|
|
393
|
+
bool: True if the sample config was generated successfully, False otherwise.
|
|
339
394
|
"""
|
|
340
395
|
|
|
341
396
|
import shutil
|
|
342
397
|
|
|
343
|
-
from mmrelay.config import get_config_paths
|
|
344
|
-
|
|
345
398
|
# Get the first config path (highest priority)
|
|
346
399
|
config_paths = get_config_paths()
|
|
347
400
|
|
|
@@ -362,8 +415,7 @@ def generate_sample_config():
|
|
|
362
415
|
# No config file exists, generate one in the first location
|
|
363
416
|
target_path = config_paths[0]
|
|
364
417
|
|
|
365
|
-
#
|
|
366
|
-
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
|
418
|
+
# Directory should already exist from get_config_paths() call
|
|
367
419
|
|
|
368
420
|
# Use the helper function to get the sample config path
|
|
369
421
|
sample_config_path = get_sample_config_path()
|
|
@@ -372,12 +424,16 @@ def generate_sample_config():
|
|
|
372
424
|
# Copy the sample config file to the target path
|
|
373
425
|
import shutil
|
|
374
426
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
427
|
+
try:
|
|
428
|
+
shutil.copy2(sample_config_path, target_path)
|
|
429
|
+
print(f"Generated sample config file at: {target_path}")
|
|
430
|
+
print(
|
|
431
|
+
"\nEdit this file with your Matrix and Meshtastic settings before running mmrelay."
|
|
432
|
+
)
|
|
433
|
+
return True
|
|
434
|
+
except (IOError, OSError) as e:
|
|
435
|
+
print(f"Error copying sample config file: {e}")
|
|
436
|
+
return False
|
|
381
437
|
|
|
382
438
|
# If the helper function failed, try using importlib.resources directly
|
|
383
439
|
try:
|
|
@@ -418,12 +474,16 @@ def generate_sample_config():
|
|
|
418
474
|
|
|
419
475
|
for path in sample_config_paths:
|
|
420
476
|
if os.path.exists(path):
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
477
|
+
try:
|
|
478
|
+
shutil.copy(path, target_path)
|
|
479
|
+
print(f"Generated sample config file at: {target_path}")
|
|
480
|
+
print(
|
|
481
|
+
"\nEdit this file with your Matrix and Meshtastic settings before running mmrelay."
|
|
482
|
+
)
|
|
483
|
+
return True
|
|
484
|
+
except (IOError, OSError) as e:
|
|
485
|
+
print(f"Error copying sample config file from {path}: {e}")
|
|
486
|
+
return False
|
|
427
487
|
|
|
428
488
|
print("Error: Could not find sample_config.yaml")
|
|
429
489
|
return False
|
|
@@ -6,10 +6,14 @@ import platformdirs
|
|
|
6
6
|
import yaml
|
|
7
7
|
from yaml.loader import SafeLoader
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
# Import application constants
|
|
10
|
+
from mmrelay.constants.app import APP_AUTHOR, APP_NAME
|
|
11
|
+
from mmrelay.constants.config import (
|
|
12
|
+
CONFIG_KEY_ACCESS_TOKEN,
|
|
13
|
+
CONFIG_KEY_BOT_USER_ID,
|
|
14
|
+
CONFIG_KEY_HOMESERVER,
|
|
15
|
+
CONFIG_SECTION_MATRIX,
|
|
16
|
+
)
|
|
13
17
|
|
|
14
18
|
# Global variable to store the custom data directory
|
|
15
19
|
custom_data_dir = None
|
|
@@ -49,14 +53,15 @@ def get_app_path():
|
|
|
49
53
|
|
|
50
54
|
def get_config_paths(args=None):
|
|
51
55
|
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
Return a prioritized list of possible configuration file paths for the application.
|
|
57
|
+
|
|
58
|
+
The search order is: a command-line specified path (if provided), the user config directory, the current working directory, and the application directory. The user config directory is skipped if it cannot be created due to permission or OS errors.
|
|
59
|
+
|
|
60
|
+
Parameters:
|
|
61
|
+
args: Parsed command-line arguments, expected to have a 'config' attribute specifying a config file path.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
List of absolute paths to candidate configuration files, ordered by priority.
|
|
60
65
|
"""
|
|
61
66
|
paths = []
|
|
62
67
|
|
|
@@ -72,9 +77,13 @@ def get_config_paths(args=None):
|
|
|
72
77
|
# Use platformdirs default for Windows
|
|
73
78
|
user_config_dir = platformdirs.user_config_dir(APP_NAME, APP_AUTHOR)
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
try:
|
|
81
|
+
os.makedirs(user_config_dir, exist_ok=True)
|
|
82
|
+
user_config_path = os.path.join(user_config_dir, "config.yaml")
|
|
83
|
+
paths.append(user_config_path)
|
|
84
|
+
except (OSError, PermissionError):
|
|
85
|
+
# If we can't create the user config directory, skip it
|
|
86
|
+
pass
|
|
78
87
|
|
|
79
88
|
# Check current directory (for backward compatibility)
|
|
80
89
|
current_dir_config = os.path.join(os.getcwd(), "config.yaml")
|
|
@@ -164,14 +173,12 @@ config_path = None
|
|
|
164
173
|
|
|
165
174
|
def set_config(module, passed_config):
|
|
166
175
|
"""
|
|
167
|
-
|
|
176
|
+
Assigns the provided configuration dictionary to a module and sets additional attributes for known module types.
|
|
168
177
|
|
|
169
|
-
|
|
170
|
-
module: The module to set the configuration for
|
|
171
|
-
passed_config: The configuration dictionary to use
|
|
178
|
+
For modules named "matrix_utils" or "meshtastic_utils", sets specific configuration attributes if present. Calls the module's `setup_config()` method if it exists for backward compatibility.
|
|
172
179
|
|
|
173
180
|
Returns:
|
|
174
|
-
The
|
|
181
|
+
dict: The configuration dictionary that was assigned to the module.
|
|
175
182
|
"""
|
|
176
183
|
# Set the module's config variable
|
|
177
184
|
module.config = passed_config
|
|
@@ -181,11 +188,20 @@ def set_config(module, passed_config):
|
|
|
181
188
|
|
|
182
189
|
if module_name == "matrix_utils":
|
|
183
190
|
# Set Matrix-specific configuration
|
|
184
|
-
if
|
|
185
|
-
module
|
|
191
|
+
if (
|
|
192
|
+
hasattr(module, "matrix_homeserver")
|
|
193
|
+
and CONFIG_SECTION_MATRIX in passed_config
|
|
194
|
+
):
|
|
195
|
+
module.matrix_homeserver = passed_config[CONFIG_SECTION_MATRIX][
|
|
196
|
+
CONFIG_KEY_HOMESERVER
|
|
197
|
+
]
|
|
186
198
|
module.matrix_rooms = passed_config["matrix_rooms"]
|
|
187
|
-
module.matrix_access_token = passed_config[
|
|
188
|
-
|
|
199
|
+
module.matrix_access_token = passed_config[CONFIG_SECTION_MATRIX][
|
|
200
|
+
CONFIG_KEY_ACCESS_TOKEN
|
|
201
|
+
]
|
|
202
|
+
module.bot_user_id = passed_config[CONFIG_SECTION_MATRIX][
|
|
203
|
+
CONFIG_KEY_BOT_USER_ID
|
|
204
|
+
]
|
|
189
205
|
|
|
190
206
|
elif module_name == "meshtastic_utils":
|
|
191
207
|
# Set Meshtastic-specific configuration
|
|
@@ -200,24 +216,31 @@ def set_config(module, passed_config):
|
|
|
200
216
|
|
|
201
217
|
|
|
202
218
|
def load_config(config_file=None, args=None):
|
|
203
|
-
"""
|
|
219
|
+
"""
|
|
220
|
+
Load the application configuration from a specified file or by searching standard locations.
|
|
221
|
+
|
|
222
|
+
If a config file path is provided and valid, attempts to load and parse it as YAML. If not, searches for a configuration file in prioritized locations and loads the first valid one found. Returns an empty dictionary if no valid configuration is found or if loading fails due to file or YAML errors.
|
|
204
223
|
|
|
205
|
-
|
|
206
|
-
config_file (str, optional): Path to
|
|
207
|
-
args:
|
|
224
|
+
Parameters:
|
|
225
|
+
config_file (str, optional): Path to a specific configuration file. If None, searches default locations.
|
|
226
|
+
args: Parsed command-line arguments, used to determine config search order.
|
|
208
227
|
|
|
209
228
|
Returns:
|
|
210
|
-
dict: The loaded configuration
|
|
229
|
+
dict: The loaded configuration dictionary, or an empty dictionary if loading fails.
|
|
211
230
|
"""
|
|
212
231
|
global relay_config, config_path
|
|
213
232
|
|
|
214
233
|
# If a specific config file was provided, use it
|
|
215
234
|
if config_file and os.path.isfile(config_file):
|
|
216
235
|
# Store the config path but don't log it yet - will be logged by main.py
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
236
|
+
try:
|
|
237
|
+
with open(config_file, "r") as f:
|
|
238
|
+
relay_config = yaml.load(f, Loader=SafeLoader)
|
|
239
|
+
config_path = config_file
|
|
240
|
+
return relay_config
|
|
241
|
+
except (yaml.YAMLError, PermissionError, OSError) as e:
|
|
242
|
+
logger.error(f"Error loading config file {config_file}: {e}")
|
|
243
|
+
return {}
|
|
221
244
|
|
|
222
245
|
# Otherwise, search for a config file
|
|
223
246
|
config_paths = get_config_paths(args)
|
|
@@ -227,9 +250,13 @@ def load_config(config_file=None, args=None):
|
|
|
227
250
|
if os.path.isfile(path):
|
|
228
251
|
config_path = path
|
|
229
252
|
# Store the config path but don't log it yet - will be logged by main.py
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
253
|
+
try:
|
|
254
|
+
with open(config_path, "r") as f:
|
|
255
|
+
relay_config = yaml.load(f, Loader=SafeLoader)
|
|
256
|
+
return relay_config
|
|
257
|
+
except (yaml.YAMLError, PermissionError, OSError) as e:
|
|
258
|
+
logger.error(f"Error loading config file {path}: {e}")
|
|
259
|
+
continue # Try the next config path
|
|
233
260
|
|
|
234
261
|
# No config file found
|
|
235
262
|
logger.error("Configuration file not found in any of the following locations:")
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
+
"""
|
|
5
|
+
Configuration checker module for MMRelay.
|
|
6
|
+
|
|
7
|
+
Note: This module contains similar functionality to the check_config function
|
|
8
|
+
in mmrelay.cli. The CLI version is more complete and uses centralized constants.
|
|
9
|
+
Future refactoring should consider consolidating these implementations to reduce
|
|
10
|
+
code duplication while maintaining backward compatibility and test coverage.
|
|
11
|
+
"""
|
|
12
|
+
|
|
4
13
|
import os
|
|
5
14
|
|
|
6
15
|
import yaml
|
|
7
16
|
from yaml.loader import SafeLoader
|
|
8
17
|
|
|
18
|
+
from mmrelay.constants.network import (
|
|
19
|
+
CONFIG_KEY_BLE_ADDRESS,
|
|
20
|
+
CONFIG_KEY_CONNECTION_TYPE,
|
|
21
|
+
CONFIG_KEY_HOST,
|
|
22
|
+
CONFIG_KEY_SERIAL_PORT,
|
|
23
|
+
CONNECTION_TYPE_BLE,
|
|
24
|
+
CONNECTION_TYPE_SERIAL,
|
|
25
|
+
CONNECTION_TYPE_TCP,
|
|
26
|
+
)
|
|
27
|
+
|
|
9
28
|
|
|
10
29
|
def get_config_paths():
|
|
11
30
|
"""
|
|
12
|
-
|
|
31
|
+
Return a list of possible file paths where the mmrelay configuration file may be located.
|
|
13
32
|
|
|
14
33
|
Returns:
|
|
15
|
-
list:
|
|
34
|
+
list: Paths to potential configuration files.
|
|
16
35
|
"""
|
|
17
36
|
from mmrelay.config import get_config_paths as get_paths
|
|
18
37
|
|
|
@@ -21,10 +40,10 @@ def get_config_paths():
|
|
|
21
40
|
|
|
22
41
|
def check_config():
|
|
23
42
|
"""
|
|
24
|
-
|
|
43
|
+
Validates the mmrelay configuration file by checking for required sections and fields.
|
|
25
44
|
|
|
26
45
|
Returns:
|
|
27
|
-
bool: True if
|
|
46
|
+
bool: True if a valid configuration file is found and passes all checks; False otherwise.
|
|
28
47
|
"""
|
|
29
48
|
config_paths = get_config_paths()
|
|
30
49
|
config_path = None
|
|
@@ -90,30 +109,40 @@ def check_config():
|
|
|
90
109
|
return False
|
|
91
110
|
|
|
92
111
|
meshtastic_section = config["meshtastic"]
|
|
93
|
-
if
|
|
112
|
+
if CONFIG_KEY_CONNECTION_TYPE not in meshtastic_section:
|
|
94
113
|
print("Error: Missing 'connection_type' in 'meshtastic' section")
|
|
95
114
|
return False
|
|
96
115
|
|
|
97
|
-
connection_type = meshtastic_section[
|
|
98
|
-
if connection_type not in [
|
|
116
|
+
connection_type = meshtastic_section[CONFIG_KEY_CONNECTION_TYPE]
|
|
117
|
+
if connection_type not in [
|
|
118
|
+
CONNECTION_TYPE_TCP,
|
|
119
|
+
CONNECTION_TYPE_SERIAL,
|
|
120
|
+
CONNECTION_TYPE_BLE,
|
|
121
|
+
]:
|
|
99
122
|
print(
|
|
100
|
-
f"Error: Invalid 'connection_type': {connection_type}. Must be '
|
|
123
|
+
f"Error: Invalid 'connection_type': {connection_type}. Must be '{CONNECTION_TYPE_TCP}', '{CONNECTION_TYPE_SERIAL}', or '{CONNECTION_TYPE_BLE}'"
|
|
101
124
|
)
|
|
102
125
|
return False
|
|
103
126
|
|
|
104
127
|
# Check connection-specific fields
|
|
105
128
|
if (
|
|
106
|
-
connection_type ==
|
|
107
|
-
and
|
|
129
|
+
connection_type == CONNECTION_TYPE_SERIAL
|
|
130
|
+
and CONFIG_KEY_SERIAL_PORT not in meshtastic_section
|
|
108
131
|
):
|
|
109
132
|
print("Error: Missing 'serial_port' for 'serial' connection type")
|
|
110
133
|
return False
|
|
111
134
|
|
|
112
|
-
if
|
|
135
|
+
if (
|
|
136
|
+
connection_type == CONNECTION_TYPE_TCP
|
|
137
|
+
and CONFIG_KEY_HOST not in meshtastic_section
|
|
138
|
+
):
|
|
113
139
|
print("Error: Missing 'host' for 'tcp' connection type")
|
|
114
140
|
return False
|
|
115
141
|
|
|
116
|
-
if
|
|
142
|
+
if (
|
|
143
|
+
connection_type == CONNECTION_TYPE_BLE
|
|
144
|
+
and CONFIG_KEY_BLE_ADDRESS not in meshtastic_section
|
|
145
|
+
):
|
|
117
146
|
print("Error: Missing 'ble_address' for 'ble' connection type")
|
|
118
147
|
return False
|
|
119
148
|
|