Mercurial > code > home > repos > light9
diff bin/dmxserver @ 209:1a84c5e83d3e
dmxserver and subcomposer work in new layout
author | drewp@bigasterisk.com |
---|---|
date | Sun, 10 Apr 2005 19:12:57 +0000 |
parents | light8/dmxserver.py@cde2ae379be0 |
children | f41004d5a507 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/dmxserver Sun Apr 10 19:12:57 2005 +0000 @@ -0,0 +1,187 @@ +#!/usr/bin/python +""" + +this is the only process to talk to the dmx hardware. other clients +can connect to this server and present dmx output, and this server +will max ('pile-on') all the client requests. + +this server has a level display which is the final set of values that +goes to the hardware. + +clients shall connect to the xmlrpc server and send: + + their PID (or some other cookie) + + a length-n list of 0..1 levels which will represent the channel + values for the n first dmx channels. + +server is port 8030; xmlrpc method is called outputlevels(pid,levellist). + +todo: + save dmx on quit and restore on restart + if parport fails, run in dummy mode (and make an option for that too) +""" + +from __future__ import division +from twisted.internet import reactor +from twisted.web import xmlrpc, server +import sys,time,os +from optparse import OptionParser +import run_local +from light9.io import ParportDMX +from light9.updatefreq import Updatefreq + +class XMLRPCServe(xmlrpc.XMLRPC): + 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 + + # desired seconds between sendlevels() calls + self.calldelay=1/options.updates_per_sec + + print "starting parport connection" + self.parportdmx=ParportDMX() + 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 + # 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) + + now=time.time() + cids=self.clientlevels.keys() + for cid in cids: + lastseen=self.lastseen[cid] + if lastseen<now-purge_age: + print ("forgetting client %s (no activity for %s sec)" % + (cid,purge_age)) + del self.clientlevels[cid] + 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) + + 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 + self.printlevels() + self.lastshownlevels=self.combinedlevels[:] + else: + self.num_unshown_updates+=1 + + if time.time()>self.laststatsprint+2: + self.laststatsprint=time.time() + self.printstats() + + if self.clientschanged or time.time()>self.lastupdate+1: + self.lastupdate=time.time() + self.sendlevels_dmx() + + 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 + for clientlist in self.clientlevels.values(): + if len(clientlist)>chan: + # clamp client levels to 0..1 + 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]) + + 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("\r") + sys.stdout.flush() + + def sendlevels_dmx(self): + """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.updatefreq.update() + + def xmlrpc_echo(self,x): + return x + + 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 cid not in self.lastseen: + print "hello new client %s" % cid + self.clientfreq[cid]=Updatefreq() + + self.lastseen[cid]=time.time() + self.clientfreq[cid].update() + return "ok" + +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, + help=('dmx output frequency')) +(options,songfiles)=parser.parse_args() + +print options + +print "starting xmlrpc server on port 8030" +reactor.listenTCP(8030,server.Site(XMLRPCServe(options))) +reactor.run() +