Changeset - 8480fb25239d
[Not reviewed]
default
0 1 0
Drew Perttula - 10 years ago 2015-06-13 04:13:38
drewp@bigasterisk.com
host side api for the arduino board
Ignore-this: 99dc5437446e4e610215a28237687b37
1 file changed with 36 insertions and 4 deletions:
0 comments (0 inline, 0 general)
light9/effecteval/effectloop.py
Show inline comments
 
@@ -81,151 +81,183 @@ class EffectLoop(object):
 
    def updateTimeFromMusic(self):
 
        t1 = time.time()
 
        with self.stats.getMusic.time():
 
            self.songTime, song = yield self.getSongTime()
 
            self.songTimeFetch = time.time()
 

	
 
        if song != self.currentSong:
 
            self.currentSong = song
 
            # this may be piling on the handlers
 
            self.graph.addHandler(self.setEffects)
 

	
 
        elapsed = time.time() - t1
 
        reactor.callLater(max(0, self.period - elapsed), self.updateTimeFromMusic)
 

	
 
    def estimatedSongTime(self):
 
        now = time.time()
 
        t = self.songTime
 
        if self.currentPlaying:
 
            t += max(0, now - self.songTimeFetch)
 
        return t
 

	
 
    @inlineCallbacks
 
    def sendLevels(self):
 
        t1 = time.time()
 
        log.debug("time since last call: %.1f ms" % (1000 * (t1 - self.lastSendLevelsTime)))
 
        self.lastSendLevelsTime = t1
 
        try:
 
            with self.stats.sendLevels.time():
 
                if self.currentSong is not None:
 
                    with self.stats.evals.time():
 
                        outputs = self.allEffectOutputs(self.estimatedSongTime())
 
                    combined = self.combineOutputs(outputs)
 
                    self.logLevels(t1, combined)
 
                    with self.stats.sendOutput.time():
 
                        yield self.sendOutput(combined)
 
                
 
                elapsed = time.time() - t1
 
                dt = max(0, self.period - elapsed)
 
        except Exception:
 
            self.stats.errors += 1
 
            traceback.print_exc()
 
            dt = .5
 

	
 
        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)]
 
        out = Submaster.sub_maxes(*outputs)
 

	
 
        return out
 
        
 
    @inlineCallbacks
 
    def sendOutput(self, combined):
 
        dmx = combined.get_dmx_list()
 
        yield dmxclient.outputlevels(dmx, twisted=True)
 
        
 
    def allEffectOutputs(self, songTime):
 
        outputs = []
 
        for e in self.currentEffects:
 
            try:
 
                out = e.eval(songTime)
 
                if isinstance(out, (list, tuple)):
 
                    outputs.extend(out)
 
                else:
 
                    outputs.append(out)
 
            except Exception as exc:
 
                now = time.time()
 
                if now > self.lastErrorLog + 5:
 
                    if hasattr(exc, 'expr'):
 
                        log.error('in expression %r', exc.expr)
 
                    log.error("effect %s: %s" % (e.uri, exc))
 
                    self.lastErrorLog = now
 
        log.debug('eval %s effects, got %s outputs', len(self.currentEffects), len(outputs))
 
                    
 
        return outputs
 
        
 
    def logLevels(self, now, out):
 
        # this would look nice on the top of the effecteval web pages too
 
        if log.isEnabledFor(logging.DEBUG):
 
            log.debug(self.logMessage(out))
 
        else:
 
            if now > self.lastLogTime + 5:
 
                msg = self.logMessage(out)
 
                if msg != self.lastLogMsg:
 
                    log.info(msg)
 
                    self.lastLogMsg = msg
 
                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()))
 

	
 
Z = numpy.zeros((50, 3), dtype=numpy.uint8)
 

	
 
class ControlBoard(object):
 
    def __init__(self, dev='/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A7027NYX-if00-port0'):
 
        self.board = serial.Serial(dev, baudrate=115200)
 

	
 
    def _8bitMessage(self, floatArray):
 
        px255 = (numpy.clip(floatArray, 0, 1) * 255).astype(numpy.uint8)
 
        return px255.reshape((-1,)).tostring()
 
        
 
    def setStrip(self, which, pixels):
 
        """
 
        which: 0 or 1 to pick the strip
 
        pixels: (50, 3) array of 0..1 floats
 
        """
 
        command = {0: '\x00', 1: '\x01'}[which]
 
        if pixels.shape != (50, 3):
 
            raise ValueError("pixels was %s" % pixels.shape)
 
        self.board.write('\x60' + command + self._8bitMessage(pixels))
 

	
 
    def setUv(self, which, level):
 
        """
 
        which: 0 or 1
 
        level: 0 to 1
 
        """
 
        command = {0: '\x02', 1: '\x03'}[which]
 
        self.board.write('\x60' + command + chr(max(0, min(1, level)) * 255))
 

	
 
    def setRgb(self, color):
 
        """
 
        color: (1, 3) array of 0..1 floats
 
        """
 
        if color.shape != (1, 3):
 
            raise ValueError("color was %s" % color.shape)
 
        self.board.write('\x60\x04%s' + self._8bitMessage(color))
 

	
 
        
 
class LedLoop(EffectLoop):
 
    def initOutput(self):
 
        kw = dict(baudrate=115200)
 
        self.boards = {
 
            'L': serial.Serial('/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A7027JI6-if00-port0', **kw),
 
            'R': serial.Serial('/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A7027NYX-if00-port0', **kw),
 
        }
 
        self.boards = serial.Serial('/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A7027NYX-if00-port0', **kw)
 
        self.lastSentBacklight = None
 
        
 
    def combineOutputs(self, outputs):
 
        combined = {'L': Z, 'R': Z, 'blacklight': 0}
 
        
 
        for out in outputs:
 
            if isinstance(out, Effects.Blacklight):
 
                combined['blacklight'] = max(combined['blacklight'], int(out * 255))
 
            elif isinstance(out, Effects.Strip):
 
                pixels = numpy.array(out.pixels, dtype=numpy.float16)
 
                px255 = (numpy.clip(pixels, 0, 1) * 255).astype(numpy.uint8)
 
                for w in out.which:
 
                    combined[w] = numpy.maximum(combined[w], px255)
 
                
 
        return combined
 

	
 
    @inlineCallbacks
 
    def sendOutput(self, combined):
 
        for which, px255 in combined.items():
 
            if which == 'blacklight':
 
                if px255 != self.lastSentBacklight:
 
                    b = min(255, max(0, px255))
 
                    yield succeed(self.serialWrite(self.boards['L'],
 
                                                   '\x60\x01' + chr(b)))
 
                    self.lastSentBacklight = px255
 
            else:
 
                board = self.boards[which]
 
                msg = '\x60\x00' + px255.reshape((-1,)).tostring()
 
                # may be stuttering more, and not smoother
 
                #yield threads.deferToThread(self.serialWrite, board, msg)
 
                yield succeed(self.serialWrite(board, msg))
 

	
 
    def serialWrite(self, serial, msg):
 
        serial.write(msg)
 
        serial.flush()
 
        
 
    def logMessage(self, out):
 
        return str([(w, p.tolist() if isinstance(p, numpy.ndarray) else p) for w,p in out.items()])
 

	
 
def makeEffectLoop(graph, stats, outputWhere):
 
    if outputWhere == 'dmx':
 
        return EffectLoop(graph, stats)
 
    elif outputWhere == 'leds':
 
        return LedLoop(graph, stats)
 
    else:
 
        raise NotImplementedError("unknown output system %r" % outputWhere)
 

	
 
        
0 comments (0 inline, 0 general)