Files
@ 3e3ba0e3d4b8
Branch filter:
Location: light9/bin/dmxserver - annotation
3e3ba0e3d4b8
7.0 KiB
text/plain
comment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 44189a37a876 44189a37a876 1a84c5e83d3e f41004d5a507 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 44189a37a876 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 941cfe1e1691 1a84c5e83d3e 1a84c5e83d3e 941cfe1e1691 1a84c5e83d3e 1a84c5e83d3e 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 44189a37a876 44189a37a876 44189a37a876 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 941cfe1e1691 1a84c5e83d3e 1a84c5e83d3e 941cfe1e1691 9d1f323fb3d3 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e 1a84c5e83d3e f41004d5a507 f41004d5a507 f41004d5a507 1a84c5e83d3e 1a84c5e83d3e | #!/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, UsbDMX
from light9.updatefreq import Updatefreq
from light9 import networking
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=UsbDMX() #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.lastseen.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))
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)
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()
# 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()
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
self.trackClientFreq(cid)
return "ok"
def xmlrpc_currentlevels(self, cid):
"""get a list of levels we're currently sending out. All
channels beyond the list you get back, they're at zero."""
# if this is still too slow, it might help to return a single
# pickled string
self.trackClientFreq(cid)
trunc = self.combinedlevels[:]
i = len(trunc) - 1
if i < 0:
return []
while trunc[i] == 0 and i >= 0:
i -= 1
if i < 0:
return []
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].update()
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
port = networking.dmxServerPort()
print "starting xmlrpc server on port %s" % port
reactor.listenTCP(port,server.Site(XMLRPCServe(options)))
reactor.run()
|