Dyson Metrics

I hooked up my Dyson air purifiers to Prometheus to see what kind of patterns I could find. Below is a snapshot, annotated with real-life events. You can see some nice logarithmic growth, exponential decay, and one dramatic spike in particulate matter when I made dinner:

Why Bother?

The Dyson app is fine for checking current air quality, but it doesn't store historical data beyond a week, or let you compare trends over time. Since I already run a monitoring stack at home, hooking up the purifiers was a natural next step—and a good excuse to play with Grafana Alloy.

Here’s how I set it all up.

Collecting

To get data out of the air purifiers, I started with zkcohen/prometheus-dyson-exporter, which already handles the Dyson protocol and exposes the metrics in a Prometheus-friendly format. I forked it and added support for per-device labeling, since I have multiple purifiers and wanted to distinguish between them in the dashboard. I used Grafana Alloy to collect and remote-write metrics to Mimir, so I didn’t need to run Prometheus or expose any local exporters.

Deployment was simple; I deployed both Alloy and the Dyson exporter using Docker on my home Docker Swarm cluster, with a Compose file and docker stack deploy.

services:
  dyson-exporter:
    image: fosscel/prometheus-dyson-exporter:latest
    environment:
      EXPORTER_PORT: "9672"
      CONFIG_PATH: "/config/devices.ini"
    configs:
      - source: devices.ini
        target: /config/devices.ini

  alloy-iot:
    image: grafana/alloy
    command:
      - "run"
      - "--server.http.listen-addr=0.0.0.0:12345"
      - "--stability.level=experimental"
      - "/etc/alloycfg/"
    configs:
      - source: dyson-collect
        target: /etc/alloycfg/dyson.alloy
      - source: mimir
        target: /etc/alloycfg/mimir.alloy

The multi-device configuration was straightforward as well, with the config.ini accepting multiple device blocks:

[office]
dyson_serial = <serial>
dyson_credential = <token>
dyson_device_type = 527K
dyson_ip = <ip>

[bedroom]
...

As well as a simple prometheus.scrape block to define the job:

prometheus.scrape "dyson" {
  targets = [
    {
      __address__ = "dyson-exporter:9672",
    },
  ]
  job_name = "dyson"
  scrape_interval = "30s"
  forward_to = [prometheus.remote_write.mimir.receiver]
}

Visualization

My Grafana setup is GitOps-based and managed with ArgoCD, so I added the new Mimir tenant as a data source in code:

datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
      - name: Mimir (iot)
        type: prometheus
        access: proxy
        url: http://mimir-query-frontend.monitoring.svc.cluster.local:8080/prometheus/
        basicAuth: false
        jsonData:
          httpHeaderName1: X-Scope-OrgID
        secureJsonData:
          httpHeaderValue1: iot

After that, I used the existing dashboard from the exporter repo and tweaked it a bit. I updated it to support multiple purifiers side-by-side and added the HCHO (formaldehyde) sensor, which wasn’t in the original. Each device gets a friendly name (like “bedroom” or "office”) through labels set by the exporter. Those labels get passed into Mimir, so the dashboard can display and filter devices clearly without needing extra config.

The Midnight Mystery

A few weeks after setting this up, I solved a problem I didn't even know I was investigating.

My smoke detector kept going off at 3am claiming "carbon monoxide detected"—always around the same time, always when I was asleep with everything turned off. I'd checked the detector, replaced batteries, even considered that maybe I was losing my mind.

Then I looked at the data. There was a clear daily spike in VOCs around 3am, consistently across multiple nights. Turns out my apartment shares HVAC with neighboring units, and someone's schedule was introducing contaminants into the shared air system at predictable intervals.

Without historical data, this would have remained an unsolvable mystery. The Dyson app would just show me a snapshot when I happened to check it, with no way to correlate the alarm times with actual air quality patterns. But with everything flowing into Prometheus, the pattern was obvious.

That's the real value of instrumentation: sometimes you instrument something "because you can," and it ends up answering questions you didn't know you needed to ask.