Controlling your Mill smart heater using Google Assistant and Azure Functions
By: Andreas Nesheim
Tags: Mill, Google Assistant, IFTTT, Millheat, Azure Functions

I recently bought a Wifi-enabled convection heater from Mill to warm up my basement since the old one broke. After learning that they had an open API you could use to integrate with I decided to play around with it. This ultimately led to a Google Assistant command that lets me set the temperature of the oven using my Google Home instead of the corresponding app from Mill. So, here’s how you can do the same thing using the Millheat API, Azure Functions and IFTTT.

For this to work you’ll need to download the Millheat app on your phone, register your device there and connect it to a room and a home.

Millheat API

First you’ll need access to the Millheat API. You can apply for it here. When you’re granted access, you’ll get an access key and a secret token. You’ll use this to authorize your requests to the API. The documentation for the API can be found here and the Swagger definition can be found here.

Start off by creating a .NET Standard class library, which will hold our logic. We’ll create a class called MillheatClient.cs and in the constructor we’ll setup a HttpClient and set the base URL for it.

To be able to perform requests you’ll first of all need to get an authorization code. This can be done by using the POST request ApplyAuthCode and passing in your access key and secret token as headers in the HTTP request. Note that I am just passing in an empty StringContent as data since the data posted isn't used.

Next you’ll have to perform the request ApplyAccessToken. This is done to retrieve the access token and the refresh token. The access token is used in the following requests to ultimately get your unit. The refresh token can be used to refresh your access token and refresh token, but we won’t be using this now. Perform the request by passing in the authorization code from the previous step as a header and your Millheat account username and password as parameters.

Now we can start performing the requests that will ultimately let us retrieve the device we want to control. We’ll need to work our way down to the unit by first retrieving the registered home, then the rooms of the home and finally the unit within that room.

We’ll start by performing the request SelectHomeList, which returns a list of homes that you have registered in the Millheat app. You’ll pass in the access token as a header to authorize the request. In the app you can see the registered name at the very top of the main screen. In my code example I am selecting the first result in the list, since I only have one registered home.

Next we’ll get the rooms that are registered to this home by performing the request SelectRoomByHome. I am selecting the first result here aswell since I only have one room registered. Pass in the ID of the home retrieved in the previous step as a request parameter and add the access token as a header.

Then we’ll perform the request SelectDeviceByRoom, which returns a list of devices for the given room. Once again, pass in the ID of the room retrieved in the previous step as a request parameter and pass in the access token as a header.

Finally — we have retrieved the device! Now we can start controlling it. This is done through the request DeviceControlForOpenApi. We can use this to turn the oven on or off or to set the temperature. The access token needs to be set as a header once again and the device ID needs to be set as a request parameter. We have three new parameters in this request: operation, status and holdTemp (optional). The operation parameter is an integer and is used to determine if you want to control the power (on or off) or the temperature for the oven — 0 for power, 1 for temperature. The status parameter is based on the operation parameter. If you set operation = 0, status is used to turn the oven off (0) or on (1). If you set operation = 1, status is used to decide if you want to control the entire room (1) or just a single control (0). The holdTemp parameter is used to set the temperature (in Celcius degrees), given that you selected operation = 1. The documentation isn’t all that self-explanatory here, but that’s what I was able to interpret from it.

Since we want to be able to set a specific temperature for the oven we’ll create the request with operation = 1 (temperature control), status = 0 (single control) and the holdTemp is given by an input parameter. In the following code I have left these as parameters to that the calling code can set these as requested.

Azure Functions

Now we’re going to create an Azure Function that will perform the steps we created above whenever the function is called. The function will take in a parameter that will decide the temperature to set.

Create a new project of type Azure Functions in your solution. Select “Http trigger” and give it a name. Set authorization level to Function. After this is done, add a reference to the .NET Standard library project you created.

Remove the boilerplate code generated in the function that was created and replace it with this:

This function will look for a request parameter called temperature. If it was found, it will perform the request to the Millheat API and set the desired temperature from the request parameter. F.ex: myexamplefunctionapp.azurewebsites.net/api/SetTemperatureFunction?temperature=20 will set the temperature to 20 degrees Celcius.

Publish the function to Azure and navigate to it inside the Azure Portal. Click on the function we named “SetTemperatureFunction” from the menu on the left and click on the link “Get function URL”. Copy the link that pops up and store this as we’ll be using this in the next step.

Note: the parameter named “code” is the authorization key that we need to perform requests to this URL, now that it is publicly available. You don’t want other people than yourself setting the temperature!

Google Assistant

Finally we will integrate all this with Google Assistant using IFTTT. First you need to register for an account on IFTTT and log in. Next we’ll create an Applet, which is basically a trigger that says “if this thing happens, then do that thing”. We can do this by navigating to “Explore” and clicking “Make your own Applets from scratch” or by clicking your account picture and choosing “Create”.

From here, click on the text marked “This”. Search for the service “Google Assistant” and select it. In the next step, choose “Say a phrase with a number” as a trigger.

Now we can chose what we want to say that Google Assistant will respond to. We’ll use the # sign to mark what the number will be in the phrase. In the first field, write “Turn up the temperature to # degrees”. In the second and third field you can optionally add other ways to say it, like “Set the temperature to # degrees”. In the fourth field we can put what we want Google Assistant to respond with. Let’s make it confirm our action by saying “The temperature has been set to # degrees”. Select “English” as language (or any other if you’ve chosen another language).

After this is done we’ll setup that this will trigger an HTTP request to the URL we saved earlier. This can be done by using Webhooks. In the next step, select the text marked “That” and search for and select the service “Webhooks”. Then, select “Make a web request”. In this step, paste in the URL that you copied earlier from the Azure Portal into the URL field. We need to append the request for the temperature at the end of the URL, like so: &temperature={{NumberField}} . The brackets at the end encapsulating NumberField indicates that the number from our trigger phrase will be used here. Leave the other fields to their defaults and click "Create action".

Wrapping up

And that’s it! You should now have a fully functional voice command to control your oven. You can test it by using Google Assistant on your phone, Google Home or any other device you have that is connected to Google Assistant. I personally had some struggles getting it to work in my native language (Norwegian), but I’m sure it works better in English. If you have any comments or questions regarding this tutorial, hit me up on Twitter or leave a comment!