Issues-4 - Fix pep8 errors.

pull/16/head
Leo Arias 2016-05-20 22:15:53 +00:00
parent 9aa9f47f72
commit d618676089
69 changed files with 852 additions and 424 deletions

View File

@ -12,4 +12,6 @@ install:
- pip install -r requirements.txt - pip install -r requirements.txt
- pip install -r test-requirements.txt - pip install -r test-requirements.txt
# command to run tests # command to run tests
script: "PYTHONPATH=. python test/test_runner.py --fail-on-error" script:
- pep8 mycroft test
- "PYTHONPATH=. python test/test_runner.py --fail-on-error"

View File

@ -50,4 +50,4 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,6 +1,11 @@
from setuptools import setup from setuptools import setup
from mycroft.util.setup_base import find_all_packages, required, get_version, place_manifest from mycroft.util.setup_base import (
find_all_packages,
required,
get_version,
place_manifest
)
__author__ = 'seanfitz' __author__ = 'seanfitz'

View File

@ -10,7 +10,8 @@ class EnclosureAPI:
""" """
This API is intended to be used to control Mycroft hardware capabilities. This API is intended to be used to control Mycroft hardware capabilities.
It exposes all possible enclosure commands to be performed by a Mycroft unit. It exposes all possible enclosure commands to be performed by a Mycroft
unit.
""" """
def __init__(self, client): def __init__(self, client):
@ -23,7 +24,8 @@ class EnclosureAPI:
self.client.emit(Message("enclosure.system.unmute")) self.client.emit(Message("enclosure.system.unmute"))
def system_blink(self, times): def system_blink(self, times):
self.client.emit(Message("enclosure.system.blink", metadata={'times': times})) self.client.emit(
Message("enclosure.system.blink", metadata={'times': times}))
def eyes_on(self): def eyes_on(self):
self.client.emit(Message("enclosure.eyes.on")) self.client.emit(Message("enclosure.eyes.on"))
@ -32,19 +34,23 @@ class EnclosureAPI:
self.client.emit(Message("enclosure.eyes.off")) self.client.emit(Message("enclosure.eyes.off"))
def eyes_blink(self, side): def eyes_blink(self, side):
self.client.emit(Message("enclosure.eyes.blink", metadata={'side': side})) self.client.emit(
Message("enclosure.eyes.blink", metadata={'side': side}))
def eyes_narrow(self): def eyes_narrow(self):
self.client.emit(Message("enclosure.eyes.narrow")) self.client.emit(Message("enclosure.eyes.narrow"))
def eyes_look(self, side): def eyes_look(self, side):
self.client.emit(Message("enclosure.eyes.look", metadata={'side': side})) self.client.emit(
Message("enclosure.eyes.look", metadata={'side': side}))
def eyes_color(self, r=255, g=255, b=255): def eyes_color(self, r=255, g=255, b=255):
self.client.emit(Message("enclosure.eyes.color", metadata={'r': r, 'g': g, 'b': b})) self.client.emit(
Message("enclosure.eyes.color", metadata={'r': r, 'g': g, 'b': b}))
def eyes_brightness(self, level=30): def eyes_brightness(self, level=30):
self.client.emit(Message("enclosure.eyes.level", metadata={'level': level})) self.client.emit(
Message("enclosure.eyes.level", metadata={'level': level}))
def mouth_reset(self): def mouth_reset(self):
self.client.emit(Message("enclosure.mouth.reset")) self.client.emit(Message("enclosure.mouth.reset"))
@ -62,4 +68,5 @@ class EnclosureAPI:
self.client.emit(Message("enclosure.mouth.smile")) self.client.emit(Message("enclosure.mouth.smile"))
def mouth_text(self, text=""): def mouth_text(self, text=""):
self.client.emit(Message("enclosure.mouth.text", metadata={'text': text})) self.client.emit(
Message("enclosure.mouth.text", metadata={'text': text}))

View File

@ -21,7 +21,8 @@ class EnclosureReader(Thread):
""" """
Reads data from Serial port. Reads data from Serial port.
Listens to all commands sent by Arduino that must be be performed on Mycroft Core. Listens to all commands sent by Arduino that must be be performed on
Mycroft Core.
E.g. Mycroft Stop Feature E.g. Mycroft Stop Feature
#. Arduino sends a Stop command after a button press on a Mycroft unit #. Arduino sends a Stop command after a button press on a Mycroft unit
@ -62,7 +63,8 @@ class EnclosureReader(Thread):
class EnclosureWriter(Thread): class EnclosureWriter(Thread):
""" """
Writes data to Serial port. Writes data to Serial port.
#. Enqueues all commands received from Mycroft enclosures implementation #. Enqueues all commands received from Mycroft enclosures
implementation
#. Process them on the received order by writing on the Serial port #. Process them on the received order by writing on the Serial port
E.g. Displaying a text on Mycroft's Mouth E.g. Displaying a text on Mycroft's Mouth
@ -109,7 +111,8 @@ class Enclosure:
E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino``
It also listens to the basis events in order to perform those core actions on the unit. It also listens to the basis events in order to perform those core actions
on the unit.
E.g. Start and Stop talk animation E.g. Start and Stop talk animation
""" """
@ -130,10 +133,14 @@ class Enclosure:
self.port = self.config.get("port") self.port = self.config.get("port")
self.rate = int(self.config.get("rate")) self.rate = int(self.config.get("rate"))
self.timeout = int(self.config.get("timeout")) self.timeout = int(self.config.get("timeout"))
self.serial = serial.serial_for_url(url=self.port, baudrate=self.rate, timeout=self.timeout) self.serial = serial.serial_for_url(
LOGGER.info("Connected to: " + self.port + " rate: " + str(self.rate) + " timeout: " + str(self.timeout)) url=self.port, baudrate=self.rate, timeout=self.timeout)
LOGGER.info(
"Connected to: " + self.port + " rate: " + str(self.rate) +
" timeout: " + str(self.timeout))
except: except:
LOGGER.error("It is not possible to connect to serial port: " + self.port) LOGGER.error(
"It is not possible to connect to serial port: " + self.port)
raise raise
def __init_events(self): def __init_events(self):

View File

@ -9,7 +9,9 @@ from speech_recognition import AudioData
from mycroft.client.speech import wakeword_recognizer from mycroft.client.speech import wakeword_recognizer
from mycroft.client.speech.mic import MutableMicrophone, Recognizer from mycroft.client.speech.mic import MutableMicrophone, Recognizer
from mycroft.client.speech.recognizer_wrapper import RemoteRecognizerWrapperFactory from mycroft.client.speech.recognizer_wrapper import (
RemoteRecognizerWrapperFactory
)
from mycroft.configuration.config import ConfigurationManager from mycroft.configuration.config import ConfigurationManager
from mycroft.messagebus.message import Message from mycroft.messagebus.message import Message
from mycroft.metrics import MetricsAggregator, Stopwatch from mycroft.metrics import MetricsAggregator, Stopwatch
@ -26,8 +28,8 @@ speech_config = ConfigurationManager.get_config().get('speech_client')
class AudioProducer(threading.Thread): class AudioProducer(threading.Thread):
""" """
AudioProducer AudioProducer
given a mic and a recognizer implementation, continuously listens to the mic for given a mic and a recognizer implementation, continuously listens to the
potential speech chunks and pushes them onto the queue. mic for potential speech chunks and pushes them onto the queue.
""" """
def __init__(self, state, queue, mic, recognizer, emitter): def __init__(self, state, queue, mic, recognizer, emitter):
threading.Thread.__init__(self) threading.Thread.__init__(self)
@ -47,8 +49,9 @@ class AudioProducer(threading.Thread):
audio = self.recognizer.listen(source) audio = self.recognizer.listen(source)
self.queue.put(audio) self.queue.put(audio)
except IOError, ex: except IOError, ex:
# NOTE: Audio stack on raspi is slightly different, throws IOError every other listen, # NOTE: Audio stack on raspi is slightly different, throws
# almost like it can't handle buffering audio between listen loops. # IOError every other listen, almost like it can't handle
# buffering audio between listen loops.
# The internet was not helpful. # The internet was not helpful.
# http://stackoverflow.com/questions/10733903/pyaudio-input-overflowed # http://stackoverflow.com/questions/10733903/pyaudio-input-overflowed
self.emitter.emit("recognizer_loop:ioerror", ex) self.emitter.emit("recognizer_loop:ioerror", ex)
@ -58,13 +61,18 @@ class WakewordExtractor:
MAX_ERROR_SECONDS = 0.02 MAX_ERROR_SECONDS = 0.02
TRIM_SECONDS = 0.1 TRIM_SECONDS = 0.1
PUSH_BACK_SECONDS = 0.2 # The seconds the safe end position is pushed back to ensure pocketsphinx is consistent # The seconds the safe end position is pushed back to ensure pocketsphinx
SILENCE_SECONDS = 0.2 # The seconds of silence padded where the wakeword was removed # is consistent
PUSH_BACK_SECONDS = 0.2
# The seconds of silence padded where the wakeword was removed
SILENCE_SECONDS = 0.2
def __init__(self, audio_data, recognizer, metrics): def __init__(self, audio_data, recognizer, metrics):
self.audio_data = audio_data self.audio_data = audio_data
self.recognizer = recognizer self.recognizer = recognizer
self.silence_data = self.__generate_silence(self.SILENCE_SECONDS, self.audio_data.sample_rate, self.audio_data.sample_width) self.silence_data = self.__generate_silence(
self.SILENCE_SECONDS, self.audio_data.sample_rate,
self.audio_data.sample_width)
self.wav_data = self.audio_data.get_wav_data() self.wav_data = self.audio_data.get_wav_data()
self.AUDIO_SIZE = float(len(self.wav_data)) self.AUDIO_SIZE = float(len(self.wav_data))
self.range = self.Range(0, self.AUDIO_SIZE / 2) self.range = self.Range(0, self.AUDIO_SIZE / 2)
@ -101,15 +109,18 @@ class WakewordExtractor:
return False return False
def audio_pos(self, raw_pos): def audio_pos(self, raw_pos):
return int(self.audio_data.sample_width * round(float(raw_pos)/self.audio_data.sample_width)) return int(self.audio_data.sample_width *
round(float(raw_pos)/self.audio_data.sample_width))
def get_audio_segment(self, begin, end): def get_audio_segment(self, begin, end):
return self.wav_data[self.audio_pos(begin) : self.audio_pos(end)] return self.wav_data[self.audio_pos(begin): self.audio_pos(end)]
def __calculate_marker(self, use_begin, sign_if_found, range, delta): def __calculate_marker(self, use_begin, sign_if_found, range, delta):
while 2 * delta >= self.MAX_ERROR_SECONDS * self.audio_data.sample_rate * self.audio_data.sample_width: while (2 * delta >= self.MAX_ERROR_SECONDS *
self.audio_data.sample_rate * self.audio_data.sample_width):
byte_data = self.get_audio_segment(range.begin, range.end) byte_data = self.get_audio_segment(range.begin, range.end)
found = self.__found_in_segment("mycroft", byte_data, self.recognizer, self.metrics) found = self.__found_in_segment(
"mycroft", byte_data, self.recognizer, self.metrics)
sign = sign_if_found if found else -sign_if_found sign = sign_if_found if found else -sign_if_found
range.add_to_marker(use_begin, delta * sign) range.add_to_marker(use_begin, delta * sign)
delta /= 2 delta /= 2
@ -117,26 +128,37 @@ class WakewordExtractor:
def calculate_range(self): def calculate_range(self):
delta = self.AUDIO_SIZE / 4 delta = self.AUDIO_SIZE / 4
self.range.end = self.__calculate_marker(False, -1, self.Range(0, self.AUDIO_SIZE / 2), delta) self.range.end = self.__calculate_marker(
False, -1, self.Range(0, self.AUDIO_SIZE / 2), delta)
# Ensures the end position is well past the wakeword part of the audio # Ensures the end position is well past the wakeword part of the audio
pos_end_safe = min(self.AUDIO_SIZE, self.range.end + self.PUSH_BACK_SECONDS * self.audio_data.sample_rate * self.audio_data.sample_width) pos_end_safe = min(
self.AUDIO_SIZE, self.range.end + self.PUSH_BACK_SECONDS *
self.audio_data.sample_rate * self.audio_data.sample_width)
delta = pos_end_safe / 4 delta = pos_end_safe / 4
begin = pos_end_safe / 2 begin = pos_end_safe / 2
self.range.begin = self.__calculate_marker(True, 1, self.Range(begin, pos_end_safe), delta) self.range.begin = self.__calculate_marker(
self.range.narrow(self.TRIM_SECONDS * self.audio_data.sample_rate * self.audio_data.sample_width) True, 1, self.Range(begin, pos_end_safe), delta)
self.range.narrow(self.TRIM_SECONDS * self.audio_data.sample_rate *
self.audio_data.sample_width)
@staticmethod @staticmethod
def __generate_silence(seconds, sample_rate, sample_width): def __generate_silence(seconds, sample_rate, sample_width):
return '\0'*int(seconds * sample_rate * sample_width) return '\0'*int(seconds * sample_rate * sample_width)
def get_audio_data_before(self): def get_audio_data_before(self):
byte_data = self.get_audio_segment(0, self.range.begin) + self.silence_data byte_data = self.get_audio_segment(
return AudioData(byte_data, self.audio_data.sample_rate,self.audio_data.sample_width) 0, self.range.begin) + self.silence_data
return AudioData(
byte_data, self.audio_data.sample_rate,
self.audio_data.sample_width)
def get_audio_data_after(self): def get_audio_data_after(self):
byte_data = self.silence_data + self.get_audio_segment(self.range.end, self.AUDIO_SIZE) byte_data = self.silence_data + self.get_audio_segment(
return AudioData(byte_data, self.audio_data.sample_rate,self.audio_data.sample_width) self.range.end, self.AUDIO_SIZE)
return AudioData(
byte_data, self.audio_data.sample_rate,
self.audio_data.sample_width)
class AudioConsumer(threading.Thread): class AudioConsumer(threading.Thread):
@ -145,10 +167,13 @@ class AudioConsumer(threading.Thread):
Consumes AudioData chunks off the queue Consumes AudioData chunks off the queue
""" """
MIN_AUDIO_SIZE = 1.0 # In seconds, the minimum audio size to be sent to remote STT # In seconds, the minimum audio size to be sent to remote STT
MIN_AUDIO_SIZE = 1.0
def __init__(self, state, queue, emitter, wakeup_recognizer, wakeword_recognizer, def __init__(
wrapped_remote_recognizer, wakeup_prefixes, wakeup_words): self, state, queue, emitter, wakeup_recognizer,
wakeword_recognizer, wrapped_remote_recognizer, wakeup_prefixes,
wakeup_words):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.daemon = True self.daemon = True
self.queue = queue self.queue = queue
@ -167,17 +192,20 @@ class AudioConsumer(threading.Thread):
@staticmethod @staticmethod
def _audio_length(audio): def _audio_length(audio):
return float(len(audio.frame_data))/(audio.sample_rate*audio.sample_width) return float(
len(audio.frame_data))/(audio.sample_rate*audio.sample_width)
def try_consume_audio(self): def try_consume_audio(self):
timer = Stopwatch() timer = Stopwatch()
hyp = None hyp = None
audio = self.queue.get() audio = self.queue.get()
self.metrics.timer("mycroft.recognizer.audio.length_s", self._audio_length(audio)) self.metrics.timer(
"mycroft.recognizer.audio.length_s", self._audio_length(audio))
self.queue.task_done() self.queue.task_done()
timer.start() timer.start()
if self.state.sleeping: if self.state.sleeping:
hyp = self.wakeup_recognizer.transcribe(audio.get_wav_data(), metrics=self.metrics) hyp = self.wakeup_recognizer.transcribe(
audio.get_wav_data(), metrics=self.metrics)
if hyp and hyp.hypstr: if hyp and hyp.hypstr:
logger.debug("sleeping recognition: " + hyp.hypstr) logger.debug("sleeping recognition: " + hyp.hypstr)
if hyp and hyp.hypstr.lower().find("wake up") >= 0: if hyp and hyp.hypstr.lower().find("wake up") >= 0:
@ -187,17 +215,24 @@ class AudioConsumer(threading.Thread):
self.metrics.increment("mycroft.wakeup") self.metrics.increment("mycroft.wakeup")
else: else:
if not self.state.skip_wakeword: if not self.state.skip_wakeword:
hyp = self.ww_recognizer.transcribe(audio.get_wav_data(), metrics=self.metrics) hyp = self.ww_recognizer.transcribe(
audio.get_wav_data(), metrics=self.metrics)
if hyp and hyp.hypstr.lower().find("mycroft") >= 0: if hyp and hyp.hypstr.lower().find("mycroft") >= 0:
extractor = WakewordExtractor(audio, self.ww_recognizer, self.metrics) extractor = WakewordExtractor(
audio, self.ww_recognizer, self.metrics)
timer.lap() timer.lap()
extractor.calculate_range() extractor.calculate_range()
self.metrics.timer("mycroft.recognizer.extractor.time_s", timer.lap()) self.metrics.timer(
"mycroft.recognizer.extractor.time_s", timer.lap())
audio_before = extractor.get_audio_data_before() audio_before = extractor.get_audio_data_before()
self.metrics.timer("mycroft.recognizer.audio_extracted.length_s", self._audio_length(audio_before)) self.metrics.timer(
"mycroft.recognizer.audio_extracted.length_s",
self._audio_length(audio_before))
audio_after = extractor.get_audio_data_after() audio_after = extractor.get_audio_data_after()
self.metrics.timer("mycroft.recognizer.audio_extracted.length_s", self._audio_length(audio_after)) self.metrics.timer(
"mycroft.recognizer.audio_extracted.length_s",
self._audio_length(audio_after))
SessionManager.touch() SessionManager.touch()
payload = { payload = {
@ -220,7 +255,8 @@ class AudioConsumer(threading.Thread):
try: try:
self.transcribe([audio]) self.transcribe([audio])
except sr.UnknownValueError: except sr.UnknownValueError:
logger.warn("Speech Recognition could not understand audio") logger.warn(
"Speech Recognition could not understand audio")
self.__speak("Sorry, I didn't catch that.") self.__speak("Sorry, I didn't catch that.")
self.metrics.increment("mycroft.recognizer.error") self.metrics.increment("mycroft.recognizer.error")
self.state.skip_wakeword = False self.state.skip_wakeword = False
@ -230,27 +266,36 @@ class AudioConsumer(threading.Thread):
def __speak(self, utterance): def __speak(self, utterance):
""" """
Speak commands should be asynchronous to avoid filling up the portaudio buffer. Speak commands should be asynchronous to avoid filling up the
portaudio buffer.
:param utterance: :param utterance:
:return: :return:
""" """
def target(): def target():
self.emitter.emit("speak", Message("speak", metadata={'utterance': utterance, self.emitter.emit(
'session': SessionManager.get().session_id})) "speak",
Message("speak",
metadata={'utterance': utterance,
'session': SessionManager.get().session_id}))
threading.Thread(target=target).start() threading.Thread(target=target).start()
def _create_remote_stt_runnable(self, audio, utterances): def _create_remote_stt_runnable(self, audio, utterances):
def runnable(): def runnable():
try: try:
text = self.wrapped_remote_recognizer.transcribe(audio, metrics=self.metrics).lower() text = self.wrapped_remote_recognizer.transcribe(
audio, metrics=self.metrics).lower()
except sr.UnknownValueError: except sr.UnknownValueError:
pass pass
except sr.RequestError as e: except sr.RequestError as e:
logger.error("Could not request results from Speech Recognition service; {0}".format(e)) logger.error(
"Could not request results from Speech Recognition "
"service; {0}".format(e))
except CerberusAccessDenied as e: except CerberusAccessDenied as e:
logger.error("AccessDenied from Cerberus proxy.") logger.error("AccessDenied from Cerberus proxy.")
self.__speak("Your device is not registered yet. To start pairing, login at cerberus.mycroft.ai") self.__speak(
"Your device is not registered yet. To start pairing, "
"login at cerberus.mycroft.ai")
utterances.append("pair my device") utterances.append("pair my device")
else: else:
logger.debug("STT: " + text) logger.debug("STT: " + text)
@ -297,15 +342,20 @@ class RecognizerLoop(pyee.EventEmitter):
device_index=None, device_index=None,
lang=core_config.get('lang')): lang=core_config.get('lang')):
pyee.EventEmitter.__init__(self) pyee.EventEmitter.__init__(self)
self.microphone = MutableMicrophone(sample_rate=sample_rate, device_index=device_index) self.microphone = MutableMicrophone(
sample_rate=sample_rate, device_index=device_index)
self.microphone.CHANNELS = channels self.microphone.CHANNELS = channels
self.ww_recognizer = wakeword_recognizer.create_recognizer(samprate=sample_rate, lang=lang) self.ww_recognizer = wakeword_recognizer.create_recognizer(
self.wakeup_recognizer = wakeword_recognizer.create_recognizer(samprate=sample_rate, lang=lang, samprate=sample_rate, lang=lang)
keyphrase="wake up mycroft") # TODO - localization self.wakeup_recognizer = wakeword_recognizer.create_recognizer(
samprate=sample_rate, lang=lang,
keyphrase="wake up mycroft") # TODO - localization
self.remote_recognizer = Recognizer() self.remote_recognizer = Recognizer()
basedir = os.path.dirname(__file__) basedir = os.path.dirname(__file__)
self.wakeup_words = read_stripped_lines(os.path.join(basedir, 'model', lang, 'WakeUpWord.voc')) self.wakeup_words = read_stripped_lines(os.path.join(
self.wakeup_prefixes = read_stripped_lines(os.path.join(basedir, 'model', lang, 'PrefixWakeUp.voc')) basedir, 'model', lang, 'WakeUpWord.voc'))
self.wakeup_prefixes = read_stripped_lines(
os.path.join(basedir, 'model', lang, 'PrefixWakeUp.voc'))
self.state = RecognizerLoopState() self.state = RecognizerLoopState()
def start_async(self): def start_async(self):
@ -321,7 +371,8 @@ class RecognizerLoop(pyee.EventEmitter):
self, self,
self.wakeup_recognizer, self.wakeup_recognizer,
self.ww_recognizer, self.ww_recognizer,
RemoteRecognizerWrapperFactory.wrap_recognizer(self.remote_recognizer), RemoteRecognizerWrapperFactory.wrap_recognizer(
self.remote_recognizer),
self.wakeup_prefixes, self.wakeup_prefixes,
self.wakeup_words).start() self.wakeup_words).start()

View File

@ -16,6 +16,7 @@ loop = None
config = ConfigurationManager.get_config() config = ConfigurationManager.get_config()
def handle_listening(): def handle_listening():
logger.info("Listening...") logger.info("Listening...")
client.emit(Message('recognizer_loop:listening')) client.emit(Message('recognizer_loop:listening'))
@ -79,7 +80,9 @@ def main():
loop.on('recognizer_loop:utterance', handle_utterance) loop.on('recognizer_loop:utterance', handle_utterance)
loop.on('speak', handle_speak) loop.on('speak', handle_speak)
client.on('speak', handle_speak) client.on('speak', handle_speak)
client.on('multi_utterance_intent_failure', handle_multi_utterance_intent_failure) client.on(
'multi_utterance_intent_failure',
handle_multi_utterance_intent_failure)
client.on('recognizer_loop:sleep', handle_sleep) client.on('recognizer_loop:sleep', handle_sleep)
client.on('recognizer_loop:wake_up', handle_wake_up) client.on('recognizer_loop:wake_up', handle_wake_up)
event_thread = Thread(target=connect) event_thread = Thread(target=connect)

View File

@ -4,7 +4,12 @@ import audioop
from time import sleep from time import sleep
import pyaudio import pyaudio
from speech_recognition import Microphone, AudioSource, WaitTimeoutError, AudioData from speech_recognition import (
Microphone,
AudioSource,
WaitTimeoutError,
AudioData
)
import speech_recognition import speech_recognition
from mycroft.util.log import getLogger from mycroft.util.log import getLogger
logger = getLogger(__name__) logger = getLogger(__name__)
@ -13,7 +18,7 @@ __author__ = 'seanfitz'
class MutableStream(object): class MutableStream(object):
def __init__(self, wrapped_stream, format, muted=False): def __init__(self, wrapped_stream, format, muted=False):
assert wrapped_stream != None assert wrapped_stream is not None
self.wrapped_stream = wrapped_stream self.wrapped_stream = wrapped_stream
self.muted = muted self.muted = muted
self.SAMPLE_WIDTH = pyaudio.get_sample_size(format) self.SAMPLE_WIDTH = pyaudio.get_sample_size(format)
@ -57,17 +62,21 @@ class MutableStream(object):
class MutableMicrophone(Microphone): class MutableMicrophone(Microphone):
def __init__(self, device_index = None, sample_rate = 16000, chunk_size = 1024): def __init__(self, device_index=None, sample_rate=16000, chunk_size=1024):
Microphone.__init__(self, device_index=device_index, sample_rate=sample_rate, chunk_size=chunk_size) Microphone.__init__(
self, device_index=device_index, sample_rate=sample_rate,
chunk_size=chunk_size)
self.muted = False self.muted = False
def __enter__(self): def __enter__(self):
assert self.stream is None, "This audio source is already inside a context manager" assert self.stream is None, \
"This audio source is already inside a context manager"
self.audio = pyaudio.PyAudio() self.audio = pyaudio.PyAudio()
self.stream = MutableStream(self.audio.open( self.stream = MutableStream(self.audio.open(
input_device_index = self.device_index, channels = 1, input_device_index=self.device_index, channels=1,
format = self.format, rate = self.SAMPLE_RATE, frames_per_buffer = self.CHUNK, format=self.format, rate=self.SAMPLE_RATE,
input = True, # stream is an input stream frames_per_buffer=self.CHUNK,
input=True, # stream is an input stream
), self.format, self.muted) ), self.format, self.muted)
return self return self
@ -94,45 +103,71 @@ class Recognizer(speech_recognition.Recognizer):
speech_recognition.Recognizer.__init__(self) speech_recognition.Recognizer.__init__(self)
self.max_audio_length_sec = 30 self.max_audio_length_sec = 30
def listen(self, source, timeout = None): def listen(self, source, timeout=None):
""" """
Records a single phrase from ``source`` (an ``AudioSource`` instance) into an ``AudioData`` instance, which it returns. Records a single phrase from ``source`` (an ``AudioSource`` instance)
into an ``AudioData`` instance, which it returns.
This is done by waiting until the audio has an energy above ``recognizer_instance.energy_threshold`` (the user has started speaking), and then recording until it encounters ``recognizer_instance.pause_threshold`` seconds of non-speaking or there is no more audio input. The ending silence is not included. This is done by waiting until the audio has an energy above
``recognizer_instance.energy_threshold`` (the user has started
speaking), and then recording until it encounters
``recognizer_instance.pause_threshold`` seconds of non-speaking or
there is no more audio input. The ending silence is not included.
The ``timeout`` parameter is the maximum number of seconds that it will wait for a phrase to start before giving up and throwing an ``speech_recognition.WaitTimeoutError`` exception. If ``timeout`` is ``None``, it will wait indefinitely. The ``timeout`` parameter is the maximum number of seconds that it
will wait for a phrase to start before giving up and throwing an
``speech_recognition.WaitTimeoutError`` exception. If ``timeout`` is
``None``, it will wait indefinitely.
""" """
assert isinstance(source, AudioSource), "Source must be an audio source" assert isinstance(source, AudioSource), \
"Source must be an audio source"
assert self.pause_threshold >= self.non_speaking_duration >= 0 assert self.pause_threshold >= self.non_speaking_duration >= 0
seconds_per_buffer = (source.CHUNK + 0.0) / source.SAMPLE_RATE seconds_per_buffer = (source.CHUNK + 0.0) / source.SAMPLE_RATE
pause_buffer_count = int(math.ceil(self.pause_threshold / seconds_per_buffer)) # number of buffers of non-speaking audio before the phrase is complete # number of buffers of non-speaking audio before the phrase is
phrase_buffer_count = int(math.ceil(self.phrase_threshold / seconds_per_buffer)) # minimum number of buffers of speaking audio before we consider the speaking audio a phrase # complete
non_speaking_buffer_count = int(math.ceil(self.non_speaking_duration / seconds_per_buffer)) # maximum number of buffers of non-speaking audio to retain before and after pause_buffer_count = int(
math.ceil(self.pause_threshold / seconds_per_buffer))
# minimum number of buffers of speaking audio before we consider the
# speaking audio a phrase
phrase_buffer_count = int(math.ceil(self.phrase_threshold /
seconds_per_buffer))
# maximum number of buffers of non-speaking audio to retain before and
# after
non_speaking_buffer_count = int(math.ceil(self.non_speaking_duration /
seconds_per_buffer))
# read audio input for phrases until there is a phrase that is long enough # read audio input for phrases until there is a phrase that is long
elapsed_time = 0 # number of seconds of audio read # enough
elapsed_time = 0 # number of seconds of audio read
while True: while True:
frames = collections.deque() frames = collections.deque()
# store audio input until the phrase starts # store audio input until the phrase starts
while True: while True:
elapsed_time += seconds_per_buffer elapsed_time += seconds_per_buffer
if timeout and elapsed_time > timeout: # handle timeout if specified # handle timeout if specified
if timeout and elapsed_time > timeout:
raise WaitTimeoutError("listening timed out") raise WaitTimeoutError("listening timed out")
buffer = source.stream.read(source.CHUNK) buffer = source.stream.read(source.CHUNK)
if len(buffer) == 0: break # reached end of the stream if len(buffer) == 0:
break # reached end of the stream
frames.append(buffer) frames.append(buffer)
if len(frames) > non_speaking_buffer_count: # ensure we only keep the needed amount of non-speaking buffers # ensure we only keep the needed amount of non-speaking buffers
if len(frames) > non_speaking_buffer_count:
frames.popleft() frames.popleft()
# detect whether speaking has started on audio input # detect whether speaking has started on audio input
energy = audioop.rms(buffer, source.SAMPLE_WIDTH) # energy of the audio signal # energy of the audio signal
if energy > self.energy_threshold: break energy = audioop.rms(buffer, source.SAMPLE_WIDTH)
if energy > self.energy_threshold:
break
# dynamically adjust the energy threshold using assymmetric weighted average # dynamically adjust the energy threshold using assymmetric
# do not adjust dynamic energy level for this sample if it is muted audio (energy == 0) # weighted average
# do not adjust dynamic energy level for this sample if it is
# muted audio (energy == 0)
self.adjust_energy_threshold(energy, seconds_per_buffer) self.adjust_energy_threshold(energy, seconds_per_buffer)
# read audio input until the phrase ends # read audio input until the phrase ends
pause_count, phrase_count = 0, 0 pause_count, phrase_count = 0, 0
@ -140,38 +175,51 @@ class Recognizer(speech_recognition.Recognizer):
elapsed_time += seconds_per_buffer elapsed_time += seconds_per_buffer
buffer = source.stream.read(source.CHUNK) buffer = source.stream.read(source.CHUNK)
if len(buffer) == 0: break # reached end of the stream if len(buffer) == 0:
break # reached end of the stream
frames.append(buffer) frames.append(buffer)
phrase_count += 1 phrase_count += 1
# check if speaking has stopped for longer than the pause threshold on the audio input # check if speaking has stopped for longer than the pause
energy = audioop.rms(buffer, source.SAMPLE_WIDTH) # energy of the audio signal # threshold on the audio input
# energy of the audio signal
energy = audioop.rms(buffer, source.SAMPLE_WIDTH)
if energy > self.energy_threshold: if energy > self.energy_threshold:
pause_count = 0 pause_count = 0
else: else:
pause_count += 1 pause_count += 1
if pause_count > pause_buffer_count: # end of the phrase if pause_count > pause_buffer_count: # end of the phrase
break break
if len(frames) * seconds_per_buffer >= self.max_audio_length_sec: if (len(frames) * seconds_per_buffer >=
# if we hit the end of the audio length, readjust energy_threshold self.max_audio_length_sec):
# if we hit the end of the audio length, readjust
# energy_threshold
for frame in frames: for frame in frames:
energy = audioop.rms(frame, source.SAMPLE_WIDTH) energy = audioop.rms(frame, source.SAMPLE_WIDTH)
self.adjust_energy_threshold(energy, seconds_per_buffer) self.adjust_energy_threshold(
energy, seconds_per_buffer)
break break
# check how long the detected phrase is, and retry listening if the phrase is too short # check how long the detected phrase is, and retry listening if
# the phrase is too short
phrase_count -= pause_count phrase_count -= pause_count
if phrase_count >= phrase_buffer_count: break # phrase is long enough, stop listening if phrase_count >= phrase_buffer_count:
break # phrase is long enough, stop listening
# obtain frame data # obtain frame data
for i in range(pause_count - non_speaking_buffer_count): frames.pop() # remove extra non-speaking frames at the end for i in range(pause_count - non_speaking_buffer_count):
frames.pop() # remove extra non-speaking frames at the end
frame_data = b"".join(list(frames)) frame_data = b"".join(list(frames))
return AudioData(frame_data, source.SAMPLE_RATE, source.SAMPLE_WIDTH) return AudioData(frame_data, source.SAMPLE_RATE, source.SAMPLE_WIDTH)
def adjust_energy_threshold(self, energy, seconds_per_buffer): def adjust_energy_threshold(self, energy, seconds_per_buffer):
if self.dynamic_energy_threshold and energy > 0: if self.dynamic_energy_threshold and energy > 0:
damping = self.dynamic_energy_adjustment_damping ** seconds_per_buffer # account for different chunk sizes and rates # account for different chunk sizes and rates
damping = (
self.dynamic_energy_adjustment_damping ** seconds_per_buffer)
target_energy = energy * self.dynamic_energy_ratio target_energy = energy * self.dynamic_energy_ratio
self.energy_threshold = self.energy_threshold * damping + target_energy * (1 - damping) self.energy_threshold = (
self.energy_threshold * damping +
target_energy * (1 - damping))

View File

@ -21,17 +21,21 @@ class GoogleRecognizerWrapper(object):
def __init__(self, recognizer): def __init__(self, recognizer):
self.recognizer = recognizer self.recognizer = recognizer
def transcribe(self, audio, language="en-US", show_all=False, metrics=None): def transcribe(
self, audio, language="en-US", show_all=False, metrics=None):
key = config.get('goog_api_key') key = config.get('goog_api_key')
return self.recognizer.recognize_google(audio, key=key, language=language, show_all=show_all) return self.recognizer.recognize_google(
audio, key=key, language=language, show_all=show_all)
class WitRecognizerWrapper(object): class WitRecognizerWrapper(object):
def __init__(self, recognizer): def __init__(self, recognizer):
self.recognizer = recognizer self.recognizer = recognizer
def transcribe(self, audio, language="en-US", show_all=False, metrics=None): def transcribe(
assert language == "en-US", "language must be default, language parameter not supported." self, audio, language="en-US", show_all=False, metrics=None):
assert language == "en-US", \
"language must be default, language parameter not supported."
key = config.get('wit_api_key') key = config.get('wit_api_key')
return self.recognizer.recognize_wit(audio, key, show_all=show_all) return self.recognizer.recognize_wit(audio, key, show_all=show_all)
@ -40,23 +44,27 @@ class IBMRecognizerWrapper(object):
def __init__(self, recognizer): def __init__(self, recognizer):
self.recognizer = recognizer self.recognizer = recognizer
def transcribe(self, audio, language="en-US", show_all=False, metrics=None): def transcribe(
self, audio, language="en-US", show_all=False, metrics=None):
username = config.get('ibm_username') username = config.get('ibm_username')
password = config.get('ibm_password') password = config.get('ibm_password')
return self.recognizer.recognize_ibm(audio, username, password, language=language, show_all=show_all) return self.recognizer.recognize_ibm(
audio, username, password, language=language, show_all=show_all)
class CerberusGoogleProxy(object): class CerberusGoogleProxy(object):
def __init__(self, _): def __init__(self, _):
self.version = get_version() self.version = get_version()
def transcribe(self, audio, language="en-US", show_all=False, metrics=None): def transcribe(
self, audio, language="en-US", show_all=False, metrics=None):
timer = Stopwatch() timer = Stopwatch()
timer.start() timer.start()
identity = IdentityManager().get() identity = IdentityManager().get()
headers = {} headers = {}
if identity.token: if identity.token:
headers['Authorization'] = 'Bearer %s:%s' % (identity.device_id, identity.token) headers['Authorization'] = 'Bearer %s:%s' % (
identity.device_id, identity.token)
response = requests.post(config.get("proxy_host") + response = requests.post(config.get("proxy_host") +
"/stt/google_v2?language=%s&version=%s" "/stt/google_v2?language=%s&version=%s"
@ -78,21 +86,27 @@ class CerberusGoogleProxy(object):
raise UnknownValueError() raise UnknownValueError()
log.info("STT JSON: " + json.dumps(actual_result)) log.info("STT JSON: " + json.dumps(actual_result))
if show_all: return actual_result if show_all:
return actual_result
# return the best guess # return the best guess
if "alternative" not in actual_result: raise UnknownValueError() if "alternative" not in actual_result:
raise UnknownValueError()
alternatives = actual_result["alternative"] alternatives = actual_result["alternative"]
if len([alt for alt in alternatives if alt.get('confidence')]) > 0: if len([alt for alt in alternatives if alt.get('confidence')]) > 0:
# if there is at least one element with confidence, force it to the front # if there is at least one element with confidence, force it to
alternatives.sort(key=lambda e: e.get('confidence', 0.0), reverse=True) # the front
alternatives.sort(
key=lambda e: e.get('confidence', 0.0), reverse=True)
for entry in alternatives: for entry in alternatives:
if "transcript" in entry: if "transcript" in entry:
return entry["transcript"] return entry["transcript"]
if len(alternatives) > 0: if len(alternatives) > 0:
log.error("Found %d entries, but none with a transcript." % len(alternatives)) log.error(
"Found %d entries, but none with a transcript." % len(
alternatives))
# no transcriptions available # no transcriptions available
raise UnknownValueError() raise UnknownValueError()

View File

@ -1,28 +1,30 @@
from mycroft.metrics import Stopwatch from mycroft.metrics import Stopwatch
__author__ = 'seanfitz'
import os import os
import sys import sys
from pocketsphinx import * from pocketsphinx import *
from cmath import exp, pi
__author__ = 'seanfitz'
BASEDIR = os.path.dirname(os.path.abspath(__file__)) BASEDIR = os.path.dirname(os.path.abspath(__file__))
from cmath import exp, pi
def fft(x): def fft(x):
""" """
fft function to clean data, but most be converted to array of IEEE floats first fft function to clean data, but most be converted to array of IEEE floats
first
:param x: :param x:
:return: :return:
""" """
N = len(x) N = len(x)
if N <= 1: return x if N <= 1:
return x
even = fft(x[0::2]) even = fft(x[0::2])
odd = fft(x[1::2]) odd = fft(x[1::2])
T= [exp(-2j*pi*k/N)*odd[k] for k in xrange(N/2)] T = [exp(-2j*pi*k/N)*odd[k] for k in xrange(N/2)]
return [even[k] + T[k] for k in xrange(N/2)] + \ return [even[k] + T[k] for k in xrange(N/2)] + \
[even[k] - T[k] for k in xrange(N/2)] [even[k] - T[k] for k in xrange(N/2)]
@ -45,8 +47,10 @@ class Recognizer(object):
def create_recognizer(samprate=16000, lang="en-us", keyphrase="hey mycroft"): def create_recognizer(samprate=16000, lang="en-us", keyphrase="hey mycroft"):
sphinx_config = Decoder.default_config() sphinx_config = Decoder.default_config()
sphinx_config.set_string('-hmm', os.path.join(BASEDIR, 'model', lang, 'hmm')) sphinx_config.set_string(
sphinx_config.set_string('-dict', os.path.join(BASEDIR, 'model', lang, 'mycroft-en-us.dict')) '-hmm', os.path.join(BASEDIR, 'model', lang, 'hmm'))
sphinx_config.set_string(
'-dict', os.path.join(BASEDIR, 'model', lang, 'mycroft-en-us.dict'))
sphinx_config.set_string('-keyphrase', keyphrase) sphinx_config.set_string('-keyphrase', keyphrase)
sphinx_config.set_float('-kws_threshold', float('1e-45')) sphinx_config.set_float('-kws_threshold', float('1e-45'))
sphinx_config.set_float('-samprate', samprate) sphinx_config.set_float('-samprate', samprate)
@ -55,4 +59,4 @@ def create_recognizer(samprate=16000, lang="en-us", keyphrase="hey mycroft"):
decoder = Decoder(sphinx_config) decoder = Decoder(sphinx_config)
return Recognizer(decoder) return Recognizer(decoder)

View File

@ -31,7 +31,7 @@ def connect():
def main(): def main():
global client global client
client = WebsocketClient() client = WebsocketClient()
if not '--quiet' in sys.argv: if '--quiet' not in sys.argv:
client.on('speak', handle_speak) client.on('speak', handle_speak)
event_thread = Thread(target=connect) event_thread = Thread(target=connect)
event_thread.setDaemon(True) event_thread.setDaemon(True)
@ -40,7 +40,9 @@ def main():
while True: while True:
print("Input:") print("Input:")
line = sys.stdin.readline() line = sys.stdin.readline()
client.emit(Message("recognizer_loop:utterance", metadata={'utterances': [line.strip()]})) client.emit(
Message("recognizer_loop:utterance",
metadata={'utterances': [line.strip()]}))
except KeyboardInterrupt, e: except KeyboardInterrupt, e:
event_thread.exit() event_thread.exit()
sys.exit() sys.exit()

View File

@ -10,9 +10,11 @@ from mycroft.util.log import getLogger
__author__ = 'seanfitz' __author__ = 'seanfitz'
logger = getLogger(__name__) logger = getLogger(__name__)
DEFAULTS_FILE = os.path.join(os.path.dirname(__file__), 'defaults', 'defaults.ini') DEFAULTS_FILE = os.path.join(
os.path.dirname(__file__), 'defaults', 'defaults.ini')
ETC_CONFIG_FILE = '/etc/mycroft/mycroft.ini' ETC_CONFIG_FILE = '/etc/mycroft/mycroft.ini'
USER_CONFIG_FILE = os.path.join(os.path.expanduser('~'), '.mycroft/mycroft.ini') USER_CONFIG_FILE = os.path.join(
os.path.expanduser('~'), '.mycroft/mycroft.ini')
DEFAULT_LOCATIONS = [DEFAULTS_FILE, ETC_CONFIG_FILE, USER_CONFIG_FILE] DEFAULT_LOCATIONS = [DEFAULTS_FILE, ETC_CONFIG_FILE, USER_CONFIG_FILE]
@ -35,7 +37,8 @@ class ConfigurationLoader(object):
def load(self): def load(self):
""" """
Loads configuration files from disk, in the locations defined by DEFAULT_LOCATIONS Loads configuration files from disk, in the locations defined by
DEFAULT_LOCATIONS
""" """
config = {} config = {}
for config_file in self.config_locations: for config_file in self.config_locations:
@ -45,10 +48,12 @@ class ConfigurationLoader(object):
cobj = ConfigObj(config_file) cobj = ConfigObj(config_file)
config = ConfigurationLoader._overwrite_merge(config, cobj) config = ConfigurationLoader._overwrite_merge(config, cobj)
except Exception, e: except Exception, e:
logger.error("Error loading config file [%s]" % config_file) logger.error(
"Error loading config file [%s]" % config_file)
logger.error(repr(e)) logger.error(repr(e))
else: else:
logger.debug("Could not find config file at [%s]" % config_file) logger.debug(
"Could not find config file at [%s]" % config_file)
return config return config
@ -70,23 +75,32 @@ class RemoteConfiguration(object):
def update(self): def update(self):
config = self.config_manager.get_config() config = self.config_manager.get_config()
remote_config_url = config.get("remote_configuration").get("url") remote_config_url = config.get("remote_configuration").get("url")
enabled = str2bool(config.get("remote_configuration").get("enabled", "False")) enabled = str2bool(
config.get("remote_configuration").get("enabled", "False"))
if enabled and self.identity.token: if enabled and self.identity.token:
auth_header = "Bearer %s:%s" % (self.identity.device_id, self.identity.token) auth_header = "Bearer %s:%s" % (
self.identity.device_id, self.identity.token)
try: try:
response = requests.get(remote_config_url, headers={"Authorization": auth_header}) response = requests.get(
remote_config_url, headers={"Authorization": auth_header})
user = response.json() user = response.json()
for attribute in user["attributes"]: for attribute in user["attributes"]:
attribute_name = attribute.get("attribute_name") attribute_name = attribute.get("attribute_name")
core_config_name = self.remote_config_mapping.get(attribute_name) core_config_name = self.remote_config_mapping.get(
attribute_name)
if core_config_name: if core_config_name:
config["core"][core_config_name] = str(attribute.get("attribute_value")) config["core"][core_config_name] = str(
logger.info("Accepting remote configuration: core[%s] == %s" % (core_config_name, attribute["attribute_value"])) attribute.get("attribute_value"))
logger.info(
"Accepting remote configuration: core[%s] == %s" %
(core_config_name, attribute["attribute_value"]))
except Exception as e: except Exception as e:
logger.error("Failed to fetch remote configuration: %s" % repr(e)) logger.error(
"Failed to fetch remote configuration: %s" % repr(e))
else: else:
logger.debug("Device not paired, cannot retrieve remote configuration.") logger.debug(
"Device not paired, cannot retrieve remote configuration.")
class ConfigurationManager(object): class ConfigurationManager(object):
@ -101,7 +115,8 @@ class ConfigurationManager(object):
Load default config files as well as any additionally specified files. Load default config files as well as any additionally specified files.
Now also loads configuration from Cerberus (if device is paired) Now also loads configuration from Cerberus (if device is paired)
:param config_files: An array of config file paths in addition to DEFAULT_LOCATIONS :param config_files: An array of config file paths in addition to
DEFAULT_LOCATIONS
:return: None :return: None
""" """

View File

@ -38,22 +38,25 @@ class MustacheDialogRenderer(object):
def render(self, template_name, context={}, index=None): def render(self, template_name, context={}, index=None):
""" """
Given a template name, pick a template and render it with the provided context. Given a template name, pick a template and render it with the provided
context.
:param template_name: the name of a template group. :param template_name: the name of a template group.
:param context: dictionary representing values to be rendered :param context: dictionary representing values to be rendered
:param index: optional, the specific index in the collection of templates :param index: optional, the specific index in the collection of
templates
:raises NotImplementedError: if no template can be found identified by template_name :raises NotImplementedError: if no template can be found identified by
template_name
:return: :return:
""" """
if template_name not in self.templates: if template_name not in self.templates:
raise NotImplementedError("Template not found: %s" % template_name) raise NotImplementedError("Template not found: %s" % template_name)
template_functions = self.templates.get(template_name) template_functions = self.templates.get(template_name)
if index == None: if index is None:
index = random.randrange(len(template_functions)) index = random.randrange(len(template_functions))
else: else:
index %= len(template_functions) index %= len(template_functions)
@ -79,10 +82,11 @@ class DialogLoader(object):
logger.warn("No dialog found: " + dialog_dir) logger.warn("No dialog found: " + dialog_dir)
return self.__renderer return self.__renderer
for f in sorted(filter(lambda x: os.path.isfile(os.path.join(dialog_dir, x)), os.listdir(dialog_dir))): for f in sorted(
filter(lambda x: os.path.isfile(
os.path.join(dialog_dir, x)), os.listdir(dialog_dir))):
dialog_entry_name = os.path.splitext(f)[0] dialog_entry_name = os.path.splitext(f)[0]
self.__renderer.load_template_file(dialog_entry_name, os.path.join(dialog_dir, f)) self.__renderer.load_template_file(
dialog_entry_name, os.path.join(dialog_dir, f))
return self.__renderer return self.__renderer

View File

@ -6,7 +6,8 @@ __author__ = 'jdorleans'
class FileSystemAccess(object): class FileSystemAccess(object):
""" """
A class for providing access to the mycroft FS sandbox. Intended to be attached to skills A class for providing access to the mycroft FS sandbox. Intended to be
attached to skills
at initialization time to provide a skill-specific namespace. at initialization time to provide a skill-specific namespace.
""" """
def __init__(self, path): def __init__(self, path):
@ -24,9 +25,11 @@ class FileSystemAccess(object):
def open(self, filename, mode): def open(self, filename, mode):
""" """
Get a handle to a file (with the provided mode) within the skill-specific namespace. Get a handle to a file (with the provided mode) within the
skill-specific namespace.
:param filename: a str representing a path relative to the namespace. subdirs not currently supported. :param filename: a str representing a path relative to the namespace.
subdirs not currently supported.
:param mode: a file handle mode :param mode: a file handle mode

View File

@ -26,7 +26,8 @@ class IdentityManager(object):
def initialize(self): def initialize(self):
if self.filesystem.exists('identity.json'): if self.filesystem.exists('identity.json'):
self.identity = DeviceIdentity.load(self.filesystem.open('identity.json', 'r')) self.identity = DeviceIdentity.load(self.filesystem.open(
'identity.json', 'r'))
else: else:
identity = DeviceIdentity(device_id=str(uuid4())) identity = DeviceIdentity(device_id=str(uuid4()))
self.update(identity) self.update(identity)

View File

@ -32,11 +32,12 @@ class WebsocketClient(object):
self.pool = ThreadPool(10) self.pool = ThreadPool(10)
def _create_new_connection(self): def _create_new_connection(self):
return WebSocketApp(self.scheme + "://" + self.host + ":" + str(self.port) + self.path, return WebSocketApp(
on_open=self.on_open, self.scheme + "://" + self.host + ":" + str(self.port) + self.path,
on_close=self.on_close, on_open=self.on_open,
on_error=self.on_error, on_close=self.on_close,
on_message=self.on_message) on_error=self.on_error,
on_message=self.on_message)
def on_open(self, ws): def on_open(self, ws):
logger.info("Connected") logger.info("Connected")
@ -52,7 +53,8 @@ class WebsocketClient(object):
except Exception, e: except Exception, e:
logger.error(repr(e)) logger.error(repr(e))
sleep_time = self.exp_backoff_counter sleep_time = self.exp_backoff_counter
logger.warn("Disconnecting on error, reconnecting in %d seconds." % sleep_time) logger.warn(
"Disconnecting on error, reconnecting in %d seconds." % sleep_time)
self.exp_backoff_counter = min(self.exp_backoff_counter * 2, 60) self.exp_backoff_counter = min(self.exp_backoff_counter * 2, 60)
time.sleep(sleep_time) time.sleep(sleep_time)
self.client = self._create_new_connection() self.client = self._create_new_connection()
@ -61,10 +63,12 @@ class WebsocketClient(object):
def on_message(self, ws, message): def on_message(self, ws, message):
self.emitter.emit('message', message) self.emitter.emit('message', message)
parsed_message = Message.deserialize(message) parsed_message = Message.deserialize(message)
self.pool.apply_async(self.emitter.emit, (parsed_message.message_type, parsed_message)) self.pool.apply_async(
self.emitter.emit, (parsed_message.message_type, parsed_message))
def emit(self, message): def emit(self, message):
if not self.client or not self.client.sock or not self.client.sock.connected: if (not self.client or not self.client.sock or
not self.client.sock.connected):
return return
if hasattr(message, 'serialize'): if hasattr(message, 'serialize'):
self.client.send(message.serialize()) self.client.send(message.serialize())
@ -86,6 +90,7 @@ class WebsocketClient(object):
def echo(): def echo():
client = WebsocketClient() client = WebsocketClient()
def echo(message): def echo(message):
logger.info(message) logger.info(message)
@ -97,4 +102,4 @@ def echo():
client.run_forever() client.run_forever()
if __name__ == "__main__": if __name__ == "__main__":
echo() echo()

View File

@ -1,6 +1,7 @@
__author__ = 'seanfitz'
import json import json
__author__ = 'seanfitz'
class Message(object): class Message(object):
def __init__(self, message_type, metadata={}, context=None): def __init__(self, message_type, metadata={}, context=None):
@ -44,4 +45,4 @@ class Message(object):
if 'target' in new_context: if 'target' in new_context:
del new_context['target'] del new_context['target']
return Message(message_type, metadata, context=new_context) return Message(message_type, metadata, context=new_context)

View File

@ -11,8 +11,6 @@ settings = {
} }
def main(): def main():
import tornado.options import tornado.options
tornado.options.parse_command_line() tornado.options.parse_command_line()

View File

@ -18,7 +18,8 @@ client_connections = []
class WebsocketEventHandler(tornado.websocket.WebSocketHandler): class WebsocketEventHandler(tornado.websocket.WebSocketHandler):
def __init__(self, application, request, **kwargs): def __init__(self, application, request, **kwargs):
tornado.websocket.WebSocketHandler.__init__(self, application, request, **kwargs) tornado.websocket.WebSocketHandler.__init__(
self, application, request, **kwargs)
self.emitter = EventBusEmitter self.emitter = EventBusEmitter
def on(self, event_name, handler): def on(self, event_name, handler):
@ -32,7 +33,8 @@ class WebsocketEventHandler(tornado.websocket.WebSocketHandler):
return return
try: try:
self.emitter.emit(deserialized_message.message_type, deserialized_message) self.emitter.emit(
deserialized_message.message_type, deserialized_message)
except Exception, e: except Exception, e:
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)
pass pass
@ -48,7 +50,8 @@ class WebsocketEventHandler(tornado.websocket.WebSocketHandler):
client_connections.remove(self) client_connections.remove(self)
def emit(self, channel_message): def emit(self, channel_message):
if hasattr(channel_message, 'serialize') and callable(getattr(channel_message, 'serialize')): if (hasattr(channel_message, 'serialize') and
callable(getattr(channel_message, 'serialize'))):
self.write_message(channel_message.serialize()) self.write_message(channel_message.serialize())
else: else:
self.write_message(json.dumps(channel_message)) self.write_message(json.dumps(channel_message))

View File

@ -12,6 +12,7 @@ from mycroft.util.setup_base import get_version
config = ConfigurationManager.get_config().get('metrics_client') config = ConfigurationManager.get_config().get('metrics_client')
metrics_log = getLogger("METRICS") metrics_log = getLogger("METRICS")
class Stopwatch(object): class Stopwatch(object):
def __init__(self): def __init__(self):
self.timestamp = None self.timestamp = None
@ -77,9 +78,11 @@ class MetricsAggregator(object):
'attributes': self._attributes 'attributes': self._attributes
} }
self.clear() self.clear()
count = len(payload['counters']) + len(payload['timers']) + len(payload['levels']) count = (len(payload['counters']) + len(payload['timers']) +
len(payload['levels']))
if count > 0: if count > 0:
metrics_log.debug(json.dumps(payload)) metrics_log.debug(json.dumps(payload))
def publish(): def publish():
publisher.publish(payload) publisher.publish(payload)
threading.Thread(target=publish).start() threading.Thread(target=publish).start()
@ -97,4 +100,7 @@ class MetricsPublisher(object):
session_id = SessionManager.get().session_id session_id = SessionManager.get().session_id
events['session_id'] = session_id events['session_id'] = session_id
if self.enabled: if self.enabled:
requests.post(self.url, headers={'Content-Type': 'application/json'}, data=json.dumps(events), verify=False) requests.post(
self.url,
headers={'Content-Type': 'application/json'},
data=json.dumps(events), verify=False)

View File

@ -26,10 +26,12 @@ class DevicePairingClient(object):
ssl=str2bool(config.get("ssl"))) ssl=str2bool(config.get("ssl")))
self.identity_manager = IdentityManager() self.identity_manager = IdentityManager()
self.identity = self.identity_manager.identity self.identity = self.identity_manager.identity
self.pairing_code = pairing_code if pairing_code else generate_pairing_code() self.pairing_code = (
pairing_code if pairing_code else generate_pairing_code())
def on_registration(self, message): def on_registration(self, message):
# TODO: actually accept the configuration message and store it in identity # TODO: actually accept the configuration message and store it in
# identity
identity = self.identity_manager.get() identity = self.identity_manager.get()
register_payload = message.metadata register_payload = message.metadata
if register_payload.get("device_id") == identity.device_id: if register_payload.get("device_id") == identity.device_id:
@ -57,9 +59,8 @@ class DevicePairingClient(object):
self.ws_client.run_forever() self.ws_client.run_forever()
def main(): def main():
DevicePairingClient().run() DevicePairingClient().run()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -53,10 +53,14 @@ class SessionManager(object):
:return: An active session :return: An active session
""" """
with SessionManager.__lock: with SessionManager.__lock:
if not SessionManager.__current_session or SessionManager.__current_session.expired(): if (not SessionManager.__current_session or
SessionManager.__current_session = Session(str(uuid4()), SessionManager.__current_session.expired()):
expiration_seconds=config.get('session_ttl_seconds', 180)) SessionManager.__current_session = Session(
logger.info("New Session Start: " + SessionManager.__current_session.session_id) str(uuid4()),
expiration_seconds=config.get('session_ttl_seconds', 180))
logger.info(
"New Session Start: " +
SessionManager.__current_session.session_id)
return SessionManager.__current_session return SessionManager.__current_session
@staticmethod @staticmethod
@ -67,5 +71,3 @@ class SessionManager(object):
:return: None :return: None
""" """
SessionManager.get().touch() SessionManager.get().touch()

View File

@ -23,7 +23,8 @@ class AlarmSkill(ScheduledCRUDSkill):
def initialize(self): def initialize(self):
super(AlarmSkill, self).initialize() super(AlarmSkill, self).initialize()
intent = IntentBuilder('AlarmSkillStopIntent').require('AlarmSkillStopVerb') \ intent = IntentBuilder(
'AlarmSkillStopIntent').require('AlarmSkillStopVerb') \
.require('AlarmSkillKeyword').build() .require('AlarmSkillKeyword').build()
self.register_intent(intent, self.__handle_stop) self.register_intent(intent, self.__handle_stop)

View File

@ -31,19 +31,24 @@ class AudioRecordSkill(ScheduledSkill):
def initialize(self): def initialize(self):
self.load_data_files(dirname(__file__)) self.load_data_files(dirname(__file__))
intent = IntentBuilder("AudioRecordSkillIntent").require("AudioRecordSkillKeyword").build() intent = IntentBuilder("AudioRecordSkillIntent").require(
"AudioRecordSkillKeyword").build()
self.register_intent(intent, self.handle_record) self.register_intent(intent, self.handle_record)
intent = IntentBuilder('AudioRecordSkillStopIntent').require('AudioRecordSkillStopVerb') \ intent = IntentBuilder('AudioRecordSkillStopIntent').require(
'AudioRecordSkillStopVerb') \
.require('AudioRecordSkillKeyword').build() .require('AudioRecordSkillKeyword').build()
self.register_intent(intent, self.handle_stop) self.register_intent(intent, self.handle_stop)
intent = IntentBuilder('AudioRecordSkillPlayIntent').require('AudioRecordSkillPlayVerb') \ intent = IntentBuilder('AudioRecordSkillPlayIntent').require(
'AudioRecordSkillPlayVerb') \
.require('AudioRecordSkillKeyword').build() .require('AudioRecordSkillKeyword').build()
self.register_intent(intent, self.handle_play) self.register_intent(intent, self.handle_play)
intent = IntentBuilder('AudioRecordSkillStopPlayIntent').require('AudioRecordSkillStopVerb') \ intent = IntentBuilder('AudioRecordSkillStopPlayIntent').require(
.require('AudioRecordSkillPlayVerb').require('AudioRecordSkillKeyword').build() 'AudioRecordSkillStopVerb') \
.require('AudioRecordSkillPlayVerb').require(
'AudioRecordSkillKeyword').build()
self.register_intent(intent, self.handle_stop_play) self.register_intent(intent, self.handle_stop_play)
def handle_record(self, message): def handle_record(self, message):
@ -55,7 +60,8 @@ class AudioRecordSkill(ScheduledSkill):
self.notify_time = now self.notify_time = now
self.feedback_start() self.feedback_start()
time.sleep(3) time.sleep(3)
self.record_process = record(self.file_path, self.duration, self.rate, self.channels) self.record_process = record(
self.file_path, self.duration, self.rate, self.channels)
self.schedule() self.schedule()
else: else:
self.speak_dialog("audio.record.disk.full") self.speak_dialog("audio.record.disk.full")
@ -76,7 +82,8 @@ class AudioRecordSkill(ScheduledSkill):
def feedback_start(self): def feedback_start(self):
if self.duration > 0: if self.duration > 0:
self.speak_dialog('audio.record.start.duration', {'duration': self.duration}) self.speak_dialog(
'audio.record.start.duration', {'duration': self.duration})
else: else:
self.speak_dialog('audio.record.start') self.speak_dialog('audio.record.start')

View File

@ -28,4 +28,4 @@ class CerberusConfigSkill(MycroftSkill):
def create_skill(): def create_skill():
return CerberusConfigSkill() return CerberusConfigSkill()

View File

@ -17,11 +17,16 @@ class SkillContainer(object):
def __init__(self, args): def __init__(self, args):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--dependency-dir", default="./lib") parser.add_argument("--dependency-dir", default="./lib")
parser.add_argument("--messagebus-host", default=messagebus_config.get("host")) parser.add_argument(
parser.add_argument("--messagebus-port", type=int, default=messagebus_config.get("port")) "--messagebus-host", default=messagebus_config.get("host"))
parser.add_argument(
"--messagebus-port", type=int,
default=messagebus_config.get("port"))
parser.add_argument("--use-ssl", action='store_true', default=False) parser.add_argument("--use-ssl", action='store_true', default=False)
parser.add_argument("--enable-intent-skill", action='store_true', default=False) parser.add_argument(
parser.add_argument("skill_directory", default=os.path.dirname(__file__)) "--enable-intent-skill", action='store_true', default=False)
parser.add_argument(
"skill_directory", default=os.path.dirname(__file__))
parsed_args = parser.parse_args(args) parsed_args = parser.parse_args(args)
if os.path.exists(parsed_args.dependency_dir): if os.path.exists(parsed_args.dependency_dir):

View File

@ -31,15 +31,20 @@ def load_vocab_from_file(path, vocab_type, emitter):
parts = line.strip().split("|") parts = line.strip().split("|")
entity = parts[0] entity = parts[0]
emitter.emit(Message("register_vocab", metadata={'start': entity, 'end': vocab_type})) emitter.emit(
Message("register_vocab",
metadata={'start': entity, 'end': vocab_type}))
for alias in parts[1:]: for alias in parts[1:]:
emitter.emit( emitter.emit(
Message("register_vocab", metadata={'start': alias, 'end': vocab_type, 'alias_of': entity})) Message("register_vocab",
metadata={'start': alias, 'end': vocab_type,
'alias_of': entity}))
def load_vocabulary(basedir, emitter): def load_vocabulary(basedir, emitter):
for vocab_type in os.listdir(basedir): for vocab_type in os.listdir(basedir):
load_vocab_from_file(join(basedir, vocab_type), splitext(vocab_type)[0], emitter) load_vocab_from_file(
join(basedir, vocab_type), splitext(vocab_type)[0], emitter)
def create_intent_envelope(intent): def create_intent_envelope(intent):
@ -56,16 +61,22 @@ def open_intent_envelope(message):
def load_skill(skill_descriptor, emitter): def load_skill(skill_descriptor, emitter):
try: try:
skill_module = imp.load_module(skill_descriptor["name"] + MainModule, *skill_descriptor["info"]) skill_module = imp.load_module(
if hasattr(skill_module, 'create_skill') and callable(skill_module.create_skill): # v2 skills framework skill_descriptor["name"] + MainModule, *skill_descriptor["info"])
if (hasattr(skill_module, 'create_skill') and
callable(skill_module.create_skill)):
# v2 skills framework
skill = skill_module.create_skill() skill = skill_module.create_skill()
skill.bind(emitter) skill.bind(emitter)
skill.initialize() skill.initialize()
return skill return skill
else: else:
logger.warn("Module %s does not appear to be skill" % (skill_descriptor["name"])) logger.warn(
"Module %s does not appear to be skill" % (
skill_descriptor["name"]))
except: except:
logger.error("Failed to load skill: " + skill_descriptor["name"], exc_info=True) logger.error(
"Failed to load skill: " + skill_descriptor["name"], exc_info=True)
return None return None
@ -74,7 +85,8 @@ def get_skills(skills_folder):
possible_skills = os.listdir(skills_folder) possible_skills = os.listdir(skills_folder)
for i in possible_skills: for i in possible_skills:
location = join(skills_folder, i) location = join(skills_folder, i)
if not isdir(location) or not MainModule + ".py" in os.listdir(location): if (not isdir(location) or
not MainModule + ".py" in os.listdir(location)):
continue continue
skills.append(create_skill_descriptor(location)) skills.append(create_skill_descriptor(location))
@ -94,13 +106,15 @@ def load_skills(emitter, skills_root=SKILLS_BASEDIR):
load_skill(skill, emitter) load_skill(skill, emitter)
for skill in skills: for skill in skills:
if skill['name'] not in PRIMARY_SKILLS and skill['name'] not in BLACKLISTED_SKILLS: if (skill['name'] not in PRIMARY_SKILLS and
skill['name'] not in BLACKLISTED_SKILLS):
load_skill(skill, emitter) load_skill(skill, emitter)
class MycroftSkill(object): class MycroftSkill(object):
""" """
Abstract base class which provides common behaviour and parameters to all Skills implementation. Abstract base class which provides common behaviour and parameters to all
Skills implementation.
""" """
def __init__(self, name, emitter=None): def __init__(self, name, emitter=None):
@ -134,7 +148,8 @@ class MycroftSkill(object):
def detach(self): def detach(self):
for name in self.registered_intents: for name in self.registered_intents:
self.emitter.emit(Message("detach_intent", metadata={"intent_name": name})) self.emitter.emit(
Message("detach_intent", metadata={"intent_name": name}))
def initialize(self): def initialize(self):
""" """
@ -155,17 +170,24 @@ class MycroftSkill(object):
handler(message) handler(message)
except: except:
# TODO: Localize # TODO: Localize
self.speak("An error occurred while processing a request in " + self.name) self.speak(
logger.error("An error occurred while processing a request in " + self.name, exc_info=True) "An error occurred while processing a request in " +
self.name)
logger.error(
"An error occurred while processing a request in " +
self.name, exc_info=True)
self.emitter.on(intent_parser.name, receive_handler) self.emitter.on(intent_parser.name, receive_handler)
def register_vocabulary(self, entity, entity_type): def register_vocabulary(self, entity, entity_type):
self.emitter.emit(Message('register_vocab', metadata={'start': entity, 'end': entity_type})) self.emitter.emit(
Message('register_vocab',
metadata={'start': entity, 'end': entity_type}))
def register_regex(self, regex_str): def register_regex(self, regex_str):
re.compile(regex_str) # validate regex re.compile(regex_str) # validate regex
self.emitter.emit(Message('register_vocab', metadata={'regex': regex_str})) self.emitter.emit(
Message('register_vocab', metadata={'regex': regex_str}))
def speak(self, utterance): def speak(self, utterance):
self.emitter.emit(Message("speak", metadata={'utterance': utterance})) self.emitter.emit(Message("speak", metadata={'utterance': utterance}))
@ -174,7 +196,8 @@ class MycroftSkill(object):
self.speak(self.dialog_renderer.render(key, data)) self.speak(self.dialog_renderer.render(key, data))
def init_dialog(self, root_directory): def init_dialog(self, root_directory):
self.dialog_renderer = DialogLoader().load(join(root_directory, 'dialog', self.lang)) self.dialog_renderer = DialogLoader().load(
join(root_directory, 'dialog', self.lang))
def load_data_files(self, root_directory): def load_data_files(self, root_directory):
self.init_dialog(root_directory) self.init_dialog(root_directory)

View File

@ -23,7 +23,8 @@ class TimeSkill(MycroftSkill):
self.register_regex("in (?P<Location>.*)") self.register_regex("in (?P<Location>.*)")
self.register_regex("at (?P<Location>.*)") self.register_regex("at (?P<Location>.*)")
intent = IntentBuilder("TimeIntent").require("TimeKeyword").optionally("Location").build() intent = IntentBuilder("TimeIntent").require(
"TimeKeyword").optionally("Location").build()
self.register_intent(intent, self.handle_intent) self.register_intent(intent, self.handle_intent)
@ -43,7 +44,8 @@ class TimeSkill(MycroftSkill):
except: except:
return None return None
# This method only handles localtime, for other timezones the task falls to Wolfram. # This method only handles localtime, for other timezones the task falls
# to Wolfram.
def handle_intent(self, message): def handle_intent(self, message):
location = message.metadata.get("Location", None) location = message.metadata.get("Location", None)

View File

@ -57,14 +57,18 @@ class DesktopLauncherSkill(MycroftSkill):
self.register_regex("for (?P<SearchTerms>.*) on") self.register_regex("for (?P<SearchTerms>.*) on")
self.register_regex("(?P<SearchTerms>.*) on") self.register_regex("(?P<SearchTerms>.*) on")
launch_intent = IntentBuilder("LaunchDesktopApplication").require("LaunchKeyword").require( launch_intent = IntentBuilder(
"LaunchDesktopApplication").require("LaunchKeyword").require(
"Application").build() "Application").build()
self.register_intent(launch_intent, self.handle_launch_desktop_app) self.register_intent(launch_intent, self.handle_launch_desktop_app)
launch_website_intent = IntentBuilder("LaunchWebsiteIntent").require("LaunchKeyword").require("Website").build() launch_website_intent = IntentBuilder(
"LaunchWebsiteIntent").require("LaunchKeyword").require(
"Website").build()
self.register_intent(launch_website_intent, self.handle_launch_website) self.register_intent(launch_website_intent, self.handle_launch_website)
search_website = IntentBuilder("SearchWebsiteIntent").require("SearchKeyword").require("Website").require( search_website = IntentBuilder("SearchWebsiteIntent").require(
"SearchKeyword").require("Website").require(
"SearchTerms").build() "SearchTerms").build()
self.register_intent(search_website, self.handle_search_website) self.register_intent(search_website, self.handle_search_website)

View File

@ -12,12 +12,18 @@ LOGGER = getLogger(__name__)
class DialCallSkill(MycroftSkill): class DialCallSkill(MycroftSkill):
DBUS_CMD = ["dbus-send", "--print-reply", "--dest=com.canonical.TelephonyServiceHandler", DBUS_CMD = [
"/com/canonical/TelephonyServiceHandler", "com.canonical.TelephonyServiceHandler.StartCall"] "dbus-send", "--print-reply",
"--dest=com.canonical.TelephonyServiceHandler",
"/com/canonical/TelephonyServiceHandler",
"com.canonical.TelephonyServiceHandler.StartCall"
]
def __init__(self): def __init__(self):
super(DialCallSkill, self).__init__(name="DialCallSkill") super(DialCallSkill, self).__init__(name="DialCallSkill")
self.contacts = {'jonathan': '12345678', 'ryan': '23456789', 'sean': '34567890'} # TODO - Use API self.contacts = {
'jonathan': '12345678', 'ryan': '23456789',
'sean': '34567890'} # TODO - Use API
def initialize(self): def initialize(self):
self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us'))
@ -25,7 +31,8 @@ class DialCallSkill(MycroftSkill):
prefixes = ['call', 'phone'] # TODO - i10n prefixes = ['call', 'phone'] # TODO - i10n
self.__register_prefixed_regex(prefixes, "(?P<Contact>.*)") self.__register_prefixed_regex(prefixes, "(?P<Contact>.*)")
intent = IntentBuilder("DialCallIntent").require("DialCallKeyword").require("Contact").build() intent = IntentBuilder("DialCallIntent").require(
"DialCallKeyword").require("Contact").build()
self.register_intent(intent, self.handle_intent) self.register_intent(intent, self.handle_intent)
def __register_prefixed_regex(self, prefixes, suffix_regex): def __register_prefixed_regex(self, prefixes, suffix_regex):
@ -51,7 +58,9 @@ class DialCallSkill(MycroftSkill):
subprocess.call(cmd) subprocess.call(cmd)
def __notify(self, contact, number): def __notify(self, contact, number):
self.emitter.emit(Message("dial_call", metadata={'contact': contact, 'number': number})) self.emitter.emit(
Message("dial_call",
metadata={'contact': contact, 'number': number}))
def stop(self): def stop(self):
pass pass

View File

@ -22,18 +22,25 @@ class IntentSkill(MycroftSkill):
best_intent = None best_intent = None
for utterance in utterances: for utterance in utterances:
try: try:
best_intent = next(self.engine.determine_intent(utterance, num_results=100)) best_intent = next(self.engine.determine_intent(
best_intent['utterance'] = utterance # TODO - Should Adapt handle this? utterance, num_results=100))
# TODO - Should Adapt handle this?
best_intent['utterance'] = utterance
except StopIteration, e: except StopIteration, e:
continue continue
if best_intent and best_intent.get('confidence', 0.0) > 0.0: if best_intent and best_intent.get('confidence', 0.0) > 0.0:
reply = message.reply(best_intent.get('intent_type'), metadata=best_intent) reply = message.reply(
best_intent.get('intent_type'), metadata=best_intent)
self.emitter.emit(reply) self.emitter.emit(reply)
elif len(utterances) == 1: elif len(utterances) == 1:
self.emitter.emit(Message("intent_failure", metadata={"utterance": utterances[0]})) self.emitter.emit(
Message("intent_failure",
metadata={"utterance": utterances[0]}))
else: else:
self.emitter.emit(Message("multi_utterance_intent_failure", metadata={"utterances": utterances})) self.emitter.emit(
Message("multi_utterance_intent_failure",
metadata={"utterances": utterances}))
def handle_register_vocab(self, message): def handle_register_vocab(self, message):
start_concept = message.metadata.get('start') start_concept = message.metadata.get('start')
@ -43,7 +50,8 @@ class IntentSkill(MycroftSkill):
if regex_str: if regex_str:
self.engine.register_regex_entity(regex_str) self.engine.register_regex_entity(regex_str)
else: else:
self.engine.register_entity(start_concept, end_concept, alias_of=alias_of) self.engine.register_entity(
start_concept, end_concept, alias_of=alias_of)
def handle_register_intent(self, message): def handle_register_intent(self, message):
intent = open_intent_envelope(message) intent = open_intent_envelope(message)
@ -51,7 +59,8 @@ class IntentSkill(MycroftSkill):
def handle_detach_intent(self, message): def handle_detach_intent(self, message):
intent_name = message.metadata.get('intent_name') intent_name = message.metadata.get('intent_name')
new_parsers = [p for p in self.engine.intent_parsers if p.name != intent_name] new_parsers = [
p for p in self.engine.intent_parsers if p.name != intent_name]
self.engine.intent_parsers = new_parsers self.engine.intent_parsers = new_parsers
def stop(self): def stop(self):

View File

@ -21,9 +21,14 @@ class IPSkill(MycroftSkill):
def handle_intent(self, message): def handle_intent(self, message):
self.speak("Here are my available I.P. addresses.") self.speak("Here are my available I.P. addresses.")
for ifaceName in interfaces(): for ifaceName in interfaces():
addresses = [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [{'addr': 'No IP addr'}])] addresses = [
i['addr'] for i in
ifaddresses(ifaceName).setdefault(
AF_INET, [{'addr': 'No IP addr'}])]
if ifaceName != "lo": if ifaceName != "lo":
self.speak('%s: %s' % ("interface: " + ifaceName + ", I.P. Address ", ', '.join(addresses))) self.speak('%s: %s' % (
"interface: " + ifaceName +
", I.P. Address ", ', '.join(addresses)))
self.speak("Those are all my I.P. addresses.") self.speak("Those are all my I.P. addresses.")
def stop(self): def stop(self):

View File

@ -13,7 +13,8 @@ class NapTimeSkill(MycroftSkill):
super(NapTimeSkill, self).__init__(name="NapTimeSkill") super(NapTimeSkill, self).__init__(name="NapTimeSkill")
def initialize(self): def initialize(self):
intent_parser = IntentBuilder("NapTimeIntent").require("SleepCommand").build() intent_parser = IntentBuilder("NapTimeIntent").require(
"SleepCommand").build()
self.register_intent(intent_parser, self.handle_intent) self.register_intent(intent_parser, self.handle_intent)
self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us'))

View File

@ -22,7 +22,8 @@ class NPRNewsSkill(MycroftSkill):
def initialize(self): def initialize(self):
self.load_data_files(dirname(__file__)) self.load_data_files(dirname(__file__))
intent = IntentBuilder("NPRNewsIntent").require("NPRNewsKeyword").build() intent = IntentBuilder("NPRNewsIntent").require(
"NPRNewsKeyword").build()
self.register_intent(intent, self.handle_intent) self.register_intent(intent, self.handle_intent)
self.weather.bind(self.emitter) self.weather.bind(self.emitter)

View File

@ -12,7 +12,8 @@ class PairingSkill(MycroftSkill):
super(PairingSkill, self).__init__(name="PairingSkill") super(PairingSkill, self).__init__(name="PairingSkill")
def initialize(self): def initialize(self):
intent = IntentBuilder("PairingIntent").require("DevicePairingPhrase").build() intent = IntentBuilder("PairingIntent").require(
"DevicePairingPhrase").build()
self.load_data_files(dirname(__file__)) self.load_data_files(dirname(__file__))
self.register_intent(intent, handler=self.handle_pairing_request) self.register_intent(intent, handler=self.handle_pairing_request)
@ -21,8 +22,9 @@ class PairingSkill(MycroftSkill):
pairing_code = pairing_client.pairing_code pairing_code = pairing_client.pairing_code
threading.Thread(target=pairing_client.run).start() threading.Thread(target=pairing_client.run).start()
self.enclosure.mouth_text("Pairing code is: " + pairing_code) self.enclosure.mouth_text("Pairing code is: " + pairing_code)
self.speak_dialog("pairing.instructions", data={"pairing_code": ', ,'.join(pairing_code)}) self.speak_dialog(
"pairing.instructions",
data={"pairing_code": ', ,'.join(pairing_code)})
def stop(self): def stop(self):
pass pass

View File

@ -13,10 +13,12 @@ __author__ = 'jdorleans'
# TODO - Localization, Sandbox # TODO - Localization, Sandbox
class ReminderSkill(ScheduledCRUDSkill): class ReminderSkill(ScheduledCRUDSkill):
PRONOUNS = {'i': 'you', 'me': 'you', 'my': 'your', 'myself': 'yourself', 'am': 'are', "'m": "are", "i'm": "you're"} PRONOUNS = {'i': 'you', 'me': 'you', 'my': 'your', 'myself': 'yourself',
'am': 'are', "'m": "are", "i'm": "you're"}
def __init__(self): def __init__(self):
super(ReminderSkill, self).__init__("ReminderSkill", None, dirname(__file__)) super(ReminderSkill, self).__init__(
"ReminderSkill", None, dirname(__file__))
self.reminder_on = False self.reminder_on = False
self.max_delay = int(self.config.get('max_delay')) self.max_delay = int(self.config.get('max_delay'))
self.repeat_time = int(self.config.get('repeat_time')) self.repeat_time = int(self.config.get('repeat_time'))
@ -24,7 +26,8 @@ class ReminderSkill(ScheduledCRUDSkill):
def initialize(self): def initialize(self):
super(ReminderSkill, self).initialize() super(ReminderSkill, self).initialize()
intent = IntentBuilder('ReminderSkillStopIntent').require('ReminderSkillStopVerb') \ intent = IntentBuilder(
'ReminderSkillStopIntent').require('ReminderSkillStopVerb') \
.require('ReminderSkillKeyword').build() .require('ReminderSkillKeyword').build()
self.register_intent(intent, self.__handle_stop) self.register_intent(intent, self.__handle_stop)
@ -57,7 +60,9 @@ class ReminderSkill(ScheduledCRUDSkill):
delay = self.__calculate_delay(self.max_delay) delay = self.__calculate_delay(self.max_delay)
while self.reminder_on and datetime.now() < delay: while self.reminder_on and datetime.now() < delay:
self.speak_dialog('reminder.notify', data=self.build_feedback_payload(timestamp)) self.speak_dialog(
'reminder.notify',
data=self.build_feedback_payload(timestamp))
time.sleep(1) time.sleep(1)
self.speak_dialog('reminder.stop') self.speak_dialog('reminder.stop')
time.sleep(self.repeat_time) time.sleep(self.repeat_time)
@ -78,7 +83,8 @@ class ReminderSkill(ScheduledCRUDSkill):
def add(self, date, message): def add(self, date, message):
utterance = message.metadata.get('utterance').lower() utterance = message.metadata.get('utterance').lower()
utterance = utterance.replace(message.metadata.get('ReminderSkillCreateVerb'), '') utterance = utterance.replace(
message.metadata.get('ReminderSkillCreateVerb'), '')
utterance = self.__fix_pronouns(utterance) utterance = self.__fix_pronouns(utterance)
self.repeat_data[date] = self.time_rules.get_week_days(utterance) self.repeat_data[date] = self.time_rules.get_week_days(utterance)
self.data[date] = self.__remove_time(utterance).strip() self.data[date] = self.__remove_time(utterance).strip()

View File

@ -14,9 +14,11 @@ __author__ = 'jdorleans'
class ScheduledSkill(MycroftSkill): class ScheduledSkill(MycroftSkill):
""" """
Abstract class which provides a repeatable notification behaviour at a specified time. Abstract class which provides a repeatable notification behaviour at a
specified time.
Skills implementation inherits this class when it needs to schedule a task or a notification. Skills implementation inherits this class when it needs to schedule a task
or a notification.
""" """
DELTA_TIME = int((datetime.now() - datetime.utcnow()).total_seconds()) DELTA_TIME = int((datetime.now() - datetime.utcnow()).total_seconds())
@ -53,7 +55,8 @@ class ScheduledSkill(MycroftSkill):
return mktime(self.calendar.parse(sentence)[0]) - self.DELTA_TIME return mktime(self.calendar.parse(sentence)[0]) - self.DELTA_TIME
def get_formatted_time(self, timestamp): def get_formatted_time(self, timestamp):
return datetime.fromtimestamp(timestamp).strftime(self.config_core.get('time.format')) return datetime.fromtimestamp(timestamp).strftime(
self.config_core.get('time.format'))
@abc.abstractmethod @abc.abstractmethod
def get_times(self): def get_times(self):
@ -66,11 +69,14 @@ class ScheduledSkill(MycroftSkill):
class ScheduledCRUDSkill(ScheduledSkill): class ScheduledCRUDSkill(ScheduledSkill):
""" """
Abstract CRUD class which provides a repeatable notification behaviour at a specified time. Abstract CRUD class which provides a repeatable notification behaviour at
a specified time.
It registers CRUD intents and exposes its functions to manipulate a provided ``data`` It registers CRUD intents and exposes its functions to manipulate a
provided ``data``
Skills implementation inherits this class when it needs to schedule a task or a notification with a provided data Skills implementation inherits this class when it needs to schedule a task
or a notification with a provided data
that can be manipulated by CRUD commands. that can be manipulated by CRUD commands.
E.g. CRUD operations for a Reminder Skill E.g. CRUD operations for a Reminder Skill
@ -96,9 +102,12 @@ class ScheduledCRUDSkill(ScheduledSkill):
self.load_repeat_data() self.load_repeat_data()
self.load_data_files(self.basedir) self.load_data_files(self.basedir)
self.register_regex("(?P<" + self.name + "Amount>\d+)") self.register_regex("(?P<" + self.name + "Amount>\d+)")
self.register_intent(self.build_intent_create().build(), self.handle_create) self.register_intent(
self.register_intent(self.build_intent_list().build(), self.handle_list) self.build_intent_create().build(), self.handle_create)
self.register_intent(self.build_intent_delete().build(), self.handle_delete) self.register_intent(
self.build_intent_list().build(), self.handle_list)
self.register_intent(
self.build_intent_delete().build(), self.handle_delete)
self.schedule() self.schedule()
@abc.abstractmethod @abc.abstractmethod
@ -110,14 +119,17 @@ class ScheduledCRUDSkill(ScheduledSkill):
pass pass
def build_intent_create(self): def build_intent_create(self):
return IntentBuilder(self.name + 'CreateIntent').require(self.name + 'CreateVerb') return IntentBuilder(
self.name + 'CreateIntent').require(self.name + 'CreateVerb')
def build_intent_list(self): def build_intent_list(self):
return IntentBuilder(self.name + 'ListIntent').require(self.name + 'ListVerb') \ return IntentBuilder(
self.name + 'ListIntent').require(self.name + 'ListVerb') \
.optionally(self.name + 'Amount').require(self.name + 'Keyword') .optionally(self.name + 'Amount').require(self.name + 'Keyword')
def build_intent_delete(self): def build_intent_delete(self):
return IntentBuilder(self.name + 'DeleteIntent').require(self.name + 'DeleteVerb') \ return IntentBuilder(
self.name + 'DeleteIntent').require(self.name + 'DeleteVerb') \
.optionally(self.name + 'Amount').require(self.name + 'Keyword') .optionally(self.name + 'Amount').require(self.name + 'Keyword')
def get_times(self): def get_times(self):
@ -136,7 +148,8 @@ class ScheduledCRUDSkill(ScheduledSkill):
self.speak_dialog('schedule.datetime.error') self.speak_dialog('schedule.datetime.error')
def feedback_create(self, utc_time): def feedback_create(self, utc_time):
self.speak_dialog('schedule.create', data=self.build_feedback_payload(utc_time)) self.speak_dialog(
'schedule.create', data=self.build_feedback_payload(utc_time))
def add_sync(self, utc_time, message): def add_sync(self, utc_time, message):
with self.LOCK: with self.LOCK:
@ -193,7 +206,8 @@ class ScheduledCRUDSkill(ScheduledSkill):
self.speak_dialog('schedule.list.empty') self.speak_dialog('schedule.list.empty')
def feedback_list(self, utc_time): def feedback_list(self, utc_time):
self.speak_dialog('schedule.list', data=self.build_feedback_payload(utc_time)) self.speak_dialog(
'schedule.list', data=self.build_feedback_payload(utc_time))
def build_feedback_payload(self, utc_time): def build_feedback_payload(self, utc_time):
timestamp = self.convert_local(float(utc_time)) timestamp = self.convert_local(float(utc_time))
@ -222,7 +236,8 @@ class ScheduledCRUDSkill(ScheduledSkill):
if amount > 1: if amount > 1:
self.speak_dialog('schedule.delete.many', data={'amount': amount}) self.speak_dialog('schedule.delete.many', data={'amount': amount})
else: else:
self.speak_dialog('schedule.delete.single', data={'amount': amount}) self.speak_dialog(
'schedule.delete.single', data={'amount': amount})
# TODO - Localization # TODO - Localization
def get_amount(self, message, default=None): def get_amount(self, message, default=None):

View File

@ -12,20 +12,25 @@ LOGGER = getLogger(__name__)
class SendSMSSkill(MycroftSkill): class SendSMSSkill(MycroftSkill):
DBUS_CMD = ["dbus-send", "--print-reply", "--dest=com.canonical.TelephonyServiceHandler", DBUS_CMD = ["dbus-send", "--print-reply",
"/com/canonical/TelephonyServiceHandler", "com.canonical.TelephonyServiceHandler.SendMessage"] "--dest=com.canonical.TelephonyServiceHandler",
"/com/canonical/TelephonyServiceHandler",
"com.canonical.TelephonyServiceHandler.SendMessage"]
def __init__(self): def __init__(self):
super(SendSMSSkill, self).__init__(name="SendSMSSkill") super(SendSMSSkill, self).__init__(name="SendSMSSkill")
self.contacts = {'jonathan': '12345678', 'ryan': '23456789', 'sean': '34567890'} # TODO - Use API self.contacts = {'jonathan': '12345678', 'ryan': '23456789',
'sean': '34567890'} # TODO - Use API
def initialize(self): def initialize(self):
self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us'))
prefixes = ['tell', 'text', 'message'] # TODO - i10n prefixes = ['tell', 'text', 'message'] # TODO - i10n
self.__register_prefixed_regex(prefixes, "(?P<Contact>\w*) (?P<Message>.*)") self.__register_prefixed_regex(
prefixes, "(?P<Contact>\w*) (?P<Message>.*)")
intent = IntentBuilder("SendSMSIntent").require("SendSMSKeyword").require("Contact").require("Message").build() intent = IntentBuilder("SendSMSIntent").require(
"SendSMSKeyword").require("Contact").require("Message").build()
self.register_intent(intent, self.handle_intent) self.register_intent(intent, self.handle_intent)
def __register_prefixed_regex(self, prefixes, suffix_regex): def __register_prefixed_regex(self, prefixes, suffix_regex):
@ -53,7 +58,11 @@ class SendSMSSkill(MycroftSkill):
subprocess.call(cmd) subprocess.call(cmd)
def __notify(self, contact, number, msg): def __notify(self, contact, number, msg):
self.emitter.emit(Message("send_sms", metadata={'contact': contact, 'number': number, 'message': msg})) self.emitter.emit(
Message(
"send_sms",
metadata={'contact': contact, 'number': number,
'message': msg}))
def stop(self): def stop(self):
pass pass

View File

@ -14,10 +14,12 @@ class SpellingSkill(MycroftSkill):
def initialize(self): def initialize(self):
self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us'))
prefixes = ['spell', 'spell the word', 'spelling of', 'spelling of the word'] prefixes = [
'spell', 'spell the word', 'spelling of', 'spelling of the word']
self.__register_prefixed_regex(prefixes, "(?P<Word>\w+)") self.__register_prefixed_regex(prefixes, "(?P<Word>\w+)")
intent = IntentBuilder("SpellingIntent").require("SpellingKeyword").require("Word").build() intent = IntentBuilder("SpellingIntent").require(
"SpellingKeyword").require("Word").build()
self.register_intent(intent, self.handle_intent) self.register_intent(intent, self.handle_intent)
def __register_prefixed_regex(self, prefixes, suffix_regex): def __register_prefixed_regex(self, prefixes, suffix_regex):

View File

@ -27,14 +27,17 @@ class AbstractTimeRules(object):
regex = regex.replace('<week_days>', self.rules.get('week_days')) regex = regex.replace('<week_days>', self.rules.get('week_days'))
regex = regex.replace('<months>', self.rules.get('months')) regex = regex.replace('<months>', self.rules.get('months'))
regex = regex.replace('<mealtimes>', self.rules.get('mealtimes')) regex = regex.replace('<mealtimes>', self.rules.get('mealtimes'))
regex = regex.replace('<celebrations>', self.rules.get('celebrations')) regex = regex.replace(
regex = regex.replace('<repeat_time_regex>', self.rules.get('repeat_time_regex')) '<celebrations>', self.rules.get('celebrations'))
regex = regex.replace(
'<repeat_time_regex>', self.rules.get('repeat_time_regex'))
self.rules.get('time_regex')[idx] = regex.lower() self.rules.get('time_regex')[idx] = regex.lower()
# days is an array starting from Monday (0) to Sunday (6) # days is an array starting from Monday (0) to Sunday (6)
def get_week_days(self, sentence): def get_week_days(self, sentence):
days = None days = None
pattern = re.compile(self.rules.get('repeat_time_regex'), re.IGNORECASE) pattern = re.compile(
self.rules.get('repeat_time_regex'), re.IGNORECASE)
result = pattern.search(sentence) result = pattern.search(sentence)
if result: if result:
group = result.group() group = result.group()
@ -63,19 +66,30 @@ class TimeRulesEnUs(AbstractTimeRules):
'time_advs': 'today|tonight|tomorrow', 'time_advs': 'today|tonight|tomorrow',
'time_units': 'second|minute|hour|day|week|month|year', 'time_units': 'second|minute|hour|day|week|month|year',
'day_parts': 'dawn|morning|noon|afternoon|evening|night|midnight', 'day_parts': 'dawn|morning|noon|afternoon|evening|night|midnight',
'week_days': 'monday|tuesday|wednesday|thursday|friday|saturday|sunday', 'week_days': (
'months': 'january|february|march|april|may|june|july|august|october|september|november|december', 'monday|tuesday|wednesday|thursday|friday|saturday|sunday'),
'mealtimes': 'breakfast|lunchtime|teatime|dinnertime|lunch time|tea time|dinner time', 'months': (
'january|february|march|april|may|june|july|august|october|'
'september|november|december'),
'mealtimes': (
'breakfast|lunchtime|teatime|dinnertime|lunch time|tea time|'
'dinner time'),
'celebrations': 'easter|christmas', 'celebrations': 'easter|christmas',
'repeat_time_regex': 'repeat_time_regex': (
'((every|each|all) (single )?(day|(<week_days>)s?( (and )?(<week_days>)s?)*))|daily|everyday', '((every|each|all) (single )?(day|(<week_days>)s?( (and )?'
'(<week_days>)s?)*))|daily|everyday'),
'time_regex': [ 'time_regex': [
'(<time_advs>)', '((at|in the|during the|tomorrow)\s)?(<day_parts>)', '(<time_advs>)',
'(in )?(a|an|one|two|\d+\.?\d*) (<time_units>)s?( later)?', 'on (<week_days>)', '((at|in the|during the|tomorrow)\s)?(<day_parts>)',
'(on|the) (\d+(rd|st|nd|th)?\s)?(<months>)( the )?(\s\d+(rd|st|nd|th)?)?(\s?,?\s?\d*)?', '(in )?(a|an|one|two|\d+\.?\d*) (<time_units>)s?( later)?',
'on (<week_days>)',
'(on|the) (\d+(rd|st|nd|th)?\s)?(<months>)( the )?'
'(\s\d+(rd|st|nd|th)?)?(\s?,?\s?\d*)?',
'in (\d\d\d\d)', 'at (<mealtimes>|<celebrations>)', 'in (\d\d\d\d)', 'at (<mealtimes>|<celebrations>)',
"(at|by) \d+((:| and )\d+)?( and a (quarter|half))?\s?((a\.?m\.?|p\.?m\.?)|o'clock)?", "(at|by) \d+((:| and )\d+)?( and a (quarter|half))?"
'(in |in the )?next (<time_units>|<day_parts>|<week_days>|<months>|<mealtimes>|<celebrations>)s?', "\s?((a\.?m\.?|p\.?m\.?)|o'clock)?",
'(in |in the )?next (<time_units>|<day_parts>|<week_days>'
'|<months>|<mealtimes>|<celebrations>)s?',
'<repeat_time_regex>' '<repeat_time_regex>'
] ]
} }
@ -83,7 +97,8 @@ class TimeRulesEnUs(AbstractTimeRules):
def build_repeat_time_regex(self): def build_repeat_time_regex(self):
week_days = self.rules.get('week_days') week_days = self.rules.get('week_days')
repeat_time_regex = self.rules.get('repeat_time_regex') repeat_time_regex = self.rules.get('repeat_time_regex')
self.rules['repeat_time_regex'] = repeat_time_regex.replace('<week_days>', week_days) self.rules['repeat_time_regex'] = repeat_time_regex.replace(
'<week_days>', week_days)
def is_all_days(self, group): def is_all_days(self, group):
for d in [' day', 'daily', 'everyday']: for d in [' day', 'daily', 'everyday']:

View File

@ -12,7 +12,8 @@ LOGGER = getLogger(__name__)
class VolumeSkill(MycroftSkill): class VolumeSkill(MycroftSkill):
VOLUMES = {0: 0, 1: 15, 2: 25, 3: 35, 4: 45, 5: 55, 6: 65, 7: 70, 8: 80, 9: 90, 10: 95, 11: 100} VOLUMES = {0: 0, 1: 15, 2: 25, 3: 35, 4: 45, 5: 55, 6: 65, 7: 70, 8: 80,
9: 90, 10: 95, 11: 100}
def __init__(self): def __init__(self):
super(VolumeSkill, self).__init__(name="VolumeSkill") super(VolumeSkill, self).__init__(name="VolumeSkill")
@ -24,19 +25,24 @@ class VolumeSkill(MycroftSkill):
self.__build_set_volume() self.__build_set_volume()
def __build_set_volume(self): def __build_set_volume(self):
intent = IntentBuilder("SetVolumeIntent").require("VolumeKeyword").require("VolumeAmount").build() intent = IntentBuilder("SetVolumeIntent").require(
"VolumeKeyword").require("VolumeAmount").build()
self.register_intent(intent, self.handle_set_volume) self.register_intent(intent, self.handle_set_volume)
intent = IntentBuilder("IncreaseVolumeIntent").require("IncreaseVolumeKeyword").build() intent = IntentBuilder("IncreaseVolumeIntent").require(
"IncreaseVolumeKeyword").build()
self.register_intent(intent, self.handle_increase_volume) self.register_intent(intent, self.handle_increase_volume)
intent = IntentBuilder("DecreaseVolumeIntent").require("DecreaseVolumeKeyword").build() intent = IntentBuilder("DecreaseVolumeIntent").require(
"DecreaseVolumeKeyword").build()
self.register_intent(intent, self.handle_decrease_volume) self.register_intent(intent, self.handle_decrease_volume)
intent = IntentBuilder("MuteVolumeIntent").require("MuteVolumeKeyword").build() intent = IntentBuilder("MuteVolumeIntent").require(
"MuteVolumeKeyword").build()
self.register_intent(intent, self.handle_mute_volume) self.register_intent(intent, self.handle_mute_volume)
intent = IntentBuilder("ResetVolumeIntent").require("ResetVolumeKeyword").build() intent = IntentBuilder("ResetVolumeIntent").require(
"ResetVolumeKeyword").build()
self.register_intent(intent, self.handle_reset_volume) self.register_intent(intent, self.handle_reset_volume)
def handle_set_volume(self, message): def handle_set_volume(self, message):
@ -60,7 +66,9 @@ class VolumeSkill(MycroftSkill):
def handle_reset_volume(self, message): def handle_reset_volume(self, message):
Mixer().setvolume(self.default_volume) Mixer().setvolume(self.default_volume)
self.speak_dialog('reset.volume', data={'volume': self.get_volume_code(self.default_volume)}) self.speak_dialog(
'reset.volume',
data={'volume': self.get_volume_code(self.default_volume)})
def __update_volume(self, level=0): def __update_volume(self, level=0):
mixer = Mixer() mixer = Mixer()

View File

@ -19,7 +19,8 @@ class WeatherSkill(MycroftSkill):
@property @property
def owm(self): def owm(self):
return OWM(API_key=self.config.get('api_key', ''), identity=IdentityManager().get()) return OWM(API_key=self.config.get('api_key', ''),
identity=IdentityManager().get())
def initialize(self): def initialize(self):
self.load_data_files(dirname(__file__)) self.load_data_files(dirname(__file__))
@ -30,16 +31,19 @@ class WeatherSkill(MycroftSkill):
self.__build_next_day_intent() self.__build_next_day_intent()
def __build_current_intent(self): def __build_current_intent(self):
intent = IntentBuilder("CurrentWeatherIntent").require("WeatherKeyword").optionally("Location").build() intent = IntentBuilder("CurrentWeatherIntent").require(
"WeatherKeyword").optionally("Location").build()
self.register_intent(intent, self.handle_current_intent) self.register_intent(intent, self.handle_current_intent)
def __build_next_hour_intent(self): def __build_next_hour_intent(self):
intent = IntentBuilder("NextHoursWeatherIntent").require("WeatherKeyword").optionally("Location") \ intent = IntentBuilder("NextHoursWeatherIntent").require(
"WeatherKeyword").optionally("Location") \
.require("NextHours").build() .require("NextHours").build()
self.register_intent(intent, self.handle_next_hour_intent) self.register_intent(intent, self.handle_next_hour_intent)
def __build_next_day_intent(self): def __build_next_day_intent(self):
intent = IntentBuilder("NextDayWeatherIntent").require("WeatherKeyword").optionally("Location") \ intent = IntentBuilder("NextDayWeatherIntent").require(
"WeatherKeyword").optionally("Location") \
.require("NextDay").build() .require("NextDay").build()
self.register_intent(intent, self.handle_next_day_intent) self.register_intent(intent, self.handle_next_day_intent)
@ -66,7 +70,8 @@ class WeatherSkill(MycroftSkill):
def handle_next_hour_intent(self, message): def handle_next_hour_intent(self, message):
try: try:
location = message.metadata.get("Location", self.location) location = message.metadata.get("Location", self.location)
weather = self.owm.three_hours_forecast(location).get_forecast().get_weathers()[0] weather = self.owm.three_hours_forecast(
location).get_forecast().get_weathers()[0]
data = self.__build_data_condition(location, weather) data = self.__build_data_condition(location, weather)
self.speak_dialog('hour.weather', data) self.speak_dialog('hour.weather', data)
except APICallError as e: except APICallError as e:
@ -77,15 +82,19 @@ class WeatherSkill(MycroftSkill):
def handle_next_day_intent(self, message): def handle_next_day_intent(self, message):
try: try:
location = message.metadata.get("Location", self.location) location = message.metadata.get("Location", self.location)
weather = self.owm.daily_forecast(location).get_forecast().get_weathers()[1] weather = self.owm.daily_forecast(
data = self.__build_data_condition(location, weather, 'day', 'min', 'max') location).get_forecast().get_weathers()[1]
data = self.__build_data_condition(
location, weather, 'day', 'min', 'max')
self.speak_dialog('tomorrow.weather', data) self.speak_dialog('tomorrow.weather', data)
except APICallError as e: except APICallError as e:
self.__api_error(e) self.__api_error(e)
except Exception as e: except Exception as e:
LOGGER.error("Error: {0}".format(e)) LOGGER.error("Error: {0}".format(e))
def __build_data_condition(self, location, weather, temp='temp', temp_min='temp_min', temp_max='temp_max'): def __build_data_condition(
self, location, weather, temp='temp', temp_min='temp_min',
temp_max='temp_max'):
data = { data = {
'location': location, 'location': location,
'scale': self.temperature, 'scale': self.temperature,

View File

@ -32,16 +32,18 @@ def OWM(API_key=None, version=constants.LATEST_OWM_API_VERSION,
support you are currently requesting. Please be aware that malformed support you are currently requesting. Please be aware that malformed
user-defined configuration modules can lead to unwanted behaviour! user-defined configuration modules can lead to unwanted behaviour!
:type config_module: str (eg: 'mypackage.mysubpackage.myconfigmodule') :type config_module: str (eg: 'mypackage.mysubpackage.myconfigmodule')
:param language: the language in which you want text results to be returned. :param language: the language in which you want text results to be
It's a two-characters string, eg: "en", "ru", "it". Defaults to: returned. It's a two-characters string, eg: "en", "ru", "it". Defaults
``None``, which means use the default language. to: ``None``, which means use the default language.
:type language: str :type language: str
:returns: an instance of a proper *OWM* subclass :returns: an instance of a proper *OWM* subclass
:raises: *ValueError* when unsupported OWM API versions are provided :raises: *ValueError* when unsupported OWM API versions are provided
""" """
if version == "2.5": if version == "2.5":
if config_module is None: if config_module is None:
config_module = "mycroft.skills.weather.owm_repackaged.configuration25_mycroft" config_module = (
"mycroft.skills.weather.owm_repackaged."
"configuration25_mycroft")
cfg_module = __import__(config_module, fromlist=['']) cfg_module = __import__(config_module, fromlist=[''])
from mycroft.skills.weather.owm_repackaged.owm25 import OWM25 from mycroft.skills.weather.owm_repackaged.owm25 import OWM25
if language is None: if language is None:

View File

@ -62,8 +62,7 @@ weather_code_registry = weathercoderegistry.WeatherCodeRegistry({
"rain": [{ "rain": [{
"start": 500, "start": 500,
"end": 531 "end": 531
}, }, {
{
"start": 300, "start": 300,
"end": 321 "end": 321
}], }],
@ -94,24 +93,21 @@ weather_code_registry = weathercoderegistry.WeatherCodeRegistry({
"tornado": [{ "tornado": [{
"start": 781, "start": 781,
"end": 781 "end": 781
}, }, {
{
"start": 900, "start": 900,
"end": 900 "end": 900
}], }],
"storm": [{ "storm": [{
"start": 901, "start": 901,
"end": 901 "end": 901
}, }, {
{
"start": 960, "start": 960,
"end": 961 "end": 961
}], }],
"hurricane": [{ "hurricane": [{
"start": 902, "start": 902,
"end": 902 "end": 902
}, }, {
{
"start": 962, "start": 962,
"end": 962 "end": 962
}] }]

View File

@ -32,8 +32,9 @@ class OWM25(owm.OWM):
:param cache: a concrete implementation of class *OWMCache* serving as the :param cache: a concrete implementation of class *OWMCache* serving as the
cache provider (defaults to a *NullCache* instance) cache provider (defaults to a *NullCache* instance)
:type cache: an *OWMCache* concrete instance :type cache: an *OWMCache* concrete instance
:param language: the language in which you want text results to be returned. :param language: the language in which you want text results to be
It's a two-characters string, eg: "en", "ru", "it". Defaults to: "en" returned. It's a two-characters string, eg: "en", "ru", "it". Defaults
to: "en"
:type language: str :type language: str
:returns: an *OWM25* instance :returns: an *OWM25* instance
@ -44,14 +45,16 @@ class OWM25(owm.OWM):
if API_key is not None: if API_key is not None:
OWM25._assert_is_string("API_key", API_key) OWM25._assert_is_string("API_key", API_key)
self._API_key = API_key self._API_key = API_key
self._httpclient = owmhttpclient.OWMHTTPClient(API_key, cache, identity) self._httpclient = owmhttpclient.OWMHTTPClient(
API_key, cache, identity)
self._language = language self._language = language
@staticmethod @staticmethod
def _assert_is_string(name, value): def _assert_is_string(name, value):
try: try:
# Python 2.x # Python 2.x
assert isinstance(value, basestring), "'%s' must be a str" % (name,) assert(isinstance(value, basestring),
"'%s' must be a str" % (name,))
except NameError: except NameError:
# Python 3.x # Python 3.x
assert isinstance(value, str), "'%s' must be a str" % (name,) assert isinstance(value, str), "'%s' must be a str" % (name,)
@ -114,8 +117,8 @@ class OWM25(owm.OWM):
def city_id_registry(self): def city_id_registry(self):
""" """
Gives the *CityIDRegistry* singleton instance that can be used to lookup Gives the *CityIDRegistry* singleton instance that can be used to
for city IDs. lookup for city IDs.
:returns: a *CityIDRegistry* instance :returns: a *CityIDRegistry* instance
""" """
@ -149,8 +152,9 @@ class OWM25(owm.OWM):
reached reached
""" """
OWM25._assert_is_string("name", name) OWM25._assert_is_string("name", name)
json_data = self._httpclient.call_API(OBSERVATION_URL, json_data = self._httpclient.call_API(
{'q': name,'lang': self._language}) OBSERVATION_URL,
{'q': name, 'lang': self._language})
return self._parsers['observation'].parse_JSON(json_data) return self._parsers['observation'].parse_JSON(json_data)
def weather_at_coords(self, lat, lon): def weather_at_coords(self, lat, lon):
@ -240,8 +244,8 @@ class OWM25(owm.OWM):
def weather_at_station(self, station_id): def weather_at_station(self, station_id):
""" """
Queries the OWM web API for the weather currently observed by a specific Queries the OWM web API for the weather currently observed by a
meteostation (eg: 29584) specific meteostation (eg: 29584)
:param station_id: the meteostation ID :param station_id: the meteostation ID
:type station_id: int :type station_id: int
@ -272,8 +276,8 @@ class OWM25(owm.OWM):
:param lon_top_left: longitude for top-left of bounding box :param lon_top_left: longitude for top-left of bounding box
must be between -180.0 and 180.0 must be between -180.0 and 180.0
:type lon_top_left: int/float :type lon_top_left: int/float
:param lat_bottom_right: latitude for bottom-right of bounding box, must :param lat_bottom_right: latitude for bottom-right of bounding box,
be between -90.0 and 90.0 must be between -90.0 and 90.0
:type lat_bottom_right: int/float :type lat_bottom_right: int/float
:param lon_bottom_right: longitude for bottom-right of bounding box, :param lon_bottom_right: longitude for bottom-right of bounding box,
must be between -180.0 and 180.0 must be between -180.0 and 180.0
@ -291,34 +295,34 @@ class OWM25(owm.OWM):
negative values are provided for limit negative values are provided for limit
""" """
assert type(lat_top_left) in (float, int), \ assert type(lat_top_left) in (float, int), \
"'lat_top_left' must be a float" "'lat_top_left' must be a float"
assert type(lon_top_left) in (float, int), \ assert type(lon_top_left) in (float, int), \
"'lon_top_left' must be a float" "'lon_top_left' must be a float"
assert type(lat_bottom_right) in (float, int), \ assert type(lat_bottom_right) in (float, int), \
"'lat_bottom_right' must be a float" "'lat_bottom_right' must be a float"
assert type(lon_bottom_right) in (float, int), \ assert type(lon_bottom_right) in (float, int), \
"'lon_bottom_right' must be a float" "'lon_bottom_right' must be a float"
assert type(cluster) is bool, "'cluster' must be a bool" assert type(cluster) is bool, "'cluster' must be a bool"
assert type(limit) in (int, type(None)), \ assert type(limit) in (int, type(None)), \
"'limit' must be an int or None" "'limit' must be an int or None"
if lat_top_left < -90.0 or lat_top_left > 90.0: if lat_top_left < -90.0 or lat_top_left > 90.0:
raise ValueError("'lat_top_left' value must be between -90 and 90") raise ValueError("'lat_top_left' value must be between -90 and 90")
if lon_top_left < -180.0 or lon_top_left > 180.0: if lon_top_left < -180.0 or lon_top_left > 180.0:
raise ValueError("'lon_top_left' value must be between -180 and" \ raise ValueError("'lon_top_left' value must be between -180 and" +
+" 180") " 180")
if lat_bottom_right < -90.0 or lat_bottom_right > 90.0: if lat_bottom_right < -90.0 or lat_bottom_right > 90.0:
raise ValueError("'lat_bottom_right' value must be between -90" \ raise ValueError("'lat_bottom_right' value must be between -90" +
+" and 90") " and 90")
if lon_bottom_right < -180.0 or lon_bottom_right > 180.0: if lon_bottom_right < -180.0 or lon_bottom_right > 180.0:
raise ValueError("'lon_bottom_right' value must be between -180 "\ raise ValueError("'lon_bottom_right' value must be between -180 " +
+"and 180") "and 180")
if limit is not None and limit < 1: if limit is not None and limit < 1:
raise ValueError("'limit' must be None or greater than zero") raise ValueError("'limit' must be None or greater than zero")
params = {'bbox': ','.join([str(lon_top_left), params = {'bbox': ','.join([str(lon_top_left),
str(lat_top_left), str(lat_top_left),
str(lon_bottom_right), str(lon_bottom_right),
str(lat_bottom_right)]), str(lat_bottom_right)]),
'cluster': 'yes' if cluster else 'no',} 'cluster': 'yes' if cluster else 'no', }
if limit is not None: if limit is not None:
params['cnt'] = limit params['cnt'] = limit
@ -376,8 +380,9 @@ class OWM25(owm.OWM):
reached reached
""" """
OWM25._assert_is_string("name", name) OWM25._assert_is_string("name", name)
json_data = self._httpclient.call_API(THREE_HOURS_FORECAST_URL, json_data = self._httpclient.call_API(
{'q': name, 'lang': self._language}) THREE_HOURS_FORECAST_URL,
{'q': name, 'lang': self._language})
forecast = self._parsers['forecast'].parse_JSON(json_data) forecast = self._parsers['forecast'].parse_JSON(json_data)
if forecast is not None: if forecast is not None:
forecast.set_interval("3h") forecast.set_interval("3h")
@ -487,7 +492,8 @@ class OWM25(owm.OWM):
def daily_forecast_at_coords(self, lat, lon, limit=None): def daily_forecast_at_coords(self, lat, lon, limit=None):
""" """
Queries the OWM web API for daily weather forecast for the specified Queries the OWM web API for daily weather forecast for the specified
geographic coordinate (eg: latitude: 51.5073509, longitude: -0.1277583). geographic coordinate (eg: latitude: 51.5073509,
longitude: -0.1277583).
A *Forecaster* object is returned, containing a *Forecast* instance A *Forecaster* object is returned, containing a *Forecast* instance
covering a global streak of fourteen days by default: this instance covering a global streak of fourteen days by default: this instance
encapsulates *Weather* objects, with a time interval of one day one encapsulates *Weather* objects, with a time interval of one day one
@ -567,7 +573,6 @@ class OWM25(owm.OWM):
else: else:
return None return None
def weather_history_at_place(self, name, start=None, end=None): def weather_history_at_place(self, name, start=None, end=None):
""" """
Queries the OWM web API for weather history for the specified location Queries the OWM web API for weather history for the specified location
@ -602,16 +607,16 @@ class OWM25(owm.OWM):
unix_start = timeformatutils.to_UNIXtime(start) unix_start = timeformatutils.to_UNIXtime(start)
unix_end = timeformatutils.to_UNIXtime(end) unix_end = timeformatutils.to_UNIXtime(end)
if unix_start >= unix_end: if unix_start >= unix_end:
raise ValueError("Error: the start time boundary must " \ raise ValueError("Error: the start time boundary must "
"precede the end time!") "precede the end time!")
current_time = time() current_time = time()
if unix_start > current_time: if unix_start > current_time:
raise ValueError("Error: the start time boundary must " \ raise ValueError("Error: the start time boundary must "
"precede the current time!") "precede the current time!")
params['start'] = str(unix_start) params['start'] = str(unix_start)
params['end'] = str(unix_end) params['end'] = str(unix_end)
else: else:
raise ValueError("Error: one of the time boundaries is None, " \ raise ValueError("Error: one of the time boundaries is None, "
"while the other is not!") "while the other is not!")
json_data = self._httpclient.call_API(CITY_WEATHER_HISTORY_URL, json_data = self._httpclient.call_API(CITY_WEATHER_HISTORY_URL,
params) params)
@ -653,16 +658,16 @@ class OWM25(owm.OWM):
unix_start = timeformatutils.to_UNIXtime(start) unix_start = timeformatutils.to_UNIXtime(start)
unix_end = timeformatutils.to_UNIXtime(end) unix_end = timeformatutils.to_UNIXtime(end)
if unix_start >= unix_end: if unix_start >= unix_end:
raise ValueError("Error: the start time boundary must " \ raise ValueError("Error: the start time boundary must "
"precede the end time!") "precede the end time!")
current_time = time() current_time = time()
if unix_start > current_time: if unix_start > current_time:
raise ValueError("Error: the start time boundary must " \ raise ValueError("Error: the start time boundary must "
"precede the current time!") "precede the current time!")
params['start'] = str(unix_start) params['start'] = str(unix_start)
params['end'] = str(unix_end) params['end'] = str(unix_end)
else: else:
raise ValueError("Error: one of the time boundaries is None, " \ raise ValueError("Error: one of the time boundaries is None, "
"while the other is not!") "while the other is not!")
json_data = self._httpclient.call_API(CITY_WEATHER_HISTORY_URL, json_data = self._httpclient.call_API(CITY_WEATHER_HISTORY_URL,
params) params)
@ -824,8 +829,9 @@ class OWM25(owm.OWM):
def __repr__(self): def __repr__(self):
return "<%s.%s - API key=%s, OWM web API version=%s, " \ return "<%s.%s - API key=%s, OWM web API version=%s, " \
"PyOWM version=%s, language=%s>" % (__name__, "PyOWM version=%s, language=%s>" % (
self.__class__.__name__, __name__,
self._API_key, self.get_API_version(), self.__class__.__name__,
self.get_version(), self._API_key, self.get_API_version(),
self._language) self.get_version(),
self._language)

View File

@ -57,19 +57,22 @@ class OWMHTTPClient(object):
else: else:
try: try:
if self._identity and self._identity.token: if self._identity and self._identity.token:
bearer_token_header = "Bearer %s:%s" % (self._identity.device_id, self._identity.token) bearer_token_header = "Bearer %s:%s" % (
self._identity.device_id, self._identity.token)
else: else:
bearer_token_header = None bearer_token_header = None
try: try:
from urllib.request import urlopen, build_opener from urllib.request import urlopen, build_opener
opener = build_opener() opener = build_opener()
if bearer_token_header: if bearer_token_header:
opener.addheaders = [('Authorization', bearer_token_header)] opener.addheaders = [
('Authorization', bearer_token_header)]
except ImportError: except ImportError:
from urllib2 import urlopen, build_opener from urllib2 import urlopen, build_opener
opener = build_opener() opener = build_opener()
if bearer_token_header: if bearer_token_header:
opener.addheaders = [('Authorization', bearer_token_header)] opener.addheaders = [
('Authorization', bearer_token_header)]
response = opener.open(url, None, timeout) response = opener.open(url, None, timeout)
except HTTPError as e: except HTTPError as e:
raise api_call_error.APICallError(str(e.reason), e) raise api_call_error.APICallError(str(e.reason), e)

View File

@ -20,16 +20,22 @@ class WikipediaSkill(MycroftSkill):
self.max_results = int(self.config['max_results']) self.max_results = int(self.config['max_results'])
self.max_phrases = int(self.config['max_phrases']) self.max_phrases = int(self.config['max_phrases'])
self.question = 'Would you like to know more about ' # TODO - i10n self.question = 'Would you like to know more about ' # TODO - i10n
self.feedback_prefix = read_stripped_lines(join(dirname(__file__), 'dialog', self.lang, 'FeedbackPrefix.dialog')) self.feedback_prefix = read_stripped_lines(
self.feedback_search = read_stripped_lines(join(dirname(__file__), 'dialog', self.lang, 'FeedbackSearch.dialog')) join(dirname(__file__), 'dialog', self.lang,
'FeedbackPrefix.dialog'))
self.feedback_search = read_stripped_lines(
join(dirname(__file__), 'dialog', self.lang,
'FeedbackSearch.dialog'))
def initialize(self): def initialize(self):
self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us'))
prefixes = ['wiki', 'wikipedia', 'tell me about', 'tell us about', 'who is', 'who was'] # TODO - i10n prefixes = ['wiki', 'wikipedia', 'tell me about', 'tell us about',
'who is', 'who was'] # TODO - i10n
self.__register_prefixed_regex(prefixes, "(?P<ArticleTitle>.*)") self.__register_prefixed_regex(prefixes, "(?P<ArticleTitle>.*)")
intent = IntentBuilder("WikipediaIntent").require("WikipediaKeyword").require("ArticleTitle").build() intent = IntentBuilder("WikipediaIntent").require(
"WikipediaKeyword").require("ArticleTitle").build()
self.register_intent(intent, self.handle_intent) self.register_intent(intent, self.handle_intent)
def __register_prefixed_regex(self, prefixes, suffix_regex): def __register_prefixed_regex(self, prefixes, suffix_regex):
@ -41,7 +47,9 @@ class WikipediaSkill(MycroftSkill):
title = message.metadata.get("ArticleTitle") title = message.metadata.get("ArticleTitle")
self.__feedback_search(title) self.__feedback_search(title)
results = wiki.search(title, self.max_results) results = wiki.search(title, self.max_results)
summary = re.sub(r'\([^)]*\)|/[^/]*/', '', wiki.summary(results[0], self.max_phrases)) summary = re.sub(
r'\([^)]*\)|/[^/]*/', '',
wiki.summary(results[0], self.max_phrases))
self.speak(summary) self.speak(summary)
except wiki.exceptions.DisambiguationError as e: except wiki.exceptions.DisambiguationError as e:
@ -55,7 +63,8 @@ class WikipediaSkill(MycroftSkill):
def __feedback_search(self, title): def __feedback_search(self, title):
prefix = self.feedback_prefix[randrange(len(self.feedback_prefix))] prefix = self.feedback_prefix[randrange(len(self.feedback_prefix))]
feedback = self.feedback_search[randrange(len(self.feedback_search))] feedback = self.feedback_search[randrange(len(self.feedback_search))]
sentence = feedback.replace('<prefix>', prefix).replace('<title>', title) sentence = feedback.replace('<prefix>', prefix).replace(
'<title>', title)
self.speak(sentence) self.speak(sentence)
def __ask_more_about(self, opts): def __ask_more_about(self, opts):

View File

@ -18,14 +18,19 @@ logger = getLogger(__name__)
class EnglishQuestionParser(object): class EnglishQuestionParser(object):
""" """
Poor-man's english question parser. Not even close to conclusive, but appears to construct some decent w|a queries Poor-man's english question parser. Not even close to conclusive, but
and responses. appears to construct some decent w|a queries and responses.
""" """
def __init__(self): def __init__(self):
self.regexes = [ self.regexes = [
re.compile(".*(?P<QuestionWord>who|what|when|where|why|which) (?P<Query1>.*) (?P<QuestionVerb>is|are|was|were) (?P<Query2>.*)"), re.compile(
re.compile(".*(?P<QuestionWord>who|what|when|where|why|which) (?P<QuestionVerb>\w+) (?P<Query>.*)") ".*(?P<QuestionWord>who|what|when|where|why|which) "
"(?P<Query1>.*) (?P<QuestionVerb>is|are|was|were) "
"(?P<Query2>.*)"),
re.compile(
".*(?P<QuestionWord>who|what|when|where|why|which) "
"(?P<QuestionVerb>\w+) (?P<Query>.*)")
] ]
def _normalize(self, groupdict): def _normalize(self, groupdict):
@ -35,7 +40,8 @@ class EnglishQuestionParser(object):
return { return {
'QuestionWord': groupdict.get('QuestionWord'), 'QuestionWord': groupdict.get('QuestionWord'),
'QuestionVerb': groupdict.get('QuestionVerb'), 'QuestionVerb': groupdict.get('QuestionVerb'),
'Query': ' '.join([groupdict.get('Query1'), groupdict.get('Query2')]) 'Query': ' '.join([groupdict.get('Query1'), groupdict.get(
'Query2')])
} }
def parse(self, utterance): def parse(self, utterance):
@ -85,7 +91,8 @@ class WolframAlphaSkill(MycroftSkill):
self.emitter.on('intent_failure', self.handle_fallback) self.emitter.on('intent_failure', self.handle_fallback)
def handle_fallback(self, message): def handle_fallback(self, message):
logger.debug("Could not determine intent, falling back to WolframAlpha Skill!") logger.debug(
"Could not determine intent, falling back to WolframAlpha Skill!")
utterance = message.metadata.get('utterance') utterance = message.metadata.get('utterance')
parsed_question = self.question_parser.parse(utterance) parsed_question = self.question_parser.parse(utterance)
@ -96,7 +103,9 @@ class WolframAlphaSkill(MycroftSkill):
self.speak("I am searching for " + utterance) self.speak("I am searching for " + utterance)
query = utterance query = utterance
if parsed_question: if parsed_question:
query = "%s %s %s" % (parsed_question.get('QuestionWord'), parsed_question.get('QuestionVerb'), parsed_question.get('Query')) query = "%s %s %s" % (parsed_question.get('QuestionWord'),
parsed_question.get('QuestionVerb'),
parsed_question.get('Query'))
try: try:
res = self.client.query(query) res = self.client.query(query)
@ -115,7 +124,8 @@ class WolframAlphaSkill(MycroftSkill):
result = self.__find_value(res.pods, 'Value') result = self.__find_value(res.pods, 'Value')
if not result: if not result:
result = self.__find_value(res.pods, 'DecimalApproximation') result = self.__find_value(
res.pods, 'DecimalApproximation')
result = result[:5] result = result[:5]
except: except:
pass pass
@ -125,7 +135,8 @@ class WolframAlphaSkill(MycroftSkill):
verb = "is" verb = "is"
structured_syntax_regex = re.compile(".*(\||\[|\\\\|\]).*") structured_syntax_regex = re.compile(".*(\||\[|\\\\|\]).*")
if parsed_question: if parsed_question:
if not input_interpretation or structured_syntax_regex.match(input_interpretation): if not input_interpretation or structured_syntax_regex.match(
input_interpretation):
input_interpretation = parsed_question.get('Query') input_interpretation = parsed_question.get('Query')
verb = parsed_question.get('QuestionVerb') verb = parsed_question.get('QuestionVerb')

View File

@ -12,7 +12,8 @@ class TTS(object):
""" """
TTS abstract class to be implemented by all TTS engines. TTS abstract class to be implemented by all TTS engines.
It aggregates the minimum required parameters and exposes ``execute(sentence)`` function. It aggregates the minimum required parameters and exposes
``execute(sentence)`` function.
""" """
def __init__(self, lang, voice, filename='/tmp/tts.wav'): def __init__(self, lang, voice, filename='/tmp/tts.wav'):
@ -30,7 +31,8 @@ class TTSValidator(object):
""" """
TTS Validator abstract class to be implemented by all TTS engines. TTS Validator abstract class to be implemented by all TTS engines.
It exposes and implements ``validate(tts)`` function as a template to validate the TTS engines. It exposes and implements ``validate(tts)`` function as a template to
validate the TTS engines.
""" """
def __init__(self): def __init__(self):
@ -45,16 +47,19 @@ class TTSValidator(object):
def __validate_instance(self, tts): def __validate_instance(self, tts):
instance = self.get_instance() instance = self.get_instance()
if not isinstance(tts, instance): if not isinstance(tts, instance):
raise AttributeError('tts must be instance of ' + instance.__name__) raise AttributeError(
'tts must be instance of ' + instance.__name__)
LOGGER.debug('TTS: ' + str(instance)) LOGGER.debug('TTS: ' + str(instance))
def __validate_filename(self, filename): def __validate_filename(self, filename):
if not (filename and filename.endswith('.wav')): if not (filename and filename.endswith('.wav')):
raise AttributeError('filename: ' + filename + ' must be a .wav file!') raise AttributeError(
'filename: ' + filename + ' must be a .wav file!')
dir_path = dirname(filename) dir_path = dirname(filename)
if not (exists(dir_path) and isdir(dir_path)): if not (exists(dir_path) and isdir(dir_path)):
raise AttributeError('filename: ' + filename + ' is not a valid file path!') raise AttributeError(
'filename: ' + filename + ' is not a valid file path!')
LOGGER.debug('Filename: ' + filename) LOGGER.debug('Filename: ' + filename)
@abc.abstractmethod @abc.abstractmethod

View File

@ -12,7 +12,8 @@ class ESpeak(TTS):
super(ESpeak, self).__init__(lang, voice) super(ESpeak, self).__init__(lang, voice)
def execute(self, sentence): def execute(self, sentence):
subprocess.call(['espeak', '-v', self.lang + '+' + self.voice, sentence]) subprocess.call(
['espeak', '-v', self.lang + '+' + self.voice, sentence])
class ESpeakValidator(TTSValidator): class ESpeakValidator(TTSValidator):
@ -27,7 +28,9 @@ class ESpeakValidator(TTSValidator):
try: try:
subprocess.call(['espeak', '--version']) subprocess.call(['espeak', '--version'])
except: except:
raise Exception('ESpeak is not installed. Run on terminal: sudo apt-get install espeak') raise Exception(
'ESpeak is not installed. Run on terminal: sudo apt-get '
'install espeak')
def get_instance(self): def get_instance(self):
return ESpeak return ESpeak

View File

@ -46,7 +46,9 @@ class FATTSValidator(TTSValidator):
if content['product'].find('FA-TTS') < 0: if content['product'].find('FA-TTS') < 0:
raise Exception('Invalid FA-TTS server.') raise Exception('Invalid FA-TTS server.')
except: except:
raise Exception('FA-TTS server could not be verified. Check your connection to the server: ' + tts.url) raise Exception(
'FA-TTS server could not be verified. Check your connection '
'to the server: ' + tts.url)
def get_instance(self): def get_instance(self):
return FATTS return FATTS

View File

@ -30,7 +30,9 @@ class GoogleTTSValidator(TTSValidator):
try: try:
gTTS(text='Hi').save(tts.filename) gTTS(text='Hi').save(tts.filename)
except: except:
raise Exception('GoogleTTS server could not be verified. Please check your internet connection.') raise Exception(
'GoogleTTS server could not be verified. Please check your '
'internet connection.')
def get_instance(self): def get_instance(self):
return GoogleTTS return GoogleTTS

View File

@ -43,7 +43,9 @@ class MaryTTSValidator(TTSValidator):
if resp.content.find('Mary TTS server') < 0: if resp.content.find('Mary TTS server') < 0:
raise Exception('Invalid MaryTTS server.') raise Exception('Invalid MaryTTS server.')
except: except:
raise Exception('MaryTTS server could not be verified. Check your connection to the server: ' + tts.url) raise Exception(
'MaryTTS server could not be verified. Check your connection '
'to the server: ' + tts.url)
def get_instance(self): def get_instance(self):
return MaryTTS return MaryTTS

View File

@ -10,7 +10,8 @@ __author__ = 'jdorleans'
config = ConfigurationManager.get_config().get("tts", {}) config = ConfigurationManager.get_config().get("tts", {})
NAME = 'mimic' NAME = 'mimic'
BIN = config.get("mimic.path", join(MYCROFT_ROOT_PATH, 'mimic', 'bin', 'mimic')) BIN = config.get(
"mimic.path", join(MYCROFT_ROOT_PATH, 'mimic', 'bin', 'mimic'))
class Mimic(TTS): class Mimic(TTS):
@ -32,7 +33,9 @@ class MimicValidator(TTSValidator):
try: try:
subprocess.call([BIN, '--version']) subprocess.call([BIN, '--version'])
except: except:
raise Exception('Mimic is not installed. Make sure install-mimic.sh ran properly.') raise Exception(
'Mimic is not installed. Make sure install-mimic.sh ran '
'properly.')
def get_instance(self): def get_instance(self):
return Mimic return Mimic

View File

@ -16,7 +16,8 @@ class RemoteTTS(TTS):
""" """
Abstract class for a Remote TTS engine implementation. Abstract class for a Remote TTS engine implementation.
It provides a common logic to perform multiple requests by splitting the whole sentence into small ones. It provides a common logic to perform multiple requests by splitting the
whole sentence into small ones.
""" """
def __init__(self, lang, voice, url, api_path): def __init__(self, lang, voice, url, api_path):
@ -49,7 +50,9 @@ class RemoteTTS(TTS):
return reqs return reqs
def __request(self, p): def __request(self, p):
return self.session.get(self.url + self.api_path, params=self.build_request_params(p), timeout=10, verify=False) return self.session.get(
self.url + self.api_path, params=self.build_request_params(p),
timeout=10, verify=False)
@abc.abstractmethod @abc.abstractmethod
def build_request_params(self, sentence): def build_request_params(self, sentence):
@ -61,7 +64,9 @@ class RemoteTTS(TTS):
self.__save(resp.content) self.__save(resp.content)
play_wav(self.filename) play_wav(self.filename)
else: else:
LOGGER.error('%s Http Error: %s for url: %s' % (resp.status_code, resp.reason, resp.url)) LOGGER.error(
'%s Http Error: %s for url: %s' %
(resp.status_code, resp.reason, resp.url))
def __save(self, data): def __save(self, data):
with open(self.filename, 'wb') as f: with open(self.filename, 'wb') as f:

View File

@ -12,7 +12,8 @@ class SpdSay(TTS):
super(SpdSay, self).__init__(lang, voice) super(SpdSay, self).__init__(lang, voice)
def execute(self, sentence): def execute(self, sentence):
subprocess.call(['spd-say', '-l', self.lang, '-t', self.voice, sentence]) subprocess.call(
['spd-say', '-l', self.lang, '-t', self.voice, sentence])
class SpdSayValidator(TTSValidator): class SpdSayValidator(TTSValidator):
@ -27,7 +28,9 @@ class SpdSayValidator(TTSValidator):
try: try:
subprocess.call(['spd-say', '--version']) subprocess.call(['spd-say', '--version'])
except: except:
raise Exception('SpdSay is not installed. Run on terminal: sudo apt-get install speech-dispatcher') raise Exception(
'SpdSay is not installed. Run on terminal: sudo apt-get'
'install speech-dispatcher')
def get_instance(self): def get_instance(self):
return SpdSay return SpdSay

View File

@ -15,7 +15,8 @@ def create():
""" """
Factory method to create a TTS engine based on configuration. Factory method to create a TTS engine based on configuration.
The configuration file ``defaults.ini`` contains a ``tts`` section with the name of a TTS module to be read by this method. The configuration file ``defaults.ini`` contains a ``tts`` section with
the name of a TTS module to be read by this method.
[tts] [tts]

View File

@ -21,9 +21,12 @@ def play_mp3(file_path):
def record(file_path, duration, rate, channels): def record(file_path, duration, rate, channels):
if duration > 0: if duration > 0:
return subprocess.Popen(["arecord", "-r", str(rate), "-c", str(channels), "-d", str(duration), file_path]) return subprocess.Popen(
["arecord", "-r", str(rate), "-c", str(channels), "-d",
str(duration), file_path])
else: else:
return subprocess.Popen(["arecord", "-r", str(rate), "-c", str(channels), file_path]) return subprocess.Popen(
["arecord", "-r", str(rate), "-c", str(channels), file_path])
def remove_last_slash(url): def remove_last_slash(url):
@ -66,5 +69,6 @@ def kill(names):
except: except:
pass pass
class CerberusAccessDenied(Exception): class CerberusAccessDenied(Exception):
pass pass

View File

@ -6,7 +6,8 @@ import argparse
__author__ = 'seanfitz' __author__ = 'seanfitz'
""" """
Audio Test Audio Test
A tool for recording X seconds of audio, and then playing them back. Useful for testing hardware, and ensures A tool for recording X seconds of audio, and then playing them back. Useful
for testing hardware, and ensures
compatibility with mycroft recognizer loop code. compatibility with mycroft recognizer loop code.
""" """
@ -22,8 +23,12 @@ def record(filename, duration):
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-f', '--filename', dest='filename', default="/tmp/test.wav", help="Filename for saved audio (Default: /tmp/test.wav)") parser.add_argument(
parser.add_argument('-d', '--duration', dest='duration', type=int, default=10, help="Duration of recording in seconds (Default: 10)") '-f', '--filename', dest='filename', default="/tmp/test.wav",
help="Filename for saved audio (Default: /tmp/test.wav)")
parser.add_argument(
'-d', '--duration', dest='duration', type=int, default=10,
help="Duration of recording in seconds (Default: 10)")
args = parser.parse_args() args = parser.parse_args()
record(args.filename, args.duration) record(args.filename, args.duration)

View File

@ -1,6 +1,7 @@
import logging
__author__ = 'seanfitz' __author__ = 'seanfitz'
import logging
FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
logging.basicConfig(format=FORMAT, level=logging.DEBUG) logging.basicConfig(format=FORMAT, level=logging.DEBUG)
logger = logging.getLogger("MYCROFT") logger = logging.getLogger("MYCROFT")

View File

@ -8,9 +8,11 @@ __author__ = 'seanfitz'
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
def place_manifest(manifest_file): def place_manifest(manifest_file):
shutil.copy(manifest_file, "MANIFEST.in") shutil.copy(manifest_file, "MANIFEST.in")
def get_version(): def get_version():
version = None version = None
try: try:
@ -18,7 +20,8 @@ def get_version():
version = mycroft.__version__.version version = mycroft.__version__.version
except Exception, e: except Exception, e:
try: try:
version = "dev-" + subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).strip() version = "dev-" + subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"]).strip()
except subprocess.CalledProcessError, e2: except subprocess.CalledProcessError, e2:
version = "development" version = "development"
@ -33,4 +36,6 @@ def required(requirements_file):
def find_all_packages(where): def find_all_packages(where):
packages = find_packages(where=where, exclude=["*test*"]) packages = find_packages(where=where, exclude=["*test*"])
return [os.path.join(where, pkg.replace(".", os.sep)) for pkg in packages] + [where] return [
os.path.join(where, pkg.replace(".", os.sep))
for pkg in packages] + [where]

View File

@ -35,4 +35,4 @@ setup(
'mycroft-skill-container=mycroft.skills.container:main' 'mycroft-skill-container=mycroft.skills.container:main'
] ]
} }
) )

View File

@ -1 +1,2 @@
xmlrunner==1.7.7 pep8
xmlrunner==1.7.7

View File

@ -2,8 +2,15 @@ from Queue import Queue
from os.path import dirname, join from os.path import dirname, join
import unittest import unittest
from speech_recognition import WavFile, AudioData from speech_recognition import WavFile, AudioData
from mycroft.client.speech.listener import WakewordExtractor, AudioConsumer, RecognizerLoop from mycroft.client.speech.listener import (
from mycroft.client.speech.recognizer_wrapper import RemoteRecognizerWrapperFactory WakewordExtractor,
AudioConsumer,
RecognizerLoop
)
from mycroft.client.speech.recognizer_wrapper import (
RemoteRecognizerWrapperFactory
)
__author__ = 'seanfitz' __author__ = 'seanfitz'
@ -34,27 +41,38 @@ class AudioConsumerTest(unittest.TestCase):
self.loop, self.loop,
self.loop.wakeup_recognizer, self.loop.wakeup_recognizer,
self.loop.ww_recognizer, self.loop.ww_recognizer,
RemoteRecognizerWrapperFactory.wrap_recognizer(self.recognizer, 'google'), RemoteRecognizerWrapperFactory.wrap_recognizer(
self.recognizer, 'google'),
self.loop.wakeup_prefixes, self.loop.wakeup_prefixes,
self.loop.wakeup_words) self.loop.wakeup_words)
def __create_sample_from_test_file(self, sample_name): def __create_sample_from_test_file(self, sample_name):
root_dir = dirname(dirname(dirname(__file__))) root_dir = dirname(dirname(dirname(__file__)))
filename = join(root_dir, 'test', 'client', 'data', sample_name + '.wav') filename = join(
root_dir, 'test', 'client', 'data', sample_name + '.wav')
wavfile = WavFile(filename) wavfile = WavFile(filename)
with wavfile as source: with wavfile as source:
return AudioData(source.stream.read(), wavfile.SAMPLE_RATE, wavfile.SAMPLE_WIDTH) return AudioData(
source.stream.read(), wavfile.SAMPLE_RATE,
wavfile.SAMPLE_WIDTH)
def test_audio_pos_front_back(self): def test_audio_pos_front_back(self):
audio = self.__create_sample_from_test_file('mycroft_in_utterance') audio = self.__create_sample_from_test_file('mycroft_in_utterance')
self.queue.put(audio) self.queue.put(audio)
TRUE_POS_BEGIN = 69857 + int(WakewordExtractor.TRIM_SECONDS * audio.sample_rate * audio.sample_width) TRUE_POS_BEGIN = 69857 + int(
TRUE_POS_END = 89138 - int(WakewordExtractor.TRIM_SECONDS * audio.sample_rate * audio.sample_width) WakewordExtractor.TRIM_SECONDS * audio.sample_rate *
audio.sample_width)
TRUE_POS_END = 89138 - int(
WakewordExtractor.TRIM_SECONDS * audio.sample_rate *
audio.sample_width)
TOLERANCE_RANGE_FRAMES = WakewordExtractor.MAX_ERROR_SECONDS * audio.sample_rate * audio.sample_width TOLERANCE_RANGE_FRAMES = (
WakewordExtractor.MAX_ERROR_SECONDS * audio.sample_rate *
audio.sample_width)
monitor = {} monitor = {}
self.recognizer.set_transcriptions(["what's the weather next week", ""]) self.recognizer.set_transcriptions(
["what's the weather next week", ""])
def wakeword_callback(message): def wakeword_callback(message):
monitor['pos_begin'] = message.get('pos_begin') monitor['pos_begin'] = message.get('pos_begin')
@ -66,17 +84,22 @@ class AudioConsumerTest(unittest.TestCase):
pos_begin = monitor.get('pos_begin') pos_begin = monitor.get('pos_begin')
self.assertIsNotNone(pos_begin) self.assertIsNotNone(pos_begin)
diff = abs(pos_begin - TRUE_POS_BEGIN) diff = abs(pos_begin - TRUE_POS_BEGIN)
self.assertTrue(diff <= TOLERANCE_RANGE_FRAMES, str(diff) + " is not less than " + str(TOLERANCE_RANGE_FRAMES)) self.assertTrue(
diff <= TOLERANCE_RANGE_FRAMES,
str(diff) + " is not less than " + str(TOLERANCE_RANGE_FRAMES))
pos_end = monitor.get('pos_end') pos_end = monitor.get('pos_end')
self.assertIsNotNone(pos_end) self.assertIsNotNone(pos_end)
diff = abs(pos_end - TRUE_POS_END) diff = abs(pos_end - TRUE_POS_END)
self.assertTrue(diff <= TOLERANCE_RANGE_FRAMES, str(diff) + " is not less than " + str(TOLERANCE_RANGE_FRAMES)) self.assertTrue(
diff <= TOLERANCE_RANGE_FRAMES,
str(diff) + " is not less than " + str(TOLERANCE_RANGE_FRAMES))
def test_wakeword_in_beginning(self): def test_wakeword_in_beginning(self):
self.queue.put(self.__create_sample_from_test_file('mycroft')) self.queue.put(self.__create_sample_from_test_file('mycroft'))
monitor = {} monitor = {}
self.recognizer.set_transcriptions(["what's the weather next week", ""]) self.recognizer.set_transcriptions([
"what's the weather next week", ""])
def callback(message): def callback(message):
monitor['utterances'] = message.get('utterances') monitor['utterances'] = message.get('utterances')
@ -91,7 +114,8 @@ class AudioConsumerTest(unittest.TestCase):
def test_wakeword_in_phrase(self): def test_wakeword_in_phrase(self):
self.queue.put(self.__create_sample_from_test_file('mycroft')) self.queue.put(self.__create_sample_from_test_file('mycroft'))
monitor = {} monitor = {}
self.recognizer.set_transcriptions(["he can do other stuff too", "what's the weather in cincinnati"]) self.recognizer.set_transcriptions([
"he can do other stuff too", "what's the weather in cincinnati"])
def callback(message): def callback(message):
monitor['utterances'] = message.get('utterances') monitor['utterances'] = message.get('utterances')
@ -107,7 +131,7 @@ class AudioConsumerTest(unittest.TestCase):
def test_call_and_response(self): def test_call_and_response(self):
self.queue.put(self.__create_sample_from_test_file('mycroft')) self.queue.put(self.__create_sample_from_test_file('mycroft'))
monitor = {} monitor = {}
self.recognizer.set_transcriptions(["mycroft",""]) self.recognizer.set_transcriptions(["mycroft", ""])
def wakeword_callback(message): def wakeword_callback(message):
monitor['wakeword'] = message.get('utterance') monitor['wakeword'] = message.get('utterance')
@ -121,7 +145,8 @@ class AudioConsumerTest(unittest.TestCase):
self.assertIsNotNone(monitor.get('wakeword')) self.assertIsNotNone(monitor.get('wakeword'))
self.queue.put(self.__create_sample_from_test_file('mycroft')) self.queue.put(self.__create_sample_from_test_file('mycroft'))
self.recognizer.set_transcriptions(["what's the weather next week", ""]) self.recognizer.set_transcriptions(
["what's the weather next week", ""])
self.loop.once('recognizer_loop:utterance', utterance_callback) self.loop.once('recognizer_loop:utterance', utterance_callback)
self.consumer.try_consume_audio() self.consumer.try_consume_audio()

View File

@ -50,7 +50,9 @@ class DynamicEnergytest(unittest.TestCase):
recognizer = Recognizer() recognizer = Recognizer()
recognizer.listen(source) recognizer.listen(source)
higher_base_energy = audioop.rms(higher_base, 2) higher_base_energy = audioop.rms(higher_base, 2)
# after recalibration (because of max audio length) new threshold should be >= 1.5 * higher_base_energy # after recalibration (because of max audio length) new threshold
delta_below_threshold = recognizer.energy_threshold - higher_base_energy # should be >= 1.5 * higher_base_energy
delta_below_threshold = (
recognizer.energy_threshold - higher_base_energy)
min_delta = higher_base_energy * .5 min_delta = higher_base_energy * .5
assert abs(delta_below_threshold - min_delta) < 1 assert abs(delta_below_threshold - min_delta) < 1

View File

@ -3,11 +3,13 @@ import os
from mycroft.client.speech import wakeword_recognizer from mycroft.client.speech import wakeword_recognizer
__author__ = 'seanfitz'
import unittest import unittest
DATA_DIR=os.path.join(os.path.abspath(os.path.dirname(__file__)), "data")
__author__ = 'seanfitz'
DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data")
class WakewordRecognizerTest(unittest.TestCase): class WakewordRecognizerTest(unittest.TestCase):
def setUp(self): def setUp(self):

View File

@ -10,10 +10,17 @@ PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
def discover_tests(): def discover_tests():
tests = {} tests = {}
skills = [skill for skill in glob.glob(os.path.join(PROJECT_ROOT, 'mycroft/skills/*')) if os.path.isdir(skill)] skills = [
skill for skill
in glob.glob(os.path.join(PROJECT_ROOT, 'mycroft/skills/*'))
if os.path.isdir(skill)
]
for skill in skills: for skill in skills:
test_intent_files = [f for f in glob.glob(os.path.join(skill, 'test/intent/*.intent.json'))] test_intent_files = [
f for f
in glob.glob(os.path.join(skill, 'test/intent/*.intent.json'))
]
if len(test_intent_files) > 0: if len(test_intent_files) > 0:
tests[skill] = test_intent_files tests[skill] = test_intent_files
@ -31,21 +38,20 @@ class IntentTestSequenceMeta(type):
for skill in tests.keys(): for skill in tests.keys():
skill_name = os.path.basename(skill) skill_name = os.path.basename(skill)
for example in tests[skill]: for example in tests[skill]:
example_name = os.path.basename(os.path.splitext(os.path.splitext(example)[0])[0]) example_name = os.path.basename(
test_name = "test_IntentValidation[%s:%s]" % (skill_name, example_name) os.path.splitext(os.path.splitext(example)[0])[0])
test_name = "test_IntentValidation[%s:%s]" % (skill_name,
example_name)
d[test_name] = gen_test(skill, example) d[test_name] = gen_test(skill, example)
return type.__new__(mcs, name, bases, d) return type.__new__(mcs, name, bases, d)
class IntentTestSequence(unittest.TestCase): class IntentTestSequence(unittest.TestCase):
__metaclass__ = IntentTestSequenceMeta __metaclass__ = IntentTestSequenceMeta
def setUp(self): def setUp(self):
self.emitter = MockSkillsLoader(os.path.join(PROJECT_ROOT, 'mycroft', 'skills')).load_skills() self.emitter = MockSkillsLoader(
os.path.join(PROJECT_ROOT, 'mycroft', 'skills')).load_skills()
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -12,7 +12,11 @@ class RegistrationOnlyEmitter(object):
self.emitter = EventEmitter() self.emitter = EventEmitter()
def on(self, event, f): def on(self, event, f):
if event in ['register_intent', 'register_vocab', 'recognizer_loop:utterance']: if event in [
'register_intent',
'register_vocab',
'recognizer_loop:utterance'
]:
self.emitter.on(event, f) self.emitter.on(event, f)
def emit(self, event, *args, **kwargs): def emit(self, event, *args, **kwargs):
@ -27,7 +31,7 @@ class MockSkillsLoader(object):
def load_skills(self): def load_skills(self):
load_skills(self.emitter, self.skills_root) load_skills(self.emitter, self.skills_root)
return self.emitter.emitter # kick out the underlying emitter return self.emitter.emitter # kick out the underlying emitter
class SkillTest(object): class SkillTest(object):
@ -40,7 +44,9 @@ class SkillTest(object):
def compare_intents(self, expected, actual): def compare_intents(self, expected, actual):
for key in expected.keys(): for key in expected.keys():
if actual.get(key, "").lower() != expected.get(key, "").lower(): if actual.get(key, "").lower() != expected.get(key, "").lower():
print "Expected %s: %s, Actual: %s" % (key, expected.get(key), actual.get(key)) print(
"Expected %s: %s, Actual: %s" % (key, expected.get(key),
actual.get(key)))
assert False assert False
def run(self): def run(self):
@ -51,8 +57,9 @@ class SkillTest(object):
self.compare_intents(example_json.get('intent'), intent.metadata) self.compare_intents(example_json.get('intent'), intent.metadata)
self.returned_intent = True self.returned_intent = True
self.emitter.once(example_json.get('intent_type'), compare) self.emitter.once(example_json.get('intent_type'), compare)
self.emitter.emit('recognizer_loop:utterance', Message('recognizer_loop:utterance', event)) self.emitter.emit(
'recognizer_loop:utterance',
Message('recognizer_loop:utterance', event))
if not self.returned_intent: if not self.returned_intent:
print("No intent handled") print("No intent handled")
assert False assert False

View File

@ -1,6 +1,5 @@
from mycroft.configuration.config import ConfigurationManager from mycroft.configuration.config import ConfigurationManager
__author__ = 'seanfitz'
import unittest import unittest
from xmlrunner import XMLTestRunner from xmlrunner import XMLTestRunner
@ -8,6 +7,9 @@ import os
import sys import sys
__author__ = 'seanfitz'
TEST_DIR = os.path.dirname(os.path.realpath(__file__)) TEST_DIR = os.path.dirname(os.path.realpath(__file__))
OUTPUT_DIR = os.path.dirname(os.path.dirname(__file__)) OUTPUT_DIR = os.path.dirname(os.path.dirname(__file__))
@ -18,4 +20,4 @@ tests = loader.discover(TEST_DIR, pattern="*_test*.py")
runner = XMLTestRunner(output="./build/report/tests") runner = XMLTestRunner(output="./build/report/tests")
result = runner.run(tests) result = runner.run(tests)
if fail_on_error and len(result.failures + result.errors) > 0: if fail_on_error and len(result.failures + result.errors) > 0:
sys.exit(1) sys.exit(1)