Changeset - 37cbb245d93c
[Not reviewed]
default
0 10 0
Drew Perttula - 8 years ago 2017-03-28 07:48:10
drewp@bigasterisk.com
fix tests. add logging, some mypy types.
Ignore-this: 61371f65438a4e77f70d21cc5d5193bf
10 files changed with 164 insertions and 152 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.namespaces import L9, RDF
 
from light9.collector.output import setListElem
 
from light9.collector.device import toOutputAttrs, resolve
 

	
 
# types only
 
from rdflib import Graph, URIRef
 
from typing import List, Dict, Tuple, Any, TypeVar, Generic
 
from light9.collector.output import Output
 

	
 
ClientType = TypeVar('ClientType')
 
ClientSessionType = TypeVar('ClientSessionType')
 

	
 
log = logging.getLogger('collector')
 

	
 
def outputMap(graph, outputs):
 
    # type: (Graph, List[Output]) -> Dict[Tuple[URIRef, URIRef], Tuple[Output, int]]
 
    """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']):
 
        log.info('mapping DeviceClass %s', dc)
 
        for dev in graph.subjects(RDF.type, dc):
 
            output = outputByUri[graph.value(dev, L9['dmxUniverse'])]
 
            log.info('  mapping device %s', dev)
 
            universe = graph.value(dev, L9['dmxUniverse'])
 
            try:
 
                output = outputByUri[universe]
 
            except Exception:
 
                log.warn('dev %r :dmxUniverse %r', dev, universe)
 
                raise
 
            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)
 
                log.info('    map %s to %s,%s', outputAttr, output, index)
 
    return ret
 
        
 
class Collector(object):
 
class Collector(Generic[ClientType, ClientSessionType]):
 
    def __init__(self, graph, outputs, clientTimeoutSec=10):
 
        # type: (Graph, List[Output], float) -> None
 
        self.graph = graph
 
        self.outputs = outputs
 
        self.clientTimeoutSec = clientTimeoutSec
 

	
 
        self.graph.addHandler(self.rebuildOutputMap)
 
        self.lastRequest = {} # client : (session, time, {(dev,devattr): latestValue})
 
        self.stickyAttrs = {} # (dev, devattr): value to use instead of 0
 

	
 
        # client : (session, time, {(dev,devattr): latestValue})
 
        self.lastRequest = {} # type: Dict[ClientType, Tuple[ClientSessionType, 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.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, _) in self.lastRequest.iteritems():
 
        for c, (_, t, _2) in self.lastRequest.iteritems():
 
            if t < now - self.clientTimeoutSec:
 
                staleClients.append(c)
 
        for c in staleClients:
 
            log.info('forgetting stale client %r', c)
 
            del self.lastRequest[c]
 

	
 
    def resolvedSettingsDict(self, settingsList):
 
        out = {}
 
        # 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:
 
            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()
 
        requestLag = now - sendTime
 
        if requestLag > .1:
 
            log.warn('collector.setAttrs from %s is running %.1fms after the request was made',
 
                     client, requestLag * 1000)
 
        self._warnOnLateRequests(client, now, sendTime)
 

	
 
        self._forgetStaleClients(now)
 

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

	
 
        deviceAttrs = {} # device: {deviceAttr: value}       
 
        for _, _, lastSettings in self.lastRequest.itervalues():
 
            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))
 
@@ -119,24 +146,25 @@ class Collector(object):
 
        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/collector_test.py
Show inline comments
 
import unittest
 
import datetime
 
import datetime, time
 
from freezegun import freeze_time
 
from rdflib import Namespace
 
from rdflib import Namespace, URIRef
 

	
 
from light9.namespaces import L9, DEV
 
from light9.collector.collector import Collector, outputMap
 
from light9.rdfdb.mock_syncedgraph import MockSyncedGraph
 

	
 
UDMX = Namespace('http://light9.bigasterisk.com/output/udmx/')
 
DMX0 = Namespace('http://light9.bigasterisk.com/output/dmx0/')
 

	
 
PREFIX = '''
 
   @prefix : <http://light9.bigasterisk.com/> .
 
        @prefix dev: <http://light9.bigasterisk.com/device/> .
 
        @prefix udmx: <http://light9.bigasterisk.com/output/udmx/> .
 
        @prefix dmx0: <http://light9.bigasterisk.com/output/dmx0/> .
 
'''
 

	
 
THEATER = '''
 
        :brightness         a :DeviceAttr; :dataType :scalar .
 

	
 
        :SimpleDimmer a :DeviceClass;
 
          :deviceAttr :brightness;
 
          :attr
 
            [ :outputAttr :level; :dmxOffset 0 ] .
 
                
 
        :ChauvetColorStrip a :DeviceClass;
 
          :deviceAttr :color;
 
          :attr
 
            [ :outputAttr :mode;  :dmxOffset 0 ],
 
            [ :outputAttr :red;   :dmxOffset 1 ],
 
            [ :outputAttr :green; :dmxOffset 2 ],
 
            [ :outputAttr :blue;  :dmxOffset 3 ] .
 

	
 
'''
 

	
 
t0 = 0 # time
 

	
 
class MockOutput(object):
 
    def __init__(self, connections):
 
    def __init__(self, uri, connections):
 
        self.connections = connections
 
        self.updates = []
 
        self.uri = uri
 
        self.numChannels = 4
 

	
 
    def allConnections(self):
 
        return self.connections
 

	
 
    def update(self, values):
 
        self.updates.append(values)
 

	
 
    def flush(self):
 
        self.updates.append('flush')
 

	
 
@unittest.skip("outputMap got rewritten and mostly doesn't raise on these cases")
 
class TestOutputMap(unittest.TestCase):
 
    def testWorking(self):
 
        out0 = MockOutput([(0, DMX0['c1'])])
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        m = outputMap(MockSyncedGraph(PREFIX + '''
 
          dmx0:c1 :connectedTo dev:inst1Brightness .
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 
        self.assertEqual({(DEV['inst1'], L9['brightness']): (out0, 0)}, m)
 
        
 
    def testMissingOutput(self):
 
        out0 = MockOutput([(0, DMX0['c1'])])
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        self.assertRaises(KeyError, outputMap, MockSyncedGraph(PREFIX + '''
 
          dmx0:c2 :connectedTo dev:inst1Brightness .
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 

	
 
    def testMissingOutputConnection(self):
 
        out0 = MockOutput([(0, DMX0['c1'])])
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        self.assertRaises(ValueError, outputMap, MockSyncedGraph(PREFIX + '''
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 

	
 
    def testMultipleOutputConnections(self):
 
        out0 = MockOutput([(0, DMX0['c1'])])
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        self.assertRaises(ValueError, outputMap, MockSyncedGraph(PREFIX + '''
 
          dmx0:c1 :connectedTo dev:inst1Brightness .
 
          dmx0:c2 :connectedTo dev:inst1Brightness .
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 

	
 

	
 

	
 
class TestCollector(unittest.TestCase):
 
    def setUp(self):
 
        self.config = MockSyncedGraph(PREFIX + '''
 

	
 
        udmx:c1 :connectedTo dev:colorStripRed .
 
        udmx:c2 :connectedTo dev:colorStripGreen .
 
        udmx:c3 :connectedTo dev:colorStripBlue .
 
        udmx:c4 :connectedTo dev:colorStripMode .
 
        self.config = MockSyncedGraph(PREFIX + THEATER + '''
 

	
 
        dev:colorStrip a :Device, :ChauvetColorStrip;
 
          :dmxUniverse udmx:; :dmxBase 1;
 
          :red dev:colorStripRed;
 
          :green dev:colorStripGreen;
 
          :blue dev:colorStripBlue;
 
          :mode dev:colorStripMode .
 

	
 
        dmx0:c1 :connectedTo dev:inst1Brightness .
 
        dev:inst1 a :Device, :Dimmer;
 
          :brightness dev:inst1Brightness .
 
        dev:inst1 a :Device, :SimpleDimmer;
 
          :dmxUniverse dmx0:; :dmxBase 1;
 
          :level dev:inst1Brightness .
 
        ''')
 

	
 
        self.dmx0 = MockOutput([(0, DMX0['c1'])])
 
        self.udmx = MockOutput([(0, UDMX['c1']),
 
        self.dmx0 = MockOutput(DMX0[None], [(0, DMX0['c1'])])
 
        self.udmx = MockOutput(UDMX[None], [(0, UDMX['c1']),
 
                                (1, UDMX['c2']),
 
                                (2, UDMX['c3']),
 
                                (3, UDMX['c4'])])
 

	
 
    def testRoutesColorOutput(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 

	
 
        c.setAttrs('client', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#00ff00')])
 
                   [(DEV['colorStrip'], L9['color'], '#00ff00')], t0)
 

	
 
        self.assertEqual([[0, 255, 0, 215], 'flush'], self.udmx.updates)
 
        self.assertEqual([], self.dmx0.updates)
 
        self.assertEqual([[215, 0, 255, 0], 'flush'], self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush'], self.dmx0.updates)
 

	
 
    def testOutputMaxOfTwoClients(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 

	
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#ff0000')])
 
                   [(DEV['colorStrip'], L9['color'], '#ff0000')], t0)
 
        c.setAttrs('client2', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#333333')])
 
                   [(DEV['colorStrip'], L9['color'], '#333333')], t0)
 

	
 
        self.assertEqual([[255, 0, 0, 215], 'flush',
 
                          [255, 51, 51, 215], 'flush'],
 
        self.assertEqual([[215, 255, 0, 0], 'flush',
 
                          [215, 255, 51, 51], 'flush'],
 
                         self.udmx.updates)
 
        self.assertEqual([], self.dmx0.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush', [0, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 

	
 
    def testClientOnSameOutputIsRememberedOverCalls(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 

	
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#080000')])
 
                   [(DEV['colorStrip'], L9['color'], '#080000')], t0)
 
        c.setAttrs('client2', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#060000')])
 
                   [(DEV['colorStrip'], L9['color'], '#060000')], t0)
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#050000')])
 
                   [(DEV['colorStrip'], L9['color'], '#050000')], t0)
 

	
 
        self.assertEqual([[8, 0, 0, 215], 'flush',
 
                          [8, 0, 0, 215], 'flush',
 
                          [6, 0, 0, 215], 'flush'],
 
        self.assertEqual([[215, 8, 0, 0], 'flush',
 
                          [215, 8, 0, 0], 'flush',
 
                          [215, 6, 0, 0], 'flush'],
 
                         self.udmx.updates)
 
        self.assertEqual([], self.dmx0.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 

	
 
    def testClientsOnDifferentOutputs(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 

	
 
        c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['color'], '#aa0000')])
 
        c.setAttrs('client2', 'sess1', [(DEV['inst1'], L9['brightness'], .5)])
 
        c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['color'], '#aa0000')], t0)
 
        c.setAttrs('client2', 'sess1', [(DEV['inst1'], L9['brightness'], .5)], t0)
 

	
 
        # ok that udmx is flushed twice- it can screen out its own duplicates
 
        self.assertEqual([[170, 0, 0, 215], 'flush',
 
                          [170, 0, 0, 215], 'flush'], self.udmx.updates)
 
        self.assertEqual([[127], 'flush'], self.dmx0.updates)
 
        self.assertEqual([[215, 170, 0, 0], 'flush',
 
                          [215, 170, 0, 0], 'flush'], self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush',
 
                          [127, 0, 0, 0], 'flush'], self.dmx0.updates)
 

	
 
    def testNewSessionReplacesPreviousOutput(self):
 
        # ..as opposed to getting max'd with it
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 

	
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .8)])
 
        c.setAttrs('client1', 'sess2', [(DEV['inst1'], L9['brightness'], .5)])
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .8)], t0)
 
        c.setAttrs('client1', 'sess2', [(DEV['inst1'], L9['brightness'], .5)], t0)
 

	
 
        self.assertEqual([[204], 'flush', [127], 'flush'], self.dmx0.updates)
 
        self.assertEqual([[204, 0, 0, 0], 'flush',
 
                          [127, 0, 0, 0], 'flush'], self.dmx0.updates)
 

	
 
    def testNewSessionDropsPreviousSettingsOfOtherAttrs(self):
 
        
 
        c = Collector(MockSyncedGraph(PREFIX + '''
 

	
 
        udmx:c1 :connectedTo dev:colorStripRed .
 
        udmx:c2 :connectedTo dev:colorStripGreen .
 
        udmx:c3 :connectedTo dev:colorStripBlue .
 
        udmx:c4 :connectedTo dev:colorStripMode .
 
        c = Collector(MockSyncedGraph(PREFIX + THEATER + '''
 

	
 
        dev:colorStrip a :Device, :ChauvetColorStrip;
 
          :dmxUniverse udmx:; :dmxBase 1;
 
          :red dev:colorStripRed;
 
          :green dev:colorStripGreen;
 
          :blue dev:colorStripBlue;
 
          :mode dev:colorStripMode .
 

	
 
        dmx0:c1 :connectedTo dev:inst1Brightness .
 
        dev:inst1 a :Device, :Dimmer;
 
          :brightness dev:inst1Brightness .
 
        dev:inst1 a :Device, :SimpleDimmer;
 
          :dmxUniverse dmx0:; :dmxBase 0;
 
          :level dev:inst1Brightness .
 
        '''), outputs=[self.dmx0, self.udmx])
 

	
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#ff0000')])
 
                   [(DEV['colorStrip'], L9['color'], '#ff0000')], t0)
 
        c.setAttrs('client1', 'sess2',
 
                   [(DEV['colorStrip'], L9['color'], '#00ff00')])
 
                   [(DEV['colorStrip'], L9['color'], '#00ff00')], t0)
 

	
 
        self.assertEqual([[255, 0, 0, 215], 'flush',
 
                          [0, 255, 0, 215], 'flush'], self.udmx.updates)
 
        self.assertEqual([[215, 255, 0, 0], 'flush',
 
                          [215, 0, 255, 0], 'flush'], self.udmx.updates)
 

	
 
    def testClientIsForgottenAfterAWhile(self):
 
        with freeze_time(datetime.datetime.now()) as ft:
 
            c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
            c.setAttrs('cli1', 'sess1', [(DEV['inst1'], L9['brightness'], .5)])
 
            c.setAttrs('cli1', 'sess1', [(DEV['inst1'], L9['brightness'], .5)],
 
                       time.time())
 
            ft.tick(delta=datetime.timedelta(seconds=1))
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .2)])
 
            # this max's with cli1's value so we still see .5
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .2)], 
 
                       time.time())
 
            ft.tick(delta=datetime.timedelta(seconds=9.1))
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .4)])
 
            self.assertEqual([[127], 'flush', [127], 'flush', [102], 'flush'],
 
            # now cli1 is forgotten, so our value appears
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .4)], 
 
                       time.time())
 
            self.assertEqual([[127, 0, 0, 0], 'flush',
 
                              [127, 0, 0, 0], 'flush',
 
                              [102, 0, 0, 0], 'flush'],
 
                             self.dmx0.updates)
 

	
 
    def testClientUpdatesAreCollected(self):
 
        # second call to setAttrs doesn't forget the first
 
    def testClientUpdatesAreNotMerged(self):
 
        # second call to setAttrs forgets the first
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        t0 = time.time()
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .5)], t0)
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], 1)], t0)
 
        c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['color'], '#00ff00')], t0)
 

	
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .5)])
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], 1)])
 
        c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['color'], '#00ff00')])
 

	
 
        self.assertEqual([[0, 255, 0, 215], 'flush'], self.udmx.updates)
 
        self.assertEqual([[127], 'flush', [255], 'flush', [255], 'flush'], self.dmx0.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush',
 
                          [215, 0, 255, 0], 'flush'],
 
                         self.udmx.updates)
 
        self.assertEqual([[127, 0, 0, 0], 'flush',
 
                          [255, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 

	
 
    def testRepeatedAttributesInOneRequestGetResolved(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        
 
        c.setAttrs('client1', 'sess1', [
 
            (DEV['inst1'], L9['brightness'], .5),
 
            (DEV['inst1'], L9['brightness'], .3),
 
        ])
 
        self.assertEqual([[127], 'flush'], self.dmx0.updates)
 
        ], t0)
 
        self.assertEqual([[127, 0, 0, 0], 'flush'], self.dmx0.updates)
 

	
 
        c.setAttrs('client1', 'sess1', [
 
            (DEV['inst1'], L9['brightness'], .3),
 
            (DEV['inst1'], L9['brightness'], .5),
 
        ])
 
        self.assertEqual([[127], 'flush', [127], 'flush'], self.dmx0.updates)
 
        ], t0)
 
        self.assertEqual([[127, 0, 0, 0], 'flush',
 
                          [127, 0, 0, 0], 'flush'], self.dmx0.updates)
 

	
light9/collector/device.py
Show inline comments
 
@@ -67,25 +67,25 @@ def resolve(deviceType, deviceAttr, valu
 
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())
 
        return float(out.toPython()) if isinstance(out, Literal) else out
 

	
 
    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),
light9/collector/device_test.py
Show inline comments
 
@@ -11,26 +11,26 @@ class TestUnknownDevice(unittest.TestCas
 
class TestColorStrip(unittest.TestCase):
 
    def testConvertDeviceToOutputAttrs(self):
 
        out = toOutputAttrs(L9['ChauvetColorStrip'],
 
                            {L9['color']: Literal('#ff0000')})
 
        self.assertEqual({L9['mode']: 215,
 
                          L9['red']: 255,
 
                          L9['green']: 0,
 
                          L9['blue']: 0
 
                      }, out)
 
        
 
class TestDimmer(unittest.TestCase):
 
    def testConvert(self):
 
        self.assertEqual({L9['brightness']: 127},
 
                         toOutputAttrs(L9['Dimmer'], {L9['brightness']: .5}))
 
        self.assertEqual({L9['level']: 127},
 
                         toOutputAttrs(L9['SimpleDimmer'], {L9['brightness']: .5}))
 

	
 
class TestMini15(unittest.TestCase):
 
    def testConvertColor(self):
 
        out = toOutputAttrs(L9['Mini15'], {L9['color']: '#010203'})
 
        self.assertEqual(255, out[L9['dimmer']])
 
        self.assertEqual(1, out[L9['red']])
 
        self.assertEqual(2, out[L9['green']])
 
        self.assertEqual(3, out[L9['blue']])
 
    def testConvertRotation(self):
 
        out = toOutputAttrs(L9['Mini15'], {L9['rx']: Literal(90), L9['ry']: Literal(45)})
 
        self.assertEqual(42, out[L9['xRotation']])
 
        self.assertEqual(127, out[L9['xFine']])
light9/collector/output.py
Show inline comments
 
@@ -14,24 +14,26 @@ def setListElem(outList, index, value, f
 
        outList.extend([fill] * (index - len(outList)))
 
    if len(outList) <= index:
 
        outList.append(value)
 
    else:
 
        outList[index] = combine(outList[index], value)
 

	
 
class Output(object):
 
    """
 
    send an array of values to some output device. Call update as
 
    often as you want- the result will be sent as soon as possible,
 
    and with repeats as needed to outlast hardware timeouts.
 
    """
 
    uri = None  # type: URIRef
 
    numChannels = None  # type: int
 
    def __init__(self):
 
        raise NotImplementedError
 
        
 
    def allConnections(self):
 
        """
 
        sequence of (index, uri) for the uris we can output, and which
 
        index in 'values' to use for them
 
        """
 
        raise NotImplementedError
 

	
 
    
 
    def update(self, values):
light9/collector/output_test.py
Show inline comments
 
@@ -24,24 +24,16 @@ class TestSetListElem(unittest.TestCase)
 
        setListElem(x, 5, 0)
 
        self.assertEqual([0, 1, 0, 0, 0, 0], x)
 
    def testCombineMax(self):
 
        x = [0, 1]
 
        setListElem(x, 1, 0, combine=max)
 
        self.assertEqual([0, 1], x)
 
    def testCombineHasNoEffectOnNewElems(self):
 
        x = [0, 1]
 
        setListElem(x, 2, 1, combine=max)
 
        self.assertEqual([0, 1, 1], x)
 
        
 
class TestDmxOutput(unittest.TestCase):
 
    def testGeneratesConnectionList(self):
 
        out = DmxOutput(L9['output/udmx/'], 3)
 
        self.assertEqual([
 
            (0, L9['output/udmx/c1']),
 
            (1, L9['output/udmx/c2']),
 
            (2, L9['output/udmx/c3']),
 
        ], list(out.allConnections()))
 

	
 
    def testFlushIsNoop(self):
 
        out = DmxOutput(L9['output/udmx/'], 3)
 
        out.flush()
 
        
light9/io/__init__.py
Show inline comments
 
from __future__ import division
 
import sys
 

	
 
class BaseIO:
 
class BaseIO(object):
 
    def __init__(self):
 
        self.dummy=1
 
        self.__name__ = 'BaseIO'
 
        # please override and set __name__ to your class name
 

	
 
    def golive(self):
 
        """call this if you want to promote the dummy object becomes a live object"""
 
        print "IO: %s is going live" % self.__name__
 
        self.dummy=0
 
        # you'd override with additional startup stuff here,
 
        # perhaps even loading a module and saving it to a class
 
        # attr so the subclass-specific functions can use it
 
@@ -74,66 +74,12 @@ class UsbDMX(BaseIO):
 
                self.out = Dmx(self.port)
 
        return self.out
 

	
 
    def sendlevels(self, levels):
 
        if self.dummy:
 
            return
 
        # I was outputting on 76 and it was turning on the light at
 
        # dmx75. So I added the 0 byte.
 
        packet = '\x00' + ''.join([chr(int(lev * 255 / 100)) 
 
                                  for lev in levels]) + "\x55"
 
        self._dmx().write(packet)
 
        
 
class SerialPots(BaseIO):
 
    """
 
    this is a dummy object (that returns zeros forever) until you call startup()
 
    which makes it bind to the port, etc
 
    
 
    """
 
    def __init__(self):
 
        # no init here- call getport() to actually initialize
 
        self.dummy=1
 
        self.__name__='SerialPots' # i thought this was automatic!
 

	
 
    def golive(self):
 
        """
 
        ls -l /dev/i2c-0
 
        crw-rw-rw-    1 root     root      89,   0 Jul 11 12:27 /dev/i2c-0
 
        """
 
        import serport
 
        self.serport = serport
 
        
 
        self.f = open("/dev/i2c-0","rw")
 

	
 
        # this is for a chip with A0,A1,A2 lines all low:
 
        port = 72
 

	
 
        from fcntl import *
 

	
 
        I2C_SLAVE = 0x0703  #/* Change slave address                 */
 
        ioctl(self.f,I2C_SLAVE,port)
 
        self.dummy=0
 

	
 
    def godummy(self):
 
        BaseIO.godummy(self)
 
        self.f.close()
 

	
 
    def getlevels(self):
 
        if self.dummy:
 
            return (0,0,0,0)
 
        else:
 
            return self.serport.read_all_adc(self.f.fileno())
 

	
 

	
 
if __name__=='__main__':
 
    
 
    """ tester program that just dumps levels for a while """
 
    from time import sleep
 
    from serport import *
 

	
 
    i=0
 
    while i<100:
 
        sleep(.033)
 
        i=i+1
 

	
 
        print read_all_adc(f.fileno())
 

	
light9/rdfdb/graphfile_test.py
Show inline comments
 
@@ -18,22 +18,22 @@ class TestGraphFileOutput(unittest.TestC
 
            return Graph()
 
        gf = GraphFile(mock.Mock(), tf.name, URIRef('uri'), mock.Mock(), getSubgraph)
 
        gf.reread()
 
        
 
        newGraph = Graph()
 
        newGraph.add((URIRef('http://example.com/boo'),
 
                      URIRef('http://example.com/n/two'),
 
                      URIRef('http://example.com/other/ns')))
 
        gf.dirty(newGraph)
 
        gf.flush()
 
        wroteContent = open(tf.name).read()
 
        self.assertEqual('''@prefix : <http://example.com/> .
 
@prefix dev: <http://light9.bigasterisk.com/device/> .
 
@prefix effect: <http://light9.bigasterisk.com/effect/> .
 
@prefix n: <http://example.com/n/> .
 
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
 
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
 
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
 
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
 

	
 
:boo n:two <http://example.com/other/ns> .
 

	
 
''', wroteContent)
 

	
makefile
Show inline comments
 
@@ -93,12 +93,20 @@ darcs_show_checkpoint:
 

	
 
/usr/share/arduino/Arduino.mk:
 
	sudo aptitude install arduino-mk
 

	
 
arduino_upload: /usr/share/arduino/Arduino.mk
 
	cd rgbled
 
	make upload
 

	
 
effect_node_setup: create_virtualenv packages binexec install_python_deps
 

	
 
coffee:
 
	zsh -c 'coffee -cw light9/web/{.,live,timeline}/*.coffee'
 

	
 
env-mypy/bin/mypy:
 
	mkdir -p env-mypy
 
	virtualenv -p /usr/bin/python3  env-mypy/
 
	env-mypy/bin/pip install mypy==0.501 lxml==3.7.3
 

	
 
mypy-collector: env-mypy/bin/mypy
 
	env-mypy/bin/mypy --py2 --ignore-missing-imports --strict-optional --custom-typeshed-dir stubs --html-report /tmp/rep bin/collector light9/collector/*.py
requirements.txt
Show inline comments
 
@@ -25,12 +25,14 @@ mock==1.0.1
 
toposort==1.0
 
pyserial==2.7
 

	
 
statprof==0.1.2
 
txzmq==0.7.4
 

	
 
pyusb==1.0.0
 
coverage==4.1
 
klein==15.3.1
 
git+http://github.com/drewp/scales.git@448d59fb491b7631877528e7695a93553bfaaa93#egg=scales
 
colormath==2.1.1
 
noise==1.2.2
 

	
 
typing==3.6.1
0 comments (0 inline, 0 general)