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)