Skip to main content

Node-RED

Using MQTT with Node-RED

Below is a valid JSON envelope for information sent by MQTT to a device for broadcast onto the mesh.

{
"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--> Radio 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.

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. Receiving a json mqtt message is very simple. Injecting a json message to be sent by a device is also very simple. You do need the correct envelope. Forwarding a text message from one device, through a broker, to another broker/device/channel would look like this. If you want to decode text and position messages without json, it gets complicated: If you are interested in my flow for this it is here:

[
{
"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.

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. The flow is:

[
{
"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.

{
"sender": "someSender",
"type": "sendposition",
"payload": {
"latitude_i": 399600000,
"longitude_i": -862600000,
"altitude": 100,
"time": 1670201543
}
}

An example of doing this in node-red: