light nerdery, 2

Screen Shot 2015-11-29 at 11.36.16 PM

So after a bunch of fighting with trying to directly build a menu then giving up and using a simplified toolkit, my little weather-based light control script now manifests a menubar icon, and a list of all of your scenes. At some point in the near future I will package it up as an .app, and add in dialogue boxes to enter your LIFX and forecast.io API keys. And then it will be pretty much done.

At some point in the further future I may want to also add the ability to actually set up what scenes it chooses between based on temperature ranges, but that’s a complicated beast involving designing and using a UI. And this is just a quick little Python hack.

edit. Now it’s on Github if anyone feels like using it or adding to it.

light nerdery

I just spent about five hours kludging together a little Python script to change the color of my foyer’s light based on the temperature. I already had that happening via If This Then That, but there were some limitations – it didn’t work well with me also wanting to change the light to a dim red in the evening.

So I dove into the mysteries of the LIFX web API, and the forecast.io web API, and wrote something. Now, my foyer will change between four different colors based on the temperature (blue/purple/yellow/nearly white) between the hours of 8am and 8:30pm, and switch to a dim red outside of those hours. There’s also a temperature offset based on the next hour’s conditions; precipitation or high winds lower the effective a bit so I’m not out there being miserable when it’s near the bottom of a temperature range and wind or rain is making it feel chillier.

# peggy's little foyer-light controller
#
# selects from a set of lifx scenes based on the temperature from forecast.io
#

import requests
import sys
import math
import time

lifxToken = 'your token goes here'
# generate your token at https://cloud.lifx.com/settings

darkSkiesToken = 'your token goes here'
# generate your token at https://developer.forecast.io

#
# desired temperature ranges for scenes you've set up via the lifx app
# 
choices = [
 {'scene':'Foyer Cold', 'lower':0-float("inf"), 'upper':54, 'note':'Wear a heavy coat, miss dragon.'},
 {'scene':'Foyer Chilly', 'lower':55, 'upper':66, 'note':'Light coat. Or sweater. Or something.'},
 {'scene':'Foyer Hot', 'lower':67, 'upper':74, 'note':'As long as your sin globes are covered, anything goes.'},
 {'scene':'Foyer Really Hot', 'lower':75, 'upper':float("inf"), 'note':'It is HOT. Take a parasol or something.'},
]

nighttime = {
 'scene':'Foyer Evening',
 'nightBegins': 20.5, # 24-hour decimal time. 8:30PM = 20.5.
 'nightEnds': 8.0, # 24-hour decimal time. 8 AM = 8.0.
}

# temperature offsets for various conditions
# as defined by forecast.io's 'icon' property of the forecast
offsets = {
 'clear-day':0,
 'clear-night':0,
 'rain':-10,
 'snow':-10,
 'sleet':-10,
 'wind':-5,
 'fog':0,
 'cloudy':-5,
 'partly-cloudy-day':-5,
 'partly-cloudy-night':-5,
}

latitude = '47.6659248'
longitude = '-122.3181908'
# where are you?
# really this should talk to OSX's location manager
# but that starts to look like work

repeatDelay = 5*60 # delay between repetitions, in seconds
 # lifx' api throttles you to about once a minute
 # forecast.io throttles to 1000 requests/day (about one every 1.4 min)

#
# picks a scene based on the forecast
#
def ChooseScene (choices, offsets, nighttime, forecast):
 # there should be some logic here to check the time
 # and if it's earlier or later than certain times
 # we cancel out and just display the nocturnal light
 
 now = time.localtime()
 now = now.tm_hour+((1.0/60)*now.tm_min)
 if (now < nighttime['nightEnds']) or (now > nighttime['nightBegins']):
 print "it's nighttime! the time is now ",now
 return nighttime['scene']
 
 # figures out temperature offset based on the next hour's condition
 condition = forecast['hourly']['data'][0]['icon']
 offset = offsets[condition]
 
 temperature = round(forecast['hourly']['data'][0]['temperature']+offset)
 print 'current temperature:',temperature
 for choice in choices:
 if temperature > choice['lower'] and temperature < choice['upper']:
 print choice['note']
 return choice['scene']


#
# contacts LIFX and acquires a list of scenes
#
def GetScenes(token):
 headers = {
 "Authorization": "Bearer %s" % token,
 }
 
 authorization = requests.get('https://api.lifx.com/v1/lights/all', headers=headers)
 
 if authorization.status_code != 200:
 print "\ninvalid authorization, maybe check your token?\n"
 sys.exit()
 
 scenerequest = requests.get('https://api.lifx.com/v1/scenes', headers=headers).json()
 
 # I am sure there is a much more pythonic way to do this. Works though.
 scenes = {}
 for scene in scenerequest:
 scenes[scene['name']] = scene['uuid'].encode('ascii')
 
 return scenes

#
# sets a lifx scene
#
def SetScene(uuid,token):
 headers = {
 "Authorization": "Bearer %s" % token,
 "duration": 60*5,
 }
 
 result = requests.put('https://api.lifx.com/v1/scenes/scene_id:'+uuid+'/activate', headers=headers)
 return result

#
# acquires a forecast from forecast.io
#
def GetForecast(latitude, longitude, token):
 forecast = requests.get('https://api.forecast.io/forecast/'+token+'/'+latitude+','+longitude).json()
 return forecast

#
# all functions are defined, time to do it to it!
#
lastScene = ''
scenes = GetScenes(lifxToken)

while True:
 forecast = GetForecast(latitude, longitude, darkSkiesToken)
 whichScene = ChooseScene(choices, offsets, nighttime, forecast)
 if lastScene == whichScene:
 print "no need to change the scene right now."
 else:
 print 'setting scene:'+whichScene
 SetScene(scenes[whichScene],lifxToken)
 lastScene = whichScene
 print "waiting a bit..."
 time.sleep(repeatDelay)

# fail silently because i'm a bad girl

…I really should get some kind of code formatting plugin for this blog someday.