From eb66dce18886cadf4cf4c7806f05ef4c703cd4db Mon Sep 17 00:00:00 2001 From: Simon Schampijer Date: Tue, 05 Jun 2007 12:00:02 +0000 Subject: - changes in the osc api: - osc messages are typesafe now (path and types of argumens are verified) - some method/class name changes - added documentation - adopted the game server to use the new osc api --- diff --git a/memosono.py b/memosono.py index 367c7dd..c91c4ce 100755 --- a/memosono.py +++ b/memosono.py @@ -22,8 +22,6 @@ import gobject import gtk, pygtk import os import socket -from osc.oscAPI import * -from osc.OSC import * import logging import random import copy @@ -31,19 +29,24 @@ import time import errno import gc - from sugar.activity import activity +from osc.oscapi import OscApi class Server: def __init__(self, _MEMO, port): - self.oscapi = OscApi() - self.port = 0 - self.oscrecv, self.port = self.oscapi.createListener('127.0.0.1', port) + + isserver = 0 + while isserver == 0: + self.port = port + self.oscapi = OscApi(self.port) + isserver = self.oscapi.isserver + port+=1 + logging.debug(" Memosono-Server has port "+str(self.port) ) - gobject.io_add_watch(self.oscrecv, gobject.IO_IN, self._handle_query) - self.oscapi.bind(self._tile, '/MEMO/tile') - self.oscapi.bind(self._connect, '/MEMO/connect') + gobject.io_add_watch(self.oscapi.iosock, gobject.IO_IN, self._handle_query) + self.oscapi.addmethod('/MEMO/tile', 'is', self._tile) + self.oscapi.addmethod('/MEMO/connect', 'i', self._connect) self.compkey = '' self.key = '' self.tile = 0 @@ -61,7 +64,7 @@ class Server: def _handle_query(self, source, condition): data, address = source.recvfrom(1024) - self.oscapi.recvhandler(data, address) + self.oscapi.handlemsg(data, address) return True # OSC-METHODS: @@ -77,12 +80,12 @@ class Server: for i in self.addresses: if msg[1][0] == self.addresses[i][0]: if msg[1][1] != self.addresses[i][1]: - self.oscapi.sendMsg("/MEMO/tile", [self.tile, self.key], - self.addresses[i][0], self.addresses[i][1]) + self.oscapi.send((self.addresses[i][0], self.addresses[i][1]), "/MEMO/tile", + [self.tile, self.key]) else: ## logging.debug(" Send the stuff ") - self.oscapi.sendMsg("/MEMO/tile", [self.tile, self.key], - self.addresses[i][0], self.addresses[i][1]) + self.oscapi.send((self.addresses[i][0], self.addresses[i][1]), "/MEMO/tile", + [self.tile, self.key]) # match if self.compkey != '': if self.compkey == self.key: @@ -100,14 +103,14 @@ class Server: self.currentplayer+=1 i = 0 for i in self.addresses: - self.oscapi.sendMsg("/MEMO/game/next",[self.players[self.currentplayer], - self.players[self.lastplayer]], - self.addresses[i][0], self.addresses[i][1]) + self.oscapi.send((self.addresses[i][0], self.addresses[i][1]),"/MEMO/game/next", + [self.players[self.currentplayer], + self.players[self.lastplayer]]) i = 0 for i in self.addresses: - self.oscapi.sendMsg("/MEMO/game/match", [self.match, self.players[self.lastplayer], - self.comtile, self.tile, self.count/self.numpairs], - self.addresses[i][0], self.addresses[i][1]) + self.oscapi.send((self.addresses[i][0], self.addresses[i][1]), "/MEMO/game/match", + [self.match, self.players[self.lastplayer], + self.comtile, self.tile, self.count/self.numpairs]) self.compkey = '' self.comptile = 0 else: @@ -144,20 +147,25 @@ class Controler(gobject.GObject): gobject.GObject.__init__(self) self._MEMO = _MEMO self.sound = 0 - # OSC-communication - self.oscapi = OscApi() - self.port = 0 self.replyaddr = (('127.0.0.1', port)) - self.serveraddr = (('127.0.0.1', port)) - self.oscrecv, self.port = self.oscapi.createListener('127.0.0.1', port+1) + self.serveraddr = (('127.0.0.1', port)) + port+=1 + # OSC-communication + isserver = 0 + while isserver == 0: + self.port = port + self.oscapi = OscApi(self.port) + isserver = self.oscapi.isserver + port+=1 + logging.debug(" Memosono-Client has port "+str(self.port) ) - self.oscapi.sendMsg("/MEMO/connect", [self.port], self.serveraddr[0], self.serveraddr[1]) - gobject.io_add_watch(self.oscrecv, gobject.IO_IN, self._handle_query) - self.oscapi.bind(self._addplayer, '/MEMO/addplayer') - self.oscapi.bind(self._game_init, '/MEMO/init') - self.oscapi.bind(self._tile, '/MEMO/tile') - self.oscapi.bind(self._game_match, '/MEMO/game/match') - self.oscapi.bind(self._game_next, '/MEMO/game/next') + self.oscapi.send((self.serveraddr[0], self.serveraddr[1]), "/MEMO/connect", [self.port]) + gobject.io_add_watch(self.oscapi.iosock, gobject.IO_IN, self._handle_query) + self.oscapi.addmethod('/MEMO/addplayer', '', self._addplayer) + self.oscapi.addmethod('/MEMO/init', 'sis', self._game_init) + self.oscapi.addmethod('/MEMO/tile', 'i', self._tile) + self.oscapi.addmethod('/MEMO/game/match', 'isiii', self._game_match) + self.oscapi.addmethod('/MEMO/game/next', 'ss', self._game_next) self.block = 0 self.count = 0 @@ -192,7 +200,7 @@ class Controler(gobject.GObject): def _handle_query(self, source, condition): data, self.replyaddr = source.recvfrom(1024) - self.oscapi.recvhandler(data, self.replyaddr) + self.oscapi.handlemsg(data, self.replyaddr) return True # SLOTS: @@ -228,7 +236,7 @@ class Controler(gobject.GObject): if requesttype == 0: logging.debug(" send to server ") - self.oscapi.sendMsg("/MEMO/tile", [tile_number, pic], self.serveraddr[0], self.serveraddr[1]) + self.oscapi.send((self.serveraddr[0], self.serveraddr[1]), "/MEMO/tile", [tile_number, pic]) if self.count == 2: self.block = 1 self.count = 0 @@ -546,6 +554,7 @@ class MemosonoActivity(activity.Activity): self.gamename = 'drumgit' self.set_title("Memosono - "+self.gamename) + logging.debug(" ---------------------Memosono start-----------------------. ") # set path _MEMO = {} diff --git a/osc/oscapi.py b/osc/oscapi.py new file mode 100755 index 0000000..bc26ddb --- /dev/null +++ b/osc/oscapi.py @@ -0,0 +1,163 @@ +""" This file is based on simpleOSC 0.2 by Daniel Holth. +This file has been modified by Simon Schampijer. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + + +import socket +import errno +import logging +import sys + +import osccore + + +class OscApi(object): + def __init__(self, port=None, host=None): + """create the send/receive socket and the callback manager + bind the socket to a name (host and port) + + Keyword arguments: + port -- the port + if no port is specified the socket will not be bound to an address and + will only be used for sending + host -- the host address, can be a dotted decimal address (e.g. '192.168.0.100') + or a hostname (e.g. 'machine', 'localhost') + if you do not specify a hostname the socket will listen on all the + devices for incoming messages + """ + + self.manager = 0 + self.iosock = 0 + self.iosock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.manager = osccore.CallbackManager() + + self.isserver = 0 + + if port is not None: + if host is None: + host = "" + try: + self.iosock.bind((host, port)) + self.isserver = 1 + except socket.error: + if errno.EADDRINUSE: + logging.error("Port " +str(port)+ " in use." + + " Maybe another oscapi is still or already running?" + + " oscapi can only be used for sending.") + self.iosock.close() + self.iosock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.iosock.setblocking(0) + + + def addmethod(self, path, types, func): + '''adds an osc method to the listener + + Keyword arguments: + func -- the callback function which gets called when the appropriate osc message is received + path -- the path (identifier) of the osc message + + ''' + self.manager.add(func, path, types) + + + def handlemsg(self, data, addr): + '''give the information read from the socket to the osc message dispatcher + + Keyword arguments: + data -- the data read from the socket + addr -- address the message is received from + ''' + self.manager.handle(data, addr) + + + def _create_binarymsg(self, path, data): + """create an OSC message in binary format + + Keyword arguments: + path -- the path (identifier) of the osc message + data -- the data of the message + + return: the osc message in binary format + """ + m = osccore.OSCMessage() + m.setAddress(path) + + if len(data) != 0: + for x in data: + m.append(x) + + return m.getBinary() + + + def send(self, to, path, data): + """send an osc message to the address specified + + Keyword arguments: + to -- address of receiver in the form (ipaddr, bundle) + path -- the path of the osc message + data -- the data of the message + """ + ### resolve host? [a,b,host] = socket.gethostbyaddr(to[0]) + msg = self._create_binarymsg(path, data) + try: + self.iosock.sendto(msg, (to[0],to[1])) + except socket.error: + cla, exc, trbk = sys.exc_info() + try: + excArgs = exc.__dict__["args"] + except KeyError: + excArgs = "" + logging.error('error '+str(excArgs)) + + def createbundle(self): + """create the header of a bundle of OSC messages""" + b = osccore.OSCMessage() + b.setAddress("") + b.append("#bundle") + b.append(0) + b.append(0) + return b + + def appendbundle(self, bundle, path, data): + """append osc message to the bundle + + Keyword arguments: + bundle -- the bundle to which you want to append a message + path -- the path of the OSC message + data -- the data of the message + """ + msg = self._create_binarymsg(path, data) + bundle.append(msg, 'b') + + + def sendbundle(self, to, bundle): + """send bundle to the address specified + + Keyword arguments: + to -- address of receiver in the form (ipaddr, bundle) + bundle -- the bundle to send + """ + try: + self.iosock.sendto(bundle.message, to) + except socket.error: + cla, exc, trbk = sys.exc_info() + try: + excArgs = exc.__dict__["args"] + except KeyError: + excArgs = "" + logging.error('error '+str(excArgs)) + diff --git a/osc/osccore.py b/osc/osccore.py new file mode 100755 index 0000000..4cab0b5 --- /dev/null +++ b/osc/osccore.py @@ -0,0 +1,405 @@ +#!/usr/bin/python +# +# Open SoundControl for Python +# Copyright (C) 2002 Daniel Holth, Clinton McChesney +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# For questions regarding this module contact +# Daniel Holth or visit +# http://www.stetson.edu/~ProctoLogic/ +# +# Changelog: +# 15 Nov. 2001: +# Removed dependency on Python 2.0 features. +# - dwh +# 13 Feb. 2002: +# Added a generic callback handler. +# - dwh +# 29 Mai 2007: +# Added type checking of messanges. +# - erikos + +import socket +import struct +import math +import sys +import string +import pprint + + +def hexDump(bytes): + """Useful utility; prints the string in hexadecimal""" + for i in range(len(bytes)): + sys.stdout.write("%2x " % (ord(bytes[i]))) + if (i+1) % 8 == 0: + print repr(bytes[i-7:i+1]) + + if(len(bytes) % 8 != 0): + print string.rjust("", 11), repr(bytes[i-len(bytes)%8:i+1]) + + +class OSCMessage: + """Builds typetagged OSC messages.""" + def __init__(self): + self.address = "" + self.typetags = "," + self.message = "" + + def setAddress(self, address): + self.address = address + + def setMessage(self, message): + self.message = message + + def setTypetags(self, typetags): + self.typetags = typetags + + def clear(self): + self.address = "" + self.clearData() + + def clearData(self): + self.typetags = "," + self.message = "" + + def append(self, argument, typehint = None): + """Appends data to the message, + updating the typetags based on + the argument's type. + If the argument is a blob (counted string) + pass in 'b' as typehint.""" + + if typehint == 'b': + binary = OSCBlob(argument) + else: + binary = OSCArgument(argument) + + self.typetags = self.typetags + binary[0] + self.rawAppend(binary[1]) + + def rawAppend(self, data): + """Appends raw data to the message. Use append().""" + self.message = self.message + data + + def getBinary(self): + """Returns the binary message (so far) with typetags.""" + address = OSCArgument(self.address)[1] + typetags = OSCArgument(self.typetags)[1] + return address + typetags + self.message + + def __repr__(self): + return self.getBinary() + +def readString(data): + length = string.find(data,"\0") + nextData = int(math.ceil((length+1) / 4.0) * 4) + return (data[0:length], data[nextData:]) + + +def readBlob(data): + length = struct.unpack(">i", data[0:4])[0] + nextData = int(math.ceil((length) / 4.0) * 4) + 4 + return (data[4:length+4], data[nextData:]) + + +def readInt(data): + if(len(data)<4): + print "Error: too few bytes for int", data, len(data) + rest = data + integer = 0 + else: + integer = struct.unpack(">i", data[0:4])[0] + rest = data[4:] + + return (integer, rest) + + + +def readLong(data): + """Tries to interpret the next 8 bytes of the data + as a 64-bit signed integer.""" + high, low = struct.unpack(">ll", data[0:8]) + big = (long(high) << 32) + low + rest = data[8:] + return (big, rest) + + + +def readFloat(data): + if(len(data)<4): + print "Error: too few bytes for float", data, len(data) + rest = data + float = 0 + else: + float = struct.unpack(">f", data[0:4])[0] + rest = data[4:] + + return (float, rest) + + +def OSCBlob(next): + """Convert a string into an OSC Blob, + returning a (typetag, data) tuple.""" + + if type(next) == type(""): + length = len(next) + padded = math.ceil((len(next)) / 4.0) * 4 + binary = struct.pack(">i%ds" % (padded), length, next) + tag = 'b' + else: + tag = '' + binary = '' + + return (tag, binary) + + +def OSCArgument(next): + """Convert some Python types to their + OSC binary representations, returning a + (typetag, data) tuple.""" + + if type(next) == type(""): + OSCstringLength = math.ceil((len(next)+1) / 4.0) * 4 + binary = struct.pack(">%ds" % (OSCstringLength), next) + tag = "s" + elif type(next) == type(42.5): + binary = struct.pack(">f", next) + tag = "f" + elif type(next) == type(13): + binary = struct.pack(">i", next) + tag = "i" + else: + binary = "" + tag = "" + + return (tag, binary) + + +def parseArgs(args): + """Given a list of strings, produces a list + where those strings have been parsed (where + possible) as floats or integers.""" + parsed = [] + for arg in args: + print arg + arg = arg.strip() + interpretation = None + try: + interpretation = float(arg) + if string.find(arg, ".") == -1: + interpretation = int(interpretation) + except: + # Oh - it was a string. + interpretation = arg + pass + parsed.append(interpretation) + return parsed + + + +def decodeOSC(data): + """Converts a typetagged OSC message to a Python list.""" + table = {"i":readInt, "f":readFloat, "s":readString, "b":readBlob} + decoded = [] + address, rest = readString(data) + typetags = "" + + if address == "#bundle": + time, rest = readLong(rest) +# decoded.append(address) +# decoded.append(time) + while len(rest)>0: + length, rest = readInt(rest) + decoded.append(decodeOSC(rest[:length])) + rest = rest[length:] + + elif len(rest) > 0: + typetags, rest = readString(rest) + decoded.append(address) + decoded.append(typetags) + if typetags[0] == ",": + for tag in typetags[1:]: + value, rest = table[tag](rest) + decoded.append(value) + else: + print "Oops, typetag lacks the magic ," + + return decoded + + +class CallbackManager: + """This utility class maps OSC addresses to callables. + + The CallbackManager calls its callbacks with a list + of decoded OSC arguments, including the address and + the typetags as the first two arguments.""" + + def __init__(self): + self.callbacks = {} + self.add(self.unbundler, "#bundle", '') + + def handle(self, data, source = None): + """Given OSC data, tries to call the callback with the + right address.""" + decoded = decodeOSC(data) + self.dispatch(decoded, source) + + def dispatch(self, message, source = None): + """Sends decoded OSC data to an appropriate callback""" + try: + if type(message[0]) == str : + # got a single message + address = message[0] + + if address in self.callbacks: + types = message[1].split(',')[1] + if(types == self.callbacks[address][1]): + self.callbacks[address][0](message, source) + else: + print '[error] path %s exist on osc server but...'%address + print ' the types received [%s] do not match with [%s].'%(types,self.callbacks[address][1]) + print ' received message: %s'%(message) + else: + print '[error] path %s does not exist on osc server.'%address + + elif type(message[0]) == list : + # smells like nested messages + for msg in message : + self.dispatch(msg, source) + + except KeyError, e: + # address not found + print 'address %s not found ' % address + pprint.pprint(message) + except IndexError, e: + print 'got malformed OSC message' + pass + except None, e: + print "Exception in", address, "callback :", e + + return + + def add(self, callback, name, types): + """Adds a callback to our set of callbacks, + or removes the callback with name if callback + is None.""" + if callback == None: + del self.callbacks[name] + else: + self.callbacks[name] = [callback, types] + + def unbundler(self, messages): + """Dispatch the messages in a decoded bundle.""" + # first two elements are #bundle and the time tag, rest are messages. + for message in messages[2:]: + self.dispatch(message) + + +if __name__ == "__main__": + hexDump("Welcome to the OSC testing program.") + print + message = OSCMessage() + message.setAddress("/foo/play") + message.append(44) + message.append(11) + message.append(4.5) + message.append("the white cliffs of dover") + hexDump(message.getBinary()) + + print "Making and unmaking a message.." + + strings = OSCMessage() + strings.append("Mary had a little lamb") + strings.append("its fleece was white as snow") + strings.append("and everywhere that Mary went,") + strings.append("the lamb was sure to go.") + strings.append(14.5) + strings.append(14.5) + strings.append(-400) + + raw = strings.getBinary() + + hexDump(raw) + + print "Retrieving arguments..." + data = raw + for i in range(6): + text, data = readString(data) + print text + + number, data = readFloat(data) + print number + + number, data = readFloat(data) + print number + + number, data = readInt(data) + print number + + hexDump(raw) + print decodeOSC(raw) + print decodeOSC(message.getBinary()) + + print "Testing Blob types." + + blob = OSCMessage() + blob.append("","b") + blob.append("b","b") + blob.append("bl","b") + blob.append("blo","b") + blob.append("blob","b") + blob.append("blobs","b") + blob.append(42) + + hexDump(blob.getBinary()) + + print decodeOSC(blob.getBinary()) + + def printingCallback(*stuff): + sys.stdout.write("Got: ") + for i in stuff: + sys.stdout.write(str(i) + " ") + sys.stdout.write("\n") + + print "Testing the callback manager." + + c = CallbackManager() + c.add(printingCallback, "/print") + + c.handle(message.getBinary()) + message.setAddress("/print") + c.handle(message.getBinary()) + + print1 = OSCMessage() + print1.setAddress("/print") + print1.append("Hey man, that's cool.") + print1.append(42) + print1.append(3.1415926) + + c.handle(print1.getBinary()) + + bundle = OSCMessage() + bundle.setAddress("") + bundle.append("#bundle") + bundle.append(0) + bundle.append(0) + bundle.append(print1.getBinary(), 'b') + bundle.append(print1.getBinary(), 'b') + + bundlebinary = bundle.message + + print "sending a bundle to the callback manager" + c.handle(bundlebinary) -- cgit v0.9.1