--- id: nodered title: Node-RED sidebar_label: Node-RED sidebar_position: 3 --- ## Using MQTT with Node-RED 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. ### Enabling MQTT Use http://client.meshtastic.org/ , the python CLI, or an Apple or Android app to connect to your device and adjust these settings. Step one: Enable and enter network SSID/PSK. - Settings--> Radio Config--> Network; Save. - Set MQTT server address. - Settings--> Module Config--> MQTT config; Verify Encryption Enabled is OFF. - (optional) 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. ### Using Node-RED with Meshtastic Now, there are three possible approaches: 1. Using JSON messages. 2. Using protobuf with a converter node. 3. Using protobuf with locally saved proto files Be aware that JSON option is somewhat less supported: > JSON was only ever intended to be a convenience for folks wanting to consume data in things like Home Assistant. > Protobufs are mesh native. #### 1. Using JSON messages Make sure that option *JSON Output Enabled* is set in MQTT module options. 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 } ``` #### 2. Using protobuf with a converter node Install Node-Red plug-in: https://flows.nodered.org/node/@meshtastic/node-red-contrib-meshtastic More info and example flow is in the plug-in source repository: https://github.com/scruplelesswizard/meshtastic-node-red #### 3. Using protobuf with locally saved proto files 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)