Node-RED Flows for Mesh Automation
Overview
Node-RED is a visual flow-based programming tool that acts as powerful middleware between your Meshtastic MQTT feed and virtually any other service. It runs on Linux (including Raspberry Pi), inside Home Assistant, or on any Node.js-capable machine.
About the JSON MQTT topics used below: Meshtastic publishes decoded JSON packets to msh/REGION/2/json/CHANNELNAME/USERID - the segment after json is the channel name, not the packet type. There is no .../json/text/ sub-topic. To catch all channels, subscribe to msh/US/2/json/# and filter on the packet type (a type field inside the JSON, e.g. "text", "position") in a switch/function node. Also note these JSON flows require the gateway to publish decoded (plaintext) packets to the broker - which bypasses channel encryption at the broker. If that broker is the public one, the decoded content (including any "emergency" channel) is public. Use a private, access-controlled broker for any sensitive channel.
Installing Node-RED
Standalone on Raspberry Pi / Linux:
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
sudo systemctl enable nodered
sudo systemctl start nodered
Access the editor at http://your-pi-ip:1880.
In Home Assistant: Install the Node-RED add-on from the add-on store. It integrates directly with your HA entities.
Core Flow Pattern 1: Message Logger
This flow captures all text messages from Meshtastic and writes them to a log file.
[MQTT In] → [JSON Parse] → [Switch: msg.payload.type == "text"] → [Function: format line] → [File Write]
MQTT In topic: msh/US/2/json/# (subscribe to all channels; filter type in the switch)
Function node:
msg.payload = new Date().toISOString() + " [!" +
msg.payload.from.toString(16).toUpperCase() + "] " +
msg.payload.payload + "
";
return msg;
File node: /home/pi/mesh_log.txt (append mode)
In the decoded text-message JSON, from is a top-level decimal node ID (prefix it with ! when displaying the hex), and for a text packet the message string is at payload.payload (not payload.payload.text). Verify the exact field nesting against a live JSON sample, since it varies by packet type.
Core Flow Pattern 2: Position Tracker to Google Sheets
Filter position packets by a specific node ID and push lat/lon to a Google Sheet via the Sheets API node (node-red-contrib-google-sheets).
[MQTT In: msh/US/2/json/#] → [JSON Parse] → [Switch: msg.payload.type == "position" AND msg.payload.from == targetNodeId]
→ [Function: build row] → [Google Sheets: append row]
Function node:
var pos = msg.payload.payload;
msg.payload = [
new Date().toISOString(),
pos.latitude_i / 1e7,
pos.longitude_i / 1e7,
pos.altitude
];
return msg;
Core Flow Pattern 3: Two-Way Bridge - Meshtastic ↔ Telegram
Install node-red-contrib-telegrambot. This flow forwards incoming mesh messages to a Telegram chat and relays Telegram replies back to the mesh channel.
Meshtastic → Telegram:
[MQTT In: msh/US/2/json/#] → [JSON] → [Switch: type == "text"] → [Function: build TG msg]
→ [Telegram Sender]
Telegram → Meshtastic:
[Telegram Receiver] → [Function: build MQTT payload]
→ [MQTT Out: msh/US/2/json/mqtt/] (downlink topic)
Downlink is more involved than it looks. Publishing arbitrary JSON to an output topic does not reliably inject a message into the mesh. For the firmware to transmit a downlinked message the gateway must have downlink enabled for that channel (mqtt module / channel downlink_enabled), and the payload must use the correct topic and envelope that the firmware accepts. If those conditions are not met, your Telegram replies will silently never reach the mesh. Confirm the current downlink topic/envelope against the Meshtastic MQTT integration docs before relying on this direction.
Emergency Channel SMS Forwarding via Twilio
Install node-red-contrib-twilio. Watch a dedicated emergency channel (channel index 1, for example) and forward any message to a phone number via SMS:
[MQTT In: msh/US/2/json/#] → [JSON] → [Switch: type == "text" AND channel == 1]
→ [Function: format SMS] → [Twilio: send SMS]
Function node:
var from = msg.payload.from.toString(16).toUpperCase();
msg.payload = "MESH EMERGENCY from !" + from + ": " +
msg.payload.payload;
return msg;
Set the Twilio node with your Account SID, Auth Token, and destination phone number.
Caution - this is a best-effort convenience path, not a primary emergency channel. This SMS-forwarding chain depends on the gateway hearing the packet over RF and on the gateway's internet/cellular connection, the MQTT broker, Node-RED, and Twilio all being up - any of which can fail silently during the exact grid-down / internet-outage scenario an emergency net plans for. Treat it as a convenience overlay on top of direct RF monitoring, never as your primary emergency communications method.
No comments to display
No comments to display