scribpro.py

# coding=latin-1
#
# File: scribpro.py
# by Justin Mangue
#
# Description:
# This is a script that enables use of a Prolog natural language parsing component to control a Scribbler II robot
# over bluetooth.  Use in sync with nlp.pl.
#
# Dependencies:
# Python 2.7.4         http://www.python.org/getit/      
# PySwip 0.2.3         https://code.google.com/p/pyswip/
# PySpeech             https://code.google.com/p/pyspeech/
# Myro 2.9.5           http://myro.roboteducation.org/download/
# Swi-Prolog 6.2.6     http://www.swi-prolog.org/Download.html

# IMPORTS
from sys import exit
from myro import *
from pyswip import Prolog
import speech as pyspeech

# GLOBAL SETTINGS
verNum = '0.6'              # ScribPro Version Number
scribblerPort = 'COM5'      # Scribbler Bluetooth Port
prologFile = 'nlp.pl'       # Prolog file to consult
debug = 'on'                # Debug mode, if enabled, shows detailed prolog parse information
inputMode = "text"          # Input in text mode by default
outputMode = "text"         # Output to text by default

# CLASSES
class PrologResult(object):
  def __init__(self, prologcall, status, pythoncode):
     self.prologcall = prologcall
     self.status = status
     self.pythoncode = pythoncode

# FUNCTIONS

def strClean(string):
    """ strClean(str) -- converts "A string like This" into a list of lowercase atoms
        i.e. [a,string,like,this] for use in Prolog. also converts commas to "and"s """
    finalstring = "["
    loweredstring = string.lower()
    exclude = '!"#$%&'()*+-./:;<=>?@[\]^_`{|}~'
    for c in exclude:
        loweredstring = loweredstring.replace(c,"")    
    for ch in loweredstring:
        if ch == ' ':
            finalstring = finalstring + ","
        elif ch == ',':
            finalstring = finalstring + ",comma"
        #elif ch == u'xb0' or ch == '°':
        #    finalstring = finalstring + ",degrees"
        else:
            finalstring = finalstring + ch
    return finalstring + "]"
       
def parse(inStr):
    """parse(str) -- cleans the user input and then throws it at Prolog for analysis"""
    codeOut = "null"
    sentence = strClean(inStr)
    prologCall = 'parseToCode(' + sentence + ', Status, CodeOut).'
    for soln in prolog.query(prologCall, maxresult=1):
        statusOut = ''.join(soln["Status"])
        if statusOut == 'valid':
            codeOut = buildMultiCode(soln["CodeOut"])
    return PrologResult(prologCall,statusOut,codeOut)

def buildCode(prologterms):
    """converts a [instruction, param1, param2, ...] list into Python-exec-friendly "instruction(param1,param2,...)" functor format"""
    parameters = [str(item) for item in prologterms]
    if len(parameters) > 2:
        codeOut = parameters[0] + '('
        for x in range(len(parameters)-2):
            codeOut = codeOut + parameters[x+1] + ', '
        codeOut = codeOut + parameters[len(parameters)-1] + ')'
    elif len(parameters) == 2:
        codeOut = parameters[0] + '(' + parameters[1] + ')'
    else:
        codeOut = parameters[0] + '()'
    return codeOut

def buildMultiCode(instructions):
    """converts a list of [instruction, param1, param2] codes into a list of Python-exec-friendly "instruction(param1,param2,...)" strings
       i.e. buildMultiCode([('forward',['1.0','3.0']),('stop',[])]) -> ['forward(1.0, 3.0)', 'stop()']"""
    multiCodeOut = []
    for x in instructions:
        line = buildCode(x)
        multiCodeOut.append(line)
    return multiCodeOut


def execute(code):       # potential security concerns here!
    """executes multiple lines of python script in sequence"""
    for line in code:
        if type(line) is list:
            execute(line)
        else:
            exec line

def toggle_inputMode(mode):
    """toggles between text and voice mode"""
    if mode == "text":
        print "Now activating voice input."
        return "voice"
    else:
        print "Switching back to text-input mode."
        return "text"

def toggle_outputMode(mode):
    """toggles between text and voice mode"""
    if mode == "text":
        print "Now activating voice output."
        return "voice"
    else:
        print "Switching back to text-only output mode."
        return "text"

def toggle_debugMode():
    """toggles between debug mode being on/off"""
    if debug == "on":
        print "Disabling debug mode."
        return "off"
    else:
        print "Enabling debug mode."
        return "on"

# addon robot commands
def takePhoto(mode):
    """Take and display a photo in the specified color mode. 2=color(fast), 1=color, 0=gray"""
    if mode == 2:
        pic = takePicture("jpeg-fast")
    elif mode == 1:
        pic = takePicture("color")
    else:
        pic = takePicture("gray")
    show(pic,"Scribby Cam")
    return

def move_until_wall():
    """Repeatedly moves forward in small increments until wall is sensed"""
    while not wall():
        forward(1.0, 0.7)
    return

def moonwalk(time):
    """Moonwalk backwards for time"""
    while timeRemaining(time):
        forward(.25,.1)                         
        backward(1,.3)                          

# MAIN 
def main():
    # global variable references
    global debug
    global prolog
    global inputMode
    global outputMode
    
    # initialize the robot
    print "***************** ScribPro v" + verNum + " *****************"
    init(scribblerPort)                           
    print "Scribbler II found on bluetooth (" + scribblerPort + ")."
    
    # instantiation of Prolog interface
    prolog = Prolog()
    prolog.consult(prologFile)
    print "Prolog has been initialized."
    print "Loaded " + prologFile + " into SWI-Prolog!"

    print "Starting in " + inputMode + " input mode. ['voice' to toggle]"
    print "Starting in " + outputMode + " output mode. ['sound' to toggle]"
    print "*************************************************"
    
    # main routine
    while(1):
        # grab a line of input, either text or voice
        if inputMode == "voice":
            try:
                inStr = pyspeech.input("nAwaiting voice command. (Ctrl-C to enter text)")
                print "Voice command: " + inStr + "n"
            except KeyboardInterrupt:
                inStr = raw_input("Type in a command: ")
        else:
            inStr = raw_input("nEnter a command: ")

        # parse the input and behave appropriately
        if (inStr == 'quit' or inStr == 'exit'):
            print "Exiting..."
            exit(0)
        elif inStr == 'reload':
            newprolog = Prolog()
            newprolog.consult(prologFile)
            prolog = newprolog
            print "Reloaded " + prologFile + " into Prolog!"
        elif inStr == 'voice':
            inputMode = toggle_inputMode(inputMode)
        elif inStr == 'sound':
            outputMode = toggle_outputMode(outputMode)
        elif inStr == 'debug':
            debug = toggle_debugMode()
        else:
            Result = parse(inStr)
            if Result.status == 'valid':
                if outputMode == "voice":
                    pyspeech.say("Okay")
                print "Okay."
                execute(Result.pythoncode)
            else:
                if outputMode == "voice":
                    errorMsg = "I don't understand " + inStr
                    pyspeech.say(errorMsg)
                print "I don't understand "" + inStr + ""."
                    
            # Print detailed debug information
            if debug == "on":   
                print "nProlog query executed:", Result.prologcall
                print "Status:", Result.status
                print "CodeOut:", Result.pythoncode

if __name__ == "__main__":
    main()
About the Author: Justin
A 34 year old Software Engineer in Seattle, WA with a love for coding, music, video games, and the great outdoors.
Author Website: http://www.justinmangue.com

Leave a Reply

Your email address will not be published. Required fields are marked *