Mercurial > code > home > repos > homeauto
comparison lib/devices_shared.py @ 1147:ef494fe0499f
forgot devices_shared.py
Ignore-this: 210e7777d9d4d11f148bb7e63f5de65a
darcs-hash:8dd34afc9d00fe796f94da254519e22ca1fa7d03
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Wed, 04 Apr 2018 14:58:27 -0700 |
parents | |
children | 980d4cf8857d |
comparison
equal
deleted
inserted
replaced
1146:b62ee2697b8b | 1147:ef494fe0499f |
---|---|
1 from __future__ import division | |
2 import time | |
3 import numpy | |
4 import logging | |
5 import imageio | |
6 from rdflib import Namespace, RDF, URIRef, Literal | |
7 | |
8 ROOM = Namespace('http://projects.bigasterisk.com/room/') | |
9 log = logging.getLogger() | |
10 | |
11 def _rgbFromHex(h): | |
12 rrggbb = h.lstrip('#') | |
13 return [int(x, 16) for x in [rrggbb[0:2], rrggbb[2:4], rrggbb[4:6]]] | |
14 | |
15 class PixelColumnsFromImages(object): | |
16 # could use this instead: | |
17 # https://github.com/OpenImageIO/oiio/blob/master/src/python/py_imagecache.cpp | |
18 def __init__(self): | |
19 self.lastImg = None, None | |
20 | |
21 def get(self, path, x, y, h): | |
22 if self.lastImg[0] != path: | |
23 self.lastImg = path, imageio.imread('../piNode/config/' + path) # or etcd or http | |
24 img = self.lastImg[1] | |
25 | |
26 y = numpy.clip(y, 0, img.shape[0] - 1) | |
27 h = numpy.clip(h, 1, img.shape[0] - y) | |
28 x = numpy.clip(x, 0, img.shape[1] - 1) | |
29 log.info('getcol y=%s h=%s img=%r ret=%r', y, h, img.shape, img[y:y + h,x,:].shape) | |
30 return img[y:y + h,x,:] | |
31 | |
32 getPixelColumn = PixelColumnsFromImages().get | |
33 | |
34 | |
35 class AnimChannel(object): | |
36 def __init__(self, x): | |
37 self.x = self.x2 = x | |
38 self.start = self.end = 0 | |
39 | |
40 def animTo(self, x2, rate): | |
41 self.get() # bring self.x to current time | |
42 log.info('animTo %s -> %s', self.x, x2) | |
43 if x2 == self.x: | |
44 return | |
45 self.start = time.time() | |
46 self.end = self.start + abs(x2 - self.x) / rate | |
47 self.x0 = self.x | |
48 self.x2 = x2 | |
49 | |
50 def get(self): | |
51 now = time.time() | |
52 if now > self.end: | |
53 self.x = self.x2 | |
54 else: | |
55 dur = self.end - self.start | |
56 self.x = (self.end - now) / dur * self.x0 + (now - self.start) / dur * self.x2 | |
57 return self.x | |
58 | |
59 class ScanGroup(object): | |
60 | |
61 def __init__(self, uri, numLeds): | |
62 self.uri = uri | |
63 self.current = numpy.zeros((numLeds, 3), dtype=numpy.uint8) | |
64 | |
65 self.x = AnimChannel(0) | |
66 self.y = AnimChannel(0) | |
67 self.height = AnimChannel(numLeds) | |
68 | |
69 def animateTo(self, x, y, height, src, rate=30, interpolate=ROOM['slide']): | |
70 log.info('anim to %s x=%s y=%s h=%s', src, x, y, height) | |
71 self.x.animTo(x, rate) | |
72 self.y.animTo(y, rate) # need separate y rate? | |
73 self.height.animTo(height, rate) | |
74 | |
75 self.src = src | |
76 | |
77 def updateCurrent(self): | |
78 try: | |
79 self.current = getPixelColumn(self.src, | |
80 int(self.x.get()), | |
81 int(self.y.get()), | |
82 int(self.height.get())) | |
83 except IOError: | |
84 pass | |
85 | |
86 def currentStatements(self): | |
87 return [] | |
88 | |
89 def colorForIndex(self, i): | |
90 return list(self.current[i,:]) | |
91 | |
92 args = {ROOM['src']: ('src', str), | |
93 ROOM['x']: ('x', int), | |
94 ROOM['y']: ('y', int), | |
95 ROOM['height']: ('height', int), | |
96 ROOM['interpolate']: ('interpolate', lambda x: x), | |
97 ROOM['rate']: ('rate', float), | |
98 } | |
99 | |
100 | |
101 class RgbPixelsAnimation(object): | |
102 | |
103 def __init__(self, graph, uri, updateOutput): | |
104 """we call updateOutput after any changes""" | |
105 self.graph = graph | |
106 self.uri = uri | |
107 self.updateOutput = updateOutput | |
108 self.setupGroups() | |
109 | |
110 def setupGroups(self): | |
111 self.groups = {} | |
112 self.groupWithIndex = {} | |
113 attrStatements = set() | |
114 for grp in self.graph.objects(self.uri, ROOM['pixelGroup']): | |
115 s = int(self.graph.value(grp, ROOM['startIndex'])) | |
116 e = int(self.graph.value(grp, ROOM['endIndex'])) | |
117 log.info('ScanGroup %s from %s to %s', grp, s, e) | |
118 sg = ScanGroup(grp, e - s + 1) | |
119 self.groups[grp] = [s, e, sg] | |
120 for i in range(s, e + 1): | |
121 self.groupWithIndex[i] = sg, i - s | |
122 attrStatements.update(self.graph.triples((grp, None, None))) | |
123 self.onStatements(attrStatements, _groups=False) | |
124 | |
125 def maxIndex(self): | |
126 return max(v[1] for v in self.groups.itervalues()) | |
127 | |
128 def hostStatements(self): | |
129 return ( | |
130 [(self.uri, ROOM['pixelGroup'], grp) for grp in self.groups.keys()] + | |
131 sum([v[2].currentStatements() | |
132 for v in self.groups.itervalues()], [])) | |
133 | |
134 def getColorOrder(self, graph, uri): | |
135 colorOrder = graph.value(uri, ROOM['colorOrder'], | |
136 default=ROOM['ledColorOrder/RGB']) | |
137 head, tail = str(colorOrder).rsplit('/', 1) | |
138 if head != str(ROOM['ledColorOrder']): | |
139 raise NotImplementedError('%r colorOrder %r' % (uri, colorOrder)) | |
140 stripType = None | |
141 return colorOrder, stripType | |
142 | |
143 def step(self): | |
144 # if animating... | |
145 self.updateOutput() | |
146 | |
147 def onStatements(self, statements, _groups=True): | |
148 | |
149 needSetup = False | |
150 animateCalls = {} # group uri : kw for animateTo | |
151 for s, p, o in statements: | |
152 if s not in self.groups: | |
153 # missing the case that you just added a new group | |
154 continue | |
155 if p in args: | |
156 k, conv = args[p] | |
157 animateCalls.setdefault(s, {})[k] = conv(o.toPython()) | |
158 else: | |
159 needSetup = True | |
160 | |
161 if needSetup and _groups: | |
162 self.setupGroups() | |
163 for grp, kw in animateCalls.items(): | |
164 for pred, (k, conv) in args.items(): | |
165 if k not in kw: | |
166 v = self.graph.value(grp, pred) | |
167 if v is not None: | |
168 kw[k] = conv(v.toPython()) | |
169 | |
170 self.groups[grp][2].animateTo(**kw) | |
171 | |
172 def outputPatterns(self): | |
173 pats = [] | |
174 for grp in self.groups: | |
175 for attr in args: | |
176 pats.append((grp, attr, None)) | |
177 return pats | |
178 | |
179 def outputWidgets(self): | |
180 return [] | |
181 | |
182 def currentColors(self): | |
183 for _, _, sg in self.groups.values(): | |
184 sg.updateCurrent() | |
185 for idx in range(self.maxIndex() + 1): | |
186 sg, offset = self.groupWithIndex[idx] | |
187 r, g, b = sg.colorForIndex(offset) | |
188 yield idx, (r, g, b) |