Changeset - 0fc61e701347
[Not reviewed]
default
0 2 0
Drew Perttula - 8 years ago 2017-06-03 20:55:01
drewp@bigasterisk.com
collector: don't confuse two clients with the same name- use the session
Ignore-this: 19d24d31b27b420a5006c32dafa49aac
2 files changed with 33 insertions and 25 deletions:
0 comments (0 inline, 0 general)
light9/collector/collector.py
Show inline comments
 
@@ -51,111 +51,120 @@ class Collector(Generic[ClientType, Clie
 
    def __init__(self, graph, outputs, listeners=None, clientTimeoutSec=10):
 
        # type: (Graph, List[Output], List[Listener], float) -> None
 
        self.graph = graph
 
        self.outputs = outputs
 
        self.listeners = listeners
 
        self.clientTimeoutSec = clientTimeoutSec
 
        self.initTime = time.time()
 
        self.allDevices = set()
 

	
 
        self.graph.addHandler(self.rebuildOutputMap)
 

	
 
        # client : (session, time, {(dev,devattr): latestValue})
 
        self.lastRequest = {} # type: Dict[ClientType, Tuple[ClientSessionType, float, Dict[Tuple[URIRef, URIRef], float]]]
 
        self.lastRequest = {} # type: Dict[Tuple[ClientType, ClientSessionType], Tuple[float, Dict[Tuple[URIRef, URIRef], float]]]
 

	
 
        # (dev, devAttr): value to use instead of 0
 
        self.stickyAttrs = {} # type: Dict[Tuple[URIRef, URIRef], float] 
 

	
 
    def rebuildOutputMap(self):
 
        self.outputMap = outputMap(self.graph, self.outputs) # (device, outputattr) : (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.allDevices.add(dev)
 
                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):
 
        # type: (float) -> None
 
        staleClients = []
 
        for c, (_, t, _2) in self.lastRequest.iteritems():
 
        staleClientSessions = []
 
        for c, (t, _) in self.lastRequest.iteritems():
 
            if t < now - self.clientTimeoutSec:
 
                staleClients.append(c)
 
        for c in staleClients:
 
                staleClientSessions.append(c)
 
        for c in staleClientSessions:
 
            log.info('forgetting stale client %r', c)
 
            del self.lastRequest[c]
 

	
 
    # todo: move to settings.py
 
    def resolvedSettingsDict(self, settingsList):
 
        # type: (List[Tuple[URIRef, URIRef, float]]) -> Dict[Tuple[URIRef, URIRef], float]
 
        out = {} # type: Dict[Tuple[URIRef, URIRef], float]
 
        for d, da, v in settingsList:
 
            if (d, da) in out:
 
                out[(d, da)] = resolve(d, da, [out[(d, da)], v])
 
            else:
 
                out[(d, da)] = v
 
        return out
 

	
 
    def _warnOnLateRequests(self, client, now, sendTime):
 
        requestLag = now - sendTime
 
        if requestLag > .1 and now > self.initTime + 5:
 
            log.warn('collector.setAttrs from %s is running %.1fms after the request was made',
 
                     client, requestLag * 1000)
 
        
 
    def setAttrs(self, client, clientSession, settings, sendTime):
 
        """
 
        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._warnOnLateRequests(client, now, sendTime)
 

	
 
        self._forgetStaleClients(now)
 

	
 
        uniqueSettings = self.resolvedSettingsDict(settings)
 
        self.lastRequest[client] = (clientSession, now, uniqueSettings)
 

	
 
    def _merge(self, lastRequests):
 
        deviceAttrs = {} # device: {deviceAttr: value}       
 
        for _, _, lastSettings in self.lastRequest.itervalues():
 
        for _, lastSettings in lastRequests:
 
            for (device, deviceAttr), value in lastSettings.iteritems():
 
                if (device, deviceAttr) in self.remapOut:
 
                    start, end = self.remapOut[(device, deviceAttr)]
 
                    value = Literal(start + float(value) * (end - start))
 

	
 
                attrs = deviceAttrs.setdefault(device, {})
 
                if deviceAttr in attrs:
 
                    value = resolve(device, deviceAttr, [attrs[deviceAttr], value])
 
                attrs[deviceAttr] = value
 
                # list should come from the graph. these are attrs
 
                # that should default to holding the last position,
 
                # not going to 0.
 
                if deviceAttr in [L9['rx'], L9['ry'], L9['zoom'], L9['focus']]:
 
                    self.stickyAttrs[(device, deviceAttr)] = value
 

	
 
        
 
        # e.g. don't let an unspecified rotation go to 0
 
        for (d, da), v in self.stickyAttrs.iteritems():
 
            daDict = deviceAttrs.setdefault(d, {})
 
            if da not in daDict:
 
                daDict[da] = v
 
                    
 
        return deviceAttrs
 

	
 
    def setAttrs(self, client, clientSession, settings, sendTime):
 
        """
 
        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.
 

	
 
        client is a string naming the type of client. (client,
 
        clientSession) is a unique client instance.
 

	
 
        Each client session's last settings will be forgotten after
 
        clientTimeoutSec.
 
        """
 
        now = time.time()
 
        self._warnOnLateRequests(client, now, sendTime)
 

	
 
        self._forgetStaleClients(now)
 

	
 
        uniqueSettings = self.resolvedSettingsDict(settings)
 
        self.lastRequest[(client, clientSession)] = (now, uniqueSettings)
 

	
 
        deviceAttrs = self._merge(self.lastRequest.itervalues())
 
        
 
        outputAttrs = {} # device: {outputAttr: value}
 
        for d in self.allDevices:
 
            try:
 
                devType = self.deviceType[d]
 
            except KeyError:
 
                log.warn("request for output to unconfigured device %s" % d)
 
                continue
 
            outputAttrs[d] = toOutputAttrs(devType, deviceAttrs.get(d, {}))
 
            if self.listeners:
 
                self.listeners.outputAttrsSet(d, outputAttrs[d], self.outputMap)
 
        
 
        pendingOut = {} # output : values
light9/collector/device.py
Show inline comments
 
@@ -53,25 +53,24 @@ def resolve(deviceType, deviceAttr, valu
 
    if deviceAttr in [L9['rx'], L9['ry'], L9['zoom'], L9['focus'], L9['iris']]:
 
        floatVals = []
 
        for v in values:
 
            if isinstance(v, Literal):
 
                floatVals.append(float(v.toPython()))
 
            elif isinstance(v, (int, float)):
 
                floatVals.append(float(v))
 
            else:
 
                raise TypeError(repr(v))
 

	
 
        # averaging with zeros? not so good
 
        return Literal(sum(floatVals) / len(floatVals))
 
    
 
    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)
0 comments (0 inline, 0 general)