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:
1 2 |
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:
1 2 |
r = requests.get('http://192.168.1.194/api/qKsJorcN2lYr-bFfmzyFNqrCe5sEV/lights/3') print r.content |
And now a wrapper function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
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 🙂
1 2 3 4 5 6 |
# 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!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
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)) |
Very informative and nice article keep sharing great stuff