diff --git a/light8/dmxserver.py b/light8/dmxserver.py --- a/light8/dmxserver.py +++ b/light8/dmxserver.py @@ -17,7 +17,6 @@ clients shall connect to the xmlrpc serv 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) @@ -27,62 +26,129 @@ from __future__ import division from twisted.internet import reactor from twisted.web import xmlrpc, server import sys,time +from optik import OptionParser from io import ParportDMX +from updatefreq import Updatefreq class XMLRPCServe(xmlrpc.XMLRPC): - def __init__(self): + def __init__(self,options): xmlrpc.XMLRPC.__init__(self) - self.clientlevels={} # clientPID : list of levels + 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 print "starting parport connection" self.parportdmx=ParportDMX() self.parportdmx.golive() # start the loop + self.updatefreq=Updatefreq() self.num_unshown_updates=None + self.lastshownlevels=None self.sendlevels() + - + def purgeclients(self): + + """forget about any clients who haven't sent levels in a while + (5 seconds)""" + now=time.time() + for cid,lastseen in self.lastseen.items(): + if lastseenchan: - x=max(x,clientlist[chan]) - self.combinedlevels.append(x) - if self.num_unshown_updates is None or (self.combinedlevels!=oldlevels and - self.num_unshown_updates>10): + self.purgeclients() + 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>10)): # not too frequent self.num_unshown_updates=0 - print "Levels:","".join(["% 2d "%x for x in self.combinedlevels]) + self.printlevels() + self.lastshownlevels=self.combinedlevels[:] else: self.num_unshown_updates+=1 - if (self.num_unshown_updates-1)%100==0: - sys.stdout.write("dmxserver up at %s \r"%time.strftime("%H:%M:%S")) - sys.stdout.flush() - # now send combinedlevels (they'll get divided by 100) + if (self.num_unshown_updates-1)%50==0: + self.printstats() + + self.sendlevels_dmx() + + 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, [server %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.parportdmx.sendlevels([l*100 for l in self.combinedlevels]) + self.updatefreq.update() + def xmlrpc_echo(self,x): return x - def xmlrpc_outputlevels(self,pid,levellist): - self.clientlevels[pid]=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)""" + 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')) +(options,songfiles)=parser.parse_args() + print "starting xmlrpc server on port 8030" -reactor.listenTCP(8030,server.Site(XMLRPCServe())) +reactor.listenTCP(8030,server.Site(XMLRPCServe(options))) reactor.run()