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
- 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
```

78
main.py
Wyświetl plik

@ -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():

Wyświetl plik

@ -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"
level: "info"