# 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()