Your MQTT broker is routing sensor data, and your MCU clients are publishing JSON payloads on a schedule. But data flowing through a broker is invisible unless you store it and visualize it. In this lesson you will build a complete MQTT-to-dashboard pipeline using Telegraf, InfluxDB, and Grafana (the TIG stack), then compare it with the managed dashboards on SiliconWit.io, and finally build a lightweight Chart.js alternative for resource-constrained gateways. #Dashboard #Grafana #DataViz
The MQTT-to-Dashboard Pipeline
What We Are Building
A real-time visualization system that subscribes to all sensor topics on your MQTT broker, stores every reading in a time-series database, and renders live dashboards with line charts, gauges, stat panels, tables, and alert indicators. No new hardware is needed. Everything runs on the same Linux machine (or Raspberry Pi) where your Mosquitto broker is already running from Lesson 2.
The pipeline has four stages, each handled by a dedicated tool:
Stage
Tool
Role
1. Message Broker
Mosquitto
Routes MQTT messages between devices
2. Data Collector
Telegraf
Subscribes to MQTT topics, parses JSON payloads, forwards structured metrics
3. Time-Series Database
InfluxDB 2.x
Stores measurements with timestamps, tags, and fields; handles retention and downsampling
4. Visualization
Grafana
Queries InfluxDB and renders interactive dashboards with panels, thresholds, and alerts
TIG Stack Data Flow
──────────────────────────────────────────
Sensor ──► Mosquitto ──► Telegraf ──► InfluxDB
(MQTT (broker) (MQTT sub (time-
publish) + parser) series DB)
│
│ Flux
│ query
▼
Grafana
(panels:
line chart,
gauge,
table,
alerts)
Each component is independent. If Grafana
goes down, data keeps flowing into InfluxDB.
Data flows in one direction: sensor publishes to Mosquitto, Telegraf consumes the message, writes it to InfluxDB, and Grafana queries InfluxDB on a refresh interval (typically every 5 seconds). Each component is independent. If Grafana goes down, data keeps flowing into InfluxDB. If Telegraf restarts, it reconnects to the broker and resumes ingestion.
Installing the TIG Stack
You can install all four components (Mosquitto + Telegraf + InfluxDB + Grafana) either through system packages or through Docker Compose. Docker is the recommended approach because it isolates each service, makes upgrades trivial, and produces the same environment on any Linux distribution.
Telegraf is the glue between your MQTT broker and InfluxDB. It subscribes to MQTT topics, parses the incoming JSON payloads, and writes structured measurements to InfluxDB. The entire behavior is controlled by a single configuration file.
# For topic "sensors/esp32-01/bme280", the measurement becomes "mqtt_consumer"
# and we extract tags from the topic path
[[inputs.mqtt_consumer.topic_parsing]]
topic = "sensors/+/+"
measurement = "measurement/_/_"
tags = "_/device/sensor_type"
[[inputs.mqtt_consumer.topic_parsing]]
topic = "home/+/+"
measurement = "measurement/_/_"
tags = "_/location/reading_type"
[[inputs.mqtt_consumer.topic_parsing]]
topic = "factory/+/+"
measurement = "measurement/_/_"
tags = "_/zone/metric"
After creating the config, restart the Telegraf container:
Restart Telegraf to load new config
dockercomposerestarttelegraf
Edit the Telegraf configuration file:
/etc/telegraf/telegraf.conf
# Global agent configuration
[agent]
interval = "10s"
round_interval = true
metric_batch_size = 1000
metric_buffer_limit = 10000
flush_interval = "10s"
flush_jitter = "0s"
# InfluxDB v2 output plugin
[[outputs.influxdb_v2]]
urls = ["http://localhost:8086"]
token = "my-super-secret-token"
organization = "siliconwit"
bucket = "iot_sensors"
# MQTT consumer input plugin
[[inputs.mqtt_consumer]]
servers = ["tcp://localhost:1883"]
topics = [
"sensors/#",
"home/#",
"factory/#"
]
qos = 1
connection_timeout = "30s"
persistent_session = false
client_id = "telegraf-subscriber"
# Parse JSON payloads
data_format = "json"
# Extract tags from MQTT topic hierarchy
[[inputs.mqtt_consumer.topic_parsing]]
topic = "sensors/+/+"
measurement = "measurement/_/_"
tags = "_/device/sensor_type"
[[inputs.mqtt_consumer.topic_parsing]]
topic = "home/+/+"
measurement = "measurement/_/_"
tags = "_/location/reading_type"
[[inputs.mqtt_consumer.topic_parsing]]
topic = "factory/+/+"
measurement = "measurement/_/_"
tags = "_/zone/metric"
Restart Telegraf:
Restart Telegraf
sudosystemctlrestarttelegraf
Understanding the Configuration
The configuration has three main sections:
Agent settings. The interval controls how often Telegraf collects data from its inputs. For MQTT this is less relevant because messages arrive asynchronously, but it affects other input plugins. The flush_interval controls how often Telegraf writes batched metrics to the output (InfluxDB).
Output plugin. The influxdb_v2 output sends data to InfluxDB 2.x. The token authenticates the connection. The organization and bucket specify where data lands. In Docker, the URL uses the container name (influxdb) instead of localhost because Docker Compose creates an internal network.
Input plugin. The mqtt_consumer subscribes to the specified topics with QoS 1. The data_format = "json" tells Telegraf to parse each MQTT payload as JSON. Every key in the JSON object becomes a field in InfluxDB.
Topic parsing. This is the most powerful part. Without topic parsing, every MQTT message lands in a single measurement called mqtt_consumer with no device or location tags. The topic_parsing blocks extract structured tags from the topic hierarchy. For example, a message on sensors/esp32-01/bme280 creates a measurement named sensors with tags device=esp32-01 and sensor_type=bme280. This makes it possible to filter and group data by device or sensor type in Grafana.
Verify Data Ingestion
Publish a test message from the command line and check that it arrives in InfluxDB:
Wait 10 seconds (one flush interval), then query InfluxDB:
Query InfluxDB for the test data
influxquery'
from(bucket: "iot_sensors")
|> range(start: -5m)
|> filter(fn: (r) => r["device"] == "esp32-test")
'--orgsiliconwit--tokenmy-super-secret-token
You should see one row with temperature, humidity, and pressure fields, tagged with device=esp32-test and sensor_type=bme280.
InfluxDB Fundamentals
Before building dashboards, you need to understand how InfluxDB organizes data. InfluxDB 2.x uses a different data model than traditional relational databases.
Core Concepts
Bucket. A bucket is where time-series data is stored. It is similar to a database in SQL terms. Each bucket has a retention policy that automatically deletes data older than a specified duration. Our iot_sensors bucket retains data for 30 days.
Measurement. A measurement is analogous to a table. Each measurement holds data points that share the same name. With our Telegraf topic parsing, the measurement name comes from the first segment of the MQTT topic (e.g., sensors, home, factory).
Tags. Tags are indexed key-value pairs used for filtering and grouping. They describe metadata about the data point: which device sent it, which location it came from, what type of sensor produced it. Tags are strings and are always indexed, making tag-based queries fast.
Fields. Fields hold the actual measured values: temperature, humidity, pressure, voltage. Fields are not indexed, which means filtering by field value requires scanning all matching data points. Fields can be floats, integers, strings, or booleans.
Timestamp. Every data point has a nanosecond-precision timestamp. If the MQTT payload does not include a timestamp, Telegraf stamps it with the current time when the message arrives.
Tags vs Fields: When to Use Each
Use Tags For
Use Fields For
Device ID (esp32-01)
Temperature (24.5)
Location (greenhouse)
Humidity (62.3)
Sensor type (bme280)
Pressure (1013.2)
Firmware version (v1.2)
Battery voltage (3.7)
Zone (zone-a)
RSSI signal strength (-67)
The rule of thumb: if you will filter or group by it, make it a tag. If you will plot or aggregate it, make it a field.
Retention Policies
Retention policies control how long data is kept. For IoT, a common pattern is to keep raw data for a short period and downsampled data for longer:
Bucket
Retention
Resolution
Purpose
iot_sensors
30 days
Raw (every message)
Recent detailed data
iot_sensors_weekly
1 year
5-minute averages
Medium-term trends
iot_sensors_archive
Forever
1-hour averages
Long-term historical analysis
You can create additional buckets and use InfluxDB tasks (scheduled Flux scripts) to downsample data automatically. We will set up a downsampling task later in this lesson.
Flux Query Language
InfluxDB 2.x uses Flux as its query language. Flux is a functional, pipe-based language where each step transforms the data and passes it to the next step.
Here is a basic query that retrieves the last hour of temperature data from a specific device:
The aggregateWindow function groups data points into 5-minute buckets and applies the mean function to each bucket. Setting createEmpty: false prevents empty windows from appearing in the output when no data was received during that interval.
Comparing Multiple Devices
To plot data from multiple devices on the same chart:
By removing the device filter, this query returns temperature data from all devices. Grafana automatically groups the results by the device tag and plots each device as a separate line on the chart.
Detecting Anomalies
A simple anomaly detection query that flags readings outside a normal range:
This returns only data points where the temperature is above 40 degrees or below 0 degrees. You can use this kind of query in Grafana alerts to trigger notifications when sensor readings leave the expected range.
Downsampling Task
Create a scheduled task that downsamples raw data into 5-minute averages:
This task runs every 10 minutes, reads the last 10 minutes of raw data, computes 5-minute averages, and writes the results to the iot_sensors_weekly bucket (which you would create with a 365-day retention policy).
Setting Up Grafana
Grafana is the visualization layer. It connects to InfluxDB, runs Flux queries, and renders the results as interactive panels on a dashboard.
Add InfluxDB as a Data Source
Open Grafana at http://localhost:3000 and log in (default: admin / admin). Grafana will prompt you to change the password on first login.
Navigate to Connections in the left sidebar, then click Data sources.
Click Add data source and select InfluxDB.
Configure the data source with these settings:
Setting
Value
Query Language
Flux
URL
http://influxdb:8086 (Docker) or http://localhost:8086 (apt)
Organization
siliconwit
Token
my-super-secret-token
Default Bucket
iot_sensors
Click Save & Test. You should see a green “Data source is working” confirmation.
Create a Dashboard
Click the + icon in the left sidebar and select New dashboard.
Click Add visualization to create your first panel.
In the query editor at the bottom, select the InfluxDB data source you just configured.
Switch to the Script Editor (click the pencil icon) and paste a Flux query.
Click Run query to preview the data, then adjust the panel settings in the right sidebar.
Click Apply to add the panel to the dashboard.
Repeat for each additional panel. Arrange panels by dragging and resizing.
Click the Save icon (disk icon) in the top toolbar and name your dashboard “IoT Sensor Dashboard”.
Auto-Refresh
Click the refresh interval dropdown in the top-right corner of the dashboard and select 5s for a near-real-time experience. Grafana will re-run all panel queries every 5 seconds.
Building Dashboard Panels
A well-designed IoT dashboard uses different panel types for different purposes. Here are the five most useful panel types for sensor data.
1. Time Series (Line Chart)
The most common panel type for IoT. It plots values over time, with one line per device or sensor.
Use case: Temperature, humidity, and pressure over the last 6 hours.
Panel query: Temperature over time, grouped by device
The variables v.timeRangeStart, v.timeRangeStop, and v.windowPeriod are provided by Grafana and automatically adjust when you change the dashboard time range or zoom into a section of the chart.
Panel settings:
Title: “Temperature Over Time”
Unit: Celsius (under Standard options, set Unit to “Temperature / Celsius”)
Thresholds: Add a red threshold at 40 to highlight overheating
Legend: Show as table, display Min, Max, and Last values
2. Gauge Panel
A gauge shows a single current value against a defined range. Good for readings that have well-known limits.
Use case: Current humidity level from one sensor.
Panel query: Latest humidity from a specific device
The pivot function transforms the data so that each field (temperature, humidity, pressure) becomes its own column instead of separate rows. This produces a clean table with one row per timestamp.
Panel settings:
Title: “Recent Readings”
Column widths: Auto
Cell display: Color text for temperature column (with thresholds)
5. Alert List Panel
An alert list panel shows the current state of all configured alerts. It provides a summary view of which alerts are firing, pending, or normal.
Panel settings:
Title: “Active Alerts”
Show: Current alerts
State filter: Alerting, Pending
Sort by: Time (newest first)
We will configure the actual alert rules in Lesson 6. For now, you can add this panel as a placeholder so the dashboard layout is ready.
Example Dashboard Layout
Here is a recommended layout for an IoT sensor dashboard. Arrange the panels in a grid:
Row
Left Panel
Center Panel
Right Panel
1
Stat: Temperature Now
Stat: Humidity Now
Stat: Pressure Now
2
Time Series: Temperature (full width, spanning all three columns)
3
Gauge: Humidity
Gauge: Battery
Alert List
4
Table: Recent Readings (full width)
To make a panel span the full width, drag its right edge to the edge of the dashboard. Grafana uses a 24-column grid, so a full-width panel is 24 units wide.
Adding Threshold Lines to Time Series
Threshold lines on a time series chart make it immediately obvious when values enter a danger zone:
Edit the Temperature time series panel.
In the right sidebar, scroll down to Thresholds.
Add a threshold at 40 with color Red and label “Max Safe”.
Add a threshold at 5 with color Blue and label “Min Safe”.
Under Threshold display, select As lines (instead of the default “As filled regions”).
Click Apply.
The chart now shows horizontal lines at 40 and 5 degrees. Any data points above or below these lines stand out visually.
Device Grouping with Template Variables
When you have multiple devices, you do not want separate dashboards for each one. Instead, use a Grafana template variable to create a device selector dropdown:
Open the dashboard settings (gear icon in the top toolbar).
Click Variables in the left menu.
Click Add variable and configure it:
Setting
Value
Name
device
Type
Query
Data source
InfluxDB
Query
import "influxdata/influxdb/schema" then schema.tagValues(bucket: "iot_sensors", tag: "device")
Multi-value
Enabled
Include All option
Enabled
Click Apply.
Now update your panel queries to use the variable:
A dropdown appears at the top of the dashboard. Selecting a device filters all panels instantly.
SiliconWit.io Dashboard Comparison
The TIG stack gives you full control, but it requires a Linux server, Docker, and ongoing maintenance. For projects where you want dashboards without managing infrastructure, SiliconWit.io provides a managed alternative.
How SiliconWit.io Works
Your MQTT clients connect to SiliconWit.io’s MQTT endpoint (or your own broker with cloud forwarding configured), and the platform handles storage, visualization, and alerting automatically. There is no Telegraf or InfluxDB to install.
Feature
Self-Hosted TIG
SiliconWit.io
Setup time
30+ minutes
Under 5 minutes
Server required
Yes (Linux machine or RPi)
No
Storage
You manage InfluxDB
Managed by platform
Visualization
Grafana (full customization)
Built-in live charts, tables, reports
Data export
Flux queries, CSV, API
CSV download, API queries, scheduled reports
Alerts
Configure in Lesson 6
Built-in threshold alerts
Cost
Free (self-hosted hardware cost)
Free tier for 3 devices
Customization
Unlimited
Predefined panel types
Maintenance
OS updates, backups, disk space
None
Viewing Data on SiliconWit.io
If you already created a SiliconWit.io account (from the Getting Started section of the course index), your sensor data is already flowing to the platform if your MCU clients are configured to publish to SiliconWit.io’s MQTT endpoint.
The SiliconWit.io dashboard provides:
Live charts that update in real time as new sensor data arrives
Data tables showing individual readings with timestamps and device metadata
Exportable reports in CSV format for analysis in spreadsheets or Python
Device overview showing online/offline status for all connected devices
Historical data browsing with adjustable time ranges
When to Use Each Approach
Use SiliconWit.io when:
You want dashboards running in under 5 minutes
You do not have a dedicated Linux server
You are prototyping or building a small deployment (under 10 devices)
You want built-in alerting without configuring Grafana alert rules
Use the self-hosted TIG stack when:
You need full control over data storage and retention
You have specific compliance or data sovereignty requirements
You want custom Grafana plugins or advanced visualization
You are building a large-scale deployment with dozens of devices
You need to integrate with internal systems that cannot reach the public internet
You can also use both: self-hosted Grafana for detailed engineering analysis and SiliconWit.io for stakeholder-facing dashboards that require no maintenance.
Lightweight Dashboard with Chart.js
Not every IoT gateway has the resources to run Grafana. A Raspberry Pi Zero or an OpenWrt router can serve a simple web dashboard using Chart.js, a lightweight JavaScript charting library that runs entirely in the browser. This is useful for local monitoring when you need a quick visual without cloud connectivity.
console.error("Failed to parse MQTT message:", err);
}
});
client.on("error", (err)=> {
console.error("MQTT connection error:", err);
});
Enabling WebSocket on Mosquitto
The browser MQTT client connects over WebSocket, so Mosquitto needs a WebSocket listener. Add this to your Mosquitto configuration:
Add to mosquitto.conf
listener 9001
protocol websockets
If you are using the Docker Compose setup from earlier, the port mapping 9001:9001 is already included. Just add the WebSocket listener to the config file and restart Mosquitto:
Restart Mosquitto
dockercomposerestartmosquitto
Serve the Dashboard
You can serve the static files with any HTTP server. Python’s built-in server works for development:
Serve the Chart.js dashboard
cdiot-dashboard
python3-mhttp.server8080
Open http://localhost:8080 in a browser. As MQTT messages arrive, the stat cards update and the charts scroll with live data. This entire dashboard is three files totaling under 5 KB (excluding the CDN-loaded libraries).
Data Export
Dashboards are great for real-time monitoring, but you often need to extract data for analysis, reporting, or archival purposes.
CSV Export from Grafana
Grafana can export any panel’s data as CSV:
Hover over a panel and click the three-dot menu icon.
Select Inspect then Data.
Click Download CSV.
The exported CSV includes timestamps, tags, and field values for the queried time range.
CSV Export via InfluxDB CLI
For automated or scripted exports, use the InfluxDB CLI with the --raw flag to get CSV output:
Building a dashboard that is actually useful (not just pretty) requires attention to design principles. Here are the practices that matter most for IoT dashboards.
Use Meaningful Colors
Colors should communicate status, not decoration. Adopt a consistent color scheme:
Color
Meaning
Example
Green
Normal, within range
Temperature 20-35 C
Yellow/Orange
Warning, approaching limit
Temperature 35-40 C
Red
Critical, out of range
Temperature above 40 C
Blue
Information, cool/low values
Temperature below 10 C
Gray
No data, unknown state
Device offline
Apply the same color mapping across all panels. If red means “above threshold” on one panel, it should not mean “below threshold” on another.
Add Threshold Lines
Threshold lines on time series charts provide visual context. A temperature reading of 38 C means nothing without knowing whether the acceptable range is 0 to 100 or 20 to 40. Add threshold lines at the boundaries of your acceptable range so operators can instantly see if values are trending toward a problem.
Group by Device or Location
Organize panels by physical grouping, not by metric type. An operator monitoring a greenhouse cares about “Greenhouse A: temperature, humidity, soil moisture” together, not “All temperatures from all locations” on one panel. Use Grafana rows or separate dashboard pages for each logical group.
Keep It Responsive
Grafana dashboards should work on tablets and phones for on-the-go monitoring:
Use stat and gauge panels (which adapt to screen size) instead of wide tables for the primary view
Set column counts in panel options to auto-wrap on smaller screens
Test your dashboard at 768px width (standard tablet) to verify readability
Use the Grafana kiosk mode (?kiosk) for wall-mounted displays
Avoid Dashboard Overload
A dashboard with 30 panels showing every possible metric is worse than useless because nobody reads it. Follow the “3 to 7 panels” rule for any single dashboard page:
Overview dashboard: 3 to 5 stat panels showing system health at a glance
Device detail dashboard: 5 to 7 panels for a single device (accessed by clicking through from the overview)
Diagnostic dashboard: Tables and raw queries for troubleshooting (not for daily monitoring)
Link dashboards together using Grafana’s drill-down links so operators can go from a high-level overview to device details in one click.
Putting It All Together
Let us trace a single sensor reading through the entire pipeline to verify everything works end to end.
Publish a test message from the command line (or let your ESP32 client send one):
Check Telegraf logs to confirm the message was consumed:
Terminal window
dockerlogstelegraf--tail5
You should see a line indicating a metric was written.
Query InfluxDB to confirm the data is stored:
Terminal window
influxquery'
from(bucket: "iot_sensors")
|> range(start: -1m)
|> filter(fn: (r) => r["device"] == "esp32-01")
'--orgsiliconwit--tokenmy-super-secret-token
Check Grafana. Open your dashboard in the browser. The time series panel should show a new data point, the stat panel should display 27.3 C, and the gauge should show 58.1%.
Publish a few more readings at intervals to build up chart data:
Verify the Chart.js dashboard by opening http://localhost:8080. It should show the same data updating in real time via the WebSocket MQTT connection.
Exercises
Build a multi-device dashboard. Set up the TIG stack and create a Grafana dashboard with a template variable for device selection. Publish data from at least two simulated devices (use mosquitto_pub with different device names in the topic, e.g., sensors/esp32-01/bme280 and sensors/esp32-02/bme280). Create a time series panel that shows temperature from the selected device, a gauge showing the latest humidity, and a table with the last 10 readings. Verify that switching the device dropdown updates all panels.
Implement a downsampling pipeline. Create a second InfluxDB bucket called iot_sensors_weekly with a 365-day retention policy. Write an InfluxDB task (using the Flux task syntax shown in this lesson) that runs every 10 minutes, computes 5-minute averages of temperature and humidity, and writes the results to the weekly bucket. Add a second time series panel to your Grafana dashboard that queries the weekly bucket and shows 7-day trends. Compare the detail level between the raw and downsampled data.
Build the Chart.js dashboard and add a pressure chart. Set up the Chart.js dashboard from this lesson and extend it with a third chart for pressure data. Add color-coded stat cards that turn red when temperature exceeds 40 C or humidity drops below 20%. Implement a “last updated” timestamp display that shows how many seconds ago the last MQTT message arrived. Serve the dashboard from a Raspberry Pi and verify it works on a phone browser over your local network.
Compare Grafana and SiliconWit.io side by side. Configure your ESP32 (or simulated publisher) to publish to both your local Mosquitto broker and SiliconWit.io’s MQTT endpoint simultaneously. Open both dashboards side by side: your self-hosted Grafana and the SiliconWit.io web interface. Document the differences in setup time, data latency, available panel types, and export options. Write a brief comparison table summarizing when you would choose each approach for a real project.
Summary
You built a complete MQTT-to-dashboard pipeline using four tools: Mosquitto as the message broker, Telegraf as the MQTT consumer and data router, InfluxDB as the time-series database, and Grafana as the visualization layer. You learned how Telegraf’s topic parsing extracts device and sensor tags from MQTT topic hierarchies, making it possible to filter and group data in queries. You explored InfluxDB’s data model (buckets, measurements, tags, fields) and wrote Flux queries to filter by device, aggregate by time window, compare multiple devices, and detect anomalies. In Grafana, you created five panel types (time series, gauge, stat, table, and alert list), configured threshold lines for visual context, and added template variables for device selection. You compared the self-hosted TIG stack with the managed SiliconWit.io dashboard and identified when each approach is the better choice. You built a lightweight Chart.js alternative that runs in any browser and connects to MQTT over WebSocket, suitable for resource-constrained gateways. Finally, you explored data export options including CSV download, CLI export, REST API queries, Python client integration, and scheduled report automation.
Comments