From c0b603ab51ee3cd7b9fabf369de1d0e6c0b67406 Mon Sep 17 00:00:00 2001 From: Michael Nguyen Date: Fri, 16 Jun 2017 13:31:44 -0500 Subject: [PATCH] state manager implemented --- mycroft/client/enclosure/__init__.py | 6 + mycroft/client/enclosure/api.py | 34 ++++- mycroft/client/enclosure/display_manager.py | 159 ++++++++++++++++++++ mycroft/client/enclosure/main.py | 1 - mycroft/skills/core.py | 5 +- 5 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 mycroft/client/enclosure/display_manager.py diff --git a/mycroft/client/enclosure/__init__.py b/mycroft/client/enclosure/__init__.py index f1f7b28366..6340bba039 100644 --- a/mycroft/client/enclosure/__init__.py +++ b/mycroft/client/enclosure/__init__.py @@ -37,11 +37,17 @@ from mycroft.util import play_wav, create_signal, connected, \ from mycroft.util.audio_test import record from mycroft.util.log import getLogger from mycroft.api import is_paired +from mycroft.client.enclosure.display_manager import run as \ + initiate_display_manager_ws + __author__ = 'aatchison', 'jdorleans', 'iward' LOG = getLogger("EnclosureClient") +# initiates the web sockets on display manager +initiate_display_manager_ws() + class EnclosureReader(Thread): """ diff --git a/mycroft/client/enclosure/api.py b/mycroft/client/enclosure/api.py index 8a17a27770..405a9a6cf6 100644 --- a/mycroft/client/enclosure/api.py +++ b/mycroft/client/enclosure/api.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Mycroft Core. If not, see . - +import mycroft.client.enclosure.display_manager as DisplayManager from mycroft.messagebus.message import Message from mycroft.util.log import getLogger from PIL import Image @@ -25,6 +25,13 @@ __author__ = 'jdorleans' LOGGER = getLogger(__name__) +''' +API for the functions that affect the Mark I device. +NOTE: current state management is poorly implemented, +will be changed in the future. +''' + + class EnclosureAPI: """ This API is intended to be used to interface with the hardware @@ -38,8 +45,18 @@ class EnclosureAPI: where there is no face at all. """ - def __init__(self, ws): + def __init__(self, ws, name=""): self.ws = ws + self.name = name + + def register(self, skill_name=""): + """Registers a skill as active. Used for speak() and speak_dialog() + to 'patch' a previous implementation. Somewhat hacky. + """ + if self.name != "": + DisplayManager.set_active(self.name) + else: + DisplayManager.set_active(skill_name) def reset(self): """The enclosure should restore itself to a started state. @@ -136,22 +153,27 @@ class EnclosureAPI: def mouth_reset(self): """Restore the mouth display to normal (blank)""" self.ws.emit(Message("enclosure.mouth.reset")) + DisplayManager.set_active(self.name) def mouth_talk(self): """Show a generic 'talking' animation for non-synched speech""" self.ws.emit(Message("enclosure.mouth.talk")) + DisplayManager.set_active(self.name) def mouth_think(self): """Show a 'thinking' image or animation""" self.ws.emit(Message("enclosure.mouth.think")) + DisplayManager.set_active(self.name) def mouth_listen(self): """Show a 'thinking' image or animation""" self.ws.emit(Message("enclosure.mouth.listen")) + DisplayManager.set_active(self.name) def mouth_smile(self): """Show a 'smile' image or animation""" self.ws.emit(Message("enclosure.mouth.smile")) + DisplayManager.set_active(self.name) def mouth_viseme(self, code): """Display a viseme mouth shape for synched speech @@ -171,6 +193,7 @@ class EnclosureAPI: Args: text (str): text string to display """ + DisplayManager.set_active(self.name) self.ws.emit(Message("enclosure.mouth.text", {'text': text})) def mouth_display(self, img_code="", x=0, y=0, refresh=True): @@ -186,7 +209,8 @@ class EnclosureAPI: Useful if you'd like to display muliple images on the faceplate at once. """ - self.ws.emit(Message("enclosure.mouth.display", + DisplayManager.set_active(self.name) + self.ws.emit(Message('enclosure.mouth.display', {'img_code': img_code, 'xOffset': x, 'yOffset': y, @@ -212,9 +236,8 @@ class EnclosureAPI: displaying the new image or not. Useful if you'd like to display muliple images on the faceplate at once. - - " """ + DisplayManager.set_active(self.name) # to understand how this funtion works you need to understand how the # Mark I arduino proprietary encoding works to display to the faceplate @@ -332,6 +355,7 @@ class EnclosureAPI: 7 = wind/mist temp (int): the temperature (either C or F, not indicated) """ + DisplayManager.set_active(self.name) self.ws.emit(Message("enclosure.weather.display", {'img_code': img_code, 'temp': temp})) diff --git a/mycroft/client/enclosure/display_manager.py b/mycroft/client/enclosure/display_manager.py new file mode 100644 index 0000000000..06ea80a672 --- /dev/null +++ b/mycroft/client/enclosure/display_manager.py @@ -0,0 +1,159 @@ + +# Copyright 2017 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 threading import Thread, Timer +from mycroft.messagebus.client.ws import WebsocketClient +from mycroft.configuration import ConfigurationManager +from mycroft.util import get_ipc_directory +import json +import os +from logging import getLogger + +__author__ = 'connorpenrod', 'michaelnguyen' + + +LOG = getLogger("Display Manager (mycroft.client.enclosure)") +managerIPCDir = os.path.join(get_ipc_directory(), "managers") + + +def write_data(dictionary): + """Writes the parama as JSON to the + IPC dir (/tmp/mycroft/ipc/managers) + args: + dict: dictionary + """ + + # change read/write permissions based on if file exists or not + path = os.path.join(managerIPCDir, "disp_info") + permission = "r+" if os.path.isfile(path) else "w+" + + if permission == "w+": + os.makedirs(managerIPCDir) + + try: + with open(path, permission) as dispFile: + + # check if file is empty + if os.stat(str(dispFile.name)).st_size != 0: + data = json.load(dispFile) + + else: + data = {} + LOG.info("Display Manager is creating " + dispFile.name) + + for key in dictionary: + data[key] = dictionary[key] + + dispFile.seek(0) + dispFile.write(json.dumps(data)) + dispFile.truncate() + + except Exception as e: + LOG.error(e) + + +def read_data(): + """ Reads the file in (/tmp/mycroft/ipc/managers/disp_info) + and returns the the data as python dict + """ + + path = os.path.join(managerIPCDir, "disp_info") + permission = "r" if os.path.isfile(path) else "w+" + + if permission == "w+": + os.makedirs(managerIPCDir) + + data = {} + try: + with open(path, permission) as dispFile: + + if os.stat(str(dispFile.name)).st_size != 0: + data = json.load(dispFile) + + except Exception as e: + LOG.error(e) + + return data + + +def set_active(skill_name): + """ Sets skill name as active in the display Manager + args: + string: skill_name + """ + write_data({"active_skill": skill_name}) + LOG.info("Setting active skill to " + skill_name) + + +def get_active(): + """ Get active skill in the display manager + """ + data = read_data() + active_skill = "" + + if "active_skill" in data: + active_skill = data["active_skill"] + + return active_skill + + +def remove_active(): + """ Remove the active skill in the skill manager + """ + LOG.error("Removing active skill...") + write_data({"active_skill": ""}) + + +def run(): + """ TODO: document + """ + + # Should remove needs to be an object so it can be referenced in functions + # [https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference] + should_remove = [True] + + def check_flag(flag): + if flag[0] is True: + remove_active() + + def set_delay(event=None): + should_remove[0] = True + Timer(2, check_flag, [should_remove]).start() + + def set_remove_flag(event=None): + should_remove[0] = False + + def connect(): + ws.run_forever() + + def remove_wake_word(): + data = read_data() + if "active_skill" in data and data["active_skill"] == "wakeword": + remove_active() + + def set_wakeword_skill(event=None): + set_active("wakeword") + Timer(10, remove_wake_word).start() + + ws = WebsocketClient() + ws.on('recognizer_loop:audio_output_end', set_delay) + ws.on('recognizer_loop:audio_output_start', set_remove_flag) + ws.on('recognizer_loop:record_begin', set_wakeword_skill) + + event_thread = Thread(target=connect) + event_thread.setDaemon(True) + event_thread.start() diff --git a/mycroft/client/enclosure/main.py b/mycroft/client/enclosure/main.py index 48eb247cb3..7da322d54c 100644 --- a/mycroft/client/enclosure/main.py +++ b/mycroft/client/enclosure/main.py @@ -17,7 +17,6 @@ import sys - from mycroft.client.enclosure import Enclosure diff --git a/mycroft/skills/core.py b/mycroft/skills/core.py index b5e494e40b..88d4ad2973 100644 --- a/mycroft/skills/core.py +++ b/mycroft/skills/core.py @@ -34,6 +34,7 @@ from mycroft.filesystem import FileSystemAccess from mycroft.messagebus.message import Message from mycroft.util.log import getLogger from mycroft.skills.settings import SkillSettings + __author__ = 'seanfitz' BLACKLISTED_SKILLS = ["send_sms", "media"] @@ -218,7 +219,7 @@ class MycroftSkill(object): def bind(self, emitter): if emitter: self.emitter = emitter - self.enclosure = EnclosureAPI(emitter) + self.enclosure = EnclosureAPI(emitter, self.name) self.__register_stop() def __register_stop(self): @@ -291,6 +292,8 @@ class MycroftSkill(object): self.emitter.emit(Message('register_vocab', {'regex': regex_str})) def speak(self, utterance, expect_response=False): + # registers the skill as being active + self.enclosure.register(self.name) data = {'utterance': utterance, 'expect_response': expect_response} self.emitter.emit(Message("speak", data))