Mercurial > code > home > repos > homeauto
changeset 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 | b62ee2697b8b |
children | 8b4eda2cf0bd |
files | lib/devices_shared.py |
diffstat | 1 files changed, 188 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/devices_shared.py Wed Apr 04 14:58:27 2018 -0700 @@ -0,0 +1,188 @@ +from __future__ import division +import time +import numpy +import logging +import imageio +from rdflib import Namespace, RDF, URIRef, Literal + +ROOM = Namespace('http://projects.bigasterisk.com/room/') +log = logging.getLogger() + +def _rgbFromHex(h): + rrggbb = h.lstrip('#') + return [int(x, 16) for x in [rrggbb[0:2], rrggbb[2:4], rrggbb[4:6]]] + +class PixelColumnsFromImages(object): + # could use this instead: + # https://github.com/OpenImageIO/oiio/blob/master/src/python/py_imagecache.cpp + def __init__(self): + self.lastImg = None, None + + def get(self, path, x, y, h): + if self.lastImg[0] != path: + self.lastImg = path, imageio.imread('../piNode/config/' + path) # or etcd or http + img = self.lastImg[1] + + y = numpy.clip(y, 0, img.shape[0] - 1) + h = numpy.clip(h, 1, img.shape[0] - y) + x = numpy.clip(x, 0, img.shape[1] - 1) + log.info('getcol y=%s h=%s img=%r ret=%r', y, h, img.shape, img[y:y + h,x,:].shape) + return img[y:y + h,x,:] + +getPixelColumn = PixelColumnsFromImages().get + + +class AnimChannel(object): + def __init__(self, x): + self.x = self.x2 = x + self.start = self.end = 0 + + def animTo(self, x2, rate): + self.get() # bring self.x to current time + log.info('animTo %s -> %s', self.x, x2) + if x2 == self.x: + return + self.start = time.time() + self.end = self.start + abs(x2 - self.x) / rate + self.x0 = self.x + self.x2 = x2 + + def get(self): + now = time.time() + if now > self.end: + self.x = self.x2 + else: + dur = self.end - self.start + self.x = (self.end - now) / dur * self.x0 + (now - self.start) / dur * self.x2 + return self.x + +class ScanGroup(object): + + def __init__(self, uri, numLeds): + self.uri = uri + self.current = numpy.zeros((numLeds, 3), dtype=numpy.uint8) + + self.x = AnimChannel(0) + self.y = AnimChannel(0) + self.height = AnimChannel(numLeds) + + def animateTo(self, x, y, height, src, rate=30, interpolate=ROOM['slide']): + log.info('anim to %s x=%s y=%s h=%s', src, x, y, height) + self.x.animTo(x, rate) + self.y.animTo(y, rate) # need separate y rate? + self.height.animTo(height, rate) + + self.src = src + + def updateCurrent(self): + try: + self.current = getPixelColumn(self.src, + int(self.x.get()), + int(self.y.get()), + int(self.height.get())) + except IOError: + pass + + def currentStatements(self): + return [] + + def colorForIndex(self, i): + return list(self.current[i,:]) + +args = {ROOM['src']: ('src', str), + ROOM['x']: ('x', int), + ROOM['y']: ('y', int), + ROOM['height']: ('height', int), + ROOM['interpolate']: ('interpolate', lambda x: x), + ROOM['rate']: ('rate', float), + } + + +class RgbPixelsAnimation(object): + + def __init__(self, graph, uri, updateOutput): + """we call updateOutput after any changes""" + self.graph = graph + self.uri = uri + self.updateOutput = updateOutput + self.setupGroups() + + def setupGroups(self): + self.groups = {} + self.groupWithIndex = {} + attrStatements = set() + for grp in self.graph.objects(self.uri, ROOM['pixelGroup']): + s = int(self.graph.value(grp, ROOM['startIndex'])) + e = int(self.graph.value(grp, ROOM['endIndex'])) + log.info('ScanGroup %s from %s to %s', grp, s, e) + sg = ScanGroup(grp, e - s + 1) + self.groups[grp] = [s, e, sg] + for i in range(s, e + 1): + self.groupWithIndex[i] = sg, i - s + attrStatements.update(self.graph.triples((grp, None, None))) + self.onStatements(attrStatements, _groups=False) + + def maxIndex(self): + return max(v[1] for v in self.groups.itervalues()) + + def hostStatements(self): + return ( + [(self.uri, ROOM['pixelGroup'], grp) for grp in self.groups.keys()] + + sum([v[2].currentStatements() + for v in self.groups.itervalues()], [])) + + def getColorOrder(self, graph, uri): + colorOrder = graph.value(uri, ROOM['colorOrder'], + default=ROOM['ledColorOrder/RGB']) + head, tail = str(colorOrder).rsplit('/', 1) + if head != str(ROOM['ledColorOrder']): + raise NotImplementedError('%r colorOrder %r' % (uri, colorOrder)) + stripType = None + return colorOrder, stripType + + def step(self): + # if animating... + self.updateOutput() + + def onStatements(self, statements, _groups=True): + + needSetup = False + animateCalls = {} # group uri : kw for animateTo + for s, p, o in statements: + if s not in self.groups: + # missing the case that you just added a new group + continue + if p in args: + k, conv = args[p] + animateCalls.setdefault(s, {})[k] = conv(o.toPython()) + else: + needSetup = True + + if needSetup and _groups: + self.setupGroups() + for grp, kw in animateCalls.items(): + for pred, (k, conv) in args.items(): + if k not in kw: + v = self.graph.value(grp, pred) + if v is not None: + kw[k] = conv(v.toPython()) + + self.groups[grp][2].animateTo(**kw) + + def outputPatterns(self): + pats = [] + for grp in self.groups: + for attr in args: + pats.append((grp, attr, None)) + return pats + + def outputWidgets(self): + return [] + + def currentColors(self): + for _, _, sg in self.groups.values(): + sg.updateCurrent() + for idx in range(self.maxIndex() + 1): + sg, offset = self.groupWithIndex[idx] + r, g, b = sg.colorForIndex(offset) + yield idx, (r, g, b)