Mercurial > hg > movesynth
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) |