kopia lustrzana https://github.com/mate-dev/meshtastic-matrix-relay
Support for multiple rooms/channels
rodzic
dafe931999
commit
add110d857
32
README.md
32
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
|
||||
```
|
||||
|
|
78
main.py
78
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():
|
||||
|
|
|
@ -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"
|
Ładowanie…
Reference in New Issue