changeset 1866:3c523c71da29

pyflakes cleanups and some refactors Ignore-this: f7372e678699175feb4e628eee3d768c
author Drew Perttula <drewp@bigasterisk.com>
date Sat, 25 May 2019 12:10:51 +0000
parents 1aa91a31c0e2
children 7a6a44aaf362
files bin/ascoltami2 bin/captureDevice bin/collector bin/collector_loadtest.py bin/effecteval bin/homepageConfig bin/inputdemo bin/inputquneo bin/keyboardcomposer bin/listsongs bin/musicPad bin/musictime bin/paintserver bin/picamserve bin/rdfdb bin/subserver bin/vidref bin/vidrefsetup bin/wavecurve lib/ipython_view.py light9/Fadable.py light9/FlyingFader.py light9/Patch.py light9/Submaster.py light9/TLUtility.py light9/ascoltami/player.py light9/ascoltami/playlist.py light9/ascoltami/webapp.py light9/coffee.py light9/collector/collector.py light9/collector/collector_test.py light9/collector/device.py light9/collector/output.py light9/collector/weblisteners.py light9/curvecalc/client.py light9/curvecalc/curve.py light9/curvecalc/curveedit.py light9/curvecalc/curveview.py light9/curvecalc/output.py light9/curvecalc/subterm.py light9/curvecalc/zoomcontrol.py light9/dmxchanedit.py light9/dmxclient.py light9/effect/effecteval.py light9/effect/sequencer.py light9/effect/settings.py light9/effect/settings_test.py light9/effecteval/effectloop.py light9/gtkpyconsole.py light9/namespaces.py light9/paint/solve.py light9/paint/solve_test.py light9/showconfig.py light9/subclient.py light9/uihelpers.py light9/vidref/main.py light9/vidref/musictime.py light9/vidref/remotepivideo.py light9/vidref/videorecorder.py light9/wavepoints.py light9/zmqtransport.py
diffstat 61 files changed, 293 insertions(+), 298 deletions(-) [+]
line wrap: on
line diff
--- a/bin/ascoltami2	Sat May 25 12:06:01 2019 +0000
+++ b/bin/ascoltami2	Sat May 25 12:10:51 2019 +0000
@@ -1,7 +1,7 @@
 #!bin/python
 from run_local import log
 from twisted.internet import reactor
-import web, _thread, sys, optparse, logging
+import sys, optparse, logging
 from rdflib import URIRef
 import gi
 gi.require_version('Gst', '1.0')
@@ -12,7 +12,7 @@
 from light9.ascoltami.webapp import makeWebApp, songUri, songLocation
 from light9 import networking, showconfig
 
-from gi.repository import GObject, Gst, Gtk
+from gi.repository import GObject, Gst
 
 
 class App(object):
--- a/bin/captureDevice	Sat May 25 12:06:01 2019 +0000
+++ b/bin/captureDevice	Sat May 25 12:10:51 2019 +0000
@@ -23,6 +23,7 @@
 from light9.effect.settings import DeviceSettings
 from light9.collector.collector_client import sendToCollector
 from rdfdb.patch import Patch
+from light9.zmqtransport import parseJsonMessage
 
 stats = scales.collection('/webServer', scales.PmfStat('setAttr'))
 
@@ -174,7 +175,8 @@
                           }),
                           (r'/stats', StatsForCyclone),
                       ]),
-                      interface='::')
+                      interface='::',
+                      cap=cap)
     log.info('serving http on %s', networking.captureDevice.port)
 
 
--- a/bin/collector	Sat May 25 12:06:01 2019 +0000
+++ b/bin/collector	Sat May 25 12:10:51 2019 +0000
@@ -9,127 +9,25 @@
 
 from run_local import log
 
-from rdflib import URIRef, Literal
 from twisted.internet import reactor, utils
-from txzmq import ZmqEndpoint, ZmqFactory, ZmqPullConnection
 import json
 import logging
 import optparse
-import time
 import traceback
 import cyclone.web, cyclone.websocket
 from greplin import scales
 
 from cycloneerr import PrettyErrorHandler
-from light9.collector.output import EnttecDmx, Udmx, DummyOutput
+from light9 import networking
 from light9.collector.collector import Collector
-from light9.namespaces import L9
-from light9 import networking
-from rdfdb.syncedgraph import SyncedGraph
+from light9.collector.weblisteners import WebListeners
 from light9.greplin_cyclone import StatsForCyclone
-
-
-def parseJsonMessage(msg):
-    body = json.loads(msg)
-    settings = []
-    for device, attr, value in body['settings']:
-        if isinstance(value, str) and value.startswith('http'):
-            value = URIRef(value)
-        else:
-            value = Literal(value)
-        settings.append((URIRef(device), URIRef(attr), value))
-    return body['client'], body['clientSession'], settings, body['sendTime']
-
-
-def startZmq(port, collector):
-    stats = scales.collection('/zmqServer', scales.PmfStat('setAttr'))
-
-    zf = ZmqFactory()
-    addr = 'tcp://*:%s' % port
-    log.info('creating zmq endpoint at %r', addr)
-    e = ZmqEndpoint('bind', addr)
-
-    class Pull(ZmqPullConnection):
-        #highWaterMark = 3
-        def onPull(self, message):
-            with stats.setAttr.time():
-                # todo: new compressed protocol where you send all URIs up
-                # front and then use small ints to refer to devices and
-                # attributes in subsequent requests.
-                client, clientSession, settings, sendTime = parseJsonMessage(
-                    message[0])
-                collector.setAttrs(client, clientSession, settings, sendTime)
-
-    s = Pull(zf, e)
-
-
-class WebListeners(object):
-
-    def __init__(self):
-        self.clients = []
-        self.pendingMessageForDev = {}  # dev: (attrs, outputmap)
-        self.lastFlush = 0
-
-    def addClient(self, client):
-        self.clients.append([client, {}])  # seen = {dev: attrs}
-        log.info('added client %s %s', len(self.clients), client)
+from light9.namespaces import L9
+from light9.zmqtransport import parseJsonMessage, startZmq
+from rdfdb.syncedgraph import SyncedGraph
 
-    def delClient(self, client):
-        self.clients = [[c, t] for c, t in self.clients if c != client]
-        log.info('delClient %s, %s left', client, len(self.clients))
-
-    def outputAttrsSet(self, dev, attrs, outputMap):
-        """called often- don't be slow"""
-
-        self.pendingMessageForDev[dev] = (attrs, outputMap)
-        try:
-            self._flush()
-        except Exception:
-            traceback.print_exc()
-            raise
-
-    def _flush(self):
-        now = time.time()
-        if now < self.lastFlush + .05 or not self.clients:
-            return
-        self.lastFlush = now
-
-        while self.pendingMessageForDev:
-            dev, (attrs, outputMap) = self.pendingMessageForDev.popitem()
-
-            msg = None  # lazy, since makeMsg is slow
+from light9.collector.output import EnttecDmx, Udmx, DummyOutput  # noqa
 
-            # this omits repeats, but can still send many
-            # messages/sec. Not sure if piling up messages for the browser
-            # could lead to slowdowns in the real dmx output.
-            for client, seen in self.clients:
-                if seen.get(dev) == attrs:
-                    continue
-                if msg is None:
-                    msg = self.makeMsg(dev, attrs, outputMap)
-
-                seen[dev] = attrs
-                client.sendMessage(msg)
-
-    def makeMsg(self, dev, attrs, outputMap):
-        attrRows = []
-        for attr, val in list(attrs.items()):
-            output, index = outputMap[(dev, attr)]
-            attrRows.append({
-                'attr': attr.rsplit('/')[-1],
-                'val': val,
-                'chan': (output.shortId(), index + 1)
-            })
-        attrRows.sort(key=lambda r: r['chan'])
-        for row in attrRows:
-            row['chan'] = '%s %s' % (row['chan'][0], row['chan'][1])
-
-        msg = json.dumps({'outputAttrsSet': {
-            'dev': dev,
-            'attrs': attrRows
-        }},
-                         sort_keys=True)
-        return msg
 
 
 class Updates(cyclone.websocket.WebSocketHandler):
@@ -163,10 +61,8 @@
     try:
         # todo: drive outputs with config files
         outputs = [
-            # EnttecDmx(L9['output/dmxA/'], '/dev/dmx3', 80),
-            Udmx(L9['output/dmxA/'], bus=5, numChannels=80),
-            #DummyOutput(L9['output/dmxA/'], 80),
-            Udmx(L9['output/dmxB/'], bus=7, numChannels=500),
+            DummyOutput(L9['output/dmxA/'], 80),
+            DummyOutput(L9['output/dmxB/'], 510),
         ]
     except Exception:
         log.error("setting up outputs:")
--- a/bin/collector_loadtest.py	Sat May 25 12:06:01 2019 +0000
+++ b/bin/collector_loadtest.py	Sat May 25 12:10:51 2019 +0000
@@ -1,7 +1,7 @@
 import sys
 sys.path.append('bin')
 from run_local import log
-from light9.collector.collector_client import sendToCollector, sendToCollectorZmq
+from light9.collector.collector_client import sendToCollector
 from light9.namespaces import L9, DEV
 from twisted.internet import reactor
 import time
--- a/bin/effecteval	Sat May 25 12:06:01 2019 +0000
+++ b/bin/effecteval	Sat May 25 12:10:51 2019 +0000
@@ -4,7 +4,7 @@
 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, itertools
+import sys, optparse, logging, json, itertools
 from rdflib import URIRef, Literal
 
 sys.path.append('/usr/lib/pymodules/python2.7/')  # for numpy, on rpi
@@ -20,6 +20,7 @@
 from greplin import scales
 
 from cycloneerr import PrettyErrorHandler
+from light9.coffee import StaticCoffee
 
 
 class EffectEdit(PrettyErrorHandler, cyclone.web.RequestHandler):
@@ -201,9 +202,9 @@
 
     def get(self):
         song = URIRef(self.get_argument('song'))
-        effects = effectsForSong(self.settings.graph, song)
+        effects = effectsForSong(self.settings.graph, song) # noqa
         raise NotImplementedError
-        self.write(maxDict(effectDmxDict(e) for e in effects))
+        self.write(maxDict(effectDmxDict(e) for e in effects)) # noqa
         # return dmx dict for all effects in the song, already combined
 
 
@@ -259,19 +260,6 @@
         log.info("listening on %s" % networking.effectEval.port)
 
 
-class StaticCoffee(PrettyErrorHandler, cyclone.web.RequestHandler):
-
-    def initialize(self, src):
-        super(StaticCoffee, self).initialize()
-        self.src = src
-
-    def get(self):
-        self.set_header('Content-Type', 'application/javascript')
-        self.write(
-            subprocess.check_output(
-                ['/usr/bin/coffee', '--compile', '--print', self.src]))
-
-
 if __name__ == "__main__":
     parser = optparse.OptionParser()
     parser.add_option(
--- a/bin/homepageConfig	Sat May 25 12:06:01 2019 +0000
+++ b/bin/homepageConfig	Sat May 25 12:10:51 2019 +0000
@@ -1,14 +1,11 @@
 #!bin/python
 from run_local import log
-from rdflib import RDF, URIRef
-from light9 import networking, showconfig
+from light9 import showconfig
 from light9.namespaces import L9
 from urllib.parse import urlparse
 from urllib.parse import splitport
 
-from rdfdb.syncedgraph import SyncedGraph
-from twisted.internet import reactor
-
+log.info('generating config')
 graph = showconfig.getGraph()
 
 netHome = graph.value(showconfig.showUri(), L9['networking'])
--- a/bin/inputdemo	Sat May 25 12:06:01 2019 +0000
+++ b/bin/inputdemo	Sat May 25 12:10:51 2019 +0000
@@ -5,13 +5,12 @@
 gtk3reactor.install()
 from twisted.internet import reactor
 from rdflib import URIRef
-import optparse, logging, urllib.request, urllib.parse, urllib.error, time
+import optparse, logging, time
 from gi.repository import Gtk
 from run_local import log
-from light9 import showconfig, networking
+from light9 import networking
 from light9 import clientsession
 from rdfdb.syncedgraph import SyncedGraph
-import cyclone.httpclient
 from light9.curvecalc.client import sendLiveInputPoint
 
 
--- a/bin/inputquneo	Sat May 25 12:06:01 2019 +0000
+++ b/bin/inputquneo	Sat May 25 12:10:51 2019 +0000
@@ -9,7 +9,7 @@
 from rdflib import URIRef
 from twisted.internet import reactor, task
 from light9.curvecalc.client import sendLiveInputPoint
-from light9.namespaces import L9, RDF, RDFS
+from light9.namespaces import L9, RDF
 from rdfdb.syncedgraph import SyncedGraph
 from light9 import networking
 
@@ -109,6 +109,7 @@
     graph = SyncedGraph(networking.rdfdb.url, "inputQuneo")
     wm = WatchMidi(graph)
     reactor.run()
+    del wm
 
 
 main()
--- a/bin/keyboardcomposer	Sat May 25 12:06:01 2019 +0000
+++ b/bin/keyboardcomposer	Sat May 25 12:10:51 2019 +0000
@@ -342,16 +342,16 @@
             for key in keys:
                 # lowercase makes full=0
                 keysym = "<KeyPress-%s>" % key
-                tkobject.bind(keysym, \
-                    lambda evt, num=keys.index(key), d=d: \
-                        self.got_nudger(num, d))
+                tkobject.bind(keysym,
+                              lambda evt, num=keys.index(key), d=d: self.
+                              got_nudger(num, d))
 
                 # uppercase makes full=1
                 keysym = "<KeyPress-%s>" % key.upper()
                 keysym = keysym.replace('SEMICOLON', 'colon')
-                tkobject.bind(keysym, \
-                    lambda evt, num=keys.index(key), d=d: \
-                        self.got_nudger(num, d, full=1))
+                tkobject.bind(keysym,
+                              lambda evt, num=keys.index(key), d=d: self.
+                              got_nudger(num, d, full=1))
 
         # Row changing:
         # Page dn, C-n, and ] do down
--- a/bin/listsongs	Sat May 25 12:06:01 2019 +0000
+++ b/bin/listsongs	Sat May 25 12:10:51 2019 +0000
@@ -7,7 +7,7 @@
 compdef _songs curvecalc
 """
 
-from run_local import log
+from run_local import log  # noqa
 from twisted.internet import reactor
 from rdflib import RDF
 from light9 import networking
--- a/bin/musicPad	Sat May 25 12:06:01 2019 +0000
+++ b/bin/musicPad	Sat May 25 12:10:51 2019 +0000
@@ -5,7 +5,6 @@
 import sys, wave, logging, os
 sys.path.append(".")
 from light9 import showconfig
-from light9.namespaces import L9
 from light9.ascoltami.playlist import Playlist
 logging.basicConfig(level=logging.INFO)
 log = logging.getLogger()
--- a/bin/musictime	Sat May 25 12:06:01 2019 +0000
+++ b/bin/musictime	Sat May 25 12:10:51 2019 +0000
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-import run_local
+import run_local  # noqa
 import light9.networking
 
 import tkinter as tk
--- a/bin/paintserver	Sat May 25 12:06:01 2019 +0000
+++ b/bin/paintserver	Sat May 25 12:10:51 2019 +0000
@@ -13,7 +13,7 @@
 from light9 import clientsession
 import light9.paint.solve
 from cycloneerr import PrettyErrorHandler
-from light9.namespaces import RDF, L9, DEV
+from light9.namespaces import L9, DEV
 import imp
 
 
--- a/bin/picamserve	Sat May 25 12:06:01 2019 +0000
+++ b/bin/picamserve	Sat May 25 12:10:51 2019 +0000
@@ -162,7 +162,6 @@
                 if not self.running:
                     raise StopIteration
 
-                now = time.time()
                 self.write("%s %s\n" % (len(frame), frameTime))
                 self.write(frame)
                 self.flush()
--- a/bin/rdfdb	Sat May 25 12:06:01 2019 +0000
+++ b/bin/rdfdb	Sat May 25 12:10:51 2019 +0000
@@ -1,5 +1,5 @@
 #!bin/python
-import run_local
+import run_local  # noqa
 import os
 from light9 import networking, showconfig
 import rdfdb.service
--- a/bin/subserver	Sat May 25 12:06:01 2019 +0000
+++ b/bin/subserver	Sat May 25 12:10:51 2019 +0000
@@ -4,11 +4,11 @@
 dragging them into CC or Timeline
 """
 from run_local import log
-import sys, optparse, logging, json, subprocess, datetime
+import optparse, logging, json, subprocess, datetime
 from dateutil.tz import tzlocal
 from twisted.internet import reactor, defer
 import cyclone.web, cyclone.httpclient, cyclone.websocket
-from rdflib import RDF, URIRef, Literal
+from rdflib import URIRef, Literal
 import pyjade.utils
 from rdfdb.syncedgraph import SyncedGraph
 from rdfdb.patch import Patch
--- a/bin/vidref	Sat May 25 12:06:01 2019 +0000
+++ b/bin/vidref	Sat May 25 12:10:51 2019 +0000
@@ -7,10 +7,9 @@
 from twisted.internet import reactor, defer
 import gobject
 gobject.threads_init()
-import gtk
 import sys, logging, optparse, json
 import cyclone.web, cyclone.httpclient, cyclone.websocket
-from light9 import networking, showconfig
+from light9 import networking
 from light9.vidref.main import Gui
 from light9.vidref.replay import snapshotDir
 from rdfdb.syncedgraph import SyncedGraph
@@ -42,7 +41,7 @@
             self.write(json.dumps({'snapshot': out}))
             self.set_header("Location", out)
             self.set_status(303)
-        except Exception as e:
+        except Exception:
             import traceback
             traceback.print_exc()
             raise
--- a/bin/vidrefsetup	Sat May 25 12:06:01 2019 +0000
+++ b/bin/vidrefsetup	Sat May 25 12:10:51 2019 +0000
@@ -3,15 +3,12 @@
 camera captures with a continuous camera capture yet """
 
 from run_local import log
-import sys, optparse, logging, json, subprocess, datetime
-from dateutil.tz import tzlocal
-from twisted.internet import reactor, defer
+import optparse, logging
+from twisted.internet import reactor
 import cyclone.web, cyclone.httpclient, cyclone.websocket
-from rdflib import RDF, URIRef, Literal
-import pyjade.utils
+from rdflib import URIRef
 from rdfdb.syncedgraph import SyncedGraph
-from rdfdb.patch import Patch
-from light9.namespaces import L9, DCTERMS
+from light9.namespaces import L9
 from light9 import networking, showconfig
 
 from cycloneerr import PrettyErrorHandler
--- a/bin/wavecurve	Sat May 25 12:06:01 2019 +0000
+++ b/bin/wavecurve	Sat May 25 12:10:51 2019 +0000
@@ -1,6 +1,6 @@
 #!bin/python
 import optparse
-import run_local
+from run_local import log
 from light9.wavepoints import simp
 
 
@@ -8,9 +8,10 @@
     print("reading %s, writing %s" % (inpath, outpath))
     points = simp(inpath.replace('.ogg', '.wav'), seconds_per_average=t)
 
-    f = file(outpath, 'w')
+    f = open(outpath, 'w')
     for time_val in points:
         print("%s %s" % time_val, file=f)
+    log.info(r'Wrote {outpath}')
 
 
 parser = optparse.OptionParser(usage="""%prog inputSong.wav outputCurve
@@ -30,8 +31,6 @@
 
 if options.all:
     from light9 import showconfig
-    from light9.namespaces import L9
-    from rdflib import RDF
     from light9.ascoltami.playlist import Playlist
     graph = showconfig.getGraph()
 
--- a/lib/ipython_view.py	Sat May 25 12:06:01 2019 +0000
+++ b/lib/ipython_view.py	Sat May 25 12:10:51 2019 +0000
@@ -21,11 +21,12 @@
 import sys
 import os
 from gi.repository import Pango
-from StringIO import StringIO
+from io import StringIO
+from functools import reduce
  
 try:
-	import IPython
-except Exception,e:
+        import IPython
+except Exception as e:
 	raise "Error importing IPython (%s)" % str(e)
  
 ansi_colors =  {'0;30': 'Black',
@@ -148,11 +149,11 @@
  
   def shell(self, cmd,verbose=0,debug=0,header=''):
     stat = 0
-    if verbose or debug: print header+cmd
+    if verbose or debug: print(header+cmd)
     # flush stdout so we don't mangle python's buffering
     if not debug:
       input, output = os.popen4(cmd)
-      print output.read()
+      print(output.read())
       output.close()
       input.close()
  
--- a/light9/Fadable.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/Fadable.py	Sat May 25 12:10:51 2019 +0000
@@ -1,5 +1,4 @@
 # taken from SnackMix -- now that's reusable code
-from tkinter.tix import *
 import time
 
 
--- a/light9/FlyingFader.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/FlyingFader.py	Sat May 25 12:10:51 2019 +0000
@@ -1,6 +1,5 @@
-from tkinter.tix import *
-from time import time, sleep
-
+from tkinter import tix
+from time import time
 
 class Mass:
 
@@ -65,7 +64,7 @@
         return not self._stopped
 
 
-class FlyingFader(Frame):
+class FlyingFader(tix.Frame):
 
     def __init__(self,
                  master,
@@ -75,7 +74,7 @@
                  font=('Arial', 8),
                  labelwidth=12,
                  **kw):
-        Frame.__init__(self, master)
+        tix.Frame.__init__(self, master)
         self.name = label
         self.variable = variable
 
@@ -94,25 +93,23 @@
         }
         scaleopts.update(kw)
         if scaleopts['orient'] == 'vert':
-            side1 = TOP
-            side2 = BOTTOM
+            side2 = tix.BOTTOM
         else:
-            side1 = RIGHT
-            side2 = LEFT
+            side2 = tix.LEFT
 
-        self.scale = Scale(self, **scaleopts)
-        self.vlabel = Label(self, text="0.0", width=6, font=font)
-        self.label = Label(self,
-                           text=label,
-                           font=font,
-                           anchor='w',
-                           width=labelwidth)  #wraplength=40, )
+        self.scale = tix.Scale(self, **scaleopts)
+        self.vlabel = tix.Label(self, text="0.0", width=6, font=font)
+        self.label = tix.Label(self,
+                               text=label,
+                               font=font,
+                               anchor='w',
+                               width=labelwidth)  #wraplength=40, )
 
         self.oldtrough = self.scale['troughcolor']
 
-        self.scale.pack(side=side2, expand=1, fill=BOTH, anchor='c')
-        self.vlabel.pack(side=side2, expand=0, fill=X)
-        self.label.pack(side=side2, expand=0, fill=X)
+        self.scale.pack(side=side2, expand=1, fill=tix.BOTH, anchor='c')
+        self.vlabel.pack(side=side2, expand=0, fill=tix.X)
+        self.label.pack(side=side2, expand=0, fill=tix.X)
 
         for k in range(1, 10):
             self.scale.bind(
@@ -150,7 +147,7 @@
         mult = 1
         if evt.state & 8 and evt.state & 4: mult = 0.25  # both
         elif evt.state & 8: mult = 0.5  # alt
-        elif evt.state & 4: mult = 2  # control
+        elif evt.state & 4: mult = 2  # control # noqa
 
         self.mass.x = self.variable.get()
         self.mass.goto(newlevel)
@@ -204,22 +201,19 @@
 
 
 if __name__ == '__main__':
-    root = Tk()
+    root = tix.Tk()
     root.tk_focusFollowsMouse()
 
-    FlyingFader(root, variable=DoubleVar(), label="suck").pack(side=LEFT,
-                                                               expand=1,
-                                                               fill=BOTH)
-    FlyingFader(root, variable=DoubleVar(), label="moof").pack(side=LEFT,
-                                                               expand=1,
-                                                               fill=BOTH)
-    FlyingFader(root, variable=DoubleVar(), label="zarf").pack(side=LEFT,
-                                                               expand=1,
-                                                               fill=BOTH)
+    FlyingFader(root, variable=tix.DoubleVar(),
+                label="suck").pack(side=tix.LEFT, expand=1, fill=tix.BOTH)
+    FlyingFader(root, variable=tix.DoubleVar(),
+                label="moof").pack(side=tix.LEFT, expand=1, fill=tix.BOTH)
+    FlyingFader(root, variable=tix.DoubleVar(),
+                label="zarf").pack(side=tix.LEFT, expand=1, fill=tix.BOTH)
     FlyingFader(root,
-                variable=DoubleVar(),
-                label="long name goes here.  got it?").pack(side=LEFT,
+                variable=tix.DoubleVar(),
+                label="long name goes here.  got it?").pack(side=tix.LEFT,
                                                             expand=1,
-                                                            fill=BOTH)
+                                                            fill=tix.BOTH)
 
     root.mainloop()
--- a/light9/Patch.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/Patch.py	Sat May 25 12:10:51 2019 +0000
@@ -1,4 +1,3 @@
-import os
 from rdflib import RDF
 from light9.namespaces import L9
 from light9 import showconfig
--- a/light9/Submaster.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/Submaster.py	Sat May 25 12:10:51 2019 +0000
@@ -1,12 +1,12 @@
-import os, logging, time
+import logging
 from rdflib import Graph, RDF
 from rdflib import RDFS, Literal, BNode
 from light9.namespaces import L9, XSD
 from light9.TLUtility import dict_scale, dict_max
 from light9 import showconfig
-from light9.Patch import resolve_name, get_dmx_channel, get_channel_uri, reload_data
+from light9.Patch import resolve_name, get_dmx_channel, get_channel_uri
 from louie import dispatcher
-from .rdfdb.patch import Patch
+from rdfdb.patch import Patch
 log = logging.getLogger('submaster')
 
 
@@ -73,7 +73,7 @@
         # not sure how useful this is
         if not isinstance(other, Submaster):
             return -1
-        return cmp(self.ident(), other.ident())
+        return cmp(self.ident(), other.ident()) # noqa
 
     def __hash__(self):
         return hash(self.ident())
@@ -189,7 +189,7 @@
                 continue
             try:
                 self.levels[name] = float(val)
-            except:
+            except Exception:
                 log.error("name %r val %r" % (name, val))
                 raise
 
@@ -326,11 +326,11 @@
 
     def get_all_subs(self):
         "All Submaster objects"
-        l = sorted(list(self.submasters.items()))
-        l = [x[1] for x in l]
+        v = sorted(list(self.submasters.items()))
+        v = [x[1] for x in v]
         songs = []
         notsongs = []
-        for s in l:
+        for s in v:
             if s.name and s.name.startswith('song'):
                 songs.append(s)
             else:
--- a/light9/TLUtility.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/TLUtility.py	Sat May 25 12:10:51 2019 +0000
@@ -56,7 +56,7 @@
     for a in [x for x in dir(o) if not callable(getattr(o, x))]:
         try:
             print("  %20s: %s " % (a, getattr(o, a)))
-        except:
+        except Exception:
             pass
     print("")
 
@@ -190,7 +190,7 @@
 
 def dict_scale(d, scl):
     """scales all values in dict and returns a new dict"""
-    return dict([(k, v * scl) for k, v in list(d.items())])
+    return dict([(k, v * scl) for k, v in d.items()])
 
 
 def dict_subset(d, dkeys, default=0):
--- a/light9/ascoltami/player.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/ascoltami/player.py	Sat May 25 12:10:51 2019 +0000
@@ -4,8 +4,8 @@
 """
 
 import time, logging, traceback
-from gi.repository import GObject, Gst
-from twisted.internet import reactor, task
+from gi.repository import Gst
+from twisted.internet import task
 
 log = logging.getLogger()
 
@@ -30,7 +30,7 @@
 
         task.LoopingCall(self.watchTime).start(.050)
 
-        bus = self.pipeline.get_bus()
+        #bus = self.pipeline.get_bus()
         # not working- see notes in pollForMessages
         #self.watchForMessages(bus)
 
@@ -46,7 +46,7 @@
                 self.pause()
 
             self.lastWatchTime = t
-        except:
+        except Exception:
             traceback.print_exc()
 
     def watchForMessages(self, bus):
--- a/light9/ascoltami/playlist.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/ascoltami/playlist.py	Sat May 25 12:10:51 2019 +0000
@@ -19,13 +19,13 @@
         try:
             currentIndex = self.songs.index(currentSong)
         except IndexError:
-            raise ValueError("%r is not in the current playlist (%r)." % \
-                (currentSong, self.playlistUri))
+            raise ValueError("%r is not in the current playlist (%r)." %
+                             (currentSong, self.playlistUri))
 
         try:
             nextSong = self.songs[currentIndex + 1]
         except IndexError:
-            raise NoSuchSong("%r is the last item in the playlist." % \
+            raise NoSuchSong("%r is the last item in the playlist." %
                              currentSong)
 
         return nextSong
--- a/light9/ascoltami/webapp.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/ascoltami/webapp.py	Sat May 25 12:10:51 2019 +0000
@@ -1,6 +1,5 @@
-import json, socket, subprocess, cyclone.web, os
+import json, socket, subprocess, cyclone.web
 from twisted.python.util import sibpath
-from twisted.python.filepath import FilePath
 from light9.namespaces import L9
 from light9.showconfig import getSongsFromShow, songOnDisk
 from rdflib import URIRef
@@ -140,7 +139,7 @@
         """
         if music is playing, this silently does nothing.
         """
-        graph, player = self.settings.app.graph, self.settings.app.player
+        player = self.settings.app.player
 
         if player.isAutostopped():
             player.resume()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/coffee.py	Sat May 25 12:10:51 2019 +0000
@@ -0,0 +1,23 @@
+from cycloneerr import PrettyErrorHandler
+import cyclone.web
+import subprocess
+
+
+class StaticCoffee(PrettyErrorHandler, cyclone.web.RequestHandler):
+    """
+    e.g.
+
+            (r'/effect\.js', StaticCoffee, {
+                'src': 'light9/effecteval/effect.coffee'
+            }),
+    """ # noqa
+
+    def initialize(self, src):
+        super(StaticCoffee, self).initialize()
+        self.src = src
+
+    def get(self):
+        self.set_header('Content-Type', 'application/javascript')
+        self.write(
+            subprocess.check_output(
+                ['/usr/bin/coffee', '--compile', '--print', self.src]))
--- a/light9/collector/collector.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/collector/collector.py	Sat May 25 12:10:51 2019 +0000
@@ -7,8 +7,9 @@
 
 # types only
 from rdflib import Graph, URIRef
-from typing import List, Dict, Tuple, Any, TypeVar, Generic
+from typing import List, Dict, Tuple, TypeVar, Generic, Optional
 from light9.collector.output import Output
+from light9.collector.weblisteners import WebListeners
 
 ClientType = TypeVar('ClientType')
 ClientSessionType = TypeVar('ClientSessionType')
@@ -24,7 +25,7 @@
     """
     ret = {}
 
-    outputByUri = {}  # universeUri : output
+    outputByUri: Dict[URIRef, Output] = {}  # universeUri : output
     for out in outputs:
         outputByUri[out.uri] = out
 
@@ -50,8 +51,11 @@
 
 class Collector(Generic[ClientType, ClientSessionType]):
 
-    def __init__(self, graph, outputs, listeners=None, clientTimeoutSec=10):
-        # type: (Graph, List[Output], List[Listener], float) -> None
+    def __init__(self,
+                 graph: Graph,
+                 outputs: List[Output],
+                 listeners: Optional[WebListeners] = None,
+                 clientTimeoutSec: float = 10):
         self.graph = graph
         self.outputs = outputs
         self.listeners = listeners
--- a/light9/collector/collector_test.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/collector/collector_test.py	Sat May 25 12:10:51 2019 +0000
@@ -1,7 +1,7 @@
 import unittest
 import datetime, time
 from freezegun import freeze_time
-from rdflib import Namespace, URIRef
+from rdflib import Namespace
 
 from light9.namespaces import L9, DEV
 from light9.collector.collector import Collector, outputMap
--- a/light9/collector/device.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/collector/device.py	Sat May 25 12:10:51 2019 +0000
@@ -1,6 +1,5 @@
 import logging
-import math
-from light9.namespaces import L9, RDF, DEV
+from light9.namespaces import L9
 from rdflib import Literal
 from webcolors import hex_to_rgb, rgb_to_hex
 from colormath.color_objects import sRGBColor, CMYColor
--- a/light9/collector/output.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/collector/output.py	Sat May 25 12:10:51 2019 +0000
@@ -3,7 +3,7 @@
 import time
 import usb.core
 import logging
-from twisted.internet import task, threads, reactor
+from twisted.internet import threads, reactor
 from greplin import scales
 log = logging.getLogger('output')
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/collector/weblisteners.py	Sat May 25 12:10:51 2019 +0000
@@ -0,0 +1,70 @@
+import logging, traceback, time, json
+log = logging.getLogger('weblisteners')
+
+class WebListeners(object):
+
+    def __init__(self):
+        self.clients = []
+        self.pendingMessageForDev = {}  # dev: (attrs, outputmap)
+        self.lastFlush = 0
+
+    def addClient(self, client):
+        self.clients.append([client, {}])  # seen = {dev: attrs}
+        log.info('added client %s %s', len(self.clients), client)
+
+    def delClient(self, client):
+        self.clients = [[c, t] for c, t in self.clients if c != client]
+        log.info('delClient %s, %s left', client, len(self.clients))
+
+    def outputAttrsSet(self, dev, attrs, outputMap):
+        """called often- don't be slow"""
+
+        self.pendingMessageForDev[dev] = (attrs, outputMap)
+        try:
+            self._flush()
+        except Exception:
+            traceback.print_exc()
+            raise
+
+    def _flush(self):
+        now = time.time()
+        if now < self.lastFlush + .05 or not self.clients:
+            return
+        self.lastFlush = now
+
+        while self.pendingMessageForDev:
+            dev, (attrs, outputMap) = self.pendingMessageForDev.popitem()
+
+            msg = None  # lazy, since makeMsg is slow
+
+            # this omits repeats, but can still send many
+            # messages/sec. Not sure if piling up messages for the browser
+            # could lead to slowdowns in the real dmx output.
+            for client, seen in self.clients:
+                if seen.get(dev) == attrs:
+                    continue
+                if msg is None:
+                    msg = self.makeMsg(dev, attrs, outputMap)
+
+                seen[dev] = attrs
+                client.sendMessage(msg)
+
+    def makeMsg(self, dev, attrs, outputMap):
+        attrRows = []
+        for attr, val in attrs.items():
+            output, index = outputMap[(dev, attr)]
+            attrRows.append({
+                'attr': attr.rsplit('/')[-1],
+                'val': val,
+                'chan': (output.shortId(), index + 1)
+            })
+        attrRows.sort(key=lambda r: r['chan'])
+        for row in attrRows:
+            row['chan'] = '%s %s' % (row['chan'][0], row['chan'][1])
+
+        msg = json.dumps({'outputAttrsSet': {
+            'dev': dev,
+            'attrs': attrRows
+        }},
+                         sort_keys=True)
+        return msg
--- a/light9/curvecalc/client.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/curvecalc/client.py	Sat May 25 12:10:51 2019 +0000
@@ -4,7 +4,6 @@
 import cyclone.httpclient
 from light9 import networking
 import urllib.request, urllib.parse, urllib.error
-from run_local import log
 
 
 def sendLiveInputPoint(curve, value):
--- a/light9/curvecalc/curve.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/curvecalc/curve.py	Sat May 25 12:10:51 2019 +0000
@@ -1,4 +1,4 @@
-import glob, time, logging, ast, os
+import logging, ast, os
 from bisect import bisect_left, bisect
 import louie as dispatcher
 from twisted.internet import reactor
@@ -45,7 +45,7 @@
 
     def load(self, filename):
         self.points[:] = []
-        for line in file(filename):
+        for line in open(filename):
             x, y = line.split()
             self.points.append((float(x), ast.literal_eval(y)))
         self.points.sort()
@@ -75,7 +75,7 @@
         if filename.endswith('-music') or filename.endswith('_music'):
             print("not saving music track")
             return
-        f = file(filename, 'w')
+        f = open(filename, 'w')
         for p in self.points:
             f.write("%s %r\n" % p)
         f.close()
--- a/light9/curvecalc/curveedit.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/curvecalc/curveedit.py	Sat May 25 12:10:51 2019 +0000
@@ -1,12 +1,11 @@
 """
 this may be split out from curvecalc someday, since it doesn't
 need to be tied to a gui """
-import cgi, time
+import cgi
 from twisted.internet import reactor
 import cyclone.web, cyclone.httpclient, cyclone.websocket
 from rdflib import URIRef
 from cycloneerr import PrettyErrorHandler
-from run_local import log
 from louie import dispatcher
 
 
--- a/light9/curvecalc/curveview.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/curvecalc/curveview.py	Sat May 25 12:10:51 2019 +0000
@@ -1,4 +1,4 @@
-import math, logging, traceback
+import math, logging
 from gi.repository import Gtk
 from gi.repository import Gdk
 from gi.repository import GooCanvas
@@ -6,7 +6,6 @@
 from rdflib import Literal
 from twisted.internet import reactor
 from light9.curvecalc.zoomcontrol import RegionZoom
-from light9.curvecalc import cursors
 from light9.curvecalc.curve import introPad, postPad
 from lib.goocanvas_compat import Points, polyline_new_line
 import imp
@@ -1276,8 +1275,8 @@
         if not self.live:  # workaround for old instances living past reload()
             return
 
-        r = self.row_under_mouse()
-        key = event.string
+        #r = self.row_under_mouse()
+        #key = event.string
         pass  # no handlers right now
 
     def row_under_mouse(self):
--- a/light9/curvecalc/output.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/curvecalc/output.py	Sat May 25 12:10:51 2019 +0000
@@ -1,8 +1,6 @@
 import time, logging
 from twisted.internet import reactor
 from light9 import Submaster, dmxclient
-from light9.namespaces import L9
-from light9.curvecalc.subterm import Subterm
 
 from louie import dispatcher
 log = logging.getLogger("output")
--- a/light9/curvecalc/subterm.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/curvecalc/subterm.py	Sat May 25 12:10:51 2019 +0000
@@ -1,8 +1,8 @@
-import math, os, random, logging
-from rdflib import Graph, URIRef, RDF, RDFS, Literal
+import logging
+from rdflib import Literal
 from louie import dispatcher
 import light9.Effects
-from light9 import Submaster, showconfig, prof
+from light9 import Submaster
 from light9.Patch import get_dmx_channel
 from rdfdb.patch import Patch
 from light9.namespaces import L9
--- a/light9/curvecalc/zoomcontrol.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/curvecalc/zoomcontrol.py	Sat May 25 12:10:51 2019 +0000
@@ -1,5 +1,3 @@
-from gi.repository import Gtk
-from gi.repository import GObject
 from gi.repository import GooCanvas
 import louie as dispatcher
 from light9.curvecalc import cursors
--- a/light9/dmxchanedit.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/dmxchanedit.py	Sat May 25 12:10:51 2019 +0000
@@ -18,7 +18,7 @@
 """
 
 import tkinter as tk
-from rdflib import RDF, Literal
+from rdflib import RDF
 import math, logging
 from decimal import Decimal
 from light9.namespaces import L9
--- a/light9/dmxclient.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/dmxclient.py	Sat May 25 12:10:51 2019 +0000
@@ -72,5 +72,5 @@
 if dummy:
     print("dmxclient: DMX is in dummy mode.")
 
-    def outputlevels(*args, **kw):
+    def outputlevels(*args, **kw): # noqa
         pass
--- a/light9/effect/effecteval.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/effect/effecteval.py	Sat May 25 12:10:51 2019 +0000
@@ -1,13 +1,10 @@
-from rdflib import URIRef, Literal
-from light9.namespaces import L9, RDF, DEV
+from rdflib import Literal
+from light9.namespaces import L9, DEV
 from webcolors import rgb_to_hex, hex_to_rgb
 from colorsys import hsv_to_rgb
-from decimal import Decimal
 import math
-import traceback
 from noise import pnoise1
 import logging
-import time
 from light9.effect.settings import DeviceSettings
 from light9.effect.scale import scale
 import random
@@ -135,7 +132,6 @@
 def effect_auraSparkles(effectSettings, strength, songTime, noteTime):
     out = {}
     tint = effectSettings.get(L9['tint'], '#ffffff')
-    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
     print(effectSettings)
     tr, tg, tb = hex_to_rgb(tint)
     for n in range(1, 5 + 1):
--- a/light9/effect/sequencer.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/effect/sequencer.py	Sat May 25 12:10:51 2019 +0000
@@ -44,7 +44,9 @@
             ea = settingValues[L9['effectAttr']]
             self.baseEffectSettings[ea] = settingValues[L9['value']]
 
-        floatVal = lambda s, p: float(g.value(s, p).toPython())
+        def floatVal(s, p):
+            return float(g.value(s, p).toPython())
+
         originTime = floatVal(uri, L9['originTime'])
         self.points = []
         for curve in g.objects(uri, L9['curve']):
--- a/light9/effect/settings.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/effect/settings.py	Sat May 25 12:10:51 2019 +0000
@@ -6,8 +6,7 @@
 import decimal
 import numpy
 from rdflib import URIRef, Literal
-from light9.namespaces import RDF, L9, DEV
-from rdfdb.patch import Patch
+from light9.namespaces import RDF, L9
 import logging
 log = logging.getLogger('settings')
 from light9.collector.device import resolve
--- a/light9/effect/settings_test.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/effect/settings_test.py	Sat May 25 12:10:51 2019 +0000
@@ -2,7 +2,7 @@
 from rdflib import Literal
 from rdfdb.patch import Patch
 from rdfdb.localsyncedgraph import LocalSyncedGraph
-from light9.namespaces import RDF, L9, DEV
+from light9.namespaces import L9, DEV
 from light9.effect.settings import DeviceSettings
 
 
--- a/light9/effecteval/effectloop.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/effecteval/effectloop.py	Sat May 25 12:10:51 2019 +0000
@@ -1,18 +1,17 @@
 import time, json, logging, traceback
 import numpy
 import serial
-from twisted.internet import reactor, threads
+from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.internet.error import TimeoutError
-from rdflib import URIRef, Literal
+from rdflib import URIRef
 import cyclone.httpclient
-from light9.namespaces import L9, RDF, RDFS
+from light9.namespaces import L9, RDF
 from light9.effecteval.effect import EffectNode
 from light9 import Effects
 from light9 import networking
 from light9 import Submaster
 from light9 import dmxclient
-from light9 import prof
 log = logging.getLogger('effectloop')
 
 
--- a/light9/gtkpyconsole.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/gtkpyconsole.py	Sat May 25 12:10:51 2019 +0000
@@ -1,5 +1,5 @@
 from lib.ipython_view import IPythonView
-import gi
+import gi # noqa
 from gi.repository import Gtk
 from gi.repository import Pango
 
--- a/light9/namespaces.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/namespaces.py	Sat May 25 12:10:51 2019 +0000
@@ -1,4 +1,4 @@
-from rdflib import Namespace, RDF, RDFS
+from rdflib import Namespace, RDF, RDFS  # noqa
 
 
 # Namespace was showing up in profiles
--- a/light9/paint/solve.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/paint/solve.py	Sat May 25 12:10:51 2019 +0000
@@ -1,11 +1,11 @@
-from light9.namespaces import RDF, L9, DEV
+from light9.namespaces import L9, DEV
 from PIL import Image
 import numpy
 import scipy.misc, scipy.ndimage, scipy.optimize
 import cairo
 import logging
 
-from light9.effect.settings import DeviceSettings, parseHex, toHex
+from light9.effect.settings import DeviceSettings, parseHex
 
 log = logging.getLogger('solve')
 
@@ -151,7 +151,7 @@
         results = []
         dist = ImageDist(img)
         if device is None:
-            items = list(self.samples.items())
+            items = self.samples.items()
         else:
             items = self.samplesForDevice[device]
         for uri, img2 in sorted(items):
@@ -220,18 +220,18 @@
             #saveNumpy('/tmp/sample_%s.png' % sample.split('/')[-1],
             #          f(picSample))
             sampleDist[sample] = dist.distanceTo(picSample)
-        results = sorted([(d, uri) for uri, d in list(sampleDist.items())])
+        results = sorted([(d, uri) for uri, d in sampleDist.items()])
 
         sample = results[0][1]
 
         # this is wrong; some wrong-alignments ought to be dimmer than full
         brightest0 = brightest(pic0)
-        brightestSample = brightest(self.samples[sample])
+        #brightestSample = brightest(self.samples[sample])
 
         if max(brightest0) < 1 / 255:
             return DeviceSettings(self.graph, [])
 
-        scale = brightest0 / brightestSample
+        #scale = brightest0 / brightestSample
 
         s = DeviceSettings.fromResource(self.graph, sample)
         # missing color scale, but it was wrong to operate on all devs at once
@@ -300,7 +300,7 @@
         for dev, devSettings in settings.byDevice():
             requestedColor = devSettings.getValue(dev, L9['color'])
             candidatePics = []  # (distance, path, picColor)
-            for sample, s in list(self.sampleSettings.items()):
+            for sample, s in self.sampleSettings.items():
                 path = self.path[sample]
                 otherDevSettings = s.ofDevice(dev)
                 if not otherDevSettings:
--- a/light9/paint/solve_test.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/paint/solve_test.py	Sat May 25 12:10:51 2019 +0000
@@ -2,7 +2,7 @@
 import numpy.testing
 from . import solve
 from rdflib import Namespace
-from light9.namespaces import RDF, L9, DEV
+from light9.namespaces import L9, DEV
 from rdfdb.localsyncedgraph import LocalSyncedGraph
 from light9.effect.settings import DeviceSettings
 
--- a/light9/showconfig.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/showconfig.py	Sat May 25 12:10:51 2019 +0000
@@ -3,7 +3,7 @@
 from os import path, getenv
 from rdflib import Graph
 from rdflib import URIRef
-from .namespaces import MUS, L9
+from .namespaces import L9
 log = logging.getLogger('showconfig')
 
 _config = None  # graph
--- a/light9/subclient.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/subclient.py	Sat May 25 12:10:51 2019 +0000
@@ -1,5 +1,5 @@
 from light9.collector.collector_client import sendToCollector
-from twisted.internet import reactor, task
+from twisted.internet import reactor
 import traceback
 import time
 import logging
@@ -38,7 +38,7 @@
         try:
             with self.graph.currentState() as g:
                 outputSettings = self.get_output_settings(_graph=g)
-        except:
+        except Exception:
             traceback.print_exc()
             return
         return sendToCollector('subclient', self.session, outputSettings)
--- a/light9/uihelpers.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/uihelpers.py	Sat May 25 12:10:51 2019 +0000
@@ -33,7 +33,7 @@
             f = open(".light9-window-geometry-%s" % name.replace(' ', '_'), 'w')
             f.write(tl.geometry())
         # else the window never got mapped
-    except Exception as e:
+    except Exception:
         # it's ok if there's no saved geometry
         pass
 
--- a/light9/vidref/main.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/vidref/main.py	Sat May 25 12:10:51 2019 +0000
@@ -79,7 +79,7 @@
         try:
             with gtk.gdk.lock:
                 self.replayViews.update(position)
-        except:
+        except Exception:
             traceback.print_exc()
         return True
 
--- a/light9/vidref/musictime.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/vidref/musictime.py	Sat May 25 12:10:51 2019 +0000
@@ -1,9 +1,7 @@
-import restkit, time, json, logging
+import time, json, logging
 from light9 import networking
 from twisted.internet import reactor
 from cyclone.httpclient import fetch
-from restkit.errors import ResourceNotFound
-import http_parser.http
 log = logging.getLogger()
 
 
@@ -29,7 +27,6 @@
         self.period = period
         self.hoverPeriod = .05
         self.onChange = onChange
-        self.musicResource = restkit.Resource(networking.musicPlayer.url)
 
         self.position = {}
         # driven by our pollCurvecalcTime and also by Gui.incomingTime
@@ -125,6 +122,9 @@
 
     def sendTime(self, t):
         """request that the player go to this time"""
-        self.musicResource.post("time",
-                                payload=json.dumps({"t": t}),
-                                headers={"content-type": "application/json"})
+        fetch(
+            method=b'POST',
+            url=networking.musicPlayer.path('time'),
+            body=json.dumps({"t": t}),
+            headers={b"content-type": [b"application/json"]},
+        )
--- a/light9/vidref/remotepivideo.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/vidref/remotepivideo.py	Sat May 25 12:10:51 2019 +0000
@@ -6,8 +6,8 @@
 import numpy
 import treq
 from twisted.internet import defer
-from light9.vidref.replay import framerate, songDir, takeDir, snapshotDir
-from light9 import prof, showconfig
+from light9.vidref.replay import songDir, takeDir, snapshotDir
+from light9 import showconfig
 from light9.namespaces import L9
 from PIL import Image
 from io import StringIO
--- a/light9/vidref/videorecorder.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/vidref/videorecorder.py	Sat May 25 12:10:51 2019 +0000
@@ -158,7 +158,7 @@
             img = Image.fromstring('RGB', (cap['width'], cap['height']),
                                    buffer.data)
             self.imagesToSave.put((position, img, buffer.timestamp))
-        except:
+        except Exception:
             traceback.print_exc()
 
         return gst.FLOW_OK
--- a/light9/wavepoints.py	Sat May 25 12:06:01 2019 +0000
+++ b/light9/wavepoints.py	Sat May 25 12:10:51 2019 +0000
@@ -4,13 +4,14 @@
 def simp(filename, seconds_per_average=0.001):
     """smaller seconds_per_average means fewer data points"""
     wavefile = wave.open(filename, 'rb')
-    print("# gnuplot data for %s, seconds_per_average=%s" % \
-        (filename, seconds_per_average))
+    print("# gnuplot data for %s, seconds_per_average=%s" %
+          (filename, seconds_per_average))
     print(
         "# %d channels, samplewidth: %d, framerate: %s, frames: %d\n# Compression type: %s (%s)"
         % wavefile.getparams())
 
     framerate = wavefile.getframerate()  # frames / second
+
     frames_to_read = int(framerate * seconds_per_average)
     print("# frames_to_read=%s" % frames_to_read)
 
@@ -31,7 +32,7 @@
         values.append(m)
         count += frames_to_read
         # if count>1000000:
-        # break
+        #     break
 
     # find the min and max
     min_value, max_value = min(values), max(values)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/zmqtransport.py	Sat May 25 12:10:51 2019 +0000
@@ -0,0 +1,41 @@
+import json
+from rdflib import URIRef, Literal
+from greplin import scales
+from txzmq import ZmqEndpoint, ZmqFactory, ZmqPullConnection
+import logging
+
+log = logging.getLogger('zmq')
+
+
+def parseJsonMessage(msg):
+    body = json.loads(msg)
+    settings = []
+    for device, attr, value in body['settings']:
+        if isinstance(value, str) and value.startswith('http'):
+            value = URIRef(value)
+        else:
+            value = Literal(value)
+        settings.append((URIRef(device), URIRef(attr), value))
+    return body['client'], body['clientSession'], settings, body['sendTime']
+
+
+def startZmq(port, collector):
+    stats = scales.collection('/zmqServer', scales.PmfStat('setAttr'))
+
+    zf = ZmqFactory()
+    addr = 'tcp://*:%s' % port
+    log.info('creating zmq endpoint at %r', addr)
+    e = ZmqEndpoint('bind', addr)
+
+    class Pull(ZmqPullConnection):
+        #highWaterMark = 3
+        def onPull(self, message):
+            with stats.setAttr.time():
+                # todo: new compressed protocol where you send all URIs up
+                # front and then use small ints to refer to devices and
+                # attributes in subsequent requests.
+                client, clientSession, settings, sendTime = parseJsonMessage(
+                    message[0])
+                collector.setAttrs(client, clientSession, settings, sendTime)
+
+    Pull(zf, e)