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()