diff --git a/bin/dmxserver b/bin/dmxserver --- a/bin/dmxserver +++ b/bin/dmxserver @@ -27,10 +27,10 @@ todo: from __future__ import division from twisted.internet import reactor from twisted.web import xmlrpc, server -import sys,time,os +import sys, time, os from optparse import OptionParser import run_local -import txosc.dispatch, txosc.async +import txosc.dispatch, txosc. async from light9.io import ParportDMX, UsbDMX from light9.updatefreq import Updatefreq @@ -39,21 +39,26 @@ from light9 import networking from txzmq import ZmqEndpoint, ZmqFactory, ZmqPullConnection, ZmqRequestTimeoutError import json + def startZmq(port, outputlevels): zf = ZmqFactory() e = ZmqEndpoint('bind', 'tcp://*:%s' % port) s = ZmqPullConnection(zf, e) + def onPull(message): msg = json.loads(message[0]) outputlevels(msg['clientid'], msg['levellist']) + s.onPull = onPull + class ReceiverApplication(object): """ receive UDP OSC messages. address is /dmx/1 for dmx channel 1, arguments are 0-1 floats for that channel and any number of following channels. """ + def __init__(self, port, lightServer): self.port = port self.lightServer = lightServer @@ -61,7 +66,7 @@ class ReceiverApplication(object): self.receiver.addCallback("/dmx/*", self.pixel_handler) self._server_port = reactor.listenUDP( self.port, - txosc.async.DatagramServerProtocol(self.receiver), + txosc. async .DatagramServerProtocol(self.receiver), interface='0.0.0.0') print "Listening OSC on udp port %s" % (self.port) @@ -70,127 +75,127 @@ class ReceiverApplication(object): startChannel = int(message.address.split('/')[2]) levels = [a.value for a in message.arguments] allLevels = [0] * (startChannel - 1) + levels - self.lightServer.xmlrpc_outputlevels("osc@%s" % startChannel, - allLevels) + self.lightServer.xmlrpc_outputlevels("osc@%s" % startChannel, allLevels) + class XMLRPCServe(xmlrpc.XMLRPC): - def __init__(self,options): + + def __init__(self, options): xmlrpc.XMLRPC.__init__(self) - - self.clientlevels={} # clientID : list of levels - self.lastseen={} # clientID : time last seen - self.clientfreq={} # clientID : updatefreq - - self.combinedlevels=[] # list of levels, after max'ing the clients - self.clientschanged=1 # have clients sent anything since the last send? - self.options=options - self.lastupdate=0 # time of last dmx send - self.laststatsprint=0 # time + + self.clientlevels = {} # clientID : list of levels + self.lastseen = {} # clientID : time last seen + self.clientfreq = {} # clientID : updatefreq + + self.combinedlevels = [] # list of levels, after max'ing the clients + self.clientschanged = 1 # have clients sent anything since the last send? + self.options = options + self.lastupdate = 0 # time of last dmx send + self.laststatsprint = 0 # time # desired seconds between sendlevels() calls - self.calldelay=1/options.updates_per_sec + self.calldelay = 1 / options.updates_per_sec print "starting parport connection" self.parportdmx = UsbDMX(dimmers=90, port=options.dmx_device) - if os.environ.get('DMXDUMMY',0): + if os.environ.get('DMXDUMMY', 0): self.parportdmx.godummy() else: self.parportdmx.golive() - - self.updatefreq=Updatefreq() # freq of actual dmx sends - self.num_unshown_updates=None - self.lastshownlevels=None + self.updatefreq = Updatefreq() # freq of actual dmx sends + self.num_unshown_updates = None + self.lastshownlevels = None # start the loop self.sendlevels() # the other loop self.purgeclients() - + def purgeclients(self): - """forget about any clients who haven't sent levels in a while. this runs in a loop""" - purge_age=10 # seconds - - reactor.callLater(1,self.purgeclients) + purge_age = 10 # seconds - now=time.time() + reactor.callLater(1, self.purgeclients) + + now = time.time() cids = self.lastseen.keys() for cid in cids: - lastseen=self.lastseen[cid] + lastseen = self.lastseen[cid] if lastseen < now - purge_age: - print ("forgetting client %s (no activity for %s sec)" % - (cid,purge_age)) + print("forgetting client %s (no activity for %s sec)" % + (cid, purge_age)) try: del self.clientlevels[cid] except KeyError: pass del self.clientfreq[cid] del self.lastseen[cid] - + def sendlevels(self): - """sends to dmx if levels have changed, or if we havent sent in a while""" - reactor.callLater(self.calldelay,self.sendlevels) + reactor.callLater(self.calldelay, self.sendlevels) if self.clientschanged: # recalc levels self.calclevels() - - if (self.num_unshown_updates is None or # first time - self.options.fast_updates or # show always - (self.combinedlevels!=self.lastshownlevels and # changed - self.num_unshown_updates>5)): # not too frequent - self.num_unshown_updates=0 + + if (self.num_unshown_updates is None or # first time + self.options.fast_updates or # show always + ( + self.combinedlevels != self.lastshownlevels and # changed + self.num_unshown_updates > 5)): # not too frequent + self.num_unshown_updates = 0 self.printlevels() - self.lastshownlevels=self.combinedlevels[:] + self.lastshownlevels = self.combinedlevels[:] else: - self.num_unshown_updates+=1 + self.num_unshown_updates += 1 - if time.time()>self.laststatsprint+2: - self.laststatsprint=time.time() + if time.time() > self.laststatsprint + 2: + self.laststatsprint = time.time() self.printstats() # used to be a fixed 1 in here, for the max delay between # calls, instead of calldelay - if self.clientschanged or time.time() > self.lastupdate + self.calldelay: - self.lastupdate=time.time() + if self.clientschanged or time.time( + ) > self.lastupdate + self.calldelay: + self.lastupdate = time.time() self.sendlevels_dmx() - self.clientschanged=0 # clear the flag - + self.clientschanged = 0 # clear the flag + def calclevels(self): """combine all the known client levels into self.combinedlevels""" - self.combinedlevels=[] - for chan in range(0,self.parportdmx.dimmers): - x=0 + self.combinedlevels = [] + for chan in range(0, self.parportdmx.dimmers): + x = 0 for clientlist in self.clientlevels.values(): - if len(clientlist)>chan: + if len(clientlist) > chan: # clamp client levels to 0..1 - cl=max(0,min(1,clientlist[chan])) - x=max(x,cl) + cl = max(0, min(1, clientlist[chan])) + x = max(x, cl) self.combinedlevels.append(x) def printlevels(self): """write all the levels to stdout""" - print "Levels:","".join(["% 2d "%(x*100) for - x in self.combinedlevels]) - + print "Levels:", "".join( + ["% 2d " % (x * 100) for x in self.combinedlevels]) + def printstats(self): """print the clock, freq, etc, with a \r at the end""" - sys.stdout.write("dmxserver up at %s, [polls %s] "% - (time.strftime("%H:%M:%S"), - str(self.updatefreq), - )) - for cid,freq in self.clientfreq.items(): - sys.stdout.write("[%s %s] " % (cid,str(freq))) + sys.stdout.write("dmxserver up at %s, [polls %s] " % ( + time.strftime("%H:%M:%S"), + str(self.updatefreq), + )) + for cid, freq in self.clientfreq.items(): + sys.stdout.write("[%s %s] " % (cid, str(freq))) sys.stdout.write("\r") sys.stdout.flush() @@ -198,18 +203,18 @@ class XMLRPCServe(xmlrpc.XMLRPC): """output self.combinedlevels to dmx, and keep the updates/sec stats""" # they'll get divided by 100 if self.parportdmx: - self.parportdmx.sendlevels([l*100 for l in self.combinedlevels]) + self.parportdmx.sendlevels([l * 100 for l in self.combinedlevels]) self.updatefreq.update() - - def xmlrpc_echo(self,x): + + def xmlrpc_echo(self, x): return x - - def xmlrpc_outputlevels(self,cid,levellist): + + def xmlrpc_outputlevels(self, cid, levellist): """send a unique id for your client (name+pid maybe), then the variable-length dmx levellist (scaled 0..1)""" - if levellist!=self.clientlevels.get(cid,None): - self.clientlevels[cid]=levellist - self.clientschanged=1 + if levellist != self.clientlevels.get(cid, None): + self.clientlevels[cid] = levellist + self.clientschanged = 1 self.trackClientFreq(cid) return "ok" @@ -227,43 +232,50 @@ class XMLRPCServe(xmlrpc.XMLRPC): i -= 1 if i < 0: return [] - trunc = trunc[:i+1] + trunc = trunc[:i + 1] return trunc - + def trackClientFreq(self, cid): if cid not in self.lastseen: print "hello new client %s" % cid - self.clientfreq[cid]=Updatefreq() - self.lastseen[cid]=time.time() + self.clientfreq[cid] = Updatefreq() + self.lastseen[cid] = time.time() self.clientfreq[cid].update() - + -parser=OptionParser() -parser.add_option("-f","--fast-updates",action='store_true', +parser = OptionParser() +parser.add_option("-f", + "--fast-updates", + action='store_true', help=('display all dmx output to stdout instead ' 'of the usual reduced output')) -parser.add_option("-r","--updates-per-sec",type='float',default=20, +parser.add_option("-r", + "--updates-per-sec", + type='float', + default=20, help=('dmx output frequency')) -parser.add_option("-d","--dmx-device", default='/dev/dmx0', +parser.add_option("-d", + "--dmx-device", + default='/dev/dmx0', help='dmx device name') -parser.add_option("-n", "--dummy", action="store_true", +parser.add_option("-n", + "--dummy", + action="store_true", help="dummy mode, same as DMXDUMMY=1 env variable") -(options,songfiles)=parser.parse_args() +(options, songfiles) = parser.parse_args() print options if options.dummy: os.environ['DMXDUMMY'] = "1" - port = networking.dmxServer.port print "starting xmlrpc server on port %s" % port xmlrpcServe = XMLRPCServe(options) -reactor.listenTCP(port,server.Site(xmlrpcServe)) +reactor.listenTCP(port, server.Site(xmlrpcServe)) startZmq(networking.dmxServerZmq.port, xmlrpcServe.xmlrpc_outputlevels) - + oscApp = ReceiverApplication(9051, xmlrpcServe) reactor.run() -