mmrelay 1.1.3__py3-none-any.whl → 1.2.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.

@@ -1,8 +1,10 @@
1
1
  import asyncio
2
+ from datetime import datetime
2
3
 
3
4
  import requests
4
5
  from meshtastic.mesh_interface import BROADCAST_NUM
5
6
 
7
+ from mmrelay.constants.formats import TEXT_MESSAGE_APP
6
8
  from mmrelay.plugins.base_plugin import BasePlugin
7
9
 
8
10
 
@@ -17,41 +19,114 @@ class Plugin(BasePlugin):
17
19
  return "Show weather forecast for a radio node using GPS location"
18
20
 
19
21
  def generate_forecast(self, latitude, longitude):
22
+ """
23
+ Generate a concise one-line weather forecast for the given GPS coordinates.
24
+
25
+ Builds and queries the Open-Meteo API for current conditions and hour-aligned forecasts ~+2h and ~+5h, formats temperatures according to the plugin configuration (`self.config["units"]`, default "metric"), and returns a single-line summary including current conditions and the two forecast points.
26
+
27
+ Parameters:
28
+ latitude (float): Latitude in decimal degrees.
29
+ longitude (float): Longitude in decimal degrees.
30
+
31
+ Returns:
32
+ str: A single-line forecast such as
33
+ "Now: ☀️ Clear sky - 12.3°C | +2h: 🌧️ Light rain - 13.1°C 20% | +5h: ⛅️ Partly cloudy - 10.8°C 5%".
34
+ On recoverable failures returns a short error message: "Weather data temporarily unavailable.",
35
+ "Error fetching weather data.", or "Error parsing weather data.".
36
+
37
+ Notes:
38
+ - Temperature units are determined by `self.config.get("units", "metric")` ("metric" -> °C, "imperial" -> °F).
39
+ - The function attempts to anchor forecasts to hourly timestamps when available; if timestamps cannot be matched it falls back to hour-of-day indexing (may be less accurate).
40
+ - Network/HTTP errors and request-related exceptions are handled and result in the "Error fetching weather data." message.
41
+ - Malformed or incomplete API responses result in "Error parsing weather data." Unexpected exceptions are re-raised.
42
+ """
20
43
  units = self.config.get("units", "metric") # Default to metric
21
44
  temperature_unit = "°C" if units == "metric" else "°F"
22
45
 
23
46
  url = (
24
47
  f"https://api.open-meteo.com/v1/forecast?"
25
48
  f"latitude={latitude}&longitude={longitude}&"
26
- f"hourly=temperature_2m,precipitation_probability,weathercode,cloudcover&"
27
- f"forecast_days=1&current_weather=true"
49
+ f"hourly=temperature_2m,precipitation_probability,weathercode,is_day&"
50
+ f"forecast_days=2&timezone=auto&current_weather=true"
28
51
  )
29
52
 
30
53
  try:
31
54
  response = requests.get(url, timeout=10)
55
+ response.raise_for_status()
32
56
  data = response.json()
33
57
 
34
58
  # Extract relevant weather data
35
59
  current_temp = data["current_weather"]["temperature"]
36
60
  current_weather_code = data["current_weather"]["weathercode"]
37
61
  is_day = data["current_weather"]["is_day"]
62
+ current_time_str = data["current_weather"]["time"]
63
+
64
+ # Parse current time to get the hour with defensive handling
65
+ current_hour = 0
66
+ current_time = None
67
+ try:
68
+ current_time = datetime.fromisoformat(
69
+ current_time_str.replace("Z", "+00:00")
70
+ )
71
+ current_hour = current_time.hour
72
+ except ValueError as ex:
73
+ self.logger.warning(
74
+ f"Unexpected current_weather.time '{current_time_str}': {ex}. Defaulting to hour=0."
75
+ )
76
+
77
+ # Calculate indices for +2h and +5h forecasts
78
+ # Try to anchor to hourly timestamps for robustness, fall back to hour-of-day
79
+ base_index = current_hour
80
+ hourly_times = data["hourly"].get("time", [])
81
+ if hourly_times and current_time:
82
+ try:
83
+ # Normalize current time to the hour and find it in hourly timestamps
84
+ base_key = current_time.replace(
85
+ minute=0, second=0, microsecond=0
86
+ ).strftime("%Y-%m-%dT%H:00")
87
+ base_index = hourly_times.index(base_key)
88
+ except (ValueError, AttributeError):
89
+ # Fall back to hour-of-day if hourly timestamps are unavailable/mismatched
90
+ self.logger.warning(
91
+ "Could not find current time in hourly timestamps. "
92
+ "Falling back to hour-of-day indexing, which may be inaccurate."
93
+ )
94
+
95
+ forecast_2h_index = base_index + 2
96
+ forecast_5h_index = base_index + 5
38
97
 
39
- # Get indices for +2h and +5h forecasts
40
- # Assuming hourly data starts from current hour
41
- forecast_2h_index = 2
42
- forecast_5h_index = 5
98
+ # Guard against empty hourly series before clamping
99
+ temps = data["hourly"].get("temperature_2m") or []
100
+ if not temps:
101
+ self.logger.warning("No hourly temperature data returned.")
102
+ return "Weather data temporarily unavailable."
103
+ max_index = len(temps) - 1
104
+ forecast_2h_index = min(forecast_2h_index, max_index)
105
+ forecast_5h_index = min(forecast_5h_index, max_index)
43
106
 
44
107
  forecast_2h_temp = data["hourly"]["temperature_2m"][forecast_2h_index]
45
108
  forecast_2h_precipitation = data["hourly"]["precipitation_probability"][
46
109
  forecast_2h_index
47
110
  ]
48
111
  forecast_2h_weather_code = data["hourly"]["weathercode"][forecast_2h_index]
112
+ # Get hour-specific day/night flag for +2h forecast
113
+ forecast_2h_is_day = (
114
+ data["hourly"]["is_day"][forecast_2h_index]
115
+ if data["hourly"].get("is_day")
116
+ else is_day
117
+ )
49
118
 
50
119
  forecast_5h_temp = data["hourly"]["temperature_2m"][forecast_5h_index]
51
120
  forecast_5h_precipitation = data["hourly"]["precipitation_probability"][
52
121
  forecast_5h_index
53
122
  ]
54
123
  forecast_5h_weather_code = data["hourly"]["weathercode"][forecast_5h_index]
124
+ # Get hour-specific day/night flag for +5h forecast
125
+ forecast_5h_is_day = (
126
+ data["hourly"]["is_day"][forecast_5h_index]
127
+ if data["hourly"].get("is_day")
128
+ else is_day
129
+ )
55
130
 
56
131
  if units == "imperial":
57
132
  # Convert temperatures from Celsius to Fahrenheit
@@ -105,27 +180,58 @@ class Plugin(BasePlugin):
105
180
  f"{current_temp}{temperature_unit} | "
106
181
  )
107
182
  forecast += (
108
- f"+2h: {weather_code_to_text(forecast_2h_weather_code, is_day)} - "
183
+ f"+2h: {weather_code_to_text(forecast_2h_weather_code, forecast_2h_is_day)} - "
109
184
  f"{forecast_2h_temp}{temperature_unit} {forecast_2h_precipitation}% | "
110
185
  )
111
186
  forecast += (
112
- f"+5h: {weather_code_to_text(forecast_5h_weather_code, is_day)} - "
187
+ f"+5h: {weather_code_to_text(forecast_5h_weather_code, forecast_5h_is_day)} - "
113
188
  f"{forecast_5h_temp}{temperature_unit} {forecast_5h_precipitation}%"
114
189
  )
115
190
 
116
191
  return forecast
117
192
 
118
- except requests.exceptions.RequestException as e:
119
- self.logger.error(f"Error fetching weather data: {e}")
120
- return "Error fetching weather data."
193
+ except Exception as e:
194
+ # Handle HTTP/network errors from requests
195
+ # Handle requests-related exceptions using safe attribute checking
196
+ try:
197
+ # Check if this is a requests exception by checking the module
198
+ if hasattr(requests, "RequestException") and isinstance(
199
+ e, requests.RequestException
200
+ ):
201
+ self.logger.error(f"Error fetching weather data: {e}")
202
+ return "Error fetching weather data."
203
+ except (AttributeError, TypeError):
204
+ # Fallback to string-based detection if isinstance fails
205
+ exception_module = getattr(type(e), "__module__", "")
206
+ if "requests" in exception_module:
207
+ self.logger.error(f"Error fetching weather data: {e}")
208
+ return "Error fetching weather data."
209
+
210
+ # Handle data parsing errors
211
+ if isinstance(
212
+ e, (KeyError, IndexError, TypeError, ValueError, AttributeError)
213
+ ):
214
+ self.logger.error(f"Malformed weather data: {e}")
215
+ return "Error parsing weather data."
216
+ else:
217
+ # Re-raise unexpected exceptions
218
+ raise
121
219
 
122
220
  async def handle_meshtastic_message(
123
221
  self, packet, formatted_message, longname, meshnet_name
124
222
  ):
223
+ """
224
+ Processes incoming Meshtastic text messages and responds with a weather forecast if the plugin command is detected.
225
+
226
+ 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.
227
+
228
+ Returns:
229
+ bool: True if the message was handled and a response was sent; False otherwise.
230
+ """
125
231
  if (
126
232
  "decoded" in packet
127
233
  and "portnum" in packet["decoded"]
128
- and packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP"
234
+ and packet["decoded"]["portnum"] == TEXT_MESSAGE_APP
129
235
  and "text" in packet["decoded"]
130
236
  ):
131
237
  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()
@@ -0,0 +1,80 @@
1
+ services:
2
+ mmrelay:
3
+ image: ghcr.io/jeremiah-k/mmrelay:latest
4
+ container_name: meshtastic-matrix-relay
5
+ restart: unless-stopped
6
+ user: "${UID:-1000}:${GID:-1000}"
7
+
8
+ environment:
9
+ - TZ=UTC
10
+ - PYTHONUNBUFFERED=1
11
+ - MPLCONFIGDIR=/tmp/matplotlib
12
+
13
+ # Matrix Authentication: Use 'mmrelay auth login' on host system to create credentials.json
14
+ # The credentials file will be automatically loaded from the volume mount below
15
+
16
+ # Meshtastic Connection Settings - Uncomment and configure as needed
17
+ # TCP Connection (most common)
18
+ # - MMRELAY_MESHTASTIC_CONNECTION_TYPE=tcp
19
+ # - MMRELAY_MESHTASTIC_HOST=192.168.1.100
20
+ # - MMRELAY_MESHTASTIC_PORT=4403 # Default port
21
+
22
+ # Serial Connection (uncomment for serial)
23
+ # - MMRELAY_MESHTASTIC_CONNECTION_TYPE=serial
24
+ # - MMRELAY_MESHTASTIC_SERIAL_PORT=/dev/ttyUSB0
25
+
26
+ # BLE Connection (uncomment for Bluetooth)
27
+ # - MMRELAY_MESHTASTIC_CONNECTION_TYPE=ble
28
+ # - MMRELAY_MESHTASTIC_BLE_ADDRESS=AA:BB:CC:DD:EE:FF
29
+
30
+ # Meshtastic Operational Settings
31
+ # - MMRELAY_MESHTASTIC_BROADCAST_ENABLED=true
32
+ # - MMRELAY_MESHTASTIC_MESHNET_NAME=My Mesh Network
33
+ # - MMRELAY_MESHTASTIC_MESSAGE_DELAY=2.2 # Minimum 2.0 seconds
34
+
35
+ # System Configuration
36
+ # - MMRELAY_LOGGING_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL
37
+ # - MMRELAY_LOG_FILE=/app/data/logs/mmrelay.log # Enables file logging
38
+ # - MMRELAY_DATABASE_PATH=/app/data/meshtastic.sqlite
39
+
40
+ # Note: Environment variables for Meshtastic, logging, and database settings take precedence over config.yaml
41
+
42
+ volumes:
43
+ # Map entire ~/.mmrelay directory to /app/data to maintain proper structure
44
+ # This includes config.yaml, plugins/, data/, logs/, credentials.json, and any other subdirectories
45
+ # The --data-dir parameter in CMD points to /app/data
46
+ # Create config.yaml first - see docs/DOCKER.md for setup instructions
47
+ - ${MMRELAY_HOME}/.mmrelay:/app/data
48
+
49
+ # For TCP connections (most common) - Meshtastic typically uses port 4403
50
+ ports:
51
+ - 4403:4403
52
+
53
+ # For serial connections, uncomment the device you need:
54
+ # devices:
55
+ # - /dev/ttyUSB0:/dev/ttyUSB0
56
+ # - /dev/ttyACM0:/dev/ttyACM0
57
+
58
+ # For BLE connections, uncomment these:
59
+ # privileged: true
60
+ # network_mode: host
61
+ # Additional volumes for BLE (add to existing volumes section above):
62
+ # - /var/run/dbus:/var/run/dbus:ro
63
+ # - /sys/bus/usb:/sys/bus/usb:ro
64
+ # - /sys/class/bluetooth:/sys/class/bluetooth:ro
65
+ # - /sys/devices:/sys/devices:ro
66
+
67
+ # Optional: Watchtower for automatic updates
68
+ # Uncomment this service to enable daily checks for new images
69
+ # watchtower:
70
+ # image: containrrr/watchtower:latest
71
+ # container_name: watchtower-mmrelay
72
+ # restart: unless-stopped
73
+ # volumes:
74
+ # - /var/run/docker.sock:/var/run/docker.sock
75
+ # environment:
76
+ # - WATCHTOWER_CLEANUP=true
77
+ # - WATCHTOWER_INCLUDE_STOPPED=true
78
+ # - WATCHTOWER_SCHEDULE=0 0 2 * * * # Daily at 2 AM
79
+ # - WATCHTOWER_TIMEOUT=30s
80
+ # command: meshtastic-matrix-relay
@@ -10,15 +10,41 @@ services:
10
10
  - PYTHONUNBUFFERED=1
11
11
  - MPLCONFIGDIR=/tmp/matplotlib
12
12
 
13
+ # Matrix Authentication: Use 'mmrelay auth login' on host system to create credentials.json
14
+ # The credentials file will be automatically loaded from the volume mount below
15
+
16
+ # Meshtastic Connection Settings - Uncomment and configure as needed
17
+ # TCP Connection (most common)
18
+ # - MMRELAY_MESHTASTIC_CONNECTION_TYPE=tcp
19
+ # - MMRELAY_MESHTASTIC_HOST=192.168.1.100
20
+ # - MMRELAY_MESHTASTIC_PORT=4403 # Default port
21
+
22
+ # Serial Connection (uncomment for serial)
23
+ # - MMRELAY_MESHTASTIC_CONNECTION_TYPE=serial
24
+ # - MMRELAY_MESHTASTIC_SERIAL_PORT=/dev/ttyUSB0
25
+
26
+ # BLE Connection (uncomment for Bluetooth)
27
+ # - MMRELAY_MESHTASTIC_CONNECTION_TYPE=ble
28
+ # - MMRELAY_MESHTASTIC_BLE_ADDRESS=AA:BB:CC:DD:EE:FF
29
+
30
+ # Meshtastic Operational Settings
31
+ # - MMRELAY_MESHTASTIC_BROADCAST_ENABLED=true
32
+ # - MMRELAY_MESHTASTIC_MESHNET_NAME=My Mesh Network
33
+ # - MMRELAY_MESHTASTIC_MESSAGE_DELAY=2.2 # Minimum 2.0 seconds
34
+
35
+ # System Configuration
36
+ # - MMRELAY_LOGGING_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL
37
+ # - MMRELAY_LOG_FILE=/app/data/logs/mmrelay.log # Enables file logging
38
+ # - MMRELAY_DATABASE_PATH=/app/data/meshtastic.sqlite
39
+
40
+ # Note: Environment variables for Meshtastic, logging, and database settings take precedence over config.yaml
41
+
13
42
  volumes:
14
- # Configuration - uses standard ~/.mmrelay/config.yaml location
15
- # Create this first with: make config
16
- - ${MMRELAY_HOME}/.mmrelay/config.yaml:/app/config.yaml:ro
17
-
18
- # Data and logs - same locations as standalone installation
19
- # These directories will be created automatically
20
- - ${MMRELAY_HOME}/.mmrelay/data:/app/data
21
- - ${MMRELAY_HOME}/.mmrelay/logs:/app/logs
43
+ # Map entire ~/.mmrelay directory to /app/data to maintain proper structure
44
+ # This includes config.yaml, plugins/, data/, logs/, credentials.json, and any other subdirectories
45
+ # The --data-dir parameter in CMD points to /app/data
46
+ # Create config.yaml first with: make config
47
+ - ${MMRELAY_HOME}/.mmrelay:/app/data
22
48
 
23
49
  # For TCP connections (most common) - Meshtastic typically uses port 4403
24
50
  ports:
@@ -3,6 +3,27 @@ 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
+ # Alternative: Automatic credentials creation (Docker-friendly)
7
+ # If you provide password instead of access_token, MMRelay will automatically
8
+ # create credentials.json on startup. Useful for Docker deployments.
9
+ #password: your_matrix_password_here # Uncomment and set your Matrix password
10
+
11
+ # End-to-End Encryption (E2EE) configuration
12
+ # NOTE: E2EE requires credentials.json instead of access_token for new sessions
13
+ #
14
+ # SETUP INSTRUCTIONS:
15
+ # 1. Install E2EE dependencies: pipx install 'mmrelay[e2e]'
16
+ # 2. Enable E2EE in config: uncomment and set enabled: true below
17
+ # 3. Create credentials: mmrelay auth login
18
+ # 4. The auth login command will create credentials.json with your Matrix login
19
+ # 5. Restart mmrelay - it will use credentials.json and enable E2EE automatically
20
+ #
21
+ #e2ee:
22
+ # # Optional: When credentials.json is present, MMRelay auto-enables E2EE.
23
+ # # Configure this section only if you want to override defaults (e.g., store_path).
24
+ # enabled: false # Explicit toggle if you need to force-enable/disable (usually not needed)
25
+ # store_path: ~/.mmrelay/store # Optional path for encryption keys storage
26
+
6
27
  # Message prefix customization (Meshtastic → Matrix direction)
7
28
  #prefix_enabled: true # Enable prefixes on messages from mesh (e.g., "[Alice/MyMesh]: message")
8
29
  #prefix_format: "[{long}/{mesh}]: " # Default format. Variables: {long1-20}, {long}, {short}, {mesh1-20}, {mesh}
@@ -31,7 +52,7 @@ meshtastic:
31
52
  # # Legacy: heartbeat_interval at meshtastic level still supported but deprecated
32
53
 
33
54
  # Additional configuration options (commented out with defaults)
34
- #broadcast_enabled: true # Must be set to true to enable Matrix to Meshtastic messages
55
+ broadcast_enabled: true # Must be set to true to enable Matrix to Meshtastic messages
35
56
  #detection_sensor: true # Must be set to true to forward messages of Meshtastic's detection sensor module
36
57
  #message_delay: 2.2 # Delay in seconds between messages sent to mesh (minimum: 2.0 due to firmware)
37
58
 
@@ -48,10 +69,14 @@ logging:
48
69
  #color_enabled: true # Set to false to disable colored console output
49
70
 
50
71
  # Component-specific debug logging (useful for troubleshooting)
72
+ # When disabled (false or omitted), external library logs are completely suppressed
73
+ # When enabled, you can use: true (DEBUG level) or specify level: "debug", "info", "warning", "error"
51
74
  #debug:
52
- # matrix_nio: false # Enable matrix-nio debug logging for Matrix client issues
53
- # bleak: false # Enable BLE (bleak) debug logging for Bluetooth connection issues
54
- # meshtastic: false # Enable meshtastic library debug logging for device communication issues
75
+ # matrix_nio: false # Disable matrix-nio logging (default: completely suppressed)
76
+ # #matrix_nio: true # Enable matrix-nio debug logging
77
+ # #matrix_nio: "warning" # Enable matrix-nio warning+ logging
78
+ # bleak: false # Disable BLE (bleak) logging (default: completely suppressed)
79
+ # meshtastic: false # Disable meshtastic library logging (default: completely suppressed)
55
80
 
56
81
  #database:
57
82
  # path: ~/.mmrelay/data/meshtastic.sqlite # Default location