Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions jasper/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from . import conversation
from . import mic
from . import local_mic
from . import restapi


class Jasper(object):
Expand Down Expand Up @@ -221,6 +222,10 @@ def __init__(self, use_local_mic=False):
self.conversation = conversation.Conversation(
self.mic, self.brain, self.config)

# Initialize RESTful API
self.restapi = restapi.RestAPI(self.config, self.mic,
self.conversation)

def list_plugins(self):
plugins = self.plugins.get_plugins()
len_name = max(len(info.name) for info in plugins)
Expand Down
66 changes: 47 additions & 19 deletions jasper/conversation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import logging
from time import sleep
from . import paths
from . import i18n
# from notifier import Notifier
Expand All @@ -16,6 +17,7 @@ def __init__(self, mic, brain, profile):
self.translations = {

}
self.suspended = False
# self.notifier = Notifier(profile)

def greet(self):
Expand All @@ -26,6 +28,28 @@ def greet(self):
salutation = self.gettext("How can I be of service?")
self.mic.say(salutation)

def handleInput(self, input):
if input:
plugin, text = self.brain.query(input)
if plugin and text:
try:
plugin.handle(text, self.mic)
except Exception:
self._logger.error('Failed to execute module',
exc_info=True)
self.mic.say(self.gettext(
"I'm sorry. I had some trouble with that " +
"operation. Please try again later."))
else:
self._logger.debug("Handling of phrase '%s' by " +
"module '%s' completed", text,
plugin.info.name)
return True
else:
self.mic.say(self.gettext("Pardon?"))

return False

def handleForever(self):
"""
Delegates user input to the handling function when activated.
Expand All @@ -37,22 +61,26 @@ def handleForever(self):
for notif in notifications:
self._logger.info("Received notification: '%s'", str(notif))"""

input = self.mic.listen()

if input:
plugin, text = self.brain.query(input)
if plugin and text:
try:
plugin.handle(text, self.mic)
except Exception:
self._logger.error('Failed to execute module',
exc_info=True)
self.mic.say(self.gettext(
"I'm sorry. I had some trouble with that " +
"operation. Please try again later."))
else:
self._logger.debug("Handling of phrase '%s' by " +
"module '%s' completed", text,
plugin.info.name)
else:
self.mic.say(self.gettext("Pardon?"))
if not self.suspended:
input = self.mic.listen()

if not self.suspended:
self.handleInput(input)

if self.suspended:
sleep(0.25)

def suspend(self):
"""
Suspends converstation handling
"""
self._logger.debug('Suspending handling conversation.')
self.suspended = True
self.mic.cancel_listen()

def resume(self):
"""
Resumes converstation handling
"""
self._logger.debug('Resuming handling conversation.')
self.suspended = False
20 changes: 14 additions & 6 deletions jasper/mic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from . import alteration
from . import paths
import logging
import tempfile
import wave
Expand All @@ -13,9 +15,6 @@
else:
import queue

from . import alteration
from . import paths


def get_config_value(config, name, default):
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -74,6 +73,7 @@ def __init__(self, input_device, output_device,
'yes' if self._output_padding else 'no')

self._threshold = 2.0**self._input_bits
self.cancelListen = False

@contextlib.contextmanager
def special_mode(self, name, phrases):
Expand Down Expand Up @@ -150,7 +150,7 @@ def wait_for_keyword(self, keyword=None):
self._input_rate):
if keyword_uttered.is_set():
self._logger.info("Keyword %s has been uttered", keyword)
return
return True
frames.append(frame)
if not recording:
snr = self._snr([frame])
Expand Down Expand Up @@ -185,10 +185,18 @@ def wait_for_keyword(self, keyword=None):
frame_queue.put(tuple(recording_frames))
self._threshold = float(
audioop.rms(b"".join(frames), 2))
if self.cancelListen:
return False

def listen(self):
self.wait_for_keyword(self._keyword)
return self.active_listen()
self.cancelListen = False
if self.wait_for_keyword(self._keyword):
return self.active_listen()
else:
return False

def cancel_listen(self):
self.cancelListen = True

def active_listen(self, timeout=3):
# record until <timeout> second of silence or double <timeout>.
Expand Down
5 changes: 2 additions & 3 deletions jasper/pluginstore.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from . import i18n
from . import plugin
import os
import logging
import imp
Expand All @@ -9,9 +11,6 @@
else:
import configparser

from . import i18n
from . import plugin


MANDATORY_OPTIONS = (
('Plugin', 'Name'),
Expand Down
125 changes: 125 additions & 0 deletions jasper/restapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
from flask import Flask, jsonify, request, Response, abort
import threading
from functools import wraps


class RestAPI(object):

def __init__(self, profile, mic, conversation):
self.profile = profile
self.mic = mic
self.conversation = conversation

try:
host = self.profile['restapi']['Host']
except KeyError:
host = '127.0.0.1'

try:
port = self.profile['restapi']['Port']
except KeyError:
port = 5000

try:
password = self.profile['restapi']['Password']
except KeyError:
password = None

# create thread for http listener
t = threading.Thread(target=self.startRestAPI,
args=(host, port, password))
t.daemon = True
t.start()

def startRestAPI(self, host, port, password):
app = Flask(__name__)

def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if password and (not auth or auth.password != password):
return Response(
'Authorization required.', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
return f(*args, **kwargs)
return decorated

@app.route('/')
def index():
return "Jasper restAPI: running"

@app.route('/jasper/say', methods=['POST'])
@requires_auth
def say_task():
if not request.json or 'text' not in request.json:
abort(400)
text = request.json['text']

self.conversation.suspend()
self.mic.say(text)
self.conversation.resume()

return jsonify({'say': text}), 201

@app.route('/jasper/transcribe', methods=['GET'])
@requires_auth
def transcribe_task():
self.conversation.suspend()
transcribed = self.mic.active_listen()
self.conversation.resume()

return jsonify({'transcribed': transcribed}), 201

@app.route('/jasper/activate', methods=['GET'])
@requires_auth
def activate_task():
self.conversation.suspend()
transcribed = self.mic.active_listen()
result = self.conversation.handleInput(transcribed)
self.conversation.resume()

return jsonify({'transcribed': transcribed, 'result': result}), 201

@app.route('/jasper/handleinput', methods=['POST'])
@requires_auth
def handleinput_task():
if not request.json or 'text' not in request.json:
abort(400)
text = request.json['text']

self.conversation.suspend()
result = self.conversation.handleInput([text])
self.conversation.resume()

return jsonify({'text': text, 'result': result}), 201

@app.route('/jasper/waitforkeyword', methods=['POST'])
@requires_auth
def waitforkeyword_task():
if not request.json or 'keyword' not in request.json:
abort(400)
keyword = request.json['keyword']

self.conversation.suspend()
self.mic.wait_for_keyword(keyword)
self.conversation.resume()

return jsonify({'keyword': keyword}), 201

@app.route('/jasper/playfile', methods=['POST'])
@requires_auth
def playfile_task():
if not request.json or 'filename' not in request.json:
abort(400)
filename = request.json['filename']

self.conversation.suspend()
self.mic.play_file(filename)
self.conversation.resume()

return jsonify({'filename': filename}), 201

# start http listener
app.run(host=host, port=port, debug=False)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ python-slugify==0.1.0
pytz==2014.10
PyYAML==3.11
requests==2.5.0
Flask==0.10.1

# Pocketsphinx STT engine
cmuclmtk==0.1.5
Expand Down