Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions neon_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

environ["OVOS_DEFAULT_CONFIG"] = join(dirname(__file__),
"configuration", "neon.yaml")
environ.setdefault('OVOS_CONFIG_BASE_FOLDER', "neon")
environ.setdefault('OVOS_CONFIG_FILENAME', "neon.yaml")

# Patching deprecation warnings
# TODO: Deprecate after migration to ovos-workshop 1.0+ and ovos-core 0.3.0
Expand Down
108 changes: 90 additions & 18 deletions neon_core/skills/skill_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,46 @@

from os import makedirs
from os.path import isdir, join, expanduser
from threading import Thread
from ovos_utils.xdg_utils import xdg_data_home
from ovos_utils.log import LOG

from ovos_utils.log import LOG, deprecated
from ovos_bus_client.message import Message
from ovos_core.skill_manager import SkillManager


class NeonSkillManager(SkillManager):
def _sync_skill_loading_state(self):
"""
Override to wait for configured ready settings before announcing the
service is ready
"""
SkillManager._sync_skill_loading_state(self)
LOG.info(
"Waiting for skill ready settings"
) # TODO Log is only for debugging
self._wait_until_skills_ready()

# Start a background thread to check for configured ready settings
# while allowing the skills service to continue initialization
ready_event_thread = Thread(target=self._check_device_ready)
ready_event_thread.daemon = True
ready_event_thread.start()

@deprecated("Legacy skills are deprecated and this method should not "
"be used.", "25.10.1")
def get_default_skills_dir(self):
"""
Go through legacy config params to locate the default skill directory
"""
skill_config = self.config["skills"]
skill_dir = skill_config.get("directory") or \
skill_config.get("extra_directories")
skill_dir = skill_dir[0] if isinstance(skill_dir, list) and \
len(skill_dir) > 0 else skill_dir or \
join(xdg_data_home(), "neon", "skills")
skill_dir = skill_config.get("directory") or skill_config.get(
"extra_directories"
)
skill_dir = (
skill_dir[0]
if isinstance(skill_dir, list) and len(skill_dir) > 0
else skill_dir or join(xdg_data_home(), "neon", "skills")
)

skill_dir = expanduser(skill_dir)
if not isdir(skill_dir):
Expand All @@ -61,20 +83,70 @@ def get_default_skills_dir(self):

return skill_dir

def _load_new_skills(self, *args, **kwargs):
# Override load method for config module checks
SkillManager._load_new_skills(self, *args, **kwargs)

def _get_plugin_skill_loader(self, skill_id, init_bus=True):
assert self.bus is not None
if not init_bus:
LOG.debug("Ignoring request not to bind bus")
return SkillManager._get_plugin_skill_loader(self, skill_id, True)

def run(self):
"""Load skills and update periodically from disk and internet."""
from os import environ
environ.setdefault('OVOS_CONFIG_BASE_FOLDER', "neon")
environ.setdefault('OVOS_CONFIG_FILENAME', "neon.yaml")
LOG.debug("set default configuration to `neon/neon.yaml`")
SkillManager.run(self)
# Re-implement support for internet and network skill load
def _wait_until_skills_ready(self):
"""
Block until configured network and internet skills are loaded to
delay skills service reporting ready.
"""
ready_settings = self.config.get("ready_settings", ["skills"])
if "network_skills" in ready_settings:
if not self._network_loaded.wait(self._network_skill_timeout):
LOG.error("Timeout waiting for network skills to load")
return False
if "internet_skills" in ready_settings:
if not self._internet_loaded.wait(self._network_skill_timeout):
LOG.error("Timeout waiting for internet skills to load")
return False
LOG.debug("Configured skill load conditions met")
return True

def _check_device_ready(self):
while not self._wait_until_skills_ready():
LOG.warning("Skills not ready, still waiting...")
ready_settings = self.config.get("ready_settings", ["skills"])
valid_services = (
"skills",
"voice",
"audio",
"gui_service",
"internet",
)
ready_services = {
s: False for s in ready_settings if s in valid_services
}
LOG.info(f"Waiting for services: {ready_services}")
while not all(ready_services.values()):
for service in ready_services:
if not ready_services[service]:
resp = self.bus.wait_for_response(
Message(
f"mycroft.{service}.is_ready",
context={
"source": ["skills"],
"destination": [service],
},
)
)
LOG.debug(
resp.data
if resp
else f"No response for service={service}"
)
service_ready = resp and resp.data.get("status")
if service_ready:
LOG.info(f"{service} reports ready")
ready_services[service] = service_ready
LOG.info(f"All configured ready settings met: {ready_services}")
self.bus.emit(
Message(
"mycroft.ready",
context={"source": ["skills"], "destination": valid_services},
)
)
3 changes: 1 addition & 2 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# ovos-core version pinned for compat. with patches in NeonCore
ovos-core[lgpl]~=0.1
ovos-core[lgpl]~=0.2


neon-utils[network]~=1.13,>=1.13.1a4
Expand Down
2 changes: 0 additions & 2 deletions test/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ class ConfigurationTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
os.environ["XDG_CONFIG_HOME"] = cls.CONFIG_PATH
os.environ["OVOS_CONFIG_BASE_FOLDER"] = "neon"
os.environ["OVOS_CONFIG_FILENAME"] = "neon.yaml"

@classmethod
def tearDownClass(cls) -> None:
Expand Down
53 changes: 38 additions & 15 deletions test/test_skills_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
import unittest
import wave

from pytest import mark
from mock import Mock, patch
from copy import deepcopy
from os.path import join, dirname, expanduser, isdir
from threading import Event
from time import time

from unittest.mock import Mock, patch
from ovos_bus_client import Message
from ovos_utils.messagebus import FakeBus
from ovos_utils.xdg_utils import xdg_data_home
Expand Down Expand Up @@ -67,8 +67,6 @@ class TestSkillService(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
os.environ["XDG_CONFIG_HOME"] = cls.config_dir
os.environ["OVOS_CONFIG_BASE_FOLDER"] = "neon"
os.environ["OVOS_CONFIG_FILENAME"] = "neon.yaml"

@classmethod
def tearDownClass(cls) -> None:
Expand Down Expand Up @@ -184,8 +182,6 @@ def setUpClass(cls) -> None:
import neon_core

os.environ["XDG_CONFIG_HOME"] = cls.test_config_dir
os.environ["OVOS_CONFIG_BASE_FOLDER"] = "neon"
os.environ["OVOS_CONFIG_FILENAME"] = "neon.yaml"
import ovos_config
import importlib
importlib.reload(ovos_config.meta)
Expand All @@ -209,8 +205,6 @@ def setUpClass(cls) -> None:
def tearDownClass(cls) -> None:
cls.intent_service.shutdown()
os.environ.pop("XDG_CONFIG_HOME")
os.environ.pop("OVOS_CONFIG_BASE_FOLDER")
os.environ.pop("OVOS_CONFIG_FILENAME")
shutil.rmtree(cls.test_config_dir)

def test_save_utterance_transcription(self):
Expand Down Expand Up @@ -357,21 +351,15 @@ class TestSkillManager(unittest.TestCase):

@classmethod
def setUpClass(cls) -> None:
#from neon_core.util.runtime_utils import use_neon_core
#from neon_utils.configuration_utils import init_config_dir
os.environ["XDG_CONFIG_HOME"] = cls.config_dir
os.environ["OVOS_CONFIG_BASE_FOLDER"] = "neon"
os.environ["OVOS_CONFIG_FILENAME"] = "neon.yaml"
#use_neon_core(init_config_dir)()

@classmethod
def tearDownClass(cls) -> None:
os.environ.pop("XDG_CONFIG_HOME")
os.environ.pop("OVOS_CONFIG_BASE_FOLDER")
os.environ.pop("OVOS_CONFIG_FILENAME")
if os.path.isdir(cls.config_dir):
shutil.rmtree(cls.config_dir)

@mark.skip("Skill directory handling is deprecated")
@patch("ovos_core.skill_manager.SkillManager.run")
def test_get_default_skills_dir(self, _):
from neon_core.skills.skill_manager import NeonSkillManager
Expand Down Expand Up @@ -410,5 +398,40 @@ def test_get_default_skills_dir(self, _):
self.assertEqual(default_dir, expanduser('~/neon-skills'))
self.assertTrue(isdir(expanduser("~/neon-skills")))

def test_wait_until_skills_ready(self):
from neon_core.skills.skill_manager import NeonSkillManager
manager = NeonSkillManager(FakeBus())
manager._network_skill_timeout = 1

# No ready settings is ready
manager.config['ready_settings'] = []
self.assertTrue(manager._wait_until_skills_ready())

# Check network skills not ready
manager.config['ready_settings'] = ['network_skills']
self.assertFalse(manager._wait_until_skills_ready())

# Check internet skills not ready
manager.config['ready_settings'].append('internet_skills')
self.assertFalse(manager._wait_until_skills_ready())

# Check skills are loaded
manager._network_loaded.set()
manager._internet_loaded.set()
self.assertTrue(manager._wait_until_skills_ready())

def test_check_device_ready(self):
from neon_core.skills.skill_manager import NeonSkillManager
manager = NeonSkillManager(FakeBus())

on_ready = Mock()
manager.bus.on("mycroft.ready", on_ready)

# No services to wait for
manager.config['ready_settings'] = []
manager._check_device_ready()
on_ready.assert_called_once()


if __name__ == "__main__":
unittest.main()