The Philips Hue has a nice API. It took me a few picnics to figure out how it works. So for your convenience I give you a few snippets of Python code.
First you have to figure out what your username is:
response = requests.post(self.ip + '/api', data=json.dumps({"devicetype": "casa"})) print response.content
With this username you can check lights or turn them on and off:
r = requests.get('http://192.168.1.194/api/qKsJorcN2lYr-bFfmzyFNqrCe5sEV/lights/3') print r.content
And now a wrapper function:
import requests import time import json import math import iot def EnhanceColor(normalized): if normalized > 0.04045: return math.pow( (normalized + 0.055) / (1.0 + 0.055), 2.4) else: return normalized / 12.92 def xy(r, g, b): rNorm = r / 255.0 gNorm = g / 255.0 bNorm = b / 255.0 rFinal = EnhanceColor(rNorm) gFinal = EnhanceColor(gNorm) bFinal = EnhanceColor(bNorm) X = rFinal * 0.649926 + gFinal * 0.103455 + bFinal * 0.197109 Y = rFinal * 0.234327 + gFinal * 0.743075 + bFinal * 0.022598 Z = rFinal * 0.000000 + gFinal * 0.053077 + bFinal * 1.035763 if X + Y + Z == 0: return (0,0) else: xFinal = X / (X + Y + Z) yFinal = Y / (X + Y + Z) return (xFinal, yFinal) class Bridge(): def __init__(self): self.ip = '192.168.1.194' self.username = "qKsJorcBTlYr-bFfma30NqrCeprG5sEV" def put(self, action, d): s = 'http://' + self.ip + "/api/" + self.username + action print s r = requests.put(s,data=json.dumps(d)) print r.content def kitchen_off(self): self.put('/groups/2/action/',{'on':False}) iot.iot_send_string(50, 'Kitchen Off') def kitchen_night_light(self): self.put('/groups/2/action/',{'on':True, 'bri':1}) iot.iot_send_string(50, 'Kitchen Night Light') def kitchen_dimmed(self): self.put('/groups/2/action/',{'on':True, 'bri':128}) iot.iot_send_string(50, 'Kitchen Dimmed') def kitchen_bright(self): self.put('/groups/2/action/',{'on':True, 'bri':254}) iot.iot_send_string(50, 'Kitchen Bright') def kitchen_blink(self): for i in range(2): self.put('/groups/2/action/',{'on':True, 'bri':254}) time.sleep(1) self.put('/groups/2/action/',{'on':False}) time.sleep(1) iot.iot_send_string(50, 'Kitchen Blink') def living_room_off(self): self.put('/groups/1/action/',{'on':False}) iot.iot_send_string(50, 'Living Room Off') def living_room_night_light(self): self.put('/groups/1/action/',{'on':True, 'bri':1}) iot.iot_send_string(50, 'Living Room Night Light') def living_room_dimmed(self): self.put('/groups/1/action/',{'on':True, 'bri':128}) iot.iot_send_string(50, 'Living Room Dimmed') def living_room_bright(self): self.put('/groups/1/action/',{'on':True, 'bri':254}) iot.iot_send_string(50, 'Living Room Bright') def bloom_color_loop(self): self.put('/lights/8/state',{'on':True, 'bri':254, 'effect':'colorloop'}) iot.iot_send_string(50, 'Hue Bloom Color Loop') def bloom_set_rgb(self, r,g,b): self.put('/lights/8/state',{'on':True, 'bri':254, 'xy':xy(r,g,b)}) iot.iot_send_string(50, 'Hue Bloom red=%d, green=%d, blue=%d' % (r,g,b))
The iot class is a homebrew event logger:
import urllib, urllib2, os, traceback, time def __iot_send(device_id, event = None): if os.name == 'nt': event = urllib.unquote(event).decode('utf8') print "%d: %s" % (device_id, event) else: try: url = 'https://www.picnicprojects.com/casa/log.php?d=' + str(device_id) if event <> None: url += '&v=' + event response = urllib2.urlopen(url) except: pass def iot_send(device_id): __iot_send(device_id) def iot_send_string(device_id, string): event = urllib.quote(string) __iot_send(device_id, event) def iot_send_value(device_id, value): event = '%f'% value __iot_send(device_id, event) def iot_send_values(device_id, values): first = 1 print values for v in values: print v if first == 1: event = '%f' % v first = 0 else: event += ',%f' % v __iot_send(device_id, event) def iot_send_trace(): __iot_send(999, 'exception: ' + traceback.format_exc())
You can use crontab to set timers. But why not make it yourself 🙂
import threading, datetime, time, os, traceback import iot, sun, hue def hue_set_lights(action, group): try: iot.iot_send_string(200, 'Hue: %s %s' % (group, action)) if action == 'on': if group == 'kitchen': bridge.kitchen_night_light() bridge.bloom_set_rgb(255,165,0) #orange elif group == 'livingroom': bridge.living_room_night_light() else: if group == 'kitchen': bridge.kitchen_off() elif group == 'livingroom': bridge.living_room_off() except: iot.iot_send_trace() def seconds_to_string(dt): h = int(dt/3600) m = (dt - h*3600) / 60 s = "%d s = %02d:%05.2f" % (dt, h, m) return s def seconds_until_end_of_day(now): dt = ((24 - now.hour - 1) * 60 * 60) + ((60 - now.minute - 1) * 60) + (60 - now.second) return dt def seconds_until_time(now, h, m, delta): t2 = now.replace(hour = h, minute = m, second = 0, microsecond = 0) + datetime.timedelta(hours=delta) dt = t2 - now dt = dt.total_seconds() return(dt) def seconds_until_sunset(now): t = sun.get_local_sunset_time(now.date()) t = t.replace(tzinfo=None) dt = datetime.datetime.combine(datetime.date.today(), t.time()) - datetime.datetime.combine(datetime.date.today(), now.time()) dt = dt.total_seconds() return(dt) def seconds_until_sunrise(now): t = sun.get_local_sunrise_time(now.date()) t = t.replace(tzinfo=None) dt = datetime.datetime.combine(datetime.date.today(), t.time()) - datetime.datetime.combine(datetime.date.today(), now.time()) dt = dt.total_seconds() return(dt) def calculate_seconds_until_event(now, days, h, m, duration, use_sun): # ignore days dt_on = -1 dt_off = -1 d = now.weekday() if ((str(d+1) in str(days)) or ('*' in str(days))): dt_on = seconds_until_time(now, h, m, 0) dt_off = seconds_until_time(now, h, m, duration) if use_sun: if h < 12: dt_sun = seconds_until_sunrise(now) if dt_sun < dt_on: dt_on = -1 dt_off = -1 elif dt_sun < dt_off: dt_off = dt_sun else: dt_sun = seconds_until_sunset(now) - 2700 if dt_sun > dt_off: dt_on = -1 dt_off = -1 elif dt_sun > dt_on: dt_on = dt_sun return(dt_on, dt_off) def log_dt(now, dt, action, group): if action == 1: a = 'on' else: a = 'off' s = "timer programmed at: %s %s %s (%s)" % ((now + datetime.timedelta(seconds = dt)), group, a, seconds_to_string(dt)) def schedule_timers(days, hour, minute, duration, use_sun, group): now = datetime.datetime.now() dt_on, dt_off = calculate_seconds_until_event(now, days, hour, minute, duration, use_sun) if dt_on > 0: log_dt(now, dt_on, 1, group) t = threading.Timer(dt_on, hue_set_lights, args=('on',group)) t.setDaemon(True) t.start() if dt_off > 0: log_dt(now, dt_off, 0, group) t = threading.Timer(dt_off, hue_set_lights, args=('off',group)) t.setDaemon(True) t.start() bridge = hue.Bridge() sun = sun.Sun() if __name__ == '__main__': while 1: try: if os.name == 'nt': fname = 'timers.txt' else: fname = '/mnt/nas/data/timers.txt' fid = open(fname, 'r') for line in fid: if '#' not in line: a = line.replace('\n','').split(',') # days, hour, minute, duration, use_sun, group schedule_timers(a[0], int(a[1]), int(a[2]), float(a[3]), int(a[4]), a[5]) # time.sleep(10) except: iot.iot_send_trace() dt = 60 + seconds_until_end_of_day(datetime.datetime.now()) time.sleep(dt)
You have to put all timers in timers.txt
Yes, I know, it looks a lot like crontab. This one is not better, but I made it myself 🙂
# days, hour, minute, duration, use_sun, group 1245, 5,30, 1,1,kitchen 12345, 7, 0,1.5,1,kitchen 67, 7, 5,3, 1,kitchen *, 17,30, 6,1,kitchen *, 18,15, 4,1,livingroom
And you need to know when the sun rises and sets. I didn’t write this class myself but I also forgot where I found it. Credits to the anonymous maker!
import math import datetime from dateutil import tz class SunTimeException(Exception): def __init__(self, message): super(SunTimeException, self).__init__(message) class Sun: """ Approximated calculation of sunrise and sunset datetimes. Adapted from: https://stackoverflow.com/questions/19615350/calculate-sunrise-and-sunset-times-for-a-given-gps-coordinate-within-postgresql """ def __init__(self, lat=52.37,lon=4.90): # default Amsterdam self._lat = lat self._lon = lon def get_sunrise_time(self, date=datetime.date.today()): """ Calculate the sunrise time for given date. :param lat: Latitude :param lon: Longitude :param date: Reference date :return: UTC sunrise datetime :raises: SunTimeException when there is no sunrise and sunset on given location and date """ sr = self._calc_sun_time(date, True) if sr is None: raise SunTimeException('The sun never rises on this location (on the specified date)') else: return sr def get_local_sunrise_time(self, date=datetime.date.today(), local_time_zone=tz.tzlocal()): """ Get sunrise time for local or custom time zone. :param date: Reference date :param local_time_zone: Local or custom time zone. :return: Local time zone sunrise datetime """ sr = self._calc_sun_time(date, True) if sr is None: raise SunTimeException('The sun never rises on this location (on the specified date)') else: return sr.astimezone(local_time_zone) def get_sunset_time(self, date=datetime.date.today()): """ Calculate the sunset time for given date. :param lat: Latitude :param lon: Longitude :param date: Reference date :return: UTC sunset datetime :raises: SunTimeException when there is no sunrise and sunset on given location and date. """ ss = self._calc_sun_time(date, False) if ss is None: raise SunTimeException('The sun never sets on this location (on the specified date)') else: return ss def get_local_sunset_time(self, date=datetime.date.today(), local_time_zone=tz.tzlocal()): """ Get sunset time for local or custom time zone. :param date: Reference date :param local_time_zone: Local or custom time zone. :return: Local time zone sunset datetime """ ss = self._calc_sun_time(date, False) if ss is None: raise SunTimeException('The sun never sets on this location (on the specified date)') else: return ss.astimezone(local_time_zone) def _calc_sun_time(self, date=datetime.date.today(), isRiseTime=True, zenith=90.8): """ Calculate sunrise or sunset date. :param date: Reference date :param isRiseTime: True if you want to calculate sunrise time. :param zenith: Sun reference zenith :return: UTC sunset or sunrise datetime :raises: SunTimeException when there is no sunrise and sunset on given location and date """ # isRiseTime == False, returns sunsetTime day = date.day month = date.month year = date.year TO_RAD = math.pi/180.0 # 1. first calculate the day of the year N1 = math.floor(275 * month / 9) N2 = math.floor((month + 9) / 12) N3 = (1 + math.floor((year - 4 * math.floor(year / 4) + 2) / 3)) N = N1 - (N2 * N3) + day - 30 # 2. convert the longitude to hour value and calculate an approximate time lngHour = self._lon / 15 if isRiseTime: t = N + ((6 - lngHour) / 24) else: #sunset t = N + ((18 - lngHour) / 24) # 3. calculate the Sun's mean anomaly M = (0.9856 * t) - 3.289 # 4. calculate the Sun's true longitude L = M + (1.916 * math.sin(TO_RAD*M)) + (0.020 * math.sin(TO_RAD * 2 * M)) + 282.634 L = self._force_range(L, 360 ) #NOTE: L adjusted into the range [0,360) # 5a. calculate the Sun's right ascension RA = (1/TO_RAD) * math.atan(0.91764 * math.tan(TO_RAD*L)) RA = self._force_range(RA, 360 ) #NOTE: RA adjusted into the range [0,360) # 5b. right ascension value needs to be in the same quadrant as L Lquadrant = (math.floor( L/90)) * 90 RAquadrant = (math.floor(RA/90)) * 90 RA = RA + (Lquadrant - RAquadrant) # 5c. right ascension value needs to be converted into hours RA = RA / 15 # 6. calculate the Sun's declination sinDec = 0.39782 * math.sin(TO_RAD*L) cosDec = math.cos(math.asin(sinDec)) # 7a. calculate the Sun's local hour angle cosH = (math.cos(TO_RAD*zenith) - (sinDec * math.sin(TO_RAD*self._lat))) / (cosDec * math.cos(TO_RAD*self._lat)) if cosH > 1: return None # The sun never rises on this location (on the specified date) if cosH < -1: return None # The sun never sets on this location (on the specified date) # 7b. finish calculating H and convert into hours if isRiseTime: H = 360 - (1/TO_RAD) * math.acos(cosH) else: #setting H = (1/TO_RAD) * math.acos(cosH) H = H / 15 #8. calculate local mean time of rising/setting T = H + RA - (0.06571 * t) - 6.622 #9. adjust back to UTC UT = T - lngHour UT = self._force_range(UT, 24) # UTC time in decimal format (e.g. 23.23) #10. Return hr = self._force_range(int(UT), 24) min = round((UT - int(UT))*60, 0) if min == 60: hr += 1 min = 0 return datetime.datetime(year, month, day, hr, int(min), tzinfo=tz.tzutc()) @staticmethod def _force_range(v, max): # force v to be >= 0 and < max if v < 0: return v + max elif v >= max: return v - max return v if __name__ == '__main__': sun = Sun(85.0, 21.00) try: print(sun.get_local_sunrise_time()) print(sun.get_local_sunset_time()) # On a special date in UTC abd = datetime.date(2014, 1, 3) abd_sr = sun.get_local_sunrise_time(abd) abd_ss = sun.get_local_sunset_time(abd) print(abd_sr) print(abd_ss) except SunTimeException as e: print("Error: {0}".format(e))