mirror of
https://github.com/meshtastic/meshtastic.git
synced 2024-11-17 11:04:13 -08:00
1167 lines
34 KiB
Plaintext
1167 lines
34 KiB
Plaintext
---
|
|
id: mqtt
|
|
title: MQTT
|
|
sidebar_label: MQTT
|
|
sidebar_position: 6
|
|
---
|
|
|
|
## Bridging networks
|
|
|
|
Meshtastic networks in different locations beyond the reach of LoRa can be easily bridged together using MQTT. The simplest option is to connect your mesh to the official Meshtastic MQTT broker. This makes your devices appear on the world map, and provides a copy of your mesh traffic, translated into JSON. All you have to do to join the public MQTT server is to Enable MQTT and set Uplink and Downlink on the channels that you want to share over MQTT. The default device configuration using the public MQTT Server is encrypted.
|
|
|
|
You can also share channel settings with a remote network. If you use the default Meshtastic MQTT server, packets are always encrypted. If you use a custom MQTT broker (ie set `mqtt.address`), the `mqtt.encryption_enabled` setting applies, which by default is false. You can also specify your own private MQTT broker and specify authentication for that broker to bridge several mesh networks together, via the internet (or just a local IP network).
|
|
|
|
You can find the settings available for MQTT [here](/docs/settings/moduleconfig/mqtt).
|
|
|
|
:::important
|
|
When MQTT is turned on, you are potentially broadcasting your entire mesh's traffic onto the public internet. This includes messages and position information.
|
|
:::
|
|
|
|
## Software Integrations
|
|
|
|
Using or emitting packets directly in/from smart home control software such as Home Assistant or other consumers that can work with JSON messages.
|
|
|
|
When MQTT is enabled, the Meshtastic device simply uplinks and/or downlinks every raw protobuf MeshPacket that it sees to the MQTT broker, encapsulated in a [ServiceEnvelope protobuf](https://buf.build/meshtastic/protobufs/docs/main:meshtastic#meshtastic.ServiceEnvelope). In addition, some packet types are serialized or deserialized from/to JSON messages for easier use in consumers. All packets are sent to the broker, whether they originate from another device on the mesh, or the gateway node itself.
|
|
|
|
### MQTT [Topics](https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices)
|
|
|
|
If no specific [root topic](/docs/settings/moduleconfig/mqtt#root-topic) is configured, the default root topic will be `msh/`.
|
|
Each device that is connected to MQTT will publish its MQTT state (`online`/`offline`) to:
|
|
|
|
`msh/2/stat/USERID`, where `USERID` is the node ID of the gateway device (the one connected to MQTT).
|
|
|
|
For each channel where uplink and/or downlink is enabled, two other topics might be used:
|
|
|
|
#### Protobufs topic
|
|
A gateway node will uplink and/or downlink raw ([protobuf](https://developers.google.com/protocol-buffers)) MeshPackets to the topic:
|
|
|
|
`msh/2/c/CHANNELNAME/USERID`, where `CHANNELNAME` is the name of the channel.
|
|
|
|
For example: `msh/2/c/LongFast/!abcd1234`
|
|
|
|
The payload is a raw protobuf, whose definitions for Meshtastic can be found [here](https://github.com/meshtastic/protobufs/blob/master/meshtastic). Reference guides for working with protobufs in several popular programming languages can be found [here](https://protobuf.dev/reference/). Looking at the MQTT traffic with a program like `mosquitto_sub` will tell you it's working, but you won't get much useful information out of it. For example:
|
|
|
|
```text
|
|
苓????"!
|
|
!937bed1cTanksTnk"D???05??=???aP`
|
|
ShortFast !937bed1c
|
|
```
|
|
|
|
If [encryption_enabled](/docs/settings/moduleconfig/mqtt#encryption-enabled) is set to true, the payload of the MeshPacket will remain encrypted with the key for the specified channel.
|
|
|
|
#### JSON topic
|
|
If [JSON is enabled](/docs/settings/moduleconfig/mqtt/#json-enabled), packets from the following [port numbers](/docs/development/firmware/portnum) are serialized to JSON: `TEXT_MESSAGE_APP`, `TELEMETRY_APP`, `NODEINFO_APP`, `POSITION_APP` and `WAYPOINT_APP`. These are then forwarded to the topic:
|
|
|
|
`msh/2/json/CHANNELNAME/USERID`.
|
|
|
|
An example of a received `NODEINFO_APP` message:
|
|
|
|
```json
|
|
{
|
|
"id": 452664778,
|
|
"channel": 0,
|
|
"from": 2130636288,
|
|
"payload": {
|
|
"hardware": 10,
|
|
"id": "!7efeee00",
|
|
"longname": "base0",
|
|
"shortname": "BA0"
|
|
},
|
|
"sender": "!7efeee00",
|
|
"timestamp": 1646832724,
|
|
"to": -1,
|
|
"type": "nodeinfo"
|
|
}
|
|
```
|
|
|
|
The meaning of these fields is as follows:
|
|
|
|
- "`id`" is the unique ID for this message.
|
|
- "`channel`" is the channel index this message was received on.
|
|
- "`from`" is the unique node number of the node on the mesh that sent this message.
|
|
- "`id`" inside the payload of a `NODEINFO_APP` message is the user ID of the node that sent it, which is currently just the hexadecimal representation of the node number.
|
|
- "`hardware`" is the [hardware model](https://github.com/meshtastic/protobufs/blob/master/meshtastic/mesh.proto#L215) of the node sending the `NODEINFO_APP` message.
|
|
- "`longname`" is the long name of the device that sent the `NODEINFO_APP` message.
|
|
- "`shortname`" is the short name of the device that sent the `NODEINFO_APP` message.
|
|
- "`sender`" is the user ID of the gateway device, which is in this case the same node that sent the `NODEINFO_APP` message (the hexadecimal value `7efeee00` represented by an integer in decimal is `2130636288`).
|
|
- "`timestamp`" is the Unix Epoch when the message was received, represented as an integer in decimal.
|
|
- "`to`" is the node number of the destination of the message. In this case, "-1" means it was a broadcast message (this is the decimal integer representation of `0xFFFFFFFF`).
|
|
- "`type`" is the type of the message, in this case it was a `NODEINFO_APP` message.
|
|
|
|
The `from` field can thus be used as a stable identifier for a specific node. Note that in firmware prior to 2.2.0, this is a signed value in JSON, while in firmware 2.2.0 and higher, the JSON values are unsigned.
|
|
|
|
If the message received contains valid JSON in the payload, the JSON is deserialized and added as a JSON object rather than a string containing the serialized JSON.
|
|
|
|
**Sent messages** will be checked if the MQTT payload contains a valid JSON-encoded envelope:
|
|
|
|
```json
|
|
{
|
|
"sender": "SENDER",
|
|
"payload": {
|
|
"key":"value"
|
|
...
|
|
}
|
|
}
|
|
```
|
|
|
|
`sender` and `payload` fields are required for a valid envelope. If a valid MQTT message is found, the message is sent over the radio as a message of type `TEXT_MESSAGE_APP` with the serialized `payload` value in the message payload.
|
|
|
|
### Basic Configuration
|
|
|
|
Check out [MQTT Settings](/docs/settings/moduleconfig/mqtt) for full information. For quick start instructions, read on.
|
|
|
|
- Connect your gateway node to wifi, by setting the `network.wifi_ssid`, `network.wifi_psk` and `network.wifi_enabled` preferences.
|
|
- Alternatively use the RAK4631 with Ethernet Module RAK13800, by setting `network.eth_mode` and `network.eth_enabled`.
|
|
- Configure your broker settings: `mqtt.address`, `mqtt.username`, and `mqtt.password`. If all are left blank, the device will connect to the Meshtastic broker.
|
|
- Set `uplink_enabled` and `downlink_enabled` as appropriate for each channel. Most users will just have a single channel (at channel index 0). `meshtastic --ch-index 0 --ch-set uplink_enabled true`
|
|
|
|
`uplink_enabled` will tell the device to publish mesh packets to MQTT.
|
|
`downlink_enabled` will tell the device to subscribe to MQTT, and forward any packets from there onto the mesh.
|
|
|
|
### Gateway nodes
|
|
|
|
Any meshtastic node that has a direct connection to the internet (either via a helper app or installed WiFi/4G/satellite hardware) can function as a "Gateway node".
|
|
|
|
Gateway nodes (via code running in the phone) will contain two tables to whitelist particular traffic to either be delivered toward the internet, or down toward the mesh. Users that are developing custom apps will be able to customize these filters/subscriptions.
|
|
|
|
Since multiple gateway nodes might be connected to a single mesh, it is possible that duplicate messages will be published on any particular topic. Therefore, subscribers to these topics should
|
|
deduplicate if needed by using the packet ID of each message.
|
|
|
|
### Optional web services
|
|
|
|
#### Public MQTT broker service
|
|
|
|
An existing public [MQTT broker](https://mosquitto.org) will be the default for this service, but clients can use any MQTT broker they choose.
|
|
|
|
## Examples
|
|
|
|
### Using mosquitto on a mac
|
|
|
|
1. install mqtt server
|
|
|
|
```sh
|
|
brew install mosquitto
|
|
```
|
|
|
|
2. start the mqtt server
|
|
|
|
```sh
|
|
brew services restart mosquitto
|
|
```
|
|
|
|
3. Do a quick test of server, start a subscriber on a topic:
|
|
|
|
Note: this will wait until you press control-c (publish a message, see below)
|
|
|
|
```sh
|
|
mosquitto_sub -t test/hello
|
|
```
|
|
|
|
4. In another window, publish a message to that topic:
|
|
|
|
```sh
|
|
mosquitto_pub -h localhost -q 0 -t test/hello -m 'yo!'
|
|
```
|
|
|
|
5. For Meshtastic to be able to access that server, two settings need to be changed in the
|
|
`/usr/local/etc/mosquitto/mosquitto.conf` file:
|
|
|
|
```shell
|
|
listener 1883 0.0.0.0
|
|
allow_anonymous true
|
|
```
|
|
|
|
6. Restart the service:
|
|
|
|
```shell
|
|
brew services restart mosquitto
|
|
```
|
|
|
|
7. If you are using the mac firewall, you will need to go into: System Preferences > Security & Privacy > Firewall > Firewall Options and add it.
|
|
|
|
### Sending/receiving messages on mosquitto server using python
|
|
|
|
Here is an example publish message in python:
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import paho.mqtt.client as mqtt
|
|
from random import randrange, uniform
|
|
import time
|
|
|
|
client = mqtt.Client("some_client_id")
|
|
client.connect('localhost')
|
|
|
|
while True:
|
|
randNumber = uniform(20.0, 21.0)
|
|
client.publish("env/test/TEMPERATURE", randNumber)
|
|
print("Just published " + str(randNumber) + " to topic TEMPERATURE")
|
|
time.sleep(1)
|
|
```
|
|
|
|
Here is example subscribe in python:
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
|
|
import paho.mqtt.client as paho
|
|
|
|
def on_message(mosq, obj, msg):
|
|
print("%-20s %d %s" % (msg.topic, msg.qos, msg.payload))
|
|
mosq.publish('pong', 'ack', 0)
|
|
|
|
def on_publish(mosq, obj, mid):
|
|
pass
|
|
|
|
if __name__ == '__main__':
|
|
client = paho.Client()
|
|
client.on_message = on_message
|
|
client.on_publish = on_publish
|
|
|
|
client.connect("localhost", 1883, 60)
|
|
|
|
client.subscribe("env/test/TEMPERATURE", 0)
|
|
|
|
while client.loop() == 0:
|
|
pass
|
|
```
|
|
|
|
### Using MQTT with Node-RED
|
|
|
|
Below is a valid JSON envelope for information sent by MQTT to a device for broadcast onto the mesh.
|
|
|
|
```json
|
|
{
|
|
"sender":"whatever you want to be the SENDER",
|
|
"type":"sendtext",
|
|
"payload": text or a json object go here
|
|
}
|
|
```
|
|
|
|
Node-RED is a free cross-platform programming tool for wiring together hardware, APIs, and online services developed originally by IBM for IOT. It is widely used for home automation by many non-professional programmers and runs well on Pi's. Node-RED has many plug-in modules written by the community. I will use this platform as a practical example on how to interface with the MQTT features of Meshtastic. Everything can be done from GUI's without using command line.
|
|
|
|
Step one: use http://client.meshtastic.org/ , the python CLI, or an Apple or Android app to connect to your device and adjust these settings.
|
|
Enable and enter network SSID/PSK. Settings--> Device Config--> Network; Save.
|
|
Set MQTT server address. Settings--> Module Config--> MQTT config; Verify Encryption Enabled is OFF. Turn JSON Output Enabled ON. Save.
|
|
Go to Channel Editor and set Uplink and Downlink enabled to True. Save.
|
|
|
|
Step two: if you don't want to depend on JSON decoding on the device, you can decode the protobuf messages off-device. To do that you will need to get the .proto files from https://github.com/meshtastic/protobufs. They function as a schema and are required for decoding in Node-RED. Save the files where the node-RED application can access them and note the file path of the "mqtt.proto" file.
|
|
|
|
Step three: install Node-RED plug-ins to your node-RED application for an embedded MQTT server and a protobuf decoder.
|
|
https://flows.nodered.org/node/node-red-contrib-aedes
|
|
https://flows.nodered.org/node/node-red-contrib-protobuf
|
|
|
|
Drag, drop, and wire the nodes like this. For this example, I ran node-RED on a Windows machine. Note that file paths might be specified differently on different platforms. MQTT server wild cards are usually the same. A "+" is a single level wildcard for a specific topic level. A "#" is a multiple level wildcard that can be used at the end of a topic filter. The debug messages shown are what happens when the inject button sends a JSON message with a topic designed to be picked up by the specified Meshtastic device and then having it rebroadcast the message.
|
|
|
|
[<img src="/documents/mqtt/NodeRedTwo.jpg" style={{zoom:'50%'}} />](/documents/mqtt/NodeRedTwo.jpg)
|
|
[<img src="/documents/mqtt/NodeRedThree.jpg" style={{zoom:'50%'}} />](/documents/mqtt/NodeRedThree.jpg)
|
|
[<img src="/documents/mqtt/NR_nodes.jpg" style={{zoom:'50%'}} />](/documents/mqtt/NR_nodes.jpg)
|
|
|
|
The aedes broker must be set up on the same flow as the other nodes. By activating the Publish debug node, you can see all the published messages.
|
|
[<img src="/documents/mqtt/Broker1.jpg" style={{zoom:'50%'}} />](/documents/mqtt/Broker1.jpg)
|
|
Receiving a json mqtt message is very simple.
|
|
[<img src="/documents/mqtt/Consume.jpg" style={{zoom:'50%'}} />](/documents/mqtt/Consume.jpg)
|
|
Injecting a json message to be sent by a device is also very simple. You do need the correct envelope.
|
|
[<img src="/documents/mqtt/Inject.jpg" style={{zoom:'50%'}} />](/documents/mqtt/Inject.jpg)
|
|
Forwarding a text message from one device, through a broker, to another broker/device/channel would look like this.
|
|
[<img src="/documents/mqtt/Forward.jpg" style={{zoom:'50%'}} />](/documents/mqtt/Forward.jpg)
|
|
If you want to decode text and position messages without json, it gets complicated:
|
|
[<img src="/documents/mqtt/DecodeNewest.jpg" style={{zoom:'50%'}} />](/documents/mqtt/DecodeNewest.jpg)
|
|
If you are interested in my flow for this it is here:
|
|
|
|
```json
|
|
[
|
|
{
|
|
"id": "10fe1b2e9cb3feb2",
|
|
"type": "decode",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "decode Protobuf",
|
|
"protofile": "a0d4288141f6a629",
|
|
"protoType": "ServiceEnvelope",
|
|
"x": 295.5,
|
|
"y": 285,
|
|
"wires": [["d3e396cf4f0a9608", "d08865b41a69d85d", "6f592d47b6a2eac4"]]
|
|
},
|
|
{
|
|
"id": "40c9ee66fe7a34cb",
|
|
"type": "function",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "function get the message as string from TEXT_MESSAGE_APP",
|
|
"func": "msg.payload = msg.payload.packet.decoded.payload;\n\nlet bufferObj = Buffer.from(msg.payload, \"base64\");\nlet decodedString = bufferObj.toString(\"utf8\");\nmsg.payload = decodedString;\n\nreturn msg;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"initialize": "",
|
|
"finalize": "",
|
|
"libs": [],
|
|
"x": 410.5,
|
|
"y": 450,
|
|
"wires": [["553374591214eaca"]]
|
|
},
|
|
{
|
|
"id": "553374591214eaca",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "text message out",
|
|
"active": true,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 762.5,
|
|
"y": 449,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "c6afbb9f1665b162",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "channelId",
|
|
"active": false,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 785.5,
|
|
"y": 257,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "607ef387d5701985",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "gatewayId",
|
|
"active": false,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 792.5,
|
|
"y": 293,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "d3e396cf4f0a9608",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "entire payload",
|
|
"active": false,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 296.5,
|
|
"y": 247,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "2339b328bb9bb1d8",
|
|
"type": "comment",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "Decode all cleartext text and position messages sent by Meshtastic devices into JSON without relying on JSON conversion on the device.",
|
|
"info": "",
|
|
"x": 515.5,
|
|
"y": 214,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "408d796d997bb832",
|
|
"type": "function",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "function get the nested payload as base64",
|
|
"func": "msg.payload = msg.payload.packet.decoded.payload;\n\nlet bufferObj = Buffer.from(msg.payload, \"base64\");\n//let decodedString = bufferObj.toString(\"hex\");\nmsg.payload = bufferObj;\nmsg.topic=\"\";\n//if you don't zero out the protubufTopic it will try to\n//decode it as part of the mqtt service envelope instead\n//of two-stage decoding\nmsg.protobufType=null;\n\nreturn msg;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"initialize": "",
|
|
"finalize": "",
|
|
"libs": [],
|
|
"x": 349,
|
|
"y": 552,
|
|
"wires": [["9435a3c605efedb4", "1ed6f96c8214d7b3"]]
|
|
},
|
|
{
|
|
"id": "61995c9f8e8266b3",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "portnum",
|
|
"active": false,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 784.5,
|
|
"y": 330,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "9435a3c605efedb4",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "nested payload",
|
|
"active": false,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "true",
|
|
"targetType": "full",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 281.5,
|
|
"y": 603,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "b832775d386f7ac9",
|
|
"type": "mqtt in",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"topic": "msh/+/c/#",
|
|
"qos": "2",
|
|
"datatype": "buffer",
|
|
"broker": "37cadac381653b1e",
|
|
"nl": false,
|
|
"rap": true,
|
|
"rh": 0,
|
|
"inputs": 0,
|
|
"x": 117.5,
|
|
"y": 286,
|
|
"wires": [["10fe1b2e9cb3feb2"]]
|
|
},
|
|
{
|
|
"id": "d08865b41a69d85d",
|
|
"type": "switch",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "switch manual decoding nested message based on portum",
|
|
"property": "payload.packet.decoded.portnum",
|
|
"propertyType": "msg",
|
|
"rules": [
|
|
{ "t": "eq", "v": "TEXT_MESSAGE_APP", "vt": "str" },
|
|
{ "t": "eq", "v": "POSITION_APP", "vt": "str" }
|
|
],
|
|
"checkall": "true",
|
|
"repair": false,
|
|
"outputs": 2,
|
|
"x": 281.5,
|
|
"y": 505,
|
|
"wires": [["40c9ee66fe7a34cb"], ["408d796d997bb832"]]
|
|
},
|
|
{
|
|
"id": "8abb1bb458af2c4f",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"rules": [
|
|
{
|
|
"t": "set",
|
|
"p": "gatewayId",
|
|
"pt": "flow",
|
|
"to": "payload",
|
|
"tot": "msg"
|
|
}
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 1021.5,
|
|
"y": 288,
|
|
"wires": [[]]
|
|
},
|
|
{
|
|
"id": "1ced0be28eeef0d3",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"rules": [
|
|
{
|
|
"t": "set",
|
|
"p": "latitude",
|
|
"pt": "flow",
|
|
"to": "payload",
|
|
"tot": "msg"
|
|
}
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 1026.5,
|
|
"y": 407,
|
|
"wires": [[]]
|
|
},
|
|
{
|
|
"id": "313fd3cfe6d91850",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"rules": [
|
|
{
|
|
"t": "set",
|
|
"p": "longitude",
|
|
"pt": "flow",
|
|
"to": "payload",
|
|
"tot": "msg"
|
|
}
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 1036.5,
|
|
"y": 450,
|
|
"wires": [["d02e53cdfb565da6"]]
|
|
},
|
|
{
|
|
"id": "33dd43e3c05f826c",
|
|
"type": "geofence",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "geofence",
|
|
"mode": "circle",
|
|
"inside": "true",
|
|
"rad": 69174.91569647488,
|
|
"points": [],
|
|
"centre": {
|
|
"latitude": 40.16287050252407,
|
|
"longitude": -86.60385131835938
|
|
},
|
|
"floor": "",
|
|
"ceiling": "",
|
|
"worldmap": true,
|
|
"outputs": 2,
|
|
"x": 1202.5,
|
|
"y": 595,
|
|
"wires": [[], ["4d01eb8f1b31f039"]]
|
|
},
|
|
{
|
|
"id": "d02e53cdfb565da6",
|
|
"type": "function",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "trigger function to send a mapping point",
|
|
"func": "let lat = parseFloat(flow.get(\"latitude\"));\nlet lon = parseFloat(flow.get(\"longitude\"));\nlat=lat * 0.0000001;\nlon=lon * 0.0000001;\nlet name = flow.get(\"from\")\n\nmsg={\"payload\":{\"name\":name,\n \"lat\":lat,\n \"lon\":lon,\n \"action\":\"send\",\n \"icon\": \"car\",\n \"label\":name\n }}\n\nreturn msg;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"initialize": "",
|
|
"finalize": "",
|
|
"libs": [],
|
|
"x": 1181.5,
|
|
"y": 520,
|
|
"wires": [["33dd43e3c05f826c", "4d01eb8f1b31f039"]]
|
|
},
|
|
{
|
|
"id": "4d01eb8f1b31f039",
|
|
"type": "worldmap",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"lat": "40",
|
|
"lon": "-86",
|
|
"zoom": "7",
|
|
"layer": "OSMG",
|
|
"cluster": "",
|
|
"maxage": "",
|
|
"usermenu": "show",
|
|
"layers": "show",
|
|
"panit": "false",
|
|
"panlock": "false",
|
|
"zoomlock": "false",
|
|
"hiderightclick": "false",
|
|
"coords": "none",
|
|
"showgrid": "false",
|
|
"showruler": "false",
|
|
"allowFileDrop": "false",
|
|
"path": "/worldmap",
|
|
"overlist": "DR,CO,RA,DN,HM",
|
|
"maplist": "OSMG,OSMC,EsriC,EsriS,EsriT,EsriDG,UKOS",
|
|
"mapname": "",
|
|
"mapurl": "",
|
|
"mapopt": "",
|
|
"mapwms": false,
|
|
"x": 1206.5,
|
|
"y": 675,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "1ed6f96c8214d7b3",
|
|
"type": "decode",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "decode Position Protobuf",
|
|
"protofile": "dbab6472b07929a0",
|
|
"protoType": "Position",
|
|
"x": 667.5,
|
|
"y": 548,
|
|
"wires": [["db1933ba36503bd9", "dad9f487318f21d9"]]
|
|
},
|
|
{
|
|
"id": "db1933ba36503bd9",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "Position decoded",
|
|
"active": false,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "true",
|
|
"targetType": "full",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 673.5,
|
|
"y": 607,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "dad9f487318f21d9",
|
|
"type": "function",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "Split",
|
|
"func": "var lat = { payload: msg.payload.latitudeI };\nvar lon = { payload: msg.payload.longitudeI };\nvar alt = { payload: msg.payload.altitude };\nvar tm = { payload: msg.payload.time };\n\nreturn [lat,lon,alt,tm];",
|
|
"outputs": 4,
|
|
"noerr": 0,
|
|
"initialize": "",
|
|
"finalize": "",
|
|
"libs": [],
|
|
"x": 875.5,
|
|
"y": 549,
|
|
"wires": [
|
|
["1ced0be28eeef0d3", "8bb97f802662976c"],
|
|
["313fd3cfe6d91850", "c8e135f3e542bb1b"],
|
|
["602fb2020680280c"],
|
|
["ed424ae3d45dd2ac"]
|
|
]
|
|
},
|
|
{
|
|
"id": "8bb97f802662976c",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "lat",
|
|
"active": true,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 1017.5,
|
|
"y": 583,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "c8e135f3e542bb1b",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "lon",
|
|
"active": true,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 1018.5,
|
|
"y": 618,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "602fb2020680280c",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "alt",
|
|
"active": true,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 1017.5,
|
|
"y": 654,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "ed424ae3d45dd2ac",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "time",
|
|
"active": true,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 1018.5,
|
|
"y": 688,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "6f592d47b6a2eac4",
|
|
"type": "function",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "Split Decoded 1",
|
|
"func": "var channelId = { payload: msg.payload.channelId};\nvar gatewayId = { payload: msg.payload.gatewayId};\nvar portnum = { payload: msg.payload.packet.decoded.portnum};\nvar fr= {payload: \"!\" + msg.payload.packet.from.toString(16)};\nvar to = {payload:\"!\"+ msg.payload.packet.to.toString(16)};\n\nreturn [channelId, gatewayId, portnum, fr, to ];",
|
|
"outputs": 5,
|
|
"noerr": 0,
|
|
"initialize": "",
|
|
"finalize": "",
|
|
"libs": [],
|
|
"x": 577.5,
|
|
"y": 294,
|
|
"wires": [
|
|
["c6afbb9f1665b162"],
|
|
["607ef387d5701985", "8abb1bb458af2c4f"],
|
|
["61995c9f8e8266b3"],
|
|
["fd881fac22422773", "a389f9875da672ec"],
|
|
["cf066ad415df30ae"]
|
|
]
|
|
},
|
|
{
|
|
"id": "fd881fac22422773",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "from",
|
|
"active": true,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 772.5,
|
|
"y": 365,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "cf066ad415df30ae",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "to",
|
|
"active": false,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 771.5,
|
|
"y": 399,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "a389f9875da672ec",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "set flow.from",
|
|
"rules": [
|
|
{ "t": "set", "p": "from", "pt": "flow", "to": "payload", "tot": "msg" }
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 1012.5,
|
|
"y": 364,
|
|
"wires": [[]]
|
|
},
|
|
{
|
|
"id": "a0d4288141f6a629",
|
|
"type": "protobuf-file",
|
|
"protopath": "E:\\Meshtastic-protobufs-master\\mqtt.proto",
|
|
"watchFile": true,
|
|
"keepCase": false
|
|
},
|
|
{
|
|
"id": "37cadac381653b1e",
|
|
"type": "mqtt-broker",
|
|
"name": "",
|
|
"broker": "192.168.2.45",
|
|
"port": "1883",
|
|
"clientid": "",
|
|
"autoConnect": true,
|
|
"usetls": false,
|
|
"protocolVersion": "4",
|
|
"keepalive": "60",
|
|
"cleansession": true,
|
|
"birthTopic": "",
|
|
"birthQos": "0",
|
|
"birthPayload": "",
|
|
"birthMsg": {},
|
|
"closeTopic": "",
|
|
"closeQos": "0",
|
|
"closePayload": "",
|
|
"closeMsg": {},
|
|
"willTopic": "",
|
|
"willQos": "0",
|
|
"willPayload": "",
|
|
"willMsg": {},
|
|
"userProps": "",
|
|
"sessionExpiry": ""
|
|
},
|
|
{
|
|
"id": "dbab6472b07929a0",
|
|
"type": "protobuf-file",
|
|
"protopath": "E:\\Meshtastic-protobufs-master\\mesh.proto",
|
|
"watchFile": true,
|
|
"keepCase": false
|
|
}
|
|
]
|
|
```
|
|
|
|
(documents/mqtt/Flow.txt)
|
|
|
|
Node-red can rapidly (minutes vs days) put together some pretty impressive output when paired with meshtastic. Here is the output of that flow geofencing and mapping via mqtt data.
|
|
[<img src="/documents/mqtt/Mapping.jpg" style={{zoom:'50%'}} />](/documents/mqtt/Mapping.jpg)
|
|
|
|
Advanced use, such as encoding Position and sending it to a device via MQTT without using JSON can get a little complicated. An example of how it can be done is below.
|
|
[<img src="/documents/mqtt/EncodingPosition.jpg" style={{zoom:'50%'}} />](/documents/mqtt/EncodingPosition.jpg)
|
|
The flow is:
|
|
|
|
```json
|
|
[
|
|
{
|
|
"id": "32ca608d9e7c5236",
|
|
"type": "inject",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"props": [{ "p": "payload" }, { "p": "topic", "vt": "str" }],
|
|
"repeat": "",
|
|
"crontab": "",
|
|
"once": false,
|
|
"onceDelay": 0.1,
|
|
"topic": "",
|
|
"payload": "",
|
|
"payloadType": "date",
|
|
"x": 96.5,
|
|
"y": 1952,
|
|
"wires": [["2b536512e8c7aef2"]]
|
|
},
|
|
{
|
|
"id": "20bbd2d1408b8dc5",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"rules": [
|
|
{
|
|
"t": "set",
|
|
"p": "channelId_outbound",
|
|
"pt": "flow",
|
|
"to": "LongFast",
|
|
"tot": "str"
|
|
}
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 772,
|
|
"y": 2027,
|
|
"wires": [[]]
|
|
},
|
|
{
|
|
"id": "c6cb373157be01d6",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"rules": [
|
|
{
|
|
"t": "set",
|
|
"p": "gatewayId_outbound",
|
|
"pt": "flow",
|
|
"to": "\"!55c7312c\"",
|
|
"tot": "str"
|
|
}
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 772,
|
|
"y": 2066,
|
|
"wires": [[]]
|
|
},
|
|
{
|
|
"id": "24199ec7eaf89c1a",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"rules": [
|
|
{
|
|
"t": "set",
|
|
"p": "portnum_outbound",
|
|
"pt": "flow",
|
|
"to": "3",
|
|
"tot": "num"
|
|
}
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 774,
|
|
"y": 2106,
|
|
"wires": [[]]
|
|
},
|
|
{
|
|
"id": "de38ad5ef343623a",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"rules": [
|
|
{
|
|
"t": "set",
|
|
"p": "from_outbound",
|
|
"pt": "flow",
|
|
"to": "1439117612",
|
|
"tot": "num"
|
|
}
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 781,
|
|
"y": 2146,
|
|
"wires": [[]]
|
|
},
|
|
{
|
|
"id": "d435e8abe0852f93",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"rules": [
|
|
{
|
|
"t": "set",
|
|
"p": "to_outbound",
|
|
"pt": "flow",
|
|
"to": "4294967295",
|
|
"tot": "num"
|
|
}
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 790,
|
|
"y": 2188,
|
|
"wires": [[]]
|
|
},
|
|
{
|
|
"id": "1f8d172708898860",
|
|
"type": "function",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "Assemble Position protobuf",
|
|
"func": "msg.protobufType=null;\nmsg.payload =\n{\n \"packet\": {\n \"from\": flow.get(\"from_outbound\"),\n \"to\": flow.get(\"to_outbound\"), \n \"decoded\":{\n //how ENUMS are handled here\n //portnum is decoded as string but encoded as number\n //in the encode/decode node-red nodes based on protobuf.js\n \"portnum\": flow.get(\"portnum_outbound\"),\n \"payload\": msg.payload \n } \n },\n\n \"channelId\": flow.get(\"channelId_outbound\"),\n \"gatewayId\": flow.get(\"gatewayId_outbound\"),\n};\nreturn msg;\n//info on how to get json data into protobuf \"bytes\" field\n//https://github.com/protobufjs/protobuf.js/wiki/Changes-in-ProtoBuf.js-3.8",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"initialize": "",
|
|
"finalize": "",
|
|
"libs": [],
|
|
"x": 1086,
|
|
"y": 2019,
|
|
"wires": [["b8ccf1cfe8bf40a3"]]
|
|
},
|
|
{
|
|
"id": "b8ccf1cfe8bf40a3",
|
|
"type": "encode",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"protofile": "a0d4288141f6a629",
|
|
"protoType": "ServiceEnvelope",
|
|
"x": 1287,
|
|
"y": 2020,
|
|
"wires": [["dbc78f035c9c2b56", "a002c148f3a06bac"]]
|
|
},
|
|
{
|
|
"id": "03a7e69ca6d471fe",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "show hex string",
|
|
"active": true,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "true",
|
|
"targetType": "full",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 1319,
|
|
"y": 2180,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "dbc78f035c9c2b56",
|
|
"type": "function",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "dump payload as hex string",
|
|
"func": "var hex=Buffer.from(msg.payload,\"hex\");\nmsg.payload=hex.toString(\"hex\");\nreturn msg;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"initialize": "",
|
|
"finalize": "",
|
|
"libs": [],
|
|
"x": 1096,
|
|
"y": 2178,
|
|
"wires": [["03a7e69ca6d471fe"]]
|
|
},
|
|
{
|
|
"id": "2b536512e8c7aef2",
|
|
"type": "function",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "Inject lat lon alt",
|
|
"func": "msg.payload={\n \"latitudeI\": 399600000,\n \"longitudeI\": -862600000,\n \"altitude\": 100\n}\nreturn msg;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"initialize": "",
|
|
"finalize": "",
|
|
"libs": [],
|
|
"x": 277.5,
|
|
"y": 1953,
|
|
"wires": [["9443a9a980e54c75"]]
|
|
},
|
|
{
|
|
"id": "9443a9a980e54c75",
|
|
"type": "encode",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "encode Position as protobuf",
|
|
"protofile": "dbab6472b07929a0",
|
|
"protoType": "Position",
|
|
"x": 506,
|
|
"y": 1953,
|
|
"wires": [["5c36d3a7f4dca14e"]]
|
|
},
|
|
{
|
|
"id": "5c36d3a7f4dca14e",
|
|
"type": "change",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "",
|
|
"rules": [
|
|
{
|
|
"t": "set",
|
|
"p": "nested_outbound",
|
|
"pt": "flow",
|
|
"to": "payload",
|
|
"tot": "msg"
|
|
}
|
|
],
|
|
"action": "",
|
|
"property": "",
|
|
"from": "",
|
|
"to": "",
|
|
"reg": false,
|
|
"x": 776,
|
|
"y": 1952,
|
|
"wires": [
|
|
[
|
|
"20bbd2d1408b8dc5",
|
|
"c6cb373157be01d6",
|
|
"24199ec7eaf89c1a",
|
|
"de38ad5ef343623a",
|
|
"d435e8abe0852f93",
|
|
"04d0c4a5f3485c6f"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "04d0c4a5f3485c6f",
|
|
"type": "function",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "dump payload as base64 string",
|
|
"func": "var hex=Buffer.from(msg.payload,\"base64\");\nmsg.payload=hex.toString(\"base64\");\nreturn msg;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"initialize": "",
|
|
"finalize": "",
|
|
"libs": [],
|
|
"x": 1082,
|
|
"y": 1952,
|
|
"wires": [["1f8d172708898860"]]
|
|
},
|
|
{
|
|
"id": "a002c148f3a06bac",
|
|
"type": "decode",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "test decode Protobuf",
|
|
"protofile": "a0d4288141f6a629",
|
|
"protoType": "ServiceEnvelope",
|
|
"x": 1249,
|
|
"y": 1860,
|
|
"wires": [["4b6fc79398d05782"]]
|
|
},
|
|
{
|
|
"id": "4b6fc79398d05782",
|
|
"type": "debug",
|
|
"z": "23dbb1ee.bc2e8e",
|
|
"name": "test entire payload",
|
|
"active": true,
|
|
"tosidebar": true,
|
|
"console": false,
|
|
"tostatus": false,
|
|
"complete": "payload",
|
|
"targetType": "msg",
|
|
"statusVal": "",
|
|
"statusType": "auto",
|
|
"x": 1458,
|
|
"y": 1859,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "a0d4288141f6a629",
|
|
"type": "protobuf-file",
|
|
"protopath": "E:\\Meshtastic-protobufs-master\\mqtt.proto",
|
|
"watchFile": true,
|
|
"keepCase": false
|
|
},
|
|
{
|
|
"id": "dbab6472b07929a0",
|
|
"type": "protobuf-file",
|
|
"protopath": "E:\\Meshtastic-protobufs-master\\mesh.proto",
|
|
"watchFile": true,
|
|
"keepCase": false
|
|
}
|
|
]
|
|
```
|
|
|
|
Sending a position to a device for broadcast to the mesh is much easier with JSON. This introduces a new MQTT Service Envelope type: "sendposition". A valid MQTT envelope and message to broadcast lat, lon, altitude looks like this.
|
|
|
|
```json
|
|
{
|
|
"sender": "someSender",
|
|
"type": "sendposition",
|
|
"payload": {
|
|
"latitude_i": 399600000,
|
|
"longitude_i": -862600000,
|
|
"altitude": 100,
|
|
"time": 1670201543
|
|
}
|
|
}
|
|
```
|
|
|
|
An example of doing this in node-red:
|
|
[<img src="/documents/mqtt/PosJSON.jpg" style={{zoom:'50%'}} />](/documents/mqtt/PosJSON.jpg)
|