============================
== Keep Calm and Route On ==
============================
infra ramblings. all views are my own

Grafana Fun with Dyson Fans, Part 1: Getting the Stats

grafana mqtt dyson

Living in California, we purchased two Dyson Pure Cool Link fans a number of years back to try and help filter the air from the fires. These fans contain sensors, which monitor ambient temperature, humidity, and air quality.

The source for this project can be found here on GitHub.

While this project specifically focuses on the Dyson Pure Cool Link, we are utilizing the (fantastic) library LibDyson which can be used for a huge number of different Dyson fans.

Gaining Access to the Dyson

While accessing the Dyson might seem trivial, the process for actually getting local access is slightly complex, and has been iterated over a couple of times by different projects.

At its core, the Dyson uses MQTT to allow the Dyson app to quickly connect to your device (when you’re on the same network) and read the sensor data from the fan devices, as well as allow changing the settings for fan speed, ocillation, etc.

Specifically, the Dyson uses MQTT version 3 (as of writing) and requires authentication to send or receive data with the devices. Where are the credentials for MQTT? Well, they aren’t published (or provided) by Dyson. The old way of getting these credentials was to authenticate against Dyson’s web API (the one their app uses or the devices use to talk to Dyson) and then cache the mqtt credentials. This (again, as of writing) is broken, since Dyson continues to change their non-public API for its own uses (and to probably prevent people who aren’t within Dyson from utilizing it).

This seems like a huge bummer, but fortunately, some smart people determined that the MQTT credentials are static per device, and that we can actually generate the credentials if we know the WiFi information for each fan.

What WiFi info? The info that is printed on a nice sticker on our devices, that tells us how to connect to the fans when they get first setup, to get them connected to our WiFi. For example, in my case, my WiFi network for one fan is DYSON-XXX-US-XXXXXXXX-475, with a WiFi password in the format of xxxxxxxx.

With these two pieces of information in hand, we can determine our MQTT credentials.

Generate those Creds!

To get started, we clone libdyson so we can use the calculate_device_credenial.py found in the project. Once cloned, we will create a virtualenv, install our requirements, and then use the script with the supplied data to generate our credentials. I found that for whatever reason, providing the whole SSID didn’t work, I instead had to provide an SSID in the format XXX-US-XXXXXXXX (which incidently is the actual username format). This also throws an error (sadness), but does provide us a working credential. For the example below, I will be using dummy info.

# Dyson WiFi Credentials
# SSID: DYSON-GH8-US-TRR1234F-475
# PASS: xxpwxyol

$ git clone https://github.com/shenxn/libdyson.git
$ cd libdyson
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip3 install -r requirements.txt
$ python3 calculate_device_credenial.py
Note that you need to input your Dyson device WiFi information on the stickeron user's manual and device body. Do not put your home WiFi information.
Device WiFi SSID: GH8-US-TRR1234F
Device WiFi password: xxpwxyol

Serial: GH8-US-TRR1234F
Credential: O3C5O2MurmlIYpIpwim9MuRuXj4ryH0DBeldWby3dv8EKzNfkN9pNO2eyzqXN8bhL82mv8oQ4g5IoA7riyHiqA==
Device Type: N223
Traceback (most recent call last):
  File "/Users/myuser/src/libdyson/calculate_device_credenial.py", line 26, in <module>
    print("Device Type Name:", DEVICE_TYPE_NAMES[model])
NameError: name 'model' is not defined

From the above, we can now see our new credentials for accessing MQTT, with the username of GH8-US-TRR1234F and the password of O3C5O2MurmlIYpIpwim9MuRuXj4ryH0DBeldWby3dv8EKzNfkN9pNO2eyzqXN8bhL82mv8oQ4g5IoA7riyHiqA==.

Now that we have these credentials, we can test using libdyson to make sure we can access our Dyson.

Testing Access & Readings

Using the data above, we can now test our credentials using LibPython and IDLE:

# Again, from our virtualenv sourced earlier
$ python3
Python 3.9.7 (default, Oct 13 2021, 06:44:56)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import libdyson as ld
>>> dyson_user = 'GH8-US-TRR1234F'
>>> dyson_pass = 'O3C5O2MurmlIYpIpwim9MuRuXj4ryH0DBeldWby3dv8EKzNfkN9pNO2eyzqXN8bhL82mv8oQ4g5IoA7riyHiqA=='
>>> dyson_ip = '192.168.168.100'
>>>
>>> device = ld.get_device(dyson_user, dyson_pass, ld.DEVICE_TYPE_PURE_COOL_LINK)
>>> device.connect(dyson_ip)
>>> device.temperature
292.7
>>> device.humidity
68
>>> round((device.temperature - 273.15) * (9/5) + 32, 2)
67.19

As we can see above, we are able to successfully connect to and query data from our Dyson. It’s worth noting here the calculation we are applying to the temperature is because the Dyson measures the temperature in Kelvin, and so we have to convert Kelvin back to Fahrenheit to get a temperature that is readable.

At this point, we are now set with getting access to our stats, and we’re ready to move onto Part 2: Formatting our data, and sending it along.