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
|
## 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
78
main.py
|
@ -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():
|
||||||
|
|
|
@ -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"
|
Ładowanie…
Reference in New Issue