diff options
| -rw-r--r-- | .travis.yml | 1 | ||||
| -rwxr-xr-x | boot/boot.py | 91 | ||||
| -rwxr-xr-x | boot/boot.sh | 2 | ||||
| -rwxr-xr-x | boot/test.py | 54 | ||||
| -rw-r--r-- | client/__init__.py | 0 | ||||
| -rwxr-xr-x | client/diagnose.py | 108 | ||||
| -rw-r--r-- | client/g2p.py | 17 | ||||
| -rwxr-xr-x | client/main.py | 68 | ||||
| -rwxr-xr-x | client/start.sh | 2 | ||||
| -rw-r--r-- | client/test.py | 40 | ||||
| -rw-r--r-- | client/vocabcompiler.py (renamed from boot/vocabcompiler.py) | 4 | ||||
| -rwxr-xr-x | jasper.py | 121 |
12 files changed, 287 insertions, 221 deletions
diff --git a/.travis.yml b/.travis.yml index 95079e7..09e0aec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,3 @@ env: script: - "pip install -r client/requirements.txt" - "cd client && python test.py --light" - - "cd ../boot && python test.py" diff --git a/boot/boot.py b/boot/boot.py index 5291ea2..6ec7137 100755 --- a/boot/boot.py +++ b/boot/boot.py @@ -1,88 +1,11 @@ #!/usr/bin/env python2 # -*- coding: utf-8-*- - +# This file exists for backwards compatibility with older versions of jasper. +# It might be removed in future versions. import os import sys - -# Set $JASPER_HOME -jasper_home = os.getenv("JASPER_HOME") -if not jasper_home or not os.path.exists(jasper_home): - if os.path.exists("/home/pi"): - jasper_home = "/home/pi" - os.environ["JASPER_HOME"] = jasper_home - else: - print("Error: $JASPER_HOME is not set.") - sys.exit(0) - -# Change CWD to $JASPER_HOME/jasper/boot -os.chdir(os.path.join(os.getenv("JASPER_HOME"), "jasper", "boot")) - -# Set $LD_LIBRARY_PATH -os.environ["LD_LIBRARY_PATH"] = "/usr/local/lib" - -# Set $PATH -path = os.getenv("PATH") -if path: - path = os.pathsep.join([path, "/usr/local/lib/"]) -else: - path = "/usr/local/lib/" -os.environ["PATH"] = path - -import urllib2 -import vocabcompiler -import traceback - -lib_path = os.path.abspath('../client') -sys.path.append(lib_path) - -import speaker as speak -speaker = speak.newSpeaker() - - -def testConnection(): - try: - urllib2.urlopen("http://www.google.com").getcode() - print "CONNECTED TO INTERNET" - - except urllib2.URLError: - print "COULD NOT CONNECT TO NETWORK" - speaker.say( - "Warning: I was unable to connect to a network. Parts of the system may not work correctly, depending on your setup.") - - -def fail(message): - traceback.print_exc() - speaker.say(message) - - -def configure(): - try: - print "COMPILING DICTIONARY" - vocabcompiler.compile( - "../client/sentences.txt", "../client/dictionary.dic", "../client/languagemodel.lm") - print "STARTING CLIENT PROGRAM" - os.system("$JASPER_HOME/jasper/client/start.sh &") - - except OSError: - print "BOOT FAILURE: OSERROR" - fail( - "There was a problem starting Jasper. You may be missing the language model and associated files. Please read the documentation to configure your Raspberry Pi.") - - except IOError: - print "BOOT FAILURE: IOERROR" - fail( - "There was a problem starting Jasper. You may have set permissions incorrectly on some part of the filesystem. Please read the documentation to configure your Raspberry Pi.") - - except: - print "BOOT FAILURE" - fail( - "There was a problem starting Jasper. Please read the documentation to configure your Raspberry Pi.") - -if __name__ == "__main__": - print "==========STARTING JASPER CLIENT==========" - print "==========================================" - print "COPYRIGHT 2013 SHUBHRO SAHA, CHARLIE MARSH" - print "==========================================" - speaker.say("Hello.... I am Jasper... Please wait one moment.") - testConnection() - configure() +import runpy +script_path = os.path.join(os.path.dirname(__file__), os.pardir, "jasper.py") +sys.path.remove(os.path.dirname(__file__)) +sys.path.insert(0, os.path.dirname(script_path)) +runpy.run_path(script_path, run_name="__main__") diff --git a/boot/boot.sh b/boot/boot.sh index 9fc8af8..240a7f7 100755 --- a/boot/boot.sh +++ b/boot/boot.sh @@ -1,4 +1,4 @@ #!/bin/bash # This file exists for backwards compatibility with older versions of Jasper. # It might be removed in future versions. -"${0%/*}/boot.py" +"${0%/*}/../jasper.py" diff --git a/boot/test.py b/boot/test.py deleted file mode 100755 index 638ca77..0000000 --- a/boot/test.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8-*- -import os - -if os.environ.get('JASPER_HOME') is None: - os.environ['JASPER_HOME'] = '/home/pi' - -import sys -import unittest -from mock import patch -import vocabcompiler - -lib_path = os.path.abspath('../client') -mod_path = os.path.abspath('../client/modules/') - -sys.path.append(lib_path) -sys.path.append(mod_path) - -import g2p - - -class UnorderedList(list): - - def __eq__(self, other): - return sorted(self) == sorted(other) - - -class TestVocabCompiler(unittest.TestCase): - - def testWordExtraction(self): - sentences = "temp_sentences.txt" - dictionary = "temp_dictionary.dic" - languagemodel = "temp_languagemodel.lm" - - words = [ - 'HACKER', 'LIFE', 'FACEBOOK', 'THIRD', 'NO', 'JOKE', - 'NOTIFICATION', 'MEANING', 'TIME', 'TODAY', 'SECOND', - 'BIRTHDAY', 'KNOCK KNOCK', 'INBOX', 'OF', 'NEWS', 'YES', - 'TOMORROW', 'EMAIL', 'WEATHER', 'FIRST', 'MUSIC', 'SPOTIFY' - ] - - with patch.object(g2p, 'translateWords') as translateWords: - with patch.object(vocabcompiler, 'text2lm') as text2lm: - vocabcompiler.compile(sentences, dictionary, languagemodel) - - # 'words' is appended with ['MUSIC', 'SPOTIFY'] - # so must be > 2 to have received WORDS from modules - translateWords.assert_called_once_with(UnorderedList(words)) - self.assertTrue(text2lm.called) - os.remove(sentences) - os.remove(dictionary) - -if __name__ == '__main__': - unittest.main() diff --git a/client/__init__.py b/client/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/client/__init__.py diff --git a/client/diagnose.py b/client/diagnose.py new file mode 100755 index 0000000..415eb6e --- /dev/null +++ b/client/diagnose.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python2 +import time +import re +import socket +import os +from subprocess import check_output, call + + +class Diagnostics: + + """ + Set of diagnostics to be run for determining the health of the + host running Jasper + + To add new checks, add a boolean returning method with a name that starts + with `check_` + """ + @classmethod + def jasper_modules_path(cls): + return os.path.abspath('../../') + + @classmethod + def check_network_connection(cls): + try: + # see if we can resolve the host name -- tells us if there is + # a DNS listening + host = socket.gethostbyname("www.google.com") + # connect to the host -- tells us if the host is actually + # reachable + socket.create_connection((host, 80), 2) + except Exception: + return False + else: + return True + + @classmethod + def check_phonetisaurus_dictionary_file(cls): + return os.path.isfile(os.path.join(cls.jasper_modules_path(), "phonetisaurus/g014b2b.fst")) + + @classmethod + def check_phonetisaurus_program(cls): + return call(['which', 'phonetisaurus-g2p']) == 0 + + @classmethod + def info_git_revision(cls): + return check_output(['git', 'rev-parse', 'HEAD']) + + +class DiagnosticRunner: + + """ + Performs a series of checks against the system, printing the results to the + console and also saving them to diagnostic.log + """ + + def __init__(self, diagnostics): + self.diagnostics = diagnostics + + def run(self): + self.initialize_log() + self.perform_checks() + + def perform_checks(self): + self.failed_checks = 0 + for check in self.select_methods('check'): + self.do_check(check) + for info in self.select_methods('info'): + self.get_info(info) + if self.failed_checks == 0: + self.log("All checks passed\n") + else: + self.log("%d checks failed\n" % self.failed_checks) + + def select_methods(self, prefix): + def is_match(method_name): + return callable(getattr(self.diagnostics, method_name)) and re.match(r"\A" + prefix + "_", method_name) + + return [method_name for method_name in dir(self.diagnostics) if is_match(method_name)] + + def initialize_log(self): + self.output = open('diagnostic.log', 'w') + self.log("Starting jasper diagnostic\n") + self.log(time.strftime("%c") + "\n") + + def log(self, msg): + print msg, + self.output.write(msg) + + def get_info(self, info_name): + message = info_name.replace("info_", "").replace("_", " ") + info_method = getattr(self.diagnostics, info_name) + info = info_method() + self.log("%s: %s" % (message, info)) + + def do_check(self, check_name): + message = check_name.replace("check_", "").replace("_", " ") + check = getattr(self.diagnostics, check_name) + self.log("Checking %s... " % message) + if check(): + self.log("OK") + else: + self.failed_checks += 1 + self.log("FAILED") + self.log("\n") + + +if __name__ == '__main__': + DiagnosticRunner(Diagnostics).run() diff --git a/client/g2p.py b/client/g2p.py index d22445a..3225c73 100644 --- a/client/g2p.py +++ b/client/g2p.py @@ -1,9 +1,9 @@ # -*- coding: utf-8-*- import os +import tempfile import subprocess import re -TEMP_FILENAME = "g2ptemp" PHONE_MATCH = re.compile(r'<s> (.*) </s>') PHONETISAURUS_PATH = os.environ['JASPER_HOME'] + "/phonetisaurus" @@ -25,12 +25,12 @@ def translateWord(word): def translateWords(words): full_text = '\n'.join(words) - f = open(TEMP_FILENAME, "wb") - f.write(full_text) - f.flush() + with tempfile.NamedTemporaryFile(suffix='.g2p', delete=False) as f: + temp_filename = f.name + f.write(full_text) - output = translateFile(TEMP_FILENAME) - os.remove(TEMP_FILENAME) + output = translateFile(temp_filename) + os.remove(temp_filename) return output @@ -43,9 +43,8 @@ def translateFile(input_filename, output_filename=None): if output_filename: out = '\n'.join(out) - f = open(output_filename, "wb") - f.write(out) - f.close() + with open(output_filename, "wb") as f: + f.write(out) return None diff --git a/client/main.py b/client/main.py index 7167c95..6ec7137 100755 --- a/client/main.py +++ b/client/main.py @@ -1,65 +1,11 @@ #!/usr/bin/env python2 # -*- coding: utf-8-*- - +# This file exists for backwards compatibility with older versions of jasper. +# It might be removed in future versions. import os import sys -import shutil - -# Change CWD to $JASPER_HOME/jasper/client -jasper_home = os.getenv("JASPER_HOME") -if not jasper_home or not os.path.exists(jasper_home): - print("Error: $JASPER_HOME is not set.") - sys.exit(0) - -os.chdir(os.path.join(jasper_home, "jasper", "client")) - -old_client = os.path.abspath(os.path.join(os.pardir, "old_client")) -if os.path.exists(old_client): - shutil.rmtree(old_client) - -import yaml -import sys -import speaker -import stt -from conversation import Conversation - - -def isLocal(): - return len(sys.argv) > 1 and sys.argv[1] == "--local" - -if isLocal(): - from local_mic import Mic -else: - from mic import Mic - -if __name__ == "__main__": - - print "===========================================================" - print " JASPER The Talking Computer " - print " Copyright 2013 Shubhro Saha & Charlie Marsh " - print "===========================================================" - - profile = yaml.safe_load(open("profile.yml", "r")) - - try: - api_key = profile['keys']['GOOGLE_SPEECH'] - except KeyError: - api_key = None - - try: - stt_engine_type = profile['stt_engine'] - except KeyError: - print "stt_engine not specified in profile, defaulting to PocketSphinx" - stt_engine_type = "sphinx" - - mic = Mic(speaker.newSpeaker(), stt.PocketSphinxSTT(), - stt.newSTTEngine(stt_engine_type, api_key=api_key)) - - addendum = "" - if 'first_name' in profile: - addendum = ", %s" % profile["first_name"] - mic.say("How can I be of service%s?" % addendum) - - conversation = Conversation("JASPER", mic, profile) - - conversation.handleForever() +import runpy +script_path = os.path.join(os.path.dirname(__file__), os.pardir, "jasper.py") +sys.path.remove(os.path.dirname(__file__)) +sys.path.insert(0, os.path.dirname(script_path)) +runpy.run_path(script_path, run_name="__main__") diff --git a/client/start.sh b/client/start.sh index cde8d8d..240a7f7 100755 --- a/client/start.sh +++ b/client/start.sh @@ -1,4 +1,4 @@ #!/bin/bash # This file exists for backwards compatibility with older versions of Jasper. # It might be removed in future versions. -"${0%/*}/main.py" +"${0%/*}/../jasper.py" diff --git a/client/test.py b/client/test.py index a8f12be..8925e66 100644 --- a/client/test.py +++ b/client/test.py @@ -11,8 +11,10 @@ import argparse from mock import patch from urllib2 import URLError, urlopen import test_mic +import vocabcompiler import g2p import brain +from diagnose import Diagnostics DEFAULT_PROFILE = { 'prefers_email': False, @@ -21,14 +23,38 @@ DEFAULT_PROFILE = { 'phone_number': '012344321' } - def activeInternet(): - try: - urlopen('http://www.google.com', timeout=1) - return True - except URLError: - return False + return Diagnostics.check_network_connection() + +class UnorderedList(list): + + def __eq__(self, other): + return sorted(self) == sorted(other) + +class TestVocabCompiler(unittest.TestCase): + + def testWordExtraction(self): + sentences = "temp_sentences.txt" + dictionary = "temp_dictionary.dic" + languagemodel = "temp_languagemodel.lm" + + words = [ + 'HACKER', 'LIFE', 'FACEBOOK', 'THIRD', 'NO', 'JOKE', + 'NOTIFICATION', 'MEANING', 'TIME', 'TODAY', 'SECOND', + 'BIRTHDAY', 'KNOCK KNOCK', 'INBOX', 'OF', 'NEWS', 'YES', + 'TOMORROW', 'EMAIL', 'WEATHER', 'FIRST', 'MUSIC', 'SPOTIFY' + ] + + with patch.object(g2p, 'translateWords') as translateWords: + with patch.object(vocabcompiler, 'text2lm') as text2lm: + vocabcompiler.compile(sentences, dictionary, languagemodel) + # 'words' is appended with ['MUSIC', 'SPOTIFY'] + # so must be > 2 to have received WORDS from modules + translateWords.assert_called_once_with(UnorderedList(words)) + self.assertTrue(text2lm.called) + os.remove(sentences) + os.remove(dictionary) class TestMic(unittest.TestCase): @@ -209,7 +235,7 @@ if __name__ == '__main__': help='runs a subset of the tests (only requires Python dependencies)') args = parser.parse_args() - test_cases = [TestBrain, TestModules] + test_cases = [TestBrain, TestModules, TestVocabCompiler] if not args.light: test_cases.append(TestG2P) test_cases.append(TestMic) diff --git a/boot/vocabcompiler.py b/client/vocabcompiler.py index f22da1a..1b4d94c 100644 --- a/boot/vocabcompiler.py +++ b/client/vocabcompiler.py @@ -7,10 +7,8 @@ import os import sys import glob -lib_path = os.path.abspath('../client') -mod_path = os.path.abspath('../client/modules/') +mod_path = os.path.abspath('modules/') -sys.path.append(lib_path) sys.path.append(mod_path) import g2p diff --git a/jasper.py b/jasper.py new file mode 100755 index 0000000..cd51960 --- /dev/null +++ b/jasper.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python2 +import os +import sys +import traceback +import shutil +import yaml + +# Set $JASPER_HOME +jasper_home = os.getenv("JASPER_HOME") +if not jasper_home or not os.path.exists(jasper_home): + if os.path.exists("/home/pi"): + jasper_home = "/home/pi" + os.environ["JASPER_HOME"] = jasper_home + else: + print("Error: $JASPER_HOME is not set.") + sys.exit(0) + +from client.diagnose import Diagnostics +from client import vocabcompiler, stt +from client import speaker as speak +from client.conversation import Conversation +if len(sys.argv) > 1 and "--local" in sys.argv[1:]: + from client.local_mic import Mic +else: + from client.mic import Mic + +# Change CWD to $JASPER_HOME/jasper/client +client_path = os.path.join(os.getenv("JASPER_HOME"), "jasper", "client") +os.chdir(client_path) +# Add $JASPER_HOME/jasper/client to sys.path +sys.path.append(client_path) + +# Set $LD_LIBRARY_PATH +os.environ["LD_LIBRARY_PATH"] = "/usr/local/lib" + +# Set $PATH +path = os.getenv("PATH") +if path: + path = os.pathsep.join([path, "/usr/local/lib/"]) +else: + path = "/usr/local/lib/" +os.environ["PATH"] = path + +speaker = speak.newSpeaker() + + +def testConnection(): + if Diagnostics.check_network_connection(): + print "CONNECTED TO INTERNET" + else: + print "COULD NOT CONNECT TO NETWORK" + speaker.say( + "Warning: I was unable to connect to a network. Parts of the system may not work correctly, depending on your setup.") + + +def fail(message): + traceback.print_exc() + speaker.say(message) + sys.exit(1) + + +def configure(): + try: + print "COMPILING DICTIONARY" + vocabcompiler.compile( + "sentences.txt", "dictionary.dic", "languagemodel.lm") + print "STARTING CLIENT PROGRAM" + + except OSError: + print "BOOT FAILURE: OSERROR" + fail( + "There was a problem starting Jasper. You may be missing the language model and associated files. Please read the documentation to configure your Raspberry Pi.") + + except IOError: + print "BOOT FAILURE: IOERROR" + fail( + "There was a problem starting Jasper. You may have set permissions incorrectly on some part of the filesystem. Please read the documentation to configure your Raspberry Pi.") + + except: + print "BOOT FAILURE" + fail( + "There was a problem starting Jasper. Please read the documentation to configure your Raspberry Pi.") + +old_client = os.path.abspath(os.path.join(os.pardir, "old_client")) +if os.path.exists(old_client): + shutil.rmtree(old_client) + +if __name__ == "__main__": + + print "===========================================================" + print " JASPER The Talking Computer " + print " Copyright 2013 Shubhro Saha & Charlie Marsh " + print "===========================================================" + + speaker.say("Hello.... I am Jasper... Please wait one moment.") + testConnection() + configure() + + profile = yaml.safe_load(open("profile.yml", "r")) + + try: + api_key = profile['keys']['GOOGLE_SPEECH'] + except KeyError: + api_key = None + + try: + stt_engine_type = profile['stt_engine'] + except KeyError: + print "stt_engine not specified in profile, defaulting to PocketSphinx" + stt_engine_type = "sphinx" + + mic = Mic(speaker, stt.PocketSphinxSTT(), + stt.newSTTEngine(stt_engine_type, api_key=api_key)) + + addendum = "" + if 'first_name' in profile: + addendum = ", %s" % profile["first_name"] + mic.say("How can I be of service%s?" % addendum) + + conversation = Conversation("JASPER", mic, profile) + conversation.handleForever() |
