0
|
1 #!/usr/bin/python
|
|
2 """
|
|
3
|
|
4 this is the only process to talk to the dmx hardware. other clients
|
|
5 can connect to this server and present dmx output, and this server
|
|
6 will max ('pile-on') all the client requests.
|
|
7
|
|
8 this server has a level display which is the final set of values that
|
|
9 goes to the hardware.
|
|
10
|
|
11 clients shall connect to the xmlrpc server and send:
|
|
12
|
|
13 their PID (or some other cookie)
|
|
14
|
|
15 a length-n list of 0..1 levels which will represent the channel
|
|
16 values for the n first dmx channels.
|
|
17
|
|
18 server is port 8030; xmlrpc method is called outputlevels(pid,levellist).
|
|
19
|
|
20 todo:
|
|
21 save dmx on quit and restore on restart
|
|
22 if parport fails, run in dummy mode (and make an option for that too)
|
|
23 """
|
|
24
|
|
25 from __future__ import division
|
|
26 from twisted.internet import reactor
|
|
27 from twisted.web import xmlrpc, server
|
|
28 import sys,time,os
|
|
29 from optparse import OptionParser
|
|
30 from io import ParportDMX
|
|
31 from updatefreq import Updatefreq
|
|
32
|
|
33 class XMLRPCServe(xmlrpc.XMLRPC):
|
|
34 def __init__(self,options):
|
|
35
|
|
36 xmlrpc.XMLRPC.__init__(self)
|
|
37
|
|
38 self.clientlevels={} # clientID : list of levels
|
|
39 self.lastseen={} # clientID : time last seen
|
|
40 self.clientfreq={} # clientID : updatefreq
|
|
41
|
|
42 self.combinedlevels=[] # list of levels, after max'ing the clients
|
|
43 self.clientschanged=1 # have clients sent anything since the last send?
|
|
44 self.options=options
|
|
45 self.lastupdate=0 # time of last dmx send
|
|
46 self.laststatsprint=0 # time
|
|
47
|
|
48 # desired seconds between sendlevels() calls
|
|
49 self.calldelay=1/options.updates_per_sec
|
|
50
|
|
51 print "starting parport connection"
|
|
52 self.parportdmx=ParportDMX()
|
|
53 if os.environ.get('DMXDUMMY',0):
|
|
54 self.parportdmx.godummy()
|
|
55 else:
|
|
56 self.parportdmx.golive()
|
|
57
|
|
58
|
|
59 self.updatefreq=Updatefreq() # freq of actual dmx sends
|
|
60 self.num_unshown_updates=None
|
|
61 self.lastshownlevels=None
|
|
62 # start the loop
|
|
63 self.sendlevels()
|
|
64
|
|
65 # the other loop
|
|
66 self.purgeclients()
|
|
67
|
|
68 def purgeclients(self):
|
|
69
|
|
70 """forget about any clients who haven't sent levels in a while.
|
|
71 this runs in a loop"""
|
|
72
|
|
73 purge_age=10 # seconds
|
|
74
|
|
75 reactor.callLater(1,self.purgeclients)
|
|
76
|
|
77 now=time.time()
|
|
78 cids=self.clientlevels.keys()
|
|
79 for cid in cids:
|
|
80 lastseen=self.lastseen[cid]
|
|
81 if lastseen<now-purge_age:
|
|
82 print ("forgetting client %s (no activity for %s sec)" %
|
|
83 (cid,purge_age))
|
|
84 del self.clientlevels[cid]
|
|
85 del self.clientfreq[cid]
|
|
86 del self.lastseen[cid]
|
|
87
|
|
88 def sendlevels(self):
|
|
89
|
|
90 """sends to dmx if levels have changed, or if we havent sent
|
|
91 in a while"""
|
|
92
|
|
93 reactor.callLater(self.calldelay,self.sendlevels)
|
|
94
|
|
95 if self.clientschanged:
|
|
96 # recalc levels
|
|
97
|
|
98 self.calclevels()
|
|
99
|
|
100 if (self.num_unshown_updates is None or # first time
|
|
101 self.options.fast_updates or # show always
|
|
102 (self.combinedlevels!=self.lastshownlevels and # changed
|
|
103 self.num_unshown_updates>5)): # not too frequent
|
|
104 self.num_unshown_updates=0
|
|
105 self.printlevels()
|
|
106 self.lastshownlevels=self.combinedlevels[:]
|
|
107 else:
|
|
108 self.num_unshown_updates+=1
|
|
109
|
|
110 if time.time()>self.laststatsprint+2:
|
|
111 self.laststatsprint=time.time()
|
|
112 self.printstats()
|
|
113
|
|
114 if self.clientschanged or time.time()>self.lastupdate+1:
|
|
115 self.lastupdate=time.time()
|
|
116 self.sendlevels_dmx()
|
|
117
|
|
118 self.clientschanged=0 # clear the flag
|
|
119
|
|
120 def calclevels(self):
|
|
121 """combine all the known client levels into self.combinedlevels"""
|
|
122 self.combinedlevels=[]
|
|
123 for chan in range(0,self.parportdmx.dimmers):
|
|
124 x=0
|
|
125 for clientlist in self.clientlevels.values():
|
|
126 if len(clientlist)>chan:
|
|
127 # clamp client levels to 0..1
|
|
128 cl=max(0,min(1,clientlist[chan]))
|
|
129 x=max(x,cl)
|
|
130 self.combinedlevels.append(x)
|
|
131
|
|
132 def printlevels(self):
|
|
133 """write all the levels to stdout"""
|
|
134 print "Levels:","".join(["% 2d "%(x*100) for
|
|
135 x in self.combinedlevels])
|
|
136
|
|
137 def printstats(self):
|
|
138 """print the clock, freq, etc, with a \r at the end"""
|
|
139
|
|
140 sys.stdout.write("dmxserver up at %s, [polls %s] "%
|
|
141 (time.strftime("%H:%M:%S"),
|
|
142 str(self.updatefreq),
|
|
143 ))
|
|
144 for cid,freq in self.clientfreq.items():
|
|
145 sys.stdout.write("[%s %s] " % (cid,str(freq)))
|
|
146 sys.stdout.write("\r")
|
|
147 sys.stdout.flush()
|
|
148
|
|
149 def sendlevels_dmx(self):
|
|
150 """output self.combinedlevels to dmx, and keep the updates/sec stats"""
|
|
151 # they'll get divided by 100
|
|
152 if self.parportdmx:
|
|
153 self.parportdmx.sendlevels([l*100 for l in self.combinedlevels])
|
|
154 self.updatefreq.update()
|
|
155
|
|
156 def xmlrpc_echo(self,x):
|
|
157 return x
|
|
158
|
|
159 def xmlrpc_outputlevels(self,cid,levellist):
|
|
160 """send a unique id for your client (name+pid maybe), then
|
|
161 the variable-length dmx levellist (scaled 0..1)"""
|
|
162 if levellist!=self.clientlevels.get(cid,None):
|
|
163 self.clientlevels[cid]=levellist
|
|
164 self.clientschanged=1
|
|
165 if cid not in self.lastseen:
|
|
166 print "hello new client %s" % cid
|
|
167 self.clientfreq[cid]=Updatefreq()
|
|
168
|
|
169 self.lastseen[cid]=time.time()
|
|
170 self.clientfreq[cid].update()
|
|
171 return "ok"
|
|
172
|
|
173 parser=OptionParser()
|
|
174 parser.add_option("-f","--fast-updates",action='store_true',
|
|
175 help=('display all dmx output to stdout instead '
|
|
176 'of the usual reduced output'))
|
|
177 parser.add_option("-r","--updates-per-sec",type='float',default=20,
|
|
178 help=('dmx output frequency'))
|
|
179 (options,songfiles)=parser.parse_args()
|
|
180
|
|
181 print options
|
|
182
|
|
183 print "starting xmlrpc server on port 8030"
|
|
184 reactor.listenTCP(8030,server.Site(XMLRPCServe(options)))
|
|
185 reactor.run()
|
|
186
|