Step 5: Node-RED Flow & Dashboard
Now let’s build the Node-RED flow that subscribes to MQTT, processes data, and shows it on a dashboard.
Access Node-RED
Node-RED should be running from Docker Compose. Open your browser:
http://localhost:1880
You should see the Node-RED editor.
Install Dashboard Nodes
First, install the dashboard nodes:
- Click the menu (☰) → Manage palette
- Go to Install tab
- Search for
node-red-dashboard - Click Install
This adds dashboard nodes for gauges, charts, and UI elements.
Build the Flow
Here’s the complete flow structure:
Step-by-Step Flow Creation
1. Add MQTT In Node
- Drag mqtt in node to canvas
- Double-click to configure:
- Server: Add new →
localhost:1883(or usemosquittoif in Docker network) - Topic:
iot/devices/+/telemetry - QoS: 1
- Server: Add new →
- Click Done
2. Add Function Node (Validation)
- Drag function node to canvas
- Connect it after MQTT In
- Double-click and paste this code:
// Validate and parse message
const msg = JSON.parse(msg.payload);
// Validate required fields
if (!msg.deviceId || !msg.ts || !msg.metrics || !msg.seq) {
node.warn("Missing required fields");
return null; // Drop invalid message
}
// Validate value ranges
const temp = msg.metrics.temperatureC;
const hum = msg.metrics.humidityPct;
const bat = msg.metrics.batteryPct;
if (temp < -50 || temp > 100 || hum < 0 || hum > 100 || bat < 0 || bat > 100) {
node.warn("Values out of range");
return null;
}
// Add derived metric (Fahrenheit)
msg.metrics.temperatureF = (temp * 9/5) + 32;
// Store device ID for routing
msg.deviceId = msg.deviceId;
// Output validated message
return msg;
3. Add Dashboard Nodes
Temperature Gauge:
- Drag gauge node to canvas
- Connect from Function
- Configure:
- Group: Add new →
IoT Dashboard - Tab: Add new →
Digital Twin - Label:
Temperature - Units:
°C - Min:
0, Max:40 - Value:
{{metrics.temperatureC}}
- Group: Add new →
Humidity Gauge:
- Another gauge node
- Same group/tab
- Label:
Humidity - Units:
% - Min:
0, Max:100 - Value:
{{metrics.humidityPct}}
Battery Gauge:
- Another gauge node
- Same group/tab
- Label:
Battery - Units:
% - Min:
0, Max:100 - Value:
{{metrics.batteryPct}}
Chart:
- Drag chart node
- Same group/tab
- Label:
Temperature Over Time - Chart Type: Line
- X-axis: Time
- Y-axis:
{{metrics.temperatureC}}
4. Deploy
Click the Deploy button (top right). The flow is now active.
View the Dashboard
Open the dashboard:
http://localhost:1880/ui
You should see:
- Three gauges showing current values
- A line chart showing temperature history
- Values updating in real-time as messages arrive
Add Debug Node (Optional)
To see raw messages in Node-RED sidebar:
- Add debug node
- Connect from Function
- Configure: Output:
complete message object - Deploy
Messages will appear in the Debug tab on the right.
Flow JSON Export
Here’s the complete flow as JSON (you can import this):
[
{
"id": "mqtt-in-1",
"type": "mqtt in",
"name": "Subscribe Telemetry",
"topic": "iot/devices/+/telemetry",
"qos": "1",
"broker": "broker-config",
"x": 100,
"y": 100,
"wires": [["function-1"]]
},
{
"id": "function-1",
"type": "function",
"name": "Validate & Transform",
"func": "const msg = JSON.parse(msg.payload);\nif (!msg.deviceId || !msg.ts || !msg.metrics || !msg.seq) {\n return null;\n}\nconst temp = msg.metrics.temperatureC;\nconst hum = msg.metrics.humidityPct;\nconst bat = msg.metrics.batteryPct;\nif (temp < -50 || temp > 100 || hum < 0 || hum > 100 || bat < 0 || bat > 100) {\n return null;\n}\nmsg.metrics.temperatureF = (temp * 9/5) + 32;\nreturn msg;",
"outputs": 1,
"x": 300,
"y": 100,
"wires": [["gauge-temp", "gauge-hum", "gauge-bat", "chart-1"]]
},
{
"id": "gauge-temp",
"type": "ui_gauge",
"name": "Temperature",
"group": "dashboard-group",
"order": 1,
"width": 0,
"height": 0,
"gtype": "gauge",
"title": "Temperature",
"label": "units",
"format": "{{value}}°C",
"min": 0,
"max": 40,
"x": 500,
"y": 80,
"wires": []
},
{
"id": "gauge-hum",
"type": "ui_gauge",
"name": "Humidity",
"group": "dashboard-group",
"order": 2,
"width": 0,
"height": 0,
"gtype": "gauge",
"title": "Humidity",
"label": "units",
"format": "{{value}}%",
"min": 0,
"max": 100,
"x": 500,
"y": 150,
"wires": []
},
{
"id": "gauge-bat",
"type": "ui_gauge",
"name": "Battery",
"group": "dashboard-group",
"order": 3,
"width": 0,
"height": 0,
"gtype": "gauge",
"title": "Battery",
"label": "units",
"format": "{{value}}%",
"min": 0,
"max": 100,
"x": 500,
"y": 220,
"wires": []
},
{
"id": "chart-1",
"type": "ui_chart",
"name": "Temperature Chart",
"group": "dashboard-group",
"order": 4,
"width": 0,
"height": 0,
"label": "Temperature Over Time",
"chartType": "line",
"legend": "false",
"xformat": "HH:mm:ss",
"interpolate": "linear",
"nodata": "",
"dot": false,
"ymin": "",
"ymax": "",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "3600",
"cutout": 0,
"x": 500,
"y": 290,
"wires": [[], []]
},
{
"id": "broker-config",
"type": "mqtt-broker",
"name": "Local Mosquitto",
"broker": "mosquitto",
"port": "1883",
"clientid": "node-red",
"autoConnect": true
},
{
"id": "dashboard-group",
"type": "ui_group",
"name": "IoT Dashboard",
"tab": "dashboard-tab",
"order": 1,
"disp": true,
"width": "6"
},
{
"id": "dashboard-tab",
"type": "ui_tab",
"name": "Digital Twin",
"icon": "dashboard",
"order": 1
}
]
To import:
- In Node-RED, click menu → Import
- Paste the JSON
- Click Import
- Configure the MQTT broker connection
- Click Deploy
Checkpoint ✅
You should have:
- ✅ Node-RED flow deployed and running
- ✅ Dashboard showing real-time values
- ✅ Gauges updating as messages arrive
- ✅ Chart showing temperature history
- ✅ Messages being validated
If something’s wrong:
- Check Node-RED logs:
docker-compose logs node-red - Check MQTT connection in Node-RED (red dot = disconnected)
- Verify simulator is running and publishing
- Check topic matches:
iot/devices/+/telemetry
What’s Next?
In the final page, you’ll add alert rules (battery low, temperature high) and learn about production considerations. This completes your digital twin system!