nodered

Использование MQTT с Node-RED

Node-RED — это бесплатный кросс-платформенный инструмент программирования для соединения аппаратного обеспечения, API и онлайн-сервисов, первоначально разработанный IBM для IoT. Он широко используется для домашней автоматизации многими непрофессиональными программистами и хорошо работает на Raspberry Pi. Node-RED имеет множество модулей плагинов, написанных сообществом.

Я буду использовать эту платформу в качестве практического примера того, как взаимодействовать с функциями MQTT Meshtastic. Всё можно сделать из графических интерфейсов без использования командной строки.

Включение MQTT

Используйте http://client.meshtastic.org/ , python CLI или приложение для Apple или Android, чтобы подключиться к вашему устройству и настроить эти параметры.

  1. Настройки--> Конфигурация радио--> Сеть

  2. На узле, который будет выступать в роли шлюза между сеткой и MQTT, включите сетевое соединение (например, WiFi, Ethernet).

  3. Сохранить

  4. Настройки--> Конфигурация модуля--> Конфигурация MQTT

  5. Настройте сетевую конфигурацию шлюза MQTT.
  6. Убедитесь, что Encryption Enabled выключено.
  7. (опционально) Включите JSON Output Enabled.
  8. Сохранить
  9. Редактор каналов
  10. Перейдите в Редактор каналов и включите Uplink и Downlink на каналах, которые вы хотите публиковать в MQTT.
  11. Сохранить

Использование Node-RED с Meshtastic

Существует три распространённых подхода:

  1. Использование сообщений, закодированных в JSON
  2. Использование сообщений, закодированных в protobuf, с узлом декодирования Meshtastic
  3. Использование сообщений, закодированных в protobuf, с узлом декодирования protobuf и определениями protobuf Meshtastic

Вывод в JSON публикует только следующий подмножество сообщений в сети Meshtastic:

JSON предназначен для удобства потребления данных в других приложениях, таких как Home Assistant.

Protobuf — это родной формат сетки.

1. Использование сообщений, закодированных в JSON

:::note JSON не поддерживается на платформе nRF52. :::

Убедитесь, что опция JSON Output Enabled установлена в опциях модуля MQTT, и у вас есть канал с названием "mqtt".

Ниже приведена действительная оболочка JSON для информации, отправляемой MQTT на устройство для трансляции в сетку. Поле to является опциональным и может быть опущено для трансляции. Поле channel также опционально и может быть опущено для отправки на основной канал.

{
  "from":<node number of the transmitter>,
  "to": <node number of the receiver for a DM (optional)>,
  "channel": <channel index (optional)>,
  "type":"sendtext",
  "payload": text or a json object go here
}

2. Использование сообщений, закодированных в protobuf, с узлом декодирования Meshtastic

Установите плагин Node-RED: https://flows.nodered.org/node/@meshtastic/node-red-contrib-meshtastic

Дополнительная информация в репозитории исходного кода плагина.

Доступен пример потока, использующий этот механизм https://github.com/scruplelesswizard/meshtastic-node-red

3. Использование сообщений, закодированных в protobuf, с узлом декодирования protobuf и определениями protobuf Meshtastic

Если вы не хотите зависеть от декодирования JSON на устройстве, вы можете декодировать сообщения protobuf вне устройства. Для этого вам нужно получить файлы .proto из https://github.com/meshtastic/protobufs. Они служат схемой и необходимы для декодирования в Node-RED. Сохраните файлы там, где приложение Node-RED может к ним получить доступ, и запишите путь к файлу "mqtt.proto".

Установите плагины Node-RED в ваше приложение Node-RED для встроенного MQTT-сервера и декодера protobuf. https://flows.nodered.org/node/node-red-contrib-aedes https://flows.nodered.org/node/node-red-contrib-protobuf

Перетащите, сбросьте и соедините узлы следующим образом. В этом примере Node-RED запущен на машине Windows. Обратите внимание, что пути к файлам могут указываться по-разному на разных платформах. Подстановочные знаки MQTT-сервера обычно одинаковы. "+" — это подстановочный знак одного уровня для конкретного уровня темы. "#" — это подстановочный знак нескольких уровней, который можно использовать в конце фильтра темы. Отображаемые отладочные сообщения показывают, что происходит, когда кнопка inject отправляет JSON-сообщение с темой, предназначенной для захвата указанным устройством Meshtastic, а затем устройство повторно транслирует сообщение.

Брокер aedes должен быть настроен в том же потоке, что и другие узлы. Активируя узел Publish debug, вы можете увидеть все опубликованные сообщения. Получение JSON-сообщения MQTT очень просто. Внедрение JSON-сообщения для отправки устройством также очень просто. Вам нужна правильная оболочка. Пересылка текстового сообщения с одного устройства через брокер на другой брокер/устройство/канал будет выглядеть так. Если вы хотите декодировать текстовые и позиционные сообщения без 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 может быстро (минуты против дней) собрать довольно впечатляющий результат в паре с Meshtastic. Вот результат этого потока: геозаборы и картирование на основе данных MQTT.
[<img src="../../../../../images/ff218bbbe8fb1545faa46e136733b105.webp" style={{zoom:'50%'}} />](../../../../documents/mqtt/Mapping.webp.html)

Расширенное использование, такое как кодирование Position и отправка его на устройство через MQTT без использования JSON, может быть немного сложным. Ниже приведён пример того, как это можно сделать.
[<img src="../../../../../images/1d04c6672d4dd57038ffbf1430f9568a.webp" style={{zoom:'50%'}} />](../../../../documents/mqtt/EncodingPosition.webp.html)
Поток следующий:

```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
  }
]

Отправка позиции на устройство для трансляции в mesh-сеть гораздо проще с использованием JSON. Сообщение должно быть опубликовано в канал под названием "mqtt". Чтобы отправить сообщение в другой канал, установите поле channel в индекс канала (0-7). Затем используйте тип MQTT Service Envelope: "sendposition". Действительный MQTT-оковелоп и сообщение для трансляции lat, lon, высоты (необязательно) и времени (необязательно) выглядит следующим образом:

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

Пример реализации этого в Node-RED:

 ```