mmrelay 1.1.2__py3-none-any.whl → 1.1.4__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.

@@ -7,6 +7,11 @@ import markdown
7
7
  import schedule
8
8
 
9
9
  from mmrelay.config import get_plugin_data_dir
10
+ from mmrelay.constants.database import (
11
+ DEFAULT_MAX_DATA_ROWS_PER_NODE_BASE,
12
+ DEFAULT_TEXT_TRUNCATION_LENGTH,
13
+ )
14
+ from mmrelay.constants.queue import DEFAULT_MESSAGE_DELAY
10
15
  from mmrelay.db_utils import (
11
16
  delete_plugin_data,
12
17
  get_plugin_data,
@@ -14,10 +19,14 @@ from mmrelay.db_utils import (
14
19
  store_plugin_data,
15
20
  )
16
21
  from mmrelay.log_utils import get_logger
22
+ from mmrelay.message_queue import queue_message
17
23
 
18
24
  # Global config variable that will be set from main.py
19
25
  config = None
20
26
 
27
+ # Track if we've already shown the deprecated warning
28
+ _deprecated_warning_shown = False
29
+
21
30
 
22
31
  class BasePlugin(ABC):
23
32
  """Abstract base class for all mmrelay plugins.
@@ -43,7 +52,7 @@ class BasePlugin(ABC):
43
52
 
44
53
  # Class-level default attributes
45
54
  plugin_name = None # Must be overridden in subclasses
46
- max_data_rows_per_node = 100
55
+ max_data_rows_per_node = DEFAULT_MAX_DATA_ROWS_PER_NODE_BASE
47
56
  priority = 10
48
57
 
49
58
  @property
@@ -59,20 +68,16 @@ class BasePlugin(ABC):
59
68
  return ""
60
69
 
61
70
  def __init__(self, plugin_name=None) -> None:
62
- """Initialize the plugin with configuration and logging.
71
+ """
72
+ Initialize the plugin instance, setting its name, logger, configuration, mapped channels, and response delay.
63
73
 
64
- Args:
65
- plugin_name (str, optional): Plugin name override. If not provided,
66
- uses class-level plugin_name attribute.
74
+ Parameters:
75
+ plugin_name (str, optional): Overrides the plugin's name. If not provided, uses the class-level `plugin_name` attribute.
67
76
 
68
77
  Raises:
69
- ValueError: If plugin_name is not set via parameter or class attribute
78
+ ValueError: If the plugin name is not set via parameter or class attribute.
70
79
 
71
- Sets up:
72
- - Plugin-specific logger
73
- - Configuration from global config
74
- - Channel mapping and validation
75
- - Response delay settings
80
+ Loads plugin-specific configuration from the global config, validates assigned channels, and determines the response delay, enforcing a minimum of 2.0 seconds. Logs a warning if deprecated configuration options are used or if channels are not mapped.
76
81
  """
77
82
  # Allow plugin_name to be passed as a parameter for simpler initialization
78
83
  # This maintains backward compatibility while providing a cleaner API
@@ -132,26 +137,43 @@ class BasePlugin(ABC):
132
137
  f"Plugin '{self.plugin_name}': Channels {invalid_channels} are not mapped in configuration."
133
138
  )
134
139
 
135
- # Get the response delay from the meshtastic config only
136
- self.response_delay = 3 # Default value
140
+ # Get the response delay from the meshtastic config
141
+ self.response_delay = DEFAULT_MESSAGE_DELAY
137
142
  if config is not None:
138
- self.response_delay = config.get("meshtastic", {}).get(
139
- "plugin_response_delay", self.response_delay
140
- )
143
+ meshtastic_config = config.get("meshtastic", {})
144
+
145
+ # Check for new message_delay option first, with fallback to deprecated option
146
+ delay = None
147
+ delay_key = None
148
+ if "message_delay" in meshtastic_config:
149
+ delay = meshtastic_config["message_delay"]
150
+ delay_key = "message_delay"
151
+ elif "plugin_response_delay" in meshtastic_config:
152
+ delay = meshtastic_config["plugin_response_delay"]
153
+ delay_key = "plugin_response_delay"
154
+ # Show deprecated warning only once globally
155
+ global _deprecated_warning_shown
156
+ if not _deprecated_warning_shown:
157
+ self.logger.warning(
158
+ "Configuration option 'plugin_response_delay' is deprecated. "
159
+ "Please use 'message_delay' instead. Support for 'plugin_response_delay' will be removed in a future version."
160
+ )
161
+ _deprecated_warning_shown = True
162
+
163
+ if delay is not None:
164
+ self.response_delay = delay
165
+ # Enforce minimum delay of 2 seconds due to firmware constraints
166
+ if self.response_delay < 2.0:
167
+ self.logger.warning(
168
+ f"{delay_key} of {self.response_delay}s is below minimum of 2.0s (firmware constraint). Using 2.0s."
169
+ )
170
+ self.response_delay = 2.0
141
171
 
142
172
  def start(self):
143
- """Start the plugin and set up scheduled tasks if configured.
144
-
145
- Called automatically when the plugin is loaded. Checks plugin configuration
146
- for scheduling settings and sets up background jobs accordingly.
147
-
148
- Supported schedule formats in config:
149
- - schedule.hours + schedule.at: Run every N hours at specific time
150
- - schedule.minutes + schedule.at: Run every N minutes at specific time
151
- - schedule.hours: Run every N hours
152
- - schedule.minutes: Run every N minutes
173
+ """
174
+ Starts the plugin and configures scheduled background tasks based on plugin settings.
153
175
 
154
- Creates a daemon thread to run the scheduler if any schedule is configured.
176
+ If scheduling options are present in the plugin configuration, sets up periodic execution of the `background_job` method using the specified schedule. Runs scheduled jobs in a separate daemon thread. If no scheduling is configured, the plugin starts without background tasks.
155
177
  """
156
178
  if "schedule" not in self.config or (
157
179
  "at" not in self.config["schedule"]
@@ -224,28 +246,106 @@ class BasePlugin(ABC):
224
246
  return data
225
247
 
226
248
  def get_response_delay(self):
227
- """Get the configured response delay for meshtastic messages.
249
+ """
250
+ Return the configured delay in seconds before sending a Meshtastic response.
228
251
 
229
- Returns:
230
- int: Delay in seconds before sending responses (default: 3)
252
+ The delay is determined by the `meshtastic.message_delay` configuration option, defaulting to 2.2 seconds with a minimum of 2.0 seconds. The deprecated `plugin_response_delay` option is also supported for backward compatibility.
231
253
 
232
- Used to prevent message flooding and ensure proper radio etiquette.
233
- Delay is configured via meshtastic.plugin_response_delay in config.
254
+ Returns:
255
+ float: The response delay in seconds.
234
256
  """
235
257
  return self.response_delay
236
258
 
237
- def is_channel_enabled(self, channel, is_direct_message=False):
238
- """Check if the plugin should respond on a specific channel.
259
+ def get_my_node_id(self):
260
+ """Get the relay's Meshtastic node ID.
261
+
262
+ Returns:
263
+ int: The relay's node ID, or None if unavailable
264
+
265
+ This method provides access to the relay's own node ID without requiring
266
+ plugins to call connect_meshtastic() directly. Useful for determining
267
+ if messages are direct messages or for other node identification needs.
268
+
269
+ The node ID is cached after first successful retrieval to avoid repeated
270
+ connection calls, as the relay's node ID is static during runtime.
271
+ """
272
+ if hasattr(self, "_my_node_id"):
273
+ return self._my_node_id
274
+
275
+ from mmrelay.meshtastic_utils import connect_meshtastic
276
+
277
+ meshtastic_client = connect_meshtastic()
278
+ if meshtastic_client and meshtastic_client.myInfo:
279
+ self._my_node_id = meshtastic_client.myInfo.my_node_num
280
+ return self._my_node_id
281
+ return None
282
+
283
+ def is_direct_message(self, packet):
284
+ """Check if a Meshtastic packet is a direct message to this relay.
239
285
 
240
286
  Args:
241
- channel: Channel identifier to check
242
- is_direct_message (bool): Whether this is a direct message
287
+ packet (dict): Meshtastic packet data
288
+
289
+ Returns:
290
+ bool: True if the packet is a direct message to this relay, False otherwise
291
+
292
+ This method encapsulates the common pattern of checking if a message
293
+ is addressed directly to the relay node, eliminating the need for plugins
294
+ to call connect_meshtastic() directly for DM detection.
295
+ """
296
+ toId = packet.get("to")
297
+ if toId is None:
298
+ return False
299
+
300
+ myId = self.get_my_node_id()
301
+ return toId == myId
302
+
303
+ def send_message(self, text: str, channel: int = 0, destination_id=None) -> bool:
304
+ """
305
+ Send a message to the Meshtastic network using the message queue.
306
+
307
+ Queues the specified text for broadcast or direct delivery on the given channel. Returns True if the message was successfully queued, or False if the Meshtastic client is unavailable.
308
+
309
+ Parameters:
310
+ text (str): The message content to send.
311
+ channel (int, optional): The channel index for sending the message. Defaults to 0.
312
+ destination_id (optional): The destination node ID for direct messages. If None, the message is broadcast.
243
313
 
244
314
  Returns:
245
- bool: True if plugin should respond, False otherwise
315
+ bool: True if the message was queued successfully; False otherwise.
316
+ """
317
+ from mmrelay.meshtastic_utils import connect_meshtastic
318
+
319
+ meshtastic_client = connect_meshtastic()
320
+ if not meshtastic_client:
321
+ self.logger.error("No Meshtastic client available")
322
+ return False
323
+
324
+ description = f"Plugin {self.plugin_name}: {text[:DEFAULT_TEXT_TRUNCATION_LENGTH]}{'...' if len(text) > DEFAULT_TEXT_TRUNCATION_LENGTH else ''}"
325
+
326
+ send_kwargs = {
327
+ "text": text,
328
+ "channelIndex": channel,
329
+ }
330
+ if destination_id:
331
+ send_kwargs["destinationId"] = destination_id
332
+
333
+ return queue_message(
334
+ meshtastic_client.sendText,
335
+ description=description,
336
+ **send_kwargs,
337
+ )
246
338
 
247
- Direct messages always return True if the plugin is active.
248
- For channel messages, checks if channel is in plugin's configured channels list.
339
+ def is_channel_enabled(self, channel, is_direct_message=False):
340
+ """
341
+ Determine whether the plugin should respond to a message on the specified channel or direct message.
342
+
343
+ Parameters:
344
+ channel: The channel identifier to check.
345
+ is_direct_message (bool): Set to True if the message is a direct message.
346
+
347
+ Returns:
348
+ bool: True if the plugin should respond on the given channel or to a direct message; False otherwise.
249
349
  """
250
350
  if is_direct_message:
251
351
  return True # Always respond to DMs if the plugin is active
@@ -2,6 +2,8 @@ import re
2
2
 
3
3
  from haversine import haversine
4
4
 
5
+ from mmrelay.constants.database import DEFAULT_DISTANCE_KM_FALLBACK, DEFAULT_RADIUS_KM
6
+ from mmrelay.constants.formats import TEXT_MESSAGE_APP
5
7
  from mmrelay.meshtastic_utils import connect_meshtastic
6
8
  from mmrelay.plugins.base_plugin import BasePlugin
7
9
 
@@ -25,6 +27,14 @@ class Plugin(BasePlugin):
25
27
  async def handle_meshtastic_message(
26
28
  self, packet, formatted_message, longname, meshnet_name
27
29
  ):
30
+ """
31
+ Handles incoming Meshtastic packets for the drop message plugin, delivering or storing dropped messages based on packet content and node location.
32
+
33
+ When a packet is received, attempts to deliver any stored dropped messages to the sender if they are within a configured radius of the message's location and are not the original dropper. If the packet contains a properly formatted drop command, extracts the message and stores it with the sender's current location for future delivery.
34
+
35
+ Returns:
36
+ True if a drop command was processed and stored, False otherwise.
37
+ """
28
38
  meshtastic_client = connect_meshtastic()
29
39
  nodeInfo = meshtastic_client.getMyNodeInfo()
30
40
 
@@ -55,10 +65,8 @@ class Plugin(BasePlugin):
55
65
  message["location"],
56
66
  )
57
67
  except (ValueError, TypeError):
58
- distance_km = 1000
59
- radius_km = (
60
- self.config["radius_km"] if "radius_km" in self.config else 5
61
- )
68
+ distance_km = DEFAULT_DISTANCE_KM_FALLBACK
69
+ radius_km = self.config.get("radius_km", DEFAULT_RADIUS_KM)
62
70
  if distance_km <= radius_km:
63
71
  target_node = packet["fromId"]
64
72
  self.logger.debug(f"Sending dropped message to {target_node}")
@@ -76,7 +84,7 @@ class Plugin(BasePlugin):
76
84
  if (
77
85
  "decoded" in packet
78
86
  and "portnum" in packet["decoded"]
79
- and packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP"
87
+ and packet["decoded"]["portnum"] == TEXT_MESSAGE_APP
80
88
  ):
81
89
  text = packet["decoded"]["text"] if "text" in packet["decoded"] else None
82
90
  if f"!{self.plugin_name}" not in text:
@@ -6,6 +6,7 @@ import re
6
6
 
7
7
  from meshtastic import mesh_pb2
8
8
 
9
+ from mmrelay.constants.database import DEFAULT_MAX_DATA_ROWS_PER_NODE_MESH_RELAY
9
10
  from mmrelay.plugins.base_plugin import BasePlugin, config
10
11
 
11
12
 
@@ -25,21 +26,17 @@ class Plugin(BasePlugin):
25
26
  """
26
27
 
27
28
  plugin_name = "mesh_relay"
28
- max_data_rows_per_node = 50
29
+ max_data_rows_per_node = DEFAULT_MAX_DATA_ROWS_PER_NODE_MESH_RELAY
29
30
 
30
31
  def normalize(self, dict_obj):
31
- """Normalize packet data to consistent dictionary format.
32
+ """
33
+ Converts packet data in various formats (dict, JSON string, or plain string) into a normalized dictionary with raw data fields removed.
32
34
 
33
- Args:
34
- dict_obj: Packet data (dict, JSON string, or plain string)
35
+ Parameters:
36
+ dict_obj: Packet data as a dictionary, JSON string, or plain string.
35
37
 
36
38
  Returns:
37
- dict: Normalized packet dictionary with raw data stripped
38
-
39
- Handles various packet formats:
40
- - Dict objects (passed through)
41
- - JSON strings (parsed)
42
- - Plain strings (wrapped in TEXT_MESSAGE_APP format)
39
+ A dictionary representing the normalized packet with raw fields stripped.
43
40
  """
44
41
  if not isinstance(dict_obj, dict):
45
42
  try:
@@ -3,6 +3,7 @@ import asyncio
3
3
  import requests
4
4
  from meshtastic.mesh_interface import BROADCAST_NUM
5
5
 
6
+ from mmrelay.constants.formats import TEXT_MESSAGE_APP
6
7
  from mmrelay.plugins.base_plugin import BasePlugin
7
8
 
8
9
 
@@ -122,10 +123,18 @@ class Plugin(BasePlugin):
122
123
  async def handle_meshtastic_message(
123
124
  self, packet, formatted_message, longname, meshnet_name
124
125
  ):
126
+ """
127
+ Processes incoming Meshtastic text messages and responds with a weather forecast if the plugin command is detected.
128
+
129
+ Checks if the message is a valid text message on the expected port, verifies channel and command enablement, retrieves the sender's GPS location, generates a weather forecast, and sends the response either as a direct message or broadcast depending on the message type.
130
+
131
+ Returns:
132
+ bool: True if the message was handled and a response was sent; False otherwise.
133
+ """
125
134
  if (
126
135
  "decoded" in packet
127
136
  and "portnum" in packet["decoded"]
128
- and packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP"
137
+ and packet["decoded"]["portnum"] == TEXT_MESSAGE_APP
129
138
  and "text" in packet["decoded"]
130
139
  ):
131
140
  message = packet["decoded"]["text"].strip()
mmrelay/setup_utils.py CHANGED
@@ -14,6 +14,7 @@ import subprocess
14
14
  import sys
15
15
  from pathlib import Path
16
16
 
17
+ from mmrelay.constants.database import PROGRESS_COMPLETE, PROGRESS_TOTAL_STEPS
17
18
  from mmrelay.tools import get_service_template_path
18
19
 
19
20
 
@@ -54,7 +55,11 @@ def print_service_commands():
54
55
 
55
56
 
56
57
  def wait_for_service_start():
57
- """Wait for the service to start with a Rich progress indicator."""
58
+ """
59
+ Displays a progress spinner while waiting up to 10 seconds for the mmrelay service to become active.
60
+
61
+ The function checks the service status after 5 seconds and completes early if the service is detected as active.
62
+ """
58
63
  import time
59
64
 
60
65
  from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
@@ -67,7 +72,7 @@ def wait_for_service_start():
67
72
  transient=True,
68
73
  ) as progress:
69
74
  # Add a task that will run for approximately 10 seconds
70
- task = progress.add_task("Starting", total=100)
75
+ task = progress.add_task("Starting", total=PROGRESS_TOTAL_STEPS)
71
76
 
72
77
  # Update progress over 10 seconds
73
78
  for i in range(10):
@@ -76,7 +81,7 @@ def wait_for_service_start():
76
81
 
77
82
  # Check if service is active after 5 seconds to potentially finish early
78
83
  if i >= 5 and is_service_active():
79
- progress.update(task, completed=100)
84
+ progress.update(task, completed=PROGRESS_COMPLETE)
80
85
  break
81
86
 
82
87
 
@@ -372,10 +377,11 @@ def check_loginctl_available():
372
377
 
373
378
 
374
379
  def check_lingering_enabled():
375
- """Check if user lingering is enabled.
380
+ """
381
+ Determine whether user lingering is enabled for the current user.
376
382
 
377
383
  Returns:
378
- bool: True if lingering is enabled, False otherwise.
384
+ bool: True if user lingering is enabled, False otherwise.
379
385
  """
380
386
  try:
381
387
  username = os.environ.get("USER", os.environ.get("USERNAME"))
@@ -386,7 +392,8 @@ def check_lingering_enabled():
386
392
  text=True,
387
393
  )
388
394
  return result.returncode == 0 and "Linger=yes" in result.stdout
389
- except Exception:
395
+ except Exception as e:
396
+ print(f"Error checking lingering status: {e}")
390
397
  return False
391
398
 
392
399
 
@@ -417,7 +424,14 @@ def enable_lingering():
417
424
 
418
425
 
419
426
  def install_service():
420
- """Install or update the MMRelay user service."""
427
+ """
428
+ Install or update the MMRelay systemd user service, guiding the user through creation, updating, enabling, and starting the service as needed.
429
+
430
+ Prompts the user for confirmation before updating an existing service file, enabling user lingering, enabling the service to start at boot, and starting or restarting the service. Handles user interruptions gracefully and prints a summary of the service status and management commands upon completion.
431
+
432
+ Returns:
433
+ bool: True if the installation or update process completes successfully, False otherwise.
434
+ """
421
435
  # Check if service already exists
422
436
  existing_service = read_service_file()
423
437
  service_path = get_user_service_path()
@@ -431,11 +445,14 @@ def install_service():
431
445
 
432
446
  if update_needed:
433
447
  print(f"The service file needs to be updated: {reason}")
434
- if (
435
- not input("Do you want to update the service file? (y/n): ")
436
- .lower()
437
- .startswith("y")
438
- ):
448
+ try:
449
+ user_input = input("Do you want to update the service file? (y/n): ")
450
+ if not user_input.lower().startswith("y"):
451
+ print("Service update cancelled.")
452
+ print_service_commands()
453
+ return True
454
+ except (EOFError, KeyboardInterrupt):
455
+ print("\nInput cancelled. Proceeding with default behavior.")
439
456
  print("Service update cancelled.")
440
457
  print_service_commands()
441
458
  return True
@@ -450,9 +467,11 @@ def install_service():
450
467
  if not create_service_file():
451
468
  return False
452
469
 
453
- # Reload daemon
470
+ # Reload daemon (continue even if this fails)
454
471
  if not reload_daemon():
455
- return False
472
+ print(
473
+ "Warning: Failed to reload systemd daemon. You may need to run 'systemctl --user daemon-reload' manually."
474
+ )
456
475
 
457
476
  if existing_service:
458
477
  print("Service file updated successfully")
@@ -473,13 +492,16 @@ def install_service():
473
492
  print(
474
493
  "Lingering allows user services to run even when you're not logged in."
475
494
  )
476
- if (
477
- input(
495
+ try:
496
+ user_input = input(
478
497
  "Do you want to enable lingering for your user? (requires sudo) (y/n): "
479
498
  )
480
- .lower()
481
- .startswith("y")
482
- ):
499
+ should_enable_lingering = user_input.lower().startswith("y")
500
+ except (EOFError, KeyboardInterrupt):
501
+ print("\nInput cancelled. Skipping lingering setup.")
502
+ should_enable_lingering = False
503
+
504
+ if should_enable_lingering:
483
505
  enable_lingering()
484
506
 
485
507
  # Check if the service is already enabled
@@ -488,11 +510,16 @@ def install_service():
488
510
  print("The service is already enabled to start at boot.")
489
511
  else:
490
512
  print("The service is not currently enabled to start at boot.")
491
- if (
492
- input("Do you want to enable the service to start at boot? (y/n): ")
493
- .lower()
494
- .startswith("y")
495
- ):
513
+ try:
514
+ user_input = input(
515
+ "Do you want to enable the service to start at boot? (y/n): "
516
+ )
517
+ enable_service = user_input.lower().startswith("y")
518
+ except (EOFError, KeyboardInterrupt):
519
+ print("\nInput cancelled. Skipping service enable.")
520
+ enable_service = False
521
+
522
+ if enable_service:
496
523
  try:
497
524
  subprocess.run(
498
525
  ["/usr/bin/systemctl", "--user", "enable", "mmrelay.service"],
@@ -509,7 +536,14 @@ def install_service():
509
536
  service_active = is_service_active()
510
537
  if service_active:
511
538
  print("The service is already running.")
512
- if input("Do you want to restart the service? (y/n): ").lower().startswith("y"):
539
+ try:
540
+ user_input = input("Do you want to restart the service? (y/n): ")
541
+ restart_service = user_input.lower().startswith("y")
542
+ except (EOFError, KeyboardInterrupt):
543
+ print("\nInput cancelled. Skipping service restart.")
544
+ restart_service = False
545
+
546
+ if restart_service:
513
547
  try:
514
548
  subprocess.run(
515
549
  ["/usr/bin/systemctl", "--user", "restart", "mmrelay.service"],
@@ -526,11 +560,14 @@ def install_service():
526
560
  print(f"Error: {e}")
527
561
  else:
528
562
  print("The service is not currently running.")
529
- if (
530
- input("Do you want to start the service now? (y/n): ")
531
- .lower()
532
- .startswith("y")
533
- ):
563
+ try:
564
+ user_input = input("Do you want to start the service now? (y/n): ")
565
+ start_now = user_input.lower().startswith("y")
566
+ except (EOFError, KeyboardInterrupt):
567
+ print("\nInput cancelled. Skipping service start.")
568
+ start_now = False
569
+
570
+ if start_now:
534
571
  if start_service():
535
572
  # Wait for the service to start
536
573
  wait_for_service_start()
@@ -3,6 +3,10 @@ matrix:
3
3
  access_token: reaalllllyloooooongsecretttttcodeeeeeeforrrrbot # See: https://t2bot.io/docs/access_tokens/
4
4
  bot_user_id: "@botuser:example.matrix.org"
5
5
 
6
+ # Message prefix customization (Meshtastic → Matrix direction)
7
+ #prefix_enabled: true # Enable prefixes on messages from mesh (e.g., "[Alice/MyMesh]: message")
8
+ #prefix_format: "[{long}/{mesh}]: " # Default format. Variables: {long1-20}, {long}, {short}, {mesh1-20}, {mesh}
9
+
6
10
  matrix_rooms: # Needs at least 1 room & channel, but supports all Meshtastic channels
7
11
  - id: "#someroomalias:example.matrix.org" # Matrix room aliases & IDs supported
8
12
  meshtastic_channel: 0
@@ -15,9 +19,6 @@ meshtastic:
15
19
  serial_port: /dev/ttyUSB0 # Only used when connection is "serial"
16
20
  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
21
  meshnet_name: Your Meshnet Name # This is displayed in full on Matrix, but is truncated when sent to a Meshnet
18
- broadcast_enabled: true # Must be set to true to enable Matrix to Meshtastic messages
19
- detection_sensor: true # Must be set to true to forward messages of Meshtastic's detection sensor module
20
- plugin_response_delay: 3 # Default response delay in seconds for plugins that respond on the mesh;
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
@@ -29,6 +30,15 @@ meshtastic:
29
30
  # # Note: BLE connections use real-time disconnection detection and skip periodic checks
30
31
  # # Legacy: heartbeat_interval at meshtastic level still supported but deprecated
31
32
 
33
+ # Additional configuration options (commented out with defaults)
34
+ #broadcast_enabled: true # Must be set to true to enable Matrix to Meshtastic messages
35
+ #detection_sensor: true # Must be set to true to forward messages of Meshtastic's detection sensor module
36
+ #message_delay: 2.2 # Delay in seconds between messages sent to mesh (minimum: 2.0 due to firmware)
37
+
38
+ # Message prefix customization (Matrix → Meshtastic direction)
39
+ #prefix_enabled: true # Enable username prefixes on messages sent to mesh (e.g., "Alice[M]: message")
40
+ #prefix_format: "{display5}[M]: " # Default format. See EXTRA_CONFIGURATION.md for all variables.
41
+
32
42
  logging:
33
43
  level: info
34
44
  #log_to_file: true # Set to true to enable file logging
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmrelay
3
- Version: 1.1.2
3
+ Version: 1.1.4
4
4
  Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
5
- Home-page: https://github.com/geoffwhittington/meshtastic-matrix-relay
5
+ Home-page: https://github.com/jeremiah-k/meshtastic-matrix-relay
6
6
  Author: Geoff Whittington, Jeremiah K., and contributors
7
7
  Author-email: jeremiahk@gmx.com
8
- Project-URL: Bug Tracker, https://github.com/geoffwhittington/meshtastic-matrix-relay/issues
8
+ Project-URL: Bug Tracker, https://github.com/jeremiah-k/meshtastic-matrix-relay/issues
9
9
  Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Topic :: Communications
@@ -24,7 +24,7 @@ Requires-Dist: haversine==2.9.0
24
24
  Requires-Dist: schedule==1.2.2
25
25
  Requires-Dist: platformdirs==4.3.8
26
26
  Requires-Dist: py-staticmaps>=0.4.0
27
- Requires-Dist: rich==14.0.0
27
+ Requires-Dist: rich==14.1.0
28
28
  Requires-Dist: setuptools==80.9.0
29
29
  Dynamic: author
30
30
  Dynamic: author-email
@@ -58,11 +58,11 @@ A powerful and easy-to-use relay between Meshtastic devices and Matrix chat room
58
58
  - ✨️ _Bidirectional replies and reactions support_ ✨️ **NEW!!**
59
59
  - ✨️ _Native Docker support_ ✨️ **NEW!!**
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
+ _We would love to support [Matrix E2EE rooms](https://github.com/jeremiah-k/meshtastic-matrix-relay/issues/33), but this is currently not implemented._
62
62
 
63
63
  ## Documentation
64
64
 
65
- Visit our [Wiki](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki) for comprehensive guides and information.
65
+ Visit our [Wiki](https://github.com/jeremiah-k/meshtastic-matrix-relay/wiki) for comprehensive guides and information.
66
66
 
67
67
  - [Installation Instructions](docs/INSTRUCTIONS.md) - Setup and configuration guide
68
68
 
@@ -101,15 +101,13 @@ Docker provides isolated environment, easy deployment, automatic restarts, and v
101
101
 
102
102
  For detailed Docker setup instructions, see the [Docker Guide](docs/DOCKER.md).
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.
105
-
106
104
  ---
107
105
 
108
106
  ## Windows Installer
109
107
 
110
108
  ![Windows Installer Screenshot](https://user-images.githubusercontent.com/1770544/235249050-8c79107a-50cc-4803-b989-39e58100342d.png)
111
109
 
112
- The latest installer is available in the [releases section](https://github.com/geoffwhittington/meshtastic-matrix-relay/releases).
110
+ The latest installer is available in the [releases section](https://github.com/jeremiah-k/meshtastic-matrix-relay/releases).
113
111
 
114
112
  ---
115
113
 
@@ -127,7 +125,7 @@ Produce high-level details about your mesh:
127
125
 
128
126
  ![Mesh Details Screenshot](https://user-images.githubusercontent.com/1770544/235245873-1ddc773b-a4cd-4c67-b0a5-b55a29504b73.png)
129
127
 
130
- See the full list of [core plugins](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Core-Plugins).
128
+ See the full list of [core plugins](https://github.com/jeremiah-k/meshtastic-matrix-relay/wiki/Core-Plugins).
131
129
 
132
130
  ### Community & Custom Plugins
133
131
 
@@ -136,9 +134,9 @@ MMRelay's plugin system allows you to extend functionality in two ways:
136
134
  - **Custom Plugins**: Create personal plugins for your own use, stored in `~/.mmrelay/plugins/custom/`
137
135
  - **Community Plugins**: Share your creations with others or use plugins developed by the community
138
136
 
139
- Check the [Community Plugins Development Guide](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Community-Plugin-Development-Guide) in our wiki to get started.
137
+ Check the [Community Plugins Development Guide](https://github.com/jeremiah-k/meshtastic-matrix-relay/wiki/Community-Plugin-Development-Guide) in our wiki to get started.
140
138
 
141
- ✨️ Visit the [Community Plugins List](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Community-Plugin-List)!
139
+ ✨️ Visit the [Community Plugins List](https://github.com/jeremiah-k/meshtastic-matrix-relay/wiki/Community-Plugin-List)!
142
140
 
143
141
  #### Install a Community Plugin
144
142
 
@@ -164,7 +162,7 @@ Plugins make it easy to extend functionality without modifying the core program.
164
162
 
165
163
  ## Getting Started with Matrix
166
164
 
167
- See our Wiki page [Getting Started With Matrix & MM Relay](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Getting-Started-With-Matrix-&-MM-Relay).
165
+ See our Wiki page [Getting Started With Matrix & MM Relay](https://github.com/jeremiah-k/meshtastic-matrix-relay/wiki/Getting-Started-With-Matrix-&-MM-Relay).
168
166
 
169
167
  ---
170
168