2016-05-26 16:16:13 +00:00
|
|
|
# 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 <http://www.gnu.org/licenses/>.
|
2016-09-04 00:59:39 +00:00
|
|
|
import abc
|
2016-05-20 14:16:01 +00:00
|
|
|
import imp
|
2017-04-15 09:50:36 +00:00
|
|
|
import time
|
|
|
|
|
2017-06-16 21:40:12 +00:00
|
|
|
import operator
|
2016-05-20 14:16:01 +00:00
|
|
|
import os.path
|
|
|
|
import re
|
2017-04-17 17:25:27 +00:00
|
|
|
import time
|
2016-06-17 16:40:21 +00:00
|
|
|
from os.path import join, dirname, splitext, isdir
|
2016-05-20 14:16:01 +00:00
|
|
|
|
2017-06-30 11:03:03 +00:00
|
|
|
from functools import wraps
|
|
|
|
|
2017-04-17 17:25:27 +00:00
|
|
|
from adapt.intent import Intent
|
|
|
|
|
2016-05-20 14:16:01 +00:00
|
|
|
from mycroft.client.enclosure.api import EnclosureAPI
|
2016-06-06 20:08:37 +00:00
|
|
|
from mycroft.configuration import ConfigurationManager
|
2016-05-20 14:16:01 +00:00
|
|
|
from mycroft.dialog import DialogLoader
|
|
|
|
from mycroft.filesystem import FileSystemAccess
|
|
|
|
from mycroft.messagebus.message import Message
|
|
|
|
from mycroft.util.log import getLogger
|
2017-04-13 05:26:45 +00:00
|
|
|
from mycroft.skills.settings import SkillSettings
|
2017-06-16 18:31:44 +00:00
|
|
|
|
2016-05-20 14:16:01 +00:00
|
|
|
__author__ = 'seanfitz'
|
|
|
|
|
2017-06-29 19:36:02 +00:00
|
|
|
skills_config = ConfigurationManager.instance().get("skills")
|
2017-06-30 19:27:33 +00:00
|
|
|
BLACKLISTED_SKILLS = skills_config.get("blacklisted_skills", [])
|
2017-06-29 19:36:02 +00:00
|
|
|
|
2017-04-06 01:01:40 +00:00
|
|
|
SKILLS_DIR = "/opt/mycroft/skills"
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
MainModule = '__init__'
|
|
|
|
|
|
|
|
logger = getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
def load_vocab_from_file(path, vocab_type, emitter):
|
2016-09-04 01:24:18 +00:00
|
|
|
if path.endswith('.voc'):
|
2016-07-18 20:44:36 +00:00
|
|
|
with open(path, 'r') as voc_file:
|
|
|
|
for line in voc_file.readlines():
|
|
|
|
parts = line.strip().split("|")
|
|
|
|
entity = parts[0]
|
|
|
|
|
2016-09-04 01:24:18 +00:00
|
|
|
emitter.emit(Message("register_vocab", {
|
|
|
|
'start': entity, 'end': vocab_type
|
|
|
|
}))
|
2016-07-18 20:44:36 +00:00
|
|
|
for alias in parts[1:]:
|
2016-09-04 01:24:18 +00:00
|
|
|
emitter.emit(Message("register_vocab", {
|
|
|
|
'start': alias, 'end': vocab_type, 'alias_of': entity
|
|
|
|
}))
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
|
2016-06-28 20:20:48 +00:00
|
|
|
def load_regex_from_file(path, emitter):
|
2016-09-04 01:24:18 +00:00
|
|
|
if path.endswith('.rx'):
|
2016-06-28 20:20:48 +00:00
|
|
|
with open(path, 'r') as reg_file:
|
|
|
|
for line in reg_file.readlines():
|
|
|
|
re.compile(line.strip())
|
|
|
|
emitter.emit(
|
2016-09-04 01:24:18 +00:00
|
|
|
Message("register_vocab", {'regex': line.strip()}))
|
2016-06-28 20:20:48 +00:00
|
|
|
|
|
|
|
|
2016-05-20 14:16:01 +00:00
|
|
|
def load_vocabulary(basedir, emitter):
|
|
|
|
for vocab_type in os.listdir(basedir):
|
2016-07-18 20:44:36 +00:00
|
|
|
if vocab_type.endswith(".voc"):
|
|
|
|
load_vocab_from_file(
|
|
|
|
join(basedir, vocab_type), splitext(vocab_type)[0], emitter)
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
|
2016-06-28 20:20:48 +00:00
|
|
|
def load_regex(basedir, emitter):
|
|
|
|
for regex_type in os.listdir(basedir):
|
|
|
|
if regex_type.endswith(".rx"):
|
|
|
|
load_regex_from_file(
|
|
|
|
join(basedir, regex_type), emitter)
|
|
|
|
|
|
|
|
|
2016-05-20 14:16:01 +00:00
|
|
|
def open_intent_envelope(message):
|
2016-09-04 01:24:18 +00:00
|
|
|
intent_dict = message.data
|
2016-05-20 14:16:01 +00:00
|
|
|
return Intent(intent_dict.get('name'),
|
|
|
|
intent_dict.get('requires'),
|
|
|
|
intent_dict.get('at_least_one'),
|
|
|
|
intent_dict.get('optional'))
|
|
|
|
|
|
|
|
|
|
|
|
def load_skill(skill_descriptor, emitter):
|
|
|
|
try:
|
2017-02-14 01:16:20 +00:00
|
|
|
logger.info("ATTEMPTING TO LOAD SKILL: " + skill_descriptor["name"])
|
2017-03-11 19:46:18 +00:00
|
|
|
if skill_descriptor['name'] in BLACKLISTED_SKILLS:
|
|
|
|
logger.info("SKILL IS BLACKLISTED " + skill_descriptor["name"])
|
|
|
|
return None
|
2016-05-20 22:15:53 +00:00
|
|
|
skill_module = imp.load_module(
|
|
|
|
skill_descriptor["name"] + MainModule, *skill_descriptor["info"])
|
|
|
|
if (hasattr(skill_module, 'create_skill') and
|
|
|
|
callable(skill_module.create_skill)):
|
|
|
|
# v2 skills framework
|
2016-05-20 14:16:01 +00:00
|
|
|
skill = skill_module.create_skill()
|
|
|
|
skill.bind(emitter)
|
2017-04-24 16:38:35 +00:00
|
|
|
skill._dir = dirname(skill_descriptor['info'][1])
|
2017-02-07 06:09:09 +00:00
|
|
|
skill.load_data_files(dirname(skill_descriptor['info'][1]))
|
2017-06-30 11:03:03 +00:00
|
|
|
# Set up intent handlers
|
2016-05-20 14:16:01 +00:00
|
|
|
skill.initialize()
|
2017-02-05 16:01:12 +00:00
|
|
|
skill._register_decorated()
|
2017-04-24 19:01:07 +00:00
|
|
|
logger.info("Loaded " + skill_descriptor["name"])
|
2016-05-20 14:16:01 +00:00
|
|
|
return skill
|
|
|
|
else:
|
2016-05-20 22:15:53 +00:00
|
|
|
logger.warn(
|
|
|
|
"Module %s does not appear to be skill" % (
|
|
|
|
skill_descriptor["name"]))
|
2017-03-30 15:50:24 +00:00
|
|
|
except:
|
2016-05-20 22:15:53 +00:00
|
|
|
logger.error(
|
2017-03-30 15:50:24 +00:00
|
|
|
"Failed to load skill: " + skill_descriptor["name"], exc_info=True)
|
2016-05-20 14:16:01 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_skills(skills_folder):
|
2017-02-15 20:57:15 +00:00
|
|
|
logger.info("LOADING SKILLS FROM " + skills_folder)
|
2016-05-20 14:16:01 +00:00
|
|
|
skills = []
|
|
|
|
possible_skills = os.listdir(skills_folder)
|
|
|
|
for i in possible_skills:
|
|
|
|
location = join(skills_folder, i)
|
2016-11-15 21:06:44 +00:00
|
|
|
if (isdir(location) and
|
2016-12-20 21:19:22 +00:00
|
|
|
not MainModule + ".py" in os.listdir(location)):
|
2016-11-15 21:06:44 +00:00
|
|
|
for j in os.listdir(location):
|
|
|
|
name = join(location, j)
|
|
|
|
if (not isdir(name) or
|
|
|
|
not MainModule + ".py" in os.listdir(name)):
|
|
|
|
continue
|
|
|
|
skills.append(create_skill_descriptor(name))
|
2016-05-20 22:15:53 +00:00
|
|
|
if (not isdir(location) or
|
|
|
|
not MainModule + ".py" in os.listdir(location)):
|
2016-05-20 14:16:01 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
skills.append(create_skill_descriptor(location))
|
|
|
|
skills = sorted(skills, key=lambda p: p.get('name'))
|
|
|
|
return skills
|
|
|
|
|
|
|
|
|
|
|
|
def create_skill_descriptor(skill_folder):
|
|
|
|
info = imp.find_module(MainModule, [skill_folder])
|
|
|
|
return {"name": os.path.basename(skill_folder), "info": info}
|
|
|
|
|
|
|
|
|
2017-04-06 01:01:40 +00:00
|
|
|
def load_skills(emitter, skills_root=SKILLS_DIR):
|
2017-02-14 01:16:20 +00:00
|
|
|
logger.info("Checking " + skills_root + " for new skills")
|
2017-01-20 21:50:00 +00:00
|
|
|
skill_list = []
|
2017-04-06 01:01:40 +00:00
|
|
|
for skill in get_skills(skills_root):
|
|
|
|
skill_list.append(load_skill(skill, emitter))
|
|
|
|
|
2017-01-20 21:50:00 +00:00
|
|
|
return skill_list
|
|
|
|
|
|
|
|
|
|
|
|
def unload_skills(skills):
|
|
|
|
for s in skills:
|
2017-01-27 18:32:20 +00:00
|
|
|
s.shutdown()
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
|
2017-02-05 15:59:46 +00:00
|
|
|
_intent_list = []
|
2017-01-31 06:28:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def intent_handler(intent_parser):
|
2017-02-05 15:59:46 +00:00
|
|
|
""" Decorator for adding a method as an intent handler. """
|
2017-01-31 06:28:04 +00:00
|
|
|
def real_decorator(func):
|
2017-06-30 11:03:03 +00:00
|
|
|
@wraps(func)
|
2017-01-31 06:28:04 +00:00
|
|
|
def handler_method(*args, **kwargs):
|
|
|
|
return func(*args, **kwargs)
|
2017-02-05 15:59:46 +00:00
|
|
|
_intent_list.append((intent_parser, func))
|
2017-01-31 06:28:04 +00:00
|
|
|
return handler_method
|
|
|
|
return real_decorator
|
|
|
|
|
|
|
|
|
2016-05-20 14:16:01 +00:00
|
|
|
class MycroftSkill(object):
|
|
|
|
"""
|
2016-05-20 22:15:53 +00:00
|
|
|
Abstract base class which provides common behaviour and parameters to all
|
|
|
|
Skills implementation.
|
2016-05-20 14:16:01 +00:00
|
|
|
"""
|
|
|
|
|
2017-07-18 06:40:55 +00:00
|
|
|
def __init__(self, name=None, emitter=None):
|
|
|
|
self.name = name or self.__class__.__name__
|
2016-05-20 14:16:01 +00:00
|
|
|
self.bind(emitter)
|
2016-09-04 01:24:18 +00:00
|
|
|
self.config_core = ConfigurationManager.get()
|
2017-07-18 06:40:55 +00:00
|
|
|
self.config = self.config_core.get(self.name)
|
2016-05-20 14:16:01 +00:00
|
|
|
self.dialog_renderer = None
|
2017-07-18 06:40:55 +00:00
|
|
|
self.file_system = FileSystemAccess(join('skills', self.name))
|
2016-05-20 14:16:01 +00:00
|
|
|
self.registered_intents = []
|
2017-07-18 06:40:55 +00:00
|
|
|
self.log = getLogger(self.name)
|
2017-03-05 20:17:46 +00:00
|
|
|
self.reload_skill = True
|
2017-04-17 17:25:27 +00:00
|
|
|
self.events = []
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def location(self):
|
2017-02-03 10:08:43 +00:00
|
|
|
""" Get the JSON data struction holding location information. """
|
|
|
|
# TODO: Allow Enclosure to override this for devices that
|
|
|
|
# contain a GPS.
|
2016-05-20 14:16:01 +00:00
|
|
|
return self.config_core.get('location')
|
|
|
|
|
2017-02-03 10:08:43 +00:00
|
|
|
@property
|
|
|
|
def location_pretty(self):
|
|
|
|
""" Get a more 'human' version of the location as a string. """
|
|
|
|
loc = self.location
|
|
|
|
if type(loc) is dict and loc["city"]:
|
|
|
|
return loc["city"]["name"]
|
|
|
|
return None
|
2017-02-15 20:57:15 +00:00
|
|
|
|
2017-02-03 10:08:43 +00:00
|
|
|
@property
|
|
|
|
def location_timezone(self):
|
|
|
|
""" Get the timezone code, such as 'America/Los_Angeles' """
|
|
|
|
loc = self.location
|
|
|
|
if type(loc) is dict and loc["timezone"]:
|
|
|
|
return loc["timezone"]["code"]
|
|
|
|
return None
|
|
|
|
|
2016-05-20 14:16:01 +00:00
|
|
|
@property
|
|
|
|
def lang(self):
|
|
|
|
return self.config_core.get('lang')
|
|
|
|
|
2017-04-24 16:38:35 +00:00
|
|
|
@property
|
|
|
|
def settings(self):
|
|
|
|
""" Load settings if not already loaded. """
|
|
|
|
try:
|
|
|
|
return self._settings
|
|
|
|
except:
|
2017-07-27 21:28:32 +00:00
|
|
|
self._settings = SkillSettings(self._dir)
|
2017-04-24 16:38:35 +00:00
|
|
|
return self._settings
|
2017-04-13 05:26:45 +00:00
|
|
|
|
2016-05-20 14:16:01 +00:00
|
|
|
def bind(self, emitter):
|
|
|
|
if emitter:
|
|
|
|
self.emitter = emitter
|
2017-06-16 18:31:44 +00:00
|
|
|
self.enclosure = EnclosureAPI(emitter, self.name)
|
2016-05-20 14:16:01 +00:00
|
|
|
self.__register_stop()
|
|
|
|
|
|
|
|
def __register_stop(self):
|
|
|
|
self.stop_time = time.time()
|
2016-09-04 01:24:18 +00:00
|
|
|
self.stop_threshold = self.config_core.get("skills").get(
|
|
|
|
'stop_threshold')
|
2016-05-20 14:16:01 +00:00
|
|
|
self.emitter.on('mycroft.stop', self.__handle_stop)
|
|
|
|
|
|
|
|
def detach(self):
|
2017-03-14 21:01:48 +00:00
|
|
|
for (name, intent) in self.registered_intents:
|
2017-03-13 17:01:57 +00:00
|
|
|
name = self.name + ':' + name
|
2016-09-04 01:24:18 +00:00
|
|
|
self.emitter.emit(Message("detach_intent", {"intent_name": name}))
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
def initialize(self):
|
|
|
|
"""
|
|
|
|
Initialization function to be implemented by all Skills.
|
|
|
|
|
|
|
|
Usually used to create intents rules and register them.
|
|
|
|
"""
|
2017-02-05 15:59:46 +00:00
|
|
|
logger.debug("No initialize function implemented")
|
2016-05-20 14:16:01 +00:00
|
|
|
|
2017-02-05 15:59:46 +00:00
|
|
|
def _register_decorated(self):
|
|
|
|
"""
|
|
|
|
Register all intent handlers that has been decorated with an intent.
|
|
|
|
"""
|
|
|
|
global _intent_list
|
|
|
|
for intent_parser, handler in _intent_list:
|
2017-01-31 06:28:04 +00:00
|
|
|
self.register_intent(intent_parser, handler, need_self=True)
|
2017-02-05 15:59:46 +00:00
|
|
|
_intent_list = []
|
2017-01-31 06:28:04 +00:00
|
|
|
|
|
|
|
def register_intent(self, intent_parser, handler, need_self=False):
|
2017-03-13 17:01:57 +00:00
|
|
|
name = intent_parser.name
|
|
|
|
intent_parser.name = self.name + ':' + intent_parser.name
|
2016-09-04 01:24:18 +00:00
|
|
|
self.emitter.emit(Message("register_intent", intent_parser.__dict__))
|
2017-03-14 21:01:48 +00:00
|
|
|
self.registered_intents.append((name, intent_parser))
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
def receive_handler(message):
|
|
|
|
try:
|
2017-06-30 11:03:03 +00:00
|
|
|
if need_self:
|
|
|
|
# When registring from decorator self is required
|
|
|
|
handler(self, message)
|
|
|
|
else:
|
|
|
|
handler(message)
|
2017-03-30 15:50:24 +00:00
|
|
|
except:
|
2016-05-20 14:16:01 +00:00
|
|
|
# TODO: Localize
|
2016-05-20 22:15:53 +00:00
|
|
|
self.speak(
|
|
|
|
"An error occurred while processing a request in " +
|
|
|
|
self.name)
|
|
|
|
logger.error(
|
|
|
|
"An error occurred while processing a request in " +
|
2017-03-30 15:50:24 +00:00
|
|
|
self.name, exc_info=True)
|
2017-03-29 17:28:10 +00:00
|
|
|
|
2017-03-13 17:01:57 +00:00
|
|
|
if handler:
|
2017-06-30 11:03:03 +00:00
|
|
|
self.emitter.on(intent_parser.name, receive_handler)
|
2017-04-17 17:25:27 +00:00
|
|
|
self.events.append((intent_parser.name, receive_handler))
|
2017-03-13 17:01:57 +00:00
|
|
|
|
|
|
|
def disable_intent(self, intent_name):
|
|
|
|
"""Disable a registered intent"""
|
2017-03-14 20:55:04 +00:00
|
|
|
logger.debug('Disabling intent ' + intent_name)
|
2017-03-13 17:01:57 +00:00
|
|
|
name = self.name + ':' + intent_name
|
|
|
|
self.emitter.emit(Message("detach_intent", {"intent_name": name}))
|
|
|
|
|
|
|
|
def enable_intent(self, intent_name):
|
|
|
|
"""Reenable a registered intent"""
|
2017-03-14 21:01:48 +00:00
|
|
|
for (name, intent) in self.registered_intents:
|
2017-03-13 17:01:57 +00:00
|
|
|
if name == intent_name:
|
2017-03-14 21:01:48 +00:00
|
|
|
self.registered_intents.remove((name, intent))
|
2017-03-13 17:01:57 +00:00
|
|
|
intent.name = name
|
|
|
|
self.register_intent(intent, None)
|
2017-03-14 20:55:04 +00:00
|
|
|
logger.debug('Enabling intent ' + intent_name)
|
2017-03-13 17:01:57 +00:00
|
|
|
break
|
2017-03-14 20:55:04 +00:00
|
|
|
else:
|
|
|
|
logger.error('Could not enable ' + intent_name +
|
|
|
|
', it hasn\'t been registered.')
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
def register_vocabulary(self, entity, entity_type):
|
2016-09-04 01:24:18 +00:00
|
|
|
self.emitter.emit(Message('register_vocab', {
|
|
|
|
'start': entity, 'end': entity_type
|
|
|
|
}))
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
def register_regex(self, regex_str):
|
|
|
|
re.compile(regex_str) # validate regex
|
2016-09-04 01:24:18 +00:00
|
|
|
self.emitter.emit(Message('register_vocab', {'regex': regex_str}))
|
2016-05-20 14:16:01 +00:00
|
|
|
|
2017-03-17 19:17:52 +00:00
|
|
|
def speak(self, utterance, expect_response=False):
|
2017-06-16 18:31:44 +00:00
|
|
|
# registers the skill as being active
|
|
|
|
self.enclosure.register(self.name)
|
2017-03-17 19:17:52 +00:00
|
|
|
data = {'utterance': utterance,
|
|
|
|
'expect_response': expect_response}
|
|
|
|
self.emitter.emit(Message("speak", data))
|
2016-05-20 14:16:01 +00:00
|
|
|
|
2017-03-17 19:17:52 +00:00
|
|
|
def speak_dialog(self, key, data={}, expect_response=False):
|
|
|
|
data['expect_response'] = expect_response
|
2016-05-20 14:16:01 +00:00
|
|
|
self.speak(self.dialog_renderer.render(key, data))
|
|
|
|
|
|
|
|
def init_dialog(self, root_directory):
|
2017-02-04 20:00:22 +00:00
|
|
|
dialog_dir = join(root_directory, 'dialog', self.lang)
|
|
|
|
if os.path.exists(dialog_dir):
|
|
|
|
self.dialog_renderer = DialogLoader().load(dialog_dir)
|
|
|
|
else:
|
2017-07-05 12:39:43 +00:00
|
|
|
logger.debug('No dialog loaded, ' + dialog_dir + ' does not exist')
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
def load_data_files(self, root_directory):
|
|
|
|
self.init_dialog(root_directory)
|
|
|
|
self.load_vocab_files(join(root_directory, 'vocab', self.lang))
|
2016-07-08 23:11:18 +00:00
|
|
|
regex_path = join(root_directory, 'regex', self.lang)
|
|
|
|
if os.path.exists(regex_path):
|
|
|
|
self.load_regex_files(regex_path)
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
def load_vocab_files(self, vocab_dir):
|
2017-02-04 20:00:22 +00:00
|
|
|
if os.path.exists(vocab_dir):
|
|
|
|
load_vocabulary(vocab_dir, self.emitter)
|
|
|
|
else:
|
2017-07-05 12:39:43 +00:00
|
|
|
logger.debug('No vocab loaded, ' + vocab_dir + ' does not exist')
|
2016-05-20 14:16:01 +00:00
|
|
|
|
2016-06-28 20:20:48 +00:00
|
|
|
def load_regex_files(self, regex_dir):
|
|
|
|
load_regex(regex_dir, self.emitter)
|
|
|
|
|
2016-05-20 14:16:01 +00:00
|
|
|
def __handle_stop(self, event):
|
2017-08-03 12:14:31 +00:00
|
|
|
"""
|
|
|
|
Handler for the "mycroft.stop" signal. Runs the user defined
|
|
|
|
`stop()` method.
|
|
|
|
"""
|
2016-05-20 14:16:01 +00:00
|
|
|
self.stop_time = time.time()
|
2017-08-03 12:14:31 +00:00
|
|
|
try:
|
|
|
|
self.stop()
|
|
|
|
except:
|
|
|
|
logger.error("Failed to stop skill: {}".format(self.name),
|
|
|
|
exc_info=True)
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def stop(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def is_stop(self):
|
|
|
|
passed_time = time.time() - self.stop_time
|
|
|
|
return passed_time < self.stop_threshold
|
2017-01-20 21:50:00 +00:00
|
|
|
|
2017-01-27 18:32:20 +00:00
|
|
|
def shutdown(self):
|
2017-01-28 00:36:19 +00:00
|
|
|
"""
|
2017-02-15 20:57:15 +00:00
|
|
|
This method is intended to be called during the skill
|
|
|
|
process termination. The skill implementation must
|
|
|
|
shutdown all processes and operations in execution.
|
2017-01-28 00:36:19 +00:00
|
|
|
"""
|
2017-04-23 20:54:22 +00:00
|
|
|
# Store settings
|
|
|
|
self.settings.store()
|
2017-04-17 17:25:27 +00:00
|
|
|
|
|
|
|
# removing events
|
|
|
|
for e, f in self.events:
|
|
|
|
self.emitter.remove(e, f)
|
|
|
|
|
|
|
|
self.emitter.emit(
|
|
|
|
Message("detach_skill", {"skill_name": self.name + ":"}))
|
2017-08-03 12:14:31 +00:00
|
|
|
try:
|
|
|
|
self.stop()
|
|
|
|
except:
|
|
|
|
logger.error("Failed to stop skill: {}".format(self.name),
|
|
|
|
exc_info=True)
|
2017-07-14 15:08:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
class FallbackSkill(MycroftSkill):
|
|
|
|
fallback_handlers = {}
|
|
|
|
|
2017-08-09 12:18:14 +00:00
|
|
|
def __init__(self, name=None, emitter=None):
|
2017-07-14 15:08:34 +00:00
|
|
|
MycroftSkill.__init__(self, name, emitter)
|
|
|
|
|
2017-08-09 12:24:21 +00:00
|
|
|
# list of fallback handlers registered by this instance
|
|
|
|
self.instance_fallback_handlers = []
|
|
|
|
|
2017-07-14 15:12:41 +00:00
|
|
|
@classmethod
|
|
|
|
def make_intent_failure_handler(cls, ws):
|
2017-07-14 15:08:34 +00:00
|
|
|
"""Goes through all fallback handlers until one returns true"""
|
|
|
|
|
|
|
|
def handler(message):
|
2017-07-14 15:12:41 +00:00
|
|
|
for _, handler in sorted(cls.fallback_handlers.items(),
|
2017-07-14 15:08:34 +00:00
|
|
|
key=operator.itemgetter(0)):
|
|
|
|
try:
|
|
|
|
if handler(message):
|
|
|
|
return
|
|
|
|
except Exception as e:
|
|
|
|
logger.info('Exception in fallback: ' + str(e))
|
|
|
|
ws.emit(Message('complete_intent_failure'))
|
|
|
|
logger.warn('No fallback could handle intent.')
|
|
|
|
|
|
|
|
return handler
|
|
|
|
|
2017-07-14 15:12:41 +00:00
|
|
|
@classmethod
|
2017-08-09 12:24:21 +00:00
|
|
|
def _register_fallback(cls, handler, priority):
|
2017-07-14 15:08:34 +00:00
|
|
|
"""
|
|
|
|
Register a function to be called as a general info fallback
|
|
|
|
Fallback should receive message and return
|
|
|
|
a boolean (True if succeeded or False if failed)
|
|
|
|
|
|
|
|
Lower priority gets run first
|
|
|
|
0 for high priority 100 for low priority
|
|
|
|
"""
|
2017-07-14 15:12:41 +00:00
|
|
|
while priority in cls.fallback_handlers:
|
2017-07-14 15:08:34 +00:00
|
|
|
priority += 1
|
|
|
|
|
2017-07-14 15:12:41 +00:00
|
|
|
cls.fallback_handlers[priority] = handler
|
2017-07-14 15:08:34 +00:00
|
|
|
|
2017-08-09 12:24:21 +00:00
|
|
|
def register_fallback(self, handler, priority):
|
|
|
|
"""
|
|
|
|
register a fallback with the list of fallback handlers
|
|
|
|
and with the list of handlers registered by this instance
|
|
|
|
"""
|
|
|
|
self.instance_fallback_handlers.append(handler)
|
|
|
|
self._register_fallback(handler, priority)
|
|
|
|
|
2017-07-14 15:12:41 +00:00
|
|
|
@classmethod
|
|
|
|
def remove_fallback(cls, handler_to_del):
|
2017-08-09 12:24:21 +00:00
|
|
|
"""
|
|
|
|
Remove a fallback handler
|
|
|
|
|
|
|
|
Args:
|
|
|
|
handler_to_del: reference to handler
|
|
|
|
"""
|
2017-07-14 15:12:41 +00:00
|
|
|
for priority, handler in cls.fallback_handlers.items():
|
2017-07-14 15:08:34 +00:00
|
|
|
if handler == handler_to_del:
|
2017-07-14 15:12:41 +00:00
|
|
|
del cls.fallback_handlers[priority]
|
2017-07-14 15:08:34 +00:00
|
|
|
return
|
|
|
|
logger.warn('Could not remove fallback!')
|
2017-08-09 12:24:21 +00:00
|
|
|
|
|
|
|
def remove_instance_handlers(self):
|
|
|
|
"""
|
|
|
|
Remove all fallback handlers registered by the fallback skill.
|
|
|
|
"""
|
|
|
|
while len(self.instance_fallback_handlers):
|
|
|
|
handler = self.instance_fallback_handlers.pop()
|
|
|
|
self.remove_fallback(handler)
|
|
|
|
|
|
|
|
def shutdown(self):
|
|
|
|
"""
|
|
|
|
Remove all registered handlers and perform skill shutdown.
|
|
|
|
"""
|
|
|
|
self.remove_instance_handlers()
|
|
|
|
super(FallbackSkill, self).shutdown()
|