Changeset - 931d2dafca12
[Not reviewed]
default
0 3 0
drewp@bigasterisk.com - 9 years ago 2016-06-11 22:08:37
drewp@bigasterisk.com
new feature: values can have their range remapped in the device processing
Ignore-this: 542047e6307a304f2aa52a69d134ba21
3 files changed with 22 insertions and 1 deletions:
0 comments (0 inline, 0 general)
light9/collector/collector.py
Show inline comments
 
from __future__ import division
 
import time
 
import logging
 
from rdflib import Literal
 
from light9.namespaces import L9, RDF, DEV
 
from light9.collector.output import setListElem
 
from light9.collector.device import toOutputAttrs, resolve
 

	
 
log = logging.getLogger('collector')
 

	
 
def outputMap(graph, outputs):
 
    """From rdf config graph, compute a map of
 
       (device, outputattr) : (output, index)
 
    that explains which output index to set for any device update.
 
    """
 
    ret = {}
 

	
 
    outputByUri = {}  # universeUri : output
 
    for out in outputs:
 
        outputByUri[out.uri] = out
 

	
 
    for dc in graph.subjects(RDF.type, L9['DeviceClass']):
 
        for dev in graph.subjects(RDF.type, dc):
 
            output = outputByUri[graph.value(dev, L9['dmxUniverse'])]
 
            dmxBase = int(graph.value(dev, L9['dmxBase']).toPython())
 
            for row in graph.objects(dc, L9['attr']):
 
                outputAttr = graph.value(row, L9['outputAttr'])
 
                offset = int(graph.value(row, L9['dmxOffset']).toPython())
 
                index = dmxBase + offset - 1
 
                ret[(dev, outputAttr)] = (output, index)
 
                log.info('map %s,%s to %s,%s', dev, outputAttr, output, index)
 
    return ret
 
        
 
class Collector(object):
 
    def __init__(self, graph, outputs, clientTimeoutSec=10):
 
        self.graph = graph
 
        self.outputs = outputs
 
        self.clientTimeoutSec = clientTimeoutSec
 

	
 
        self.graph.addHandler(self.rebuildOutputMap)
 
        self.lastRequest = {} # client : (session, time, {(dev,attr): latestValue})
 

	
 
    def rebuildOutputMap(self):
 
        self.outputMap = outputMap(self.graph, self.outputs) # (device, attr) : (output, index)
 
        self.deviceType = {} # uri: type that's a subclass of Device
 
        self.remapOut = {} # (device, deviceAttr) : (start, end)
 
        for dc in self.graph.subjects(RDF.type, L9['DeviceClass']):
 
            for dev in self.graph.subjects(RDF.type, dc):
 
                self.deviceType[dev] = dc
 

	
 
                for remap in self.graph.objects(dev, L9['outputAttrRange']):
 
                    attr = self.graph.value(remap, L9['outputAttr'])
 
                    start = float(self.graph.value(remap, L9['start']))
 
                    end = float(self.graph.value(remap, L9['end']))
 
                    self.remapOut[(dev, attr)] = start, end
 

	
 
    def _forgetStaleClients(self, now):
 
        staleClients = []
 
        for c, (_, t, _) in self.lastRequest.iteritems():
 
            if t < now - self.clientTimeoutSec:
 
                staleClients.append(c)
 
        for c in staleClients:
 
            del self.lastRequest[c]
 

	
 
    def resolvedSettingsDict(self, settingsList):
 
        out = {}
 
        for d, a, v in settingsList:
 
            if (d, a) in out:
 
                out[(d, a)] = resolve(d, a, [out[(d, a)], v])
 
            else:
 
                out[(d, a)] = v
 
        return out
 

	
 
    def setAttrs(self, client, clientSession, settings):
 
        """
 
        settings is a list of (device, attr, value). These attrs are
 
        device attrs. We resolve conflicting values, process them into
 
        output attrs, and call Output.update/Output.flush to send the
 
        new outputs.
 

	
 
        Call with settings=[] to ping us that your session isn't dead.
 
        """
 
        now = time.time()
 

	
 
        self._forgetStaleClients(now)
 

	
 
        if 0: # client updates their past requests?
 
            row = self.lastRequest.get(client)
 
            if row is not None:
 
                sess, _, prevClientSettings = row
 
                if sess != clientSession:
 
                    prevClientSettings = {}
 
            else:
 
                prevClientSettings = {}
 
        else: # client always provides all the nonzero settings it wants
 
            prevClientSettings = {}
 
            
 
        prevClientSettings.update(self.resolvedSettingsDict(settings))
 
        self.lastRequest[client] = (clientSession, now, prevClientSettings)
 

	
 

	
 
        # inputs that are omitted, implying a zero, should be added
 
        # back here if they would remap to something nonzero.
 
        
 
        deviceAttrs = {} # device: {deviceAttr: value}
 
        for _, _, lastSettings in self.lastRequest.itervalues():
 
            for (device, deviceAttr), value in lastSettings.iteritems():
 
                attrs = deviceAttrs.setdefault(device, {})
 
                if deviceAttr in attrs:
 
                    value = resolve(device, deviceAttr, [attrs[deviceAttr],
 
                                                         value])
 
                if (device, deviceAttr) in self.remapOut:
 
                    start, end = self.remapOut[(device, deviceAttr)]
 
                    value = Literal(start + float(value) * (end - start))
 
                attrs[deviceAttr] = value
 

	
 
        outputAttrs = {} # device: {outputAttr: value}
 
        for d in deviceAttrs:
 
            try:
 
                devType = self.deviceType[d]
 
            except KeyError:
 
                log.warn("request for output to unconfigured device %s" % d)
 
                continue
 
            outputAttrs[d] = toOutputAttrs(devType, deviceAttrs[d])
 
        
 
        pendingOut = {} # output : values
 
        for out in self.outputs:
 
            pendingOut[out] = [0] * out.numChannels
 
        for device, attrs in outputAttrs.iteritems():
 
            for outputAttr, value in attrs.iteritems():
 
                self.setAttr(device, outputAttr, value, pendingOut)
 

	
 
        dt1 = 1000 * (time.time() - now)
 
        self.flush(pendingOut)
 
        dt2 = 1000 * (time.time() - now)
 
        if dt1 > 10:
 
            print "slow setAttrs: %.1fms -> flush -> %.1fms. lr %s da %s oa %s" % (
 
                dt1, dt2, len(self.lastRequest), len(deviceAttrs), len(outputAttrs)
light9/collector/device.py
Show inline comments
 
@@ -37,48 +37,50 @@ def clamp255(x):
 
    
 
def _8bit(f):
 
    if not isinstance(f, (int, float)):
 
        raise TypeError(repr(f))
 
    return clamp255(int(f * 255))
 

	
 
def resolve(deviceType, deviceAttr, values):
 
    """
 
    return one value to use for this attr, given a set of them that
 
    have come in simultaneously. len(values) >= 1.
 
    """
 
    if len(values) == 1:
 
        return values[0]
 
    if deviceAttr == L9['color']:
 
        rgbs = [hex_to_rgb(v) for v in values]
 
        return rgb_to_hex([max(*component) for component in zip(*rgbs)])
 
    # angles should perhaps use average; gobo choice use the most-open one
 
    return max(values)
 
    
 
def toOutputAttrs(deviceType, deviceAttrSettings):
 
    """
 
    Given device attr settings like {L9['color']: Literal('#ff0000')},
 
    return a similar dict where the keys are output attrs (like
 
    L9['red']) and the values are suitable for Collector.setAttr
 

	
 
    :outputAttrRange happens before we get here.
 
    """
 
    def floatAttr(attr, default=0):
 
        out = deviceAttrSettings.get(attr)
 
        if out is None:
 
            return default
 
        return float(out.toPython())
 

	
 
    def rgbAttr(attr):
 
        color = deviceAttrSettings.get(attr, '#000000')
 
        r, g, b = hex_to_rgb(color)
 
        return r, g, b
 

	
 
    def cmyAttr(attr):
 
        rgb = sRGBColor.new_from_rgb_hex(deviceAttrSettings.get(attr, '#000000'))
 
        out = colormath.color_conversions.convert_color(rgb, CMYColor)
 
        return (
 
            _8bit(out.cmy_c),
 
            _8bit(out.cmy_m),
 
            _8bit(out.cmy_y))
 

	
 
    def fine16Attr(attr):
 
        x = floatAttr(attr)
 
        hi = _8bit(x)
 
        lo = _8bit((x * 255) % 1.0)
show/dance2016/theaterLightConfig.n3
Show inline comments
 
@prefix : <http://light9.bigasterisk.com/> .
 
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
 
@prefix dev: <http://light9.bigasterisk.com/device/> .
 
@prefix udmxB: <http://light9.bigasterisk.com/output/udmx/> .
 
@prefix dmxA: <http://light9.bigasterisk.com/output/dmx0/> .
 

	
 
#dev:colorStrip a :ChauvetColorStrip; :dmxUniverse dmxA:; :dmxBase 87 .
 
#dev:moving1 a :Mini15; rdfs:label "moving1"; :dmxUniverse udmxB:; :dmxBase 5 .
 

	
 
dev:houseSide a :SimpleDimmer; rdfs:label "house-side"; :dmxUniverse dmxA:; :dmxBase 68 .
 
dev:house1 a :SimpleDimmer; rdfs:label "house1"; :dmxUniverse dmxA:; :dmxBase 69 .
 
dev:house4 a :SimpleDimmer; rdfs:label "house4"; :dmxUniverse dmxA:; :dmxBase 70 .
 
dev:house3 a :SimpleDimmer; rdfs:label "house3"; :dmxUniverse dmxA:; :dmxBase 71 .
 
dev:house2 a :SimpleDimmer; rdfs:label "house2"; :dmxUniverse dmxA:; :dmxBase 72 .
 

	
 
dev:f1 a :SimpleDimmer; :dmxUniverse dmxA:; :dmxBase 4 .
 
dev:f2 a :SimpleDimmer; :dmxUniverse dmxA:; :dmxBase 8 .
 
dev:f3 a :SimpleDimmer; :dmxUniverse dmxA:; :dmxBase 10 .
 

	
 
dev:cycR a :SimpleDimmer; :dmxUniverse dmxA:; :dmxBase 42 .
 
dev:cycL a :SimpleDimmer; :dmxUniverse dmxA:; :dmxBase 44 .
 

	
 
dev:q1 a :MacQuantum; :dmxUniverse udmxB:; :dmxBase 325 .
 
dev:q1 :outputAttrRange dev:q1rx . dev:q1rx :outputAttr :rx; :start 0.143; :end 0.271 .
 
dev:q1 :outputAttrRange dev:q1ry . dev:q1ry :outputAttr :ry; :start 0.71; :end 0.821 .
 
dev:q2 a :MacQuantum; :dmxUniverse udmxB:; :dmxBase 352 .
 
dev:q2 :outputAttrRange dev:q2rx . dev:q2rx :outputAttr :rx; :start 0.097; :end 0.227 .
 
dev:q2 :outputAttrRange dev:q2ry . dev:q2ry :outputAttr :ry; :start 0.739; :end 0.824 .
 
dev:q3 a :MacQuantum; :dmxUniverse udmxB:; :dmxBase 379 .
 
dev:q3 :outputAttrRange dev:q3rx . dev:q3rx :outputAttr :rx; :start 0.064; :end 0.198 .
 
dev:q3 :outputAttrRange dev:q3ry . dev:q3ry :outputAttr :ry; :start 0.765; :end 0.822 .
 

	
 
dev:aura1 a :MacAura; :dmxUniverse udmxB:; :dmxBase 406 .
 
dev:aura2 a :MacAura; :dmxUniverse udmxB:; :dmxBase 420 .
 
dev:aura3 a :MacAura; :dmxUniverse udmxB:; :dmxBase 434 .
 
dev:aura4 a :MacAura; :dmxUniverse udmxB:; :dmxBase 448 .
 
dev:aura5 a :MacAura; :dmxUniverse udmxB:; :dmxBase 462 .
 
dev:auraStage a :MacAura; :dmxUniverse udmxB:; :dmxBase 476; rdfs:comment "rx=.5 ry<.45" .
 

	
 
dev:down2 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 157 .
 
dev:down3 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 193 .
 
dev:down4 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 169 .
 
dev:down5Edge a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 175 .
 
dev:backlight1 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 241 .
 
dev:backlight2 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 247 .
 
dev:backlight3 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 253 .
 
dev:backlight4 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 259 .
 
dev:backlight5 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 265 .
 
dev:hexLow3 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 223 .
 
dev:hexLow5 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 229 .
 
dev:hexLowBacklight1 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 211 .
 
dev:hexLowBacklight6 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 235 .
 
dev:hexSlant a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 319 .
 
dev:lip1 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 121 .
 
dev:lip2 a :ChauvetHex12; :dmxUniverse udmxB:; :dmxBase 127 .
0 comments (0 inline, 0 general)