mmrelay 1.0.11__py3-none-any.whl → 1.1.0__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.0.11"
17
+ __version__ = "1.1.0"
mmrelay/log_utils.py CHANGED
@@ -30,6 +30,17 @@ log_file_path = None
30
30
 
31
31
 
32
32
  def get_logger(name):
33
+ """
34
+ Create and configure a logger with console and optional file output, supporting colorized output and log rotation.
35
+
36
+ The logger's level, color usage, and file logging behavior are determined by global configuration and command line arguments. Console output uses rich formatting if enabled. File logging supports log rotation and stores logs in a configurable or default location. The log file path is stored globally if the logger name is "M<>M Relay".
37
+
38
+ Parameters:
39
+ name (str): The name of the logger to create.
40
+
41
+ Returns:
42
+ logging.Logger: The configured logger instance.
43
+ """
33
44
  logger = logging.getLogger(name=name)
34
45
 
35
46
  # Default to INFO level if config is not available
@@ -134,3 +145,48 @@ def get_logger(name):
134
145
  logger.addHandler(file_handler)
135
146
 
136
147
  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
mmrelay/main.py CHANGED
@@ -20,7 +20,7 @@ from mmrelay.db_utils import (
20
20
  update_shortnames,
21
21
  wipe_message_map,
22
22
  )
23
- from mmrelay.log_utils import get_logger
23
+ from mmrelay.log_utils import get_logger, setup_upstream_logging_capture
24
24
  from mmrelay.matrix_utils import connect_matrix, join_matrix_room
25
25
  from mmrelay.matrix_utils import logger as matrix_logger
26
26
  from mmrelay.matrix_utils import on_room_member, on_room_message
@@ -50,13 +50,12 @@ def print_banner():
50
50
 
51
51
  async def main(config):
52
52
  """
53
- Main asynchronous function to set up and run the relay.
54
- Includes logic for wiping the message_map if configured in database.msg_map.wipe_on_restart
55
- or db.msg_map.wipe_on_restart (legacy format).
56
- Also updates longnames and shortnames periodically as before.
53
+ Sets up and runs the asynchronous relay between Meshtastic and Matrix, managing connections, event handling, and graceful shutdown.
57
54
 
58
- Args:
59
- config: The loaded configuration
55
+ This function initializes the database, configures logging, loads plugins, connects to both Meshtastic and Matrix, joins specified Matrix rooms, and registers event callbacks for message and membership events. It periodically updates node names from the Meshtastic network and manages the Matrix sync loop, handling reconnections and shutdown signals. If configured, it wipes the message map on startup and shutdown.
56
+
57
+ Parameters:
58
+ config: The loaded configuration dictionary containing Matrix, Meshtastic, and database settings.
60
59
  """
61
60
  # Extract Matrix configuration
62
61
  from typing import List
@@ -69,6 +68,9 @@ async def main(config):
69
68
  # Initialize the SQLite database
70
69
  initialize_database()
71
70
 
71
+ # Set up upstream logging capture to format library messages consistently
72
+ setup_upstream_logging_capture()
73
+
72
74
  # Check database config for wipe_on_restart (preferred format)
73
75
  database_config = config.get("database", {})
74
76
  msg_map_config = database_config.get("msg_map", {})
@@ -131,11 +133,8 @@ async def main(config):
131
133
  # On Windows, we can't use add_signal_handler, so we'll handle KeyboardInterrupt
132
134
  pass
133
135
 
134
- # -------------------------------------------------------------------
135
- # IMPORTANT: We create a task to run the meshtastic_utils.check_connection()
136
- # so its while loop runs in parallel with the matrix sync loop
137
- # Use "_" to avoid trunk's "assigned but unused variable" warning
138
- # -------------------------------------------------------------------
136
+ # Start connection health monitoring using getMetadata() heartbeat
137
+ # This provides proactive connection detection for all interface types
139
138
  _ = asyncio.create_task(meshtastic_utils.check_connection())
140
139
 
141
140
  # Start the Matrix client sync loop
mmrelay/matrix_utils.py CHANGED
@@ -207,9 +207,9 @@ async def join_matrix_room(matrix_client, room_id_or_alias: str) -> None:
207
207
  if room_id_or_alias.startswith("#"):
208
208
  # If it's a room alias, resolve it to a room ID
209
209
  response = await matrix_client.room_resolve_alias(room_id_or_alias)
210
- if not response.room_id:
210
+ if not hasattr(response, "room_id") or not response.room_id:
211
211
  logger.error(
212
- f"Failed to resolve room alias '{room_id_or_alias}': {response.message}"
212
+ f"Failed to resolve room alias '{room_id_or_alias}': {getattr(response, 'message', str(response))}"
213
213
  )
214
214
  return
215
215
  room_id = response.room_id
@@ -510,23 +510,42 @@ async def send_reply_to_meshtastic(
510
510
  try:
511
511
  if reply_id is not None:
512
512
  # Send as a structured reply using our custom function
513
- sent_packet = sendTextReply(
514
- meshtastic_interface,
515
- text=reply_message,
516
- reply_id=reply_id,
517
- channelIndex=meshtastic_channel,
518
- )
519
- meshtastic_logger.info(
520
- f"Relaying Matrix reply from {full_display_name} to radio broadcast as structured reply to message {reply_id}"
521
- )
513
+ try:
514
+ sent_packet = sendTextReply(
515
+ meshtastic_interface,
516
+ text=reply_message,
517
+ reply_id=reply_id,
518
+ channelIndex=meshtastic_channel,
519
+ )
520
+ meshtastic_logger.info(
521
+ f"Relaying Matrix reply from {full_display_name} to radio broadcast as structured reply to message {reply_id}"
522
+ )
523
+ meshtastic_logger.debug(
524
+ f"sendTextReply returned packet: {sent_packet}"
525
+ )
526
+ except Exception as e:
527
+ meshtastic_logger.error(
528
+ f"Error sending structured reply to Meshtastic: {e}"
529
+ )
530
+ return
522
531
  else:
523
532
  # Send as regular message (fallback for when no reply_id is available)
524
- sent_packet = meshtastic_interface.sendText(
525
- text=reply_message, channelIndex=meshtastic_channel
526
- )
527
- meshtastic_logger.info(
528
- f"Relaying Matrix reply from {full_display_name} to radio broadcast as regular message"
529
- )
533
+ try:
534
+ meshtastic_logger.debug(
535
+ f"Attempting to send text to Meshtastic: '{reply_message}' on channel {meshtastic_channel}"
536
+ )
537
+ sent_packet = meshtastic_interface.sendText(
538
+ text=reply_message, channelIndex=meshtastic_channel
539
+ )
540
+ meshtastic_logger.info(
541
+ f"Relaying Matrix reply from {full_display_name} to radio broadcast as regular message"
542
+ )
543
+ meshtastic_logger.debug(f"sendText returned packet: {sent_packet}")
544
+ except Exception as e:
545
+ meshtastic_logger.error(
546
+ f"Error sending reply message to Meshtastic: {e}"
547
+ )
548
+ return
530
549
 
531
550
  # Store the reply in message map if storage is enabled
532
551
  if storage_enabled and sent_packet and hasattr(sent_packet, "id"):
@@ -766,9 +785,16 @@ async def on_room_message(
766
785
  logger.debug(
767
786
  f"Sending reaction to Meshtastic with meshnet={local_meshnet_name}: {reaction_message}"
768
787
  )
769
- meshtastic_interface.sendText(
770
- text=reaction_message, channelIndex=meshtastic_channel
771
- )
788
+ try:
789
+ sent_packet = meshtastic_interface.sendText(
790
+ text=reaction_message, channelIndex=meshtastic_channel
791
+ )
792
+ logger.debug(
793
+ f"Remote reaction sendText returned packet: {sent_packet}"
794
+ )
795
+ except Exception as e:
796
+ logger.error(f"Error sending remote reaction to Meshtastic: {e}")
797
+ return
772
798
  # We've relayed the remote reaction to our local mesh, so we're done.
773
799
  return
774
800
 
@@ -829,9 +855,16 @@ async def on_room_message(
829
855
  logger.debug(
830
856
  f"Sending reaction to Meshtastic with meshnet={local_meshnet_name}: {reaction_message}"
831
857
  )
832
- meshtastic_interface.sendText(
833
- text=reaction_message, channelIndex=meshtastic_channel
834
- )
858
+ try:
859
+ sent_packet = meshtastic_interface.sendText(
860
+ text=reaction_message, channelIndex=meshtastic_channel
861
+ )
862
+ logger.debug(
863
+ f"Local reaction sendText returned packet: {sent_packet}"
864
+ )
865
+ except Exception as e:
866
+ logger.error(f"Error sending local reaction to Meshtastic: {e}")
867
+ return
835
868
  return
836
869
 
837
870
  # Handle Matrix replies to Meshtastic messages (only if replies are enabled)
@@ -938,13 +971,25 @@ async def on_room_message(
938
971
  if portnum == "DETECTION_SENSOR_APP":
939
972
  # If detection_sensor is enabled, forward this data as detection sensor data
940
973
  if config["meshtastic"].get("detection_sensor", False):
941
- sent_packet = meshtastic_interface.sendData(
942
- data=full_message.encode("utf-8"),
943
- channelIndex=meshtastic_channel,
944
- portNum=meshtastic.protobuf.portnums_pb2.PortNum.DETECTION_SENSOR_APP,
945
- )
946
- # Note: Detection sensor messages are not stored in message_map because they are never replied to
947
- # Only TEXT_MESSAGE_APP messages need to be stored for reaction handling
974
+ try:
975
+ meshtastic_logger.debug(
976
+ f"Attempting to send detection sensor data to Meshtastic: '{full_message}' on channel {meshtastic_channel}"
977
+ )
978
+ sent_packet = meshtastic_interface.sendData(
979
+ data=full_message.encode("utf-8"),
980
+ channelIndex=meshtastic_channel,
981
+ portNum=meshtastic.protobuf.portnums_pb2.PortNum.DETECTION_SENSOR_APP,
982
+ )
983
+ meshtastic_logger.debug(
984
+ f"sendData returned packet: {sent_packet}"
985
+ )
986
+ # Note: Detection sensor messages are not stored in message_map because they are never replied to
987
+ # Only TEXT_MESSAGE_APP messages need to be stored for reaction handling
988
+ except Exception as e:
989
+ meshtastic_logger.error(
990
+ f"Error sending detection sensor data to Meshtastic: {e}"
991
+ )
992
+ return
948
993
  else:
949
994
  meshtastic_logger.debug(
950
995
  f"Detection sensor packet received from {full_display_name}, but detection sensor processing is disabled."
@@ -958,8 +1003,15 @@ async def on_room_message(
958
1003
  sent_packet = meshtastic_interface.sendText(
959
1004
  text=full_message, channelIndex=meshtastic_channel
960
1005
  )
1006
+ if not sent_packet:
1007
+ meshtastic_logger.warning(
1008
+ "sendText returned None - message may not have been sent"
1009
+ )
961
1010
  except Exception as e:
962
1011
  meshtastic_logger.error(f"Error sending message to Meshtastic: {e}")
1012
+ import traceback
1013
+
1014
+ meshtastic_logger.error(f"Full traceback: {traceback.format_exc()}")
963
1015
  return
964
1016
  # Store message_map only if storage is enabled and only for TEXT_MESSAGE_APP
965
1017
  # (these are the only messages that can be replied to and thus need reaction handling)
@@ -47,14 +47,17 @@ reconnecting = False
47
47
  shutting_down = False
48
48
  reconnect_task = None # To keep track of the reconnect task
49
49
 
50
+ # Track pubsub subscription state to prevent duplicate subscriptions during reconnections
51
+ subscribed_to_messages = False
52
+ subscribed_to_connection_lost = False
53
+
50
54
 
51
55
  def is_running_as_service():
52
56
  """
53
- Check if the application is running as a systemd service.
54
- This is used to determine whether to show Rich progress indicators.
57
+ Determine if the application is running as a systemd service.
55
58
 
56
59
  Returns:
57
- bool: True if running as a service, False otherwise
60
+ bool: True if running under systemd (either via environment variable or parent process), False otherwise.
58
61
  """
59
62
  # Check for INVOCATION_ID environment variable (set by systemd)
60
63
  if os.environ.get("INVOCATION_ID"):
@@ -85,14 +88,16 @@ def serial_port_exists(port_name):
85
88
 
86
89
  def connect_meshtastic(passed_config=None, force_connect=False):
87
90
  """
88
- Establish a connection to the Meshtastic device.
89
- Attempts a connection based on connection_type (serial/ble/network).
90
- Retries until successful or shutting_down is set.
91
- If already connected and not force_connect, returns the existing client.
91
+ Establishes and manages a connection to a Meshtastic device using the configured connection type (serial, BLE, or TCP).
92
92
 
93
- Args:
94
- passed_config: The configuration dictionary to use (will update global config)
95
- force_connect: Whether to force a new connection even if one exists
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
+
95
+ Parameters:
96
+ passed_config (dict, optional): Configuration dictionary to use for the connection. If provided, updates the global configuration.
97
+ force_connect (bool, optional): If True, forces a new connection even if one already exists.
98
+
99
+ Returns:
100
+ Meshtastic interface client if the connection is successful, or None if the connection fails or shutdown is in progress.
96
101
  """
97
102
  global meshtastic_client, shutting_down, config, matrix_rooms
98
103
  if shutting_down:
@@ -197,11 +202,19 @@ def connect_meshtastic(passed_config=None, force_connect=False):
197
202
  f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}"
198
203
  )
199
204
 
200
- # Subscribe to message and connection lost events
201
- pub.subscribe(on_meshtastic_message, "meshtastic.receive")
202
- pub.subscribe(
203
- on_lost_meshtastic_connection, "meshtastic.connection.lost"
204
- )
205
+ # Subscribe to message and connection lost events (only if not already subscribed)
206
+ global subscribed_to_messages, subscribed_to_connection_lost
207
+ if not subscribed_to_messages:
208
+ pub.subscribe(on_meshtastic_message, "meshtastic.receive")
209
+ subscribed_to_messages = True
210
+ logger.debug("Subscribed to meshtastic.receive")
211
+
212
+ if not subscribed_to_connection_lost:
213
+ pub.subscribe(
214
+ on_lost_meshtastic_connection, "meshtastic.connection.lost"
215
+ )
216
+ subscribed_to_connection_lost = True
217
+ logger.debug("Subscribed to meshtastic.connection.lost")
205
218
 
206
219
  except (
207
220
  serial.SerialException,
@@ -631,20 +644,31 @@ def on_meshtastic_message(packet, interface):
631
644
  async def check_connection():
632
645
  """
633
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.
634
648
  If it fails or doesn't return the firmware version, we assume the connection is lost
635
649
  and attempt to reconnect.
636
650
  """
637
- global meshtastic_client, shutting_down, config
651
+ global meshtastic_client, shutting_down, config, reconnecting
638
652
 
639
653
  # Check if config is available
640
654
  if config is None:
641
655
  logger.error("No configuration available. Cannot check connection.")
642
656
  return
643
657
 
658
+ # Get heartbeat interval from config, default to 180 seconds
659
+ heartbeat_interval = config["meshtastic"].get("heartbeat_interval", 180)
644
660
  connection_type = config["meshtastic"]["connection_type"]
661
+
662
+ logger.info(
663
+ f"Starting connection heartbeat monitor (interval: {heartbeat_interval}s)"
664
+ )
665
+
645
666
  while not shutting_down:
646
- if meshtastic_client:
667
+ if meshtastic_client and not reconnecting:
647
668
  try:
669
+ logger.debug(
670
+ f"Checking {connection_type} connection health using getMetadata()"
671
+ )
648
672
  output_capture = io.StringIO()
649
673
  with contextlib.redirect_stdout(
650
674
  output_capture
@@ -655,10 +679,23 @@ async def check_connection():
655
679
  if "firmware_version" not in console_output:
656
680
  raise Exception("No firmware_version in getMetadata output.")
657
681
 
682
+ logger.debug(f"{connection_type.capitalize()} connection healthy")
683
+
658
684
  except Exception as e:
659
- logger.error(f"{connection_type.capitalize()} connection lost: {e}")
660
- on_lost_meshtastic_connection(meshtastic_client)
661
- await asyncio.sleep(30) # Check connection every 30 seconds
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")
693
+ elif reconnecting:
694
+ logger.debug("Skipping connection check - reconnection in progress")
695
+ elif not meshtastic_client:
696
+ logger.debug("Skipping connection check - no client available")
697
+
698
+ await asyncio.sleep(heartbeat_interval)
662
699
 
663
700
 
664
701
  def sendTextReply(
@@ -0,0 +1,32 @@
1
+ services:
2
+ mmrelay:
3
+ build: .
4
+ container_name: meshtastic-matrix-relay
5
+ restart: unless-stopped
6
+
7
+ environment:
8
+ - TZ=UTC
9
+ - PYTHONUNBUFFERED=1
10
+ - MPLCONFIGDIR=/tmp/matplotlib
11
+
12
+ volumes:
13
+ # Configuration - uses standard ~/.mmrelay/config.yaml location
14
+ # Create this first with: make config
15
+ - ${MMRELAY_HOME}/.mmrelay/config.yaml:/app/config.yaml:ro
16
+
17
+ # Data and logs - same locations as standalone installation
18
+ # These directories will be created automatically
19
+ - ${MMRELAY_HOME}/.mmrelay/data:/app/data
20
+ - ${MMRELAY_HOME}/.mmrelay/logs:/app/logs
21
+
22
+ # For TCP connections (most common) - Meshtastic typically uses port 4403
23
+ ports:
24
+ - 4403:4403
25
+
26
+ # For serial connections, uncomment the device you need:
27
+ # devices:
28
+ # - /dev/ttyUSB0:/dev/ttyUSB0
29
+ # - /dev/ttyACM0:/dev/ttyACM0
30
+
31
+ # For BLE connections, uncomment this:
32
+ # privileged: true
@@ -0,0 +1,10 @@
1
+ # Docker Compose environment variables
2
+ # Customize these paths if needed
3
+
4
+ # Base directory for mmrelay data
5
+ # This will be expanded by your shell when docker compose runs
6
+ MMRELAY_HOME=$HOME
7
+
8
+ # Preferred editor for config editing
9
+ # Will be set automatically when you select an editor via 'make edit'
10
+ EDITOR=nano
@@ -10,18 +10,18 @@ matrix_rooms: # Needs at least 1 room & channel, but supports all Meshtastic cha
10
10
  meshtastic_channel: 2
11
11
 
12
12
  meshtastic:
13
- connection_type: serial # Choose either "tcp", "serial", or "ble"
14
- serial_port: /dev/ttyUSB0 # Only used when connection is "serial"
13
+ connection_type: tcp # Choose either "tcp", "serial", or "ble"
15
14
  host: meshtastic.local # Only used when connection is "tcp"
15
+ serial_port: /dev/ttyUSB0 # Only used when connection is "serial"
16
16
  ble_address: AA:BB:CC:DD:EE:FF # Only used when connection is "ble" - Uses either an address or name from a `meshtastic --ble-scan`
17
17
  meshnet_name: Your Meshnet Name # This is displayed in full on Matrix, but is truncated when sent to a Meshnet
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)
21
22
  message_interactions: # Configure reactions and replies (both require message storage in database)
22
23
  reactions: false # Enable reaction relaying between platforms
23
24
  replies: false # Enable reply relaying between platforms
24
- # Note: Legacy 'relay_reactions' setting is deprecated but still supported
25
25
 
26
26
  logging:
27
27
  level: info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmrelay
3
- Version: 1.0.11
3
+ Version: 1.1.0
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
@@ -14,8 +14,8 @@ Classifier: Topic :: Communications
14
14
  Requires-Python: >=3.8
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: meshtastic
18
- Requires-Dist: Pillow==11.2.1
17
+ Requires-Dist: meshtastic>=2.6.4
18
+ Requires-Dist: Pillow==11.3.0
19
19
  Requires-Dist: matrix-nio==0.25.2
20
20
  Requires-Dist: matplotlib==3.10.1
21
21
  Requires-Dist: requests==2.32.4
@@ -26,7 +26,17 @@ Requires-Dist: platformdirs==4.3.8
26
26
  Requires-Dist: py-staticmaps>=0.4.0
27
27
  Requires-Dist: rich==14.0.0
28
28
  Requires-Dist: setuptools==80.9.0
29
+ Dynamic: author
30
+ Dynamic: author-email
31
+ Dynamic: classifier
32
+ Dynamic: description
33
+ Dynamic: description-content-type
34
+ Dynamic: home-page
29
35
  Dynamic: license-file
36
+ Dynamic: project-url
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
30
40
 
31
41
  # M<>M Relay
32
42
 
@@ -34,13 +44,26 @@ Dynamic: license-file
34
44
 
35
45
  A powerful and easy-to-use relay between Meshtastic devices and Matrix chat rooms, allowing seamless communication across platforms. This opens the door for bridging Meshtastic devices to [many other platforms](https://matrix.org/bridges/).
36
46
 
47
+ ## Features
48
+
49
+ - Bidirectional message relay between Meshtastic devices and Matrix chat rooms, capable of supporting multiple meshnets
50
+ - Supports serial, network, and **_BLE (now too!)_** connections for Meshtastic devices
51
+ - Custom fields are embedded in Matrix messages for relaying messages between multiple meshnets
52
+ - Truncates long messages to fit within Meshtastic's payload size
53
+ - SQLite database to store node information for improved functionality
54
+ - Customizable logging level for easy debugging
55
+ - Configurable through a simple YAML file
56
+ - Supports mapping multiple rooms and channels 1:1
57
+ - Relays messages to/from an MQTT broker, if configured in the Meshtastic firmware
58
+ - ✨️ _Bidirectional replies and reactions support_ ✨️ **NEW!!**
59
+
60
+ _We would love to support [Matrix E2EE rooms](https://github.com/geoffwhittington/meshtastic-matrix-relay/issues/33), but this is currently not implemented._
61
+
37
62
  ## Documentation
38
63
 
39
64
  Visit our [Wiki](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki) for comprehensive guides and information.
40
65
 
41
66
  - [Installation Instructions](docs/INSTRUCTIONS.md) - Setup and configuration guide
42
- - [v1.0 Release Announcement](docs/ANNOUNCEMENT.md) - New changes in v1.0
43
- - [Upgrade Guide](docs/UPGRADE_TO_V1.md) - Migration guidance for existing users
44
67
 
45
68
  ---
46
69
 
@@ -61,22 +84,23 @@ mmrelay --install-service
61
84
 
62
85
  For detailed installation and configuration instructions, see the [Installation Guide](docs/INSTRUCTIONS.md).
63
86
 
64
- ---
87
+ ## Docker
65
88
 
66
- ## Features
89
+ MMRelay includes official Docker support for easy deployment and management:
67
90
 
68
- - Bidirectional message relay between Meshtastic devices and Matrix chat rooms, capable of supporting multiple meshnets
69
- - Supports serial, network, and **_BLE (now too!)_** connections for Meshtastic devices
70
- - Custom fields are embedded in Matrix messages for relaying messages between multiple meshnets
71
- - Truncates long messages to fit within Meshtastic's payload size
72
- - SQLite database to store node information for improved functionality
73
- - Customizable logging level for easy debugging
74
- - Configurable through a simple YAML file
75
- - Supports mapping multiple rooms and channels 1:1
76
- - Relays messages to/from an MQTT broker, if configured in the Meshtastic firmware
77
- - ✨️ _Cross-platform reactions support_ ✨️ **NEW!!**
91
+ ```bash
92
+ # Quick setup with Docker
93
+ make setup # Copy config and open editor (first time)
94
+ make build # Build the Docker image
95
+ make run # Start the container
96
+ make logs # View logs
97
+ ```
78
98
 
79
- _We would love to support [Matrix E2EE rooms](https://github.com/geoffwhittington/meshtastic-matrix-relay/issues/33), but this is currently not implemented._
99
+ Docker provides isolated environment, easy deployment, automatic restarts, and volume persistence.
100
+
101
+ For detailed Docker setup instructions, see the [Docker Guide](DOCKER.md).
102
+
103
+ > **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.
80
104
 
81
105
  ---
82
106
 
@@ -84,7 +108,7 @@ _We would love to support [Matrix E2EE rooms](https://github.com/geoffwhittingto
84
108
 
85
109
  ![Windows Installer Screenshot](https://user-images.githubusercontent.com/1770544/235249050-8c79107a-50cc-4803-b989-39e58100342d.png)
86
110
 
87
- The latest installer is available [here](https://github.com/geoffwhittington/meshtastic-matrix-relay/releases).
111
+ The latest installer is available in the [releases section](https://github.com/geoffwhittington/meshtastic-matrix-relay/releases).
88
112
 
89
113
  ---
90
114
 
@@ -102,7 +126,7 @@ Produce high-level details about your mesh:
102
126
 
103
127
  ![Mesh Details Screenshot](https://user-images.githubusercontent.com/1770544/235245873-1ddc773b-a4cd-4c67-b0a5-b55a29504b73.png)
104
128
 
105
- See the full list of core plugins [here](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Core-Plugins).
129
+ See the full list of [core plugins](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Core-Plugins).
106
130
 
107
131
  ### Community & Custom Plugins
108
132
 
@@ -147,6 +171,6 @@ See our Wiki page [Getting Started With Matrix & MM Relay](https://github.com/ge
147
171
 
148
172
  Join us!
149
173
 
150
- - Our project's room: [#mmrelay:meshnet.club](https://matrix.to/#/#mmrelay:meshnet.club)
151
- - Part of the Meshtastic Community Matrix space: [#meshtastic-community:meshnet.club](https://matrix.to/#/#meshtastic-community:meshnet.club)
152
- - Public Relay Room: [#relay-room:meshnet.club](https://matrix.to/#/#relay-room:meshnet.club) - Where we bridge multiple meshnets. Feel free to join us, with or without a relay!
174
+ - Our project's room: [#mmrelay:matrix.org](https://matrix.to/#/#mmrelay:matrix.org)
175
+ - Part of the Meshtastic Community Matrix space: [#meshnetclub:matrix.org](https://matrix.to/#/#meshnetclub:matrix.org)
176
+ - 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!
@@ -1,12 +1,12 @@
1
- mmrelay/__init__.py,sha256=XeHKtvrDgq8NM54pZypZ-29wpSYfW2ZBq4vBqTw3FQM,595
1
+ mmrelay/__init__.py,sha256=wo5bAmayppAqLHI02pEu7w2K2_YUxHWbnanigSNlXa8,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
5
5
  mmrelay/db_utils.py,sha256=eTMMkYVWsmO_DkrBfnZMw4ohg_xa0S9TXJoBjRFTwzo,13590
6
- mmrelay/log_utils.py,sha256=FXhaq4WSDHwlqiG3k1BbSxiOl5be4P4Kyr1gsIThOBw,4572
7
- mmrelay/main.py,sha256=88hmJzv-Fw3CSjcMSlQWKjlZlIlZBjOujMfi85r5uCk,11301
8
- mmrelay/matrix_utils.py,sha256=dtmj6yFUax6xR5NTpbgK_6WYHIcg8saFhU05_tviN7A,43172
9
- mmrelay/meshtastic_utils.py,sha256=7r8FnZiwlmLZbj3PtVuMO2Plt1reYeGnclxFyMq7hBU,26953
6
+ mmrelay/log_utils.py,sha256=ot0GpYppyNuPOWY8d3EXdLPojoJYEqfmUjVlcYm8neY,7532
7
+ mmrelay/main.py,sha256=TL5xWFXIGwAQKau-hN4sRB0wxtNWzc629ry_qPbovv0,11585
8
+ mmrelay/matrix_utils.py,sha256=XSOHztRrdIjFGyYt8QhGBL6nMv_6iodeYL288pG9voA,45813
9
+ mmrelay/meshtastic_utils.py,sha256=WdRiqbmCtUJrd45VZdJvfbrXct6rD09rtusi9nHMzc4,29163
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,10 +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_config.yaml,sha256=HVEUYawNILwMukAnQL4Eh_2PlSvt2581eyfkxl8OxsA,3258
27
- mmrelay-1.0.11.dist-info/licenses/LICENSE,sha256=yceWauM1c0-FHxVplsD7W1-AbSeRaUNlmqT4UO1msBU,1073
28
- mmrelay-1.0.11.dist-info/METADATA,sha256=qWciy6Gpa4aa0pToMg4wDSgQUqM0HMZhhUaIA2fr8jM,5919
29
- mmrelay-1.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
- mmrelay-1.0.11.dist-info/entry_points.txt,sha256=SJZwGUOEpQ-qx4H8UL4xKFnKeInGUaZNW1I0ddjK7Ws,45
31
- mmrelay-1.0.11.dist-info/top_level.txt,sha256=B_ZLCRm7NYAmI3PipRUyHGymP-C-q16LSeMGzmqJfo4,8
32
- mmrelay-1.0.11.dist-info/RECORD,,
26
+ mmrelay/tools/sample-docker-compose.yaml,sha256=eXi-7TGb4fjC4g0Q-qs55pBZ4UWNKAstSO1ozOXlrUI,939
27
+ mmrelay/tools/sample.env,sha256=RP-o3rX3jnEIrVG2rqCZq31O1yRXou4HcGrXWLVbKKw,311
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,,