All posts by Jos

Controlling a dumb floor heating pump with Tado smart thermostat and a Philips Hue power plug

My home has a floor heating system on the main floor, which basically has a pump that normally runs 24/7 and has no smart controls or connectivity. If hot water comes from the Central Heating, it’ll heat the floor, otherwise it’ll cool it.

I wanted to save on;

  1. not having the pump running at all times (~35€ / year savings)
  2. not heating the floor when ONLY heating other rooms in the house , like upstairs in the evening/morning (~110€/year savings)

All rooms in my house have a Tado smart thermostat which measures the temperature and humidity, and which can turn on the Central Heating unit if temperatures drop below a preset value. Using a Tado in your home and if have a ‘dumb’ pump like me, you could automate and save a lot of money 🙂 Kitlist:

  1. Philips Hue Bridge
  2. Philips Hue Smart Plug
  3. Tado Thermostat
  4. Raspberry pi zero

First, set up your Pi Zero so it has connectivity (same subnet as the Hue Bridge, and outbound internet access), and install Python3 on it.

Then set up your Hue Bridge and ensure the Hue Smart Plug shows up in your Hue app.

Put the Smart Plug in between your wall outlet and the Floor Heating pump:

Yes, the color of the light makes one wonder what else happens in this room, I’ll blog about my Hydroponics hobby some other time 🙂

Create a Hue API username and determine the plug’s ID

Create a Tado account and set up a heating profile for the room. If you don’t have an old radiator in the room, just plug it onto another one to set it up, then remove it and place it somewhere in the kitchen to make it think it is controlling a radiator, as these plugs are actually mechanical and meant to rotate:

A tado thermostat tricked into controlling the floor heating

Customize my script and run! I scheduled it to run every minute using a cronjob on the pi.

Since we already had Tado and Hue, the total cost of the project was only 35€ (raspberry pi + smart plug), so the investment was well worth it.

Get the code at my git lab or below:

Git lab Tado Floor Heating Pump Controller with Philips Hue Smart Plug

import requests 
import json
import os
import datetime
import time

data = {'grant_type':'password', 
        'client_secret':'wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc', #see on how to get this client secret
        'username':'', #your tado login/email
        'password':'MyPassword' #your tado password
hueBaseURL = "http://HUEBRIDGEIPHERE/api/USERNAMEHERE" #the IP and username of your HUE, see for instructions on how to get the username
hueLightID = '6' #the ID of the Hue Power Plug in which you connected your floor heating pump, see for instructions on how to get all ID's
tadoHomeId = '12345' #the home ID of your tado account, see on how to get the ID
tadoHeatingZoneId = '6' #the ID of the tado zone you are heating using a floor heating system, see on how to get the ID
r ='', data = data) 
j = json.loads(r.text)
TOKEN = j["access_token"]
headers={'Authorization': "Bearer " + TOKEN}
r = requests.get(''+tadoHomeId+'/zones/'+tadoHeatingZoneId+'/state', headers=headers)
j = json.loads(r.text)
desiredTemp = j["setting"]["temperature"]["celsius"]
currentTemp = j["sensorDataPoints"]["insideTemperature"]["celsius"]
currentPower = j["activityDataPoints"]["heatingPower"]["percentage"]
print("Status zone "+tadoHeatingZoneId+" : "+str(j["setting"]["power"]))
print("Heating power for zone "+tadoHeatingZoneId+" : "+str(currentPower)+"%")
print("Current temperature in zone "+tadoHeatingZoneId+" : "+str(currentTemp))
print("Target temp in zone "+tadoHeatingZoneId+" : "+str(desiredTemp))
r = requests.get(hueBaseURL+'/lights/'+hueLightID)
j = json.loads(r.text)
pumpState = j["state"]["on"]
print("Floor heating pump state: "+str(pumpState))
if currentPower > 0 and pumpState == False :
    r = requests.put(hueBaseURL+'/lights/'+hueLightID+'/state', data = '{"on":true}') 
    print("PUMP TURNED ON")
elif currentPower <= 0 and pumpState == True :
    print("PUMP SHOULD BE OFF")
    r = requests.put(hueBaseURL+'/lights/'+hueLightID+'/state', data = '{"on":false}')  
    print("PUMP TURNED OFF")

Conditional nested ARM template to add WVD application group to Workspace

In Windows Virtual Desktop (ARM version), applications are part of application groups, which in turn get nested under Workspaces.

In an ‘Infra As Code’ world these should be deployed through ARM templates (or Az Cli/Ps scripts). I had a long wrestle with ARM today getting applications assigned to workspaces ONLY if they weren’t already assigned.

Azure throws a friendly 400 error if you try to add an app that already exists, and interestingly, the ARM ‘contains’ function fails to properly evaluate WVD Workspace members when in a nested template.

So, I had to resort to some trickery by converting it to a string. For anyone else wanting to incrementally attach application groups to workspaces, feel free to copy/clone my template 🙂

Git source

    "$schema": "",
    "contentVersion": "",
    "parameters": {
        "workspaceName": {
            "type": "string",
            "metadata": {
                "description": "The name of the Workspace."
            "defaultValue": "NLD-WVD-WS01"
        "workspaceResourceGroup": {
            "type": "string",
            "metadata": {
                "description": "The workspace resource group Name."
            "defaultValue": "WE-WVD-RG"
        "appGroupName": {
            "type": "string",
            "metadata": {
                "description": "The name of the Application Group to be linked."
            "defaultValue": "testag2"
    "variables": {
        "appGroupResourceId": "[resourceId('Microsoft.DesktopVirtualization/applicationgroups/', parameters('appGroupName'))]"
    "resources": [
            "apiVersion": "2018-05-01",
            "name": "AddAppGroupToWorkspaceIncrementally",
            "type": "Microsoft.Resources/deployments",
            "resourceGroup": "[parameters('workspaceResourceGroup')]",
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "",
                    "contentVersion": "",
                    "resources": [
                            "name": "[parameters('workspaceName')]",
                            "apiVersion": "2019-12-10-preview",
                            "condition": "[not(greater(indexOf(string(reference(concat('/subscriptions/',subscription().subscriptionId,'/resourceGroups/',parameters('workspaceResourceGroup'),'/providers/Microsoft.DesktopVirtualization/workspaces/',parameters('workspaceName')),'2019-12-10-preview','Full').properties.applicationGroupReferences),variables('appGroupResourceId')),0))]",
                            "type": "Microsoft.DesktopVirtualization/workspaces",
                            "location": "eastus",
                            "properties": {
                                "applicationGroupReferences": "[union(reference(concat('/subscriptions/',subscription().subscriptionId,'/resourceGroups/',parameters('workspaceResourceGroup'),'/providers/Microsoft.DesktopVirtualization/workspaces/',parameters('workspaceName')),'2019-12-10-preview','Full').properties.applicationGroupReferences,array(variables('appGroupResourceId')))]"

Grouping devices in MDATP based on registered users

Microsoft Defender Advanced Threat Protection seems to be becoming the defacto leader in the A/V industry, at least when Windows is concerned, but other OS’es seem to be following quickly 🙂

At one of my international customers, many different locations and departments exist and we’d like to group devices in MDATP based on their primary user so we can assigned different administrators automatically, and apply different web filtering policies.

MDATP has the following options available for grouping:

These membership rules don’t say anything about the user, and the machine domains are all cloud native (no hybrid joins). So we need to use Tags to gain flexible targeting in MDATP.

The following PowerShell script can be scheduled as an Azure Runbook to automatically tag all your MDATP devices based on the ‘Company’ attribute of the device’s primary user. It could also be modified easily to e.g. parse a user’s group membership or UPN’s domain.

If you have a lot of devices, it may take a while for the first run (beyond Azure Automation limits), in that case run it locally first and then schedule it.

Sensitive group protection

It is best practise in IT to secure access to resources with Groups.

Membership of a security group means access to whatever resources are secured by that group. Sometimes these groups are self-managed by an owner, sometimes centrally.

In all cases, fairly low privileged users, that are not global admins, can add users to these groups including themselves. Imagine that you have a group called ‘Global Admins’, and your helpdesk user assigns himself to that group. You’d like to know right?

With Privileged Access Groups in Azure AD (Preview) you can protect groups like these actively, but, this requires a P2 license and still lacks some customization features.

An alternative method is to use a simple alerting rule in MCAS (Microsoft Cloud App Security), where you set an alert when ‘someone’ joins a specific group, or if you want to do more than alerting you could also run an automation playbook.

Here’s how to protect a specific Azure AD or Office 365 group with MCAS:

  1. look up its GUID in AzureAD
  2. Create an Activity Policy in the MCAS console
  3. Specify the group GUID as ‘Activity object ID’ in the policy and the correct action type:

OnedriveMapper 4.04 with auto reconnect

Version 4.04 OnedriveMapper now automatically reconnects drives (of any type) when the cookie expires. No more ‘broken’ mappings! The script is smart enough to detect if its just a connectivity issue (= do nothing) or an actual drive issue.

All improvements since 4.00:

  1. Auto Remap (automatically reconnect disconnected drives)
  2. Block the IE firstrun wizard properly
  3. Bugfix: properly handle existing shortcuts instead of throwing an error
  4. Increase Converged Drive (single mapping with sub-mappings) reliability
  5. Better cleanup of existing mappings
  6. Always force the ‘keep me signed in’ option
  7. Support for root-level mappings