#!/usr/bin/python # -*- coding: UTF-8 -*- """ Nabaztag python API by Ricardo Varela modified and expanded by Mike McGurrin This module wraps up the Nabaztag API based on Nabaztag API 2.0 (see http://api.nabaztag.com/docs/home.html) """ import urllib import xml.etree.ElementTree as ET """ Encapsulate the TTS voice ids """ class TTSVoices: # french FR_1 = 'julie22k', FR_2 = 'claire22s', FR_3 = 'caroline22k', FR_4 = 'bruno22k', # english EN_1 = 'graham22s', EN_2 = 'lucy22s', EN_3 = 'heather22k', EN_4 = 'ryan22k', EN_5 = 'aaron22s', EN_6 = 'laura22s' """ Helper class to provide alternative parsings of the returned strings from the Nabaztag class. The Nabaztag class returns the raw XML strings returned from the call to Violet's Nabaztag server (or a clone you call, such as OpenNab). This class provides two alternate forms, an ElementTree and a string representing a complete walk of the tree. """ class FormatResponse: def __init__(self): self.input = "" def tree(self, input): """Converts the input string (which is an xml-formatted string returned from the call to the Nabaztag API) to an ElementTree""" tree=ET.XML(input) return tree def text(self, input): """Converts the input string (which is an xml-formatted string returned from the call to the Nabaztag API) to a text string created by walking through the ElementTree""" tree=ET.XML(input) """ The following parsing code is taken (with slight modifications) from listing 3.py of "Simple XML Processing With elementtree" at http://www.xml.com/pub/a/2003/02/12/py-xml.html """ str_list = [] root = tree #Create an iterator iter = root.getiterator() #Iterate for element in iter: #First the element tag name str_list.append("Element: ") str_list.append(element.tag) str_list.append("\n") #Next the attributes (available on the instance itself using #the Python dictionary protocol if element.keys(): str_list.append("\tAttributes:\n") for name, value in element.items(): str_list.append("\tName: ") str_list.append(name) str_list.append(" Value: ") str_list.append(value) str_list.append("\n") #Next the text #Text that precedes all child elements (may be None) if element.text: text = element.text text = len(text) > 40 and text[:40] + "..." or text str_list.append("\tText: ") str_list.append(repr(text)) str_list.append("\n") if element.getchildren(): #Can also use: "for child in element.getchildren():" for child in element: # We don't print child elements here, because they'll be processed # on their own, but there may be "tail" text. #The "tail" on each child element consists of the text #that comes after it in the parent element content, but #before its next sibling. if child.tail: text = child.tail text = len(text) > 40 and text[:40] + "..." or text str_list.append("\tText: ") str_list.append(repre(text)) str_list.append("\n") return ''.join(str_list) """ Encapsulate a compound command. This supports sending multiple API commands in a single call. Call the appropriate "add" method to add a command to the set to be sent. The set of commands is kept as a list of dictionaries in the commands attribute. To send the command, call the doCompoundCommand method of the Nabaztag class, and pass it the commands attribute of the instance you've created of the CompoundCommand class. """ class CompoundCommand: default_voice = TTSVoices.EN_6 def __init__(self): self.commands = [] def _add(self, **kwargs): """builds compound command, so bunny can be commanded to do more than one thing in a single call""" self.commands.append(kwargs) return def addMessage(self, idmessage): """adds a message in the library or uploaded mp3""" self._add(idmessage=idmessage) return def addSay(self, text, voice=default_voice, speed='', pitch=''): """Adds saying a message using optional voicename""" self._add(tts=text, voice=voice, speed=speed, pitch=pitch) return def addPlay(self, urls): """Adds playing remote mp3s using given single url or list of urls""" if isinstance(urls, str): urls = (urls,) self._add(urlList='|'.join(urls)) return def addAction(self, actionID): """Adds sending a specified "action" command, as defined at http://api.nabaztag.com/docs/home.html""" self._add(action=actionID) def addSetEars(self, posright='', posleft=''): """Adds sending command(s) to move ear(s) to specified position""" """0-16, with 0 being straight up""" self._add(posright=posright, posleft=posleft) return def addGetEars(self): """adds polling server to get ear positions""" value='ok' self._add(ears=value) return def addDoChoreography(self, choreography): """Does the choreography specified in the given instance of the Choreography class """ if isinstance(choreography, Choreography): self._add(chor=choreography.buildChoreography()) else: raise TypeException, "Invalid parameter in doChoreography (%s)" % choreography return """ Encapsulate a choreography """ class Choreography: tempo = '' commands = [] def __init__(self, tempo='10'): self.tempo = tempo def addEarCommand(self, heure, side, angle, direction): """ add a new ear movement command to the choreography """ # parameter check badParam = '' if not heure >= 0: badParam = 'heure' elif not (side == Choreography.EAR_LEFT or side == Choreography.EAR_RIGHT): badParam = 'side' elif not angle >=0 and angle <=180: badParam = 'angle' elif not (direction == Choreography.EAR_BACK or direction == Choreography.EAR_FRONT): badParam = 'direction' if badParam != '': raise ValueError, "Invalid parameter in ear command (%s)" % badParam else: self.commands.append(self.buildEarCommand(heure, side, angle, direction)) def buildEarCommand(self, heure, side, angle, direction): """ return a string with the API representation of the command """ return "%s,motor,%s,%s,0,%s" % (heure, side, angle, direction) def addLedCommand(self, heure, led, r, g, b): """ add a new led command to the choreography """ # parameter check badParam = '' if not heure >= 0: badParam = 'heure' elif not (led == Choreography.LED_BOTTOM or led == Choreography.LED_LEFT or led == Choreography.LED_MIDDLE or led == Choreography.LED_RIGHT or led == Choreography.LED_TOP): badParam = 'led' elif not (r >=0 and r <=255): badParam = 'r' elif not (g >=0 and g <=255): badParam = 'g' elif not (b >=0 and b <=255): badParam = 'b' if badParam != '': raise ValueError, "Invalid parameter in ear command (%s)" % badParam else: self.commands.append(self.buildLedCommand(heure, led, r, g, b)) def buildLedCommand(self, heure, led, r, g, b): """ return a string with the API representation of the command """ return "%s,led,%s,%s,%s,%s" % (heure, led, r, g, b) def buildChoreography(self): """ returns a string with the API representation for this whole choreography """ if len(self.commands) == 0: raise ValueError, "can't build an empty choreography" else: choreography = "%s" % self.tempo for command in self.commands: choreography += ",%s" % command return choreography # some useful constants # for ear commands EAR_LEFT = 1 EAR_RIGHT = 0 EAR_BACK = 1 EAR_FRONT = 0 # for led commands LED_BOTTOM = 0 LED_LEFT = 1 LED_MIDDLE = 2 LED_RIGHT = 3 LED_TOP = 4 class Nabaztag: base_uri = 'http://api.nabaztag.com/vl/FR/api.jsp?' options = {} default_voice = TTSVoices.EN_6 def __init__(self, sn, token, key=''): self.options['sn'] = sn self.options['token'] = token self.options['key'] = key def _get(self, **kwargs): """Fetches uri to API with given arguments""" options = {} options.update(self.options) options.update(kwargs) print self.base_uri + urllib.urlencode(options) try: fread = urllib.urlopen(self.base_uri, urllib.urlencode(options)) except IOError, e: # print error if any print "Error: %s" % e else: # or if not, print the response response = fread.read() ## # raw print for debugging ## print response ## # TODO: add some nicer printing here return response def sendMessage(self, idmessage): """Sends a message in the library or uploaded mp3""" return self._get(idmessage=idmessage) def say(self, text, voice=default_voice, speed='', pitch=''): """Says a message using optional voicename""" return self._get(tts=text, voice=voice, speed=speed, pitch=pitch) def play(self, urls): """Plays remote mp3s using given single url or list of urls""" if isinstance(urls, str): urls = (urls,) return self._get(urlList='|'.join(urls)) def action(self, actionID): """Sends specified "action" command, as defined at http://api.nabaztag.com/docs/home.html""" return self._get(action=actionID) def setEars(self, posright='', posleft=''): """Sends command(s) to move ear(s) to specified position""" """0-16, with 0 being straight up""" return self._get(posright=posright, posleft=posleft) def doCompoundCommand(self, commandList): """Input list of dictionaries holding the key value pairs (one list item per command)""" """Convert to a single dictionary of key value pairs, then send command """ commandDict = {} for command in commandList: commandDict.update(command) # Fetches uri to API with given arguments""" commandDict.update(self.options) print self.base_uri + urllib.urlencode(commandDict) try: fread = urllib.urlopen(self.base_uri, urllib.urlencode(commandDict)) except IOError, e: # print error if any print "Error: %s" % e else: # or if not, print the response response = fread.read() ## # raw print for debugging ## print response ## # TODO: add some nicer printing here return response def getEars(self): """Polls server to get ear positions""" value='ok' result = self._get(ears=value) return result def doChoreography(self, choreography): """Does the choreography specified in the given instance of the Choreography class """ if isinstance(choreography, Choreography): return self._get(chor=choreography.buildChoreography()) else: raise TypeException, "Invalid parameter in doChoreography (%s)" % choreography def sendTextNabcast(self, nabcast, nabcasttitle, text, voice=default_voice, speed='', pitch=''): """Sends a text to speech message as a Nabcast. User must have a nabcast ID to use """ result=self._get(nabcast=nabcast, nabcasttitle=nabcasttitle, tts=text, voice=voice, speed=speed, pitch=pitch) return result def sentMessageNabcast(self, nabcast, nabcasttitle, idmessage): """Sends a message as a Nabcast User must have a nabcast ID to use """ result=self._get(nabcast=nabcast, nabcasttitle=nabcasttitle, idmessage=idmessage) return result