From add110d857bda8f5316eaab4e2c6e544c096d492 Mon Sep 17 00:00:00 2001 From: Jeremiah K Date: Thu, 20 Apr 2023 13:27:04 -0500 Subject: [PATCH] Support for multiple rooms/channels --- README.md | 32 +++++++++++-------- main.py | 78 +++++++++++++++++++++++++++++----------------- sample_config.yaml | 12 ++++--- 3 files changed, 76 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 2350d2b..a591849 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,13 @@ A powerful and easy-to-use relay between Meshtastic devices and Matrix chat room ## Features - Bidirectional message relay between Meshtastic devices and Matrix chat rooms, capable of supporting multiple meshnets -- Supports both serial and network connections for Meshtastic devices +- Supports both serial and network connections for Meshtastic devices - Custom keys are embedded in Matrix messages which are used when relaying messages between two or more meshnets. - Truncates long messages to fit within Meshtastic's payload size - SQLite database to store Meshtastic longnames for improved functionality - Customizable logging level for easy debugging - Configurable through a simple YAML file +- **New:** Supports mapping multiple rooms and channels 1:1 ## Custom Keys in Matrix Messages @@ -21,9 +22,9 @@ Example message format with custom keys: ``` { "msgtype": "m.text", -"body": "[Alice/RemoteMesh]: Hello from the remote meshnet!", +"body": "[Alice/VeryCoolMeshnet]: Hello from my very cool meshnet!", "meshtastic_longname": "Alice", -"meshtastic_meshnet": "RemoteMesh" +"meshtastic_meshnet": "VeryCoolMeshnet" } ``` @@ -60,18 +61,22 @@ matrix: homeserver: "https://example.matrix.org" access_token: "reaalllllyloooooongsecretttttcodeeeeeeforrrrbot" bot_user_id: "@botuser:example.matrix.org" - room_id: "!someroomid:example.matrix.org" + +matrix_rooms: # Needs at least 1 room & channel, but supports all Meshtastic channels + - id: "!someroomid:example.matrix.org" + meshtastic_channel: 0 + - id: "!someroomid2:example.matrix.org" + meshtastic_channel: 2 meshtastic: connection_type: serial # Choose either "network" or "serial" serial_port: /dev/ttyUSB0 # Only used when connection is "serial" - host: "meshtastic.local" # Only used when connection is "network" - channel: 0 # Channel ID of the Meshtastic Channel you want to relay - meshnet_name: "Your Meshnet Name" # This is displayed in full on Matrix, but is truncated when sent to a remote Meshnet - display_meshnet_name: true + host: "meshtastic.local" # Only used when connection is "network" + meshnet_name: "VeryCoolMeshnet" # This is displayed in full on Matrix, but is truncated when sent to a Meshnet + broadcast_enabled: true logging: - level: "debug" + level: "info" ``` ## Usage @@ -86,14 +91,15 @@ python main.py Example output: ``` $ python main.py +$ python main.py INFO:meshtastic.matrix.relay:Starting Meshtastic <==> Matrix Relay... INFO:meshtastic.matrix.relay:Connecting to radio at meshtastic.local ... INFO:meshtastic.matrix.relay:Connected to radio at meshtastic.local. INFO:meshtastic.matrix.relay:Listening for inbound radio messages ... INFO:meshtastic.matrix.relay:Listening for inbound matrix messages ... -INFO:meshtastic.matrix.relay:Sending radio message from Alice to radio broadcast -INFO:meshtastic.matrix.relay:Processing inbound radio message from !613501e4 INFO:meshtastic.matrix.relay:Processing matrix message from @bob:matrix.org: Hi Alice! -INFO:meshtastic.matrix.relay:Sending radio message from Alice to radio broadcast -INFO:meshtastic.matrix.relay:Processing inbound radio message from !613501e4 +INFO:meshtastic.matrix.relay:Sending radio message from Bob to radio broadcast +INFO:meshtastic.matrix.relay:Processing inbound radio message from !613501e4 on channel 0 +INFO:meshtastic.matrix.relay:Relaying Meshtastic message from Alice to Matrix: [Alice/VeryCoolMeshnet]: Hey Bob! +INFO:meshtastic.matrix.relay:Sent inbound radio message to matrix room: !someroomid:example.matrix.org ``` diff --git a/main.py b/main.py index f7d3ebb..bfa4228 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,7 @@ import meshtastic.serial_interface from nio import AsyncClient, AsyncClientConfig, MatrixRoom, RoomMessageText from pubsub import pub from yaml.loader import SafeLoader +from typing import List bot_start_time = int(time.time() * 1000) @@ -79,10 +80,10 @@ matrix_client = None matrix_homeserver = relay_config["matrix"]["homeserver"] matrix_access_token = relay_config["matrix"]["access_token"] bot_user_id = relay_config["matrix"]["bot_user_id"] -matrix_room_id = relay_config["matrix"]["room_id"] +matrix_rooms: List[dict] = relay_config["matrix_rooms"] # Send message to the Matrix room -async def matrix_relay(matrix_client, message, longname, meshnet_name): +async def matrix_relay(matrix_client, room_id, message, longname, meshnet_name): try: content = { "msgtype": "m.text", @@ -92,18 +93,19 @@ async def matrix_relay(matrix_client, message, longname, meshnet_name): } await asyncio.wait_for( matrix_client.room_send( - room_id=matrix_room_id, + room_id=room_id, message_type="m.room.message", content=content, ), timeout=0.5, ) - logger.info(f"Sent inbound radio message to matrix room: {matrix_room_id}") + 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 {matrix_room_id}: {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): @@ -112,33 +114,40 @@ def on_meshtastic_message(packet, loop=None): if "text" in packet["decoded"] and packet["decoded"]["text"]: text = packet["decoded"]["text"] - # Check if the channel is in the packet if "channel" in packet: channel = packet["channel"] else: - # Assume any decoded packet without a channel number and includes - # 'portnum': 'TEXT_MESSAGE_APP' as a channel 0 if packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP": channel = 0 else: logger.debug(f"Unknown packet") return - if channel == relay_config["meshtastic"]["channel"]: - logger.info(f"Processing inbound radio message from {sender} on channel {channel}") + # 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 - longname = get_longname(sender) or sender - meshnet_name = relay_config["meshtastic"]["meshnet_name"] + if not channel_mapped: + logger.debug(f"Skipping message from unmapped channel {channel}") + return - formatted_message = f"[{longname}/{meshnet_name}]: {text}" - logger.info(f"Relaying Meshtastic message from {longname} to Matrix: {formatted_message}") + logger.info(f"Processing inbound radio message from {sender} on channel {channel}") - asyncio.run_coroutine_threadsafe( - matrix_relay(matrix_client, formatted_message, longname, meshnet_name), - loop=loop, - ) - else: - logger.debug(f"Skipping message from 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}") + + 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": @@ -152,7 +161,6 @@ def on_meshtastic_message(packet, loop=None): - def truncate_message(text, max_bytes=234): #234 is the maximum that we can run without an error. Trying it for awhile, otherwise lower this to 230 or less. """ Truncate the given text to fit within the specified byte size. @@ -166,11 +174,12 @@ def truncate_message(text, max_bytes=234): #234 is the maximum that we can run +# Callback for new messages in Matrix room # Callback for new messages in Matrix room async def on_room_message(room: MatrixRoom, event: RoomMessageText) -> None: full_display_name = "Unknown user" - if room.room_id == matrix_room_id and event.sender != bot_user_id: + if event.sender != bot_user_id: message_timestamp = event.server_timestamp if message_timestamp > bot_start_time: @@ -204,13 +213,24 @@ async def on_room_message(room: MatrixRoom, event: RoomMessageText) -> None: text = truncate_message(text) - if relay_config["meshtastic"]["broadcast_enabled"]: - logger.info(f"Sending radio message from {full_display_name} to radio broadcast") - meshtastic_interface.sendText( - text=text, channelIndex=relay_config["meshtastic"]["channel"] - ) - else: - logger.debug(f"Broadcast not supported: Message from {full_display_name} dropped.") + # Find the corresponding room configuration + room_config = None + for config in matrix_rooms: + if config["id"] == room.room_id: + room_config = config + break + + 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=text, channelIndex=meshtastic_channel + ) + else: + logger.debug(f"Broadcast not supported: Message from {full_display_name} dropped.") + async def main(): diff --git a/sample_config.yaml b/sample_config.yaml index 78a93ab..ab59d19 100644 --- a/sample_config.yaml +++ b/sample_config.yaml @@ -2,15 +2,19 @@ matrix: homeserver: "https://example.matrix.org" access_token: "reaalllllyloooooongsecretttttcodeeeeeeforrrrbot" bot_user_id: "@botuser:example.matrix.org" - room_id: "!someroomid:example.matrix.org" + +matrix_rooms: # Needs at least 1 room & channel, but supports all Meshtastic channels + - id: "!someroomid:example.matrix.org" + meshtastic_channel: 0 + - id: "!someroomid2:example.matrix.org" + meshtastic_channel: 2 meshtastic: connection_type: serial # Choose either "network" or "serial" serial_port: /dev/ttyUSB0 # Only used when connection is "serial" host: "meshtastic.local" # Only used when connection is "network" - channel: 0 meshnet_name: "Your Meshnet Name" # This is displayed in full on Matrix, but is truncated when sent to a Meshnet - display_meshnet_name: true + broadcast_enabled: true logging: - level: "debug" \ No newline at end of file + level: "info" \ No newline at end of file