diff --git a/docs/software/mqtt/index.mdx b/docs/software/mqtt/index.mdx index 7b7b4f19..edaf7c66 100644 --- a/docs/software/mqtt/index.mdx +++ b/docs/software/mqtt/index.mdx @@ -135,1032 +135,8 @@ An existing public [MQTT broker](https://mosquitto.org) will be the default for ## Examples -### Using mosquitto on a mac +- [Using mosquitto on a mac](/docs/software/mqtt/mosquitto.mdx) -1. install mqtt server +- [Sending/receiving messages on mosquitto server using python](/docs/software/mqtt/python.mdx) - ```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. - -[](/documents/mqtt/NodeRedTwo.jpg) -[](/documents/mqtt/NodeRedThree.jpg) -[](/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. -[](/documents/mqtt/Broker1.jpg) -Receiving a json mqtt message is very simple. -[](/documents/mqtt/Consume.jpg) -Injecting a json message to be sent by a device is also very simple. You do need the correct envelope. -[](/documents/mqtt/Inject.jpg) -Forwarding a text message from one device, through a broker, to another broker/device/channel would look like this. -[](/documents/mqtt/Forward.jpg) -If you want to decode text and position messages without json, it gets complicated: -[](/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. -[](/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. -[](/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: -[](/documents/mqtt/PosJSON.jpg) +- [Using MQTT with Node-RED](/docs/software/mqtt/nodered.mdx) diff --git a/docs/software/mqtt/mosquitto.mdx b/docs/software/mqtt/mosquitto.mdx new file mode 100644 index 00000000..337933b8 --- /dev/null +++ b/docs/software/mqtt/mosquitto.mdx @@ -0,0 +1,50 @@ +--- +id: mosquitto +title: Mosquitto +sidebar_label: Mosquitto +sidebar_position: 1 +--- + +### 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. \ No newline at end of file diff --git a/docs/software/mqtt/nodered.mdx b/docs/software/mqtt/nodered.mdx new file mode 100644 index 00000000..ddf18973 --- /dev/null +++ b/docs/software/mqtt/nodered.mdx @@ -0,0 +1,945 @@ +--- +id: nodered +title: Node-RED +sidebar_label: Node-RED +sidebar_position: 3 +--- + +### 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. + +[](/documents/mqtt/NodeRedTwo.jpg) +[](/documents/mqtt/NodeRedThree.jpg) +[](/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. +[](/documents/mqtt/Broker1.jpg) +Receiving a json mqtt message is very simple. +[](/documents/mqtt/Consume.jpg) +Injecting a json message to be sent by a device is also very simple. You do need the correct envelope. +[](/documents/mqtt/Inject.jpg) +Forwarding a text message from one device, through a broker, to another broker/device/channel would look like this. +[](/documents/mqtt/Forward.jpg) +If you want to decode text and position messages without json, it gets complicated: +[](/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. +[](/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. +[](/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: +[](/documents/mqtt/PosJSON.jpg) diff --git a/docs/software/mqtt/python.mdx b/docs/software/mqtt/python.mdx new file mode 100644 index 00000000..6f14c1a7 --- /dev/null +++ b/docs/software/mqtt/python.mdx @@ -0,0 +1,53 @@ +--- +id: mqtt-python +title: Python +sidebar_label: Python +sidebar_position: 2 +--- + +### 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 +```