Magic Lights: Controlling Hue with a Raspberry Pi

This post shows you how to control the Hue light bulbs using a Raspberry Pi. In particular, it shows you how to fade up your lights half an hour before to sunset.

20131006-073131.jpg

In true Heath Robinson style the solution is rather convoluted. But hey, that’s where the fun resides.

Email me, Sun

First, we set up IFTTT to email us sunrise and sunset times.

20131006-073820.jpg
This involves setting up two recipes triggered based on the weather channel.

Read my emails, Pi

Now we need to set up a Python script to read our Gmail. To set this up I first, via the Gmail web interface, set up the account to auto-label emails from IFTTT containing the word “Sun” as “Sun”. This makes the emails easier to read as we then only need to read the email ‘folder’ “Sun”.

The email reading script is similar to that used in the previous post.

#!/usr/bin/env python

import imaplib
from email.parser import HeaderParser
import sqlite3 as lite
import datetime

def extract_date(word):
	date_index_start = word.find('for ')+4
	date_index_end = word.find(' at')+11
	date_out = word[date_index_start:date_index_end]
	return date_out

def extract_sunevent(word):
	word_out = word[0:7]
	return word_out.strip()

def read_subjects(label):
	obj = imaplib.IMAP4_SSL('imap.gmail.com', '993')
	obj.login('username@gmail.com', 'password')
	obj.select(label)
	typ ,data = obj.search(None,'UnSeen')

	subjects =[]

	for num in data[0].split():
		data = obj.fetch(num, '(BODY[HEADER])')
		header_data = data[1][0][1]
		parser = HeaderParser()
		msg = parser.parsestr(header_data)
		subjects.append(msg['Subject'])
	return subjects

def storesuninsql(rows):
	#Save in database
	con = lite.connect('/home/[pi username]/sun.db')

	with con:
	    cur = con.cursor()
	    #Create a sun event table if it doesn't already exist
	    cur.execute('CREATE TABLE IF NOT EXISTS sun (r_datetime TIMESTAMP, sunevent TEXT)')

	    for row_values in rows:
	    	#print row_values
	    	#row_values = rows[i]

	    	cur.execute('INSERT INTO sun VALUES(?,?)', (row_values[0], row_values[1]))

def store_sun(subjects):
#Initialise temporary array for data
	rows = []
	unread_count = len(subjects)

	#Process and store unread mail items
	for j in range(0,unread_count):
		#print subjects[j]
		#Extract date/time of sun event
		extracted_date = extract_date(subjects[j])
		#Extract time of sunevent
		event_time = datetime.datetime.strptime(extracted_date, '%B %d, %Y at %I:%M%p')
		#Extract event name
		event_name = extract_sunevent(subjects[j])
		#Add (event time, event name) tuple to rows
		rows.append((event_time, event_name))
	storesuninsql(rows)

if __name__ == '__main__':
	subjects = read_subjects('Sun')
	store_sun(subjects)

The code ‘reads’ the emails using the imaplib Python library. It extracts the subject lines, which indicate the sun event (“Sunset” or “Sunrise”), the date and the time. It then stores a datetime stamp in an SQLite database with a text field indicating the event type.

This Python script is then scheduled to run (via cron) overnight.

Fade Up

Now we write a little Python script to fade up all our Hue light bulbs. This has become a lot easier since Philips launched the official Hue API and documentation in March 2013 (fair play to Philips – I think this is done really well).

First go to the Getting Started page of the Hue Developers site. Follow the simple steps there to add and authenticate a new user.

Next we write the code. In short summary the Hue API works using an HTTP PUT request. This passes a JSON object (basically a string) containing ‘variablename:value’ pairs that sets the state of a bulb. More detail is found on the Hue Developers site. To fade up I use the ‘transitiontime’ variable, which I set to 6000 (6000*100ms=10minutes). To be snazzy I use xy values for the D65 standard illuminant – [0.32,0.33]. Another nice colour, with an orangey sunset feel has xy values of around [0.43,0.53].

The code is:

import requests
import time

ip = "[Your Bridge IP Address]"
sunrise='{"on":true,"bri":0,"xy":[0.32,0.33]}'

def set_light(light, data):
	global ip, key
	requests.put("http://%s/api/[username]/lights/%s/state" % (ip, light), data=data)

def fade_up_all():
	for i in range(1,4):
		set_light(i, sunrise)
		time.sleep(1)
		data = '{"on":true, "bri":255, "transitiontime":6000}'
		set_light(i, data)

fade_up_all()

PS: I did try to use a for-loop to iterate through brightness values from 1 to 255 but this caused some buggy behaviour – the lights would flash and jump in brightness. The ‘transitiontime’ variable is a great improvement.

Cron Legacy

Finally we have my favourite bit – editing a cron tab via Python to schedule our fade up script half an hour before sunset.

Luckily some lovely person has provided a module called, unsurprisingly, python-crontab. To install it I first had to install a utility called dateutil:

sudo pip install python-dateutil
sudo pip install python-crontab

The documentation can be found at the above link. The comments in the code below will hopefully explain how to use it – it’s pretty simple. The only trick is to work out the syntax: [crontab object].[crontab field].on([value]) sets that field value, for example: cron.minute.on(5) sets the minute field to 5. Also you need to clear each field (e.g. cron.minute.clear()) before setting using .on() otherwise it add the value to the existing value (e.g. an existing value of ‘0’ and the previous would give ‘0,5’). In the code below the first function extracts the time of the last sunset from the database and returns hour and minute values for a time half an hour before yesterday’s sunset (the trick to that is to use timedelta). Also remember with cron to use absolute paths in your code – cron jobs are typically run as if you were in your home directory.

from crontab import CronTab
import datetime
import sqlite3 as lite

def gettime():
	con = lite.connect('/home/[pi username]/sun.db')

	#Get last sunset time
	cur = con.execute("SELECT r_datetime FROM sun WHERE sunevent='Sunset' ORDER BY r_datetime DESC LIMIT 1")
	#We can use fetchone() as only one record will be returned
	record = cur.fetchone()
	sunsetdt = datetime.datetime.strptime(record[0], "%Y-%m-%d %H:%M:%S")
	timearray = [sunsetdt.hour, sunsetdt.minute]
	return timearray

def setcron(timearray):
	#timearray input is an array in the form [hour, minute]
	hour = timearray[0]
	minute = timearray[1]
	user_cron = CronTab('[Your raspberry pi username]') #Open crontab for user

	list = user_cron.find_comment('sunon') #look for existing cron job for fade up lights using comment
	if not list: #if no job added
		job = user_cron.new(command='python /home/[pi username]/MyCode/Hue/fadeupall.py',comment='sunon') #Add new job
	else: #if existing jon
		job = list[0] #select first job returned from search
	job.minute.clear() #Clear previous minute
	job.hour.clear() #Clear previous hour
	job.minute.on(minute) #set minute value
	job.hour.on(hour) #set hour value
	job.enable() #enable job
	user_cron.write() #write crontab

timearray = gettime()
setcron(timearray)

The above code is scheduled to run once a day sometime after midnight, after the script to read my emails above. At some point I’ll get all this on to github.  Update: I have finally added the code (updated in places) to github: https://github.com/benhoyle/Hue.

Hey presto. My lights turn on half an hour before sunset everyday!

5 thoughts on “Magic Lights: Controlling Hue with a Raspberry Pi

  1. Would it be useful if the ‘on’ method cleared the previous selection in that slice? I’m open to changing that part of the API since I’ve never specified what the API would do if one called ‘on’ more than once. (should test for that)

  2. Nice article.

    I’m comparing Philips with LIFX as I want to be able to program the Pi to scan for mac addresses, and when I come home and my iPhone auto connects to the wifi, to recognise the iPhone is now on the wifi and turn on the lights (if > sunset and < bedtime)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s