diff --git a/mycroft/client/enclosure/enclosure.py b/mycroft/client/enclosure/enclosure.py index b55852fc3e..c782622b7f 100644 --- a/mycroft/client/enclosure/enclosure.py +++ b/mycroft/client/enclosure/enclosure.py @@ -16,11 +16,12 @@ # along with Mycroft Core. If not, see . -import serial import sys from Queue import Queue from threading import Thread +import serial + from mycroft.client.enclosure.arduino import EnclosureArduino from mycroft.client.enclosure.eyes import EnclosureEyes from mycroft.client.enclosure.mouth import EnclosureMouth @@ -143,7 +144,7 @@ class Enclosure: self.eyes = EnclosureEyes(self.client, self.writer) self.mouth = EnclosureMouth(self.client, self.writer) self.system = EnclosureArduino(self.client, self.writer) - self.__init_events() + self.__register_events() def __init_serial(self): try: @@ -161,11 +162,30 @@ class Enclosure: "It is not possible to connect to serial port: " + self.port) raise - def __init_events(self): + def __register_events(self): + self.client.on('mycroft.paired', self.__update_events) + self.client.on('recognizer_loop:wakeword', self.eyes.blink) + self.__register_mouth_events() + + def __register_mouth_events(self): self.client.on('recognizer_loop:listening', self.mouth.listen) self.client.on('recognizer_loop:audio_output_start', self.mouth.talk) self.client.on('recognizer_loop:audio_output_end', self.mouth.reset) - self.client.on('recognizer_loop:wakeword', self.eyes.blink) + + def __remove_mouth_events(self): + self.client.remove('recognizer_loop:listening', self.mouth.listen) + self.client.remove('recognizer_loop:audio_output_start', + self.mouth.talk) + self.client.remove('recognizer_loop:audio_output_end', + self.mouth.reset) + self.mouth.reset() + + def __update_events(self, event=None): + if event and event.metadata: + if event.metadata.get('paired', False): + self.__register_mouth_events() + else: + self.__remove_mouth_events() def run(self): try: diff --git a/mycroft/client/speech/listener.py b/mycroft/client/speech/listener.py index 2b4fc6ef6f..b0bc8481eb 100644 --- a/mycroft/client/speech/listener.py +++ b/mycroft/client/speech/listener.py @@ -81,7 +81,7 @@ class AudioConsumer(threading.Thread): """ # In seconds, the minimum audio size to be sent to remote STT - MIN_AUDIO_SIZE = 1.0 + MIN_AUDIO_SIZE = 0.5 def __init__(self, state, queue, emitter, wakeup_recognizer, mycroft_recognizer, remote_recognizer): diff --git a/mycroft/messagebus/client/ws.py b/mycroft/messagebus/client/ws.py index 1a90a31f57..979af83959 100644 --- a/mycroft/messagebus/client/ws.py +++ b/mycroft/messagebus/client/ws.py @@ -17,14 +17,15 @@ import json -from multiprocessing.pool import ThreadPool import time -from mycroft.configuration.config import ConfigurationManager -from mycroft.messagebus.message import Message -import mycroft.util.log +from multiprocessing.pool import ThreadPool + from pyee import EventEmitter from websocket import WebSocketApp +import mycroft.util.log +from mycroft.configuration.config import ConfigurationManager +from mycroft.messagebus.message import Message from mycroft.util import str2bool __author__ = 'seanfitz' @@ -99,6 +100,9 @@ class WebsocketClient(object): def once(self, event_name, func): self.emitter.once(event_name, func) + def remove(self, event_name, func): + self.emitter.remove_listener(event_name, func) + def run_forever(self): self.client.run_forever() diff --git a/mycroft/pairing/client.py b/mycroft/pairing/client.py index 58b773628c..62a2af3c33 100644 --- a/mycroft/pairing/client.py +++ b/mycroft/pairing/client.py @@ -17,6 +17,7 @@ import shortuuid + from mycroft.configuration.config import ConfigurationManager from mycroft.identity import IdentityManager from mycroft.messagebus.client.ws import WebsocketClient @@ -34,6 +35,7 @@ def generate_pairing_code(): class DevicePairingClient(object): def __init__(self, config=_config, pairing_code=None): self.config = config + self.paired = False self.ws_client = WebsocketClient(host=config.get("host"), port=config.get("port"), path=config.get("route"), @@ -53,6 +55,7 @@ class DevicePairingClient(object): identity.owner = register_payload.get('user') self.identity_manager.update(identity) self.ws_client.close() + self.paired = True def send_device_info(self): msg = Message("device_info", @@ -63,7 +66,8 @@ class DevicePairingClient(object): self.ws_client.emit(msg) - def print_error(self, message): + @staticmethod + def print_error(message): print(repr(message)) def run(self): @@ -76,5 +80,6 @@ class DevicePairingClient(object): def main(): DevicePairingClient().run() + if __name__ == "__main__": main() diff --git a/mycroft/skills/pairing/__init__.py b/mycroft/skills/pairing/__init__.py index f3de1bd931..acc8ac88b1 100644 --- a/mycroft/skills/pairing/__init__.py +++ b/mycroft/skills/pairing/__init__.py @@ -15,11 +15,12 @@ # You should have received a copy of the GNU General Public License # along with Mycroft Core. If not, see . -import threading +from threading import Thread from adapt.intent import IntentBuilder from os.path import dirname +from mycroft.messagebus.message import Message from mycroft.pairing.client import DevicePairingClient from mycroft.skills.core import MycroftSkill @@ -27,21 +28,41 @@ from mycroft.skills.core import MycroftSkill class PairingSkill(MycroftSkill): def __init__(self): super(PairingSkill, self).__init__(name="PairingSkill") + self.client = None + self.displaying = False def initialize(self): intent = IntentBuilder("PairingIntent").require( - "DevicePairingPhrase").build() + "DevicePairingPhrase").build() self.load_data_files(dirname(__file__)) self.register_intent(intent, handler=self.handle_pairing_request) def handle_pairing_request(self, message): - pairing_client = DevicePairingClient() - pairing_code = pairing_client.pairing_code - threading.Thread(target=pairing_client.run).start() - self.enclosure.mouth_text("Pairing code is: " + pairing_code) + if not self.client: + self.displaying = False + self.__emit_paired(False) + self.client = DevicePairingClient() + Thread(target=self.client.run).start() + self.emitter.on("recognizer_loop:audio_output_start", + self.__display_pairing_code) self.speak_dialog( - "pairing.instructions", - data={"pairing_code": ', ,'.join(pairing_code)}) + "pairing.instructions", + data={"pairing_code": ', ,'.join(self.client.pairing_code)}) + + def __display_pairing_code(self, event=None): + if self.client.paired: + self.enclosure.mouth_talk() + self.client = None + self.__emit_paired(True) + self.emitter.remove("recognizer_loop:audio_output_start", + self.__display_pairing_code) + elif not self.displaying: + self.displaying = True + self.enclosure.mouth_text(self.client.pairing_code) + + def __emit_paired(self, paired): + msg = Message('mycroft.paired', metadata={'paired': paired}) + self.emitter.emit(msg) def stop(self): pass diff --git a/mycroft/skills/welcome/__init__.py b/mycroft/skills/welcome/__init__.py new file mode 100644 index 0000000000..1e0976c55a --- /dev/null +++ b/mycroft/skills/welcome/__init__.py @@ -0,0 +1,48 @@ +# Copyright 2016 Mycroft AI, Inc. +# +# This file is part of Mycroft Core. +# +# Mycroft Core is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Mycroft Core is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Mycroft Core. If not, see . + +from os.path import dirname + +from adapt.intent import IntentBuilder +from mycroft.skills.core import MycroftSkill +from mycroft.util.log import getLogger + +__author__ = 'eward' + +LOGGER = getLogger(__name__) + + +class WelcomeSkill(MycroftSkill): + + def __init__(self): + super(WelcomeSkill, self).__init__(name="WelcomeSkill") + + def initialize(self): + self.load_data_files(dirname(__file__)) + + welcome_intent = IntentBuilder("WelcIntent").require("WelcKey").build() + self.register_intent(welcome_intent, self.handle_welcome_intent) + + def handle_welcome_intent(self, message): + self.speak_dialog('Welcome') + + def stop(self): + pass + + +def create_skill(): + return WelcomeSkill() diff --git a/mycroft/skills/welcome/dialog/en-us/Welcome.dialog b/mycroft/skills/welcome/dialog/en-us/Welcome.dialog new file mode 100644 index 0000000000..855fb3e3bd --- /dev/null +++ b/mycroft/skills/welcome/dialog/en-us/Welcome.dialog @@ -0,0 +1 @@ +You're welcome. diff --git a/mycroft/skills/welcome/vocab/en-us/WelcKey.voc b/mycroft/skills/welcome/vocab/en-us/WelcKey.voc new file mode 100644 index 0000000000..d280f1b73a --- /dev/null +++ b/mycroft/skills/welcome/vocab/en-us/WelcKey.voc @@ -0,0 +1,2 @@ +thank you +thanks diff --git a/test/client/audio_consumer_test.py b/test/client/audio_consumer_test.py index 8a6ba53843..db43531d22 100644 --- a/test/client/audio_consumer_test.py +++ b/test/client/audio_consumer_test.py @@ -190,3 +190,41 @@ class AudioConsumerTest(unittest.TestCase): self.assertIsNotNone(utterances) self.assertTrue(len(utterances) == 1) self.assertEquals("what's the weather next week", utterances[0]) + + def test_stop(self): + self.queue.put(self.__create_sample_from_test_file('mycroft')) + self.consumer.read_audio() + + self.queue.put(self.__create_sample_from_test_file('stop')) + self.recognizer.set_transcriptions(["stop"]) + monitor = {} + + def utterance_callback(message): + monitor['utterances'] = message.get('utterances') + + self.loop.once('recognizer_loop:utterance', utterance_callback) + self.consumer.read_audio() + + utterances = monitor.get('utterances') + self.assertIsNotNone(utterances) + self.assertTrue(len(utterances) == 1) + self.assertEquals("stop", utterances[0]) + + def test_record(self): + self.queue.put(self.__create_sample_from_test_file('mycroft')) + self.consumer.read_audio() + + self.queue.put(self.__create_sample_from_test_file('record')) + self.recognizer.set_transcriptions(["record"]) + monitor = {} + + def utterance_callback(message): + monitor['utterances'] = message.get('utterances') + + self.loop.once('recognizer_loop:utterance', utterance_callback) + self.consumer.read_audio() + + utterances = monitor.get('utterances') + self.assertIsNotNone(utterances) + self.assertTrue(len(utterances) == 1) + self.assertEquals("record", utterances[0]) diff --git a/test/client/data/record.wav b/test/client/data/record.wav new file mode 100644 index 0000000000..5062ebc0d3 Binary files /dev/null and b/test/client/data/record.wav differ diff --git a/test/client/data/stop.wav b/test/client/data/stop.wav new file mode 100644 index 0000000000..dcf10e6639 Binary files /dev/null and b/test/client/data/stop.wav differ