mmrelay 1.2.2__tar.gz → 1.2.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.

Files changed (114) hide show
  1. {mmrelay-1.2.2/src/mmrelay.egg-info → mmrelay-1.2.4}/PKG-INFO +2 -2
  2. {mmrelay-1.2.2 → mmrelay-1.2.4}/requirements.txt +2 -2
  3. {mmrelay-1.2.2 → mmrelay-1.2.4}/setup.py +1 -1
  4. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/__init__.py +1 -1
  5. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/cli.py +27 -30
  6. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/config.py +14 -16
  7. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/constants/queue.py +4 -1
  8. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/e2ee_utils.py +7 -2
  9. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/log_utils.py +29 -8
  10. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/main.py +14 -5
  11. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/matrix_utils.py +73 -73
  12. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/meshtastic_utils.py +20 -21
  13. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/message_queue.py +16 -15
  14. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/base_plugin.py +7 -7
  15. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/mesh_relay_plugin.py +16 -17
  16. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/setup_utils.py +5 -5
  17. mmrelay-1.2.4/src/mmrelay/tools/sample-docker-compose-prebuilt.yaml +30 -0
  18. mmrelay-1.2.4/src/mmrelay/tools/sample-docker-compose.yaml +30 -0
  19. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/windows_utils.py +1 -1
  20. {mmrelay-1.2.2 → mmrelay-1.2.4/src/mmrelay.egg-info}/PKG-INFO +2 -2
  21. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay.egg-info/requires.txt +1 -1
  22. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_auth_flow_fixes.py +5 -8
  23. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_base_plugin.py +4 -4
  24. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_e2ee_unified.py +153 -15
  25. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_log_utils.py +68 -0
  26. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_main_entry_point.py +1 -1
  27. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_matrix_utils.py +604 -151
  28. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_matrix_utils_error_handling.py +3 -3
  29. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_mesh_relay_plugin.py +86 -20
  30. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_performance_stress.py +10 -15
  31. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_plugin_loader.py +141 -3
  32. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_setup_utils.py +2 -2
  33. mmrelay-1.2.2/src/mmrelay/tools/sample-docker-compose-prebuilt.yaml +0 -19
  34. mmrelay-1.2.2/src/mmrelay/tools/sample-docker-compose.yaml +0 -19
  35. {mmrelay-1.2.2 → mmrelay-1.2.4}/LICENSE +0 -0
  36. {mmrelay-1.2.2 → mmrelay-1.2.4}/MANIFEST.in +0 -0
  37. {mmrelay-1.2.2 → mmrelay-1.2.4}/README.md +0 -0
  38. {mmrelay-1.2.2 → mmrelay-1.2.4}/pyproject.toml +0 -0
  39. {mmrelay-1.2.2 → mmrelay-1.2.4}/setup.cfg +0 -0
  40. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/__main__.py +0 -0
  41. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/cli_utils.py +0 -0
  42. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/constants/__init__.py +0 -0
  43. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/constants/app.py +0 -0
  44. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/constants/config.py +0 -0
  45. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/constants/database.py +0 -0
  46. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/constants/formats.py +0 -0
  47. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/constants/messages.py +0 -0
  48. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/constants/network.py +0 -0
  49. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/db_utils.py +0 -0
  50. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugin_loader.py +0 -0
  51. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/__init__.py +0 -0
  52. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/debug_plugin.py +0 -0
  53. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/drop_plugin.py +0 -0
  54. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/health_plugin.py +0 -0
  55. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/help_plugin.py +0 -0
  56. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/map_plugin.py +0 -0
  57. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/nodes_plugin.py +0 -0
  58. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/ping_plugin.py +0 -0
  59. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/telemetry_plugin.py +0 -0
  60. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/plugins/weather_plugin.py +0 -0
  61. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/runtime_utils.py +0 -0
  62. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/tools/__init__.py +0 -0
  63. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/tools/mmrelay.service +0 -0
  64. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/tools/sample.env +0 -0
  65. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay/tools/sample_config.yaml +0 -0
  66. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay.egg-info/SOURCES.txt +0 -0
  67. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay.egg-info/dependency_links.txt +0 -0
  68. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay.egg-info/entry_points.txt +0 -0
  69. {mmrelay-1.2.2 → mmrelay-1.2.4}/src/mmrelay.egg-info/top_level.txt +0 -0
  70. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_async_patterns.py +0 -0
  71. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_cli.py +0 -0
  72. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_cli_diagnose.py +0 -0
  73. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_cli_edge_cases.py +0 -0
  74. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_cli_utils.py +0 -0
  75. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_cli_windows_integration.py +0 -0
  76. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_config.py +0 -0
  77. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_config_checker.py +0 -0
  78. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_config_edge_cases.py +0 -0
  79. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_constants.py +0 -0
  80. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_core_utils_coverage.py +0 -0
  81. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_db_utils.py +0 -0
  82. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_db_utils_edge_cases.py +0 -0
  83. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_debug_plugin.py +0 -0
  84. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_drop_plugin.py +0 -0
  85. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_e2ee_encryption.py +0 -0
  86. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_e2ee_integration.py +0 -0
  87. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_e2ee_runner.py +0 -0
  88. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_error_boundaries.py +0 -0
  89. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_health_plugin.py +0 -0
  90. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_help_plugin.py +0 -0
  91. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_imports.py +0 -0
  92. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_integration_scenarios.py +0 -0
  93. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_main.py +0 -0
  94. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_map_plugin.py +0 -0
  95. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_matrix_utils_edge_cases.py +0 -0
  96. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_meshtastic_broadcast_enabled.py +0 -0
  97. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_meshtastic_utils.py +0 -0
  98. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_meshtastic_utils_edge_cases.py +0 -0
  99. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_message_queue.py +0 -0
  100. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_message_queue_edge_cases.py +0 -0
  101. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_network_reliability.py +0 -0
  102. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_nodes_plugin.py +0 -0
  103. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_patch_coverage_improvements.py +0 -0
  104. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_ping_plugin.py +0 -0
  105. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_plugin_loader_edge_cases.py +0 -0
  106. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_prefix_customization.py +0 -0
  107. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_runtime_no_errors.py +0 -0
  108. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_setup_utils_edge_cases.py +0 -0
  109. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_setup_utils_execstart_improvements.py +0 -0
  110. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_setup_utils_systemctl.py +0 -0
  111. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_telemetry_plugin.py +0 -0
  112. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_tools_init.py +0 -0
  113. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_weather_plugin.py +0 -0
  114. {mmrelay-1.2.2 → mmrelay-1.2.4}/tests/test_windows_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmrelay
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
5
5
  Home-page: https://github.com/jeremiah-k/meshtastic-matrix-relay
6
6
  Author: Geoff Whittington, Jeremiah K., and contributors
@@ -18,7 +18,7 @@ Classifier: Topic :: Communications
18
18
  Requires-Python: >=3.9
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
- Requires-Dist: meshtastic>=2.6.4
21
+ Requires-Dist: meshtastic>=2.7.3
22
22
  Requires-Dist: Pillow==11.3.0
23
23
  Requires-Dist: matrix-nio==0.25.2
24
24
  Requires-Dist: matplotlib==3.10.1
@@ -1,4 +1,4 @@
1
- meshtastic>=2.6.4
1
+ meshtastic>=2.7.3
2
2
  Pillow==11.3.0
3
3
  matrix-nio==0.25.2
4
4
  matplotlib==3.10.1
@@ -16,7 +16,7 @@ setuptools>=80.9.0 # Required for console script entry points and Windows compa
16
16
  # Testing and coverage
17
17
  coverage==7.10.7
18
18
  pytest==8.4.2
19
- pytest-cov==6.3.0
19
+ pytest-cov==7.0.0
20
20
  pytest-asyncio==1.2.0
21
21
  pytest-env==1.1.5
22
22
  pytest-timeout==2.4.0
@@ -44,7 +44,7 @@ setup(
44
44
  ],
45
45
  python_requires=">=3.9",
46
46
  install_requires=[
47
- "meshtastic>=2.6.4",
47
+ "meshtastic>=2.7.3",
48
48
  "Pillow==11.3.0",
49
49
  "matrix-nio==0.25.2",
50
50
  "matplotlib==3.10.1",
@@ -2,4 +2,4 @@
2
2
  Meshtastic Matrix Relay - Bridge between Meshtastic mesh networks and Matrix chat rooms.
3
3
  """
4
4
 
5
- __version__ = "1.2.2"
5
+ __version__ = "1.2.4"
@@ -238,11 +238,11 @@ def print_version():
238
238
  def _validate_e2ee_dependencies():
239
239
  """
240
240
  Check whether end-to-end encryption (E2EE) is usable on the current platform.
241
-
241
+
242
242
  Returns:
243
243
  bool: True if the platform is supported and required E2EE libraries can be imported;
244
244
  False otherwise.
245
-
245
+
246
246
  Notes:
247
247
  - This function performs only local checks (platform and importability) and does not perform
248
248
  network I/O.
@@ -1499,13 +1499,13 @@ def handle_auth_logout(args):
1499
1499
  def handle_service_command(args):
1500
1500
  """
1501
1501
  Dispatch service-related subcommands.
1502
-
1502
+
1503
1503
  Currently supports the "install" subcommand which imports and runs mmrelay.setup_utils.install_service().
1504
1504
  Returns 0 on successful installation, 1 on failure or for unknown subcommands.
1505
-
1505
+
1506
1506
  Parameters:
1507
1507
  args: argparse.Namespace with a `service_command` attribute indicating the requested action.
1508
-
1508
+
1509
1509
  Returns:
1510
1510
  int: Exit code (0 on success, 1 on error).
1511
1511
  """
@@ -1524,15 +1524,15 @@ def handle_service_command(args):
1524
1524
 
1525
1525
  def _diagnose_config_paths(args):
1526
1526
  """
1527
- Print a diagnostic summary of resolved configuration file search paths and their directory accessibility.
1528
-
1529
- Uses get_config_paths(args) to compute the ordered list of candidate config file locations, then prints each path with a concise directory status icon:
1527
+ Prints a diagnostic summary of resolved configuration file search paths and their directory accessibility.
1528
+
1529
+ Each candidate config path is printed with a status icon:
1530
1530
  - ✅ directory exists and is writable
1531
1531
  - ⚠️ directory exists but is not writable
1532
1532
  - ❌ directory does not exist
1533
-
1533
+
1534
1534
  Parameters:
1535
- args (argparse.Namespace): Parsed CLI arguments used to determine the config search order.
1535
+ args (argparse.Namespace): CLI arguments used to determine the ordered list of candidate config paths (passed to get_config_paths).
1536
1536
  """
1537
1537
  print("1. Testing configuration paths...")
1538
1538
  from mmrelay.config import get_config_paths
@@ -1551,11 +1551,11 @@ def _diagnose_config_paths(args):
1551
1551
  def _diagnose_sample_config_accessibility():
1552
1552
  """
1553
1553
  Check availability of the bundled sample configuration and print a short diagnostic.
1554
-
1554
+
1555
1555
  Performs two non-destructive checks and prints human-readable results:
1556
1556
  1) Verifies whether the sample config file exists at the path returned by mmrelay.tools.get_sample_config_path().
1557
1557
  2) Attempts to read the embedded resource "sample_config.yaml" from the mmrelay.tools package via importlib.resources and reports success and the content length.
1558
-
1558
+
1559
1559
  Returns:
1560
1560
  bool: True if a filesystem sample config exists at the resolved path; False otherwise.
1561
1561
  """
@@ -1586,18 +1586,15 @@ def _diagnose_sample_config_accessibility():
1586
1586
 
1587
1587
  def _diagnose_platform_specific(args):
1588
1588
  """
1589
- Run platform-specific diagnostic checks.
1590
-
1591
- On Windows, imports and runs Windows-specific requirement checks and a configuration-generation
1592
- test from mmrelay.windows_utils, printing per-component results and any warnings. On non-Windows
1593
- platforms this reports that platform-specific tests are not required.
1594
-
1589
+ Run platform-specific diagnostic checks and print a concise report.
1590
+
1591
+ On Windows, executes Windows-specific requirement checks and a configuration-generation test using the provided CLI arguments; on non-Windows platforms, reports that platform-specific tests are not required.
1592
+
1595
1593
  Parameters:
1596
- args (argparse.Namespace): Parsed CLI arguments; passed through to the Windows
1597
- config-generation test when running on Windows.
1598
-
1594
+ args (argparse.Namespace): CLI arguments forwarded to the Windows configuration-generation test (used only when running on Windows).
1595
+
1599
1596
  Returns:
1600
- bool: True if the platform is Windows (Windows checks were executed), False otherwise.
1597
+ bool: `True` if Windows checks were executed (running on Windows), `False` otherwise.
1601
1598
  """
1602
1599
  print("3. Platform-specific diagnostics...")
1603
1600
  import sys
@@ -1704,7 +1701,7 @@ logging:
1704
1701
  def _diagnose_minimal_config_template():
1705
1702
  """
1706
1703
  Validate the built-in minimal YAML configuration template and print a concise pass/fail status.
1707
-
1704
+
1708
1705
  Attempts to parse the string returned by _get_minimal_config_template() using yaml.safe_load. Prints a single-line result showing a ✅ with the template character length when the template is valid YAML, or a ❌ with the YAML parsing error when invalid. This is a non-destructive diagnostic helper that prints output and does not return a value.
1709
1706
  """
1710
1707
  print("4. Testing minimal config template fallback...")
@@ -1720,15 +1717,15 @@ def _diagnose_minimal_config_template():
1720
1717
 
1721
1718
  def handle_config_diagnose(args):
1722
1719
  """
1723
- Run non-destructive diagnostics for the MMRelay configuration subsystem and print a human-readable report.
1724
-
1725
- Performs four checks: resolves candidate config paths and reports directory accessibility; verifies packaged sample config availability (filesystem and importlib.resources fallback); runs platform-specific diagnostics (Windows-focused where applicable); and validates the built-in minimal YAML template. Prints findings and suggested next steps to stdout/stderr.
1726
-
1720
+ Run a set of non-destructive diagnostics for the MMRelay configuration subsystem and print a concise, human-readable report.
1721
+
1722
+ Performs four checks without modifying user files: (1) resolves and reports candidate configuration file paths and their directory accessibility, (2) verifies availability and readability of the packaged sample configuration, (3) executes platform-specific diagnostics (Windows checks when applicable), and (4) validates the built-in minimal YAML configuration template. Results and actionable guidance are written to stdout/stderr; additional Windows-specific guidance may be printed to stderr on unexpected failures.
1723
+
1727
1724
  Parameters:
1728
- args (argparse.Namespace): Parsed CLI arguments used to resolve config search paths and to control platform-specific checks.
1729
-
1725
+ args (argparse.Namespace): Parsed CLI arguments used to determine configuration search paths and to control platform-specific diagnostic behavior.
1726
+
1730
1727
  Returns:
1731
- int: Exit code (0 on success, 1 on failure).
1728
+ int: Exit code where `0` indicates diagnostics completed successfully and `1` indicates a failure occurred (an error summary is printed to stderr).
1732
1729
  """
1733
1730
  print("MMRelay Configuration System Diagnostics")
1734
1731
  print("=" * 40)
@@ -196,14 +196,12 @@ def get_log_dir():
196
196
 
197
197
  def get_e2ee_store_dir():
198
198
  """
199
- Return the absolute path to the E2EE data store directory, creating it if missing.
200
-
201
- On Linux and macOS this is "<base_dir>/store" where base_dir is returned by get_base_dir().
202
- On Windows this is "<custom_data_dir>/store" when module-level custom_data_dir is set; otherwise it uses
203
- platformdirs.user_data_dir(APP_NAME, APP_AUTHOR)/store.
204
-
199
+ Get the absolute path to the application's end-to-end encryption (E2EE) data store directory, creating it if necessary.
200
+
201
+ On Linux and macOS the directory is located under the application base directory; on Windows it uses the configured custom data directory when set, otherwise the platform-specific user data directory. The directory will be created if it does not exist.
202
+
205
203
  Returns:
206
- str: Absolute path to the ensured store directory. The directory is created if it does not exist.
204
+ store_dir (str): Absolute path to the ensured E2EE store directory.
207
205
  """
208
206
  if sys.platform in ["linux", "darwin"]:
209
207
  # Use ~/.mmrelay/store/ for Linux and Mac
@@ -388,12 +386,12 @@ def is_e2ee_enabled(config):
388
386
  def check_e2ee_enabled_silently(args=None):
389
387
  """
390
388
  Check silently whether End-to-End Encryption (E2EE) is enabled in the first readable configuration file.
391
-
389
+
392
390
  Searches candidate configuration paths returned by get_config_paths(args) in priority order, loads the first readable YAML file, and returns True if that configuration enables E2EE (via is_e2ee_enabled). I/O and YAML parsing errors are ignored and the function continues to the next candidate. On Windows this always returns False.
393
-
391
+
394
392
  Parameters:
395
393
  args (optional): Parsed command-line arguments that can influence config search order.
396
-
394
+
397
395
  Returns:
398
396
  bool: True if E2EE is enabled in the first valid configuration file found; otherwise False.
399
397
  """
@@ -493,16 +491,16 @@ def load_credentials():
493
491
  def save_credentials(credentials):
494
492
  """
495
493
  Persist a JSON-serializable credentials mapping to <base_dir>/credentials.json.
496
-
494
+
497
495
  Writes the provided credentials (a JSON-serializable mapping) to the application's
498
496
  base configuration directory as credentials.json, creating the base directory if
499
497
  necessary. On Unix-like systems the file permissions are adjusted to be
500
498
  restrictive (0o600) when possible. I/O and permission errors are caught and
501
499
  logged; the function does not raise them.
502
-
500
+
503
501
  Parameters:
504
502
  credentials (dict): JSON-serializable mapping of credentials to persist.
505
-
503
+
506
504
  Returns:
507
505
  None
508
506
  """
@@ -822,18 +820,18 @@ def load_config(config_file=None, args=None):
822
820
  def validate_yaml_syntax(config_content, config_path):
823
821
  """
824
822
  Validate YAML text for syntax and common style issues, parse it with PyYAML, and return results.
825
-
823
+
826
824
  Performs lightweight line-based checks for frequent mistakes (using '=' instead of ':'
827
825
  for mappings and non-standard boolean words like 'yes'/'no' or 'on'/'off') and then
828
826
  attempts to parse the content with yaml.safe_load. If only style warnings are found,
829
827
  parsing is considered successful and warnings are returned; if parsing fails or true
830
828
  syntax errors are detected, a detailed error message is returned that references
831
829
  config_path to identify the source.
832
-
830
+
833
831
  Parameters:
834
832
  config_content (str): Raw YAML text to validate.
835
833
  config_path (str): Path or label used in error messages to identify the source of the content.
836
-
834
+
837
835
  Returns:
838
836
  tuple:
839
837
  is_valid (bool): True if YAML parsed successfully (style warnings allowed), False on syntax/parsing error.
@@ -6,7 +6,10 @@ delays, size limits, and water marks for queue management.
6
6
  """
7
7
 
8
8
  # Message timing constants
9
- DEFAULT_MESSAGE_DELAY = 2.0 # Firmware-enforced minimum delay in seconds
9
+ DEFAULT_MESSAGE_DELAY = (
10
+ 2.5 # Set above the 2.0s firmware limit to prevent message dropping
11
+ )
12
+ MINIMUM_MESSAGE_DELAY = 2.1 # Minimum delay enforced to stay above firmware limit
10
13
 
11
14
  # Queue size management
12
15
  MAX_QUEUE_SIZE = 500
@@ -80,8 +80,13 @@ def get_e2ee_status(
80
80
  importlib.import_module("olm")
81
81
 
82
82
  if os.getenv("MMRELAY_TESTING") != "1":
83
- importlib.import_module("nio.crypto").OlmDevice
84
- importlib.import_module("nio.store").SqliteStore
83
+ nio_crypto = importlib.import_module("nio.crypto")
84
+ if not hasattr(nio_crypto, "OlmDevice"):
85
+ raise ImportError("nio.crypto.OlmDevice is unavailable")
86
+
87
+ nio_store = importlib.import_module("nio.store")
88
+ if not hasattr(nio_store, "SqliteStore"):
89
+ raise ImportError("nio.store.SqliteStore is unavailable")
85
90
 
86
91
  status["dependencies_installed"] = True
87
92
  except ImportError:
@@ -69,14 +69,13 @@ _COMPONENT_LOGGERS = {
69
69
 
70
70
  def configure_component_debug_logging():
71
71
  """
72
- Configure log levels for external component loggers based on config["logging"]["debug"].
72
+ Configure log levels and handlers for external component loggers based on config.
73
73
 
74
- Reads per-component entries under `config["logging"]["debug"]` and applies one of:
75
- - falsy or missing: silence the component by setting its loggers to CRITICAL+1
76
- - boolean True: enable DEBUG for the component's loggers
77
- - string: interpret as a logging level name (case-insensitive); invalid names fall back to DEBUG
74
+ Reads `config["logging"]["debug"]` and for each component:
75
+ - If enabled (True or a valid log level string), sets the component's loggers to the specified level and attaches the main application's handlers to them. This makes component logs appear in the console and log file.
76
+ - If disabled (falsy or missing), silences the component by setting its loggers to a level higher than CRITICAL.
78
77
 
79
- This function mutates the levels of loggers listed in _COMPONENT_LOGGERS and runs only once per process; no-op if called again or if global `config` is None.
78
+ This function runs only once. It is not thread-safe and should be called early in the application startup, after the main logger is configured but before other modules are imported.
80
79
  """
81
80
  global _component_debug_configured, config
82
81
 
@@ -84,7 +83,22 @@ def configure_component_debug_logging():
84
83
  if _component_debug_configured or config is None:
85
84
  return
86
85
 
87
- debug_config = config.get("logging", {}).get("debug", {})
86
+ # Get the main application logger and its handlers to attach to component loggers
87
+ main_logger = logging.getLogger(APP_DISPLAY_NAME)
88
+ main_handlers = main_logger.handlers
89
+ debug_settings = config.get("logging", {}).get("debug")
90
+
91
+ # Ensure debug_config is a dictionary, handling malformed configs gracefully
92
+ if isinstance(debug_settings, dict):
93
+ debug_config = debug_settings
94
+ else:
95
+ if debug_settings is not None:
96
+ main_logger.warning(
97
+ "Debug logging section is not a dictionary. "
98
+ "All component debug logging will be disabled. "
99
+ "Check your config.yaml debug section formatting."
100
+ )
101
+ debug_config = {}
88
102
 
89
103
  for component, loggers in _COMPONENT_LOGGERS.items():
90
104
  component_config = debug_config.get(component)
@@ -105,8 +119,15 @@ def configure_component_debug_logging():
105
119
  # Invalid config, fall back to DEBUG
106
120
  log_level = logging.DEBUG
107
121
 
122
+ # Configure all loggers for this component
108
123
  for logger_name in loggers:
109
- logging.getLogger(logger_name).setLevel(log_level)
124
+ component_logger = logging.getLogger(logger_name)
125
+ component_logger.setLevel(log_level)
126
+ component_logger.propagate = False # Prevent duplicate logging
127
+ # Attach main handlers to the component logger
128
+ for handler in main_handlers:
129
+ if handler not in component_logger.handlers:
130
+ component_logger.addHandler(handler)
110
131
  else:
111
132
  # Component debug is disabled - completely suppress external library logging
112
133
  # Use a level higher than CRITICAL to effectively disable all messages
@@ -73,9 +73,18 @@ def print_banner():
73
73
 
74
74
  async def main(config):
75
75
  """
76
- Coordinates the main asynchronous relay loop between Meshtastic and Matrix clients.
76
+ Coordinate the asynchronous relay loop between Meshtastic and Matrix clients.
77
77
 
78
- Initializes the database, loads plugins, starts the message queue, and establishes connections to both Meshtastic and Matrix. Joins configured Matrix rooms, registers event callbacks for message and membership events, and periodically updates node names from the Meshtastic network. Monitors connection health, manages the Matrix sync loop with reconnection and shutdown handling, and ensures graceful shutdown of all components. Optionally wipes the message map on startup and shutdown if configured.
78
+ Initializes the database and plugins, starts the message queue, connects to Meshtastic and Matrix, joins configured Matrix rooms, registers event callbacks, monitors connection health, runs the Matrix sync loop with automatic retries, and ensures an orderly shutdown of all components (including optional message map wiping on startup and shutdown).
79
+
80
+ Parameters:
81
+ config (dict): Application configuration mapping. Expected keys used by this function include:
82
+ - "matrix_rooms": list of room dicts with at least an "id" entry,
83
+ - "meshtastic": optional dict with "message_delay",
84
+ - "database" (preferred) or legacy "db": optional dict containing "msg_map" with "wipe_on_restart" boolean.
85
+
86
+ Raises:
87
+ ConnectionError: If connecting to Matrix fails and no Matrix client can be obtained.
79
88
  """
80
89
  # Extract Matrix configuration
81
90
  from typing import List
@@ -154,9 +163,9 @@ async def main(config):
154
163
 
155
164
  async def shutdown():
156
165
  """
157
- Signal the application to shut down.
158
-
159
- Sets the Meshtastic shutdown flag and triggers the local shutdown event so tasks waiting on that event can begin shutdown/cleanup. This coroutine only signals shutdown; it does not close clients or perform cleanup itself.
166
+ Signal the application to begin shutdown.
167
+
168
+ Set the Meshtastic shutdown flag and set the local shutdown event so any coroutines waiting on that event can start cleanup.
160
169
  """
161
170
  matrix_logger.info("Shutdown signal received. Closing down...")
162
171
  meshtastic_utils.shutting_down = True # Set the shutting_down flag