raspberry-pi intermediate 45 min

Raspberry Pi: time-series dashboards with InfluxDB + Grafana

The self-hosted dashboard stack: InfluxDB stores sensor time series, Grafana graphs them. Install, wire an ESP32's MQTT feed, retention included.

Code available for: Python
Published Sep 22, 2026

The SQLite pattern stores data; the question it cannot answer well is “show me a graph I can leave on a tablet in the kitchen.” That is the InfluxDB + Grafana stack: a time-series database that eats sensor data all day, and a dashboard tool that graphs anything in it, self-hosted on your Pi, no cloud account.

This tutorial installs both, wires an ESP32’s MQTT readings in, and sets retention so the Pi’s SD card survives the year.

What you need

  • Raspberry Pi 4 (2 GB min; a Pi 3 runs it but is the floor)
  • 32 GB SD card (time series add up)
  • An ESP32 publishing sensor readings over MQTT (the MQTT tutorial’s home/+/sensor topics are the feed here)

Install

# InfluxDB 2.x via the official repo
curl -s https://repos.influxdata.com/influxdata-archive.key | sudo gpg --dearmor -o /usr/share/keyrings/influxdata.gpg
echo "deb [signed-by=/usr/share/influxdata.gpg] https://repos.influxdata.com/debian stable main" | sudo tee /etc/apt/sources.list.d/influxdata.list
sudo apt update && sudo apt install influxdb2 -y
sudo systemctl enable --now influxdb

# Grafana
sudo apt install -y apt-transport-https
curl -s https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update && sudo apt install grafana -y
sudo systemctl enable --now grafana-server

Ports after install: Influx UI at 8086, Grafana at 3000.

Setup walkthrough

InfluxDB (one time): open http://pi-ip:8086, Get Started, create user + org + a bucket named home, save the API token it offers.

Grafana: open http://pi-ip:3000, login admin/admin, change it, then Connections >> Add connection >> InfluxDB:

FieldValue
Query languageFlux
URLhttp://localhost:8086
Orgyours
Tokenthe one you saved
Default buckethome

Getting the ESP32 data in

Two routes, and the choice matters:

Route A: MQTT bridge (recommended). A small Python service subscribes to your MQTT broker and writes into Influx. The ESP32 code does not change at all:

# ~/mqtt2influx/bridge.py
import json, time
import paho.mqtt.client as mqtt
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS

writer = InfluxDBClient(url="http://localhost:8086", token="YOUR_TOKEN",
                        org="home").write_api(write_options=SYNCHRONOUS)

def on_message(client, userdata, msg):
    # topic home/kitchen/sensor, payload {"temp":22.5,"hum":45.2}
    room = msg.topic.split("/")[1]
    try:
        data = json.loads(msg.payload)
        p = Point("sensor").tag("room", room).field("temp", float(data["temp"]))
        writer.write(bucket="home", record=p)
    except (ValueError, KeyError):
        pass   # malformed payload: skip it, never crash the bridge

client = mqtt.Client()
client.on_message = on_message
client.connect("localhost", 1883)
client.subscribe("home/+/sensor")
client.loop_forever()

Run it under systemd (same service pattern as the Flask tutorial). One process, forever, and every ESP32 in the house lands in the same bucket automatically.

Route B: ESP32 writes HTTP directly. The ESP32 POSTs line protocol to Influx’s write API. Fine for one device; the bridge wins as soon as you have two (e.g. one place to change credentials).

The dashboard

Grafana >> Dashboards >> New:

  1. Add visualization >> InfluxDB as source.
  2. Flux query: from(bucket: "home") |> range(start: -24h) |> filter(fn: (r) => r._measurement == "sensor" and r._field == "temp")
  3. Graph appears. Group by the room tag and you get one line per room automatically.
  4. Panel >> repeat by room, if you want a grid instead of one stacked chart.

Time range picker top right (last 6 h, last 7 d) works on every panel at once (e.g. the “is it always this cold in the office” question is one range change).

Retention, before the SD card fills

Raw readings at 30 s/room is 1.4 M rows/month. Influx solves this natively with retention policies: keep raw for 30 days, downsampled hourly averages for 2 years. In the UI: Data >> Tasks >> Create:

option task = {name: "hourly", every: 1h}
from(bucket: "home")
  |> range(start: -task.every)
  |> aggregateWindow(every: 1h, fn: mean)
  |> set(key: "_measurement", value: "sensor_hourly")
  |> to(bucket: "home")

Then set the home bucket retention to 30d and let the hourly task carry history. The Pi’s card stays at a few GB forever (e.g. same pattern Grafana’s own demo dashboards assume).

What you learned

  • InfluxDB stores, Grafana graphs, MQTT bridge feeds, all self-hosted.
  • One bridge process makes every MQTT-pubbing device appear in dashboards with zero per-device work.
  • Retention + downsampling keeps household hardware honest.

When something breaks

  • Grafana cannot reach InfluxDB: the token, not the URL, usually. Tokens expire if you rotated them; re-paste from Data >> Tokens.
  • No data in panels: bridge service dead (systemctl status mqtt2influx). Then check the ESP32’s topic against the bridge’s subscription (e.g. home/+/sensor vs home/kitchen/sensor).
  • Pi grinds to a halt after weeks: no downsample task, card thrashing. Add the hourly task, set raw retention to 30 d.
  • Docker vs apt mixup: two InfluxDBs can end up installed via different routes and fight over 8086. Pick one install path (the apt route above is the durable one on Raspberry Pi OS).

What to build next

  • The SQLite tutorial is the lighter sibling (SQL answers, no dashboards).
  • The MQTT tutorial is the sensor side feeding this stack.
  • Put Grafana behind the Wireguard tunnel for access from anywhere without exposing it.