From b996ff4dc405c19b4a3f6dbef7857b08c5657e34 Mon Sep 17 00:00:00 2001 From: geoffwhittington Date: Thu, 27 Apr 2023 13:04:54 -0400 Subject: [PATCH] Feature/refactor (#18) --- config.py | 6 + log_utils.py | 20 +++ main.py | 339 ++++----------------------------------- matrix_utils.py | 192 ++++++++++++++++++++++ meshtastic_utils.py | 101 ++++++++++++ plugin_loader.py | 25 +++ plugins/base_plugin.py | 4 - plugins/health_plugin.py | 12 +- plugins/map_plugin.py | 11 +- 9 files changed, 389 insertions(+), 321 deletions(-) create mode 100644 config.py create mode 100644 log_utils.py create mode 100644 matrix_utils.py create mode 100644 meshtastic_utils.py create mode 100644 plugin_loader.py diff --git a/config.py b/config.py new file mode 100644 index 0000000..d5dc443 --- /dev/null +++ b/config.py @@ -0,0 +1,6 @@ +import yaml +from yaml.loader import SafeLoader + +relay_config = {} +with open("config.yaml", "r") as f: + relay_config = yaml.load(f, Loader=SafeLoader) diff --git a/log_utils.py b/log_utils.py new file mode 100644 index 0000000..951aa30 --- /dev/null +++ b/log_utils.py @@ -0,0 +1,20 @@ +import logging +from config import relay_config + + +def get_logger(name): + logger = logging.getLogger(name=name) + log_level = getattr(logging, relay_config["logging"]["level"].upper()) + + logger.setLevel(log_level) + logger.propagate = False # Add this line to prevent double logging + + handler = logging.StreamHandler() + handler.setFormatter( + logging.Formatter( + fmt=f"%(asctime)s %(levelname)s:%(name)s:%(message)s", + datefmt="%Y-%m-%d %H:%M:%S %z", + ) + ) + logger.addHandler(handler) + return logger diff --git a/main.py b/main.py index 49a0926..ebb3c18 100644 --- a/main.py +++ b/main.py @@ -3,332 +3,46 @@ This script connects a Meshtastic mesh network to Matrix chat rooms by relaying It uses Meshtastic-python and Matrix nio client library to interface with the radio and the Matrix server respectively. """ import asyncio -import time -import logging -import re -import yaml -import certifi -import ssl -import os -import importlib -import sys -import meshtastic.tcp_interface -import meshtastic.serial_interface from nio import ( - AsyncClient, - AsyncClientConfig, - MatrixRoom, RoomMessageText, - RoomAliasEvent, RoomMessageNotice, ) from pubsub import pub -from yaml.loader import SafeLoader -from typing import List, Union -from datetime import datetime -from pathlib import Path - -from db_utils import initialize_database, get_longname, update_longnames - -bot_start_time = int( - time.time() * 1000 -) # Timestamp when the bot starts, used to filter out old messages - -# Load configuration -with open("config.yaml", "r") as f: - relay_config = yaml.load(f, Loader=SafeLoader) - -# Configure logging -logger = logging.getLogger(name="M<>M Relay") -log_level = getattr(logging, relay_config["logging"]["level"].upper()) - - -logger.setLevel(log_level) -logger.propagate = False # Add this line to prevent double logging - -handler = logging.StreamHandler() -handler.setFormatter( - logging.Formatter( - fmt=f"%(asctime)s %(levelname)s:%(name)s:%(message)s", - datefmt="%Y-%m-%d %H:%M:%S %z", - ) +from typing import List +from db_utils import initialize_database, update_longnames +from matrix_utils import ( + connect_matrix, + join_matrix_room, + on_room_message, + logger as matrix_logger, ) -logger.addHandler(handler) +from config import relay_config +from log_utils import get_logger +from meshtastic_utils import ( + connect_meshtastic, + on_meshtastic_message, + logger as meshtastic_logger, +) -def load_plugins(): - plugins = [] - plugin_folder = Path("plugins") - sys.path.insert(0, str(plugin_folder.resolve())) - - for plugin_file in plugin_folder.glob("*.py"): - plugin_name = plugin_file.stem - if plugin_name == "__init__": - continue - plugin_module = importlib.import_module(plugin_name) - if hasattr(plugin_module, "Plugin"): - plugins.append(plugin_module.Plugin()) - - return plugins - - -async def join_matrix_room(matrix_client, room_id_or_alias: str) -> None: - """Join a Matrix room by its ID or alias.""" - try: - if room_id_or_alias.startswith("#"): - response = await matrix_client.resolve_room_alias(room_id_or_alias) - if not response.room_id: - logger.error( - f"Failed to resolve room alias '{room_id_or_alias}': {response.message}" - ) - return - room_id = response.room_id - else: - room_id = room_id_or_alias - - if room_id not in matrix_client.rooms: - response = await matrix_client.join(room_id) - if response and hasattr(response, "room_id"): - logger.info(f"Joined room '{room_id_or_alias}' successfully") - else: - logger.error( - f"Failed to join room '{room_id_or_alias}': {response.message}" - ) - else: - logger.debug(f"Bot is already in room '{room_id_or_alias}'") - except Exception as e: - logger.error(f"Error joining room '{room_id_or_alias}': {e}") - - -# Initialize Meshtastic interface -connection_type = relay_config["meshtastic"]["connection_type"] -if connection_type == "serial": - serial_port = relay_config["meshtastic"]["serial_port"] - logger.info(f"Connecting to radio using serial port {serial_port} ...") - meshtastic_interface = meshtastic.serial_interface.SerialInterface(serial_port) -else: - target_host = relay_config["meshtastic"]["host"] - logger.info(f"Connecting to radio at {target_host} ...") - meshtastic_interface = meshtastic.tcp_interface.TCPInterface(hostname=target_host) - -matrix_client = None - -# Matrix configuration -matrix_homeserver = relay_config["matrix"]["homeserver"] -matrix_access_token = relay_config["matrix"]["access_token"] -bot_user_id = relay_config["matrix"]["bot_user_id"] +logger = get_logger(name="M<>M Relay") +meshtastic_interface = connect_meshtastic() matrix_rooms: List[dict] = relay_config["matrix_rooms"] - - -# Send message to the Matrix room -async def matrix_relay(matrix_client, room_id, message, longname, meshnet_name): - try: - content = { - "msgtype": "m.text", - "body": message, - "meshtastic_longname": longname, - "meshtastic_meshnet": meshnet_name, - } - await asyncio.wait_for( - matrix_client.room_send( - room_id=room_id, - message_type="m.room.message", - content=content, - ), - timeout=0.5, - ) - logger.info(f"Sent inbound radio message to matrix room: {room_id}") - - except asyncio.TimeoutError: - logger.error(f"Timed out while waiting for Matrix response") - except Exception as e: - logger.error(f"Error sending radio message to matrix room {room_id}: {e}") - - -# Callback for new messages from Meshtastic -def on_meshtastic_message(packet, loop=None): - sender = packet["fromId"] - - if "text" in packet["decoded"] and packet["decoded"]["text"]: - text = packet["decoded"]["text"] - - if "channel" in packet: - channel = packet["channel"] - else: - if packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP": - channel = 0 - else: - logger.debug(f"Unknown packet") - return - - # Check if the channel is mapped to a Matrix room in the configuration - channel_mapped = False - for room in matrix_rooms: - if room["meshtastic_channel"] == channel: - channel_mapped = True - break - - if not channel_mapped: - logger.debug(f"Skipping message from unmapped channel {channel}") - return - - logger.info( - f"Processing inbound radio message from {sender} on channel {channel}" - ) - - longname = get_longname(sender) or sender - meshnet_name = relay_config["meshtastic"]["meshnet_name"] - - formatted_message = f"[{longname}/{meshnet_name}]: {text}" - logger.info( - f"Relaying Meshtastic message from {longname} to Matrix: {formatted_message}" - ) - - # Plugin functionality - for plugin in plugins: - plugin.configure(matrix_client, meshtastic_interface) - asyncio.run_coroutine_threadsafe( - plugin.handle_meshtastic_message( - packet, formatted_message, longname, meshnet_name - ), - loop=loop, - ) - - for room in matrix_rooms: - if room["meshtastic_channel"] == channel: - asyncio.run_coroutine_threadsafe( - matrix_relay( - matrix_client, - room["id"], - formatted_message, - longname, - meshnet_name, - ), - loop=loop, - ) - else: - portnum = packet["decoded"]["portnum"] - if portnum == "TELEMETRY_APP": - logger.debug("Ignoring Telemetry packet") - elif portnum == "POSITION_APP": - logger.debug("Ignoring Position packet") - elif portnum == "ADMIN_APP": - logger.debug("Ignoring Admin packet") - else: - logger.debug(f"Ignoring Unknown packet") - - -def truncate_message( - text, max_bytes=227 -): # 227 is the maximum that we can run without an error so far. 228 throws an error. - """ - Truncate the given text to fit within the specified byte size. - - :param text: The text to truncate. - :param max_bytes: The maximum allowed byte size for the truncated text. - :return: The truncated text. - """ - truncated_text = text.encode("utf-8")[:max_bytes].decode("utf-8", "ignore") - return truncated_text - - -# Callback for new messages in Matrix room -async def on_room_message( - room: MatrixRoom, event: Union[RoomMessageText, RoomMessageNotice] -) -> None: - full_display_name = "Unknown user" - - if event.sender != bot_user_id: - message_timestamp = event.server_timestamp - - if message_timestamp > bot_start_time: - text = event.body.strip() - - longname = event.source["content"].get("meshtastic_longname") - meshnet_name = event.source["content"].get("meshtastic_meshnet") - local_meshnet_name = relay_config["meshtastic"]["meshnet_name"] - - if longname and meshnet_name: - full_display_name = f"{longname}/{meshnet_name}" - if meshnet_name != local_meshnet_name: - logger.info(f"Processing message from remote meshnet: {text}") - short_longname = longname[:3] - short_meshnet_name = meshnet_name[:4] - prefix = f"{short_longname}/{short_meshnet_name}: " - text = re.sub( - rf"^\[{full_display_name}\]: ", "", text - ) # Remove the original prefix from the text - text = truncate_message(text) - full_message = f"{prefix}{text}" - else: - # This is a message from a local user, it should be ignored no log is needed - return - - else: - display_name_response = await matrix_client.get_displayname( - event.sender - ) - full_display_name = display_name_response.displayname or event.sender - short_display_name = full_display_name[:5] - prefix = f"{short_display_name}[M]: " - logger.info( - f"Processing matrix message from [{full_display_name}]: {text}" - ) - text = truncate_message(text) - full_message = f"{prefix}{text}" - - room_config = None - for config in matrix_rooms: - if config["id"] == room.room_id: - room_config = config - break - - # Plugin functionality - for plugin in plugins: - plugin.configure(matrix_client, meshtastic_interface) - await plugin.handle_room_message(room, event, full_message) - - if room_config: - meshtastic_channel = room_config["meshtastic_channel"] - - if relay_config["meshtastic"]["broadcast_enabled"]: - logger.info( - f"Sending radio message from {full_display_name} to radio broadcast" - ) - meshtastic_interface.sendText( - text=full_message, channelIndex=meshtastic_channel - ) - - else: - logger.debug( - f"Broadcast not supported: Message from {full_display_name} dropped." - ) +matrix_access_token = relay_config["matrix"]["access_token"] async def main(): - global matrix_client - global plugins - plugins = load_plugins() - # Initialize the SQLite database initialize_database() - # Create SSL context using certifi's certificates - ssl_context = ssl.create_default_context(cafile=certifi.where()) + matrix_client = await connect_matrix() - # Initialize the Matrix client with custom SSL context - config = AsyncClientConfig(encryption_enabled=False) - matrix_client = AsyncClient( - matrix_homeserver, bot_user_id, config=config, ssl=ssl_context - ) - matrix_client.access_token = matrix_access_token - - logger.info("Connecting to Matrix server...") + matrix_logger.info("Connecting ...") try: login_response = await matrix_client.login(matrix_access_token) - logger.info(f"Login response: {login_response}") + matrix_logger.info(f"Login response: {login_response}") except Exception as e: - logger.error(f"Error connecting to Matrix server: {e}") + matrix_logger.error(f"Error connecting to Matrix server: {e}") return # Join the rooms specified in the config.yaml @@ -336,13 +50,14 @@ async def main(): await join_matrix_room(matrix_client, room["id"]) # Register the Meshtastic message callback - logger.info(f"Listening for inbound radio messages ...") + meshtastic_logger.info(f"Listening for inbound radio messages ...") pub.subscribe( on_meshtastic_message, "meshtastic.receive", loop=asyncio.get_event_loop() ) # Register the message callback - logger.info(f"Listening for inbound matrix messages ...") + + matrix_logger.info(f"Listening for inbound matrix messages ...") matrix_client.add_event_callback( on_room_message, (RoomMessageText, RoomMessageNotice) ) @@ -353,11 +68,11 @@ async def main(): # Update longnames update_longnames(meshtastic_interface.nodes) - logger.info("Syncing with Matrix server...") + matrix_logger.info("Syncing with Matrix server...") await matrix_client.sync_forever(timeout=30000) - logger.info("Sync completed.") + matrix_logger.info("Sync completed.") except Exception as e: - logger.error(f"Error syncing with Matrix server: {e}") + matrix_logger.error(f"Error syncing with Matrix server: {e}") await asyncio.sleep(60) # Update longnames every 60 seconds diff --git a/matrix_utils.py b/matrix_utils.py new file mode 100644 index 0000000..c09d396 --- /dev/null +++ b/matrix_utils.py @@ -0,0 +1,192 @@ +import asyncio +import time +import re +import certifi +import ssl +from typing import List, Union +from nio import ( + AsyncClient, + AsyncClientConfig, + MatrixRoom, + RoomMessageText, + RoomMessageNotice, +) +from config import relay_config +from log_utils import get_logger +from plugin_loader import load_plugins +from meshtastic_utils import connect_meshtastic + +matrix_homeserver = relay_config["matrix"]["homeserver"] +bot_user_id = relay_config["matrix"]["bot_user_id"] +matrix_rooms: List[dict] = relay_config["matrix_rooms"] +matrix_access_token = relay_config["matrix"]["access_token"] + +bot_user_id = relay_config["matrix"]["bot_user_id"] +bot_start_time = int( + time.time() * 1000 +) # Timestamp when the bot starts, used to filter out old messages + +logger = get_logger(name="Matrix") + +matrix_client = None + + +async def connect_matrix(): + global matrix_client + if matrix_client: + return matrix_client + + # Create SSL context using certifi's certificates + ssl_context = ssl.create_default_context(cafile=certifi.where()) + + # Initialize the Matrix client with custom SSL context + config = AsyncClientConfig(encryption_enabled=False) + matrix_client = AsyncClient( + matrix_homeserver, bot_user_id, config=config, ssl=ssl_context + ) + matrix_client.access_token = matrix_access_token + return matrix_client + + +async def join_matrix_room(matrix_client, room_id_or_alias: str) -> None: + """Join a Matrix room by its ID or alias.""" + try: + if room_id_or_alias.startswith("#"): + response = await matrix_client.resolve_room_alias(room_id_or_alias) + if not response.room_id: + logger.error( + f"Failed to resolve room alias '{room_id_or_alias}': {response.message}" + ) + return + room_id = response.room_id + else: + room_id = room_id_or_alias + + if room_id not in matrix_client.rooms: + response = await matrix_client.join(room_id) + if response and hasattr(response, "room_id"): + logger.info(f"Joined room '{room_id_or_alias}' successfully") + else: + logger.error( + f"Failed to join room '{room_id_or_alias}': {response.message}" + ) + else: + logger.debug(f"Bot is already in room '{room_id_or_alias}'") + except Exception as e: + logger.error(f"Error joining room '{room_id_or_alias}': {e}") + + +# Send message to the Matrix room +async def matrix_relay(room_id, message, longname, meshnet_name): + matrix_client = await connect_matrix() + try: + content = { + "msgtype": "m.text", + "body": message, + "meshtastic_longname": longname, + "meshtastic_meshnet": meshnet_name, + } + await asyncio.wait_for( + matrix_client.room_send( + room_id=room_id, + message_type="m.room.message", + content=content, + ), + timeout=0.5, + ) + logger.info(f"Sent inbound radio message to matrix room: {room_id}") + + except asyncio.TimeoutError: + logger.error(f"Timed out while waiting for Matrix response") + except Exception as e: + logger.error(f"Error sending radio message to matrix room {room_id}: {e}") + + +def truncate_message( + text, max_bytes=227 +): # 227 is the maximum that we can run without an error so far. 228 throws an error. + """ + Truncate the given text to fit within the specified byte size. + + :param text: The text to truncate. + :param max_bytes: The maximum allowed byte size for the truncated text. + :return: The truncated text. + """ + truncated_text = text.encode("utf-8")[:max_bytes].decode("utf-8", "ignore") + return truncated_text + + +# Callback for new messages in Matrix room +async def on_room_message( + room: MatrixRoom, event: Union[RoomMessageText, RoomMessageNotice] +) -> None: + full_display_name = "Unknown user" + + if event.sender != bot_user_id: + message_timestamp = event.server_timestamp + + if message_timestamp > bot_start_time: + text = event.body.strip() + + longname = event.source["content"].get("meshtastic_longname") + meshnet_name = event.source["content"].get("meshtastic_meshnet") + local_meshnet_name = relay_config["meshtastic"]["meshnet_name"] + + if longname and meshnet_name: + full_display_name = f"{longname}/{meshnet_name}" + if meshnet_name != local_meshnet_name: + logger.info(f"Processing message from remote meshnet: {text}") + short_longname = longname[:3] + short_meshnet_name = meshnet_name[:4] + prefix = f"{short_longname}/{short_meshnet_name}: " + text = re.sub( + rf"^\[{full_display_name}\]: ", "", text + ) # Remove the original prefix from the text + text = truncate_message(text) + full_message = f"{prefix}{text}" + else: + # This is a message from a local user, it should be ignored no log is needed + return + + else: + display_name_response = await matrix_client.get_displayname( + event.sender + ) + full_display_name = display_name_response.displayname or event.sender + short_display_name = full_display_name[:5] + prefix = f"{short_display_name}[M]: " + logger.debug( + f"Processing matrix message from [{full_display_name}]: {text}" + ) + text = truncate_message(text) + full_message = f"{prefix}{text}" + + room_config = None + for config in matrix_rooms: + if config["id"] == room.room_id: + room_config = config + break + + # Plugin functionality + plugins = load_plugins() + meshtastic_interface = connect_meshtastic() + from meshtastic_utils import logger as meshtastic_logger + + for plugin in plugins: + await plugin.handle_room_message(room, event, full_message) + + if room_config: + meshtastic_channel = room_config["meshtastic_channel"] + + if relay_config["meshtastic"]["broadcast_enabled"]: + meshtastic_logger.info( + f"Relaying message from {full_display_name} to radio broadcast" + ) + meshtastic_interface.sendText( + text=full_message, channelIndex=meshtastic_channel + ) + + else: + logger.debug( + f"Broadcast not supported: Message from {full_display_name} dropped." + ) diff --git a/meshtastic_utils.py b/meshtastic_utils.py new file mode 100644 index 0000000..806a299 --- /dev/null +++ b/meshtastic_utils.py @@ -0,0 +1,101 @@ +import asyncio +import meshtastic.tcp_interface +import meshtastic.serial_interface +from typing import List + +from config import relay_config +from log_utils import get_logger +from db_utils import get_longname +from plugin_loader import load_plugins + +matrix_rooms: List[dict] = relay_config["matrix_rooms"] + +logger = get_logger(name="Meshtastic") + + +meshtastic_client = None + + +def connect_meshtastic(): + global meshtastic_client + if meshtastic_client: + return meshtastic_client + # Initialize Meshtastic interface + connection_type = relay_config["meshtastic"]["connection_type"] + if connection_type == "serial": + serial_port = relay_config["meshtastic"]["serial_port"] + logger.info(f"Connecting to serial port {serial_port} ...") + meshtastic_client = meshtastic.serial_interface.SerialInterface(serial_port) + else: + target_host = relay_config["meshtastic"]["host"] + logger.info(f"Connecting to host {target_host} ...") + meshtastic_client = meshtastic.tcp_interface.TCPInterface(hostname=target_host) + return meshtastic_client + + +# Callback for new messages from Meshtastic +def on_meshtastic_message(packet, loop=None): + from matrix_utils import connect_matrix, matrix_relay + + sender = packet["fromId"] + + if "text" in packet["decoded"] and packet["decoded"]["text"]: + text = packet["decoded"]["text"] + + if "channel" in packet: + channel = packet["channel"] + else: + if packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP": + channel = 0 + else: + logger.debug(f"Unknown packet") + return + + # Check if the channel is mapped to a Matrix room in the configuration + channel_mapped = False + for room in matrix_rooms: + if room["meshtastic_channel"] == channel: + channel_mapped = True + break + + if not channel_mapped: + logger.debug(f"Skipping message from unmapped channel {channel}") + return + + logger.info( + f"Processing inbound radio message from {sender} on channel {channel}" + ) + + longname = get_longname(sender) or sender + meshnet_name = relay_config["meshtastic"]["meshnet_name"] + + formatted_message = f"[{longname}/{meshnet_name}]: {text}" + logger.info( + f"Relaying Meshtastic message from {longname} to Matrix: {formatted_message}" + ) + + # Plugin functionality + plugins = load_plugins() + + for plugin in plugins: + asyncio.run_coroutine_threadsafe( + plugin.handle_meshtastic_message( + packet, formatted_message, longname, meshnet_name + ), + loop=loop, + ) + + for room in matrix_rooms: + if room["meshtastic_channel"] == channel: + asyncio.run_coroutine_threadsafe( + matrix_relay( + room["id"], + formatted_message, + longname, + meshnet_name, + ), + loop=loop, + ) + else: + portnum = packet["decoded"]["portnum"] + logger.debug(f"Ignoring {portnum} packet") diff --git a/plugin_loader.py b/plugin_loader.py new file mode 100644 index 0000000..fb6d0cb --- /dev/null +++ b/plugin_loader.py @@ -0,0 +1,25 @@ +import sys +import importlib +from pathlib import Path + +plugins = [] + + +def load_plugins(): + global plugins + if plugins: + return plugins + + plugins = [] + plugin_folder = Path("plugins") + sys.path.insert(0, str(plugin_folder.resolve())) + + for plugin_file in plugin_folder.glob("*.py"): + plugin_name = plugin_file.stem + if plugin_name == "__init__": + continue + plugin_module = importlib.import_module(plugin_name) + if hasattr(plugin_module, "Plugin"): + plugins.append(plugin_module.Plugin()) + + return plugins diff --git a/plugins/base_plugin.py b/plugins/base_plugin.py index a6dfe41..f888e4e 100644 --- a/plugins/base_plugin.py +++ b/plugins/base_plugin.py @@ -2,10 +2,6 @@ from abc import ABC, abstractmethod class BasePlugin(ABC): - def configure(self, matrix_client, meshtastic_client) -> None: - self.matrix_client = matrix_client - self.meshtastic_client = meshtastic_client - @abstractmethod async def handle_meshtastic_message( packet, formatted_message, longname, meshnet_name diff --git a/plugins/health_plugin.py b/plugins/health_plugin.py index 3d532fc..dd322f5 100644 --- a/plugins/health_plugin.py +++ b/plugins/health_plugin.py @@ -2,6 +2,9 @@ import re import statistics from base_plugin import BasePlugin +from matrix_utils import connect_matrix +from meshtastic_utils import connect_meshtastic + class Plugin(BasePlugin): async def handle_meshtastic_message( @@ -10,13 +13,16 @@ class Plugin(BasePlugin): return async def handle_room_message(self, room, event, full_message): + meshtastic_client = connect_meshtastic() + matrix_client = await connect_matrix() + match = re.match(r"^.*: !health$", full_message) if match: battery_levels = [] air_util_tx = [] snr = [] - for node, info in self.meshtastic_client.nodes.items(): + for node, info in meshtastic_client.nodes.items(): if "deviceMetrics" in info: battery_levels.append(info["deviceMetrics"]["batteryLevel"]) air_util_tx.append(info["deviceMetrics"]["airUtilTx"]) @@ -24,7 +30,7 @@ class Plugin(BasePlugin): snr.append(info["snr"]) low_battery = len([n for n in battery_levels if n <= 10]) - radios = len(self.meshtastic_client.nodes) + radios = len(meshtastic_client.nodes) avg_battery = statistics.mean(battery_levels) if battery_levels else 0 mdn_battery = statistics.median(battery_levels) avg_air = statistics.mean(air_util_tx) if air_util_tx else 0 @@ -32,7 +38,7 @@ class Plugin(BasePlugin): avg_snr = statistics.mean(snr) if snr else 0 mdn_snr = statistics.median(snr) - response = await self.matrix_client.room_send( + response = await matrix_client.room_send( room_id=room.room_id, message_type="m.room.message", content={ diff --git a/plugins/map_plugin.py b/plugins/map_plugin.py index 9d641b5..5a6c47d 100644 --- a/plugins/map_plugin.py +++ b/plugins/map_plugin.py @@ -8,6 +8,10 @@ from nio import AsyncClient, UploadResponse from base_plugin import BasePlugin +from matrix_utils import connect_matrix +from meshtastic_utils import connect_meshtastic + + def anonymize_location(lat, lon, radius=1000): # Generate random offsets for latitude and longitude lat_offset = random.uniform(-radius / 111320, radius / 111320) @@ -83,6 +87,9 @@ class Plugin(BasePlugin): return async def handle_room_message(self, room, event, full_message): + matrix_client = await connect_matrix() + meshtastic_client = connect_meshtastic() + pattern = r"^.*:(?: !map(?: zoom=(\d+))?(?: size=(\d+),(\d+))?)?$" match = re.match(pattern, full_message) if match: @@ -106,7 +113,7 @@ class Plugin(BasePlugin): image_size = (1000, 1000) locations = [] - for node, info in self.meshtastic_client.nodes.items(): + for node, info in meshtastic_client.nodes.items(): if "position" in info and "latitude" in info["position"]: locations.append( { @@ -119,4 +126,4 @@ class Plugin(BasePlugin): locations=locations, zoom=zoom, image_size=image_size ) - await send_image(self.matrix_client, room.room_id, pillow_image) + await send_image(matrix_client, room.room_id, pillow_image)