Mercurial > code > home > repos > light9
comparison light8/dmxserver.py @ 112:afbdae5e1359
dmx light output is now via a separate process which light8 talks to.
dmx light output is now via a separate process which light8 talks to.
other programs can also submit dmx to the server
author | drewp |
---|---|
date | Wed, 11 Jun 2003 09:36:35 +0000 |
parents | 45b12307c695 |
children | 9ddea0c614ee |
comparison
equal
deleted
inserted
replaced
111:0c619695d6c6 | 112:afbdae5e1359 |
---|---|
1 #!/usr/bin/python | |
2 """ | 1 """ |
3 | 2 |
4 this is the only process to talk to the dmx hardware. other clients | 3 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 | 4 can connect to this server and present dmx output, and this server |
6 will max ('pile-on') all the client requests. | 5 will max ('pile-on') all the client requests. |
15 a length-n list of 0..1 levels which will represent the channel | 14 a length-n list of 0..1 levels which will represent the channel |
16 values for the n first dmx channels. | 15 values for the n first dmx channels. |
17 | 16 |
18 server is port 8030; xmlrpc method is called outputlevels(pid,levellist). | 17 server is port 8030; xmlrpc method is called outputlevels(pid,levellist). |
19 | 18 |
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 """ | 19 """ |
24 | 20 |
25 from __future__ import division | 21 from __future__ import division |
26 from twisted.internet import reactor | 22 from twisted.internet import reactor |
27 from twisted.web import xmlrpc, server | 23 from twisted.web import xmlrpc, server |
28 import sys,time,os | 24 |
29 from optparse import OptionParser | 25 import sys |
26 sys.path.append("../light8") | |
30 from io import ParportDMX | 27 from io import ParportDMX |
31 from updatefreq import Updatefreq | |
32 | 28 |
33 class XMLRPCServe(xmlrpc.XMLRPC): | 29 class XMLRPCServe(xmlrpc.XMLRPC): |
34 def __init__(self,options): | 30 def __init__(self): |
35 | 31 self.clientlevels={} # clientPID : list of levels |
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 | 32 self.combinedlevels=[] # list of levels, after max'ing the clients |
43 self.clientschanged=1 # have clients sent anything since the last send? | 33 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 | 34 |
51 print "starting parport connection" | 35 print "starting parport connection" |
52 self.parportdmx=ParportDMX() | 36 self.parportdmx=ParportDMX() |
53 if os.environ.get('DMXDUMMY',0): | 37 self.parportdmx.golive() |
54 self.parportdmx.godummy() | |
55 else: | |
56 self.parportdmx.golive() | |
57 | |
58 | 38 |
59 self.updatefreq=Updatefreq() # freq of actual dmx sends | |
60 self.num_unshown_updates=None | |
61 self.lastshownlevels=None | |
62 # start the loop | 39 # start the loop |
40 self.numupdates=0 | |
63 self.sendlevels() | 41 self.sendlevels() |
64 | 42 |
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): | 43 def sendlevels(self): |
89 | 44 reactor.callLater(.02,self.sendlevels) |
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: | 45 if self.clientschanged: |
96 # recalc levels | 46 # recalc levels |
47 self.combinedlevels=[] | |
48 for chan in range(0,self.parportdmx.dimmers): | |
49 x=0 | |
50 for clientlist in self.clientlevels.values(): | |
51 if len(clientlist)>chan: | |
52 x=max(x,clientlist[chan]) | |
53 self.combinedlevels.append(x) | |
97 | 54 |
98 self.calclevels() | 55 self.numupdates=self.numupdates+1 |
99 | 56 if (self.numupdates%200)==0: |
100 if (self.num_unshown_updates is None or # first time | 57 print self.combinedlevels |
101 self.options.fast_updates or # show always | 58 |
102 (self.combinedlevels!=self.lastshownlevels and # changed | 59 # now send combinedlevels (they'll get divided by 100) |
103 self.num_unshown_updates>5)): # not too frequent | 60 self.parportdmx.sendlevels([l*100 for l in self.combinedlevels]) |
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 | 61 |
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): | 62 def xmlrpc_echo(self,x): |
157 return x | 63 return x |
158 | 64 |
159 def xmlrpc_outputlevels(self,cid,levellist): | 65 def xmlrpc_outputlevels(self,pid,levellist): |
160 """send a unique id for your client (name+pid maybe), then | 66 self.clientlevels[pid]=levellist |
161 the variable-length dmx levellist (scaled 0..1)""" | 67 self.clientschanged=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" | 68 return "ok" |
172 | 69 |
173 parser=OptionParser() | 70 print "starting server on 8030" |
174 parser.add_option("-f","--fast-updates",action='store_true', | 71 reactor.listenTCP(8030,server.Site(XMLRPCServe())) |
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() | 72 reactor.run() |
186 | 73 |