How to Pipe MQTT Telemetry from Embedded Devices into Grafana with InfluxDB

Dashboard monitoring screens displaying real-time MQTT telemetry data flowing from embedded IoT devices into Grafana

You’ve spent weeks getting your sensor readings perfect—calibrated ADCs, filtered noise, rock-solid MQTT publishes every five seconds. Then someone asks, “What does the data actually look like over time?” And you realize: you have no idea. Your observability strategy is a serial terminal and mosquitto_sub running in a tmux pane that somebody will eventually close.

The gap between “my device publishes telemetry” and “I can see what my fleet is doing” is surprisingly narrow. The pipeline is four components, the cloud free tiers cover prototyping, and you can have a live dashboard in under an hour. Here’s exactly how to wire MQTT telemetry into Grafana with InfluxDB, from payload format to Flux query.

┌─────────────────┐      MQTT       ┌──────────────┐
│  Embedded Device│───────────────►  │  MQTT Broker │
│  (ESP32 / STM32)│   QoS 1, TLS   │  (Mosquitto / │
└─────────────────┘                  │   HiveMQ)    │
                                     └──────┬───────┘
                                            │ subscribe
                                            ▼
                                     ┌──────────────┐
                                     │   Telegraf    │
                                     │ (mqtt_consumer│
                                     │  + influxdb_v2│
                                     │   output)     │
                                     └──────┬───────┘
                                            │ HTTP/TLS
                                            ▼
                                     ┌──────────────┐
                                     │InfluxDB Cloud │
                                     │  (bucket:     │
                                     │  "iot_telem") │
                                     └──────┬───────┘
                                            │ Flux query
                                            ▼
                                     ┌──────────────┐
                                     │ Grafana Cloud │
                                     │  (dashboard)  │
                                     └──────────────┘

Why Telegraf Is the Piece You’re Missing

You already know the broker. You’ve probably heard of InfluxDB and Grafana. The component that’s likely new is Telegraf, and it’s the glue that eliminates custom code between your broker and your database.

Telegraf is InfluxData’s open-source metrics agent. Its inputs.mqtt_consumer plugin subscribes to your broker topics, parses payloads, and its outputs.influxdb_v2 plugin writes the data to InfluxDB in batches over HTTP. It handles format translation, buffering, and retries. The alternative is a custom Python script with paho-mqtt and the InfluxDB client library. Fine for a hackathon, miserable to maintain.

We’re using cloud-hosted InfluxDB and Grafana here. No VMs, no Docker Compose files, no “I’ll set up proper backups later.” Both offer free tiers that handle tens of thousands of data points per day, plenty for prototyping or a small fleet.

Prerequisites Checklist

Before you start, have these ready:

  • MQTT broker: Mosquitto running locally, or a cloud broker like HiveMQ Cloud or EMQX Cloud (see our guide on [choosing an MQTT broker])
  • InfluxDB Cloud account: free tier at cloud2.influxdata.com
  • Grafana Cloud account: free tier at grafana.com
  • A Linux or macOS machine to run Telegraf (this can be a Raspberry Pi, your laptop, or a small VPS)
  • mosquitto_pub installed for testing (apt install mosquitto-clients or brew install mosquitto)

Step 1: Structure Your MQTT Payload for Clean Ingestion

Payload format is where most MQTT-to-InfluxDB-to-Grafana pipelines silently break. Telegraf can parse JSON, but it needs flat, consistent keys to map them into InfluxDB’s data model correctly.

BAD (nested, no timestamp, no device ID):
─────────────────────────────────────────
{
  "data": {
    "sensors": {
      "temp": 22.4,
      "hum": 61
    }
  }
}

GOOD (flat, explicit device_id & timestamp):
─────────────────────────────────────────
{
  "device_id": "esp32-A1B2",
  "timestamp": 1718032000,
  "temp_c": 22.4,
  "humidity_pct": 61.2,
  "batt_v": 3.72
}

The nested version requires custom parsing. The flat version maps directly into InfluxDB with zero transformation. Here’s how each key lands:

┌────────────────┬───────────┬─────────────────────────┐
│ JSON Key       │ InfluxDB  │ Why                     │
├────────────────┼───────────┼─────────────────────────┤
│ device_id      │ Tag       │ Indexed; used to filter │
│ temp_c         │ Field     │ Numeric measurement     │
│ humidity_pct   │ Field     │ Numeric measurement     │
│ batt_v         │ Field     │ Numeric measurement     │
│ timestamp      │ Timestamp │ Point's time value      │
└────────────────┴───────────┴─────────────────────────┘

Tags are indexed strings you filter and group by (WHERE device_id = "esp32-A1B2"). Fields are the numeric values you actually chart. Confusing the two is the number-one InfluxDB beginner mistake: storing high-cardinality values as tags will bloat your index and eventually tank query performance.

Step 2: Configure Telegraf as the Bridge

Install Telegraf on your Linux/macOS machine:

# Ubuntu/Debian
curl -s https://repos.influxdata.com/influxdata-archive.key | sudo apt-key add -
echo "deb https://repos.influxdata.com/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/influxdata.list
sudo apt update && sudo apt install telegraf

# macOS
brew install telegraf

Replace the default config (or create a minimal one). Here’s a complete, annotated telegraf.conf:

# ── Input: Subscribe to MQTT topics ──────────────────────
[[inputs.mqtt_consumer]]
  servers = ["tcp://your-broker-address:1883"]      # Use ssl:// for TLS
  topics  = ["telemetry/+/sensors"]                 # + wildcard matches any device_id
  qos     = 1
  data_format = "json"

  # Map device_id to an InfluxDB tag (indexed, filterable)
  tag_keys = ["device_id"]

  # Parse the timestamp from the JSON payload
  json_time_key    = "timestamp"
  json_time_format = "unix"                         # epoch seconds

  # The InfluxDB measurement name (like a table)
  name_override = "sensor_data"

# ── Output: Write to InfluxDB Cloud ─────────────────────
[[outputs.influxdb_v2]]
  urls         = ["https://us-east-1-1.aws.cloud2.influxdata.com"]  # Your region URL
  token        = "$INFLUX_TOKEN"                    # Use env var, not plaintext
  organization = "your-org"
  bucket       = "iot_telem"

A few things to note:

  • The + in telemetry/+/sensors is an MQTT single-level wildcard. It matches telemetry/esp32-A1B2/sensors, telemetry/stm32-C3D4/sensors, etc.
  • tag_keys tells Telegraf which JSON keys become InfluxDB tags. Everything else numeric becomes a field automatically.
  • json_time_key pulls the timestamp from your payload rather than using Telegraf’s receive time. This matters when devices have intermittent connectivity.

Test before running as a service:

export INFLUX_TOKEN="your-token-here"
telegraf --config telegraf.conf --test

Then, in another terminal, fire a test message:

mosquitto_pub -h your-broker-address -t "telemetry/test-device/sensors" \
  -m '{"device_id":"test-device","timestamp":1718032000,"temp_c":22.4,"humidity_pct":61.2,"batt_v":3.72}'

If --test prints the parsed metric, you’re good. Start Telegraf as a service:

sudo systemctl enable --now telegraf

Step 3: Verify Data Landed in InfluxDB Cloud

Log into your InfluxDB Cloud console and open Data Explorer. Select your iot_telem bucket, the sensor_data measurement, and the temp_c field. You should see your test point.

For a quick programmatic check, run this Flux query in the Script Editor:

from(bucket: "iot_telem")
  |> range(start: -1h)
  |> filter(fn: (r) => r._measurement == "sensor_data")
  |> filter(fn: (r) => r.device_id == "test-device")
  |> limit(n: 5)

If you see nothing, check these common gotchas:

  • Wrong token scope. Your API token needs write permission to the iot_telem bucket.
  • Bucket name mismatch. The bucket in telegraf.conf must exactly match what you created in the InfluxDB UI.
  • Timestamp in the future. If your device clock is wrong, the data may exist outside your query’s time range. Widen range(start: -30d) to check.

Step 4: Build Your Embedded MQTT Dashboard in Grafana Cloud

In Grafana Cloud, go to Connections → Data Sources → Add data source and select InfluxDB. Set the query language to Flux, enter your InfluxDB Cloud URL, organization, token, and default bucket.

Temperature Line Chart

Create a new dashboard, add a panel, and paste this Flux query:

from(bucket: "iot_telem")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r._measurement == "sensor_data")
  |> filter(fn: (r) => r._field == "temp_c")
  |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)

Set visualization to Time series. You now have a live temperature chart.

Battery Voltage Gauge

Add another panel. Same query structure, but filter on r._field == "batt_v", set visualization to Gauge, and configure thresholds (green above 3.3V, yellow 3.0–3.3V, red below 3.0V).

Multi-Device Filtering

Add a dashboard variable: Settings → Variables → New. Name it device, set type to Query, and use:

import "influxdata/influxdb/schema"
schema.tagValues(bucket: "iot_telem", tag: "device_id")

Then add |> filter(fn: (r) => r.device_id == "${device}") to each panel query. You’ll get a dropdown to switch between devices.

Set dashboard auto-refresh to 10s and you have a real-time embedded MQTT dashboard.

Firmware-Side Code to Complete the Loop

Here’s a minimal ESP-IDF/Arduino-style example showing the publish side. Map each JSON key back to the Telegraf config from Step 2:

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

#define MQTT_BROKER  "your-broker-address"
#define MQTT_PORT    1883
#define DEVICE_ID    "esp32-A1B2"
#define PUB_TOPIC    "telemetry/" DEVICE_ID "/sensors"   // matches Telegraf's telemetry/+/sensors

WiFiClient   wifiClient;
PubSubClient mqtt(wifiClient);

void publishTelemetry(float temp, float humidity, float batt) {
    StaticJsonDocument<128> doc;
    doc["device_id"]     = DEVICE_ID;        // → InfluxDB tag (via tag_keys)
    doc["timestamp"]     = (long)time(NULL); // → InfluxDB timestamp (via json_time_key)
    doc["temp_c"]        = temp;             // → InfluxDB field
    doc["humidity_pct"]  = humidity;         // → InfluxDB field
    doc["batt_v"]        = batt;            // → InfluxDB field

    char payload[128];
    serializeJson(doc, payload);

    mqtt.publish(PUB_TOPIC, payload, /* retained */ false);
}

void setup() {
    WiFi.begin("SSID", "password");
    mqtt.setServer(MQTT_BROKER, MQTT_PORT);
    mqtt.connect(DEVICE_ID);
}

void loop() {
    mqtt.loop();
    float temp = readTempSensor();
    float hum  = readHumiditySensor();
    float batt = readBattVoltage();

    publishTelemetry(temp, hum, batt);
    delay(5000);  // 5-second interval
}

Make sure time(NULL) returns a valid epoch. If your device doesn’t have NTP synced, omit the timestamp field and remove json_time_key from telegraf.conf. Telegraf will use its own receive time instead.

Hardening This for Production

This stack works for prototyping as-is. For a production fleet, address these:

  • [MQTT QoS levels]: Use QoS 1 for telemetry that matters. QoS 0 is fine for high-frequency data where losing a point is acceptable.
  • TLS everywhere: Enable TLS on your broker connection and use ssl:// in Telegraf’s servers config. HiveMQ Cloud and EMQX Cloud enforce this by default.
  • Telegraf buffering: Tune metric_batch_size (default 1000) and metric_buffer_limit (default 10000) in the [agent] section if you have bursty traffic or intermittent cloud connectivity.
  • InfluxDB retention: Set a retention policy on your bucket (e.g., 30 days) so free-tier storage doesn’t fill up silently.
  • [Grafana alerting]: Add alert rules for battery voltage drops or device silence (no data for 5 minutes). This turns your dashboard from a screen you look at into a system that looks out for you.

What to Build Next

You now have a working end-to-end pipeline: device → broker → Telegraf → InfluxDB Cloud → Grafana Cloud. Every new device you add only needs to publish flat JSON to the right topic. Telegraf, InfluxDB, and Grafana handle the rest automatically via the wildcard subscription and the device_id tag.

From here, the highest-value next steps: add three more devices and use Grafana’s variable dropdown to flip between them. Set up a “device offline” alert. Explore Flux’s movingAverage() and derivative() functions to detect trends your raw data doesn’t reveal. And when you’re ready to think about scaling beyond prototyping, look at how this pipeline fits into a broader [IoT network architecture].

The whole point of building firmware is to make hardware do something useful. Now you can actually see it doing it.


Hubble Network connects Bluetooth devices directly to satellite — no gateways, no terrestrial infrastructure. See how it works →