mmrelay 1.1.0__py3-none-any.whl → 1.1.1__py3-none-any.whl

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/__init__.py CHANGED
@@ -14,4 +14,4 @@ else:
14
14
  __version__ = version("mmrelay")
15
15
  except PackageNotFoundError:
16
16
  # If all else fails, use hardcoded version
17
- __version__ = "1.1.0"
17
+ __version__ = "1.1.1"
@@ -50,14 +50,15 @@ reconnect_task = None # To keep track of the reconnect task
50
50
  # Track pubsub subscription state to prevent duplicate subscriptions during reconnections
51
51
  subscribed_to_messages = False
52
52
  subscribed_to_connection_lost = False
53
+ subscribed_to_connection_established = False
53
54
 
54
55
 
55
56
  def is_running_as_service():
56
57
  """
57
- Determine if the application is running as a systemd service.
58
-
58
+ Check if the application is running as a systemd service.
59
+
59
60
  Returns:
60
- bool: True if running under systemd (either via environment variable or parent process), False otherwise.
61
+ True if the process is running under systemd, either by detecting the INVOCATION_ID environment variable or by verifying that the parent process is systemd; otherwise, False.
61
62
  """
62
63
  # Check for INVOCATION_ID environment variable (set by systemd)
63
64
  if os.environ.get("INVOCATION_ID"):
@@ -88,14 +89,14 @@ def serial_port_exists(port_name):
88
89
 
89
90
  def connect_meshtastic(passed_config=None, force_connect=False):
90
91
  """
91
- Establishes and manages a connection to a Meshtastic device using the configured connection type (serial, BLE, or TCP).
92
-
93
- If a configuration is provided, updates the global configuration and Matrix room mappings. Handles reconnection logic, including closing any existing connection if `force_connect` is True. Retries connection attempts with exponential backoff until successful or shutdown is initiated. Subscribes to message and connection lost events only once per process to avoid duplicate subscriptions.
94
-
92
+ Establishes and manages a connection to a Meshtastic device using the specified connection type (serial, BLE, or TCP).
93
+
94
+ If a configuration is provided, updates the global configuration and Matrix room mappings. Handles reconnection logic, including closing any existing connection if `force_connect` is True. Retries connection attempts with exponential backoff until successful or shutdown is initiated. Subscribes to message and connection events only once per process to avoid duplicate subscriptions.
95
+
95
96
  Parameters:
96
97
  passed_config (dict, optional): Configuration dictionary to use for the connection. If provided, updates the global configuration.
97
98
  force_connect (bool, optional): If True, forces a new connection even if one already exists.
98
-
99
+
99
100
  Returns:
100
101
  Meshtastic interface client if the connection is successful, or None if the connection fails or shutdown is in progress.
101
102
  """
@@ -202,8 +203,8 @@ def connect_meshtastic(passed_config=None, force_connect=False):
202
203
  f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}"
203
204
  )
204
205
 
205
- # Subscribe to message and connection lost events (only if not already subscribed)
206
- global subscribed_to_messages, subscribed_to_connection_lost
206
+ # Subscribe to message and connection events (only if not already subscribed)
207
+ global subscribed_to_messages, subscribed_to_connection_lost, subscribed_to_connection_established
207
208
  if not subscribed_to_messages:
208
209
  pub.subscribe(on_meshtastic_message, "meshtastic.receive")
209
210
  subscribed_to_messages = True
@@ -216,6 +217,13 @@ def connect_meshtastic(passed_config=None, force_connect=False):
216
217
  subscribed_to_connection_lost = True
217
218
  logger.debug("Subscribed to meshtastic.connection.lost")
218
219
 
220
+ if not subscribed_to_connection_established:
221
+ pub.subscribe(
222
+ on_established_meshtastic_connection, "meshtastic.connection.established"
223
+ )
224
+ subscribed_to_connection_established = True
225
+ logger.debug("Subscribed to meshtastic.connection.established")
226
+
219
227
  except (
220
228
  serial.SerialException,
221
229
  BleakDBusError,
@@ -241,10 +249,13 @@ def connect_meshtastic(passed_config=None, force_connect=False):
241
249
  return meshtastic_client
242
250
 
243
251
 
244
- def on_lost_meshtastic_connection(interface=None):
252
+ def on_lost_meshtastic_connection(interface=None, detection_source="detected by library"):
245
253
  """
246
- Callback invoked when the Meshtastic connection is lost.
247
- Initiates a reconnect sequence unless shutting_down is True.
254
+ Handles Meshtastic connection loss by initiating a reconnection sequence unless a shutdown is in progress or a reconnection is already underway.
255
+
256
+ Parameters:
257
+ interface: The interface that lost connection (unused; present for compatibility).
258
+ detection_source (str): Description of how the disconnection was detected.
248
259
  """
249
260
  global meshtastic_client, reconnecting, shutting_down, event_loop, reconnect_task
250
261
  with meshtastic_lock:
@@ -257,7 +268,7 @@ def on_lost_meshtastic_connection(interface=None):
257
268
  )
258
269
  return
259
270
  reconnecting = True
260
- logger.error("Lost connection. Reconnecting...")
271
+ logger.error(f"Lost connection ({detection_source}). Reconnecting...")
261
272
 
262
273
  if meshtastic_client:
263
274
  try:
@@ -278,8 +289,9 @@ def on_lost_meshtastic_connection(interface=None):
278
289
 
279
290
  async def reconnect():
280
291
  """
281
- Asynchronously attempts to reconnect with exponential backoff.
282
- Stops if shutting_down is set.
292
+ Asynchronously attempts to reconnect to the Meshtastic device using exponential backoff, stopping if a shutdown is initiated.
293
+
294
+ The function increases the wait time between attempts up to a maximum of 5 minutes and provides a progress bar if not running as a service. The reconnection process halts immediately if the shutdown flag is set or if reconnection succeeds.
283
295
  """
284
296
  global meshtastic_client, reconnecting, shutting_down
285
297
  backoff_time = 10
@@ -334,11 +346,23 @@ async def reconnect():
334
346
  reconnecting = False
335
347
 
336
348
 
337
- def on_meshtastic_message(packet, interface):
349
+ def on_established_meshtastic_connection(interface=None):
350
+ """
351
+ Callback triggered when a Meshtastic connection is successfully established.
352
+
353
+ Clears the reconnecting flag and logs the connection event.
338
354
  """
339
- Processes incoming Meshtastic messages and relays them to Matrix rooms or plugins based on message type and interaction settings.
355
+ global reconnecting
356
+ with meshtastic_lock:
357
+ logger.info("Connection established (detected by library)")
358
+ reconnecting = False # Clear reconnecting flag when connection is confirmed
359
+
340
360
 
341
- Handles reactions and replies by relaying them to Matrix if enabled in the interaction settings. 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. Filters out messages from unmapped channels or disabled detection sensors, and ensures sender information is retrieved or stored as needed.
361
+ def on_meshtastic_message(packet, interface):
362
+ """
363
+ Process incoming Meshtastic messages and relay them to Matrix rooms or plugins according to message type and interaction settings.
364
+
365
+ 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 filtered out, and sender information is retrieved or stored as needed.
342
366
  """
343
367
  global config, matrix_rooms
344
368
 
@@ -643,10 +667,11 @@ def on_meshtastic_message(packet, interface):
643
667
 
644
668
  async def check_connection():
645
669
  """
646
- Periodically checks the Meshtastic connection by calling localNode.getMetadata().
647
- This creates an admin message to verify the connection is alive for all interface types.
648
- If it fails or doesn't return the firmware version, we assume the connection is lost
649
- and attempt to reconnect.
670
+ Periodically verifies the health of the Meshtastic connection and triggers reconnection if needed.
671
+
672
+ For non-BLE connections, this coroutine calls `localNode.getMetadata()` at a configurable interval to confirm the connection is alive. If the health check fails or does not return expected metadata, it invokes the connection lost handler to initiate reconnection. BLE connections are excluded from periodic checks, as their library provides real-time disconnection detection.
673
+
674
+ This coroutine runs continuously until shutdown is requested.
650
675
  """
651
676
  global meshtastic_client, shutting_down, config, reconnecting
652
677
 
@@ -663,33 +688,44 @@ async def check_connection():
663
688
  f"Starting connection heartbeat monitor (interval: {heartbeat_interval}s)"
664
689
  )
665
690
 
691
+ # Track if we've logged the BLE skip message to avoid spam
692
+ ble_skip_logged = False
693
+
666
694
  while not shutting_down:
667
695
  if meshtastic_client and not reconnecting:
668
- try:
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()
696
+ # BLE has real-time disconnection detection in the library
697
+ # Skip periodic health checks to avoid duplicate reconnection attempts
698
+ if connection_type == "ble":
699
+ if not ble_skip_logged:
700
+ logger.info("BLE connection uses real-time disconnection detection - health checks disabled")
701
+ ble_skip_logged = True
702
+ else:
703
+ try:
704
+ logger.debug(
705
+ f"Checking {connection_type} connection health using getMetadata()"
706
+ )
707
+ output_capture = io.StringIO()
708
+ with contextlib.redirect_stdout(
709
+ output_capture
710
+ ), contextlib.redirect_stderr(output_capture):
711
+ meshtastic_client.localNode.getMetadata()
677
712
 
678
- console_output = output_capture.getvalue()
679
- if "firmware_version" not in console_output:
680
- raise Exception("No firmware_version in getMetadata output.")
713
+ console_output = output_capture.getvalue()
714
+ if "firmware_version" not in console_output:
715
+ raise Exception("No firmware_version in getMetadata output.")
681
716
 
682
- logger.debug(f"{connection_type.capitalize()} connection healthy")
717
+ logger.debug(f"{connection_type.capitalize()} connection healthy")
683
718
 
684
- except Exception as e:
685
- # Only trigger reconnection if we're not already reconnecting
686
- if not reconnecting:
687
- logger.warning(
688
- f"{connection_type.capitalize()} connection health check failed: {e}"
689
- )
690
- on_lost_meshtastic_connection(meshtastic_client)
691
- else:
692
- logger.debug("Skipping reconnection trigger - already reconnecting")
719
+ except Exception as e:
720
+ # Only trigger reconnection if we're not already reconnecting
721
+ if not reconnecting:
722
+ logger.warning(
723
+ f"{connection_type.capitalize()} connection health check failed: {e}"
724
+ )
725
+ # Use existing handler with health check reason
726
+ on_lost_meshtastic_connection(detection_source=f"health check failed: {str(e)}")
727
+ else:
728
+ logger.debug("Skipping reconnection trigger - already reconnecting")
693
729
  elif reconnecting:
694
730
  logger.debug("Skipping connection check - reconnection in progress")
695
731
  elif not meshtastic_client:
@@ -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 this:
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmrelay
3
- Version: 1.1.0
3
+ Version: 1.1.1
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
 
@@ -1,4 +1,4 @@
1
- mmrelay/__init__.py,sha256=wo5bAmayppAqLHI02pEu7w2K2_YUxHWbnanigSNlXa8,594
1
+ mmrelay/__init__.py,sha256=v-xcK3JoA0m2eBmIudme82RPgp5PQInzRSs5mitHB28,594
2
2
  mmrelay/cli.py,sha256=hdPTlcGsXTJC9GEUiScG7b3IFp02B3lwhqgwFpU3NsM,13835
3
3
  mmrelay/config.py,sha256=5VZag8iSc5yLQgvwI76bbpizbtqag74cHnfXCrWHNyA,7910
4
4
  mmrelay/config_checker.py,sha256=UnoHVTXzfdTfFkbmXv9r_Si76v-sxXLb5FOaQSOM45E,4909
@@ -6,7 +6,7 @@ mmrelay/db_utils.py,sha256=eTMMkYVWsmO_DkrBfnZMw4ohg_xa0S9TXJoBjRFTwzo,13590
6
6
  mmrelay/log_utils.py,sha256=ot0GpYppyNuPOWY8d3EXdLPojoJYEqfmUjVlcYm8neY,7532
7
7
  mmrelay/main.py,sha256=TL5xWFXIGwAQKau-hN4sRB0wxtNWzc629ry_qPbovv0,11585
8
8
  mmrelay/matrix_utils.py,sha256=XSOHztRrdIjFGyYt8QhGBL6nMv_6iodeYL288pG9voA,45813
9
- mmrelay/meshtastic_utils.py,sha256=WdRiqbmCtUJrd45VZdJvfbrXct6rD09rtusi9nHMzc4,29163
9
+ mmrelay/meshtastic_utils.py,sha256=MgCNGSufQDddncVKJVgmp_6DDZbcHSvOvW72vuNDLeg,31596
10
10
  mmrelay/plugin_loader.py,sha256=NRiXF6Ty1WD9jNXXKvzJh7kE0ba5oICXNVAfMaTPqH4,39247
11
11
  mmrelay/setup_utils.py,sha256=N6qdScHKHEMFKDmT1l7dcLDPNTusZXPkyxrLXjFLhRI,19910
12
12
  mmrelay/plugins/__init__.py,sha256=KVMQIXRhe0wlGj4O3IZ0vOIQRKFkfPYejHXhJL17qrc,51
@@ -23,12 +23,12 @@ mmrelay/plugins/telemetry_plugin.py,sha256=8SxWv4BLXMUTbiVaD3MjlMMdQyS7S_1OfLlVN
23
23
  mmrelay/plugins/weather_plugin.py,sha256=1bQhmiX-enNphzGoFVprU0LcZQX9BvGxWAJAG8Wekg0,8596
24
24
  mmrelay/tools/__init__.py,sha256=WFjDQjdevgg19_zT6iEoL29rvb1JPqYSd8708Jn5D7A,838
25
25
  mmrelay/tools/mmrelay.service,sha256=3vqK1VbfXvVftkTrTEOan77aTHeOT36hIAL7HqJsmTg,567
26
- mmrelay/tools/sample-docker-compose.yaml,sha256=eXi-7TGb4fjC4g0Q-qs55pBZ4UWNKAstSO1ozOXlrUI,939
26
+ mmrelay/tools/sample-docker-compose.yaml,sha256=vVgJrh-6l48hkj5F-52JA5tpDWPBjiPQ36CE9Pkqn44,1251
27
27
  mmrelay/tools/sample.env,sha256=RP-o3rX3jnEIrVG2rqCZq31O1yRXou4HcGrXWLVbKKw,311
28
28
  mmrelay/tools/sample_config.yaml,sha256=0BKND0qbke8z9X9J9iHleu567dZt3RmHUxhZlQEEdFk,3290
29
- mmrelay-1.1.0.dist-info/licenses/LICENSE,sha256=yceWauM1c0-FHxVplsD7W1-AbSeRaUNlmqT4UO1msBU,1073
30
- mmrelay-1.1.0.dist-info/METADATA,sha256=IstoM9NyCmerUqL9QOEygC2BfAoxrHJLfXWY-J7kEd0,6658
31
- mmrelay-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
- mmrelay-1.1.0.dist-info/entry_points.txt,sha256=SJZwGUOEpQ-qx4H8UL4xKFnKeInGUaZNW1I0ddjK7Ws,45
33
- mmrelay-1.1.0.dist-info/top_level.txt,sha256=B_ZLCRm7NYAmI3PipRUyHGymP-C-q16LSeMGzmqJfo4,8
34
- mmrelay-1.1.0.dist-info/RECORD,,
29
+ mmrelay-1.1.1.dist-info/licenses/LICENSE,sha256=yceWauM1c0-FHxVplsD7W1-AbSeRaUNlmqT4UO1msBU,1073
30
+ mmrelay-1.1.1.dist-info/METADATA,sha256=ltSYRsnKQvdwsKd0CqAqMoSMWLsqZq8Pcpmfy_OjqI8,6713
31
+ mmrelay-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
+ mmrelay-1.1.1.dist-info/entry_points.txt,sha256=SJZwGUOEpQ-qx4H8UL4xKFnKeInGUaZNW1I0ddjK7Ws,45
33
+ mmrelay-1.1.1.dist-info/top_level.txt,sha256=B_ZLCRm7NYAmI3PipRUyHGymP-C-q16LSeMGzmqJfo4,8
34
+ mmrelay-1.1.1.dist-info/RECORD,,