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