Mercurial > code > home > repos > homeauto
comparison service/colplay/colplay.py @ 1058:2dfd367f7113
move code from nightlight.py into new colplay.py
Ignore-this: ca60cb8905438b9c9b876422618f526f
darcs-hash:e6920524032f6e3edb6ba93ddf9bf5f82ebec65a
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Mon, 21 Mar 2016 04:17:57 -0700 |
parents | |
children | 33883457d1c2 |
comparison
equal
deleted
inserted
replaced
1057:c1961da4180a | 1058:2dfd367f7113 |
---|---|
1 """ | |
2 Color player: emits many color values that change over time, by | |
3 scanning across images and creating new images by blending other | |
4 patterns. | |
5 | |
6 Rewrite of pixel/nightlight.py | |
7 """ | |
8 from __future__ import division | |
9 import time, os, logging, json, traceback | |
10 from PIL import Image | |
11 from datetime import datetime, timedelta | |
12 from twisted.internet import reactor, task | |
13 import cyclone.web | |
14 from dateutil.tz import tzlocal | |
15 from cyclone.httpclient import fetch | |
16 from webcolors import rgb_to_hex | |
17 | |
18 logging.basicConfig(level=logging.DEBUG) | |
19 log = logging.getLogger() | |
20 logging.getLogger('restkit.client').setLevel(logging.WARN) | |
21 | |
22 class Img(object): | |
23 def __init__(self, filename): | |
24 self.filename = filename | |
25 self.reread() | |
26 | |
27 def reread(self): | |
28 try: | |
29 self.img = Image.open(self.filename) | |
30 except IOError: # probably mid-write | |
31 time.sleep(.5) | |
32 self.img = Image.open(self.filename) | |
33 self.mtime = os.path.getmtime(self.filename) | |
34 | |
35 def getColor(self, x, y): | |
36 """10-bit rgb""" | |
37 if os.path.getmtime(self.filename) > self.mtime: | |
38 self.reread() | |
39 return [v * 4 for v in self.img.getpixel((x, y))[:3]] | |
40 | |
41 lightResource = { | |
42 'theater0': 'http://bang:9059/output?s=http://bigasterisk.com/homeauto/board0/rgb_right_top_2&p=http://projects.bigasterisk.com/room/color', | |
43 } | |
44 | |
45 lightYPos = { | |
46 'theater0' : 135, | |
47 } | |
48 | |
49 def hexFromRgb(rgb): | |
50 return rgb_to_hex(tuple([x // 4 for x in rgb])).encode('ascii') | |
51 | |
52 def setColor(lightName, rgb, _req): | |
53 """takes 10-bit r,g,b | |
54 | |
55 returns even if the server is down | |
56 """ | |
57 log.debug("setColor(%r,%r)", lightName, rgb) | |
58 | |
59 serv = lightResource[lightName] | |
60 try: | |
61 h = hexFromRgb(rgb) | |
62 log.debug("put %r to %r", h, serv) | |
63 r = _req(method='PUT', url=serv, body=h, | |
64 headers={"content-type":"text/plain"}) | |
65 return r | |
66 except Exception, e: | |
67 log.warn("Talking to: %r" % serv) | |
68 log.warn(e) | |
69 return None | |
70 | |
71 def setColorAsync(lightName, rgb): | |
72 """ | |
73 uses twisted http, return deferred or sometimes None when there | |
74 was a warning | |
75 """ | |
76 def _req(method, url, body, headers): | |
77 d = fetch(url=url, method=method, postdata=body, | |
78 headers=dict((k,[v]) for k,v in headers.items())) | |
79 @d.addErrback | |
80 def err(e): | |
81 log.warn("http client error on %s: %s" % (url, e)) | |
82 raise e | |
83 return d | |
84 setColor(lightName, rgb, _req=_req) | |
85 | |
86 | |
87 class LightState(object): | |
88 def __init__(self): | |
89 self.lastUpdateTime = 0 | |
90 self.lastErrorTime = 0 | |
91 self.lastError = "" | |
92 self.img = Img("pattern.png") | |
93 self.autosetAfter = dict.fromkeys(lightYPos.keys(), | |
94 datetime.fromtimestamp(0, tzlocal())) | |
95 | |
96 def mute(self, name, secs): | |
97 """don't autoset this light for a few seconds""" | |
98 self.autosetAfter[name] = datetime.now(tzlocal()) + timedelta(seconds=secs) | |
99 | |
100 def step(self): | |
101 try: | |
102 now = datetime.now(tzlocal()) | |
103 hr = now.hour + now.minute / 60 + now.second / 3600 | |
104 x = int(((hr - 12) % 24) * 50) | |
105 log.debug("x = %s", x) | |
106 | |
107 for name, ypos in lightYPos.items(): | |
108 if now > self.autosetAfter[name]: | |
109 c = self.img.getColor(x, ypos) | |
110 setColorAsync(name, c) | |
111 self.lastUpdateTime = time.time() | |
112 except Exception: | |
113 self.lastError = traceback.format_exc() | |
114 self.lastErrorTime = time.time() | |
115 | |
116 | |
117 class IndexHandler(cyclone.web.RequestHandler): | |
118 def get(self): | |
119 ls = self.settings.lightState | |
120 now = time.time() | |
121 self.set_header("content-type", "application/json") | |
122 self.set_status(200 if ls.lastUpdateTime > ls.lastErrorTime else 500) | |
123 self.write(json.dumps(dict( | |
124 secsSinceLastUpdate=now - ls.lastUpdateTime, | |
125 secsSinceLastError=now - ls.lastErrorTime, | |
126 lastError=ls.lastError, | |
127 ), indent=4)) | |
128 | |
129 lightState = LightState() | |
130 task.LoopingCall(lightState.step).start(1) | |
131 log.info("listening http on 9051") | |
132 reactor.listenTCP(9051, cyclone.web.Application([ | |
133 (r'/', IndexHandler), | |
134 ], lightState=lightState)) | |
135 reactor.run() |