Gino Eising
Gino Eising
Nerd by Nature
May 2, 2024 4 min read

Your IoT sensors deserve proper observability: MQTT to Prometheus with a Helm chart

thumbnail for this post

May 2024 — because your home produces data worth keeping

Smart home devices generate a constant stream of data. Temperature readings, power consumption, motion events, CO₂ levels, humidity. Most of it disappears into a cloud service or gets forgotten after being displayed on a dashboard for a few seconds.

I wanted that data in Prometheus. Not because Prometheus is trendy, but because it means: Grafana dashboards with real history, alerting when something is wrong, the same tooling I use for everything else in the cluster applied to the physical environment. One observability stack to rule them all.

The bridge between “smart home devices” and “Prometheus metrics” is MQTT + mqtt2prometheus.


The architecture

IoT devices / sensors
    │
    ↓ MQTT publish
MQTT broker (Mosquitto, on your network)
    │
    ↓ subscribe
mqtt2prometheus (one per device class / topic pattern)
    │
    ↓ /metrics endpoint
Prometheus scrape
    │
    ↓ query
Grafana dashboard

mqtt2prometheus is a Go binary that subscribes to MQTT topics and exposes the values as Prometheus metrics. It’s simple and solid. Each instance handles one device class — you configure the topic pattern, how to parse the payload, and what to call the metric.

The deployment complexity is: you might have several of these, each with different topic patterns and metric names, all needing a Prometheus ServiceMonitor. That’s where a Helm chart helps.


What the Helm chart does

Rather than deploying and managing multiple mqtt2prometheus instances by hand, the chart takes a values.yaml with your exporter configurations and generates:

  • A Deployment for each exporter (one pod per device class)
  • A ConfigMap with the mqtt2prometheus configuration
  • A Service exposing the /metrics endpoint
  • A ServiceMonitor telling Prometheus where to scrape

One helm upgrade --install to bring up all your exporters at once.

Installing the chart

helm repo add djieno https://helm.djieno.com
helm repo update
helm upgrade --install homekit --values myvalues.yaml djieno/mqtt2prometheus

A values.yaml example

exporters:
  - name: homekit-environment
    mqttBroker: "tcp://mosquitto.home.svc.cluster.local:1883"
    topicPattern: "homekit/sensors/+/environment"
    metrics:
      - mqttName: temperature
        prometheusName: home_temperature_celsius
        type: gauge
        help: "Room temperature in Celsius"
      - mqttName: humidity
        prometheusName: home_humidity_percent
        type: gauge
        help: "Relative humidity percentage"

  - name: homekit-power
    mqttBroker: "tcp://mosquitto.home.svc.cluster.local:1883"
    topicPattern: "homekit/devices/+/power"
    metrics:
      - mqttName: watts
        prometheusName: home_device_power_watts
        type: gauge
        help: "Device power consumption in watts"

service:
  port: 9641
  type: ClusterIP

serviceMonitor:
  enabled: true
  namespace: monitoring
  interval: 30s

This creates two mqtt2prometheus deployments — one for environment sensors, one for power monitoring — each with its own Service and ServiceMonitor.


The ServiceMonitor: why it matters

Without a ServiceMonitor, you have to manually add each mqtt2prometheus endpoint to Prometheus’s scrape config. Every time you add an exporter, you edit Prometheus config and reload it.

With a ServiceMonitor (a custom resource from the Prometheus Operator), you declare once: “scrape any Service with these labels every 30 seconds.” The Prometheus Operator picks it up automatically. Adding a new exporter means a helm upgrade with updated values — Prometheus starts scraping it within a minute.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: homekit-environment
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: mqtt2prometheus
      exporter: homekit-environment
  endpoints:
    - port: metrics
      interval: 30s
  namespaceSelector:
    matchNames:
      - home

What you get in Grafana

Once the metrics land in Prometheus, standard Grafana queries work:

# 24-hour temperature history for the living room
home_temperature_celsius{room="living_room"}

# Power consumption over the last week
sum(home_device_power_watts) by (device)

# Alert: CO₂ above 1000ppm for more than 10 minutes
home_co2_ppm > 1000

Grafana’s built-in alerting or Alertmanager can send a notification when the CO₂ climbs (open a window), when a device stops publishing (sensor offline), or when power consumption spikes unexpectedly (someone left the oven on).


The MQTT side

You need something publishing to MQTT. This works with:

  • HomeKit accessories via HomeBridge — HomeBridge has MQTT plugins that bridge HomeKit sensor data to MQTT topics
  • Zigbee2MQTT — direct bridge between Zigbee sensors and MQTT
  • ESPHome / Tasmota — DIY IoT devices that publish to MQTT natively
  • Any sensor with MQTT support — the protocol is generic

I run Mosquitto as the broker on the same cluster, deployed as a simple StatefulSet. The IoT devices publish to it; mqtt2prometheus subscribes from it; Prometheus scrapes from mqtt2prometheus. The broker is the only thing that touches the network boundary.


Why not just use a cloud service?

The honest answer is: you can, and for many people it’s fine. Cloud services for smart home data (Apple Home, Google Home, Home Assistant Cloud) work well.

But your home produces data continuously, forever, about where you are and how you live. Having that data in a system you control — where it doesn’t leave your network, where you can query it however you want, where it’s retained for as long as you choose — is worth the setup cost.

The Helm chart makes the setup cost low enough that there’s no good reason not to.