Changeset - 818275850003
[Not reviewed]
default
0 3 0
Drew Perttula - 11 years ago 2014-06-13 05:27:46
drewp@bigasterisk.com
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
3 files changed with 76 insertions and 7 deletions:
0 comments (0 inline, 0 general)
bin/effecteval
Show inline comments
 
@@ -4,17 +4,17 @@ from run_local import log
 
from twisted.internet import reactor
 
from twisted.internet.defer import inlineCallbacks, returnValue
 
import cyclone.web, cyclone.websocket, cyclone.httpclient
 
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
 
from light9.rdfdb.syncedgraph import SyncedGraph
 
from greplin import scales
 

	
 
@@ -192,13 +192,14 @@ class App(object):
 
                                       scales.PmfStat('getMusic'),
 
                                       scales.PmfStat('writeDmx'),
 
                                       scales.IntStat('errors'),
 
                                       )
 

	
 
    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=[
 
            (r'/()', SFH,
 
             {'path': 'light9/effecteval', 'default_filename': 'index.html'}),
 
            (r'/effect', EffectEdit),
 
@@ -233,18 +234,18 @@ if __name__ == "__main__":
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
                      default=showconfig.showUri())
 
    parser.add_option("-v", "--verbose", action="store_true",
 
                      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)
 
    reactor.run()
light9/Effects.py
Show inline comments
 
@@ -12,12 +12,21 @@ log = logging.getLogger()
 

	
 
registered = []
 
def register(f):
 
    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):
 
    """names is list of URIs. returns a submaster that chases through
 
    the inputs"""
 
    if random:
 
@@ -48,12 +57,19 @@ def hsv(h, s, v, light='all', centerScal
 
        lev[80], lev[81], lev[82] = r,g,b
 
    if light in ['center', 'all']:
 
        lev[88], lev[89], lev[90] = r*centerScale,g*centerScale,b*centerScale
 
    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
 

	
 
    fade=0 makes steps, fade=1 means each one gets its full fraction
 
    of the time to fade in. Fades never...
 
    """
light9/effecteval/effectloop.py
Show inline comments
 
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
 
log = logging.getLogger('effectloop')
 

	
 
class EffectLoop(object):
 
@@ -24,12 +27,19 @@ class EffectLoop(object):
 
        self.graph.addHandler(self.setEffects)
 
        self.period = 1 / 30
 
        self.coastSecs = .3 # main reason to keep this low is to notice play/pause
 

	
 
        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):
 
        self.currentEffects = []
 
        if self.currentSong is None:
 
            return
 
@@ -70,13 +80,13 @@ class EffectLoop(object):
 
                if song is None:
 
                    return
 

	
 
                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)
 
        except Exception:
 
            self.stats.errors += 1
 
            traceback.print_exc()
 
@@ -84,12 +94,13 @@ class EffectLoop(object):
 

	
 
        reactor.callLater(dt, self.sendLevels)
 

	
 
    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
 
        
 
    @inlineCallbacks
 
    def sendOutput(self, combined):
 
@@ -122,7 +133,48 @@ class EffectLoop(object):
 
                self.lastLogTime = now
 
                
 
    def logMessage(self, out):
 
        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
 

	
 
        
0 comments (0 inline, 0 general)