MQTT to Node-RED Integration
Node-RED is a browser-based visual programming tool built on Node.js, ideal for wiring together MQTT data streams from Meshtastic with downstream services such as dashboards, databases, and messaging platforms. This page covers installing Node-RED, subscribing to Meshtastic MQTT topics, parsing protobuf and JSON payloads, building a simple monitoring dashboard, and forwarding mesh alerts to Telegram and Discord.
Installing Node-RED
On Raspberry Pi / Debian / Ubuntu
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
# Enable and start Node-RED as a service
sudo systemctl enable nodered.service
sudo systemctl start nodered.service
# Node-RED web UI is available at http://<IP>:1880
On x86 Linux via npm
sudo npm install -g --unsafe-perm node-red
# Start manually:
node-red
# Or install as systemd service manually
Installing Required Palettes
Open the Node-RED web UI at http://<IP>:1880, then click the hamburger
menu → Manage Palette → Install. Search for and install:
node-red-dashboard- UI widgets (gauges, charts, text displays).node-red-contrib-protobuf- decode Meshtastic protobuf payloads.node-red-contrib-telegrambot- send messages to Telegram.
Alternatively via CLI
cd ~/.node-red
npm install node-red-dashboard node-red-contrib-protobuf node-red-contrib-telegrambot
sudo systemctl restart nodered.service
Subscribing to Meshtastic MQTT Topics
Add an mqtt in node to your flow:
- Server: your broker address and port.
- Topic:
msh/#to receive all Meshtastic traffic, ormsh/US/2/json/LongFast/#for JSON output from the LongFast channel. - QoS: 0 (at most once) is sufficient for mesh monitoring.
- Output: auto-detect (string or buffer).
Parsing Protobuf Payloads
The protobuf format requires a .proto schema file. Obtain the Meshtastic proto files:
mkdir -p ~/.node-red/proto
cd ~/.node-red/proto
wget https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto
wget https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/portnums.proto
wget https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/telemetry.proto
Flow wiring for protobuf decode:
[mqtt in] → [function: base64 to buffer] → [protobuf decode] → [debug / further processing]
Add a function node between mqtt in and protobuf decode with this code:
// Convert base64 MQTT payload to Buffer for protobuf decoder
msg.payload = Buffer.from(msg.payload, 'base64');
msg.protofile = '/home/pi/.node-red/proto/mesh.proto';
msg.protobufType = 'meshtastic.ServiceEnvelope';
return msg;
The protobuf decode node outputs a JavaScript object representing the
ServiceEnvelope. The inner packet.decoded field contains the decrypted
Data (if Node-RED has the channel key - see note below).
Note on channel key decryption: The node-red-contrib-meshtastic
community package (if available) can handle channel key decryption. Without it, you can only
decode the outer envelope metadata. For full access to message text and telemetry, enable JSON
output mode on the gateway (which the gateway decrypts before publishing).
Parsing JSON Payloads (Recommended for Simplicity)
If JSON output is enabled on your gateway node, subscribe to the JSON topic and use a json node to parse directly - no protobuf library needed:
[mqtt in (topic: msh/US/2/json/LongFast/#)] → [json] → [switch] → [handlers]
A decoded JSON message looks like:
{
"id": 3456789012,
"channel": 0,
"from": 2887456789,
"sender": "!abcd1234",
"type": "text",
"payload": {
"text": "Hello from City A!"
},
"timestamp": 1714953600,
"rssi": -87,
"snr": 6.25,
"hops_away": 2
}
Building a Simple Dashboard
With node-red-dashboard installed, add a UI group for "Mesh Monitor":
- Add a ui_text node: label "Last Message", wired from the JSON parse output
using a function to extract
msg.payload.payload.text. - Add a ui_gauge node: label "RSSI (dBm)", min -130, max -50, wired to
msg.payload.rssi. - Add a ui_chart node: "Message Rate / min", use an rbe (report-by-exception) node plus a counter function to track message frequency.
- Add a ui_table node: "Recent Messages" - build a rolling array of the last 20 messages using a context store.
Access the dashboard at http://<IP>:1880/ui.
Forwarding Alerts to Telegram
Pre-requisite: create a Telegram Bot via @BotFather and note the bot token and your chat ID.
Wire a flow like this:
[mqtt in] → [json] → [switch: type == "text"] → [function: format alert] → [telegram sender]
Function node code to format the Telegram message:
const p = msg.payload;
if (!p.payload || !p.payload.text) return null; // skip non-text packets
msg.payload = {
chatId: "YOUR_CHAT_ID_HERE",
type: "message",
content: `📡 Mesh Message
` +
`From: ${p.sender}
` +
`Text: ${p.payload.text}
` +
`RSSI: ${p.rssi} dBm | SNR: ${p.snr} | Hops: ${p.hops_away}`
};
return msg;
Configure the Telegram Sender node with your bot token.
Forwarding Alerts to Discord
Use a http request node pointed at your Discord webhook URL:
[mqtt in] → [json] → [switch: type == "text"] → [function: build webhook body] → [http request]
Function node code:
const p = msg.payload;
if (!p.payload || !p.payload.text) return null;
msg.method = "POST";
msg.url = "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN";
msg.headers = { "Content-Type": "application/json" };
msg.payload = JSON.stringify({
username: "MeshAmerica Bot",
content: `**Mesh Message** from \`${p.sender}\`
` +
`> ${p.payload.text}
` +
`RSSI: ${p.rssi} dBm | SNR: ${p.snr} | Hops: ${p.hops_away}`
});
return msg;
Example: Full Flow JSON Export
Below is a minimal Node-RED flow (importable via Import → Clipboard) that subscribes to the JSON MQTT topic, parses messages, displays them in a dashboard, and forwards them to a Discord webhook:
[
{
"id": "mqtt_in_1",
"type": "mqtt in",
"name": "Meshtastic JSON",
"topic": "msh/US/2/json/LongFast/#",
"qos": "0",
"broker": "local_broker",
"wires": [["json_parse_1"]]
},
{
"id": "json_parse_1",
"type": "json",
"name": "Parse JSON",
"wires": [["switch_1", "dashboard_table_1"]]
},
{
"id": "switch_1",
"type": "switch",
"name": "Text only",
"property": "payload.type",
"rules": [{"t": "eq", "v": "text"}],
"wires": [["format_discord_1"]]
},
{
"id": "format_discord_1",
"type": "function",
"name": "Format Discord",
"func": "const p = msg.payload;
if (!p.payload||!p.payload.text) return null;
msg.method='POST';
msg.url='https://discord.com/api/webhooks/XXX/YYY';
msg.headers={'Content-Type':'application/json'};
msg.payload=JSON.stringify({username:'MeshBot',content:`**${p.sender}**: ${p.payload.text}`});
return msg;",
"wires": [["http_discord_1"]]
},
{
"id": "http_discord_1",
"type": "http request",
"name": "Discord Webhook",
"method": "POST",
"wires": [[]]
}
]
Replace the Discord webhook URL placeholder with your real webhook before importing.
No comments to display
No comments to display