annotate tim_grid_mapper/OSC.py @ 50:f4c6999ecfe9 tip

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