Using Sunset to adjust Philips Hue Sensor On/Off Times

I’ve been meaning to do this for ages, and it turned out to be a bit easier than I thought. The Python I’m using is a bit rough and ready – rather than the 8 separate API calls to PUT the date changes back [4 rules for each sensor], I could do this with one API call, based on the single data structure for all of the rules. It doesn’t really matter that much.

I haven’t done anything for sunrise processing, which fits our usage patterns with the sensors.

Here’s the script, which I’m calling from cron, once a day, which is a bit of overkill. I could probably do it weekly:

#!/usr/bin/env python3

import requests
import json
import time
from datetime import datetime, timedelta

fudgeFactorMins = -30
morningOffTime = "T08:00:00"

The fudgeFactorMins variable is an offset for the actual sunset: I eventually subtract this from the sunset date. The rule numbers are not going to be nailed on, but they do appear to be contiguous for each sensor, in groups of 4:

# Format: "T20:00:00/T08:00:00"

# 8am / 8pm: day-on
bathroomDayOnRuleURL = "http://yourIP/api/yourKey/rules/1"
# 8am / 8pm: day-dark-on
bathroomDayDarkOnRuleURL = "http://yourIP/api/yourKey/rules/2"
# 8pm / 8am: night-on
bathroomNightOnRuleURL = "http://yourIP/api/yourKey/rules/3"
# 8pm / 8am: night-dark-on
bathroomNightDarkOnRuleURL = "http://10.40.0.3/yourIP/api/yourKey/rules/4"

# 8am / 8pdm: day-on
livingroomDayOnRuleURL = "http://yourIP/api/yourKey/rules/9"
# 8am / 8pm: day-dark-on
livingroomDayDarkOnRuleURL = "http://yourIP/api/yourKey/rules/10"
# 8pm / 8am: night-on
livingroomNightOnRuleURL = "http://yourIP/api/yourKey/rules/11"
# 8pm / 8am: night-dark-on
livingroomNightDarkOnRuleURL = "http://yourIP/api/yourKey/rules/12"

This is a bit messy but…

allUrls = [bathroomDayOnRuleURL,bathroomDayDarkOnRuleURL,bathroomNightOnRuleURL,bathroomNightDarkOnRuleURL,li
vingroomDayOnRuleURL, livingroomDayDarkOnRuleURL, livingroomNightOnRuleURL, livingroomNightDarkOnRuleURL]

The sunsetAPIurl refers to a free API. I had a look at one implementation on StackOverflow which calculates locally. This endpoint is returning results within a few mins of the app on my phone, which is certainly good enough.

One slight nuisance is that I need to adjust for British SummerTime. What I’m doing for now is using Daylight Saving as the Python datetime library gives you it for free. It’s going to ‘wrong’ for the week or so when DST and BST are out of synch at the start and end of the summer, but I can live with that for now:

sunsetAPIurl = "https://api.sunrise-sunset.org/json?lat=yourLat8&lng=-yourLong"

def getSunset(sunsetAPIurl):
   r = requests.get(sunsetAPIurl)
   jsonData = r.json()
   # date format is 8:08:18 PM
   dateFromJson = datetime.strptime(jsonData["results"]["sunset"], '%I:%M:%S %p')
   if is_dst():
     adjustedSunset = dateFromJson + timedelta(hours=1)
     return adjustedSunset
   else:
     return dateFromJson

def is_dst( ):
    return bool(time.localtime( ).tm_isdst)

So another quick adjustment to reformat to the requirements of the Hue endpoint date handling, after processing the fudge factor:

def adjustSunsetToString(sunset, fudgeFactorMins):
  fudgeAdjusted = sunset + timedelta(minutes=fudgeFactorMins)
  return fudgeAdjusted.strftime('T%H:%M:%S')

def hitEndpoint(allUrls, dayOnString, nightOnString):
  for oneUrl in allUrls:
    r = requests.get(oneUrl)
    jsonData = r.json()
    currentDate = jsonData["conditions"][0]["value"]
    #print("currentDate: " + currentDate)
    ruleName = jsonData["name"]
    #print("rulename: " + ruleName)

The dates that I push back into the JSON need to conditionally flick around [earliest / latest or vice versa], which I do based on the substring search for ‘day’ on the rule name:

 
    if "day" in ruleName:
      jsonData["conditions"][0]["value"] = dayOnString
    else:
      jsonData["conditions"][0]["value"] = nightOnString
    newJsonPayload = {"conditions": jsonData["conditions"], "actions": jsonData["actions"]}
    r = requests.put(oneUrl, json = newJsonPayload)
    #print("response: " + r.text)


sunset = getSunset(sunsetAPIurl)
adjStrDate = adjustSunsetToString(sunset, fudgeFactorMins)

dayOnString = morningOffTime + "/" + adjStrDate
nightOnString = adjStrDate + "/" +  morningOffTime

hitEndpoint(allUrls, dayOnString, nightOnString)

 

Philips’ Hue Sensor Automation

I wrote about my first pass at the Philips’ Hue API to customise the motion sensor functionality back in December. At the time, I tested out my approach to force a third time slot [use of a nightlight setting for part of the the night] using a simple mobile app in Objective C. The approach isn’t that fancy: it’s basically 3 API PUT calls: two to rules, and then a third to a resourcelink all basically doing the same thing: inverting references to scenes [one for full brightness, and the other for the nightlight setting].

At the time I thought it would be handy to use a Raspberry Pi using cron jobs to toggle it off and on, and the long and short of it, that’s what I’ve gone with. It’s a simple Python script [using the Requests module, which is great, to make the API calls], and it’s working really well so far.

A couple of observations. First, I found an article on the Hue discussion forum – which I only found when I got a weird error and which, unfortunately I can’t find any more – that has what’s probably a more purist way of using the API to create extra slots. While it’s smarter than what I’m doing, unfortunately [and the author of the post does point this out] it has the effect of stopping the mobile app from working to set the sensor.

Secondly, my approach is pretty brittle. Between writing the app and implementing the Python script the state had changed in the bridge: there was a new scene [not one we added – at least intentionally] which was causing the API calls to fail.

Currently, my script is doing exactly what my mobile app did, which is creating the PUT payload for the API calls from files, and ‘hoping’ that they work. What the Philips’ mobile app does is to GET the various API resources and then PUT them back. I’ll do this when the current implementation breaks as it’s more trouble than it’s worth for now.

I was telling someone at work [a proper developer] about my results with proxying via ZAP, which I had to revisit to figure out why my original implementation had stopped working, and he couldn’t believe the fact that the app is making around 55 API calls to effect a change in the sensor setup. Based on the diffs I looked at, It seems to pretty blindly do the GETting and PUTting where no changes have been made.

I managed to completely corrupt the setup on the bridge by playing the ‘I wonder what will happen if I do this?’ game: I deleted the scene that was breaking my API calls. I ended up having to do a factory reset – about a five minute job for me, but worth bearing in mind if you are playing with this stuff and have a more sophisticated configuration.

Automating Philips Hue Motion Sensor Functionality

OK, this turned out to be a *lot* more complicated than I thought. Here’s what I wanted to do: we have a Hue motion sensor in the bathroom which I’ve set up to do nothing during the day, and then come on at night. Nothing too controversial there. But I wanted to have a 3rd option: for a certain section of the night – say after midnight – to come on at a nightlight setting.

There’s a ‘3 times a charm formula‘ from the Hue Labs which sounded promising, but that only allows you to set the idle time after motion activation. So I started looking at the API. There were a few obvious options I tried which didn’t work [like simply setting the brightness – over-written by whatever scene you have set in the accessory setup].

This article set me on the right track for what I needed to do insofar as rule settings. By toggling changes on the smartphone app and then looking at the results in the Clip API debugger I narrowed down the changes to two rules called ‘MotionSensor 5.night-on’ and ‘MotionSensor 5.night-dark-on’. [Obviously the number is dependent on the identifier the sensor is registered under.]

This isn’t enough on its own. I gave up on this after my first pass, waiting for a rainy day to come back to it – today.

I ended up using OWASP ZAP to proxy the Hue app traffic, to see what it was doing when you submit a change to the sensor configuration. That’s quite interesting actually: I was initially a bit puzzled at what looked like a mountain of traffic on first pass, but what in part turns out to be a very zealous keep-alive – roughly twice a second.

Long and short of it is that resetting the scene config using the app overwrites a ‘resourcelink’ with new scene specific values, as well as updating the rules.

I’ve roughed this out with my own iOS app just to check that it works in a way that I can transplant to another setup [a cron job on a Raspberry Pi would be a reasonable candidate]. To toggle on the ‘dim at night after midnight’ setting I make 3 API calls. You’ll need to fiddle around with the clip tool to get the equivalents for your hub – specifically, your sensor number will be different. For me, these are:

  • Rule: MotionSensor 5.night-on
  • Rule: MotionSensor 5.night-dark-on
  • Resourcelink: MotionSensor 5

For the rules, I Post the same ‘actions’ payload to both of the corresponding endpoints. It looks like:

{"actions": [
                {
                        "address": "/groups/YourActionNumber/action",
                        "method": "PUT",
                        "body": {
                                "scene": "YourDimSceneIDHere"
                        }
                },
                {
                        "address": "/sensors/YourStateNumber/state",
                        "method": "PUT",
                        "body": {
                                "status": 1
                        }
                }
        ]
}

Then for the resourcelink, I post:

{
    "name": "MotionSensor 5",
    "description": "MotionSensor 5 behavior",
    "type": "Link",
    "classid": 10020,
    "owner": "OwnerIDHere",
    "recycle": false,
    "links": [
              "/sensors/5",
              "/sensors/6",
              "/sensors/19",
              "/groups/5",
              "/rules/4",
              "/rules/5",
              "/rules/6",
              [etc.....]
              "/scenes/YourDimSceneIDHere",
              "/scenes/RecoverRoomSceneIDHere"
              ]
}

The sensor and rule set you’re linking to will be specific to your own setup.

I’ve no idea what that ‘recover scene’ is. I’m guessing it’s something that’s created when you configure a sensor in the first instance, and is some sort of default state.

There’s a corresponding 3 API calls which I need to make to toggle back to the scene for ‘bright pre-midnight’, replacing the scene ID accordingly.

At the point I gave up – where I was just making the rule changes – I could tell that it wasn’t right by loading up the config in the app. The partial configuration – without the resourcelink change – manifest itself simply as ‘do nothing’ for the nighttime slot. Resetting this restored the full config. I guess there’s always the danger that if you run wild and free with setting values incorrectly, you might invalidate the ruleset to the point where unpicking it could be a problem. All I can say is that the above approach works for me, but test carefully!