Mercurial > code > home > repos > light9
changeset 1300:d51014267bfd
move device-specific code out of collector. resolver isn't done yet. live.html can edit colors
Ignore-this: d537a0418ffde35b928387706977cbe0
author | Drew Perttula <drewp@bigasterisk.com> |
---|---|
date | Mon, 30 May 2016 11:13:06 +0000 |
parents | 02415a3cebb2 |
children | c13e0705a011 |
files | light9/collector/collector.py light9/collector/collector_test.py light9/collector/device.py light9/collector/device_test.py light9/web/lib/bower.json light9/web/live.html makefile |
diffstat | 7 files changed, 228 insertions(+), 75 deletions(-) [+] |
line wrap: on
line diff
--- a/light9/collector/collector.py Mon May 30 08:27:04 2016 +0000 +++ b/light9/collector/collector.py Mon May 30 11:13:06 2016 +0000 @@ -1,16 +1,12 @@ from __future__ import division import time import logging -from webcolors import hex_to_rgb from light9.namespaces import L9, RDF, DEV from light9.collector.output import setListElem +from light9.collector.device import toOutputAttrs log = logging.getLogger('collector') -#class Device(object): -# def setAttrs(): -# pass - def outputMap(graph, outputs): """From rdf config graph, compute a map of (device, attr) : (output, index) @@ -55,12 +51,19 @@ staleClients.append(c) for c in staleClients: del self.lastRequest[c] + + def _deviceType(self, d): + for t in self.config.objects(d, RDF.type): + if t == L9['Device']: + continue + return t def setAttrs(self, client, clientSession, settings): """ - settings is a list of (device, attr, value). Interpret rgb colors, - resolve conflicting values, and call - Output.update/Output.flush to send the new outputs. + 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. """ @@ -77,28 +80,29 @@ for d, a, v in settings: prevClientSettings[(d, a)] = v self.lastRequest[client] = (clientSession, now, prevClientSettings) + + + deviceAttrs = {} # device: {attr: value} + for _, _, settings in self.lastRequest.itervalues(): + for (device, attr), value in settings.iteritems(): + # resolving conflicts goes around here + deviceAttrs.setdefault(device, {})[attr] = value + + outputAttrs = {} # device: {attr: value} + for d in deviceAttrs: + outputAttrs[d] = toOutputAttrs(self._deviceType(d), deviceAttrs[d]) pendingOut = {} # output : values - - # device always wants this - self.setAttr(DEV['colorStrip'], L9['mode'], 215/255, pendingOut) - - for _, _, settings in self.lastRequest.itervalues(): - for (device, attr), value in settings.iteritems(): + for device, attrs in outputAttrs.iteritems(): + for attr, value in attrs.iteritems(): self.setAttr(device, attr, value, pendingOut) self.flush(pendingOut) def setAttr(self, device, attr, value, pendingOut): - if attr == L9['color']: - [self.setAttr(device, a, x / 255, pendingOut) for a, x in zip( - [L9['red'], L9['green'], L9['blue']], - hex_to_rgb(value))] - return - output, index = self.outputMap[(device, attr)] outList = pendingOut.setdefault(output, []) - setListElem(outList, index, int(float(value) * 255), combine=max) + setListElem(outList, index, value, combine=max) def flush(self, pendingOut): """write any changed outputs"""
--- a/light9/collector/collector_test.py Mon May 30 08:27:04 2016 +0000 +++ b/light9/collector/collector_test.py Mon May 30 11:13:06 2016 +0000 @@ -28,7 +28,7 @@ def allConnections(self): return self.connections - + def update(self, values): self.updates.append(values) @@ -63,26 +63,26 @@ dmx0:c2 :connectedTo dev:inst1Brightness . dev:inst1 a :Device; :brightness dev:inst1Brightness . '''), [out0]) - - - + + + class TestCollector(unittest.TestCase): def setUp(self): self.config = fromN3(''' - + udmx:c1 :connectedTo dev:colorStripRed . udmx:c2 :connectedTo dev:colorStripGreen . udmx:c3 :connectedTo dev:colorStripBlue . udmx:c4 :connectedTo dev:colorStripMode . - dev:colorStrip a :Device; + dev:colorStrip a :Device, :ChauvetColorStrip; :red dev:colorStripRed; :green dev:colorStripGreen; :blue dev:colorStripBlue; :mode dev:colorStripMode . dmx0:c1 :connectedTo dev:inst1Brightness . - dev:inst1 a :Device; + dev:inst1 a :Device, :Dimmer; :brightness dev:inst1Brightness . ''') @@ -92,68 +92,62 @@ (2, UDMX['c3']), (3, UDMX['c4'])]) - def testRoutesSimpleOutput(self): - c = Collector(self.config, outputs=[self.dmx0, self.udmx]) - - c.setAttrs('client', 'sess1', - [(DEV['colorStrip'], L9['green'], Literal(1.0))]) - - self.assertEqual([[0, 255, 0, 215], 'flush'], self.udmx.updates) - self.assertEqual([], self.dmx0.updates) - def testRoutesColorOutput(self): c = Collector(self.config, outputs=[self.dmx0, self.udmx]) c.setAttrs('client', 'sess1', - [(DEV['colorStrip'], L9['color'], Literal('#ff0000'))]) + [(DEV['colorStrip'], L9['color'], '#00ff00')]) - self.assertEqual([[255, 0, 0, 215], 'flush'], self.udmx.updates) + self.assertEqual([[0, 255, 0, 215], 'flush'], self.udmx.updates) self.assertEqual([], self.dmx0.updates) def testOutputMaxOfTwoClients(self): c = Collector(self.config, outputs=[self.dmx0, self.udmx]) c.setAttrs('client1', 'sess1', - [(DEV['colorStrip'], L9['color'], Literal('#ff0000'))]) + [(DEV['colorStrip'], L9['color'], '#ff0000')]) c.setAttrs('client2', 'sess1', - [(DEV['colorStrip'], L9['color'], Literal('#333333'))]) + [(DEV['colorStrip'], L9['color'], '#333333')]) self.assertEqual([[255, 0, 0, 215], 'flush', [255, 51, 51, 215], 'flush'], self.udmx.updates) self.assertEqual([], self.dmx0.updates) - + def testClientOnSameOutputIsRememberedOverCalls(self): c = Collector(self.config, outputs=[self.dmx0, self.udmx]) - c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['red'], .8)]) - c.setAttrs('client2', 'sess1', [(DEV['colorStrip'], L9['red'], .6)]) - c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['red'], .5)]) - - self.assertEqual([[204, 0, 0, 215], 'flush', - [204, 0, 0, 215], 'flush', - [153, 0, 0, 215], 'flush'], + c.setAttrs('client1', 'sess1', + [(DEV['colorStrip'], L9['color'], '#080000')]) + c.setAttrs('client2', 'sess1', + [(DEV['colorStrip'], L9['color'], '#060000')]) + c.setAttrs('client1', 'sess1', + [(DEV['colorStrip'], L9['color'], '#050000')]) + + self.assertEqual([[8, 0, 0, 215], 'flush', + [8, 0, 0, 215], 'flush', + [6, 0, 0, 215], 'flush'], self.udmx.updates) self.assertEqual([], self.dmx0.updates) - + def testClientsOnDifferentOutputs(self): c = Collector(self.config, outputs=[self.dmx0, self.udmx]) - c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['red'], .8)]) + c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['color'], '#aa0000')]) c.setAttrs('client2', 'sess1', [(DEV['inst1'], L9['brightness'], .5)]) # ok that udmx is flushed twice- it can screen out its own duplicates - self.assertEqual([[204, 0, 0, 215], 'flush', - [204, 0, 0, 215], 'flush'], self.udmx.updates) + self.assertEqual([[170, 0, 0, 215], 'flush', + [170, 0, 0, 215], 'flush'], self.udmx.updates) self.assertEqual([[127], '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)]) - + self.assertEqual([[204], 'flush', [127], 'flush'], self.dmx0.updates) def testNewSessionDropsPreviousSettingsOfOtherAttrs(self): @@ -161,10 +155,10 @@ c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['red'], 1)]) c.setAttrs('client1', 'sess2', [(DEV['colorStrip'], L9['green'], 1)]) - + self.assertEqual([[255, 0, 0, 215], 'flush', [0, 255, 0, 215], 'flush'], self.udmx.updates) - + def testClientIsForgottenAfterAWhile(self): with freeze_time(datetime.datetime.now()) as ft: c = Collector(self.config, outputs=[self.dmx0, self.udmx]) @@ -177,11 +171,12 @@ self.dmx0.updates) def testClientUpdatesAreCollected(self): + # second call to setAttrs doesn't forget the first c = Collector(self.config, outputs=[self.dmx0, self.udmx]) - c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['red'], 1)]) - c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['green'], 1)]) - - self.assertEqual([[255, 0, 0, 215], 'flush', - [255, 255, 0, 215], 'flush'], self.udmx.updates) - self.assertEqual([], self.dmx0.updates) + 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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/collector/device.py Mon May 30 11:13:06 2016 +0000 @@ -0,0 +1,82 @@ +from __future__ import division +import logging +import math +from light9.namespaces import L9, RDF, DEV +from webcolors import hex_to_rgb + +log = logging.getLogger('device') + +class Device(object): + def setAttrs(): + pass + + +class ChauvetColorStrip(Device): + """ + device attrs: + color + """ + +class Mini15(Device): + """ + plan: + + device attrs + rx, ry + color + gobo + goboShake + imageAim (configured with a file of calibration data) + """ + +def _8bit(f): + return min(255, max(0, 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 + """ + raise NotImplementedError + +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 + """ + if deviceType == L9['ChauvetColorStrip']: + color = deviceAttrSettings.get(L9['color'], '#000000') + r, g, b = hex_to_rgb(color) + return { + L9['mode']: 215, + L9['red']: r, + L9['green']: g, + L9['blue']: b + } + elif deviceType == L9['Dimmer']: + return {L9['brightness']: _8bit(deviceAttrSettings.get(L9['brightness'], 0))} + elif deviceType == L9['Mini15']: + inp = deviceAttrSettings + rx8 = float(inp.get(L9['rx'], 0)) / 540 * 255 + ry8 = float(inp.get(L9['ry'], 0)) / 240 * 255 + r, g, b = hex_to_rgb(inp.get(L9['color'], '#000000')) + + return { + L9['xRotation']: int(math.floor(rx8)), + # didn't find docs on this, but from tests it looks like 64 fine steps takes you to the next coarse step + L9['xFine']: _8bit((rx8 % 1.0) / 4), + L9['yRotation']: int(math.floor(ry8)), + L9['yFine']: _8bit((ry8 % 1.0) / 4), + L9['rotationSpeed']: 0, + L9['dimmer']: 255, + L9['red']: r, + L9['green']: g, + L9['blue']: b, + L9['colorChange']: 0, + L9['colorSpeed']: 0, + L9['goboShake']: 0, + L9['goboChoose']: 0, + } + else: + raise NotImplementedError('device %r' % deviceType)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/collector/device_test.py Mon May 30 11:13:06 2016 +0000 @@ -0,0 +1,35 @@ +import unittest +from rdflib import Literal +from light9.namespaces import L9 + +from light9.collector.device import toOutputAttrs + +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})) + +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(31, out[L9['xFine']]) + self.assertEqual(47, out[L9['yRotation']]) + self.assertEqual(51, out[L9['yFine']]) + self.assertEqual(0, out[L9['rotationSpeed']])
--- a/light9/web/lib/bower.json Mon May 30 08:27:04 2016 +0000 +++ b/light9/web/lib/bower.json Mon May 30 11:13:06 2016 +0000 @@ -9,5 +9,9 @@ "jquery-ui": "~1.11.4", "QueryString": "http://unixpapa.com/js/QueryString.js", "knockout": "~3.4.0" + }, + "resolutions": { + "paper-styles": "^1.1.4", + "web-animations-js": "^2.2.0" } }
--- a/light9/web/live.html Mon May 30 08:27:04 2016 +0000 +++ b/light9/web/live.html Mon May 30 11:13:06 2016 +0000 @@ -14,9 +14,21 @@ <dom-module id="light9-live-control"> <template> <style> + paper-slider { width: 100%; } </style> <iron-ajax url="/collector/attrs" method="PUT" id="put"></iron-ajax> - <paper-slider min="0" max="1" step=".01" editable content-type="application/json" immediate-value="{{v1}}"></paper-slider> + + <template is="dom-if" if="{{useSlider}}"> + <paper-slider min="0" + max="{{max}}" + step=".01" + editable + content-type="application/json" + immediate-value="{{v1}}"></paper-slider> + </template> + <template is="dom-if" if="{{useColor}}"> + <input type="color" id="col" on-input="onPickedColor" value="{{pickedColor}}"> + </template> </template> <script> HTMLImports.whenReady(function () { @@ -25,13 +37,20 @@ properties: { device: {type: String}, attr: {type: String}, + max: {type: Number, value: 1}, v1: {type: Number, notify: true, observer: "ch"}, + useSlider: {type: Boolean, computed: '_useSlider(attr)'}, + useColor: {type: Boolean, computed: '_useColor(attr)'}, + pickedColor: {observer: 'onPickedColor'}, }, ready: function() { // only need 1 ping for the clientsession, not one per // control. setInterval(this.ping.bind(this), 9000); }, + onPickedColor: function(ev) { + this.ch(ev.target.value); + }, ch: function(lev) { this.$.put.body = JSON.stringify({ "settings":[ @@ -46,22 +65,36 @@ "client":"c", "clientSession":"cs"}); this.$.put.generateRequest(); - } + }, + _useSlider: function(attr) { + return attr != 'http://light9.bigasterisk.com/color'; + }, + _useColor: function(attr) { + return attr == 'http://light9.bigasterisk.com/color'; + }, }); }); </script> </dom-module> <light9-live-control device="http://light9.bigasterisk.com/device/colorStrip" - attr="http://light9.bigasterisk.com/red" + attr="http://light9.bigasterisk.com/color" ></light9-live-control> - <light9-live-control - device="http://light9.bigasterisk.com/device/colorStrip" - attr="http://light9.bigasterisk.com/green" - ></light9-live-control> - <light9-live-control - device="http://light9.bigasterisk.com/device/colorStrip" - attr="http://light9.bigasterisk.com/blue" - ></light9-live-control> + <hr> + <h2>moving</h2> + rx <light9-live-control + device="http://light9.bigasterisk.com/device/moving1" + attr="http://light9.bigasterisk.com/rx" max="540" + ></light9-live-control> + ry <light9-live-control + device="http://light9.bigasterisk.com/device/moving1" + attr="http://light9.bigasterisk.com/ry" max="240" + ></light9-live-control> + color <light9-live-control + device="http://light9.bigasterisk.com/device/moving1" + attr="http://light9.bigasterisk.com/color" + ></light9-live-control> + + </body> </html>
--- a/makefile Mon May 30 08:27:04 2016 +0000 +++ b/makefile Mon May 30 11:13:06 2016 +0000 @@ -1,4 +1,4 @@ -NOSEARGS="--no-path-adjustment light9.rdfdb.rdflibpatch light9.rdfdb.patch light9.effecteval.test_effect light9.collector.collector_test light9.collector.output_test" +NOSEARGS="--no-path-adjustment light9.rdfdb.rdflibpatch light9.rdfdb.patch light9.effecteval.test_effect light9.collector" tests: eval env/bin/nosetests -x $(NOSEARGS)