handle all pending requests in a "frame"
[pyosc:devel.git] / testbench.py
1 #!/usr/bin/env python
2
3 import optparse
4 from OSC import *
5 from OSC import _readString, _readFloat, _readInt
6
7 def testStreamingServerAndClient(listen_address):
8         """ Testbench for streaming OSC code (OSC over TCP).
9         We do not have to test the message generation etc. here. This is the job of
10         other tests. Therefore we start a server and a client and send some
11         messages around and make sure that bothe  shut down cleanly. Then we
12         terminate the whole testbench. 
13         """
14         message = OSCMessage("/print")
15         message += [44, 11, 4.5, "the white cliffs of dover"]
16         strings = OSCMessage("/prin{ce,t}")
17         strings += ["Mary", "had", "a", "little", "lamb", 14.5, -400]
18
19         blob = OSCMessage("/pri*")
20         blob.append("","b")
21         blob.append("b","b")
22         blob.append("blob","b")
23         blob.append("blobs","b")
24         blob.append(42)
25
26         print1 = OSCMessage("/print")
27         print1.append("Hey man, that's cool.")
28         print1.append(42)
29         print1.append(3.1415926)
30
31         bundle = OSCBundle()
32         bundle.append(print1)
33         bundle.append({'addr':"/print", 'args':["bundled messages:", 2]})
34         bundle.setAddress("/*print")
35         bundle.append(("no,", 3, "actually."))
36
37         print "\nInstantiating OSCStreamingServer:"
38         
39         # define a message-handler function for the server to call.
40         def printing_handler(addr, tags, stuff, source):
41                 msg_string = "%s [%s] %s" % (addr, tags, str(stuff))
42                 msg_string = "SERVER: Got '%s' from %s" % (msg_string, getUrlStr(source))
43                 print msg_string
44                 
45                 # send a reply to the client.
46                 msg = OSCMessage("/printed")
47                 msg.append(msg_string)
48                 return msg
49
50         # define a message-handler function for the server to call.
51         def info_handler(addr, tags, stuff, source):
52                 print "SERVER: Info ", addr
53                 
54         def default_handler(addr, tags, stuff, source):
55                 print "SERVER: No handler registered for ", addr
56                 return None
57
58         class DemoOSCStreamRequestHandler(OSCStreamRequestHandler):
59                 """ A basic OSC connection/stream handler. For each connection the
60                 server instantiates a new object of this type. A reference to the
61                 server is available under self.server but it must be payed attention
62                 to multi threading design guide lines to avoid corrupted data and
63                 race conditions (the shared variable in this example
64                 """
65                 def setupAddressSpace(self):
66                         self.addMsgHandler("/exit", self.exit_handler)
67                         self.addMsgHandler("/print", printing_handler)
68                         self.addMsgHandler("/info", info_handler)
69                         self.addMsgHandler("default", default_handler)
70                         print "SERVER: Address space:"
71                         for addr in self.getOSCAddressSpace():
72                                 print addr
73                         
74                 def exit_handler(self, addr, tags, stuff, source):
75                         print "SERVER: EXIT ", addr
76                         self.server.run = False
77                         return None
78
79         class DemoServer(OSCStreamingServerThreading):
80                 RequestHandlerClass = DemoOSCStreamRequestHandler
81                 def __init__(self, listen_address):
82                         OSCStreamingServerThreading.__init__(self, listen_address)
83                         self.run = True
84                         
85         s = DemoServer(listen_address)
86         print s
87
88         print "Starting ", s
89         s.start()
90         
91         # Instantiate OSCClient
92         print "Instantiating OSCStreamingClient:"
93         def printed_handler(addr, tags, stuff, source):
94                 print "CLIENT: Printed Handler: ", addr
95         def broadcast_handler(addr, tags, stuff, source):
96                 print "CLIENT: Broadcast Handler: ", addr
97                 
98         c = OSCStreamingClient()
99         c.connect(listen_address)
100         c.addMsgHandler("/printed", printed_handler)
101         c.addMsgHandler("/broadcast", broadcast_handler)
102         
103         print "\nSending Messages"
104         print2 = print1.copy()
105         print2.setAddress('/noprint')
106         for m in (message, print1, print2, strings, bundle):
107                 print "sending: ", m
108                 c.sendOSC(m)
109                 time.sleep(0.1)
110                 
111         print "\nThe next message's address will match both the '/print' and '/printed' handlers..."
112         print "sending: ", blob
113         c.sendOSC(blob)
114         time.sleep(0.1)
115
116         print "\nBundles can be given a timestamp.\nThe receiving server should 'hold' the bundle until its time has come"
117         
118         waitbundle = OSCBundle("/print")
119         waitbundle.setTimeTag(time.time() + 5)
120         waitbundle.append("Note how the (single-thread) server blocks while holding this bundle")
121         
122         print "Set timetag 5 s into the future"
123         print "sending: ", waitbundle
124         c.sendOSC(waitbundle)
125         time.sleep(0.1)
126
127         print "Recursing bundles, with timetags set to 10 s [25 s, 20 s, 10 s]"
128         bb = OSCBundle("/print")
129         bb.setTimeTag(time.time() + 1)
130         
131         b = OSCBundle("/print")
132         b.setTimeTag(time.time() + 3)
133         b.append("held for 3 sec")
134         bb.append(b)
135         
136         b.clearData()
137         b.setTimeTag(time.time() + 5)
138         b.append("held for 5 sec")
139         bb.append(b)
140         
141         b.clearData()
142         b.setTimeTag(time.time() + 4)
143         b.append("held for 4 sec")
144         bb.append(b)
145         
146         print "sending: ", bb
147         c.sendOSC(bb)
148         time.sleep(0.1)
149         
150         try:
151                 # we let the server run autonomously for some seconds
152                 count = 6
153                 while s.run :
154                         time.sleep(1)
155                         # test broadcasting to all connected clients
156                         msg = OSCMessage("/broadcast")
157                         msg.append(count)
158                         s.broadcastToClients(msg)
159                         
160                         msg = OSCMessage("/print")
161                         msg.append(count)
162                         c.sendOSC(msg)
163                         count -= 1
164                         if count == 0:
165                                 # send termination message (test context message handlers)
166                                 msg = OSCMessage("/exit")
167                                 c.sendOSC(msg)
168                         
169         except KeyboardInterrupt:
170                 print "Interrupted."
171                         
172         print "Closing client"
173         c.close()
174         
175         print "Closing server"
176         # make sure server receiving thread is scheduled before we close the server
177         # so that it can recognize, that the client disconnected itself 
178         time.sleep(1)
179         s.stop()
180         
181         print "Done. Arrivederci!"
182         sys.exit()
183                         
184
185 ###############################################################################
186 ## MAIN TESTBENCH
187 ###############################################################################
188
189 if __name__ == "__main__":
190         
191         default_port = 2222
192                 
193         # define command-line options
194         op = optparse.OptionParser(description="OSC.py OpenSoundControl-for-Python Test Program")
195         op.add_option("-l", "--listen", dest="listen",
196                         help="listen on given host[:port]. default = '0.0.0.0:%d'" % default_port)
197         op.add_option("-s", "--sendto", dest="sendto",
198                         help="send to given host[:port]. default = '127.0.0.1:%d'" % default_port)
199         op.add_option("-t", "--threading", action="store_true", dest="threading",
200                         help="Test ThreadingOSCServer")
201         op.add_option("-f", "--forking", action="store_true", dest="forking",
202                         help="Test ForkingOSCServer")
203         op.add_option("-u", "--usage", action="help", help="show this help message and exit")
204         
205         op.add_option("-c", "--streaming", action="store_true", dest="streaming",
206                         help="Test streaming OSC (OSC over TCP)")
207         
208         op.set_defaults(listen=":%d" % default_port)
209         op.set_defaults(sendto="")
210         op.set_defaults(threading=False)
211         op.set_defaults(forking=False)
212         op.set_defaults(streaming=False)
213
214         # Parse args
215         (opts, args) = op.parse_args()
216         
217         addr, server_prefix = parseUrlStr(opts.listen)
218         if addr != None and addr[0] != None:
219                 if addr[1] != None:
220                         listen_address = addr
221                 else:
222                         listen_address = (addr[0], default_port)
223         else:
224                 listen_address = ('', default_port)
225                         
226         targets = {}
227         for trg in opts.sendto.split(','):
228                 (addr, prefix) = parseUrlStr(trg)
229                 if len(prefix): 
230                         (prefix, filters) = parseFilterStr(prefix)
231                 else:
232                         filters = {}
233                 
234                 if addr != None:
235                         if addr[1] != None:
236                                 targets[addr] = [prefix, filters]
237                         else:
238                                 targets[(addr[0], listen_address[1])] = [prefix, filters]
239                 elif len(prefix) or len(filters):
240                         targets[listen_address] = [prefix, filters]
241         
242         # If the user selected the streaming test...
243         if opts.streaming:
244                 testStreamingServerAndClient(listen_address)
245                 sys.exit(0)
246         
247         welcome = "Welcome to the OSC testing program."
248         print welcome
249         hexDump(welcome)
250         print
251         message = OSCMessage()
252         message.setAddress("/print")
253         message.append(44)
254         message.append(11)
255         message.append(4.5)
256         message.append("the white cliffs of dover")
257         
258         print message
259         hexDump(message.getBinary())
260
261         print "\nMaking and unmaking a message.."
262
263         strings = OSCMessage("/prin{ce,t}")
264         strings.append("Mary had a little lamb")
265         strings.append("its fleece was white as snow")
266         strings.append("and everywhere that Mary went,")
267         strings.append("the lamb was sure to go.")
268         strings.append(14.5)
269         strings.append(14.5)
270         strings.append(-400)
271
272         raw  = strings.getBinary()
273
274         print strings
275         hexDump(raw)
276
277         print "Retrieving arguments..."
278         data = raw
279         for i in range(6):
280                 text, data = _readString(data)
281                 print text
282
283         number, data = _readFloat(data)
284         print number
285
286         number, data = _readFloat(data)
287         print number
288
289         number, data = _readInt(data)
290         print number
291
292         print decodeOSC(raw)
293
294         print "\nTesting Blob types."
295
296         blob = OSCMessage("/pri*")
297         blob.append("","b")
298         blob.append("b","b")
299         blob.append("bl","b")
300         blob.append("blo","b")
301         blob.append("blob","b")
302         blob.append("blobs","b")
303         blob.append(42)
304
305         print blob
306         hexDump(blob.getBinary())
307
308         print1 = OSCMessage()
309         print1.setAddress("/print")
310         print1.append("Hey man, that's cool.")
311         print1.append(42)
312         print1.append(3.1415926)
313
314         print "\nTesting OSCBundle"
315
316         bundle = OSCBundle()
317         bundle.append(print1)
318         bundle.append({'addr':"/print", 'args':["bundled messages:", 2]})
319         bundle.setAddress("/*print")
320         bundle.append(("no,", 3, "actually."))
321
322         print bundle
323         hexDump(bundle.getBinary())
324         
325         # Instantiate OSCClient
326         print "\nInstantiating OSCClient:"
327         if len(targets):
328                 c = OSCMultiClient()
329                 c.updateOSCTargets(targets)
330         else:
331                 c = OSCClient()
332                 c.connect(listen_address)       # connect back to our OSCServer
333         
334         print c
335         if hasattr(c, 'getOSCTargetStrings'):
336                 print "Sending to:"
337                 for (trg, filterstrings) in c.getOSCTargetStrings():
338                         out = trg
339                         for fs in filterstrings:
340                                 out += " %s" % fs
341                                 
342                         print out
343
344         # Now an OSCServer...
345         print "\nInstantiating OSCServer:"
346         
347         # define a message-handler function for the server to call.
348         def printing_handler(addr, tags, stuff, source):
349                 msg_string = "%s [%s] %s" % (addr, tags, str(stuff))
350                 sys.stdout.write("OSCServer Got: '%s' from %s\n" % (msg_string, getUrlStr(source)))
351                 
352                 # send a reply to the client.
353                 msg = OSCMessage("/printed")
354                 msg.append(msg_string)
355                 return msg
356
357         if opts.threading:
358                 s = ThreadingOSCServer(listen_address, c, return_port=listen_address[1])
359         elif opts.forking:
360                 s = ForkingOSCServer(listen_address, c, return_port=listen_address[1])
361         else:
362                 s = OSCServer(listen_address, c, return_port=listen_address[1])
363         
364         print s
365         
366         # Set Server to return errors as OSCMessages to "/error"
367         s.setSrvErrorPrefix("/error")
368         # Set Server to reply to server-info requests with OSCMessages to "/serverinfo"
369         s.setSrvInfoPrefix("/serverinfo")
370         
371         # this registers a 'default' handler (for unmatched messages), 
372         # an /'error' handler, an '/info' handler.
373         # And, if the client supports it, a '/subscribe' & '/unsubscribe' handler
374         s.addDefaultHandlers()
375
376         s.addMsgHandler("/print", printing_handler)
377         
378         # if client & server are bound to 'localhost', server replies return to itself!
379         s.addMsgHandler("/printed", s.msgPrinter_handler)
380         s.addMsgHandler("/serverinfo", s.msgPrinter_handler)
381         
382         print "Registered Callback-functions:"
383         for addr in s.getOSCAddressSpace():
384                 print addr
385                 
386         print "\nStarting OSCServer. Use ctrl-C to quit."
387         st = threading.Thread(target=s.serve_forever)
388         st.start()
389         
390         if hasattr(c, 'targets') and listen_address not in c.targets.keys():
391                 print "\nSubscribing local Server to local Client"
392                 c2 = OSCClient()
393                 c2.connect(listen_address)
394                 subreq = OSCMessage("/subscribe")
395                 subreq.append(listen_address)
396
397                 print "sending: ", subreq
398                 c2.send(subreq)
399                 c2.close()
400
401                 time.sleep(0.1)
402         
403         print "\nRequesting OSC-address-space and subscribed clients from OSCServer"
404         inforeq = OSCMessage("/info")
405         for cmd in ("info", "list", "clients"):
406                 inforeq.clearData()
407                 inforeq.append(cmd)
408         
409                 print "sending: ", inforeq
410                 c.send(inforeq)
411                 
412                 time.sleep(0.1)
413         
414         print2 = print1.copy()
415         print2.setAddress('/noprint')
416         
417         print "\nSending Messages"
418         
419         for m in (message, print1, print2, strings, bundle):
420                 print "sending: ", m
421                 c.send(m)
422
423                 time.sleep(0.1)
424                 
425         print "\nThe next message's address will match both the '/print' and '/printed' handlers..."
426         print "sending: ", blob
427         c.send(blob)
428         
429         time.sleep(0.1)
430
431         print "\nBundles can be given a timestamp.\nThe receiving server should 'hold' the bundle until its time has come"
432         
433         waitbundle = OSCBundle("/print")
434         waitbundle.setTimeTag(time.time() + 5)
435         if s.__class__ == OSCServer:
436                 waitbundle.append("Note how the (single-thread) OSCServer blocks while holding this bundle")
437         else:
438                 waitbundle.append("Note how the %s does not block while holding this bundle" % s.__class__.__name__)
439         
440         print "Set timetag 5 s into the future"
441         print "sending: ", waitbundle
442         c.send(waitbundle)
443         
444         time.sleep(0.1)
445
446         print "Recursing bundles, with timetags set to 10 s [25 s, 20 s, 10 s]"
447         bb = OSCBundle("/print")
448         bb.setTimeTag(time.time() + 10)
449         
450         b = OSCBundle("/print")
451         b.setTimeTag(time.time() + 25)
452         b.append("held for 25 sec")
453         bb.append(b)
454         
455         b.clearData()
456         b.setTimeTag(time.time() + 20)
457         b.append("held for 20 sec")
458         bb.append(b)
459         
460         b.clearData()
461         b.setTimeTag(time.time() + 15)
462         b.append("held for 15 sec")
463         bb.append(b)
464         
465         if s.__class__ == OSCServer:
466                 bb.append("Note how the (single-thread) OSCServer handles the bundle's contents in order of appearance")
467         else:
468                 bb.append("Note how the %s handles the sub-bundles in the order dictated by their timestamps" % s.__class__.__name__)
469                 bb.append("Each bundle's contents, however, are processed in random order (dictated by the kernel's threading)")
470         
471         print "sending: ", bb
472         c.send(bb)
473         
474         time.sleep(0.1)
475
476         print "\nMessages sent!"
477         
478         print "\nWaiting for OSCServer. Use ctrl-C to quit.\n"
479         
480         try:
481                 while True:
482                         time.sleep(30)
483         
484         except KeyboardInterrupt:
485                 print "\nClosing OSCServer."
486                 s.close()
487                 print "Waiting for Server-thread to finish"
488                 st.join()
489                 print "Closing OSCClient"
490                 c.close()
491                 print "Done"
492                 
493         sys.exit(0)
494