From 42a4e4a76ab166027ecca14599aae5a97d313da1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 11 Jun 2023 14:48:29 -0500 Subject: [PATCH 1/5] Add support for MQTT-only operation --- main.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/main.py b/main.py index ec35b95..53dd626 100644 --- a/main.py +++ b/main.py @@ -86,22 +86,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"]: From 5a4ca73e8cce5b899c9d5e557bb6762eae75bc02 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 11 Jun 2023 18:31:56 -0500 Subject: [PATCH 2/5] Adds plugin to push location messages to an Owntracks mqtt instance --- main.py | 37 ++++++++++++------------ plugins.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 18 deletions(-) diff --git a/main.py b/main.py index 53dd626..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( diff --git a/plugins.py b/plugins.py index 290d2e9..80977d8 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") From 7d20077166020b85f0cb5c5d7ffea321989a1d9d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 11 Jun 2023 18:42:31 -0500 Subject: [PATCH 3/5] Remove wait for mqtt, fixes deadlock --- plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins.py b/plugins.py index 80977d8..5016333 100644 --- a/plugins.py +++ b/plugins.py @@ -391,7 +391,7 @@ class OwntracksPlugin(Plugin): self.logger.debug("Sending owntracks message") info = mqtt_server.publish("owntracks/user/" + tid_table[from_str][0], json.dumps(message)) - info.wait_for_publish() + #info.wait_for_publish() self.logger.debug("Message sent") From 6d186ea339f0d60d287af07b0147be2c8b6bfa35 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 11 Jun 2023 18:48:23 -0500 Subject: [PATCH 4/5] Add documentation to the use-cases --- use-cases/owntracks/config.mqtt.yaml | 12 ++++++++++++ use-cases/owntracks/config.net.yaml | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 use-cases/owntracks/config.mqtt.yaml create mode 100644 use-cases/owntracks/config.net.yaml 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"] From 0eea64f2e42ef0d8363b060676a93efde2e72dd6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 11 Jun 2023 18:55:03 -0500 Subject: [PATCH 5/5] Add more Owntracks_plugin documentation --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) 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`