diff --git a/bin/collector b/bin/collector --- a/bin/collector +++ b/bin/collector @@ -61,8 +61,8 @@ def startZmq(port, collector): def launch(graph): # todo: drive outputs with config files - outputs = [EnttecDmx(L9['output/dmx0/'], 100, '/dev/dmx0'), - Udmx(L9['output/udmx/'], 100)] + outputs = [EnttecDmx(L9['output/dmx0/'], '/dev/dmx0'), + Udmx(L9['output/udmx/'])] c = Collector(graph, outputs) server = WebServer(c) diff --git a/light9/collector/collector.py b/light9/collector/collector.py --- a/light9/collector/collector.py +++ b/light9/collector/collector.py @@ -9,35 +9,25 @@ log = logging.getLogger('collector') def outputMap(graph, outputs): """From rdf config graph, compute a map of - (device, attr) : (output, index) + (device, outputattr) : (output, index) that explains which output index to set for any device update. """ ret = {} - outIndex = {} # port : (output, index) + outputByUri = {} # universeUri : output for out in outputs: - for index, uri in out.allConnections(): - outIndex[uri] = (out, index) + outputByUri[out.uri] = out - for dev in graph.subjects(RDF.type, L9['Device']): - for attr, connectedTo in graph.predicate_objects(dev): - if attr == RDF.type: - continue - outputPorts = list(graph.subjects(L9['connectedTo'], connectedTo)) - if len(outputPorts) == 0: - raise ValueError('no output port :connectedTo %r' % connectedTo) - elif len(outputPorts) > 1: - raise ValueError('multiple output ports (%r) :connectedTo %r' % - (outputPorts, connectedTo)) - else: - try: - output, index = outIndex[outputPorts[0]] - except KeyError: - log.warn('skipping %r', outputPorts[0]) - continue - ret[(dev, attr)] = output, index - log.debug('outputMap (%r, %r) -> %r, %r', dev, attr, output, index) - + 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): @@ -52,10 +42,9 @@ class Collector(object): def rebuildOutputMap(self): self.outputMap = outputMap(self.graph, self.outputs) # (device, attr) : (output, index) self.deviceType = {} # uri: type that's a subclass of Device - for dev in self.graph.subjects(RDF.type, L9['Device']): - for t in self.graph.objects(dev, RDF.type): - if t != L9['Device']: - self.deviceType[dev] = t + for dc in self.graph.subjects(RDF.type, L9['DeviceClass']): + for dev in self.graph.subjects(RDF.type, dc): + self.deviceType[dev] = dc def _forgetStaleClients(self, now): staleClients = [] @@ -97,27 +86,33 @@ class Collector(object): self.lastRequest[client] = (clientSession, now, prevClientSettings) - deviceAttrs = {} # device: {attr: value} + deviceAttrs = {} # device: {deviceAttr: value} for _, _, lastSettings in self.lastRequest.itervalues(): - for (device, attr), value in lastSettings.iteritems(): + for (device, deviceAttr), value in lastSettings.iteritems(): attrs = deviceAttrs.setdefault(device, {}) - if attr in attrs: - value = resolve(device, attr, [attrs[attr], value]) - attrs[attr] = value + if deviceAttr in attrs: + value = resolve(device, deviceAttr, [attrs[deviceAttr], + value]) + attrs[deviceAttr] = value - outputAttrs = {} # device: {attr: value} + outputAttrs = {} # device: {outputAttr: value} for d in deviceAttrs: - outputAttrs[d] = toOutputAttrs(self.deviceType[d], deviceAttrs[d]) + 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 device, attrs in outputAttrs.iteritems(): - for attr, value in attrs.iteritems(): - self.setAttr(device, attr, value, pendingOut) + for outputAttr, value in attrs.iteritems(): + self.setAttr(device, outputAttr, value, pendingOut) self.flush(pendingOut) - def setAttr(self, device, attr, value, pendingOut): - output, index = self.outputMap[(device, attr)] + def setAttr(self, device, outputAttr, value, pendingOut): + output, index = self.outputMap[(device, outputAttr)] outList = pendingOut.setdefault(output, []) setListElem(outList, index, value, combine=max) diff --git a/light9/collector/device.py b/light9/collector/device.py --- a/light9/collector/device.py +++ b/light9/collector/device.py @@ -47,9 +47,9 @@ def resolve(deviceType, deviceAttr, valu def toOutputAttrs(deviceType, deviceAttrSettings): """ - Given settings like {L9['color']: Literal('#ff0000')}, return a - similar dict where the keys are output attrs and the values are - suitable for Collector.setAttr + 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 """ if deviceType == L9['ChauvetColorStrip']: color = deviceAttrSettings.get(L9['color'], '#000000') diff --git a/light9/collector/output.py b/light9/collector/output.py --- a/light9/collector/output.py +++ b/light9/collector/output.py @@ -48,13 +48,8 @@ class Output(object): class DmxOutput(Output): - def __init__(self, baseUri, channels): - self.baseUri = baseUri - self.channels = channels - - def allConnections(self): - return ((i, URIRef('%sc%s' % (self.baseUri, i + 1))) - for i in range(self.channels)) + def __init__(self, uri): + self.uri = uri def flush(self): pass @@ -65,8 +60,8 @@ class EnttecDmx(DmxOutput): scales.PmfStat('write'), scales.PmfStat('update')) - def __init__(self, baseUri, channels, devicePath='/dev/dmx0'): - DmxOutput.__init__(self, baseUri, channels) + def __init__(self, uri, devicePath='/dev/dmx0'): + DmxOutput.__init__(self, uri) sys.path.append("dmx_usb_module") from dmx import Dmx @@ -95,8 +90,8 @@ class Udmx(DmxOutput): scales.PmfStat('update'), scales.PmfStat('write'), scales.IntStat('usbErrors')) - def __init__(self, baseUri, channels): - DmxOutput.__init__(self, baseUri, channels) + def __init__(self, uri): + DmxOutput.__init__(self, uri) from light9.io.udmx import Udmx self.dev = Udmx() diff --git a/show/dance2016/theaterLightConfig.n3 b/show/dance2016/theaterLightConfig.n3 --- a/show/dance2016/theaterLightConfig.n3 +++ b/show/dance2016/theaterLightConfig.n3 @@ -3,34 +3,30 @@ @prefix udmx: . @prefix dmx0: . -dmx0:c87 :connectedTo dev:colorStripMode . -dmx0:c88 :connectedTo dev:colorStripRed . -dmx0:c89 :connectedTo dev:colorStripGreen . -dmx0:c90 :connectedTo dev:colorStripBlue . - -dev:colorStrip a :ChauvetColorStrip, :Device; - :mode dev:colorStripMode; - :red dev:colorStripRed; - :green dev:colorStripGreen; - :blue dev:colorStripBlue . +:ChauvetColorStrip a :DeviceClass . +:ChauvetColorStrip :attr :ccsa0 . :ccsa0 :outputAttr :mode; :dmxOffset 0 . +:ChauvetColorStrip :attr :ccsa1 . :ccsa1 :outputAttr :red; :dmxOffset 1 . +:ChauvetColorStrip :attr :ccsa2 . :ccsa2 :outputAttr :green; :dmxOffset 2 . +:ChauvetColorStrip :attr :ccsa3 . :ccsa3 :outputAttr :blue; :dmxOffset 3 . -# All these bnodes don't refresh well, but they need to be rewritten -# as offsets from a single dmx start index, and they need to be -# inherited with the device type -dev:moving1 a :Mini15, :Device; - :xRotation [ is :connectedTo of udmx:c5 ]; - :xFine [ is :connectedTo of udmx:c6 ]; - :yRotation [ is :connectedTo of udmx:c7 ]; - :yFine [ is :connectedTo of udmx:c8 ]; - :rotationSpeed [ is :connectedTo of udmx:c9 ]; - :dimmer [ is :connectedTo of udmx:c10 ]; - :red [ is :connectedTo of udmx:c11 ]; - :green [ is :connectedTo of udmx:c12 ]; - :blue [ is :connectedTo of udmx:c13 ]; - :colorChange [ is :connectedTo of udmx:c14 ]; - :colorSpeed [ is :connectedTo of udmx:c15 ]; - :goboShake [ is :connectedTo of udmx:c16 ]; - :goboChoose [ is :connectedTo of udmx:c17 ] . +:Mini15 a :DeviceClass . +:Mini15 :attr :Mini15a0 . :Mini15a0 :outputAttr :xRotation; :dmxOffset 0 . +:Mini15 :attr :Mini15a1 . :Mini15a1 :outputAttr :xFine; :dmxOffset 1 . +:Mini15 :attr :Mini15a2 . :Mini15a2 :outputAttr :yRotation; :dmxOffset 2 . +:Mini15 :attr :Mini15a3 . :Mini15a3 :outputAttr :yFine; :dmxOffset 3 . +:Mini15 :attr :Mini15a4 . :Mini15a4 :outputAttr :rotationSpeed; :dmxOffset 4 . +:Mini15 :attr :Mini15a5 . :Mini15a5 :outputAttr :dimmer; :dmxOffset 5 . +:Mini15 :attr :Mini15a6 . :Mini15a6 :outputAttr :red; :dmxOffset 6 . +:Mini15 :attr :Mini15a7 . :Mini15a7 :outputAttr :green; :dmxOffset 7 . +:Mini15 :attr :Mini15a8 . :Mini15a8 :outputAttr :blue; :dmxOffset 8 . +:Mini15 :attr :Mini15a9 . :Mini15a9 :outputAttr :colorChange; :dmxOffset 9 . +:Mini15 :attr :Mini15a10 . :Mini15a10 :outputAttr :colorSpeed; :dmxOffset 10 . +:Mini15 :attr :Mini15a11 . :Mini15a11 :outputAttr :goboShake; :dmxOffset 11 . +:Mini15 :attr :Mini15a12 . :Mini15a12 :outputAttr :goboChoose; :dmxOffset 12 . + +dev:colorStrip a :ChauvetColorStrip; :dmxUniverse dmx0:; :dmxBase 87 . + +dev:moving1 a :Mini15; :dmxUniverse udmx:; :dmxBase 1 . # [ :name "cyc-right"; :output dmx:c42 ] . # [ :name "cyc-mid"; :output dmx:c43 ] .