diff --git a/README.md b/README.md index fcbb5af..38cbfc2 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ The following plugins can be used in the `pipelines` section of `config.yaml`: | `decrypt_filter` | Decrypt a packet originating from MQTT | | `radio_message_plugin` | Send a packet to a specified `device` | | `nostr_plugin` | Send a NoStr event to a relay | +| `owntracks_plugin` | Send location data to MQTT server for Owntracks | ### debugger - Output the contents of a packet @@ -261,6 +262,27 @@ Placeholders can be used with the **message** value: - `{MSG}` - Packet text +### owntracks_plugin - Send location data to MQTT server for Owntracks + +- **log_level** `debug` or `info`. Default `info` +- **server_name** The mqtt server to send owntracks messages to +- **tid_table** Table of the numeric from IDs of each node, mapped to an Owntracks name and TID + + +For example: + +``` +owntracks_plugin: + server_name: external + tid_table: + "1234": ["Van", "GV"] + "-5678": ["Home", "HR"] +``` + +Placeholders can be used with the **message** value: + +- `{MSG}` - Packet text + ### radio_message_plugin - Send a packet to a radio - **log_level** `debug` or `info`. Default `info` diff --git a/main.py b/main.py index ec35b95..bd431b7 100644 --- a/main.py +++ b/main.py @@ -31,32 +31,33 @@ class CustomTCPInterface(meshtastic.tcp_interface.TCPInterface): 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") + if "pipelines" in bridge_config: + for pipeline, pipeline_plugins in bridge_config["pipelines"].items(): + logger.debug(f"Pipeline {pipeline} initiated") - p = plugins["packet_filter"] - pipeline_packet = p.do_action(packet) + p = plugins["packet_filter"] + 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}") + for plugin in pipeline_plugins: if not pipeline_packet: - logger.debug("Skipping since the packet is null") continue - if plugin_key not in plugins: - logger.error(f"No such plugin: {plugin_key}. Skipping") - continue + for plugin_key, plugin_config in plugin.items(): + logger.debug(f"Processing plugin: {pipeline}/{plugin_key}") + if not pipeline_packet: + logger.debug("Skipping since the packet is null") + continue - p = plugins[plugin_key] - p.configure(devices, mqtt_servers, plugin_config) + if plugin_key not in plugins: + logger.error(f"No such plugin: {plugin_key}. Skipping") + continue - pipeline_packet = p.do_action(pipeline_packet) + p = plugins[plugin_key] + p.configure(devices, mqtt_servers, plugin_config) - logger.debug(f"Pipeline {pipeline} completed") + pipeline_packet = p.do_action(pipeline_packet) + + logger.debug(f"Pipeline {pipeline} completed") def onConnection( @@ -86,22 +87,23 @@ with open("config.yaml") as f: devices = {} mqtt_servers = {} -for device in bridge_config["devices"]: - if "active" in device and not device["active"]: - continue +if "devices" in bridge_config: + for device in bridge_config["devices"]: + if "active" in device and not device["active"]: + continue - if "serial" in device: - devices[device["name"]] = meshtastic.serial_interface.SerialInterface( - devPath=device["serial"] - ) - elif "tcp" in device: - 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 "serial" in device: + devices[device["name"]] = meshtastic.serial_interface.SerialInterface( + devPath=device["serial"] + ) + elif "tcp" in device: + 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"]: diff --git a/plugins.py b/plugins.py index 290d2e9..5016333 100644 --- a/plugins.py +++ b/plugins.py @@ -318,6 +318,89 @@ class MQTTPlugin(Plugin): plugins["mqtt_plugin"] = MQTTPlugin() +class OwntracksPlugin(Plugin): + logger = logging.getLogger(name="meshtastic.bridge.plugin.Owntracks") + + def do_action(self, packet): + + required_options = ["tid_table", "server_name"] + for option in required_options: + if option not in self.config: + self.logger.warning(f"Missing config: {option}") + return packet + tid_table = self.config["tid_table"] + + if not "from" in packet: + self.logger.warning("Missing from: field") + return packet + + if not str(packet["from"]) in self.config["tid_table"]: + self.logger.warning(f"Sender not in tid_table: {packet}") + return packet + + from_str = str(packet["from"]) + + message = json.loads('{"_type":"location", "bs":0}') + message["tid"] = tid_table[from_str][1] + self.logger.debug(f"processing packet {packet}") + #Packet direct from radio + if ( + "decoded" in packet + and "position" in packet["decoded"] + and "latitude" in packet["decoded"]["position"] + and packet["decoded"]["position"]["latitude"] != 0 + ): + message["lat"] = packet["decoded"]["position"]["latitude"] + message["lon"] = packet["decoded"]["position"]["longitude"] + message["tst"] = packet["decoded"]["position"]["time"] + message["created_at"] = packet["rxTime"] + if "altitude" in packet["decoded"]["position"]: + message["alt"] = packet["decoded"]["position"]["altitude"] + + #packet from mqtt + elif ( + "type" in packet + and packet["type"] == "position" + and "payload" in packet + and "latitude_i" in packet["payload"] + and packet["payload"]["latitude_i"] != 0 + ): + message["lat"] = packet["payload"]["latitude_i"]/10000000 + message["lon"] = packet["payload"]["longitude_i"]/10000000 + message["tst"] = packet["timestamp"] + if ("time" in packet["payload"]): + message["created_at"] = packet["payload"]["time"] + else: + message["created_at"] = packet["timestamp"] + if "altitude" in packet["payload"]: + message["alt"] = packet["payload"]["altitude"] + else: + self.logger.debug("Not a location packet") + return packet + + if self.config["server_name"] not in self.mqtt_servers: + self.logger.warning(f"No server established: {self.config['server_name']}") + return packet + + mqtt_server = self.mqtt_servers[self.config["server_name"]] + + if not mqtt_server.is_connected(): + self.logger.error("Not sent, not connected") + return + + self.logger.debug("Sending owntracks message") + + info = mqtt_server.publish("owntracks/user/" + tid_table[from_str][0], json.dumps(message)) + #info.wait_for_publish() + + self.logger.debug("Message sent") + + return packet + + +plugins["owntracks_plugin"] = OwntracksPlugin() + + class EncryptFilter(Plugin): logger = logging.getLogger(name="meshtastic.bridge.filter.encrypt") diff --git a/use-cases/owntracks/config.mqtt.yaml b/use-cases/owntracks/config.mqtt.yaml new file mode 100644 index 0000000..0eda8f9 --- /dev/null +++ b/use-cases/owntracks/config.mqtt.yaml @@ -0,0 +1,12 @@ +mqtt_servers: + - name: local + server: localhost + port: 1883 + topic: msh/2/json/# + pipelines: + owntrack: + - owntracks_plugin: + server_name: local + tid_table: + "12345": ["Van", "GV"] + "-6789": ["Home", "HR"] diff --git a/use-cases/owntracks/config.net.yaml b/use-cases/owntracks/config.net.yaml new file mode 100644 index 0000000..bb1bfe3 --- /dev/null +++ b/use-cases/owntracks/config.net.yaml @@ -0,0 +1,18 @@ +devices: + - name: radio1 + tcp: 192.168.1.110 + +mqtt_servers: + - name: external + server: localhost + port: 1883 + + + +pipelines: + owntracks: + - owntracks_plugin: + server_name: external + tid_table: + "1234": ["Van", "GV"] + "-5678": ["Home", "HR"]