Changeset - f66dbe512025
[Not reviewed]
default
0 8 0
drewp@bigasterisk.com - 6 years ago 2019-06-08 07:45:25
drewp@bigasterisk.com
reformat
Ignore-this: c968c4cd1b9a580c7b0fedc00df8a80
8 files changed with 67 insertions and 47 deletions:
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
@@ -14,12 +14,13 @@ from light9 import networking, showconfi
 
from standardservice.scalessetup import gatherProcessStats
 

	
 
from gi.repository import GObject, Gst
 

	
 
gatherProcessStats()
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self, graph, show):
 
        self.graph = graph
 
        self.player = Player(onEOS=self.onEOS)
 
        self.show = show
bin/effecteval
Show inline comments
 
@@ -20,12 +20,13 @@ from standardservice.scalessetup import 
 

	
 
from cycloneerr import PrettyErrorHandler
 
from light9.coffee import StaticCoffee
 

	
 
gatherProcessStats()
 

	
 

	
 
class EffectEdit(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        self.set_header('Content-Type', 'text/html')
 
        self.write(open("light9/effecteval/effect.html").read())
 

	
light9/collector/collector.py
Show inline comments
 
@@ -199,13 +199,15 @@ class Collector:
 
            for outputAttr, value in attrs.items():
 
                output, _index = self.outputMap[(device, outputAttr)]
 
                outputUri = OutputUri(output.uri)
 
                index = DmxMessageIndex(_index)
 
                _, outArray = pendingOut[outputUri]
 
                if outArray[index] != 0:
 
                    log.warn(f'conflict: {output} output array was already nonzero at 0-based index {index}')
 
                    log.warn(
 
                        f'conflict: {output} output array was already nonzero at 0-based index {index}'
 
                    )
 
                    raise ValueError(f"someone already wrote to index {index}")
 
                outArray[index] = value
 

	
 
        dt1 = 1000 * (time.time() - now)
 
        for uri, (out, buf) in pendingOut.items():
 
            out.update(bytes(buf))
light9/collector/dmx_controller_output.py
Show inline comments
 
@@ -8,51 +8,66 @@
 
# by LCDproc
 
#
 
#######################################################
 
from pyftdi import ftdi
 

	
 
#FTDI device info
 
vendor=0x0403
 
product=0x6001
 
vendor = 0x0403
 
product = 0x6001
 

	
 

	
 
#####################
 
# DMX USB controller
 
#####################
 
class OpenDmxUsb():
 

	
 
    def __init__(self):
 
        self.baud_rate = 250000
 
        self.data_bits = 8
 
        self.stop_bits = 2
 
        self.parity = 'N'
 
        self.flow_ctrl = ''
 
        self.rts_state = 0
 
        self._init_dmx()
 

	
 
    #Initialize the controller
 
    def _init_dmx(self):
 
        self.ftdi=ftdi.Ftdi()
 
        self.ftdi.open(vendor,product,0)
 
        self.ftdi = ftdi.Ftdi()
 
        self.ftdi.open(vendor, product, 0)
 
        self.ftdi.set_baudrate(self.baud_rate)
 
        self.ftdi.set_line_property(self.data_bits,self.stop_bits,self.parity,break_=0)
 
        self.ftdi.set_line_property(self.data_bits,
 
                                    self.stop_bits,
 
                                    self.parity,
 
                                    break_=0)
 
        self.ftdi.set_flowctrl(self.flow_ctrl)
 
        self.ftdi.purge_rx_buffer()
 
        self.ftdi.purge_tx_buffer()
 
        self.ftdi.set_rts(self.rts_state)
 

	
 
    #Send DMX data
 
    def send_dmx(self,channelVals):
 
    def send_dmx(self, channelVals):
 
        assert self.ftdi.write_data(channelVals) == 513
 
        # Need to generate two bits for break
 
        self.ftdi.set_line_property(self.data_bits,self.stop_bits,self.parity,break_=1)
 
        self.ftdi.set_line_property(self.data_bits,self.stop_bits,self.parity,break_=1)
 
        self.ftdi.set_line_property(self.data_bits,self.stop_bits,self.parity,break_=0)            
 
        self.ftdi.set_line_property(self.data_bits,
 
                                    self.stop_bits,
 
                                    self.parity,
 
                                    break_=1)
 
        self.ftdi.set_line_property(self.data_bits,
 
                                    self.stop_bits,
 
                                    self.parity,
 
                                    break_=1)
 
        self.ftdi.set_line_property(self.data_bits,
 
                                    self.stop_bits,
 
                                    self.parity,
 
                                    break_=0)
 

	
 
if __name__=="__main__":
 
    dmxUsb=OpenDmxUsb()
 

	
 
if __name__ == "__main__":
 
    dmxUsb = OpenDmxUsb()
 

	
 
    channelVals=bytearray([0]*513)
 
    channelVals[0]=0 # dummy channel 0
 
    while(True):
 
        for x in range(1,468+1):
 
    channelVals = bytearray([0] * 513)
 
    channelVals[0] = 0  # dummy channel 0
 
    while (True):
 
        for x in range(1, 468 + 1):
 
            channelVals[x] = 255
 

	
 
        dmxUsb.send_dmx(channelVals)
light9/collector/output.py
Show inline comments
 
@@ -25,27 +25,25 @@ class Output(object):
 
        self.uri = uri
 

	
 
        self.statPath = '/output%s' % self.shortId()
 
        scales.init(self, self.statPath)
 

	
 
        self._writeStats = scales.collection(
 
            self.statPath + '/write',
 
            scales.IntStat('succeed'),
 
            scales.IntStat('fail'),
 
            scales.PmfStat('call', recalcPeriod=1),
 
            self.statPath + '/write', scales.IntStat('succeed'),
 
            scales.IntStat('fail'), scales.PmfStat('call', recalcPeriod=1),
 
            scales.RecentFpsStat('fps'))
 

	
 
        self._currentBuffer = b''
 

	
 
        if log.isEnabledFor(logging.DEBUG):
 
            self._lastLoggedMsg = ''
 
            task.LoopingCall(self._periodicLog).start(1)
 

	
 
    def reconnect(self):
 
        pass
 
            
 

	
 
    def shortId(self) -> str:
 
        """short string to distinguish outputs"""
 
        return self.uri.rstrip('/').rsplit('/')[-1]
 

	
 
    def update(self, buf: bytes) -> None:
 
        """caller asks for the output to be this buffer"""
 
@@ -54,24 +52,25 @@ class Output(object):
 
    def _periodicLog(self):
 
        msg = '%s: %s' % (self.shortId(), ' '.join(map(str,
 
                                                       self._currentBuffer)))
 
        if msg != self._lastLoggedMsg:
 
            log.debug(msg)
 
            self._lastLoggedMsg = msg
 
            
 

	
 
    def _write(self, buf: bytes) -> None:
 
        """
 
        write buffer to output hardware (may be throttled if updates are
 
        too fast, or repeated if they are too slow)
 
        """
 
        pass
 

	
 
    def crash(self):
 
        log.error('unrecoverable- exiting')
 
        reactor.crash()
 

	
 

	
 
class DummyOutput(Output):
 

	
 
    def __init__(self, uri, **kw):
 
        super().__init__(uri)
 

	
 

	
 
@@ -101,37 +100,37 @@ class BackgroundLoopOutput(Output):
 
            log.error(e)
 
            reactor.callLater(.2, self._loop)
 

	
 
        d = threads.deferToThread(self._write, sendingBuffer)
 
        d.addCallbacks(done, err)
 

	
 

	
 
class FtdiDmx(BackgroundLoopOutput):
 

	
 
    def __init__(self, uri, lastDmxChannel, rate=22):
 
        super().__init__(uri)
 
        self.lastDmxChannel = lastDmxChannel
 
        from .dmx_controller_output import OpenDmxUsb
 
        self.dmx = OpenDmxUsb()
 
        
 

	
 
    def _write(self, buf):
 
        self._writeStats.fps.mark()
 
        with self._writeStats.call.time():
 
            if not buf:
 
                logAllDmx.debug('%s: empty buf- no output',
 
                                self.shortId())
 
                logAllDmx.debug('%s: empty buf- no output', self.shortId())
 
                return
 

	
 
            # ok to truncate the last channels if they just went
 
            # to 0? No it is not. DMX receivers don't add implicit
 
            # zeros there.
 
            buf = bytes([0]) + buf[:self.lastDmxChannel]
 

	
 
            if logAllDmx.isEnabledFor(logging.DEBUG):
 
                # for testing fps, smooth fades, etc
 
                logAllDmx.debug(
 
                    '%s: %s...' %
 
                    (self.shortId(), ' '.join(map(str, buf[:32]))))
 
                logAllDmx.debug('%s: %s...' %
 
                                (self.shortId(), ' '.join(map(str, buf[:32]))))
 

	
 
            self.dmx.send_dmx(buf)
 

	
 

	
 
class ArtnetDmx(BackgroundLoopOutput):
 
    # adapted from https://github.com/spacemanspiff2007/PyArtNet/blob/master/pyartnet/artnet_node.py (gpl3)
 
@@ -187,19 +186,20 @@ class Udmx(BackgroundLoopOutput):
 
        self.address = address
 
        self.lastDmxChannel = lastDmxChannel
 
        self.dev = None
 
        super().__init__(uri, rate=rate)
 

	
 
        self._errStats = scales.collection(self.statPath + '/write',
 
                                          scales.IntStat('overflow'),
 
                                          scales.IntStat('ioError'),
 
                                          scales.IntStat('pipeError')
 
        )
 
                                           scales.IntStat('overflow'),
 
                                           scales.IntStat('ioError'),
 
                                           scales.IntStat('pipeError'))
 
        self.reconnect()
 

	
 
    def shortId(self) -> str:
 
        return super().shortId() + f'_bus={self.bus}'
 

	
 
    def reconnect(self):
 
        self._connected = 0
 
        from pyudmx import pyudmx
 
        self.dev = pyudmx.uDMXDevice()
 
        if not self.dev.open(bus=self.bus, address=self.address):
 
            raise ValueError("dmx open failed")
 
@@ -217,14 +217,13 @@ class Udmx(BackgroundLoopOutput):
 
            log.info('%s: trying to connect', self.shortId())
 
            raise ValueError()
 
        self._writeStats.fps.mark()
 
        with self._writeStats.call.time():
 
            try:
 
                if not buf:
 
                    logAllDmx.debug('%s: empty buf- no output',
 
                                    self.shortId())
 
                    logAllDmx.debug('%s: empty buf- no output', self.shortId())
 
                    return
 

	
 
                # ok to truncate the last channels if they just went
 
                # to 0? No it is not. DMX receivers don't add implicit
 
                # zeros there.
 
                buf = buf[:self.lastDmxChannel]
 
@@ -244,20 +243,20 @@ class Udmx(BackgroundLoopOutput):
 
            except usb.core.USBError as e:
 
                # not in main thread
 
                if e.errno == 75:
 
                    self._errStats.overflow += 1
 
                    return
 

	
 
                if e.errno == 5: # i/o err
 
                if e.errno == 5:  # i/o err
 
                    self._errStats.ioError += 1
 
                    return
 

	
 
                if e.errno == 32: # pipe err
 
                if e.errno == 32:  # pipe err
 
                    self._errStats.pipeError += 1
 
                    return
 
                
 

	
 
                msg = 'usb: sending %s bytes to %r; error %r' % (len(buf),
 
                                                                 self.uri, e)
 
                log.warn(msg)
 

	
 
                if e.errno == 13:  # permissions
 
                    return self.crash()
light9/effect/effecteval.py
Show inline comments
 
@@ -384,20 +384,22 @@ def effect_lightning(effectSettings, str
 
    for i, dev in enumerate(devs):
 
        n = noise(songTime * 8 + i * 6.543)
 
        if n > .4:
 
            out[(dev, L9['color'])] = col
 
    return out
 

	
 

	
 
def sample(img, x, y, repeat=False):
 
    if 0 <= x < img.width:
 
        return img.getpixel((x, y))
 
    elif not repeat:
 
        return (0, 0, 0)
 
    else:
 
        return img.getpixel((x % img.width, y))
 

	
 

	
 
def effect_image(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    imgPath = f'cur/anim/{effectSettings[L9["image"]]}'
 
    t_offset = effectSettings.get(L9['tOffset'], 0)
 
    pxPerSec = effectSettings.get(L9['pxPerSec'], 30)
 
    img = Image.open(imgPath)
light9/effect/sequencer.py
Show inline comments
 
@@ -117,14 +117,13 @@ class Note(object):
 
        """
 
        report = {
 
            'note': str(self.uri),
 
            'effectClass': self.effectEval.effect,
 
        }
 
        effectSettings: Dict[DeviceAttr, Union[float, str]] = dict(
 
            (DeviceAttr(da), v)
 
            for da, v in self.baseEffectSettings.items())
 
            (DeviceAttr(da), v) for da, v in self.baseEffectSettings.items())
 
        effectSettings[L9['strength']] = self.evalCurve(t)
 

	
 
        def prettyFormat(x: Union[float, str]):
 
            if isinstance(x, float):
 
                return round(x, 4)
 
            return x
 
@@ -189,13 +188,13 @@ class Sequencer(object):
 
        self.updateLoop()
 

	
 
    def onCodeChange(self):
 
        log.debug('seq.onCodeChange')
 
        self.graph.addHandler(self.compileGraph)
 
        #self.updateLoop()
 
        
 

	
 
    @compileStats.graph.time()
 
    def compileGraph(self) -> None:
 
        """rebuild our data from the graph"""
 
        for song in self.graph.subjects(RDF.type, L9['Song']):
 

	
 
            def compileSong(song: Song = cast(Song, song)) -> None:
 
@@ -206,13 +205,14 @@ class Sequencer(object):
 
    @compileStats.song.time()
 
    def compileSong(self, song: Song) -> None:
 
        anyErrors = False
 
        self.notes[song] = []
 
        for note in self.graph.objects(song, L9['note']):
 
            try:
 
                n = Note(self.graph, NoteUri(note), effecteval, self.simpleOutputs)
 
                n = Note(self.graph, NoteUri(note), effecteval,
 
                         self.simpleOutputs)
 
            except Exception:
 
                log.warn(f"failed to build Note {note} - skipping")
 
                anyErrors = True
 
                continue
 
            self.notes[song].append(n)
 
        if not anyErrors:
 
@@ -232,20 +232,20 @@ class Sequencer(object):
 
            took = time.time() - frameStart
 
            updateStats.updateLoopLatency = took
 

	
 
            if not self.lastLoopSucceeded:
 
                log.info('Sequencer.update is working')
 
                self.lastLoopSucceeded = True
 
        
 

	
 
            delay = max(0, 1 / self.fps - took)
 
            reactor.callLater(delay, self.updateLoop)
 

	
 
    @updateStats.updateFps.rate()
 
    @inlineCallbacks
 
    def update(self) -> Deferred:
 
        
 

	
 
        with updateStats.s0_getMusic.time():
 
            musicState = self.music.getLatest()
 
            if not musicState.get('song') or not isinstance(
 
                    musicState.get('t'), float):
 
                return defer.succeed(0.0)
 
            song = Song(URIRef(musicState['song']))
 
@@ -254,14 +254,13 @@ class Sequencer(object):
 
                                'song': str(song),
 
                                't': musicState['t']
 
                            })
 

	
 
        with updateStats.s1_eval.time():
 
            settings = []
 
            songNotes = sorted(self.notes.get(song, []),
 
                               key=lambda n: n.uri)
 
            songNotes = sorted(self.notes.get(song, []), key=lambda n: n.uri)
 
            noteReports = []
 
            for note in songNotes:
 
                try:
 
                    s, report = note.outputSettings(musicState['t'])
 
                except Exception:
 
                    traceback.print_exc()
 
@@ -269,20 +268,21 @@ class Sequencer(object):
 
                noteReports.append(report)
 
                settings.append(s)
 
            devSettings = DeviceSettings.fromList(self.graph, settings)
 

	
 
        dispatcher.send('state', update={'songNotes': noteReports})
 

	
 
        with updateStats.s3_send.time(): # our measurement
 
        with updateStats.s3_send.time():  # our measurement
 
            sendSecs = yield self.sendToCollector(devSettings)
 

	
 
        # sendToCollector's own measurement.
 
        # (sometimes it's None, not sure why, and neither is mypy)
 
        #if isinstance(sendSecs, float):
 
        #    updateStats.s3_send_client = sendSecs
 

	
 

	
 
class Updates(cyclone.sse.SSEHandler):
 

	
 
    def __init__(self, application, request, **kwargs) -> None:
 
        cyclone.sse.SSEHandler.__init__(self, application, request, **kwargs)
 
        self.state: Dict = {}
 
        dispatcher.connect(self.updateState, 'state')
light9/subclient.py
Show inline comments
 
@@ -9,13 +9,13 @@ from rdfdb.syncedgraph import SyncedGrap
 
log = logging.getLogger()
 

	
 

	
 
class SubClient:
 
    graph: SyncedGraph
 
    session: URIRef
 
    
 

	
 
    def __init__(self):
 
        """assumed that your init saves self.graph"""
 
        pass  # we may later need init code for network setup
 

	
 
    def get_levels_as_sub(self):
 
        """Subclasses must implement this method and return a Submaster
0 comments (0 inline, 0 general)