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