comparison light8/dmxserver.py @ 0:45b12307c695

Initial revision
author drewp
date Wed, 03 Jul 2002 09:37:57 +0000
parents
children afbdae5e1359
comparison
equal deleted inserted replaced
-1:000000000000 0:45b12307c695
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