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))