mmrelay 1.1.0__tar.gz → 1.1.2__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.0/src/mmrelay.egg-info → mmrelay-1.1.2}/PKG-INFO +4 -3
- {mmrelay-1.1.0 → mmrelay-1.1.2}/README.md +3 -2
- {mmrelay-1.1.0 → mmrelay-1.1.2}/setup.py +1 -1
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/__init__.py +1 -1
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/log_utils.py +40 -48
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/main.py +31 -11
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/meshtastic_utils.py +89 -59
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/tools/sample-docker-compose.yaml +8 -1
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/tools/sample_config.yaml +13 -1
- {mmrelay-1.1.0 → mmrelay-1.1.2/src/mmrelay.egg-info}/PKG-INFO +4 -3
- mmrelay-1.1.2/src/mmrelay.egg-info/requires.txt +12 -0
- mmrelay-1.1.0/requirements.txt +0 -15
- {mmrelay-1.1.0 → mmrelay-1.1.2}/LICENSE +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/MANIFEST.in +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/pyproject.toml +0 -0
- /mmrelay-1.1.0/src/mmrelay.egg-info/requires.txt → /mmrelay-1.1.2/requirements.txt +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/setup.cfg +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/cli.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/config.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/config_checker.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/db_utils.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/matrix_utils.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugin_loader.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/__init__.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/base_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/debug_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/drop_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/health_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/help_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/map_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/mesh_relay_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/nodes_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/ping_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/telemetry_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/plugins/weather_plugin.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/setup_utils.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/tools/__init__.py +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/tools/mmrelay.service +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay/tools/sample.env +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay.egg-info/SOURCES.txt +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay.egg-info/dependency_links.txt +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay.egg-info/entry_points.txt +0 -0
- {mmrelay-1.1.0 → mmrelay-1.1.2}/src/mmrelay.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mmrelay
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
|
|
5
5
|
Home-page: https://github.com/geoffwhittington/meshtastic-matrix-relay
|
|
6
6
|
Author: Geoff Whittington, Jeremiah K., and contributors
|
|
@@ -56,6 +56,7 @@ A powerful and easy-to-use relay between Meshtastic devices and Matrix chat room
|
|
|
56
56
|
- Supports mapping multiple rooms and channels 1:1
|
|
57
57
|
- Relays messages to/from an MQTT broker, if configured in the Meshtastic firmware
|
|
58
58
|
- ✨️ _Bidirectional replies and reactions support_ ✨️ **NEW!!**
|
|
59
|
+
- ✨️ _Native Docker support_ ✨️ **NEW!!**
|
|
59
60
|
|
|
60
61
|
_We would love to support [Matrix E2EE rooms](https://github.com/geoffwhittington/meshtastic-matrix-relay/issues/33), but this is currently not implemented._
|
|
61
62
|
|
|
@@ -98,7 +99,7 @@ make logs # View logs
|
|
|
98
99
|
|
|
99
100
|
Docker provides isolated environment, easy deployment, automatic restarts, and volume persistence.
|
|
100
101
|
|
|
101
|
-
For detailed Docker setup instructions, see the [Docker Guide](DOCKER.md).
|
|
102
|
+
For detailed Docker setup instructions, see the [Docker Guide](docs/DOCKER.md).
|
|
102
103
|
|
|
103
104
|
> **Note**: Docker builds currently use a temporary fork of the meshtastic library with BLE hanging fixes. PyPI releases use the upstream library. This will be resolved when the fixes are merged upstream.
|
|
104
105
|
|
|
@@ -172,5 +173,5 @@ See our Wiki page [Getting Started With Matrix & MM Relay](https://github.com/ge
|
|
|
172
173
|
Join us!
|
|
173
174
|
|
|
174
175
|
- Our project's room: [#mmrelay:matrix.org](https://matrix.to/#/#mmrelay:matrix.org)
|
|
175
|
-
- Part of the
|
|
176
|
+
- Part of the Meshnet Club Matrix space: [#meshnetclub:matrix.org](https://matrix.to/#/#meshnetclub:matrix.org)
|
|
176
177
|
- Public Relay Room: [#mmrelay-relay-room:matrix.org](https://matrix.to/#/#mmrelay-relay-room:matrix.org) - Where we bridge multiple meshnets. Feel free to join us, with or without a relay!
|
|
@@ -16,6 +16,7 @@ A powerful and easy-to-use relay between Meshtastic devices and Matrix chat room
|
|
|
16
16
|
- Supports mapping multiple rooms and channels 1:1
|
|
17
17
|
- Relays messages to/from an MQTT broker, if configured in the Meshtastic firmware
|
|
18
18
|
- ✨️ _Bidirectional replies and reactions support_ ✨️ **NEW!!**
|
|
19
|
+
- ✨️ _Native Docker support_ ✨️ **NEW!!**
|
|
19
20
|
|
|
20
21
|
_We would love to support [Matrix E2EE rooms](https://github.com/geoffwhittington/meshtastic-matrix-relay/issues/33), but this is currently not implemented._
|
|
21
22
|
|
|
@@ -58,7 +59,7 @@ make logs # View logs
|
|
|
58
59
|
|
|
59
60
|
Docker provides isolated environment, easy deployment, automatic restarts, and volume persistence.
|
|
60
61
|
|
|
61
|
-
For detailed Docker setup instructions, see the [Docker Guide](DOCKER.md).
|
|
62
|
+
For detailed Docker setup instructions, see the [Docker Guide](docs/DOCKER.md).
|
|
62
63
|
|
|
63
64
|
> **Note**: Docker builds currently use a temporary fork of the meshtastic library with BLE hanging fixes. PyPI releases use the upstream library. This will be resolved when the fixes are merged upstream.
|
|
64
65
|
|
|
@@ -132,5 +133,5 @@ See our Wiki page [Getting Started With Matrix & MM Relay](https://github.com/ge
|
|
|
132
133
|
Join us!
|
|
133
134
|
|
|
134
135
|
- Our project's room: [#mmrelay:matrix.org](https://matrix.to/#/#mmrelay:matrix.org)
|
|
135
|
-
- Part of the
|
|
136
|
+
- Part of the Meshnet Club Matrix space: [#meshnetclub:matrix.org](https://matrix.to/#/#meshnetclub:matrix.org)
|
|
136
137
|
- Public Relay Room: [#mmrelay-relay-room:matrix.org](https://matrix.to/#/#mmrelay-relay-room:matrix.org) - Where we bridge multiple meshnets. Feel free to join us, with or without a relay!
|
|
@@ -6,7 +6,7 @@ with open("README.md", encoding="utf-8") as f:
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name="mmrelay",
|
|
9
|
-
version="1.1.
|
|
9
|
+
version="1.1.2",
|
|
10
10
|
author="Geoff Whittington, Jeremiah K., and contributors",
|
|
11
11
|
author_email="jeremiahk@gmx.com",
|
|
12
12
|
description="Bridge between Meshtastic mesh networks and Matrix chat rooms",
|
|
@@ -28,15 +28,52 @@ config = None
|
|
|
28
28
|
# Global variable to store the log file path
|
|
29
29
|
log_file_path = None
|
|
30
30
|
|
|
31
|
+
# Track if component debug logging has been configured
|
|
32
|
+
_component_debug_configured = False
|
|
33
|
+
|
|
34
|
+
# Component logger mapping for data-driven configuration
|
|
35
|
+
_COMPONENT_LOGGERS = {
|
|
36
|
+
"matrix_nio": ["nio", "nio.client", "nio.http", "nio.crypto"],
|
|
37
|
+
"bleak": ["bleak", "bleak.backends"],
|
|
38
|
+
"meshtastic": [
|
|
39
|
+
"meshtastic",
|
|
40
|
+
"meshtastic.serial_interface",
|
|
41
|
+
"meshtastic.tcp_interface",
|
|
42
|
+
"meshtastic.ble_interface",
|
|
43
|
+
],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def configure_component_debug_logging():
|
|
48
|
+
"""
|
|
49
|
+
Enables debug-level logging for selected external components based on configuration settings.
|
|
50
|
+
|
|
51
|
+
This function sets the log level to DEBUG for specific libraries (matrix_nio, bleak, meshtastic) if enabled in the global configuration under `logging.debug`. It ensures that component debug logging is configured only once per application run.
|
|
52
|
+
"""
|
|
53
|
+
global _component_debug_configured, config
|
|
54
|
+
|
|
55
|
+
# Only configure once
|
|
56
|
+
if _component_debug_configured or config is None:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
debug_config = config.get("logging", {}).get("debug", {})
|
|
60
|
+
|
|
61
|
+
for component, loggers in _COMPONENT_LOGGERS.items():
|
|
62
|
+
if debug_config.get(component, False):
|
|
63
|
+
for logger_name in loggers:
|
|
64
|
+
logging.getLogger(logger_name).setLevel(logging.DEBUG)
|
|
65
|
+
|
|
66
|
+
_component_debug_configured = True
|
|
67
|
+
|
|
31
68
|
|
|
32
69
|
def get_logger(name):
|
|
33
70
|
"""
|
|
34
|
-
Create and configure a logger with console and optional file output,
|
|
71
|
+
Create and configure a logger with console and optional rotating file output, using global configuration and command-line arguments.
|
|
35
72
|
|
|
36
|
-
The logger
|
|
73
|
+
The logger supports colorized console output via Rich if enabled, and writes logs to a rotating file if configured or requested via command-line arguments. Log file location and rotation parameters are determined by priority: command-line argument, configuration file, or a default directory. The function ensures the log directory exists and stores the log file path globally if the logger name is "M<>M Relay".
|
|
37
74
|
|
|
38
75
|
Parameters:
|
|
39
|
-
name (str): The name of the logger to create.
|
|
76
|
+
name (str): The name of the logger to create and configure.
|
|
40
77
|
|
|
41
78
|
Returns:
|
|
42
79
|
logging.Logger: The configured logger instance.
|
|
@@ -145,48 +182,3 @@ def get_logger(name):
|
|
|
145
182
|
logger.addHandler(file_handler)
|
|
146
183
|
|
|
147
184
|
return logger
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def setup_upstream_logging_capture():
|
|
151
|
-
"""
|
|
152
|
-
Redirects warning and error log messages from upstream libraries and the root logger into the application's formatted logging system.
|
|
153
|
-
|
|
154
|
-
This ensures that log output from external dependencies (such as "meshtastic", "bleak", and "asyncio") appears with consistent formatting alongside the application's own logs. Only messages at WARNING level or higher are captured, and messages originating from the application's own loggers are excluded to prevent recursion.
|
|
155
|
-
"""
|
|
156
|
-
# Get our main logger
|
|
157
|
-
main_logger = get_logger("Upstream")
|
|
158
|
-
|
|
159
|
-
# Create a custom handler that redirects root logger messages
|
|
160
|
-
class UpstreamLogHandler(logging.Handler):
|
|
161
|
-
def emit(self, record):
|
|
162
|
-
# Skip if this is already from our logger to avoid recursion
|
|
163
|
-
"""
|
|
164
|
-
Redirects log records from external sources to the main logger, mapping their severity and prefixing with the original logger name.
|
|
165
|
-
|
|
166
|
-
Skips records originating from the application's own loggers to prevent recursion.
|
|
167
|
-
"""
|
|
168
|
-
if record.name.startswith("mmrelay") or record.name == "Upstream":
|
|
169
|
-
return
|
|
170
|
-
|
|
171
|
-
# Map the log level and emit through our logger
|
|
172
|
-
if record.levelno >= logging.ERROR:
|
|
173
|
-
main_logger.error(f"[{record.name}] {record.getMessage()}")
|
|
174
|
-
elif record.levelno >= logging.WARNING:
|
|
175
|
-
main_logger.warning(f"[{record.name}] {record.getMessage()}")
|
|
176
|
-
elif record.levelno >= logging.INFO:
|
|
177
|
-
main_logger.info(f"[{record.name}] {record.getMessage()}")
|
|
178
|
-
else:
|
|
179
|
-
main_logger.debug(f"[{record.name}] {record.getMessage()}")
|
|
180
|
-
|
|
181
|
-
# Add our handler to the root logger
|
|
182
|
-
root_logger = logging.getLogger()
|
|
183
|
-
upstream_handler = UpstreamLogHandler()
|
|
184
|
-
upstream_handler.setLevel(logging.WARNING) # Only capture warnings and errors
|
|
185
|
-
root_logger.addHandler(upstream_handler)
|
|
186
|
-
|
|
187
|
-
# Also set up specific loggers for known upstream libraries
|
|
188
|
-
for logger_name in ["meshtastic", "bleak", "asyncio"]:
|
|
189
|
-
upstream_logger = logging.getLogger(logger_name)
|
|
190
|
-
upstream_logger.addHandler(upstream_handler)
|
|
191
|
-
upstream_logger.setLevel(logging.WARNING)
|
|
192
|
-
upstream_logger.propagate = False # Prevent duplicate messages via root logger
|
|
@@ -4,6 +4,7 @@ It uses Meshtastic-python and Matrix nio client library to interface with the ra
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
|
+
import concurrent.futures
|
|
7
8
|
import logging
|
|
8
9
|
import signal
|
|
9
10
|
import sys
|
|
@@ -20,7 +21,7 @@ from mmrelay.db_utils import (
|
|
|
20
21
|
update_shortnames,
|
|
21
22
|
wipe_message_map,
|
|
22
23
|
)
|
|
23
|
-
from mmrelay.log_utils import get_logger
|
|
24
|
+
from mmrelay.log_utils import get_logger
|
|
24
25
|
from mmrelay.matrix_utils import connect_matrix, join_matrix_room
|
|
25
26
|
from mmrelay.matrix_utils import logger as matrix_logger
|
|
26
27
|
from mmrelay.matrix_utils import on_room_member, on_room_message
|
|
@@ -50,12 +51,9 @@ def print_banner():
|
|
|
50
51
|
|
|
51
52
|
async def main(config):
|
|
52
53
|
"""
|
|
53
|
-
|
|
54
|
+
Run the main asynchronous relay loop, managing connections between Meshtastic and Matrix, event handling, and graceful shutdown.
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Parameters:
|
|
58
|
-
config: The loaded configuration dictionary containing Matrix, Meshtastic, and database settings.
|
|
56
|
+
Initializes the database, loads plugins, connects to Meshtastic and Matrix, joins configured Matrix rooms, and registers event callbacks for message and membership events. Periodically updates node names from the Meshtastic network and manages the Matrix sync loop, handling reconnections and shutdown signals. If configured, wipes the message map on both startup and shutdown.
|
|
59
57
|
"""
|
|
60
58
|
# Extract Matrix configuration
|
|
61
59
|
from typing import List
|
|
@@ -68,9 +66,6 @@ async def main(config):
|
|
|
68
66
|
# Initialize the SQLite database
|
|
69
67
|
initialize_database()
|
|
70
68
|
|
|
71
|
-
# Set up upstream logging capture to format library messages consistently
|
|
72
|
-
setup_upstream_logging_capture()
|
|
73
|
-
|
|
74
69
|
# Check database config for wipe_on_restart (preferred format)
|
|
75
70
|
database_config = config.get("database", {})
|
|
76
71
|
msg_map_config = database_config.get("msg_map", {})
|
|
@@ -183,9 +178,31 @@ async def main(config):
|
|
|
183
178
|
if meshtastic_utils.meshtastic_client:
|
|
184
179
|
meshtastic_logger.info("Closing Meshtastic client...")
|
|
185
180
|
try:
|
|
186
|
-
|
|
181
|
+
# Timeout wrapper to prevent infinite hanging during shutdown
|
|
182
|
+
# The meshtastic library can sometimes hang indefinitely during close()
|
|
183
|
+
# operations, especially with BLE connections. This timeout ensures
|
|
184
|
+
# the application can shut down gracefully within 10 seconds.
|
|
185
|
+
|
|
186
|
+
def _close_meshtastic():
|
|
187
|
+
"""
|
|
188
|
+
Closes the Meshtastic client connection synchronously.
|
|
189
|
+
"""
|
|
190
|
+
meshtastic_utils.meshtastic_client.close()
|
|
191
|
+
|
|
192
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
|
193
|
+
future = executor.submit(_close_meshtastic)
|
|
194
|
+
future.result(timeout=10.0) # 10-second timeout
|
|
195
|
+
|
|
196
|
+
meshtastic_logger.info("Meshtastic client closed successfully")
|
|
197
|
+
except concurrent.futures.TimeoutError:
|
|
198
|
+
meshtastic_logger.warning(
|
|
199
|
+
"Meshtastic client close timed out - forcing shutdown"
|
|
200
|
+
)
|
|
187
201
|
except Exception as e:
|
|
188
|
-
meshtastic_logger.
|
|
202
|
+
meshtastic_logger.error(
|
|
203
|
+
f"Unexpected error during Meshtastic client close: {e}",
|
|
204
|
+
exc_info=True,
|
|
205
|
+
)
|
|
189
206
|
|
|
190
207
|
# Attempt to wipe message_map on shutdown if enabled
|
|
191
208
|
if wipe_on_restart:
|
|
@@ -264,6 +281,9 @@ def run_main(args):
|
|
|
264
281
|
set_config(db_utils, config)
|
|
265
282
|
set_config(base_plugin, config)
|
|
266
283
|
|
|
284
|
+
# Configure component debug logging now that config is available
|
|
285
|
+
log_utils.configure_component_debug_logging()
|
|
286
|
+
|
|
267
287
|
# Get config path and log file path for logging
|
|
268
288
|
from mmrelay.config import config_path
|
|
269
289
|
from mmrelay.log_utils import log_file_path
|
|
@@ -47,7 +47,7 @@ reconnecting = False
|
|
|
47
47
|
shutting_down = False
|
|
48
48
|
reconnect_task = None # To keep track of the reconnect task
|
|
49
49
|
|
|
50
|
-
#
|
|
50
|
+
# Subscription flags to prevent duplicate subscriptions
|
|
51
51
|
subscribed_to_messages = False
|
|
52
52
|
subscribed_to_connection_lost = False
|
|
53
53
|
|
|
@@ -57,7 +57,7 @@ def is_running_as_service():
|
|
|
57
57
|
Determine if the application is running as a systemd service.
|
|
58
58
|
|
|
59
59
|
Returns:
|
|
60
|
-
bool: True if running under systemd (
|
|
60
|
+
bool: True if running under systemd (as indicated by the INVOCATION_ID environment variable or parent process), False otherwise.
|
|
61
61
|
"""
|
|
62
62
|
# Check for INVOCATION_ID environment variable (set by systemd)
|
|
63
63
|
if os.environ.get("INVOCATION_ID"):
|
|
@@ -88,16 +88,16 @@ def serial_port_exists(port_name):
|
|
|
88
88
|
|
|
89
89
|
def connect_meshtastic(passed_config=None, force_connect=False):
|
|
90
90
|
"""
|
|
91
|
-
Establishes and manages a connection to a Meshtastic device using
|
|
91
|
+
Establishes and manages a connection to a Meshtastic device using serial, BLE, or TCP, with automatic retries and event subscriptions.
|
|
92
92
|
|
|
93
|
-
If a configuration is provided, updates the global configuration and Matrix room mappings.
|
|
93
|
+
If a configuration is provided, updates the global configuration and Matrix room mappings. If already connected and not forced, returns the existing client. Handles reconnection logic with exponential backoff, verifies serial port existence, and subscribes to message and connection lost events upon successful connection.
|
|
94
94
|
|
|
95
95
|
Parameters:
|
|
96
|
-
passed_config (dict, optional): Configuration dictionary to use for the connection.
|
|
96
|
+
passed_config (dict, optional): Configuration dictionary to use for the connection.
|
|
97
97
|
force_connect (bool, optional): If True, forces a new connection even if one already exists.
|
|
98
98
|
|
|
99
99
|
Returns:
|
|
100
|
-
|
|
100
|
+
meshtastic_client: The connected Meshtastic client instance, or None if connection fails or shutdown is in progress.
|
|
101
101
|
"""
|
|
102
102
|
global meshtastic_client, shutting_down, config, matrix_rooms
|
|
103
103
|
if shutting_down:
|
|
@@ -202,7 +202,7 @@ def connect_meshtastic(passed_config=None, force_connect=False):
|
|
|
202
202
|
f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}"
|
|
203
203
|
)
|
|
204
204
|
|
|
205
|
-
# Subscribe to message and connection lost events (only
|
|
205
|
+
# Subscribe to message and connection lost events (only once per application run)
|
|
206
206
|
global subscribed_to_messages, subscribed_to_connection_lost
|
|
207
207
|
if not subscribed_to_messages:
|
|
208
208
|
pub.subscribe(on_meshtastic_message, "meshtastic.receive")
|
|
@@ -241,10 +241,13 @@ def connect_meshtastic(passed_config=None, force_connect=False):
|
|
|
241
241
|
return meshtastic_client
|
|
242
242
|
|
|
243
243
|
|
|
244
|
-
def on_lost_meshtastic_connection(interface=None):
|
|
244
|
+
def on_lost_meshtastic_connection(interface=None, detection_source="unknown"):
|
|
245
245
|
"""
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
Handles loss of Meshtastic connection by initiating a reconnection sequence unless the system is shutting down or already reconnecting.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
interface: The Meshtastic interface (optional, for compatibility)
|
|
250
|
+
detection_source: Source that detected the connection loss (for debugging)
|
|
248
251
|
"""
|
|
249
252
|
global meshtastic_client, reconnecting, shutting_down, event_loop, reconnect_task
|
|
250
253
|
with meshtastic_lock:
|
|
@@ -257,7 +260,7 @@ def on_lost_meshtastic_connection(interface=None):
|
|
|
257
260
|
)
|
|
258
261
|
return
|
|
259
262
|
reconnecting = True
|
|
260
|
-
logger.error("Lost connection. Reconnecting...")
|
|
263
|
+
logger.error(f"Lost connection ({detection_source}). Reconnecting...")
|
|
261
264
|
|
|
262
265
|
if meshtastic_client:
|
|
263
266
|
try:
|
|
@@ -278,8 +281,9 @@ def on_lost_meshtastic_connection(interface=None):
|
|
|
278
281
|
|
|
279
282
|
async def reconnect():
|
|
280
283
|
"""
|
|
281
|
-
Asynchronously attempts to reconnect
|
|
282
|
-
|
|
284
|
+
Asynchronously attempts to reconnect to the Meshtastic device using exponential backoff, stopping if a shutdown is initiated.
|
|
285
|
+
|
|
286
|
+
Reconnection attempts start with a 10-second delay, doubling up to a maximum of 5 minutes between attempts. If not running as a service, a progress bar is displayed during the wait. The process stops immediately if `shutting_down` is set to True or upon successful reconnection.
|
|
283
287
|
"""
|
|
284
288
|
global meshtastic_client, reconnecting, shutting_down
|
|
285
289
|
backoff_time = 10
|
|
@@ -336,9 +340,9 @@ async def reconnect():
|
|
|
336
340
|
|
|
337
341
|
def on_meshtastic_message(packet, interface):
|
|
338
342
|
"""
|
|
339
|
-
|
|
343
|
+
Process incoming Meshtastic messages and relay them to Matrix rooms or plugins according to message type and configuration.
|
|
340
344
|
|
|
341
|
-
Handles reactions and replies by relaying them to Matrix if enabled
|
|
345
|
+
Handles reactions and replies by relaying them to Matrix if enabled. Normal text messages are relayed to all mapped Matrix rooms unless handled by a plugin or directed to the relay node. Non-text messages are passed to plugins for processing. Messages from unmapped channels or disabled detection sensors are ignored. Ensures sender information is retrieved or stored as needed.
|
|
342
346
|
"""
|
|
343
347
|
global config, matrix_rooms
|
|
344
348
|
|
|
@@ -643,53 +647,80 @@ def on_meshtastic_message(packet, interface):
|
|
|
643
647
|
|
|
644
648
|
async def check_connection():
|
|
645
649
|
"""
|
|
646
|
-
Periodically checks the Meshtastic connection
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
+
Periodically checks the health of the Meshtastic connection and triggers reconnection if the connection is lost.
|
|
651
|
+
|
|
652
|
+
Health checks can be enabled/disabled via configuration. When enabled, for non-BLE connections,
|
|
653
|
+
invokes `localNode.getMetadata()` at configurable intervals (default 60 seconds) to verify connectivity.
|
|
654
|
+
If the check fails or the firmware version is missing, initiates reconnection logic.
|
|
655
|
+
|
|
656
|
+
BLE connections rely on real-time disconnection detection and skip periodic health checks.
|
|
657
|
+
The function runs continuously until shutdown is requested.
|
|
658
|
+
|
|
659
|
+
Configuration:
|
|
660
|
+
health_check.enabled: Enable/disable health checks (default: true)
|
|
661
|
+
health_check.heartbeat_interval: Interval between checks in seconds (default: 60)
|
|
650
662
|
"""
|
|
651
|
-
global meshtastic_client, shutting_down, config
|
|
663
|
+
global meshtastic_client, shutting_down, config
|
|
652
664
|
|
|
653
665
|
# Check if config is available
|
|
654
666
|
if config is None:
|
|
655
667
|
logger.error("No configuration available. Cannot check connection.")
|
|
656
668
|
return
|
|
657
669
|
|
|
658
|
-
# Get heartbeat interval from config, default to 180 seconds
|
|
659
|
-
heartbeat_interval = config["meshtastic"].get("heartbeat_interval", 180)
|
|
660
670
|
connection_type = config["meshtastic"]["connection_type"]
|
|
661
671
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
)
|
|
672
|
+
# Get health check configuration
|
|
673
|
+
health_config = config["meshtastic"].get("health_check", {})
|
|
674
|
+
health_check_enabled = health_config.get("enabled", True)
|
|
675
|
+
heartbeat_interval = health_config.get("heartbeat_interval", 60)
|
|
665
676
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
logger.debug(
|
|
670
|
-
f"Checking {connection_type} connection health using getMetadata()"
|
|
671
|
-
)
|
|
672
|
-
output_capture = io.StringIO()
|
|
673
|
-
with contextlib.redirect_stdout(
|
|
674
|
-
output_capture
|
|
675
|
-
), contextlib.redirect_stderr(output_capture):
|
|
676
|
-
meshtastic_client.localNode.getMetadata()
|
|
677
|
+
# Support legacy heartbeat_interval configuration for backward compatibility
|
|
678
|
+
if "heartbeat_interval" in config["meshtastic"]:
|
|
679
|
+
heartbeat_interval = config["meshtastic"]["heartbeat_interval"]
|
|
677
680
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
+
# Exit early if health checks are disabled
|
|
682
|
+
if not health_check_enabled:
|
|
683
|
+
logger.info("Connection health checks are disabled in configuration")
|
|
684
|
+
return
|
|
681
685
|
|
|
682
|
-
|
|
686
|
+
ble_skip_logged = False
|
|
683
687
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
688
|
+
while not shutting_down:
|
|
689
|
+
if meshtastic_client and not reconnecting:
|
|
690
|
+
# BLE has real-time disconnection detection in the library
|
|
691
|
+
# Skip periodic health checks to avoid duplicate reconnection attempts
|
|
692
|
+
if connection_type == "ble":
|
|
693
|
+
if not ble_skip_logged:
|
|
694
|
+
logger.info(
|
|
695
|
+
"BLE connection uses real-time disconnection detection - health checks disabled"
|
|
689
696
|
)
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
697
|
+
ble_skip_logged = True
|
|
698
|
+
else:
|
|
699
|
+
try:
|
|
700
|
+
output_capture = io.StringIO()
|
|
701
|
+
with contextlib.redirect_stdout(
|
|
702
|
+
output_capture
|
|
703
|
+
), contextlib.redirect_stderr(output_capture):
|
|
704
|
+
meshtastic_client.localNode.getMetadata()
|
|
705
|
+
|
|
706
|
+
console_output = output_capture.getvalue()
|
|
707
|
+
if "firmware_version" not in console_output:
|
|
708
|
+
raise Exception("No firmware_version in getMetadata output.")
|
|
709
|
+
|
|
710
|
+
except Exception as e:
|
|
711
|
+
# Only trigger reconnection if we're not already reconnecting
|
|
712
|
+
if not reconnecting:
|
|
713
|
+
logger.error(
|
|
714
|
+
f"{connection_type.capitalize()} connection health check failed: {e}"
|
|
715
|
+
)
|
|
716
|
+
on_lost_meshtastic_connection(
|
|
717
|
+
interface=meshtastic_client,
|
|
718
|
+
detection_source=f"health check failed: {str(e)}",
|
|
719
|
+
)
|
|
720
|
+
else:
|
|
721
|
+
logger.debug(
|
|
722
|
+
"Skipping reconnection trigger - already reconnecting"
|
|
723
|
+
)
|
|
693
724
|
elif reconnecting:
|
|
694
725
|
logger.debug("Skipping connection check - reconnection in progress")
|
|
695
726
|
elif not meshtastic_client:
|
|
@@ -707,21 +738,20 @@ def sendTextReply(
|
|
|
707
738
|
channelIndex: int = 0,
|
|
708
739
|
):
|
|
709
740
|
"""
|
|
710
|
-
Send a text message as a reply to a previous message.
|
|
741
|
+
Send a Meshtastic text message as a reply to a specific previous message.
|
|
711
742
|
|
|
712
|
-
|
|
713
|
-
in the Data protobuf message, which the standard sendText() method doesn't support.
|
|
743
|
+
Creates and sends a reply message by setting the `reply_id` field in the Meshtastic Data protobuf, enabling proper reply threading. Returns the sent packet with its ID populated.
|
|
714
744
|
|
|
715
|
-
|
|
716
|
-
interface: The Meshtastic interface to send through
|
|
717
|
-
text: The
|
|
718
|
-
reply_id: The ID of the message
|
|
719
|
-
destinationId:
|
|
720
|
-
wantAck: Whether to request acknowledgment
|
|
721
|
-
channelIndex:
|
|
745
|
+
Parameters:
|
|
746
|
+
interface: The Meshtastic interface to send through.
|
|
747
|
+
text (str): The message content to send.
|
|
748
|
+
reply_id (int): The ID of the message being replied to.
|
|
749
|
+
destinationId: The recipient address (defaults to broadcast).
|
|
750
|
+
wantAck (bool): Whether to request acknowledgment for the message.
|
|
751
|
+
channelIndex (int): The channel index to send the message on.
|
|
722
752
|
|
|
723
753
|
Returns:
|
|
724
|
-
The sent
|
|
754
|
+
The sent MeshPacket with its ID field populated.
|
|
725
755
|
"""
|
|
726
756
|
logger.debug(f"Sending text reply: '{text}' replying to message ID {reply_id}")
|
|
727
757
|
|
|
@@ -3,6 +3,7 @@ services:
|
|
|
3
3
|
build: .
|
|
4
4
|
container_name: meshtastic-matrix-relay
|
|
5
5
|
restart: unless-stopped
|
|
6
|
+
user: "${UID:-1000}:${GID:-1000}"
|
|
6
7
|
|
|
7
8
|
environment:
|
|
8
9
|
- TZ=UTC
|
|
@@ -28,5 +29,11 @@ services:
|
|
|
28
29
|
# - /dev/ttyUSB0:/dev/ttyUSB0
|
|
29
30
|
# - /dev/ttyACM0:/dev/ttyACM0
|
|
30
31
|
|
|
31
|
-
# For BLE connections, uncomment
|
|
32
|
+
# For BLE connections, uncomment these:
|
|
32
33
|
# privileged: true
|
|
34
|
+
# network_mode: host
|
|
35
|
+
# Additional volumes for BLE (add to existing volumes section above):
|
|
36
|
+
# - /var/run/dbus:/var/run/dbus:ro
|
|
37
|
+
# - /sys/bus/usb:/sys/bus/usb:ro
|
|
38
|
+
# - /sys/class/bluetooth:/sys/class/bluetooth:ro
|
|
39
|
+
# - /sys/devices:/sys/devices:ro
|
|
@@ -18,11 +18,17 @@ meshtastic:
|
|
|
18
18
|
broadcast_enabled: true # Must be set to true to enable Matrix to Meshtastic messages
|
|
19
19
|
detection_sensor: true # Must be set to true to forward messages of Meshtastic's detection sensor module
|
|
20
20
|
plugin_response_delay: 3 # Default response delay in seconds for plugins that respond on the mesh;
|
|
21
|
-
# heartbeat_interval: 180 # Interval in seconds to check connection health using getMetadata() (default: 180)
|
|
22
21
|
message_interactions: # Configure reactions and replies (both require message storage in database)
|
|
23
22
|
reactions: false # Enable reaction relaying between platforms
|
|
24
23
|
replies: false # Enable reply relaying between platforms
|
|
25
24
|
|
|
25
|
+
# Connection health monitoring configuration
|
|
26
|
+
#health_check:
|
|
27
|
+
# enabled: true # Enable/disable periodic health checks (default: true)
|
|
28
|
+
# heartbeat_interval: 60 # Interval in seconds between health checks (default: 60)
|
|
29
|
+
# # Note: BLE connections use real-time disconnection detection and skip periodic checks
|
|
30
|
+
# # Legacy: heartbeat_interval at meshtastic level still supported but deprecated
|
|
31
|
+
|
|
26
32
|
logging:
|
|
27
33
|
level: info
|
|
28
34
|
#log_to_file: true # Set to true to enable file logging
|
|
@@ -31,6 +37,12 @@ logging:
|
|
|
31
37
|
#backup_count: 1 # Keeps 1 backup as the default if omitted
|
|
32
38
|
#color_enabled: true # Set to false to disable colored console output
|
|
33
39
|
|
|
40
|
+
# Component-specific debug logging (useful for troubleshooting)
|
|
41
|
+
#debug:
|
|
42
|
+
# matrix_nio: false # Enable matrix-nio debug logging for Matrix client issues
|
|
43
|
+
# bleak: false # Enable BLE (bleak) debug logging for Bluetooth connection issues
|
|
44
|
+
# meshtastic: false # Enable meshtastic library debug logging for device communication issues
|
|
45
|
+
|
|
34
46
|
#database:
|
|
35
47
|
# path: ~/.mmrelay/data/meshtastic.sqlite # Default location
|
|
36
48
|
# msg_map: # The message map is necessary for the relay_reactions functionality. If `relay_reactions` is set to false, nothing will be saved to the message map.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mmrelay
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
|
|
5
5
|
Home-page: https://github.com/geoffwhittington/meshtastic-matrix-relay
|
|
6
6
|
Author: Geoff Whittington, Jeremiah K., and contributors
|
|
@@ -56,6 +56,7 @@ A powerful and easy-to-use relay between Meshtastic devices and Matrix chat room
|
|
|
56
56
|
- Supports mapping multiple rooms and channels 1:1
|
|
57
57
|
- Relays messages to/from an MQTT broker, if configured in the Meshtastic firmware
|
|
58
58
|
- ✨️ _Bidirectional replies and reactions support_ ✨️ **NEW!!**
|
|
59
|
+
- ✨️ _Native Docker support_ ✨️ **NEW!!**
|
|
59
60
|
|
|
60
61
|
_We would love to support [Matrix E2EE rooms](https://github.com/geoffwhittington/meshtastic-matrix-relay/issues/33), but this is currently not implemented._
|
|
61
62
|
|
|
@@ -98,7 +99,7 @@ make logs # View logs
|
|
|
98
99
|
|
|
99
100
|
Docker provides isolated environment, easy deployment, automatic restarts, and volume persistence.
|
|
100
101
|
|
|
101
|
-
For detailed Docker setup instructions, see the [Docker Guide](DOCKER.md).
|
|
102
|
+
For detailed Docker setup instructions, see the [Docker Guide](docs/DOCKER.md).
|
|
102
103
|
|
|
103
104
|
> **Note**: Docker builds currently use a temporary fork of the meshtastic library with BLE hanging fixes. PyPI releases use the upstream library. This will be resolved when the fixes are merged upstream.
|
|
104
105
|
|
|
@@ -172,5 +173,5 @@ See our Wiki page [Getting Started With Matrix & MM Relay](https://github.com/ge
|
|
|
172
173
|
Join us!
|
|
173
174
|
|
|
174
175
|
- Our project's room: [#mmrelay:matrix.org](https://matrix.to/#/#mmrelay:matrix.org)
|
|
175
|
-
- Part of the
|
|
176
|
+
- Part of the Meshnet Club Matrix space: [#meshnetclub:matrix.org](https://matrix.to/#/#meshnetclub:matrix.org)
|
|
176
177
|
- Public Relay Room: [#mmrelay-relay-room:matrix.org](https://matrix.to/#/#mmrelay-relay-room:matrix.org) - Where we bridge multiple meshnets. Feel free to join us, with or without a relay!
|
mmrelay-1.1.0/requirements.txt
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# TEMPORARY: Using fork with BLE hanging fix until upstream merge
|
|
2
|
-
# Commit: 33d23e9c28508a740da6567627e42d81552c9b58
|
|
3
|
-
# For PyPI compatibility, setup.py uses upstream meshtastic>=2.6.4
|
|
4
|
-
meshtastic @ git+https://github.com/jeremiah-k/meshtastic-python.git@33d23e9c28508a740da6567627e42d81552c9b58
|
|
5
|
-
Pillow==11.3.0
|
|
6
|
-
matrix-nio==0.25.2
|
|
7
|
-
matplotlib==3.10.1
|
|
8
|
-
requests==2.32.4
|
|
9
|
-
markdown==3.8.2
|
|
10
|
-
haversine==2.9.0
|
|
11
|
-
schedule==1.2.2
|
|
12
|
-
platformdirs==4.3.8
|
|
13
|
-
py-staticmaps>=0.4.0
|
|
14
|
-
rich==14.0.0
|
|
15
|
-
setuptools==80.9.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|