Support for 1.3 (2.0)

This commit is contained in:
Geoff Whittington
2022-10-31 17:07:30 -04:00
parent 8e3cd56228
commit 74114f1acc
4 changed files with 188 additions and 102 deletions

View File

@@ -105,7 +105,7 @@ The following plugins can be used in the `pipelines` section of `config.yaml`:
| ----------------- | -------------------------------------------------------------------- |
| `debugger` | Log the packet to the system console |
| `message_filter` | Filters out packets from the bridge that match a specific criteria |
| `distance_filter` | Filters out packets that originate too far from a specified `device` |
| `location_filter` | Filters out packets that originate too far from a specified `device` |
| `webhook` | Send HTTP requests with custom payloads using packet information |
| `mqtt_plugin` | Send packets to a MQTT server |
| `encrypt_filter` | Encrypt a packet for a desired MQTT recipient |
@@ -131,28 +131,37 @@ Useful for troubleshooting.
- **app** Name of meshtastic application to allow or disallow
- **from** The packet `fromId` values to allow or disallow
- **to** The packet `toId` values to allow or disallow
- **message** The packet `message` values to allow or disallow. Supports Regex.
For example:
```
message_filter:
app:
from:
allow:
- !bd5ba0ec
- !f85bc0bc
disallow:
- !c15ba2ec
message:
disallow:
- Good night
```
### distance_filter - Allow or block packets based on distance from origin to radio
### location_filter - Filter packets by location from current node (default) or specific location
- **log_level** `debug` or `info`. Default `info`
- **max_distance_km** Number of kilometers
- **max_distance_km** Filter packets more than a certain distance
- **comparison** `within` or `outside`. Default `within`
- **compare_latitude** Set the latitude to compare against
- **compare_longitude** Set the longitude to compare against
- **latitude** Set the latitude
- **longitude** Set the longitude
For example:
For example
```
distance_filter:
location_filter:
max_distance_km: 1000
```
@@ -225,7 +234,7 @@ decrypt_filter:
key: '/home/user/keys/key.pem'
```
### send_plugin - Send a packet to a radio
### radio_message_plugin - Send a packet to a radio
- **log_level** `debug` or `info`. Default `info`
- **active** Plugin is active. Values: `true` or `false`. Default `true`.
@@ -242,7 +251,7 @@ For example:
Broadcasts all packets to the "remote" radio network that are destined to the node `12354345`.
```
send_plugin:
radio_message_plugin:
device: remote
node_mapping:
12354345: ^all

37
main.py
View File

@@ -1,5 +1,6 @@
import json
import logging
from re import I
import meshtastic
import meshtastic.serial_interface
import meshtastic.tcp_interface
@@ -20,7 +21,15 @@ logger = logging.getLogger(name="meshtastic.bridge")
logger.setLevel(logging.DEBUG)
class CustomTCPInterface(meshtastic.tcp_interface.TCPInterface):
def __init__(self, hostname, device_name):
self.device_name = device_name
self.hostname = hostname
super(CustomTCPInterface, self).__init__(hostname)
def onReceive(packet, interface): # called when a packet arrives
nodeInfo = interface.getMyNodeInfo()
for pipeline, pipeline_plugins in bridge_config["pipelines"].items():
logger.debug(f"Pipeline {pipeline} initiated")
@@ -29,6 +38,9 @@ def onReceive(packet, interface): # called when a packet arrives
pipeline_packet = p.do_action(packet)
for plugin in pipeline_plugins:
if not pipeline_packet:
continue
for plugin_key, plugin_config in plugin.items():
logger.debug(f"Processing plugin: {pipeline}/{plugin_key}")
@@ -57,8 +69,17 @@ def onConnection(
)
def onLost(interface):
logger.debug(f"Connecting to {interface.hostname} ...")
devices[interface.device_name] = CustomTCPInterface(
hostname=interface.hostname, device_name=interface.device_name
)
logger.debug(f"Connected to {interface.hostname}")
pub.subscribe(onReceive, "meshtastic.receive")
pub.subscribe(onConnection, "meshtastic.connection.established")
pub.subscribe(onLost, "meshtastic.connection.lost")
with open("config.yaml") as f:
bridge_config = yaml.load(f, Loader=SafeLoader)
@@ -75,12 +96,15 @@ for device in bridge_config["devices"]:
devPath=device["serial"]
)
elif "tcp" in device:
devices[device["name"]] = meshtastic.tcp_interface.TCPInterface(
hostname=device["tcp"]
logger.debug(f"Connecting to {device['tcp']} ...")
devices[device["name"]] = CustomTCPInterface(
hostname=device["tcp"], device_name=device["name"]
)
logger.debug(f"Connected to {device['tcp']}")
else:
devices[device["name"]] = meshtastic.serial_interface.SerialInterface()
if "mqtt_servers" in bridge_config:
for config in bridge_config["mqtt_servers"]:
required_options = [
"name",
@@ -110,7 +134,7 @@ for config in bridge_config["mqtt_servers"]:
logger.debug(f"Connected to MQTT {config['name']}")
def on_message(mqttc, obj, msg):
packet = msg.payload.decode()
orig_packet = msg.payload.decode()
logger.debug(f"MQTT {config['name']}: on_message")
@@ -120,11 +144,16 @@ for config in bridge_config["mqtt_servers"]:
for pipeline, pipeline_plugins in config["pipelines"].items():
packet = orig_packet
logger.debug(f"MQTT {config['name']} pipeline {pipeline} started")
if not packet:
continue
for plugin in pipeline_plugins:
if not packet:
continue
for plugin_key, plugin_config in plugin.items():
if plugin_key not in plugins:
logger.error(f"No such plugin: {plugin_key}. Skipping")
@@ -166,8 +195,10 @@ for config in bridge_config["mqtt_servers"]:
while True:
time.sleep(1000)
if devices:
for device, instance in devices.items():
instance.close()
if mqtt_servers:
for server, instance in mqtt_servers.items():
instance.disconnect()

View File

@@ -1,10 +1,11 @@
from haversine import haversine
from meshtastic import mesh_pb2
from meshtastic.__init__ import BROADCAST_ADDR
from random import randrange
import base64
import json
import logging
import os
import re
plugins = {}
@@ -74,6 +75,31 @@ class MessageFilter(Plugin):
self.logger.error("Missing packet")
return packet
text = packet["decoded"]["text"] if "text" in packet["decoded"] else None
if text and "message" in self.config:
if "allow" in self.config["message"]:
matches = False
for allow_regex in self.config["message"]["allow"]:
if not matches and re.search(allow_regex, text):
matches = True
if not matches:
self.logger.debug(
f"Dropped because it doesn't match message allow filter"
)
return None
if text and "disallow" in self.config["message"]:
matches = False
for disallow_regex in self.config["message"]["disallow"]:
if not matches and re.search(disallow_regex, text):
matches = True
if matches:
self.logger.debug(f"Dropped because it matches message disallow filter")
return None
filters = {
"app": packet["decoded"]["portnum"],
"from": packet["fromId"],
@@ -83,12 +109,15 @@ class MessageFilter(Plugin):
for filter_key, value in filters.items():
if filter_key in self.config:
filter_val = self.config[filter_key]
if (
"allow" in filter_val
and filter_val["allow"]
and value not in filter_val["allow"]
):
self.logger.debug(f"Dropped because it doesn't match allow filter")
self.logger.debug(
f"Dropped because it doesn't match {filter_key} allow filter"
)
return None
if (
@@ -96,7 +125,9 @@ class MessageFilter(Plugin):
and filter_val["disallow"]
and value in filter_val["disallow"]
):
self.logger.debug(f"Dropped because it matches disallow filter")
self.logger.debug(
f"Dropped because it matches {filter_key} disallow filter"
)
return None
self.logger.debug(f"Accepted")
@@ -106,21 +137,24 @@ class MessageFilter(Plugin):
plugins["message_filter"] = MessageFilter()
class DistanceFilter(Plugin):
class LocationFilter(Plugin):
logger = logging.getLogger(name="meshtastic.bridge.filter.distance")
def do_action(self, packet):
if "device" not in self.config:
return packet
if "position" not in packet["decoded"]:
return packet
message_source_position = None
current_local_position = None
if "device" in self.config and self.config["device"] in self.devices:
nodeInfo = self.devices[self.config["device"]].getMyNodeInfo()
current_local_position = (
nodeInfo["position"]["latitude"],
nodeInfo["position"]["longitude"],
)
if (
"latitude" in packet["decoded"]["position"]
"decoded" in packet
and "position" in packet["decoded"]
and "latitude" in packet["decoded"]["position"]
and "longitude" in packet["decoded"]["position"]
):
message_source_position = (
@@ -128,31 +162,43 @@ class DistanceFilter(Plugin):
packet["decoded"]["position"]["longitude"],
)
nodeInfo = self.devices[self.config["device"]].getMyNodeInfo()
if "compare_latitude" in self.config and "compare_longitude" in self.config:
current_local_position = (
nodeInfo["position"]["latitude"],
nodeInfo["position"]["longitude"],
self.config["compare_latitude"],
self.config["compare_longitude"],
)
if message_source_position and current_local_position:
distance_km = haversine(message_source_position, current_local_position)
comparison = (
self.config["comparison"] if "comparison" in self.config else "within"
)
# message originates from too far a distance
if (
"max_distance_km" in self.config
and self.config["max_distance_km"] > 0
and distance_km > self.config["max_distance_km"]
):
logger.debug(
f"Packet from too far: {distance_km} > {SUPPORTED_BRIDGE_DISTANCE_KM}"
if "max_distance_km" in self.config and self.config["max_distance_km"] > 0:
acceptable_distance = self.config["max_distance_km"]
if comparison == "within" and distance_km > acceptable_distance:
self.logger.debug(
f"Packet from too far: {distance_km} > {acceptable_distance}"
)
return None
elif comparison == "outside" and distance_km < acceptable_distance:
self.logger.debug(
f"Packet too close: {distance_km} < {acceptable_distance}"
)
return None
if "latitude" in self.config:
packet["decoded"]["position"]["latitude"] = self.config["latitude"]
if "longitude" in self.config:
packet["decoded"]["position"]["longitude"] = self.config["longitude"]
return packet
plugins["distance_filter"] = DistanceFilter()
plugins["location_filter"] = LocationFilter()
class WebhookPlugin(Plugin):
@@ -311,7 +357,7 @@ class DecryptFilter(Plugin):
plugins["decrypt_filter"] = DecryptFilter()
class SendPlugin(Plugin):
class RadioMessagePlugin(Plugin):
logger = logging.getLogger(name="meshtastic.bridge.plugin.send")
def do_action(self, packet):
@@ -386,4 +432,4 @@ class SendPlugin(Plugin):
return packet
plugins["send_plugin"] = SendPlugin()
plugins["radio_message_plugin"] = RadioMessagePlugin()