Support for multiple rooms/channels

feature/plugins-parameters
Jeremiah K 2023-04-20 13:27:04 -05:00
rodzic dafe931999
commit add110d857
3 zmienionych plików z 76 dodań i 46 usunięć

Wyświetl plik

@ -5,12 +5,13 @@ A powerful and easy-to-use relay between Meshtastic devices and Matrix chat room
## Features ## Features
- Bidirectional message relay between Meshtastic devices and Matrix chat rooms, capable of supporting multiple meshnets - 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. - 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 - Truncates long messages to fit within Meshtastic's payload size
- SQLite database to store Meshtastic longnames for improved functionality - SQLite database to store Meshtastic longnames for improved functionality
- Customizable logging level for easy debugging - Customizable logging level for easy debugging
- Configurable through a simple YAML file - Configurable through a simple YAML file
- **New:** Supports mapping multiple rooms and channels 1:1
## Custom Keys in Matrix Messages ## Custom Keys in Matrix Messages
@ -21,9 +22,9 @@ Example message format with custom keys:
``` ```
{ {
"msgtype": "m.text", "msgtype": "m.text",
"body": "[Alice/RemoteMesh]: Hello from the remote meshnet!", "body": "[Alice/VeryCoolMeshnet]: Hello from my very cool meshnet!",
"meshtastic_longname": "Alice", "meshtastic_longname": "Alice",
"meshtastic_meshnet": "RemoteMesh" "meshtastic_meshnet": "VeryCoolMeshnet"
} }
``` ```
@ -60,18 +61,22 @@ matrix:
homeserver: "https://example.matrix.org" homeserver: "https://example.matrix.org"
access_token: "reaalllllyloooooongsecretttttcodeeeeeeforrrrbot" access_token: "reaalllllyloooooongsecretttttcodeeeeeeforrrrbot"
bot_user_id: "@botuser:example.matrix.org" 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: meshtastic:
connection_type: serial # Choose either "network" or "serial" connection_type: serial # Choose either "network" or "serial"
serial_port: /dev/ttyUSB0 # Only used when connection is "serial" serial_port: /dev/ttyUSB0 # Only used when connection is "serial"
host: "meshtastic.local" # Only used when connection is "network" host: "meshtastic.local" # Only used when connection is "network"
channel: 0 # Channel ID of the Meshtastic Channel you want to relay meshnet_name: "VeryCoolMeshnet" # This is displayed in full on Matrix, but is truncated when sent to a Meshnet
meshnet_name: "Your Meshnet Name" # This is displayed in full on Matrix, but is truncated when sent to a remote Meshnet broadcast_enabled: true
display_meshnet_name: true
logging: logging:
level: "debug" level: "info"
``` ```
## Usage ## Usage
@ -86,14 +91,15 @@ python main.py
Example output: Example output:
``` ```
$ python main.py $ python main.py
$ python main.py
INFO:meshtastic.matrix.relay:Starting Meshtastic <==> Matrix Relay... INFO:meshtastic.matrix.relay:Starting Meshtastic <==> Matrix Relay...
INFO:meshtastic.matrix.relay:Connecting to radio at meshtastic.local ... INFO:meshtastic.matrix.relay:Connecting to radio at meshtastic.local ...
INFO:meshtastic.matrix.relay:Connected 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 radio messages ...
INFO:meshtastic.matrix.relay:Listening for inbound matrix 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: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:Sending radio message from Bob to radio broadcast
INFO:meshtastic.matrix.relay:Processing inbound radio message from !613501e4 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
``` ```

78
main.py
Wyświetl plik

@ -9,6 +9,7 @@ import meshtastic.serial_interface
from nio import AsyncClient, AsyncClientConfig, MatrixRoom, RoomMessageText from nio import AsyncClient, AsyncClientConfig, MatrixRoom, RoomMessageText
from pubsub import pub from pubsub import pub
from yaml.loader import SafeLoader from yaml.loader import SafeLoader
from typing import List
bot_start_time = int(time.time() * 1000) bot_start_time = int(time.time() * 1000)
@ -79,10 +80,10 @@ matrix_client = None
matrix_homeserver = relay_config["matrix"]["homeserver"] matrix_homeserver = relay_config["matrix"]["homeserver"]
matrix_access_token = relay_config["matrix"]["access_token"] matrix_access_token = relay_config["matrix"]["access_token"]
bot_user_id = relay_config["matrix"]["bot_user_id"] 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 # 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: try:
content = { content = {
"msgtype": "m.text", "msgtype": "m.text",
@ -92,18 +93,19 @@ async def matrix_relay(matrix_client, message, longname, meshnet_name):
} }
await asyncio.wait_for( await asyncio.wait_for(
matrix_client.room_send( matrix_client.room_send(
room_id=matrix_room_id, room_id=room_id,
message_type="m.room.message", message_type="m.room.message",
content=content, content=content,
), ),
timeout=0.5, 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: except asyncio.TimeoutError:
logger.error(f"Timed out while waiting for Matrix response") logger.error(f"Timed out while waiting for Matrix response")
except Exception as e: 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 # Callback for new messages from Meshtastic
def on_meshtastic_message(packet, loop=None): 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"]: if "text" in packet["decoded"] and packet["decoded"]["text"]:
text = packet["decoded"]["text"] text = packet["decoded"]["text"]
# Check if the channel is in the packet
if "channel" in packet: if "channel" in packet:
channel = packet["channel"] channel = packet["channel"]
else: 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": if packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP":
channel = 0 channel = 0
else: else:
logger.debug(f"Unknown packet") logger.debug(f"Unknown packet")
return return
if channel == relay_config["meshtastic"]["channel"]: # Check if the channel is mapped to a Matrix room in the configuration
logger.info(f"Processing inbound radio message from {sender} on channel {channel}") channel_mapped = False
for room in matrix_rooms:
if room["meshtastic_channel"] == channel:
channel_mapped = True
break
longname = get_longname(sender) or sender if not channel_mapped:
meshnet_name = relay_config["meshtastic"]["meshnet_name"] logger.debug(f"Skipping message from unmapped channel {channel}")
return
formatted_message = f"[{longname}/{meshnet_name}]: {text}" logger.info(f"Processing inbound radio message from {sender} on channel {channel}")
logger.info(f"Relaying Meshtastic message from {longname} to Matrix: {formatted_message}")
asyncio.run_coroutine_threadsafe( longname = get_longname(sender) or sender
matrix_relay(matrix_client, formatted_message, longname, meshnet_name), meshnet_name = relay_config["meshtastic"]["meshnet_name"]
loop=loop,
) formatted_message = f"[{longname}/{meshnet_name}]: {text}"
else: logger.info(f"Relaying Meshtastic message from {longname} to Matrix: {formatted_message}")
logger.debug(f"Skipping message from channel {channel}")
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: else:
portnum = packet["decoded"]["portnum"] portnum = packet["decoded"]["portnum"]
if portnum == "TELEMETRY_APP": 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. 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. 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 # Callback for new messages in Matrix room
async def on_room_message(room: MatrixRoom, event: RoomMessageText) -> None: async def on_room_message(room: MatrixRoom, event: RoomMessageText) -> None:
full_display_name = "Unknown user" 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 message_timestamp = event.server_timestamp
if message_timestamp > bot_start_time: if message_timestamp > bot_start_time:
@ -204,13 +213,24 @@ async def on_room_message(room: MatrixRoom, event: RoomMessageText) -> None:
text = truncate_message(text) text = truncate_message(text)
if relay_config["meshtastic"]["broadcast_enabled"]: # Find the corresponding room configuration
logger.info(f"Sending radio message from {full_display_name} to radio broadcast") room_config = None
meshtastic_interface.sendText( for config in matrix_rooms:
text=text, channelIndex=relay_config["meshtastic"]["channel"] if config["id"] == room.room_id:
) room_config = config
else: break
logger.debug(f"Broadcast not supported: Message from {full_display_name} dropped.")
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(): async def main():

Wyświetl plik

@ -2,15 +2,19 @@ matrix:
homeserver: "https://example.matrix.org" homeserver: "https://example.matrix.org"
access_token: "reaalllllyloooooongsecretttttcodeeeeeeforrrrbot" access_token: "reaalllllyloooooongsecretttttcodeeeeeeforrrrbot"
bot_user_id: "@botuser:example.matrix.org" 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: meshtastic:
connection_type: serial # Choose either "network" or "serial" connection_type: serial # Choose either "network" or "serial"
serial_port: /dev/ttyUSB0 # Only used when connection is "serial" serial_port: /dev/ttyUSB0 # Only used when connection is "serial"
host: "meshtastic.local" # Only used when connection is "network" 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 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: logging:
level: "debug" level: "info"