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
7 files changed with 42 insertions and 22 deletions:
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
@@ -8,24 +8,25 @@ gi.require_version('Gst', '1.0')
 
gi.require_version('Gtk', '3.0')
 

	
 
from light9.ascoltami.player import Player
 
from light9.ascoltami.playlist import Playlist, NoSuchSong
 
from light9.ascoltami.webapp import makeWebApp, songUri, songLocation
 
from light9 import networking, showconfig
 
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
 
        self.playlist = Playlist.fromShow(graph, show)
 

	
 
    def onEOS(self, song):
 
        self.player.pause()
 
        self.player.seek(0)
 

	
bin/effecteval
Show inline comments
 
@@ -14,24 +14,25 @@ from light9.effecteval.effectloop import
 
from greplin.scales.cyclonehandler import StatsHandler
 
from light9.namespaces import L9
 
from rdfdb.patch import Patch
 
from rdfdb.syncedgraph import SyncedGraph
 
from greplin import scales
 
from standardservice.scalessetup import gatherProcessStats
 

	
 
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())
 

	
 
    def delete(self):
 
        graph = self.settings.graph
 
        uri = URIRef(self.get_argument('uri'))
 
        with graph.currentState(tripleFilter=(None, L9['effect'], uri)) as g:
 
            song = ctx = list(g.subjects(L9['effect'], uri))[0]
 
        self.settings.graph.patch(
light9/collector/collector.py
Show inline comments
 
@@ -193,25 +193,27 @@ class Collector:
 

	
 
        pendingOut: Dict[OutputUri, Tuple[OutputInstance, bytearray]] = {}
 
        for out in self.outputs:
 
            pendingOut[OutputUri(out.uri)] = (out, bytearray(512))
 

	
 
        for device, attrs in outputAttrs.items():
 
            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))
 
        dt2 = 1000 * (time.time() - now)
 
        if dt1 > 30:
 
            log.warn(
 
                "slow setAttrs: %.1fms -> flush -> %.1fms. lr %s da %s oa %s" %
 
                (dt1, dt2, len(
 
                    self.lastRequest), len(deviceAttrs), len(outputAttrs)))
light9/collector/dmx_controller_output.py
Show inline comments
 
@@ -5,54 +5,69 @@
 
# This program is published under a GPLv2 license
 
#
 
# This code implements a DMX controller with UI provided
 
# by LCDproc
 
#
 
#######################################################
 
from pyftdi import ftdi
 

	
 
#FTDI device info
 
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.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):
 
        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()
 

	
 
    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
 
@@ -19,28 +19,26 @@ class Output(object):
 
    This base class doesn't ever call _write. Subclasses below have
 
    strategies for that.
 
    """
 
    uri: URIRef
 

	
 
    def __init__(self, uri: URIRef):
 
        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:
 
@@ -60,24 +58,25 @@ class Output(object):
 
            
 
    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)
 

	
 

	
 
class BackgroundLoopOutput(Output):
 
    """Call _write forever at 20hz in background threads"""
 

	
 
    rate: float
 

	
 
    def __init__(self, uri, rate=22):
 
@@ -95,48 +94,48 @@ class BackgroundLoopOutput(Output):
 
            self._writeStats.succeed += 1
 
            reactor.callLater(max(0, start + 1 / self.rate - time.time()),
 
                              self._loop)
 

	
 
        def err(e):
 
            self._writeStats.fail += 1
 
            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...' %
 
                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)
 
    def __init__(self, uri, host, port, rate):
 
        """sends UDP messages to the given host/port"""
 
        super().__init__(uri, rate)
 
        packet = bytearray()
 
        packet.extend(map(ord, "Art-Net"))
 
@@ -183,54 +182,54 @@ class Udmx(BackgroundLoopOutput):
 
    _connected = scales.IntStat('connected')
 

	
 
    def __init__(self, uri, bus, address, lastDmxChannel, rate=22):
 
        self.bus = bus
 
        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('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")
 
        log.info(f'opened {self.dev}')
 
        self._connected = 1
 
        self._reconnections += 1
 

	
 
    #def update(self, buf:bytes):
 
    #    self._write(buf)
 

	
 
    #def _loop(self):
 
    #    pass
 
    def _write(self, buf):
 
        if not self.dev:
 
            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]
 

	
 
                if logAllDmx.isEnabledFor(logging.DEBUG):
 
                    # for testing fps, smooth fades, etc
 
                    logAllDmx.debug(
 
                        '%s: %s...' %
 
                        (self.shortId(), ' '.join(map(str, buf[:32]))))
light9/effect/effecteval.py
Show inline comments
 
@@ -378,32 +378,34 @@ def effect_lightning(effectSettings, str
 
        L9['device/backlight4'], L9['device/backlight5'], L9['device/down2'],
 
        L9['device/down3'], L9['device/down4'], L9['device/hexLow3'],
 
        L9['device/hexLow5'], L9['device/postL1'], L9['device/postR1']
 
    ]
 
    out = {}
 
    col = rgb_to_hex([int(255 * strength)] * 3)
 
    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)
 
    x = (noteTime * pxPerSec)
 

	
 
    scl = effectSettings.get(L9['strength'], 1)
 
    for dev, y in [(L9['theater/skyline/device/strip1'], 0),
 
                   (L9['theater/skyline/device/strip2'], 1),
 
                   (L9['theater/skyline/device/strip3'], 2)]:
light9/effect/sequencer.py
Show inline comments
 
@@ -111,26 +111,25 @@ class Note(object):
 

	
 
    def outputSettings(
 
            self,
 
            t: float) -> Tuple[List[Tuple[DeviceUri, DeviceAttr, float]], Dict]:
 
        """
 
        list of (device, attr, value), and a report for web
 
        """
 
        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
 

	
 
        report['effectSettings'] = dict(
 
            (str(k), prettyFormat(v))
 
            for k, v in sorted(effectSettings.items()))
 
        report['nonZero'] = cast(float, effectSettings[L9['strength']]) > 0
 
        out, evalReport = self.effectEval.outputFromEffect(
 
@@ -200,25 +199,26 @@ class Sequencer(object):
 

	
 
            def compileSong(song: Song = cast(Song, song)) -> None:
 
                self.compileSong(song)
 

	
 
            self.graph.addHandler(compileSong)
 

	
 
    @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:
 
            log.info('built all notes')
 

	
 
    @inlineCallbacks
 
    def updateLoop(self) -> None:
 
        frameStart = time.time()
 
        try:
 
@@ -248,47 +248,47 @@ class Sequencer(object):
 
            if not musicState.get('song') or not isinstance(
 
                    musicState.get('t'), float):
 
                return defer.succeed(0.0)
 
            song = Song(URIRef(musicState['song']))
 
            dispatcher.send('state',
 
                            update={
 
                                '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()
 
                    raise
 
                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
 
            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')
 
        self.numConnected = 0
 

	
 
    def updateState(self, update: Dict):
 
        self.state.update(update)
 

	
 
    def bind(self) -> None:
0 comments (0 inline, 0 general)