Grafana for IoT Dashboard and Visualization
Grafana is the leading open-source platform for building IoT monitoring dashboards, capable of visualizing sensor data from InfluxDB, TimescaleDB, PostgreSQL, and other time-series backends with real-time graphs, alerting, and public dashboard sharing. This guide covers building IoT monitoring dashboards, configuring data sources, creating sensor visualizations, setting up alerts, and sharing dashboards.
Prerequisites
- Ubuntu 20.04/22.04 or CentOS 8/Rocky Linux 8+
- Root or sudo access
- InfluxDB 2.x or TimescaleDB/PostgreSQL with IoT data
- MQTT broker (Mosquitto) collecting sensor data
- Telegraf for data collection (optional)
Install Grafana
Ubuntu/Debian:
# Add Grafana repository
sudo apt install -y software-properties-common wget
wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key
echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" \
| sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
sudo systemctl enable grafana-server
sudo systemctl start grafana-server
CentOS/Rocky Linux:
cat > /etc/yum.repos.d/grafana.repo << 'EOF'
[grafana]
name=grafana
baseurl=https://rpm.grafana.com
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://rpm.grafana.com/gpg.key
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
EOF
sudo dnf install -y grafana
sudo systemctl enable --now grafana-server
Access Grafana at http://your-server:3000. Default credentials: admin / admin. Change the password immediately on first login.
Configure InfluxDB Data Source
# Via Grafana UI:
# Configuration > Data Sources > Add data source > InfluxDB
# For InfluxDB 2.x with Flux query language:
# Name: InfluxDB-IoT
# Query Language: Flux
# URL: http://localhost:8086
# Organization: myorg
# Token: your-influxdb-token
# Default Bucket: iot-sensors
# Test the connection, then save
Sample Flux queries for IoT panels:
// Current temperature per sensor
from(bucket: "iot-sensors")
|> range(start: -5m)
|> filter(fn: (r) => r._measurement == "temperature")
|> filter(fn: (r) => r._field == "value")
|> last()
// Temperature over time for all devices (time series panel)
from(bucket: "iot-sensors")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "temperature")
|> filter(fn: (r) => r._field == "value")
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|> yield(name: "mean_temp")
// Filter by variable (device_id from Grafana template variable)
from(bucket: "iot-sensors")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "temperature")
|> filter(fn: (r) => r.device_id == "${device_id}")
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
Configure TimescaleDB Data Source
For IoT data stored in TimescaleDB (PostgreSQL extension):
# Via Grafana UI:
# Configuration > Data Sources > Add data source > PostgreSQL
# Host: localhost:5432
# Database: iotdb
# User: grafana_reader
# Password: readonlypassword
# SSL Mode: disable (or require for production)
# TimescaleDB: ON (enables time_bucket functions)
-- Create a read-only user for Grafana
CREATE USER grafana_reader WITH PASSWORD 'readonlypassword';
GRANT CONNECT ON DATABASE iotdb TO grafana_reader;
GRANT USAGE ON SCHEMA public TO grafana_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO grafana_reader;
Sample TimescaleDB queries:
-- Time series panel: average temperature per 5-minute bucket
SELECT
time_bucket('5 minutes', time) AS time,
device_id,
avg(value) AS avg_temp
FROM sensor_readings
WHERE
$__timeFilter(time)
AND measurement = 'temperature'
AND device_id IN ($device_id)
GROUP BY 1, device_id
ORDER BY 1;
-- Stat panel: latest reading
SELECT value
FROM sensor_readings
WHERE measurement = 'temperature' AND device_id = '${device_id}'
ORDER BY time DESC LIMIT 1;
Build Sensor Dashboards
Create a comprehensive IoT sensor dashboard:
# Dashboard JSON (importable via Grafana UI > Import)
# Use this as a starting point, then customize in the UI
# Via UI: Dashboards > New Dashboard > Add Panel
# Panel types for IoT:
# - Time Series: temperature/humidity over time
# - Gauge: current value with min/max
# - Stat: single current value
# - Geomap: device locations
# - Table: all devices with latest values
# - Alert List: active sensor alerts
Recommended panel configurations:
Temperature Gauge:
- Panel type: Gauge
- Title: Room Temperature
- Unit: Celsius or Fahrenheit
- Min: 0, Max: 50
- Thresholds: Green (0-25), Yellow (25-35), Red (35+)
Humidity Time Series:
- Panel type: Time series
- Title: Humidity Last 24 Hours
- Unit: Percent (0-100)
- Fill opacity: 20
- Line width: 2
Battery Status Table:
// InfluxDB: Battery status for all devices
from(bucket: "iot-sensors")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "battery")
|> filter(fn: (r) => r._field == "percent")
|> group(columns: ["device_id"])
|> last()
|> map(fn: (r) => ({
r with
status: if r._value < 20 then "critical"
else if r._value < 50 then "low"
else "ok"
}))
Variable Templates
Template variables make dashboards reusable across devices and locations:
# In Dashboard Settings > Variables > New Variable
# Variable 1: device_id
# Name: device_id
# Label: Device
# Type: Query
# Data source: InfluxDB-IoT
# Query (Flux):
import "influxdata/influxdb/schema"
schema.tagValues(bucket: "iot-sensors", tag: "device_id")
# Variable 2: location
# Name: location
# Label: Location
# Type: Query (or Custom for static values)
# Custom values: living_room,bedroom,kitchen,garage
# Variable 3: interval (for time aggregation)
# Name: interval
# Label: Interval
# Type: Interval
# Values: 1m,5m,15m,1h,6h,1d
Use variables in panel queries:
// Use ${device_id} and ${interval} variables
from(bucket: "iot-sensors")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r.device_id == "${device_id}")
|> filter(fn: (r) => r.location == "${location}")
|> aggregateWindow(every: ${interval}, fn: mean, createEmpty: false)
Alerting Configuration
Set up alerts for sensor threshold violations:
# Via UI: Alerting > Alert rules > New alert rule
# Alert: High Temperature
# Name: High Room Temperature
# Rule type: Grafana managed alert
# Query A (condition):
# from(bucket: "iot-sensors")
# |> range(start: -5m)
# |> filter(fn: (r) => r._measurement == "temperature")
# |> mean()
# Condition:
# WHEN: last() IS ABOVE 30 (degrees)
# For: 5m (must be above threshold for 5 minutes)
# Create notification contact point first:
# Alerting > Contact points > New contact point
Configure a Slack notification contact point:
# Alerting > Contact points > Add contact point
# Type: Slack
# Webhook URL: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
# Message template:
# {{ define "slack.myorg.text" }}
# {{ range .Alerts }}
# *Alert:* {{ .Labels.alertname }}
# *Device:* {{ .Labels.device_id }}
# *Value:* {{ .Values.A | printf "%.1f" }}°C
# *Status:* {{ .Status }}
# {{ end }}
# {{ end }}
Email notification:
# Configure SMTP in grafana.ini
sudo nano /etc/grafana/grafana.ini
# [smtp]
# enabled = true
# host = smtp.sendgrid.net:587
# user = apikey
# password = your-sendgrid-api-key
# from_address = [email protected]
# from_name = Grafana IoT Alerts
# skip_verify = false
sudo systemctl restart grafana-server
# Add Email contact point via UI:
# Alerting > Contact points > Type: Email
# Addresses: [email protected],[email protected]
Real-Time Data Streaming
Configure Grafana for real-time streaming data:
# Enable live streaming in grafana.ini
sudo nano /etc/grafana/grafana.ini
# [live]
# max_connections = 100
# Use Grafana Live for real-time updates
# In panel query settings: set "Min interval" to match your data frequency
# For real-time MQTT data, use Grafana's WebSocket plugin or
# stream data via InfluxDB's built-in streaming:
# Configure Telegraf to write MQTT data to InfluxDB at 1-second intervals
cat > /etc/telegraf/telegraf.d/mqtt.conf << 'EOF'
[[inputs.mqtt_consumer]]
servers = ["tcp://localhost:1883"]
topics = ["home/+/+"]
username = "telegraf"
password = "telegrafpassword"
data_format = "json"
json_name_key = "measurement"
[[outputs.influxdb_v2]]
urls = ["http://localhost:8086"]
token = "your-influx-token"
organization = "myorg"
bucket = "iot-sensors"
EOF
sudo systemctl restart telegraf
Public Dashboard Sharing
Share dashboards publicly without requiring Grafana login:
# Enable public dashboards in grafana.ini
sudo nano /etc/grafana/grafana.ini
# [feature_toggles]
# publicDashboards = true
sudo systemctl restart grafana-server
Via UI:
- Open a dashboard
- Click the Share icon (top toolbar)
- Click Public dashboard tab
- Toggle Enable public access
- Copy the public URL
For embedding in a website:
<!-- Embed a specific Grafana panel -->
<iframe
src="https://grafana.example.com/d-solo/DASHBOARD_UID/iot?orgId=1&panelId=2&from=now-24h&to=now&theme=light"
width="800"
height="400"
frameborder="0">
</iframe>
<!-- For public dashboards (no auth required) -->
<iframe
src="https://grafana.example.com/public-dashboards/PUBLIC_DASHBOARD_UID"
width="100%"
height="600"
frameborder="0">
</iframe>
Troubleshooting
Data source connection error:
# Test InfluxDB connectivity from Grafana server
curl -X POST "http://localhost:8086/api/v2/query?org=myorg" \
-H "Authorization: Token your-token" \
-H "Content-Type: application/vnd.flux" \
--data 'from(bucket:"iot-sensors") |> range(start:-5m) |> last()'
# For PostgreSQL/TimescaleDB:
psql -h localhost -U grafana_reader -d iotdb -c "SELECT 1;"
Panels show "No data":
# Check time range matches when data was inserted
# Extend time range to "Last 24 hours" or "Last 7 days"
# Verify measurement names in Flux query
influx query --org myorg 'import "influxdata/influxdb/schema"
schema.measurements(bucket: "iot-sensors")'
# Check for time zone mismatches
# In Grafana: Dashboard Settings > Time zones > Set to server timezone
Alerts not firing:
# Check alert evaluation logs
sudo journalctl -u grafana-server -n 50 | grep -i alert
# Test notification contact point
# Alerting > Contact points > Test button
# Verify alert is in "Normal" state first
# Alerting > Alert rules > Check rule status
Conclusion
Grafana transforms raw IoT sensor data into actionable dashboards with minimal configuration. Pair it with InfluxDB or TimescaleDB for efficient time-series storage, configure template variables to make dashboards reusable across devices and locations, and set up alerts to be notified immediately when sensors exceed thresholds. Public dashboard sharing lets you expose read-only views to stakeholders without requiring Grafana accounts.


