comparison tim_grid_mapper/OSC.py @ 3:7bd1f07044ab

Added grid_mapper to map from Kinect data to Joe's synth and Ableton. Added OSC.py which is a library file. You need it to run grid_mapper.py but please don't change it.
author Tim MB <tim.murraybrowne@eecs.qmul.ac.uk>
date Tue, 15 Feb 2011 17:31:11 +0000
parents
children
comparison
equal deleted inserted replaced
2:b111b478075d 3:7bd1f07044ab
1 #!/usr/bin/python
2 """
3 This module contains an OpenSoundControl implementation (in Pure Python), based (somewhat) on the
4 good old 'SimpleOSC' implementation by Daniel Holth & Clinton McChesney.
5
6 This implementation is intended to still be 'Simple' to the user, but much more complete
7 (with OSCServer & OSCClient classes) and much more powerful
8 (the OSCMultiClient supports subscriptions & message-filtering,
9 OSCMessage & OSCBundle are now proper container-types)
10
11 ================
12 OpenSoundControl
13 ================
14
15 OpenSoundControl is a network-protocol for sending (small) packets of addressed data over network sockets.
16 This OSC-implementation uses the UDP/IP protocol for sending and receiving packets.
17 (Although it is theoretically possible to send OSC-packets over TCP, almost all known implementations use UDP)
18
19 OSC-packets come in two kinds:
20 - OSC-messages consist of an 'address'-string (not to be confused with a (host:port) network-address!),
21 followed by a string of 'typetags' associated with the message's arguments (ie. 'payload'),
22 and finally the arguments themselves, encoded in an OSC-specific way.
23 The OSCMessage class makes it easy to create & manipulate OSC-messages of this kind in a 'pythonesque' way
24 (that is, OSCMessage-objects behave a lot like lists)
25
26 - OSC-bundles are a special type of OSC-message containing only OSC-messages as 'payload'. Recursively.
27 (meaning; an OSC-bundle could contain other OSC-bundles, containing OSC-bundles etc.)
28 OSC-bundles start with the special keyword '#bundle' and do not have an OSC-address. (but the OSC-messages
29 a bundle contains will have OSC-addresses!)
30 Also, an OSC-bundle can have a timetag, essentially telling the receiving Server to 'hold' the bundle until
31 the specified time.
32 The OSCBundle class allows easy cration & manipulation of OSC-bundles.
33
34 see also http://opensoundcontrol.org/spec-1_0
35
36 ---------
37
38 To send OSC-messages, you need an OSCClient, and to receive OSC-messages you need an OSCServer.
39
40 The OSCClient uses an 'AF_INET / SOCK_DGRAM' type socket (see the 'socket' module) to send
41 binary representations of OSC-messages to a remote host:port address.
42
43 The OSCServer listens on an 'AF_INET / SOCK_DGRAM' type socket bound to a local port, and handles
44 incoming requests. Either one-after-the-other (OSCServer) or in a multi-threaded / multi-process fashion
45 (ThreadingOSCServer / ForkingOSCServer). If the Server has a callback-function (a.k.a. handler) registered
46 to 'deal with' (i.e. handle) the received message's OSC-address, that function is called, passing it the (decoded) message
47
48 The different OSCServers implemented here all support the (recursive) un-bundling of OSC-bundles,
49 and OSC-bundle timetags.
50
51 In fact, this implementation supports:
52
53 - OSC-messages with 'i' (int32), 'f' (float32), 's' (string) and 'b' (blob / binary data) types
54 - OSC-bundles, including timetag-support
55 - OSC-address patterns including '*', '?', '{,}' and '[]' wildcards.
56
57 (please *do* read the OSC-spec! http://opensoundcontrol.org/spec-1_0 it explains what these things mean.)
58
59 In addition, the OSCMultiClient supports:
60 - Sending a specific OSC-message to multiple remote servers
61 - Remote server subscription / unsubscription (through OSC-messages, of course)
62 - Message-address filtering.
63
64 ---------
65
66 Stock, V2_Lab, Rotterdam, 2008
67
68 ----------
69 Changelog:
70 ----------
71 v0.3.0 - 27 Dec. 2007
72 Started out to extend the 'SimpleOSC' implementation (v0.2.3) by Daniel Holth & Clinton McChesney.
73 Rewrote OSCMessage
74 Added OSCBundle
75
76 v0.3.1 - 3 Jan. 2008
77 Added OSClient
78 Added OSCRequestHandler, loosely based on the original CallbackManager
79 Added OSCServer
80 Removed original CallbackManager
81 Adapted testing-script (the 'if __name__ == "__main__":' block at the end) to use new Server & Client
82
83 v0.3.2 - 5 Jan. 2008
84 Added 'container-type emulation' methods (getitem(), setitem(), __iter__() & friends) to OSCMessage
85 Added ThreadingOSCServer & ForkingOSCServer
86 - 6 Jan. 2008
87 Added OSCMultiClient
88 Added command-line options to testing-script (try 'python OSC.py --help')
89
90 v0.3.3 - 9 Jan. 2008
91 Added OSC-timetag support to OSCBundle & OSCRequestHandler
92 Added ThreadingOSCRequestHandler
93
94 v0.3.4 - 13 Jan. 2008
95 Added message-filtering to OSCMultiClient
96 Added subscription-handler to OSCServer
97 Added support fon numpy/scipy int & float types. (these get converted to 'standard' 32-bit OSC ints / floats!)
98 Cleaned-up and added more Docstrings
99
100 v0.3.5 - 14 aug. 2008
101 Added OSCServer.reportErr(...) method
102
103 -----------------
104 Original Comments
105 -----------------
106
107 > Open SoundControl for Python
108 > Copyright (C) 2002 Daniel Holth, Clinton McChesney
109 >
110 > This library is free software; you can redistribute it and/or modify it under
111 > the terms of the GNU Lesser General Public License as published by the Free
112 > Software Foundation; either version 2.1 of the License, or (at your option) any
113 > later version.
114 >
115 > This library is distributed in the hope that it will be useful, but WITHOUT ANY
116 > WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
117 > PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
118 > details.
119
120 > You should have received a copy of the GNU Lesser General Public License along
121 > with this library; if not, write to the Free Software Foundation, Inc., 59
122 > Temple Place, Suite 330, Boston, MA 02111-1307 USA
123
124 > For questions regarding this module contact Daniel Holth <dholth@stetson.edu>
125 > or visit http://www.stetson.edu/~ProctoLogic/
126
127 > Changelog:
128 > 15 Nov. 2001:
129 > Removed dependency on Python 2.0 features.
130 > - dwh
131 > 13 Feb. 2002:
132 > Added a generic callback handler.
133 > - dwh
134 """
135
136 import math, re, socket, select, string, struct, sys, threading, time, types
137 from SocketServer import UDPServer, DatagramRequestHandler, ForkingMixIn, ThreadingMixIn
138
139 global version
140 version = ("0.3","5b", "$Rev: 5294 $"[6:-2])
141
142 global FloatTypes
143 FloatTypes = [types.FloatType]
144
145 global IntTypes
146 IntTypes = [types.IntType]
147
148 ##
149 # numpy/scipy support:
150 ##
151
152 try:
153 from numpy import typeDict
154
155 for ftype in ['float32', 'float64', 'float128']:
156 try:
157 FloatTypes.append(typeDict[ftype])
158 except KeyError:
159 pass
160
161 for itype in ['int8', 'int16', 'int32', 'int64']:
162 try:
163 IntTypes.append(typeDict[itype])
164 IntTypes.append(typeDict['u' + itype])
165 except KeyError:
166 pass
167
168 # thanks for those...
169 del typeDict, ftype, itype
170
171 except ImportError:
172 pass
173
174 ######
175 #
176 # OSCMessage classes
177 #
178 ######
179
180 class OSCMessage(object):
181 """ Builds typetagged OSC messages.
182
183 OSCMessage objects are container objects for building OSC-messages.
184 On the 'front' end, they behave much like list-objects, and on the 'back' end
185 they generate a binary representation of the message, which can be sent over a network socket.
186 OSC-messages consist of an 'address'-string (not to be confused with a (host, port) IP-address!),
187 followed by a string of 'typetags' associated with the message's arguments (ie. 'payload'),
188 and finally the arguments themselves, encoded in an OSC-specific way.
189
190 On the Python end, OSCMessage are lists of arguments, prepended by the message's address.
191 The message contents can be manipulated much like a list:
192 >>> msg = OSCMessage("/my/osc/address")
193 >>> msg.append('something')
194 >>> msg.insert(0, 'something else')
195 >>> msg[1] = 'entirely'
196 >>> msg.extend([1,2,3.])
197 >>> msg += [4, 5, 6.]
198 >>> del msg[3:6]
199 >>> msg.pop(-2)
200 5
201 >>> print msg
202 /my/osc/address ['something else', 'entirely', 1, 6.0]
203
204 OSCMessages can be concatenated with the + operator. In this case, the resulting OSCMessage
205 inherits its address from the left-hand operand. The right-hand operand's address is ignored.
206 To construct an 'OSC-bundle' from multiple OSCMessage, see OSCBundle!
207
208 Additional methods exist for retreiving typetags or manipulating items as (typetag, value) tuples.
209 """
210 def __init__(self, address=""):
211 """Instantiate a new OSCMessage.
212 The OSC-address can be specified with the 'address' argument
213 """
214 self.clear(address)
215
216 def setAddress(self, address):
217 """Set or change the OSC-address
218 """
219 self.address = address
220
221 def clear(self, address=""):
222 """Clear (or set a new) OSC-address and clear any arguments appended so far
223 """
224 self.address = address
225 self.clearData()
226
227 def clearData(self):
228 """Clear any arguments appended so far
229 """
230 self.typetags = ","
231 self.message = ""
232
233 def append(self, argument, typehint=None):
234 """Appends data to the message, updating the typetags based on
235 the argument's type. If the argument is a blob (counted
236 string) pass in 'b' as typehint.
237 'argument' may also be a list or tuple, in which case its elements
238 will get appended one-by-one, all using the provided typehint
239 """
240 if type(argument) == types.DictType:
241 argument = argument.items()
242 elif isinstance(argument, OSCMessage):
243 raise TypeError("Can only append 'OSCMessage' to 'OSCBundle'")
244
245 if hasattr(argument, '__iter__'):
246 for arg in argument:
247 self.append(arg, typehint)
248
249 return
250
251 if typehint == 'b':
252 binary = OSCBlob(argument)
253 tag = 'b'
254 elif typehint == 't':
255 binary = OSCTimeTag(argument)
256 tag = 't'
257 else:
258 tag, binary = OSCArgument(argument, typehint)
259
260 self.typetags += tag
261 self.message += binary
262
263 def getBinary(self):
264 """Returns the binary representation of the message
265 """
266 binary = OSCString(self.address)
267 binary += OSCString(self.typetags)
268 binary += self.message
269
270 return binary
271
272 def __repr__(self):
273 """Returns a string containing the decode Message
274 """
275 return str(decodeOSC(self.getBinary()))
276
277 def __str__(self):
278 """Returns the Message's address and contents as a string.
279 """
280 return "%s %s" % (self.address, str(self.values()))
281
282 def __len__(self):
283 """Returns the number of arguments appended so far
284 """
285 return (len(self.typetags) - 1)
286
287 def __eq__(self, other):
288 """Return True if two OSCMessages have the same address & content
289 """
290 if not isinstance(other, self.__class__):
291 return False
292
293 return (self.address == other.address) and (self.typetags == other.typetags) and (self.message == other.message)
294
295 def __ne__(self, other):
296 """Return (not self.__eq__(other))
297 """
298 return not self.__eq__(other)
299
300 def __add__(self, values):
301 """Returns a copy of self, with the contents of 'values' appended
302 (see the 'extend()' method, below)
303 """
304 msg = self.copy()
305 msg.extend(values)
306 return msg
307
308 def __iadd__(self, values):
309 """Appends the contents of 'values'
310 (equivalent to 'extend()', below)
311 Returns self
312 """
313 self.extend(values)
314 return self
315
316 def __radd__(self, values):
317 """Appends the contents of this OSCMessage to 'values'
318 Returns the extended 'values' (list or tuple)
319 """
320 out = list(values)
321 out.extend(self.values())
322
323 if type(values) == types.TupleType:
324 return tuple(out)
325
326 return out
327
328 def _reencode(self, items):
329 """Erase & rebuild the OSCMessage contents from the given
330 list of (typehint, value) tuples"""
331 self.clearData()
332 for item in items:
333 self.append(item[1], item[0])
334
335 def values(self):
336 """Returns a list of the arguments appended so far
337 """
338 return decodeOSC(self.getBinary())[2:]
339
340 def tags(self):
341 """Returns a list of typetags of the appended arguments
342 """
343 return list(self.typetags.lstrip(','))
344
345 def items(self):
346 """Returns a list of (typetag, value) tuples for
347 the arguments appended so far
348 """
349 out = []
350 values = self.values()
351 typetags = self.tags()
352 for i in range(len(values)):
353 out.append((typetags[i], values[i]))
354
355 return out
356
357 def __contains__(self, val):
358 """Test if the given value appears in the OSCMessage's arguments
359 """
360 return (val in self.values())
361
362 def __getitem__(self, i):
363 """Returns the indicated argument (or slice)
364 """
365 return self.values()[i]
366
367 def __delitem__(self, i):
368 """Removes the indicated argument (or slice)
369 """
370 items = self.items()
371 del items[i]
372
373 self._reencode(items)
374
375 def _buildItemList(self, values, typehint=None):
376 if isinstance(values, OSCMessage):
377 items = values.items()
378 elif type(values) == types.ListType:
379 items = []
380 for val in values:
381 if type(val) == types.TupleType:
382 items.append(val[:2])
383 else:
384 items.append((typehint, val))
385 elif type(values) == types.TupleType:
386 items = [values[:2]]
387 else:
388 items = [(typehint, values)]
389
390 return items
391
392 def __setitem__(self, i, val):
393 """Set indicatated argument (or slice) to a new value.
394 'val' can be a single int/float/string, or a (typehint, value) tuple.
395 Or, if 'i' is a slice, a list of these or another OSCMessage.
396 """
397 items = self.items()
398
399 new_items = self._buildItemList(val)
400
401 if type(i) != types.SliceType:
402 if len(new_items) != 1:
403 raise TypeError("single-item assignment expects a single value or a (typetag, value) tuple")
404
405 new_items = new_items[0]
406
407 # finally...
408 items[i] = new_items
409
410 self._reencode(items)
411
412 def setItem(self, i, val, typehint=None):
413 """Set indicated argument to a new value (with typehint)
414 """
415 items = self.items()
416
417 items[i] = (typehint, val)
418
419 self._reencode(items)
420
421 def copy(self):
422 """Returns a deep copy of this OSCMessage
423 """
424 msg = self.__class__(self.address)
425 msg.typetags = self.typetags
426 msg.message = self.message
427 return msg
428
429 def count(self, val):
430 """Returns the number of times the given value occurs in the OSCMessage's arguments
431 """
432 return self.values().count(val)
433
434 def index(self, val):
435 """Returns the index of the first occurence of the given value in the OSCMessage's arguments.
436 Raises ValueError if val isn't found
437 """
438 return self.values().index(val)
439
440 def extend(self, values):
441 """Append the contents of 'values' to this OSCMessage.
442 'values' can be another OSCMessage, or a list/tuple of ints/floats/strings
443 """
444 items = self.items() + self._buildItemList(values)
445
446 self._reencode(items)
447
448 def insert(self, i, val, typehint = None):
449 """Insert given value (with optional typehint) into the OSCMessage
450 at the given index.
451 """
452 items = self.items()
453
454 for item in reversed(self._buildItemList(val)):
455 items.insert(i, item)
456
457 self._reencode(items)
458
459 def popitem(self, i):
460 """Delete the indicated argument from the OSCMessage, and return it
461 as a (typetag, value) tuple.
462 """
463 items = self.items()
464
465 item = items.pop(i)
466
467 self._reencode(items)
468
469 return item
470
471 def pop(self, i):
472 """Delete the indicated argument from the OSCMessage, and return it.
473 """
474 return self.popitem(i)[1]
475
476 def reverse(self):
477 """Reverses the arguments of the OSCMessage (in place)
478 """
479 items = self.items()
480
481 items.reverse()
482
483 self._reencode(items)
484
485 def remove(self, val):
486 """Removes the first argument with the given value from the OSCMessage.
487 Raises ValueError if val isn't found.
488 """
489 items = self.items()
490
491 # this is not very efficient...
492 i = 0
493 for (t, v) in items:
494 if (v == val):
495 break
496 i += 1
497 else:
498 raise ValueError("'%s' not in OSCMessage" % str(m))
499 # but more efficient than first calling self.values().index(val),
500 # then calling self.items(), which would in turn call self.values() again...
501
502 del items[i]
503
504 self._reencode(items)
505
506 def __iter__(self):
507 """Returns an iterator of the OSCMessage's arguments
508 """
509 return iter(self.values())
510
511 def __reversed__(self):
512 """Returns a reverse iterator of the OSCMessage's arguments
513 """
514 return reversed(self.values())
515
516 def itervalues(self):
517 """Returns an iterator of the OSCMessage's arguments
518 """
519 return iter(self.values())
520
521 def iteritems(self):
522 """Returns an iterator of the OSCMessage's arguments as
523 (typetag, value) tuples
524 """
525 return iter(self.items())
526
527 def itertags(self):
528 """Returns an iterator of the OSCMessage's arguments' typetags
529 """
530 return iter(self.tags())
531
532 class OSCBundle(OSCMessage):
533 """Builds a 'bundle' of OSC messages.
534
535 OSCBundle objects are container objects for building OSC-bundles of OSC-messages.
536 An OSC-bundle is a special kind of OSC-message which contains a list of OSC-messages
537 (And yes, OSC-bundles may contain other OSC-bundles...)
538
539 OSCBundle objects behave much the same as OSCMessage objects, with these exceptions:
540 - if an item or items to be appended or inserted are not OSCMessage objects,
541 OSCMessage objectss are created to encapsulate the item(s)
542 - an OSC-bundle does not have an address of its own, only the contained OSC-messages do.
543 The OSCBundle's 'address' is inherited by any OSCMessage the OSCBundle object creates.
544 - OSC-bundles have a timetag to tell the receiver when the bundle should be processed.
545 The default timetag value (0) means 'immediately'
546 """
547 def __init__(self, address="", time=0):
548 """Instantiate a new OSCBundle.
549 The default OSC-address for newly created OSCMessages
550 can be specified with the 'address' argument
551 The bundle's timetag can be set with the 'time' argument
552 """
553 super(OSCBundle, self).__init__(address)
554 self.timetag = time
555
556 def __str__(self):
557 """Returns the Bundle's contents (and timetag, if nonzero) as a string.
558 """
559 if (self.timetag > 0.):
560 out = "#bundle (%s) [" % self.getTimeTagStr()
561 else:
562 out = "#bundle ["
563
564 if self.__len__():
565 for val in self.values():
566 out += "%s, " % str(val)
567 out = out[:-2] # strip trailing space and comma
568
569 return out + "]"
570
571 def setTimeTag(self, time):
572 """Set or change the OSCBundle's TimeTag
573 In 'Python Time', that's floating seconds since the Epoch
574 """
575 if time >= 0:
576 self.timetag = time
577
578 def getTimeTagStr(self):
579 """Return the TimeTag as a human-readable string
580 """
581 fract, secs = math.modf(self.timetag)
582 out = time.ctime(secs)[11:19]
583 out += ("%.3f" % fract)[1:]
584
585 return out
586
587 def append(self, argument, typehint = None):
588 """Appends data to the bundle, creating an OSCMessage to encapsulate
589 the provided argument unless this is already an OSCMessage.
590 Any newly created OSCMessage inherits the OSCBundle's address at the time of creation.
591 If 'argument' is an iterable, its elements will be encapsuated by a single OSCMessage.
592 Finally, 'argument' can be (or contain) a dict, which will be 'converted' to an OSCMessage;
593 - if 'addr' appears in the dict, its value overrides the OSCBundle's address
594 - if 'args' appears in the dict, its value(s) become the OSCMessage's arguments
595 """
596 if isinstance(argument, OSCMessage):
597 binary = OSCBlob(argument.getBinary())
598 else:
599 msg = OSCMessage(self.address)
600 if type(argument) == types.DictType:
601 if 'addr' in argument:
602 msg.setAddress(argument['addr'])
603 if 'args' in argument:
604 msg.append(argument['args'], typehint)
605 else:
606 msg.append(argument, typehint)
607
608 binary = OSCBlob(msg.getBinary())
609
610 self.message += binary
611 self.typetags += 'b'
612
613 def getBinary(self):
614 """Returns the binary representation of the message
615 """
616 binary = OSCString("#bundle")
617 binary += OSCTimeTag(self.timetag)
618 binary += self.message
619
620 return binary
621
622 def _reencapsulate(self, decoded):
623 if decoded[0] == "#bundle":
624 msg = OSCBundle()
625 msg.setTimeTag(decoded[1])
626 for submsg in decoded[2:]:
627 msg.append(self._reencapsulate(submsg))
628
629 else:
630 msg = OSCMessage(decoded[0])
631 tags = decoded[1].lstrip(',')
632 for i in range(len(tags)):
633 msg.append(decoded[2+i], tags[i])
634
635 return msg
636
637 def values(self):
638 """Returns a list of the OSCMessages appended so far
639 """
640 out = []
641 for decoded in decodeOSC(self.getBinary())[2:]:
642 out.append(self._reencapsulate(decoded))
643
644 return out
645
646 def __eq__(self, other):
647 """Return True if two OSCBundles have the same timetag & content
648 """
649 if not isinstance(other, self.__class__):
650 return False
651
652 return (self.timetag == other.timetag) and (self.typetags == other.typetags) and (self.message == other.message)
653
654 def copy(self):
655 """Returns a deep copy of this OSCBundle
656 """
657 copy = super(OSCBundle, self).copy()
658 copy.timetag = self.timetag
659 return copy
660
661 ######
662 #
663 # OSCMessage encoding functions
664 #
665 ######
666
667 def OSCString(next):
668 """Convert a string into a zero-padded OSC String.
669 The length of the resulting string is always a multiple of 4 bytes.
670 The string ends with 1 to 4 zero-bytes ('\x00')
671 """
672
673 OSCstringLength = math.ceil((len(next)+1) / 4.0) * 4
674 return struct.pack(">%ds" % (OSCstringLength), str(next))
675
676 def OSCBlob(next):
677 """Convert a string into an OSC Blob.
678 An OSC-Blob is a binary encoded block of data, prepended by a 'size' (int32).
679 The size is always a mutiple of 4 bytes.
680 The blob ends with 0 to 3 zero-bytes ('\x00')
681 """
682
683 if type(next) in types.StringTypes:
684 OSCblobLength = math.ceil((len(next)) / 4.0) * 4
685 binary = struct.pack(">i%ds" % (OSCblobLength), OSCblobLength, next)
686 else:
687 binary = ""
688
689 return binary
690
691 def OSCArgument(next, typehint=None):
692 """ Convert some Python types to their
693 OSC binary representations, returning a
694 (typetag, data) tuple.
695 """
696 if not typehint:
697 if type(next) in FloatTypes:
698 binary = struct.pack(">f", float(next))
699 tag = 'f'
700 elif type(next) in IntTypes:
701 binary = struct.pack(">i", int(next))
702 tag = 'i'
703 else:
704 binary = OSCString(next)
705 tag = 's'
706
707 elif typehint == 'f':
708 try:
709 binary = struct.pack(">f", float(next))
710 tag = 'f'
711 except ValueError:
712 binary = OSCString(next)
713 tag = 's'
714 elif typehint == 'i':
715 try:
716 binary = struct.pack(">i", int(next))
717 tag = 'i'
718 except ValueError:
719 binary = OSCString(next)
720 tag = 's'
721 else:
722 binary = OSCString(next)
723 tag = 's'
724
725 return (tag, binary)
726
727 def OSCTimeTag(time):
728 """Convert a time in floating seconds to its
729 OSC binary representation
730 """
731 if time > 0:
732 fract, secs = math.modf(time)
733 binary = struct.pack('>ll', long(secs), long(fract * 1e9))
734 else:
735 binary = struct.pack('>ll', 0L, 1L)
736
737 return binary
738
739 ######
740 #
741 # OSCMessage decoding functions
742 #
743 ######
744
745 def _readString(data):
746 """Reads the next (null-terminated) block of data
747 """
748 length = string.find(data,"\0")
749 nextData = int(math.ceil((length+1) / 4.0) * 4)
750 return (data[0:length], data[nextData:])
751
752 def _readBlob(data):
753 """Reads the next (numbered) block of data
754 """
755
756 length = struct.unpack(">i", data[0:4])[0]
757 nextData = int(math.ceil((length) / 4.0) * 4) + 4
758 return (data[4:length+4], data[nextData:])
759
760 def _readInt(data):
761 """Tries to interpret the next 4 bytes of the data
762 as a 32-bit integer. """
763
764 if(len(data)<4):
765 print "Error: too few bytes for int", data, len(data)
766 rest = data
767 integer = 0
768 else:
769 integer = struct.unpack(">i", data[0:4])[0]
770 rest = data[4:]
771
772 return (integer, rest)
773
774 def _readLong(data):
775 """Tries to interpret the next 8 bytes of the data
776 as a 64-bit signed integer.
777 """
778
779 high, low = struct.unpack(">ll", data[0:8])
780 big = (long(high) << 32) + low
781 rest = data[8:]
782 return (big, rest)
783
784 def _readTimeTag(data):
785 """Tries to interpret the next 8 bytes of the data
786 as a TimeTag.
787 """
788 high, low = struct.unpack(">ll", data[0:8])
789 if (high == 0) and (low <= 1):
790 time = 0.0
791 else:
792 time = int(high) + float(low / 1e9)
793 rest = data[8:]
794 return (time, rest)
795
796 def _readFloat(data):
797 """Tries to interpret the next 4 bytes of the data
798 as a 32-bit float.
799 """
800
801 if(len(data)<4):
802 print "Error: too few bytes for float", data, len(data)
803 rest = data
804 float = 0
805 else:
806 float = struct.unpack(">f", data[0:4])[0]
807 rest = data[4:]
808
809 return (float, rest)
810
811 def decodeOSC(data):
812 """Converts a binary OSC message to a Python list.
813 """
814 table = {"i":_readInt, "f":_readFloat, "s":_readString, "b":_readBlob}
815 decoded = []
816 address, rest = _readString(data)
817 if address.startswith(","):
818 typetags = address
819 address = ""
820 else:
821 typetags = ""
822
823 if address == "#bundle":
824 time, rest = _readTimeTag(rest)
825 decoded.append(address)
826 decoded.append(time)
827 while len(rest)>0:
828 length, rest = _readInt(rest)
829 decoded.append(decodeOSC(rest[:length]))
830 rest = rest[length:]
831
832 elif len(rest)>0:
833 if not len(typetags):
834 typetags, rest = _readString(rest)
835 decoded.append(address)
836 decoded.append(typetags)
837 if typetags.startswith(","):
838 for tag in typetags[1:]:
839 value, rest = table[tag](rest)
840 decoded.append(value)
841 else:
842 raise OSCError("OSCMessage's typetag-string lacks the magic ','")
843
844 return decoded
845
846 ######
847 #
848 # Utility functions
849 #
850 ######
851
852 def hexDump(bytes):
853 """ Useful utility; prints the string in hexadecimal.
854 """
855 print "byte 0 1 2 3 4 5 6 7 8 9 A B C D E F"
856
857 num = len(bytes)
858 for i in range(num):
859 if (i) % 16 == 0:
860 line = "%02X0 : " % (i/16)
861 line += "%02X " % ord(bytes[i])
862 if (i+1) % 16 == 0:
863 print "%s: %s" % (line, repr(bytes[i-15:i+1]))
864 line = ""
865
866 bytes_left = num % 16
867 if bytes_left:
868 print "%s: %s" % (line.ljust(54), repr(bytes[-bytes_left:]))
869
870 def getUrlStr(*args):
871 """Convert provided arguments to a string in 'host:port/prefix' format
872 Args can be:
873 - (host, port)
874 - (host, port), prefix
875 - host, port
876 - host, port, prefix
877 """
878 if not len(args):
879 return ""
880
881 if type(args[0]) == types.TupleType:
882 host = args[0][0]
883 port = args[0][1]
884 args = args[1:]
885 else:
886 host = args[0]
887 port = args[1]
888 args = args[2:]
889
890 if len(args):
891 prefix = args[0]
892 else:
893 prefix = ""
894
895 if len(host) and (host != '0.0.0.0'):
896 try:
897 (host, _, _) = socket.gethostbyaddr(host)
898 except socket.error:
899 pass
900 else:
901 host = 'localhost'
902
903 if type(port) == types.IntType:
904 return "%s:%d%s" % (host, port, prefix)
905 else:
906 return host + prefix
907
908 def parseUrlStr(url):
909 """Convert provided string in 'host:port/prefix' format to it's components
910 Returns ((host, port), prefix)
911 """
912 if not (type(url) in types.StringTypes and len(url)):
913 return (None, '')
914
915 i = url.find("://")
916 if i > -1:
917 url = url[i+3:]
918
919 i = url.find(':')
920 if i > -1:
921 host = url[:i].strip()
922 tail = url[i+1:].strip()
923 else:
924 host = ''
925 tail = url
926
927 for i in range(len(tail)):
928 if not tail[i].isdigit():
929 break
930 else:
931 i += 1
932
933 portstr = tail[:i].strip()
934 tail = tail[i:].strip()
935
936 found = len(tail)
937 for c in ('/', '+', '-', '*'):
938 i = tail.find(c)
939 if (i > -1) and (i < found):
940 found = i
941
942 head = tail[:found].strip()
943 prefix = tail[found:].strip()
944
945 prefix = prefix.strip('/')
946 if len(prefix) and prefix[0] not in ('+', '-', '*'):
947 prefix = '/' + prefix
948
949 if len(head) and not len(host):
950 host = head
951
952 if len(host):
953 try:
954 host = socket.gethostbyname(host)
955 except socket.error:
956 pass
957
958 try:
959 port = int(portstr)
960 except ValueError:
961 port = None
962
963 return ((host, port), prefix)
964
965 ######
966 #
967 # OSCClient class
968 #
969 ######
970
971 class OSCClient(object):
972 """Simple OSC Client. Handles the sending of OSC-Packets (OSCMessage or OSCBundle) via a UDP-socket
973 """
974 # set outgoing socket buffer size
975 sndbuf_size = 4096 * 8
976
977 def __init__(self, server=None):
978 """Construct an OSC Client.
979 When the 'address' argument is given this client is connected to a specific remote server.
980 - address ((host, port) tuple): the address of the remote server to send all messages to
981 Otherwise it acts as a generic client:
982 If address == 'None', the client doesn't connect to a specific remote server,
983 and the remote address must be supplied when calling sendto()
984 - server: Local OSCServer-instance this client will use the socket of for transmissions.
985 If none is supplied, a socket will be created.
986 """
987 self.socket = None
988
989 if server == None:
990 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
991 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, self.sndbuf_size)
992 self._fd = self.socket.fileno()
993
994 self.server = None
995 else:
996 self.setServer(server)
997
998 self.client_address = None
999
1000 def setServer(self, server):
1001 """Associate this Client with given server.
1002 The Client will send from the Server's socket.
1003 The Server will use this Client instance to send replies.
1004 """
1005 if not isinstance(server, OSCServer):
1006 raise ValueError("'server' argument is not a valid OSCServer object")
1007
1008 if self.socket != None:
1009 self.close()
1010
1011 self.socket = server.socket.dup()
1012 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, self.sndbuf_size)
1013 self._fd = self.socket.fileno()
1014
1015 self.server = server
1016
1017 if self.server.client != None:
1018 self.server.client.close()
1019
1020 self.server.client = self
1021
1022 def close(self):
1023 """Disconnect & close the Client's socket
1024 """
1025 if self.socket != None:
1026 self.socket.close()
1027 self.socket = None
1028
1029 def __str__(self):
1030 """Returns a string containing this Client's Class-name, software-version
1031 and the remote-address it is connected to (if any)
1032 """
1033 out = self.__class__.__name__
1034 out += " v%s.%s-%s" % version
1035 addr = self.address()
1036 if addr:
1037 out += " connected to osc://%s" % getUrlStr(addr)
1038 else:
1039 out += " (unconnected)"
1040
1041 return out
1042
1043 def __eq__(self, other):
1044 """Compare function.
1045 """
1046 if not isinstance(other, self.__class__):
1047 return False
1048
1049 isequal = cmp(self.socket._sock, other.socket._sock)
1050 if isequal and self.server and other.server:
1051 return cmp(self.server, other.server)
1052
1053 return isequal
1054
1055 def __ne__(self, other):
1056 """Compare function.
1057 """
1058 return not self.__eq__(other)
1059
1060 def address(self):
1061 """Returns a (host,port) tuple of the remote server this client is
1062 connected to or None if not connected to any server.
1063 """
1064 try:
1065 return self.socket.getpeername()
1066 except socket.error:
1067 return None
1068
1069 def connect(self, address):
1070 """Bind to a specific OSC server:
1071 the 'address' argument is a (host, port) tuple
1072 - host: hostname of the remote OSC server,
1073 - port: UDP-port the remote OSC server listens to.
1074 """
1075 try:
1076 self.socket.connect(address)
1077 self.client_address = address
1078 except socket.error, e:
1079 self.client_address = None
1080 raise OSCClientError("SocketError: %s" % str(e))
1081
1082 if self.server != None:
1083 self.server.return_port = address[1]
1084
1085 def sendto(self, msg, address, timeout=None):
1086 """Send the given OSCMessage to the specified address.
1087 - msg: OSCMessage (or OSCBundle) to be sent
1088 - address: (host, port) tuple specifing remote server to send the message to
1089 - timeout: A timeout value for attempting to send. If timeout == None,
1090 this call blocks until socket is available for writing.
1091 Raises OSCClientError when timing out while waiting for the socket.
1092 """
1093 if not isinstance(msg, OSCMessage):
1094 raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object")
1095
1096 ret = select.select([],[self._fd], [], timeout)
1097 try:
1098 ret[1].index(self._fd)
1099 except:
1100 # for the very rare case this might happen
1101 raise OSCClientError("Timed out waiting for file descriptor")
1102
1103 try:
1104 self.socket.connect(address)
1105 self.socket.sendall(msg.getBinary())
1106
1107 if self.client_address:
1108 self.socket.connect(self.client_address)
1109
1110 except socket.error, e:
1111 if e[0] in (7, 65): # 7 = 'no address associated with nodename', 65 = 'no route to host'
1112 raise e
1113 #elif e[0]==61:
1114 # pass # ADDED BY TIM: 61 = 'Connection refused.'
1115 else:
1116 raise OSCClientError("while sending to %s: %s" % (str(address), str(e)))
1117
1118 def send(self, msg, timeout=None):
1119 """Send the given OSCMessage.
1120 The Client must be already connected.
1121 - msg: OSCMessage (or OSCBundle) to be sent
1122 - timeout: A timeout value for attempting to send. If timeout == None,
1123 this call blocks until socket is available for writing.
1124 Raises OSCClientError when timing out while waiting for the socket,
1125 or when the Client isn't connected to a remote server.
1126 """
1127 if not isinstance(msg, OSCMessage):
1128 raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object")
1129
1130 ret = select.select([],[self._fd], [], timeout)
1131 try:
1132 ret[1].index(self._fd)
1133 except:
1134 # for the very rare case this might happen
1135 raise OSCClientError("Timed out waiting for file descriptor")
1136
1137 try:
1138 self.socket.sendall(msg.getBinary())
1139 except socket.error, e:
1140 if e[0] in (7, 65): # 7 = 'no address associated with nodename', 65 = 'no route to host'
1141 raise e
1142 else:
1143 raise OSCClientError("while sending: %s" % str(e))
1144
1145 ######
1146 #
1147 # FilterString Utility functions
1148 #
1149 ######
1150
1151 def parseFilterStr(args):
1152 """Convert Message-Filter settings in '+<addr> -<addr> ...' format to a dict of the form
1153 { '<addr>':True, '<addr>':False, ... }
1154 Returns a list: ['<prefix>', filters]
1155 """
1156 out = {}
1157
1158 if type(args) in types.StringTypes:
1159 args = [args]
1160
1161 prefix = None
1162 for arg in args:
1163 head = None
1164 for plus in arg.split('+'):
1165 minus = plus.split('-')
1166 plusfs = minus.pop(0).strip()
1167 if len(plusfs):
1168 plusfs = '/' + plusfs.strip('/')
1169
1170 if (head == None) and (plusfs != "/*"):
1171 head = plusfs
1172 elif len(plusfs):
1173 if plusfs == '/*':
1174 out = { '/*':True } # reset all previous filters
1175 else:
1176 out[plusfs] = True
1177
1178 for minusfs in minus:
1179 minusfs = minusfs.strip()
1180 if len(minusfs):
1181 minusfs = '/' + minusfs.strip('/')
1182 if minusfs == '/*':
1183 out = { '/*':False } # reset all previous filters
1184 else:
1185 out[minusfs] = False
1186
1187 if prefix == None:
1188 prefix = head
1189
1190 return [prefix, out]
1191
1192 def getFilterStr(filters):
1193 """Return the given 'filters' dict as a list of
1194 '+<addr>' | '-<addr>' filter-strings
1195 """
1196 if not len(filters):
1197 return []
1198
1199 if '/*' in filters.keys():
1200 if filters['/*']:
1201 out = ["+/*"]
1202 else:
1203 out = ["-/*"]
1204 else:
1205 if False in filters.values():
1206 out = ["+/*"]
1207 else:
1208 out = ["-/*"]
1209
1210 for (addr, bool) in filters.items():
1211 if addr == '/*':
1212 continue
1213
1214 if bool:
1215 out.append("+%s" % addr)
1216 else:
1217 out.append("-%s" % addr)
1218
1219 return out
1220
1221 # A translation-table for mapping OSC-address expressions to Python 're' expressions
1222 OSCtrans = string.maketrans("{,}?","(|).")
1223
1224 def getRegEx(pattern):
1225 """Compiles and returns a 'regular expression' object for the given address-pattern.
1226 """
1227 # Translate OSC-address syntax to python 're' syntax
1228 pattern = pattern.replace(".", r"\.") # first, escape all '.'s in the pattern.
1229 pattern = pattern.replace("(", r"\(") # escape all '('s.
1230 pattern = pattern.replace(")", r"\)") # escape all ')'s.
1231 pattern = pattern.replace("*", r".*") # replace a '*' by '.*' (match 0 or more characters)
1232 pattern = pattern.translate(OSCtrans) # change '?' to '.' and '{,}' to '(|)'
1233
1234 return re.compile(pattern)
1235
1236 ######
1237 #
1238 # OSCMultiClient class
1239 #
1240 ######
1241
1242 class OSCMultiClient(OSCClient):
1243 """'Multiple-Unicast' OSC Client. Handles the sending of OSC-Packets (OSCMessage or OSCBundle) via a UDP-socket
1244 This client keeps a dict of 'OSCTargets'. and sends each OSCMessage to each OSCTarget
1245 The OSCTargets are simply (host, port) tuples, and may be associated with an OSC-address prefix.
1246 the OSCTarget's prefix gets prepended to each OSCMessage sent to that target.
1247 """
1248 def __init__(self, server=None):
1249 """Construct a "Multi" OSC Client.
1250 - server: Local OSCServer-instance this client will use the socket of for transmissions.
1251 If none is supplied, a socket will be created.
1252 """
1253 super(OSCMultiClient, self).__init__(server)
1254
1255 self.targets = {}
1256
1257 def _searchHostAddr(self, host):
1258 """Search the subscribed OSCTargets for (the first occurence of) given host.
1259 Returns a (host, port) tuple
1260 """
1261 try:
1262 host = socket.gethostbyname(host)
1263 except socket.error:
1264 pass
1265
1266 for addr in self.targets.keys():
1267 if host == addr[0]:
1268 return addr
1269
1270 raise NotSubscribedError((host, None))
1271
1272 def _updateFilters(self, dst, src):
1273 """Update a 'filters' dict with values form another 'filters' dict:
1274 - src[a] == True and dst[a] == False: del dst[a]
1275 - src[a] == False and dst[a] == True: del dst[a]
1276 - a not in dst: dst[a] == src[a]
1277 """
1278 if '/*' in src.keys(): # reset filters
1279 dst.clear() # 'match everything' == no filters
1280 if not src.pop('/*'):
1281 dst['/*'] = False # 'match nothing'
1282
1283 for (addr, bool) in src.items():
1284 if (addr in dst.keys()) and (dst[addr] != bool):
1285 del dst[addr]
1286 else:
1287 dst[addr] = bool
1288
1289 def _setTarget(self, address, prefix=None, filters=None):
1290 """Add (i.e. subscribe) a new OSCTarget, or change the prefix for an existing OSCTarget.
1291 - address ((host, port) tuple): IP-address & UDP-port
1292 - prefix (string): The OSC-address prefix prepended to the address of each OSCMessage
1293 sent to this OSCTarget (optional)
1294 """
1295 if address not in self.targets.keys():
1296 self.targets[address] = ["",{}]
1297
1298 if prefix != None:
1299 if len(prefix):
1300 # make sure prefix starts with ONE '/', and does not end with '/'
1301 prefix = '/' + prefix.strip('/')
1302
1303 self.targets[address][0] = prefix
1304
1305 if filters != None:
1306 if type(filters) in types.StringTypes:
1307 (_, filters) = parseFilterStr(filters)
1308 elif type(filters) != types.DictType:
1309 raise TypeError("'filters' argument must be a dict with {addr:bool} entries")
1310
1311 self._updateFilters(self.targets[address][1], filters)
1312
1313 def setOSCTarget(self, address, prefix=None, filters=None):
1314 """Add (i.e. subscribe) a new OSCTarget, or change the prefix for an existing OSCTarget.
1315 the 'address' argument can be a ((host, port) tuple) : The target server address & UDP-port
1316 or a 'host' (string) : The host will be looked-up
1317 - prefix (string): The OSC-address prefix prepended to the address of each OSCMessage
1318 sent to this OSCTarget (optional)
1319 """
1320 if type(address) in types.StringTypes:
1321 address = self._searchHostAddr(address)
1322
1323 elif (type(address) == types.TupleType):
1324 (host, port) = address[:2]
1325 try:
1326 host = socket.gethostbyname(host)
1327 except:
1328 pass
1329
1330 address = (host, port)
1331 else:
1332 raise TypeError("'address' argument must be a (host, port) tuple or a 'host' string")
1333
1334 self._setTarget(address, prefix, filters)
1335
1336 def setOSCTargetFromStr(self, url):
1337 """Adds or modifies a subscribed OSCTarget from the given string, which should be in the
1338 '<host>:<port>[/<prefix>] [+/<filter>]|[-/<filter>] ...' format.
1339 """
1340 (addr, tail) = parseUrlStr(url)
1341 (prefix, filters) = parseFilterStr(tail)
1342 self._setTarget(addr, prefix, filters)
1343
1344 def _delTarget(self, address, prefix=None):
1345 """Delete the specified OSCTarget from the Client's dict.
1346 the 'address' argument must be a (host, port) tuple.
1347 If the 'prefix' argument is given, the Target is only deleted if the address and prefix match.
1348 """
1349 try:
1350 if prefix == None:
1351 del self.targets[address]
1352 elif prefix == self.targets[address][0]:
1353 del self.targets[address]
1354 except KeyError:
1355 raise NotSubscribedError(address, prefix)
1356
1357 def delOSCTarget(self, address, prefix=None):
1358 """Delete the specified OSCTarget from the Client's dict.
1359 the 'address' argument can be a ((host, port) tuple), or a hostname.
1360 If the 'prefix' argument is given, the Target is only deleted if the address and prefix match.
1361 """
1362 if type(address) in types.StringTypes:
1363 address = self._searchHostAddr(address)
1364
1365 if type(address) == types.TupleType:
1366 (host, port) = address[:2]
1367 try:
1368 host = socket.gethostbyname(host)
1369 except socket.error:
1370 pass
1371 address = (host, port)
1372
1373 self._delTarget(address, prefix)
1374
1375 def hasOSCTarget(self, address, prefix=None):
1376 """Return True if the given OSCTarget exists in the Client's dict.
1377 the 'address' argument can be a ((host, port) tuple), or a hostname.
1378 If the 'prefix' argument is given, the return-value is only True if the address and prefix match.
1379 """
1380 if type(address) in types.StringTypes:
1381 address = self._searchHostAddr(address)
1382
1383 if type(address) == types.TupleType:
1384 (host, port) = address[:2]
1385 try:
1386 host = socket.gethostbyname(host)
1387 except socket.error:
1388 pass
1389 address = (host, port)
1390
1391 if address in self.targets.keys():
1392 if prefix == None:
1393 return True
1394 elif prefix == self.targets[address][0]:
1395 return True
1396
1397 return False
1398
1399 def getOSCTargets(self):
1400 """Returns the dict of OSCTargets: {addr:[prefix, filters], ...}
1401 """
1402 out = {}
1403 for ((host, port), pf) in self.targets.items():
1404 try:
1405 (host, _, _) = socket.gethostbyaddr(host)
1406 except socket.error:
1407 pass
1408
1409 out[(host, port)] = pf
1410
1411 return out
1412
1413 def getOSCTarget(self, address):
1414 """Returns the OSCTarget matching the given address as a ((host, port), [prefix, filters]) tuple.
1415 'address' can be a (host, port) tuple, or a 'host' (string), in which case the first matching OSCTarget is returned
1416 Returns (None, ['',{}]) if address not found.
1417 """
1418 if type(address) in types.StringTypes:
1419 address = self._searchHostAddr(address)
1420
1421 if (type(address) == types.TupleType):
1422 (host, port) = address[:2]
1423 try:
1424 host = socket.gethostbyname(host)
1425 except socket.error:
1426 pass
1427 address = (host, port)
1428
1429 if (address in self.targets.keys()):
1430 try:
1431 (host, _, _) = socket.gethostbyaddr(host)
1432 except socket.error:
1433 pass
1434
1435 return ((host, port), self.targets[address])
1436
1437 return (None, ['',{}])
1438
1439 def clearOSCTargets(self):
1440 """Erases all OSCTargets from the Client's dict
1441 """
1442 self.targets = {}
1443
1444 def updateOSCTargets(self, dict):
1445 """Update the Client's OSCTargets dict with the contents of 'dict'
1446 The given dict's items MUST be of the form
1447 { (host, port):[prefix, filters], ... }
1448 """
1449 for ((host, port), (prefix, filters)) in dict.items():
1450 val = [prefix, {}]
1451 self._updateFilters(val[1], filters)
1452
1453 try:
1454 host = socket.gethostbyname(host)
1455 except socket.error:
1456 pass
1457
1458 self.targets[(host, port)] = val
1459
1460 def getOSCTargetStr(self, address):
1461 """Returns the OSCTarget matching the given address as a ('osc://<host>:<port>[<prefix>]', ['<filter-string>', ...])' tuple.
1462 'address' can be a (host, port) tuple, or a 'host' (string), in which case the first matching OSCTarget is returned
1463 Returns (None, []) if address not found.
1464 """
1465 (addr, (prefix, filters)) = self.getOSCTarget(address)
1466 if addr == None:
1467 return (None, [])
1468
1469 return ("osc://%s" % getUrlStr(addr, prefix), getFilterStr(filters))
1470
1471 def getOSCTargetStrings(self):
1472 """Returns a list of all OSCTargets as ('osc://<host>:<port>[<prefix>]', ['<filter-string>', ...])' tuples.
1473 """
1474 out = []
1475 for (addr, (prefix, filters)) in self.targets.items():
1476 out.append(("osc://%s" % getUrlStr(addr, prefix), getFilterStr(filters)))
1477
1478 return out
1479
1480 def connect(self, address):
1481 """The OSCMultiClient isn't allowed to connect to any specific
1482 address.
1483 """
1484 return NotImplemented
1485
1486 def sendto(self, msg, address, timeout=None):
1487 """Send the given OSCMessage.
1488 The specified address is ignored. Instead this method calls send() to
1489 send the message to all subscribed clients.
1490 - msg: OSCMessage (or OSCBundle) to be sent
1491 - address: (host, port) tuple specifing remote server to send the message to
1492 - timeout: A timeout value for attempting to send. If timeout == None,
1493 this call blocks until socket is available for writing.
1494 Raises OSCClientError when timing out while waiting for the socket.
1495 """
1496 self.send(msg, timeout)
1497
1498 def _filterMessage(self, filters, msg):
1499 """Checks the given OSCMessge against the given filters.
1500 'filters' is a dict containing OSC-address:bool pairs.
1501 If 'msg' is an OSCBundle, recursively filters its constituents.
1502 Returns None if the message is to be filtered, else returns the message.
1503 or
1504 Returns a copy of the OSCBundle with the filtered messages removed.
1505 """
1506 if isinstance(msg, OSCBundle):
1507 out = msg.copy()
1508 msgs = out.values()
1509 out.clearData()
1510 for m in msgs:
1511 m = self._filterMessage(filters, m)
1512 if m: # this catches 'None' and empty bundles.
1513 out.append(m)
1514
1515 elif isinstance(msg, OSCMessage):
1516 if '/*' in filters.keys():
1517 if filters['/*']:
1518 out = msg
1519 else:
1520 out = None
1521
1522 elif False in filters.values():
1523 out = msg
1524 else:
1525 out = None
1526
1527 else:
1528 raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object")
1529
1530 expr = getRegEx(msg.address)
1531
1532 for addr in filters.keys():
1533 if addr == '/*':
1534 continue
1535
1536 match = expr.match(addr)
1537 if match and (match.end() == len(addr)):
1538 if filters[addr]:
1539 out = msg
1540 else:
1541 out = None
1542 break
1543
1544 return out
1545
1546 def _prefixAddress(self, prefix, msg):
1547 """Makes a copy of the given OSCMessage, then prepends the given prefix to
1548 The message's OSC-address.
1549 If 'msg' is an OSCBundle, recursively prepends the prefix to its constituents.
1550 """
1551 out = msg.copy()
1552
1553 if isinstance(msg, OSCBundle):
1554 msgs = out.values()
1555 out.clearData()
1556 for m in msgs:
1557 out.append(self._prefixAddress(prefix, m))
1558
1559 elif isinstance(msg, OSCMessage):
1560 out.setAddress(prefix + out.address)
1561
1562 else:
1563 raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object")
1564
1565 return out
1566
1567 def send(self, msg, timeout=None):
1568 """Send the given OSCMessage to all subscribed OSCTargets
1569 - msg: OSCMessage (or OSCBundle) to be sent
1570 - timeout: A timeout value for attempting to send. If timeout == None,
1571 this call blocks until socket is available for writing.
1572 Raises OSCClientError when timing out while waiting for the socket.
1573 """
1574 for (address, (prefix, filters)) in self.targets.items():
1575 if len(filters):
1576 out = self._filterMessage(filters, msg)
1577 if not out: # this catches 'None' and empty bundles.
1578 continue
1579 else:
1580 out = msg
1581
1582 if len(prefix):
1583 out = self._prefixAddress(prefix, msg)
1584
1585 binary = out.getBinary()
1586
1587 ret = select.select([],[self._fd], [], timeout)
1588 try:
1589 ret[1].index(self._fd)
1590 except:
1591 # for the very rare case this might happen
1592 raise OSCClientError("Timed out waiting for file descriptor")
1593
1594 try:
1595 while len(binary):
1596 sent = self.socket.sendto(binary, address)
1597 binary = binary[sent:]
1598
1599 except socket.error, e:
1600 if e[0] in (7, 65): # 7 = 'no address associated with nodename', 65 = 'no route to host'
1601 raise e
1602 else:
1603 raise OSCClientError("while sending to %s: %s" % (str(address), str(e)))
1604
1605 ######
1606 #
1607 # OSCRequestHandler classes
1608 #
1609 ######
1610
1611 class OSCRequestHandler(DatagramRequestHandler):
1612 """RequestHandler class for the OSCServer
1613 """
1614 def dispatchMessage(self, pattern, tags, data):
1615 """Attmept to match the given OSC-address pattern, which may contain '*',
1616 against all callbacks registered with the OSCServer.
1617 Calls the matching callback and returns whatever it returns.
1618 If no match is found, and a 'default' callback is registered, it calls that one,
1619 or raises NoCallbackError if a 'default' callback is not registered.
1620
1621 - pattern (string): The OSC-address of the receied message
1622 - tags (string): The OSC-typetags of the receied message's arguments, without ','
1623 - data (list): The message arguments
1624 """
1625 if len(tags) != len(data):
1626 raise OSCServerError("Malformed OSC-message; got %d typetags [%s] vs. %d values" % (len(tags), tags, len(data)))
1627
1628 expr = getRegEx(pattern)
1629
1630 replies = []
1631 matched = 0
1632 for addr in self.server.callbacks.keys():
1633 match = expr.match(addr)
1634 if match and (match.end() == len(addr)):
1635 reply = self.server.callbacks[addr](pattern, tags, data, self.client_address)
1636 matched += 1
1637 if isinstance(reply, OSCMessage):
1638 replies.append(reply)
1639 elif reply != None:
1640 raise TypeError("Message-callback %s did not return OSCMessage or None: %s" % (self.server.callbacks[addr], type(reply)))
1641
1642 if matched == 0:
1643 if 'default' in self.server.callbacks:
1644 reply = self.server.callbacks['default'](pattern, tags, data, self.client_address)
1645 if isinstance(reply, OSCMessage):
1646 replies.append(reply)
1647 elif reply != None:
1648 raise TypeError("Message-callback %s did not return OSCMessage or None: %s" % (self.server.callbacks['default'], type(reply)))
1649 else:
1650 raise NoCallbackError(pattern)
1651
1652 return replies
1653
1654 def setup(self):
1655 """Prepare RequestHandler.
1656 Unpacks request as (packet, source socket address)
1657 Creates an empty list for replies.
1658 """
1659 (self.packet, self.socket) = self.request
1660 self.replies = []
1661
1662 def _unbundle(self, decoded):
1663 """Recursive bundle-unpacking function"""
1664 if decoded[0] != "#bundle":
1665 self.replies += self.dispatchMessage(decoded[0], decoded[1][1:], decoded[2:])
1666 return
1667
1668 now = time.time()
1669 timetag = decoded[1]
1670 if (timetag > 0.) and (timetag > now):
1671 time.sleep(timetag - now)
1672
1673 for msg in decoded[2:]:
1674 self._unbundle(msg)
1675
1676 def handle(self):
1677 """Handle incoming OSCMessage
1678 """
1679 decoded = decodeOSC(self.packet)
1680 if not len(decoded):
1681 return
1682
1683 self._unbundle(decoded)
1684
1685 def finish(self):
1686 """Finish handling OSCMessage.
1687 Send any reply returned by the callback(s) back to the originating client
1688 as an OSCMessage or OSCBundle
1689 """
1690 if self.server.return_port:
1691 self.client_address = (self.client_address[0], self.server.return_port)
1692
1693 if len(self.replies) > 1:
1694 msg = OSCBundle()
1695 for reply in self.replies:
1696 msg.append(reply)
1697 elif len(self.replies) == 1:
1698 msg = self.replies[0]
1699 else:
1700 return
1701
1702 self.server.client.sendto(msg, self.client_address)
1703
1704 class ThreadingOSCRequestHandler(OSCRequestHandler):
1705 """Multi-threaded OSCRequestHandler;
1706 Starts a new RequestHandler thread for each unbundled OSCMessage
1707 """
1708 def _unbundle(self, decoded):
1709 """Recursive bundle-unpacking function
1710 This version starts a new thread for each sub-Bundle found in the Bundle,
1711 then waits for all its children to finish.
1712 """
1713 if decoded[0] != "#bundle":
1714 self.replies += self.dispatchMessage(decoded[0], decoded[1][1:], decoded[2:])
1715 return
1716
1717 now = time.time()
1718 timetag = decoded[1]
1719 if (timetag > 0.) and (timetag > now):
1720 time.sleep(timetag - now)
1721 now = time.time()
1722
1723 children = []
1724
1725 for msg in decoded[2:]:
1726 t = threading.Thread(target = self._unbundle, args = (msg,))
1727 t.start()
1728 children.append(t)
1729
1730 # wait for all children to terminate
1731 for t in children:
1732 t.join()
1733
1734 ######
1735 #
1736 # OSCServer classes
1737 #
1738 ######
1739
1740 class OSCServer(UDPServer):
1741 """A Synchronous OSCServer
1742 Serves one request at-a-time, until the OSCServer is closed.
1743 The OSC address-pattern is matched against a set of OSC-adresses
1744 that have been registered to the server with a callback-function.
1745 If the adress-pattern of the message machtes the registered address of a callback,
1746 that function is called.
1747 """
1748
1749 # set the RequestHandlerClass, will be overridden by ForkingOSCServer & ThreadingOSCServer
1750 RequestHandlerClass = OSCRequestHandler
1751
1752 # define a socket timeout, so the serve_forever loop can actually exit.
1753 socket_timeout = 1
1754
1755 # DEBUG: print error-tracebacks (to stderr)?
1756 print_tracebacks = False
1757
1758 def __init__(self, server_address, client=None, return_port=0):
1759 """Instantiate an OSCServer.
1760 - server_address ((host, port) tuple): the local host & UDP-port
1761 the server listens on
1762 - client (OSCClient instance): The OSCClient used to send replies from this server.
1763 If none is supplied (default) an OSCClient will be created.
1764 - return_port (int): if supplied, sets the default UDP destination-port
1765 for replies coming from this server.
1766 """
1767 UDPServer.__init__(self, server_address, self.RequestHandlerClass)
1768
1769 self.callbacks = {}
1770 self.setReturnPort(return_port)
1771 self.error_prefix = ""
1772 self.info_prefix = "/info"
1773
1774 self.socket.settimeout(self.socket_timeout)
1775
1776 self.running = False
1777 self.client = None
1778
1779 if client == None:
1780 self.client = OSCClient(server=self)
1781 else:
1782 self.setClient(client)
1783
1784 def setClient(self, client):
1785 """Associate this Server with a new local Client instance, closing the Client this Server is currently using.
1786 """
1787 if not isinstance(client, OSCClient):
1788 raise ValueError("'client' argument is not a valid OSCClient object")
1789
1790 if client.server != None:
1791 raise OSCServerError("Provided OSCClient already has an OSCServer-instance: %s" % str(client.server))
1792
1793 # Server socket is already listening at this point, so we can't use the client's socket.
1794 # we'll have to force our socket on the client...
1795 client_address = client.address() # client may be already connected
1796 client.close() # shut-down that socket
1797
1798 # force our socket upon the client
1799 client.socket = self.socket.dup()
1800 client.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, client.sndbuf_size)
1801 client._fd = client.socket.fileno()
1802 client.server = self
1803
1804 if client_address:
1805 client.connect(client_address)
1806 if not self.return_port:
1807 self.return_port = client_address[1]
1808
1809 if self.client != None:
1810 self.client.close()
1811
1812 self.client = client
1813
1814 def serve_forever(self):
1815 """Handle one request at a time until server is closed."""
1816 self.running = True
1817 while self.running:
1818 self.handle_request() # this times-out when no data arrives.
1819
1820 def close(self):
1821 """Stops serving requests, closes server (socket), closes used client
1822 """
1823 self.running = False
1824 self.client.close()
1825 self.server_close()
1826
1827 def __str__(self):
1828 """Returns a string containing this Server's Class-name, software-version and local bound address (if any)
1829 """
1830 out = self.__class__.__name__
1831 out += " v%s.%s-%s" % version
1832 addr = self.address()
1833 if addr:
1834 out += " listening on osc://%s" % getUrlStr(addr)
1835 else:
1836 out += " (unbound)"
1837
1838 return out
1839
1840 def __eq__(self, other):
1841 """Compare function.
1842 """
1843 if not isinstance(other, self.__class__):
1844 return False
1845
1846 return cmp(self.socket._sock, other.socket._sock)
1847
1848 def __ne__(self, other):
1849 """Compare function.
1850 """
1851 return not self.__eq__(other)
1852
1853 def address(self):
1854 """Returns a (host,port) tuple of the local address this server is bound to,
1855 or None if not bound to any address.
1856 """
1857 try:
1858 return self.socket.getsockname()
1859 except socket.error:
1860 return None
1861
1862 def setReturnPort(self, port):
1863 """Set the destination UDP-port for replies returning from this server to the remote client
1864 """
1865 if (port > 1024) and (port < 65536):
1866 self.return_port = port
1867 else:
1868 self.return_port = None
1869
1870
1871 def setSrvInfoPrefix(self, pattern):
1872 """Set the first part of OSC-address (pattern) this server will use to reply to server-info requests.
1873 """
1874 if len(pattern):
1875 pattern = '/' + pattern.strip('/')
1876
1877 self.info_prefix = pattern
1878
1879 def setSrvErrorPrefix(self, pattern=""):
1880 """Set the OSC-address (pattern) this server will use to report errors occuring during
1881 received message handling to the remote client.
1882
1883 If pattern is empty (default), server-errors are not reported back to the client.
1884 """
1885 if len(pattern):
1886 pattern = '/' + pattern.strip('/')
1887
1888 self.error_prefix = pattern
1889
1890 def addMsgHandler(self, address, callback):
1891 """Register a handler for an OSC-address
1892 - 'address' is the OSC address-string.
1893 the address-string should start with '/' and may not contain '*'
1894 - 'callback' is the function called for incoming OSCMessages that match 'address'.
1895 The callback-function will be called with the same arguments as the 'msgPrinter_handler' below
1896 """
1897 for chk in '*?,[]{}# ':
1898 if chk in address:
1899 raise OSCServerError("OSC-address string may not contain any characters in '*?,[]{}# '")
1900
1901 if type(callback) not in (types.FunctionType, types.MethodType):
1902 raise OSCServerError("Message callback '%s' is not callable" % repr(callback))
1903
1904 if address != 'default':
1905 address = '/' + address.strip('/')
1906
1907 self.callbacks[address] = callback
1908
1909 def delMsgHandler(self,address):
1910 """Remove the registered handler for the given OSC-address
1911 """
1912 del self.callbacks[address]
1913
1914 def getOSCAddressSpace(self):
1915 """Returns a list containing all OSC-addresses registerd with this Server.
1916 """
1917 return self.callbacks.keys()
1918
1919 def addDefaultHandlers(self, prefix="", info_prefix="/info", error_prefix="/error"):
1920 """Register a default set of OSC-address handlers with this Server:
1921 - 'default' -> noCallback_handler
1922 the given prefix is prepended to all other callbacks registered by this method:
1923 - '<prefix><info_prefix' -> serverInfo_handler
1924 - '<prefix><error_prefix> -> msgPrinter_handler
1925 - '<prefix>/print' -> msgPrinter_handler
1926 and, if the used Client supports it;
1927 - '<prefix>/subscribe' -> subscription_handler
1928 - '<prefix>/unsubscribe' -> subscription_handler
1929
1930 Note: the given 'error_prefix' argument is also set as default 'error_prefix' for error-messages
1931 *sent from* this server. This is ok, because error-messages generally do not elicit a reply from the receiver.
1932
1933 To do this with the serverInfo-prefixes would be a bad idea, because if a request received on '/info' (for example)
1934 would send replies to '/info', this could potentially cause a never-ending loop of messages!
1935 Do *not* set the 'info_prefix' here (for incoming serverinfo requests) to the same value as given to
1936 the setSrvInfoPrefix() method (for *replies* to incoming serverinfo requests).
1937 For example, use '/info' for incoming requests, and '/inforeply' or '/serverinfo' or even just '/print' as the
1938 info-reply prefix.
1939 """
1940 self.error_prefix = error_prefix
1941 self.addMsgHandler('default', self.noCallback_handler)
1942 self.addMsgHandler(prefix + info_prefix, self.serverInfo_handler)
1943 self.addMsgHandler(prefix + error_prefix, self.msgPrinter_handler)
1944 self.addMsgHandler(prefix + '/print', self.msgPrinter_handler)
1945
1946 if isinstance(self.client, OSCMultiClient):
1947 self.addMsgHandler(prefix + '/subscribe', self.subscription_handler)
1948 self.addMsgHandler(prefix + '/unsubscribe', self.subscription_handler)
1949
1950 def printErr(self, txt):
1951 """Writes 'OSCServer: txt' to sys.stderr
1952 """
1953 sys.stderr.write("OSCServer: %s\n" % txt)
1954
1955 def sendOSCerror(self, txt, client_address):
1956 """Sends 'txt', encapsulated in an OSCMessage to the default 'error_prefix' OSC-addres.
1957 Message is sent to the given client_address, with the default 'return_port' overriding
1958 the client_address' port, if defined.
1959 """
1960 lines = txt.split('\n')
1961 if len(lines) == 1:
1962 msg = OSCMessage(self.error_prefix)
1963 msg.append(lines[0])
1964 elif len(lines) > 1:
1965 msg = OSCBundle(self.error_prefix)
1966 for line in lines:
1967 msg.append(line)
1968 else:
1969 return
1970
1971 if self.return_port:
1972 client_address = (client_address[0], self.return_port)
1973
1974 self.client.sendto(msg, client_address)
1975
1976 def reportErr(self, txt, client_address):
1977 """Writes 'OSCServer: txt' to sys.stderr
1978 If self.error_prefix is defined, sends 'txt' as an OSC error-message to the client(s)
1979 (see printErr() and sendOSCerror())
1980 """
1981 self.printErr(txt)
1982
1983 if len(self.error_prefix):
1984 self.sendOSCerror(txt, client_address)
1985
1986 def sendOSCinfo(self, txt, client_address):
1987 """Sends 'txt', encapsulated in an OSCMessage to the default 'info_prefix' OSC-addres.
1988 Message is sent to the given client_address, with the default 'return_port' overriding
1989 the client_address' port, if defined.
1990 """
1991 lines = txt.split('\n')
1992 if len(lines) == 1:
1993 msg = OSCMessage(self.info_prefix)
1994 msg.append(lines[0])
1995 elif len(lines) > 1:
1996 msg = OSCBundle(self.info_prefix)
1997 for line in lines:
1998 msg.append(line)
1999 else:
2000 return
2001
2002 if self.return_port:
2003 client_address = (client_address[0], self.return_port)
2004
2005 self.client.sendto(msg, client_address)
2006
2007 ###
2008 # Message-Handler callback functions
2009 ###
2010
2011 def handle_error(self, request, client_address):
2012 """Handle an exception in the Server's callbacks gracefully.
2013 Writes the error to sys.stderr and, if the error_prefix (see setSrvErrorPrefix()) is set,
2014 sends the error-message as reply to the client
2015 """
2016 (e_type, e) = sys.exc_info()[:2]
2017 if not str(e).endswith('[Errno 61] Connection refused'): # ADDED BY TIM
2018 self.printErr("%s on request from %s: %s" % (e_type.__name__, getUrlStr(client_address), str(e)))
2019
2020 if self.print_tracebacks:
2021 import traceback
2022 traceback.print_exc() # XXX But this goes to stderr!
2023
2024 if len(self.error_prefix):
2025 self.sendOSCerror("%s: %s" % (e_type.__name__, str(e)), client_address)
2026
2027 def noCallback_handler(self, addr, tags, data, client_address):
2028 """Example handler for OSCMessages.
2029 All registerd handlers must accept these three arguments:
2030 - addr (string): The OSC-address pattern of the received Message
2031 (the 'addr' string has already been matched against the handler's registerd OSC-address,
2032 but may contain '*'s & such)
2033 - tags (string): The OSC-typetags of the received message's arguments. (without the preceding comma)
2034 - data (list): The OSCMessage's arguments
2035 Note that len(tags) == len(data)
2036 - client_address ((host, port) tuple): the host & port this message originated from.
2037
2038 a Message-handler function may return None, but it could also return an OSCMessage (or OSCBundle),
2039 which then gets sent back to the client.
2040
2041 This handler prints a "No callback registered to handle ..." message.
2042 Returns None
2043 """
2044 self.reportErr("No callback registered to handle OSC-address '%s'" % addr, client_address)
2045
2046 def msgPrinter_handler(self, addr, tags, data, client_address):
2047 """Example handler for OSCMessages.
2048 All registerd handlers must accept these three arguments:
2049 - addr (string): The OSC-address pattern of the received Message
2050 (the 'addr' string has already been matched against the handler's registerd OSC-address,
2051 but may contain '*'s & such)
2052 - tags (string): The OSC-typetags of the received message's arguments. (without the preceding comma)
2053 - data (list): The OSCMessage's arguments
2054 Note that len(tags) == len(data)
2055 - client_address ((host, port) tuple): the host & port this message originated from.
2056
2057 a Message-handler function may return None, but it could also return an OSCMessage (or OSCBundle),
2058 which then gets sent back to the client.
2059
2060 This handler prints the received message.
2061 Returns None
2062 """
2063 txt = "OSCMessage '%s' from %s: " % (addr, getUrlStr(client_address))
2064 txt += str(data)
2065
2066 self.printErr(txt) # strip trailing comma & space
2067
2068 def serverInfo_handler(self, addr, tags, data, client_address):
2069 """Example handler for OSCMessages.
2070 All registerd handlers must accept these three arguments:
2071 - addr (string): The OSC-address pattern of the received Message
2072 (the 'addr' string has already been matched against the handler's registerd OSC-address,
2073 but may contain '*'s & such)
2074 - tags (string): The OSC-typetags of the received message's arguments. (without the preceding comma)
2075 - data (list): The OSCMessage's arguments
2076 Note that len(tags) == len(data)
2077 - client_address ((host, port) tuple): the host & port this message originated from.
2078
2079 a Message-handler function may return None, but it could also return an OSCMessage (or OSCBundle),
2080 which then gets sent back to the client.
2081
2082 This handler returns a reply to the client, which can contain various bits of information
2083 about this server, depending on the first argument of the received OSC-message:
2084 - 'help' | 'info' : Reply contains server type & version info, plus a list of
2085 available 'commands' understood by this handler
2086 - 'list' | 'ls' : Reply is a bundle of 'address <string>' messages, listing the server's
2087 OSC address-space.
2088 - 'clients' | 'targets' : Reply is a bundle of 'target osc://<host>:<port>[<prefix>] [<filter>] [...]'
2089 messages, listing the local Client-instance's subscribed remote clients.
2090 """
2091 if len(data) == 0:
2092 return None
2093
2094 cmd = data.pop(0)
2095
2096 reply = None
2097 if cmd in ('help', 'info'):
2098 reply = OSCBundle(self.info_prefix)
2099 reply.append(('server', str(self)))
2100 reply.append(('info_command', "ls | list : list OSC address-space"))
2101 reply.append(('info_command', "clients | targets : list subscribed clients"))
2102 elif cmd in ('ls', 'list'):
2103 reply = OSCBundle(self.info_prefix)
2104 for addr in self.callbacks.keys():
2105 reply.append(('address', addr))
2106 elif cmd in ('clients', 'targets'):
2107 if hasattr(self.client, 'getOSCTargetStrings'):
2108 reply = OSCBundle(self.info_prefix)
2109 for trg in self.client.getOSCTargetStrings():
2110 reply.append(('target',) + trg)
2111 else:
2112 cli_addr = self.client.address()
2113 if cli_addr:
2114 reply = OSCMessage(self.info_prefix)
2115 reply.append(('target', "osc://%s/" % getUrlStr(cli_addr)))
2116 else:
2117 self.reportErr("unrecognized command '%s' in /info request from osc://%s. Try 'help'" % (cmd, getUrlStr(client_address)), client_address)
2118
2119 return reply
2120
2121 def _subscribe(self, data, client_address):
2122 """Handle the actual subscription. the provided 'data' is concatenated together to form a
2123 '<host>:<port>[<prefix>] [<filter>] [...]' string, which is then passed to
2124 parseUrlStr() & parseFilterStr() to actually retreive <host>, <port>, etc.
2125
2126 This 'long way 'round' approach (almost) guarantees that the subscription works,
2127 regardless of how the bits of the <url> are encoded in 'data'.
2128 """
2129 url = ""
2130 have_port = False
2131 for item in data:
2132 if (type(item) == types.IntType) and not have_port:
2133 url += ":%d" % item
2134 have_port = True
2135 elif type(item) in types.StringTypes:
2136 url += item
2137
2138 (addr, tail) = parseUrlStr(url)
2139 (prefix, filters) = parseFilterStr(tail)
2140
2141 if addr != None:
2142 (host, port) = addr
2143 if not host:
2144 host = client_address[0]
2145 if not port:
2146 port = client_address[1]
2147 addr = (host, port)
2148 else:
2149 addr = client_address
2150
2151 self.client._setTarget(addr, prefix, filters)
2152
2153 trg = self.client.getOSCTargetStr(addr)
2154 if trg[0] != None:
2155 reply = OSCMessage(self.info_prefix)
2156 reply.append(('target',) + trg)
2157 return reply
2158
2159 def _unsubscribe(self, data, client_address):
2160 """Handle the actual unsubscription. the provided 'data' is concatenated together to form a
2161 '<host>:<port>[<prefix>]' string, which is then passed to
2162 parseUrlStr() to actually retreive <host>, <port> & <prefix>.
2163
2164 This 'long way 'round' approach (almost) guarantees that the unsubscription works,
2165 regardless of how the bits of the <url> are encoded in 'data'.
2166 """
2167 url = ""
2168 have_port = False
2169 for item in data:
2170 if (type(item) == types.IntType) and not have_port:
2171 url += ":%d" % item
2172 have_port = True
2173 elif type(item) in types.StringTypes:
2174 url += item
2175
2176 (addr, _) = parseUrlStr(url)
2177
2178 if addr == None:
2179 addr = client_address
2180 else:
2181 (host, port) = addr
2182 if not host:
2183 host = client_address[0]
2184 if not port:
2185 try:
2186 (host, port) = self.client._searchHostAddr(host)
2187 except NotSubscribedError:
2188 port = client_address[1]
2189
2190 addr = (host, port)
2191
2192 try:
2193 self.client._delTarget(addr)
2194 except NotSubscribedError, e:
2195 txt = "%s: %s" % (e.__class__.__name__, str(e))
2196 self.printErr(txt)
2197
2198 reply = OSCMessage(self.error_prefix)
2199 reply.append(txt)
2200 return reply
2201
2202 def subscription_handler(self, addr, tags, data, client_address):
2203 """Handle 'subscribe' / 'unsubscribe' requests from remote hosts,
2204 if the local Client supports this (i.e. OSCMultiClient).
2205
2206 Supported commands:
2207 - 'help' | 'info' : Reply contains server type & version info, plus a list of
2208 available 'commands' understood by this handler
2209 - 'list' | 'ls' : Reply is a bundle of 'target osc://<host>:<port>[<prefix>] [<filter>] [...]'
2210 messages, listing the local Client-instance's subscribed remote clients.
2211 - '[subscribe | listen | sendto | target] <url> [<filter> ...] : Subscribe remote client/server at <url>,
2212 and/or set message-filters for messages being sent to the subscribed host, with the optional <filter>
2213 arguments. Filters are given as OSC-addresses (or '*') prefixed by a '+' (send matching messages) or
2214 a '-' (don't send matching messages). The wildcard '*', '+*' or '+/*' means 'send all' / 'filter none',
2215 and '-*' or '-/*' means 'send none' / 'filter all' (which is not the same as unsubscribing!)
2216 Reply is an OSCMessage with the (new) subscription; 'target osc://<host>:<port>[<prefix>] [<filter>] [...]'
2217 - '[unsubscribe | silence | nosend | deltarget] <url> : Unsubscribe remote client/server at <url>
2218 If the given <url> isn't subscribed, a NotSubscribedError-message is printed (and possibly sent)
2219
2220 The <url> given to the subscribe/unsubscribe handler should be of the form:
2221 '[osc://][<host>][:<port>][<prefix>]', where any or all components can be omitted.
2222
2223 If <host> is not specified, the IP-address of the message's source is used.
2224 If <port> is not specified, the <host> is first looked up in the list of subscribed hosts, and if found,
2225 the associated port is used.
2226 If <port> is not specified and <host> is not yet subscribed, the message's source-port is used.
2227 If <prefix> is specified on subscription, <prefix> is prepended to the OSC-address of all messages
2228 sent to the subscribed host.
2229 If <prefix> is specified on unsubscription, the subscribed host is only unsubscribed if the host,
2230 port and prefix all match the subscription.
2231 If <prefix> is not specified on unsubscription, the subscribed host is unsubscribed if the host and port
2232 match the subscription.
2233 """
2234 if not isinstance(self.client, OSCMultiClient):
2235 raise OSCServerError("Local %s does not support subsctiptions or message-filtering" % self.client.__class__.__name__)
2236
2237 addr_cmd = addr.split('/')[-1]
2238
2239 if len(data):
2240 if data[0] in ('help', 'info'):
2241 reply = OSCBundle(self.info_prefix)
2242 reply.append(('server', str(self)))
2243 reply.append(('subscribe_command', "ls | list : list subscribed targets"))
2244 reply.append(('subscribe_command', "[subscribe | listen | sendto | target] <url> [<filter> ...] : subscribe to messages, set filters"))
2245 reply.append(('subscribe_command', "[unsubscribe | silence | nosend | deltarget] <url> : unsubscribe from messages"))
2246 return reply
2247
2248 if data[0] in ('ls', 'list'):
2249 reply = OSCBundle(self.info_prefix)
2250 for trg in self.client.getOSCTargetStrings():
2251 reply.append(('target',) + trg)
2252 return reply
2253
2254 if data[0] in ('subscribe', 'listen', 'sendto', 'target'):
2255 return self._subscribe(data[1:], client_address)
2256
2257 if data[0] in ('unsubscribe', 'silence', 'nosend', 'deltarget'):
2258 return self._unsubscribe(data[1:], client_address)
2259
2260 if addr_cmd in ('subscribe', 'listen', 'sendto', 'target'):
2261 return self._subscribe(data, client_address)
2262
2263 if addr_cmd in ('unsubscribe', 'silence', 'nosend', 'deltarget'):
2264 return self._unsubscribe(data, client_address)
2265
2266 class ForkingOSCServer(ForkingMixIn, OSCServer):
2267 """An Asynchronous OSCServer.
2268 This server forks a new process to handle each incoming request.
2269 """
2270 # set the RequestHandlerClass, will be overridden by ForkingOSCServer & ThreadingOSCServer
2271 RequestHandlerClass = ThreadingOSCRequestHandler
2272
2273 class ThreadingOSCServer(ThreadingMixIn, OSCServer):
2274 """An Asynchronous OSCServer.
2275 This server starts a new thread to handle each incoming request.
2276 """
2277 # set the RequestHandlerClass, will be overridden by ForkingOSCServer & ThreadingOSCServer
2278 RequestHandlerClass = ThreadingOSCRequestHandler
2279
2280 ######
2281 #
2282 # OSCError classes
2283 #
2284 ######
2285
2286 class OSCError(Exception):
2287 """Base Class for all OSC-related errors
2288 """
2289 def __init__(self, message):
2290 self.message = message
2291
2292 def __str__(self):
2293 return self.message
2294
2295 class OSCClientError(OSCError):
2296 """Class for all OSCClient errors
2297 """
2298 pass
2299
2300 class OSCServerError(OSCError):
2301 """Class for all OSCServer errors
2302 """
2303 pass
2304
2305 class NoCallbackError(OSCServerError):
2306 """This error is raised (by an OSCServer) when an OSCMessage with an 'unmatched' address-pattern
2307 is received, and no 'default' handler is registered.
2308 """
2309 def __init__(self, pattern):
2310 """The specified 'pattern' should be the OSC-address of the 'unmatched' message causing the error to be raised.
2311 """
2312 self.message = "No callback registered to handle OSC-address '%s'" % pattern
2313
2314 class NotSubscribedError(OSCClientError):
2315 """This error is raised (by an OSCMultiClient) when an attempt is made to unsubscribe a host
2316 that isn't subscribed.
2317 """
2318 def __init__(self, addr, prefix=None):
2319 if prefix:
2320 url = getUrlStr(addr, prefix)
2321 else:
2322 url = getUrlStr(addr, '')
2323
2324 self.message = "Target osc://%s is not subscribed" % url
2325
2326 ######
2327 #
2328 # Testing Program
2329 #
2330 ######
2331
2332
2333 if __name__ == "__main__":
2334 import optparse
2335
2336 default_port = 2222
2337
2338 # define command-line options
2339 op = optparse.OptionParser(description="OSC.py OpenSoundControl-for-Python Test Program")
2340 op.add_option("-l", "--listen", dest="listen",
2341 help="listen on given host[:port]. default = '0.0.0.0:%d'" % default_port)
2342 op.add_option("-s", "--sendto", dest="sendto",
2343 help="send to given host[:port]. default = '127.0.0.1:%d'" % default_port)
2344 op.add_option("-t", "--threading", action="store_true", dest="threading",
2345 help="Test ThreadingOSCServer")
2346 op.add_option("-f", "--forking", action="store_true", dest="forking",
2347 help="Test ForkingOSCServer")
2348 op.add_option("-u", "--usage", action="help", help="show this help message and exit")
2349
2350 op.set_defaults(listen=":%d" % default_port)
2351 op.set_defaults(sendto="")
2352 op.set_defaults(threading=False)
2353 op.set_defaults(forking=False)
2354
2355 # Parse args
2356 (opts, args) = op.parse_args()
2357
2358 addr, server_prefix = parseUrlStr(opts.listen)
2359 if addr != None and addr[0] != None:
2360 if addr[1] != None:
2361 listen_address = addr
2362 else:
2363 listen_address = (addr[0], default_port)
2364 else:
2365 listen_address = ('', default_port)
2366
2367 targets = {}
2368 for trg in opts.sendto.split(','):
2369 (addr, prefix) = parseUrlStr(trg)
2370 if len(prefix):
2371 (prefix, filters) = parseFilterStr(prefix)
2372 else:
2373 filters = {}
2374
2375 if addr != None:
2376 if addr[1] != None:
2377 targets[addr] = [prefix, filters]
2378 else:
2379 targets[(addr[0], listen_address[1])] = [prefix, filters]
2380 elif len(prefix) or len(filters):
2381 targets[listen_address] = [prefix, filters]
2382
2383 welcome = "Welcome to the OSC testing program."
2384 print welcome
2385 hexDump(welcome)
2386 print
2387 message = OSCMessage()
2388 message.setAddress("/print")
2389 message.append(44)
2390 message.append(11)
2391 message.append(4.5)
2392 message.append("the white cliffs of dover")
2393
2394 print message
2395 hexDump(message.getBinary())
2396
2397 print "\nMaking and unmaking a message.."
2398
2399 strings = OSCMessage("/prin{ce,t}")
2400 strings.append("Mary had a little lamb")
2401 strings.append("its fleece was white as snow")
2402 strings.append("and everywhere that Mary went,")
2403 strings.append("the lamb was sure to go.")
2404 strings.append(14.5)
2405 strings.append(14.5)
2406 strings.append(-400)
2407
2408 raw = strings.getBinary()
2409
2410 print strings
2411 hexDump(raw)
2412
2413 print "Retrieving arguments..."
2414 data = raw
2415 for i in range(6):
2416 text, data = _readString(data)
2417 print text
2418
2419 number, data = _readFloat(data)
2420 print number
2421
2422 number, data = _readFloat(data)
2423 print number
2424
2425 number, data = _readInt(data)
2426 print number
2427
2428 print decodeOSC(raw)
2429
2430 print "\nTesting Blob types."
2431
2432 blob = OSCMessage("/pri*")
2433 blob.append("","b")
2434 blob.append("b","b")
2435 blob.append("bl","b")
2436 blob.append("blo","b")
2437 blob.append("blob","b")
2438 blob.append("blobs","b")
2439 blob.append(42)
2440
2441 print blob
2442 hexDump(blob.getBinary())
2443
2444 print1 = OSCMessage()
2445 print1.setAddress("/print")
2446 print1.append("Hey man, that's cool.")
2447 print1.append(42)
2448 print1.append(3.1415926)
2449
2450 print "\nTesting OSCBundle"
2451
2452 bundle = OSCBundle()
2453 bundle.append(print1)
2454 bundle.append({'addr':"/print", 'args':["bundled messages:", 2]})
2455 bundle.setAddress("/*print")
2456 bundle.append(("no,", 3, "actually."))
2457
2458 print bundle
2459 hexDump(bundle.getBinary())
2460
2461 # Instantiate OSCClient
2462 print "\nInstantiating OSCClient:"
2463 if len(targets):
2464 c = OSCMultiClient()
2465 c.updateOSCTargets(targets)
2466 else:
2467 c = OSCClient()
2468 c.connect(listen_address) # connect back to our OSCServer
2469
2470 print c
2471 if hasattr(c, 'getOSCTargetStrings'):
2472 print "Sending to:"
2473 for (trg, filterstrings) in c.getOSCTargetStrings():
2474 out = trg
2475 for fs in filterstrings:
2476 out += " %s" % fs
2477
2478 print out
2479
2480 # Now an OSCServer...
2481 print "\nInstantiating OSCServer:"
2482
2483 # define a message-handler function for the server to call.
2484 def printing_handler(addr, tags, stuff, source):
2485 msg_string = "%s [%s] %s" % (addr, tags, str(stuff))
2486 sys.stdout.write("OSCServer Got: '%s' from %s\n" % (msg_string, getUrlStr(source)))
2487
2488 # send a reply to the client.
2489 msg = OSCMessage("/printed")
2490 msg.append(msg_string)
2491 return msg
2492
2493 if opts.threading:
2494 s = ThreadingOSCServer(listen_address, c, return_port=listen_address[1])
2495 elif opts.forking:
2496 s = ForkingOSCServer(listen_address, c, return_port=listen_address[1])
2497 else:
2498 s = OSCServer(listen_address, c, return_port=listen_address[1])
2499
2500 print s
2501
2502 # Set Server to return errors as OSCMessages to "/error"
2503 s.setSrvErrorPrefix("/error")
2504 # Set Server to reply to server-info requests with OSCMessages to "/serverinfo"
2505 s.setSrvInfoPrefix("/serverinfo")
2506
2507 # this registers a 'default' handler (for unmatched messages),
2508 # an /'error' handler, an '/info' handler.
2509 # And, if the client supports it, a '/subscribe' & '/unsubscribe' handler
2510 s.addDefaultHandlers()
2511
2512 s.addMsgHandler("/print", printing_handler)
2513
2514 # if client & server are bound to 'localhost', server replies return to itself!
2515 s.addMsgHandler("/printed", s.msgPrinter_handler)
2516 s.addMsgHandler("/serverinfo", s.msgPrinter_handler)
2517
2518 print "Registered Callback-functions:"
2519 for addr in s.getOSCAddressSpace():
2520 print addr
2521
2522 print "\nStarting OSCServer. Use ctrl-C to quit."
2523 st = threading.Thread(target=s.serve_forever)
2524 st.start()
2525
2526 if hasattr(c, 'targets') and listen_address not in c.targets.keys():
2527 print "\nSubscribing local Server to local Client"
2528 c2 = OSCClient()
2529 c2.connect(listen_address)
2530 subreq = OSCMessage("/subscribe")
2531 subreq.append(listen_address)
2532
2533 print "sending: ", subreq
2534 c2.send(subreq)
2535 c2.close()
2536
2537 time.sleep(0.1)
2538
2539 print "\nRequesting OSC-address-space and subscribed clients from OSCServer"
2540 inforeq = OSCMessage("/info")
2541 for cmd in ("info", "list", "clients"):
2542 inforeq.clearData()
2543 inforeq.append(cmd)
2544
2545 print "sending: ", inforeq
2546 c.send(inforeq)
2547
2548 time.sleep(0.1)
2549
2550 print2 = print1.copy()
2551 print2.setAddress('/noprint')
2552
2553 print "\nSending Messages"
2554
2555 for m in (message, print1, print2, strings, bundle):
2556 print "sending: ", m
2557 c.send(m)
2558
2559 time.sleep(0.1)
2560
2561 print "\nThe next message's address will match both the '/print' and '/printed' handlers..."
2562 print "sending: ", blob
2563 c.send(blob)
2564
2565 time.sleep(0.1)
2566
2567 print "\nBundles can be given a timestamp.\nThe receiving server should 'hold' the bundle until its time has come"
2568
2569 waitbundle = OSCBundle("/print")
2570 waitbundle.setTimeTag(time.time() + 5)
2571 if s.__class__ == OSCServer:
2572 waitbundle.append("Note how the (single-thread) OSCServer blocks while holding this bundle")
2573 else:
2574 waitbundle.append("Note how the %s does not block while holding this bundle" % s.__class__.__name__)
2575
2576 print "Set timetag 5 s into the future"
2577 print "sending: ", waitbundle
2578 c.send(waitbundle)
2579
2580 time.sleep(0.1)
2581
2582 print "Recursing bundles, with timetags set to 10 s [25 s, 20 s, 10 s]"
2583 bb = OSCBundle("/print")
2584 bb.setTimeTag(time.time() + 10)
2585
2586 b = OSCBundle("/print")
2587 b.setTimeTag(time.time() + 25)
2588 b.append("held for 25 sec")
2589 bb.append(b)
2590
2591 b.clearData()
2592 b.setTimeTag(time.time() + 20)
2593 b.append("held for 20 sec")
2594 bb.append(b)
2595
2596 b.clearData()
2597 b.setTimeTag(time.time() + 15)
2598 b.append("held for 15 sec")
2599 bb.append(b)
2600
2601 if s.__class__ == OSCServer:
2602 bb.append("Note how the (single-thread) OSCServer handles the bundle's contents in order of appearance")
2603 else:
2604 bb.append("Note how the %s handles the sub-bundles in the order dictated by their timestamps" % s.__class__.__name__)
2605 bb.append("Each bundle's contents, however, are processed in random order (dictated by the kernel's threading)")
2606
2607 print "sending: ", bb
2608 c.send(bb)
2609
2610 time.sleep(0.1)
2611
2612 print "\nMessages sent!"
2613
2614 print "\nWaiting for OSCServer. Use ctrl-C to quit.\n"
2615
2616 try:
2617 while True:
2618 time.sleep(30)
2619
2620 except KeyboardInterrupt:
2621 print "\nClosing OSCServer."
2622 s.close()
2623 print "Waiting for Server-thread to finish"
2624 st.join()
2625 print "Closing OSCClient"
2626 c.close()
2627 print "Done"
2628
2629 sys.exit(0)