changeset 1115:818275850003

effecteval screens for only the kinds of outputs that it can send, so i can run a second instance just for LEDs Ignore-this: e1b1fdd4564096ab9e214bb5e52db765
author Drew Perttula <drewp@bigasterisk.com>
date Fri, 13 Jun 2014 05:27:46 +0000
parents a38955ba6f40
children 2a89050c140b
files bin/effecteval light9/Effects.py light9/effecteval/effectloop.py
diffstat 3 files changed, 76 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/bin/effecteval	Fri Jun 13 05:25:02 2014 +0000
+++ b/bin/effecteval	Fri Jun 13 05:27:46 2014 +0000
@@ -7,11 +7,11 @@
 import sys, optparse, logging, subprocess, json, time, traceback, itertools
 from rdflib import URIRef, Literal
 
-sys.path.append(".")
+sys.path.append('/usr/lib/python2.7/dist-packages') # For numpy
 from light9 import networking, showconfig, Submaster, dmxclient
 from light9.curvecalc.curve import CurveResource
 from light9.effecteval.effect import EffectNode
-from light9.effecteval.effectloop import EffectLoop
+from light9.effecteval.effectloop import makeEffectLoop
 from light9.greplin_cyclone import StatsForCyclone
 from light9.namespaces import L9, RDF, RDFS
 from light9.rdfdb.patch import Patch
@@ -195,7 +195,8 @@
                                        )
 
     def launch(self, *args):
-        self.loop = EffectLoop(self.graph, self.stats)
+        self.loop = makeEffectLoop(self.graph, self.stats, self.outputWhere)
+        self.loop.startLoop()
         
         SFH = cyclone.web.StaticFileHandler
         self.cycloneApp = cyclone.web.Application(handlers=[
@@ -236,14 +237,14 @@
                       help="logging.DEBUG")
     parser.add_option("--twistedlog", action="store_true",
                       help="twisted logging")
+    parser.add_option("--output", metavar="WHERE", help="dmx or leds")
     (options, args) = parser.parse_args()
-
     log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 
     if not options.show:
         raise ValueError("missing --show http://...")
             
-    app = App(URIRef(options.show))
+    app = App(URIRef(options.show), options.output)
     if options.twistedlog:
         from twisted.python import log as twlog
         twlog.startLogging(sys.stderr)
--- a/light9/Effects.py	Fri Jun 13 05:25:02 2014 +0000
+++ b/light9/Effects.py	Fri Jun 13 05:27:46 2014 +0000
@@ -15,6 +15,15 @@
     registered.append(f)
     return f
 
+class ColorStrip(object):
+    """list of r,g,b tuples for sending to an LED strip"""
+    which = 'L'
+    pixels = []
+    
+class Blacklight(float):
+    """a level for the blacklight PWM output"""
+
+    
 @register
 def chase(t, ontime=0.5, offset=0.2, onval=1.0, 
           offval=0.0, names=None, combiner=max, random=False):
@@ -51,6 +60,13 @@
     return Submaster.Submaster(name='hsv', levels=lev)
 
 @register
+def colorDemo():
+    s = ColorStrip()
+    s.which = 'L'
+    s.pixels = [(0,0,1)] * 50
+    return s
+    
+@register
 def stack(t, names=None, fade=0):
     """names is list of URIs. returns a submaster that stacks the the inputs
 
--- a/light9/effecteval/effectloop.py	Fri Jun 13 05:25:02 2014 +0000
+++ b/light9/effecteval/effectloop.py	Fri Jun 13 05:27:46 2014 +0000
@@ -1,11 +1,14 @@
 from __future__ import division
 import time, json, logging, traceback
+import numpy
+import serial
 from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from rdflib import URIRef, Literal
 import cyclone.httpclient
 from light9.namespaces import L9, RDF, RDFS
 from light9.effecteval.effect import EffectNode
+from light9 import Effects
 from light9 import networking
 from light9 import Submaster
 from light9 import dmxclient
@@ -27,6 +30,13 @@
 
         self.songTimeFromRequest = 0
         self.requestTime = 0 # unix sec for when we fetched songTime
+        self.initOutput()
+
+    def initOutput(self):
+        pass
+
+    def startLoop(self):
+        log.info("startLoop")
         reactor.callLater(self.period, self.sendLevels)
 
     def setEffects(self):
@@ -73,7 +83,7 @@
                 outputs = self.allEffectOutputs(songTime)
                 combined = self.combineOutputs(outputs)
                 self.logLevels(t1, combined)
-                self.sendOutput(combined)
+                yield self.sendOutput(combined)
                 
                 elapsed = time.time() - t1
                 dt = max(0, self.period - elapsed)
@@ -87,6 +97,7 @@
     def combineOutputs(self, outputs):
         """pick usable effect outputs and reduce them into one for sendOutput"""
         outputs = [x for x in outputs if isinstance(x, Submaster.Submaster)]
+        log.info('outputs %r', outputs)
         out = Submaster.sub_maxes(*outputs)
 
         return out
@@ -125,4 +136,45 @@
         return ("send dmx: {%s}" %
                 ", ".join("%r: %.3g" % (str(k), v)
                           for k,v in out.get_levels().items()))
+
+class LedLoop(EffectLoop):
+    def initOutput(self):
+        kw = dict(baudrate=115200)
+        self.boards = {
+            'L': serial.Serial('/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A7027NYX-if00-port0', **kw),
+            'R': serial.Serial('/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A7027JI6-if00-port0', **kw),
+        }
         
+    def combineOutputs(self, outputs):
+        combined = []
+        for out in outputs:
+            if isinstance(out, (Effects.ColorStrip, Effects.Blacklight)):
+                # todo: take the max of all matching outputs, not just the last one
+                pixels = numpy.array(out.pixels, dtype=numpy.float16)
+                px255 = (numpy.clip(pixels, 0, 1) * 255).astype(numpy.uint8)
+                combined.append((out.which, px255))
+                
+        return combined
+                
+    @inlineCallbacks
+    def sendOutput(self, combined):
+        for which, px255 in combined:
+            board = self.boards[which]
+            msg = '\x60\x00' + px255.reshape((-1,)).tostring()
+            board.write(msg)
+            board.flush()
+            
+        yield succeed(None)
+        
+    def logMessage(self, out):
+        return str([(w, p.tolist()) for w,p in out])
+
+def makeEffectLoop(graph, stats, outputWhere):
+    if outputWhere == 'dmx':
+        return EffectLoop(graph, stats)
+    elif outputWhere == 'leds':
+        return LedLoop(graph, stats)
+    else:
+        raise NotImplementedError
+
+