From 5616aae0ce312c72bbae29ee7bde2fbcf0f65ecd Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Mon, 14 Sep 2020 14:44:28 +0930 Subject: [PATCH 01/68] fix broken link; minor edits --- .github/CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 027deb727c75..1d3b019348a2 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -46,11 +46,11 @@ git push -f ``` 10. If possible, create unit tests for your changes * [Unit Tests for most contributions](https://github.com/MycroftAI/mycroft-core/tree/dev/test) - * [Intent Tests for new skills](https://mycroft-ai.gitbook.io/docs/#testing-your-skill) + * [Intent Tests for new skills](https://mycroft-ai.gitbook.io/docs/skill-development/voight-kampff) * We utilize TRAVIS-CI, which will test each pull request. To test locally you can run: `./start-mycroft.sh unittest` - 11. Once everything is okay, you can finally [create a Pull Request (PR)](https://help.github.com/articles/using-pull-requests/) on [MycroftAi/mycroft-core](https://github.com/MycroftAI/mycroft-core/pulls) to have your code reviewed and merged. + 11. Once everything is okay, you can [create a Pull Request (PR)](https://help.github.com/articles/using-pull-requests/) on [MycroftAi/mycroft-core](https://github.com/MycroftAI/mycroft-core/pulls) to have your code reviewed and merged. -**Note**: Even if you have write access to the master branch, do not work directly on master! +**Note**: Do not work directly on the Master branch. All changes must be merged into dev, and will be automatically pushed to Master in the next release. ## Submit Changes From 3c668a1b51c8ed7ba341102234a32575a9dbeede Mon Sep 17 00:00:00 2001 From: el-tocino Date: Tue, 6 Oct 2020 17:13:21 -0500 Subject: [PATCH 02/68] easy fix for cli issue 1943 --- start-mycroft.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/start-mycroft.sh b/start-mycroft.sh index 05814c510d8f..d21954d834e8 100755 --- a/start-mycroft.sh +++ b/start-mycroft.sh @@ -190,7 +190,9 @@ if [[ "${1}" == "restart" ]] || [[ "${_opt}" == "restart" ]] ; then fi _params=$@ -check-dependencies +if ! grep -i cli <<< ${_params} ; then + check-dependencies +fi case ${_opt} in "all") From 5084de97913401874e1833e5a1c420c8076a192e Mon Sep 17 00:00:00 2001 From: el-tocino Date: Tue, 6 Oct 2020 17:18:10 -0500 Subject: [PATCH 03/68] add quotes for good measure. --- start-mycroft.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start-mycroft.sh b/start-mycroft.sh index d21954d834e8..a84b0beadd71 100755 --- a/start-mycroft.sh +++ b/start-mycroft.sh @@ -190,7 +190,7 @@ if [[ "${1}" == "restart" ]] || [[ "${_opt}" == "restart" ]] ; then fi _params=$@ -if ! grep -i cli <<< ${_params} ; then +if ! grep -i cli <<< "${_params}" ; then check-dependencies fi From e0c7016b4da6f4f90b97addd0404ef4bf9af75e4 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Wed, 28 Oct 2020 16:30:10 +0930 Subject: [PATCH 04/68] revert a temporary bugfix from PR #2626 --- test/integrationtests/voight_kampff/features/environment.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/integrationtests/voight_kampff/features/environment.py b/test/integrationtests/voight_kampff/features/environment.py index 6b1c3c8fcb9b..ca92a87adb41 100644 --- a/test/integrationtests/voight_kampff/features/environment.py +++ b/test/integrationtests/voight_kampff/features/environment.py @@ -90,12 +90,6 @@ def before_all(context): else: sleep(1) - # Temporary bugfix - First test to run sometimes fails - # Sleeping to see if something isn't finished setting up when tests start - # More info in Jira Ticket MYC-370 - # TODO - remove and fix properly dependant on if failures continue - sleep(10) - context.bus = bus context.step_timeout = 10 # Reset the step_timeout to 10 seconds context.matched_message = None From e0489a8488f4412a4d081d233470797cec187b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Sun, 17 Jan 2021 21:52:17 +0100 Subject: [PATCH 05/68] Add update comment logic to Jenkins --- Jenkinsfile | 54 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index be7ea9d8fa08..bbc124bc0d17 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -120,16 +120,33 @@ pipeline { } failure { script { + def comment_text = 'Voight Kampff Integration Test Failed ([Results](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + ')). ' + + '\nMycroft logs are also available: ' + + '[skills.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/skills.log), ' + + '[audio.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/audio.log), ' + + '[voice.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/voice.log), ' + + '[bus.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/bus.log), ' + + '[enclosure.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/enclosure.log)' + // Create comment for Pull Requests if (env.CHANGE_ID) { - echo 'Sending PR comment' - pullRequest.comment('Voight Kampff Integration Test Failed ([Results](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + ')). ' + - '\nMycroft logs are also available: ' + - '[skills.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/skills.log), ' + - '[audio.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/audio.log), ' + - '[voice.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/voice.log), ' + - '[bus.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/bus.log), ' + - '[enclosure.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/enclosure.log)') + def found_comment = false + for (comment in pullRequest.comments) { + echo "Author: ${comment.user}" + if (comment.user == "devops-mycroft" && + comment.body.contains("Voight Kampff")) { + echo "Updating comment..." + found_comment = true + pullRequest.editComment( + comment.id, + comment_text + ) + } + } + if (!found_comment) { + echo 'Sending PR comment' + pullRequest.comment(comment_text) + } } } // Send failure email containing a link to the Jenkins build @@ -184,8 +201,25 @@ pipeline { success { script { if (env.CHANGE_ID) { - echo 'Sending PR comment' - pullRequest.comment('Voight Kampff Integration Test Succeeded ([Results](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '))') + def comment_text = 'Voight Kampff Integration Test Succeeded ([Results](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '))' + def found_comment = false + for (comment in pullRequest.comments) { + echo "Author: ${comment.user}" + if (comment.user == "devops-mycroft" && + comment.body.contains("Voight Kampff")) { + echo "Updating comment!" + found_comment = true + pullRequest.editComment( + comment.id, + comment_text + ) + break + } + } + if (!found_comment) { + echo 'Sending PR comment' + pullRequest.comment(comment_text) + } } } // Send success email containing a link to the Jenkins build From 5d68309523f8a38c88ecbf908cbdf8e587b6ad4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Wed, 28 Apr 2021 21:03:10 +0200 Subject: [PATCH 06/68] Refactor mimic_tts to not load config when importing Hitting the entire configuration fetching routine with call to the backend is not polite to do when just importing the file. This moves the config lookup out of the global scope and into special functions for finding the mimic binary and looking up the data path for the subscriber voices. --- mycroft/tts/mimic_tts.py | 64 +++++++++++++++++++--------- test/unittests/tts/test_mimic_tts.py | 13 +++--- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/mycroft/tts/mimic_tts.py b/mycroft/tts/mimic_tts.py index 47d1d185bdae..69eb817526f3 100644 --- a/mycroft/tts/mimic_tts.py +++ b/mycroft/tts/mimic_tts.py @@ -32,19 +32,35 @@ from .tts import TTS, TTSValidator -CONFIG = Configuration.get().get("tts").get("mimic") -DATA_DIR = expanduser(Configuration.get()['data_dir']) -BIN = CONFIG.get("path", - os.path.join(MYCROFT_ROOT_PATH, 'mimic', 'bin', 'mimic')) +def get_mimic_binary(): + """Find the mimic binary, either from config or from PATH. -if not os.path.isfile(BIN): - # Search for mimic on the path - import distutils.spawn + Returns: + (str) path of mimic executable + """ + config = Configuration.get().get("tts").get("mimic") + + bin_ = config.get("path", + os.path.join(MYCROFT_ROOT_PATH, 'mimic', 'bin', 'mimic')) + + if not os.path.isfile(bin_): + # Search for mimic on the path + import distutils.spawn + + bin_ = distutils.spawn.find_executable("mimic") - BIN = distutils.spawn.find_executable("mimic") + return bin_ -SUBSCRIBER_VOICES = {'trinity': join(DATA_DIR, 'voices/mimic_tn')} + +def get_subscriber_voices(): + """Get dict of mimic voices exclusive to subscribers. + + Returns: + (dict) map of voices to custom Mimic executables. + """ + data_dir = expanduser(Configuration.get()['data_dir']) + return {'trinity': join(data_dir, 'voices/mimic_tn')} def download_subscriber_voices(selected_voice): @@ -52,6 +68,7 @@ def download_subscriber_voices(selected_voice): The function starts with the currently selected if applicable """ + subscriber_voices = get_subscriber_voices() def make_executable(dest): """Call back function to make the downloaded file executable.""" @@ -61,7 +78,7 @@ def make_executable(dest): os.chmod(dest, file_stat.st_mode | stat.S_IEXEC) # First download the selected voice if needed - voice_file = SUBSCRIBER_VOICES.get(selected_voice) + voice_file = subscriber_voices.get(selected_voice) if voice_file is not None and not exists(voice_file): LOG.info('Voice doesn\'t exist, downloading') url = DeviceApi().get_subscriber_voice_url(selected_voice) @@ -76,8 +93,8 @@ def make_executable(dest): .format(selected_voice)) # Download the rest of the subsciber voices as needed - for voice in SUBSCRIBER_VOICES: - voice_file = SUBSCRIBER_VOICES[voice] + for voice in subscriber_voices: + voice_file = subscriber_voices[voice] if not exists(voice_file): url = DeviceApi().get_subscriber_voice_url(voice) # Check we got an url @@ -111,9 +128,12 @@ def __init__(self, lang, config): lang, config, MimicValidator(self), 'wav', ssml_tags=["speak", "ssml", "phoneme", "voice", "audio", "prosody"] ) + self.default_binary = get_mimic_binary() + self.clear_cache() # Download subscriber voices if needed + self.subscriber_voices = get_subscriber_voices() self.is_subscriber = DeviceApi().is_subscriber if self.is_subscriber: trd = Thread(target=download_subscriber_voices, args=[self.voice]) @@ -137,18 +157,19 @@ def modify_tag(self, tag): @property def args(self): """Build mimic arguments.""" - if (self.voice in SUBSCRIBER_VOICES and - exists(SUBSCRIBER_VOICES[self.voice]) and self.is_subscriber): + subscriber_voices = self.subscriber_voices + if (self.voice in subscriber_voices and + exists(subscriber_voices[self.voice]) and self.is_subscriber): # Use subscriber voice - mimic_bin = SUBSCRIBER_VOICES[self.voice] + mimic_bin = subscriber_voices[self.voice] voice = self.voice - elif self.voice in SUBSCRIBER_VOICES: + elif self.voice in subscriber_voices: # Premium voice but bin doesn't exist, use ap while downloading - mimic_bin = BIN + mimic_bin = self.default_binary voice = 'ap' else: # Normal case use normal binary and selected voice - mimic_bin = BIN + mimic_bin = self.default_binary voice = self.voice args = [mimic_bin, '-voice', voice, '-psdur', '-ssml'] @@ -195,11 +216,12 @@ def validate_lang(self): def validate_connection(self): """Check that Mimic executable is found and works.""" + mimic_bin = get_mimic_binary() try: - subprocess.call([BIN, '--version']) + subprocess.call([mimic_bin, '--version']) except Exception as err: - if BIN: - LOG.error('Failed to find mimic at: {}'.format(BIN)) + if mimic_bin: + LOG.error('Failed to find mimic at: {}'.format(mimic_bin)) else: LOG.error('Mimic executable not found') raise Exception( diff --git a/test/unittests/tts/test_mimic_tts.py b/test/unittests/tts/test_mimic_tts.py index e9bb5c9cb3d9..fd2bb94612f3 100644 --- a/test/unittests/tts/test_mimic_tts.py +++ b/test/unittests/tts/test_mimic_tts.py @@ -3,8 +3,9 @@ import unittest from unittest import mock -from mycroft.tts.mimic_tts import (Mimic, download_subscriber_voices, BIN, - SUBSCRIBER_VOICES) +from mycroft.tts.mimic_tts import (Mimic, download_subscriber_voices, + get_mimic_binary, + get_subscriber_voices) device_instance_mock = mock.Mock(name='device_api_instance') @@ -55,15 +56,17 @@ def test_viseme(self, _, mock_device_api): @mock.patch('mycroft.tts.mimic_tts.Thread') def test_subscriber(self, mock_thread, _, mock_device_api): mock_device_api.return_value = subscribed_device - + default_mimic = get_mimic_binary() + trinity_mimic = get_subscriber_voices()['trinity'] m = Mimic('en-US', {'voice': 'trinity'}) mock_thread.assert_called_with(target=download_subscriber_voices, args=['trinity']) self.assertTrue(m.is_subscriber) - self.assertEqual(m.args, [BIN, '-voice', 'ap', '-psdur', '-ssml']) + self.assertEqual(m.args, + [default_mimic, '-voice', 'ap', '-psdur', '-ssml']) with mock.patch('mycroft.tts.mimic_tts.exists') as mock_exists: mock_exists.return_value = True - self.assertEqual(m.args, [SUBSCRIBER_VOICES['trinity'], '-voice', + self.assertEqual(m.args, [trinity_mimic, '-voice', 'trinity', '-psdur', '-ssml']) @mock.patch('mycroft.tts.mimic_tts.sleep') From d884da5b564eb49ab27205eb5433ed1813b61b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Wed, 28 Apr 2021 22:31:30 +0200 Subject: [PATCH 07/68] Be more defensive when loading mimic config Default config will generally always have a mimic block but in combination with newer Mycroft variants a more defensive approach is probably good --- mycroft/tts/mimic_tts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mycroft/tts/mimic_tts.py b/mycroft/tts/mimic_tts.py index 69eb817526f3..d4ddf1094341 100644 --- a/mycroft/tts/mimic_tts.py +++ b/mycroft/tts/mimic_tts.py @@ -39,7 +39,7 @@ def get_mimic_binary(): Returns: (str) path of mimic executable """ - config = Configuration.get().get("tts").get("mimic") + config = Configuration.get().get("tts", {}).get("mimic") bin_ = config.get("path", os.path.join(MYCROFT_ROOT_PATH, 'mimic', 'bin', 'mimic')) From 249e352f65ffe055079f4d4dcf88fa2b6039e235 Mon Sep 17 00:00:00 2001 From: Ken Smith Date: Tue, 4 May 2021 07:32:08 -0400 Subject: [PATCH 08/68] Add Common Play to active Skills when invoked --- mycroft/skills/common_play_skill.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mycroft/skills/common_play_skill.py b/mycroft/skills/common_play_skill.py index 162810a6039b..7e32bee560f6 100644 --- a/mycroft/skills/common_play_skill.py +++ b/mycroft/skills/common_play_skill.py @@ -55,6 +55,7 @@ class CommonPlaySkill(MycroftSkill, ABC): mycroft-playback-control skill and no special vocab for starting playback is needed. """ + def __init__(self, name=None, bus=None): super().__init__(name, bus) self.audioservice = None @@ -165,6 +166,8 @@ def __handle_play_start(self, message): # "... on the chromecast" self.play_service_string = phrase + self.make_active() + # Invoke derived class to provide playback data self.CPS_start(phrase, data) From e75a05557b0f25a4e774c2f9b7615747d25e4810 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Wed, 5 May 2021 14:11:56 +0930 Subject: [PATCH 09/68] docstring cleanup --- doc/index.rst | 1 + doc/source/mycroft.audio.rst | 10 +- doc/source/mycroft.rst | 21 +-- doc/source/mycroft.util.format.rst | 2 +- doc/source/mycroft.util.rst | 16 --- mycroft/api/__init__.py | 11 +- mycroft/audio/audioservice.py | 12 +- mycroft/audio/services/__init__.py | 10 +- mycroft/audio/speech.py | 6 +- mycroft/client/enclosure/__main__.py | 2 +- mycroft/client/enclosure/mark1/mouth.py | 6 +- mycroft/client/speech/data_structures.py | 6 +- mycroft/client/speech/hotword_factory.py | 12 +- mycroft/client/speech/listener.py | 2 +- mycroft/client/speech/mic.py | 14 +- mycroft/configuration/config.py | 16 +-- mycroft/configuration/locations.py | 2 +- mycroft/dialog/dialog.py | 8 +- mycroft/enclosure/api.py | 2 +- mycroft/enclosure/gui.py | 26 ++-- mycroft/filesystem/__init__.py | 2 +- mycroft/metrics/__init__.py | 2 +- mycroft/skills/__main__.py | 4 +- mycroft/skills/api.py | 2 +- mycroft/skills/audioservice.py | 14 +- mycroft/skills/common_play_skill.py | 12 +- mycroft/skills/common_query_skill.py | 2 +- mycroft/skills/context.py | 4 +- mycroft/skills/event_scheduler.py | 22 +-- mycroft/skills/fallback_skill.py | 6 +- mycroft/skills/intent_service.py | 38 ++--- mycroft/skills/intent_service_interface.py | 14 +- .../skills/intent_services/adapt_service.py | 12 +- .../intent_services/fallback_service.py | 2 +- .../intent_services/padatious_service.py | 24 ++-- .../skills/mycroft_skill/event_container.py | 8 +- mycroft/skills/mycroft_skill/mycroft_skill.py | 135 ++++++++++-------- mycroft/skills/settings.py | 2 +- mycroft/skills/skill_data.py | 8 +- mycroft/skills/skill_loader.py | 8 +- mycroft/skills/skill_manager.py | 4 +- mycroft/stt/__init__.py | 10 +- mycroft/tts/cache.py | 12 +- mycroft/tts/espeak_tts.py | 2 +- mycroft/tts/google_tts.py | 2 +- mycroft/tts/mimic2_tts.py | 18 +-- mycroft/tts/mimic_tts.py | 4 +- mycroft/tts/tts.py | 28 ++-- mycroft/util/audio_utils.py | 14 +- mycroft/util/combo_lock.py | 4 +- mycroft/util/download.py | 10 +- mycroft/util/file_utils.py | 37 ++--- mycroft/util/monotonic_event.py | 2 +- mycroft/util/parse.py | 12 +- mycroft/util/plugins.py | 4 +- mycroft/util/process_utils.py | 12 +- mycroft/util/time.py | 8 +- test/integrationtests/skills/runner.py | 2 +- test/integrationtests/skills/skill_tester.py | 2 +- .../features/steps/configuration.py | 12 +- .../features/steps/utterance_responses.py | 4 +- .../voight_kampff/test_setup.py | 8 +- test/integrationtests/voight_kampff/tools.py | 12 +- .../skills/test_mycroft_skill_get_response.py | 2 +- test/unittests/skills/test_skill_api.py | 2 +- 65 files changed, 376 insertions(+), 357 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index 0ef0c9b1c1d7..1e1a1b25a731 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,4 +1,5 @@ .. Mycroft documentation master file + Mycroft-core technical documentation ==================================== diff --git a/doc/source/mycroft.audio.rst b/doc/source/mycroft.audio.rst index ede939da0114..79a6bfc30ca1 100644 --- a/doc/source/mycroft.audio.rst +++ b/doc/source/mycroft.audio.rst @@ -1,6 +1,8 @@ -mycroft.audio package +mycroft.audio ==================== -wait_while_speaking -------------------- -.. autofunction:: mycroft.audio.wait_while_speaking +.. automodule:: mycroft.audio + :members: + is_speaking, + stop_speaking, + wait_while_speaking diff --git a/doc/source/mycroft.rst b/doc/source/mycroft.rst index fb02c6d49547..4cb02c051d10 100644 --- a/doc/source/mycroft.rst +++ b/doc/source/mycroft.rst @@ -5,25 +5,25 @@ mycroft.skills ============== MycroftSkill class - Base class for all Mycroft skills ------------------- +------------------------------------------------------ .. autoclass:: mycroft.MycroftSkill :members: CommonIoTSkill class -------------------- +-------------------- .. autoclass:: mycroft.skills.common_iot_skill.CommonIoTSkill :show-inheritance: :members: CommonPlaySkill class -------------------- +--------------------- .. autoclass:: mycroft.skills.common_play_skill.CommonPlaySkill :show-inheritance: :members: CommonQuerySkill class -------------------- +---------------------- .. autoclass:: mycroft.skills.common_query_skill.CommonQuerySkill :show-inheritance: :members: @@ -56,6 +56,12 @@ removes_context decorator ------------------------- .. autofunction:: mycroft.removes_context +mycroft.audio +================== + +.. toctree:: + mycroft.audio + mycroft.filesystem ================== @@ -73,16 +79,15 @@ mycroft.util .. toctree:: mycroft.util.parse + Parsing functions for extracting data from natural speech. .. toctree:: mycroft.util.format + Formatting functions for producing natural speech from common datatypes such as numbers, dates and times. .. toctree:: mycroft.util.time -A collection of functions for handling local, system and global times. ------------------ -.. automodule:: - mycroft.util.time +A collection of functions for handling local, system and global times. diff --git a/doc/source/mycroft.util.format.rst b/doc/source/mycroft.util.format.rst index 22c7fc9a1b3f..82fede0e5f32 100644 --- a/doc/source/mycroft.util.format.rst +++ b/doc/source/mycroft.util.format.rst @@ -1,5 +1,5 @@ mycroft.util.format -================== +=================== .. automodule:: mycroft.util.format :members: diff --git a/doc/source/mycroft.util.rst b/doc/source/mycroft.util.rst index e66bce0ab0d8..c4ac308de240 100644 --- a/doc/source/mycroft.util.rst +++ b/doc/source/mycroft.util.rst @@ -25,22 +25,6 @@ play_ogg --------- .. autofunction:: mycroft.util.play_ogg -extract_datetime ----------------- -.. autofunction:: mycroft.util.extract_datetime - -extract_number -------------- -.. autofunction:: mycroft.util.extract_number - -normalize ---------- -.. autofunction:: mycroft.util.normalize - -nice_number ------------ -.. autofunction:: mycroft.util.nice_number - resolve_resource_file --------------------- .. autofunction:: mycroft.util.resolve_resource_file diff --git a/mycroft/api/__init__.py b/mycroft/api/__init__.py index 43663ee1270f..2e74268bebc1 100644 --- a/mycroft/api/__init__.py +++ b/mycroft/api/__init__.py @@ -113,7 +113,7 @@ def send(self, params, no_refresh=False): The method handles Etags and will return a cached response value if nothing has changed on the remote. - Arguments: + Args: params (dict): request parameters no_refresh (bool): optional parameter to disable refreshs of token @@ -156,9 +156,10 @@ def get_response(self, response, no_refresh=False): Will try to refresh the access token if it's expired. - Arguments: + Args: response (requests Response object): Response to parse no_refresh (bool): Disable refreshing of the token + Returns: data fetched from server """ @@ -373,7 +374,7 @@ def get_skill_settings(self): def upload_skill_metadata(self, settings_meta): """Upload skill metadata. - Arguments: + Args: settings_meta (dict): skill info and settings in JSON format """ return self.request({ @@ -386,7 +387,7 @@ def upload_skills_data(self, data): """ Upload skills.json file. This file contains a manifest of installed and failed installations for use with the Marketplace. - Arguments: + Args: data: dictionary with skills data from msm """ if not isinstance(data, dict): @@ -512,7 +513,7 @@ def is_paired(ignore_errors=True): def check_remote_pairing(ignore_errors): """Check that a basic backend endpoint accepts our pairing. - Arguments: + Args: ignore_errors (bool): True if errors should be ignored when Returns: diff --git a/mycroft/audio/audioservice.py b/mycroft/audio/audioservice.py index d7f5abf79d3f..a706f2e6add3 100644 --- a/mycroft/audio/audioservice.py +++ b/mycroft/audio/audioservice.py @@ -91,10 +91,11 @@ def get_services(services_folder): def setup_service(service_module, config, bus): """Run the appropriate setup function and return created service objects. - Arguments: + Args: service_module: Python module to run config (dict): Mycroft configuration dict bus (MessageBusClient): Messagebus interface + Returns: (list) List of created services. """ @@ -116,11 +117,12 @@ def setup_service(service_module, config, bus): def load_internal_services(config, bus, path=None): """Load audio services included in Mycroft-core. - Arguments: + Args: config: configuration dict for the audio backends. bus: Mycroft messagebus path: (default None) optional path for builtin audio service implementations + Returns: List of started services """ @@ -150,7 +152,7 @@ def load_internal_services(config, bus, path=None): def load_plugins(config, bus): """Load installed audioservice plugins. - Arguments: + Args: config: configuration dict for the audio backends. bus: Mycroft messagebus @@ -173,7 +175,7 @@ def load_services(config, bus, path=None): parameter) for services and plugins registered with the "mycroft.plugin.audioservice" entrypoint group. - Arguments: + Args: config: configuration dict for the audio backends. bus: Mycroft messagebus path: (default None) optional path for builtin audio service @@ -261,7 +263,7 @@ def load_services(self): def wait_for_load(self, timeout=3 * MINUTES): """Wait for services to be loaded. - Arguments: + Args: timeout (float): Seconds to wait (default 3 minutes) Returns: (bool) True if loading completed within timeout, else False. diff --git a/mycroft/audio/services/__init__.py b/mycroft/audio/services/__init__.py index c9d8a6aeff3b..9a387dc7da0f 100644 --- a/mycroft/audio/services/__init__.py +++ b/mycroft/audio/services/__init__.py @@ -23,7 +23,7 @@ class AudioBackend(metaclass=ABCMeta): """Base class for all audio backend implementations. - Arguments: + Args: config (dict): configuration dict for the instance bus (MessageBusClient): Mycroft messagebus emitter """ @@ -50,7 +50,7 @@ def clear_list(self): def add_list(self, tracks): """Add tracks to backend's playlist. - Arguments: + Args: tracks (list): list of tracks. """ @@ -61,7 +61,7 @@ def play(self, repeat=False): Starts playing the first track in the playlist and will contiune until all tracks have been played. - Arguments: + Args: repeat (bool): Repeat playlist, defaults to False """ @@ -119,14 +119,14 @@ def restore_volume(self): def seek_forward(self, seconds=1): """Skip X seconds. - Arguments: + Args: seconds (int): number of seconds to seek, if negative rewind """ def seek_backward(self, seconds=1): """Rewind X seconds. - Arguments: + Args: seconds (int): number of seconds to seek, if negative jump forward. """ diff --git a/mycroft/audio/speech.py b/mycroft/audio/speech.py index f439a22892d9..635b8c89dffd 100644 --- a/mycroft/audio/speech.py +++ b/mycroft/audio/speech.py @@ -108,7 +108,7 @@ def handle_speak(event): def mute_and_speak(utterance, ident, listen=False): """Mute mic and start speaking the utterance using selected tts backend. - Arguments: + Args: utterance: The sentence to be spoken ident: Ident tying the utterance to the source query """ @@ -152,7 +152,7 @@ def _get_mimic_fallback(): def mimic_fallback_tts(utterance, ident, listen): """Speak utterance using fallback TTS if connection is lost. - Arguments: + Args: utterance (str): sentence to speak ident (str): interaction id for metrics listen (bool): True if interaction should end with mycroft listening @@ -177,7 +177,7 @@ def handle_stop(event): def init(messagebus): """Start speech related handlers. - Arguments: + Args: messagebus: Connection to the Mycroft messagebus """ diff --git a/mycroft/client/enclosure/__main__.py b/mycroft/client/enclosure/__main__.py index 7ff403f41758..f7bdb5dde9b6 100644 --- a/mycroft/client/enclosure/__main__.py +++ b/mycroft/client/enclosure/__main__.py @@ -37,7 +37,7 @@ def on_error(e='Unknown'): def create_enclosure(platform): """Create an enclosure based on the provided platform string. - Arguments: + Args: platform (str): platform name string Returns: diff --git a/mycroft/client/enclosure/mark1/mouth.py b/mycroft/client/enclosure/mark1/mouth.py index 78301e21688d..d381e85b10aa 100644 --- a/mycroft/client/enclosure/mark1/mouth.py +++ b/mycroft/client/enclosure/mark1/mouth.py @@ -93,7 +93,7 @@ def text(self, event=None): def __display(self, code, clear_previous, x_offset, y_offset): """ Write the encoded image to enclosure screen. - Arguments: + Args: code (str): encoded image to display clean_previous (str): if "True" will clear the screen before drawing. @@ -119,7 +119,7 @@ def __display(self, code, clear_previous, x_offset, y_offset): def display(self, event=None): """ Display a Mark-1 specific code. - Arguments: + Args: event (Message): messagebus message with data to display """ code = "" @@ -139,7 +139,7 @@ def display_image(self, event=None): The method uses PIL to convert the image supplied into a code suitable for the Mark-1 display. - Arguments: + Args: event (Message): messagebus message with data to display """ if not event: diff --git a/mycroft/client/speech/data_structures.py b/mycroft/client/speech/data_structures.py index 3c67ff51b4fb..cba94e64967b 100644 --- a/mycroft/client/speech/data_structures.py +++ b/mycroft/client/speech/data_structures.py @@ -21,7 +21,7 @@ class RollingMean: The optimization is made for cases where value retrieval is made at a comparative rate to the sample additions. - Arguments: + Args: mean_samples: Number of samples to use for mean value """ def __init__(self, mean_samples): @@ -66,7 +66,7 @@ class CyclicAudioBuffer: TODO: The class is still unoptimized and performance can probably be enhanced. - Arguments: + Args: size (int): size in bytes initial_data (bytes): initial buffer data """ @@ -78,7 +78,7 @@ def __init__(self, size, initial_data): def append(self, data): """Add new data to the buffer, and slide out data if the buffer is full - Arguments: + Args: data (bytes): binary data to append to the buffer. If buffer size is exceeded the oldest data will be dropped. """ diff --git a/mycroft/client/speech/hotword_factory.py b/mycroft/client/speech/hotword_factory.py index e5385bf5ed82..4b12cc993fc6 100644 --- a/mycroft/client/speech/hotword_factory.py +++ b/mycroft/client/speech/hotword_factory.py @@ -50,7 +50,7 @@ class NoModelAvailable(Exception): def msec_to_sec(msecs): """Convert milliseconds to seconds. - Arguments: + Args: msecs: milliseconds Returns: @@ -62,7 +62,7 @@ def msec_to_sec(msecs): class HotWordEngine: """Hotword/Wakeword base class to be implemented by all wake word plugins. - Arguments: + Args: key_phrase (str): string representation of the wake word config (dict): Configuration block for the specific wake word lang (str): language code (BCP-47) @@ -89,7 +89,7 @@ def found_wake_word(self, frame_data): Checks if the wake word has been found. Should reset any internal tracking of the wake word state. - Arguments: + Args: frame_data (binary data): Deprecated. Audio data for large chunk of audio to be processed. This should not be used to detect audio data instead @@ -104,7 +104,7 @@ def update(self, chunk): The engine should process the data and update internal trigger state. - Arguments: + Args: chunk (bytes): Chunk of audio data to process """ @@ -403,7 +403,7 @@ def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"): def update(self, chunk): """Update detection state from a chunk of audio data. - Arguments: + Args: chunk (bytes): Audio data to parse """ pcm = struct.unpack_from("h" * (len(chunk)//2), chunk) @@ -443,7 +443,7 @@ def stop(self): def load_wake_word_plugin(module_name): """Wrapper function for loading wake word plugin. - Arguments: + Args: (str) Mycroft wake word module name from config """ return load_plugin('mycroft.plugin.wake_word', module_name) diff --git a/mycroft/client/speech/listener.py b/mycroft/client/speech/listener.py index 59ba739fd8bc..48867be53a90 100644 --- a/mycroft/client/speech/listener.py +++ b/mycroft/client/speech/listener.py @@ -276,7 +276,7 @@ class RecognizerLoop(EventEmitter): Local wake word recognizer and remote general speech recognition. - Arguments: + Args: watchdog: (callable) function to call periodically indicating operational status. """ diff --git a/mycroft/client/speech/mic.py b/mycroft/client/speech/mic.py index 028d682e8b48..e65c912ee10e 100644 --- a/mycroft/client/speech/mic.py +++ b/mycroft/client/speech/mic.py @@ -79,7 +79,7 @@ def unmute(self): def read(self, size, of_exc=False): """Read data from stream. - Arguments: + Args: size (int): Number of bytes to read of_exc (bool): flag determining if the audio producer thread should throw IOError at overflows. @@ -189,7 +189,7 @@ def is_muted(self): def duration_to_bytes(self, sec): """Converts a duration in seconds to number of recorded bytes. - Arguments: + Args: sec: number of seconds Returns: @@ -209,7 +209,7 @@ class NoiseTracker: in one continous sequence) followed by a short period of continous quiet audio data to be considered complete. - Arguments: + Args: minimum (int): lower noise level will be threshold for "quiet" level maximum (int): ceiling of noise level sec_per_buffer (float): the length of each buffer used when updating @@ -262,7 +262,7 @@ def _decrease_noise(self): def update(self, is_loud): """Update the tracking. with either a loud chunk or a quiet chunk. - Arguments: + Args: is_loud: True if a loud chunk should be registered False if a quiet chunk should be registered """ @@ -543,7 +543,7 @@ def _upload_wakeword(self, audio, metadata): def _send_wakeword_info(self, emitter): """Send messagebus message indicating that a wakeword was received. - Arguments: + Args: emitter: bus emitter to send information on. """ SessionManager.touch() @@ -554,7 +554,7 @@ def _send_wakeword_info(self, emitter): def _write_wakeword_to_disk(self, audio, metadata): """Write wakeword to disk. - Arguments: + Args: audio: Audio data to write metadata: List of metadata about the captured wakeword """ @@ -586,7 +586,7 @@ def _handle_wakeword_found(self, audio_data, source): def _wait_until_wake_word(self, source, sec_per_buffer): """Listen continuously on source until a wake word is spoken - Arguments: + Args: source (AudioSource): Source producing the audio chunks sec_per_buffer (float): Fractional number of seconds in each chunk """ diff --git a/mycroft/configuration/config.py b/mycroft/configuration/config.py index 32af4d41bb84..3ff92912f025 100644 --- a/mycroft/configuration/config.py +++ b/mycroft/configuration/config.py @@ -41,7 +41,7 @@ def is_remote_list(values): def translate_remote(config, setting): """Translate config names from server to equivalents for mycroft-core. - Arguments: + Args: config: base config to populate settings: remote settings to be translated """ @@ -69,7 +69,7 @@ def translate_remote(config, setting): def translate_list(config, values): """Translate list formated by mycroft server. - Arguments: + Args: config (dict): target config values (list): list from mycroft server config """ @@ -92,7 +92,7 @@ def __init__(self, path): def load_local(self, path): """Load local json file into self. - Arguments: + Args: path (str): file to load """ if exists(path) and isfile(path): @@ -180,7 +180,7 @@ def get(configs=None, cache=True): Returns cached instance if available otherwise builds a new configuration dict. - Arguments: + Args: configs (list): List of configuration dicts cache (boolean): True if the result should be cached @@ -196,7 +196,7 @@ def get(configs=None, cache=True): def load_config_stack(configs=None, cache=False): """Load a stack of config dicts into a single dict - Arguments: + Args: configs (list): list of dicts to load cache (boolean): True if result should be cached @@ -231,7 +231,7 @@ def load_config_stack(configs=None, cache=False): def set_config_update_handlers(bus): """Setup websocket handlers to update config. - Arguments: + Args: bus: Message bus client instance """ bus.on("configuration.updated", Configuration.updated) @@ -250,7 +250,7 @@ def updated(message): def patch(message): """Patch the volatile dict usable by skills - Arguments: + Args: message: Messagebus message should contain a config in the data payload. """ @@ -262,7 +262,7 @@ def patch(message): def patch_clear(message): """Clear the config patch space. - Arguments: + Args: message: Messagebus message should contain a config in the data payload. """ diff --git a/mycroft/configuration/locations.py b/mycroft/configuration/locations.py index cbbaab6ca943..28f57a2894d7 100644 --- a/mycroft/configuration/locations.py +++ b/mycroft/configuration/locations.py @@ -26,7 +26,7 @@ def __ensure_folder_exists(path): """ Make sure the directory for the specified path exists. - Arguments: + Args: path (str): path to config file """ directory = dirname(path) diff --git a/mycroft/dialog/dialog.py b/mycroft/dialog/dialog.py index e10925678096..ec01fdb05e38 100644 --- a/mycroft/dialog/dialog.py +++ b/mycroft/dialog/dialog.py @@ -49,7 +49,7 @@ def __init__(self): def load_template_file(self, template_name, filename): """Load a template by file name into the templates cache. - Arguments: + Args: template_name (str): a unique identifier for a group of templates filename (str): a fully qualified filename of a mustache template. """ @@ -78,7 +78,7 @@ def render(self, template_name, context=None, index=None): Tries not to let Mycroft say exactly the same thing twice in a row. - Arguments: + Args: template_name (str): the name of a template group. context (dict): dictionary representing values to be rendered index (int): optional, the specific index in the collection of @@ -123,7 +123,7 @@ def render(self, template_name, context=None, index=None): def load_dialogs(dialog_dir, renderer=None): """Load all dialog files within the specified directory. - Arguments: + Args: dialog_dir (str): directory that contains dialog files Returns: @@ -151,7 +151,7 @@ def get(phrase, lang=None, context=None): If no file is found, the requested phrase is returned as the string. This will use the default language for translations. - Arguments: + Args: phrase (str): resource phrase to retrieve/translate lang (str): the language to use context (dict): values to be inserted into the string diff --git a/mycroft/enclosure/api.py b/mycroft/enclosure/api.py index 9b20bb658bf4..a581d4b2b4a8 100644 --- a/mycroft/enclosure/api.py +++ b/mycroft/enclosure/api.py @@ -224,7 +224,7 @@ def mouth_smile(self): def mouth_viseme(self, start, viseme_pairs): """ Send mouth visemes as a list in a single message. - Arguments: + Args: start (int): Timestamp for start of speech viseme_pairs: Pairs of viseme id and cumulative end times (code, end time) diff --git a/mycroft/enclosure/gui.py b/mycroft/enclosure/gui.py index 33130bca7961..3686231b8a01 100644 --- a/mycroft/enclosure/gui.py +++ b/mycroft/enclosure/gui.py @@ -69,7 +69,7 @@ def register_handler(self, event, handler): When using the triggerEvent method from Qt triggerEvent("event", {"data": "cool"}) - Arguments: + Args: event (str): event to catch handler: function to handle the event """ @@ -80,7 +80,7 @@ def set_on_gui_changed(self, callback): """Registers a callback function to run when a value is changed from the GUI. - Arguments: + Args: callback: Function to call when a value is changed """ self.on_gui_changed_callback = callback @@ -88,7 +88,7 @@ def set_on_gui_changed(self, callback): def gui_set(self, message): """Handler catching variable changes from the GUI. - Arguments: + Args: message: Messagebus message """ for key in message.data: @@ -132,7 +132,7 @@ def clear(self): def send_event(self, event_name, params=None): """Trigger a gui event. - Arguments: + Args: event_name (str): name of event to be triggered params: json serializable object containing any parameters that should be sent along with the request. @@ -147,7 +147,7 @@ def show_page(self, name, override_idle=None, override_animations=False): """Begin showing the page in the GUI - Arguments: + Args: name (str): Name of page (e.g "mypage.qml") to display override_idle (boolean, int): True: Takes over the resting page indefinitely @@ -163,7 +163,7 @@ def show_pages(self, page_names, index=0, override_idle=None, override_animations=False): """Begin showing the list of pages in the GUI. - Arguments: + Args: page_names (list): List of page names (str) to display, such as ["Weather.qml", "Forecast.qml", "Details.qml"] index (int): Page number (0-based) to show initially. For the @@ -214,7 +214,7 @@ def show_pages(self, page_names, index=0, override_idle=None, def remove_page(self, page): """Remove a single page from the GUI. - Arguments: + Args: page (str): Page to remove from the GUI """ return self.remove_pages([page]) @@ -222,7 +222,7 @@ def remove_page(self, page): def remove_pages(self, page_names): """Remove a list of pages in the GUI. - Arguments: + Args: page_names (list): List of page names (str) to display, such as ["Weather.qml", "Forecast.qml", "Other.qml"] """ @@ -252,7 +252,7 @@ def show_text(self, text, title=None, override_idle=None, override_animations=False): """Display a GUI page for viewing simple text. - Arguments: + Args: text (str): Main text content. It will auto-paginate title (str): A title to display above the text content. override_idle (boolean, int): @@ -273,7 +273,7 @@ def show_image(self, url, caption=None, override_idle=None, override_animations=False): """Display a GUI page for viewing an image. - Arguments: + Args: url (str): Pointer to the image caption (str): A caption to show under the image title (str): A title to display above the image content @@ -299,7 +299,7 @@ def show_animated_image(self, url, caption=None, override_idle=None, override_animations=False): """Display a GUI page for viewing an image. - Arguments: + Args: url (str): Pointer to the .gif image caption (str): A caption to show under the image title (str): A title to display above the image content @@ -324,7 +324,7 @@ def show_html(self, html, resource_url=None, override_idle=None, override_animations=False): """Display an HTML page in the GUI. - Arguments: + Args: html (str): HTML text to display resource_url (str): Pointer to HTML resources override_idle (boolean, int): @@ -344,7 +344,7 @@ def show_url(self, url, override_idle=None, override_animations=False): """Display an HTML page in the GUI. - Arguments: + Args: url (str): URL to render override_idle (boolean, int): True: Takes over the resting page indefinitely diff --git a/mycroft/filesystem/__init__.py b/mycroft/filesystem/__init__.py index b88042415850..df6776f0e9b2 100644 --- a/mycroft/filesystem/__init__.py +++ b/mycroft/filesystem/__init__.py @@ -58,7 +58,7 @@ def open(self, filename, mode): def exists(self, filename): """Check if file exists in the namespace. - Arguments: + Args: filename (str): a path relative to the namespace. subdirs not currently supported. Returns: diff --git a/mycroft/metrics/__init__.py b/mycroft/metrics/__init__.py index 795000af495c..1894e11120a7 100644 --- a/mycroft/metrics/__init__.py +++ b/mycroft/metrics/__init__.py @@ -71,7 +71,7 @@ def report_metric(name, data): def report_timing(ident, system, timing, additional_data=None): """Create standardized message for reporting timing. - Arguments: + Args: ident (str): identifier of user interaction system (str): system the that's generated the report timing (stopwatch): Stopwatch object with recorded timing diff --git a/mycroft/skills/__main__.py b/mycroft/skills/__main__.py index 5a4f09622121..1009c0283a8a 100644 --- a/mycroft/skills/__main__.py +++ b/mycroft/skills/__main__.py @@ -52,7 +52,7 @@ class DevicePrimer(object): """Container handling the device preparation. - Arguments: + Args: message_bus_client: Bus client used to interact with the system config (dict): Mycroft configuration """ @@ -241,7 +241,7 @@ def main(alive_hook=on_alive, started_hook=on_started, ready_hook=on_ready, def _register_intent_services(bus): """Start up the all intent services and connect them as needed. - Arguments: + Args: bus: messagebus client to register the services on """ service = IntentService(bus) diff --git a/mycroft/skills/api.py b/mycroft/skills/api.py index 2212faf5855b..3546ee6944e4 100644 --- a/mycroft/skills/api.py +++ b/mycroft/skills/api.py @@ -53,7 +53,7 @@ def method(*args, **kwargs): @staticmethod def get(skill): """Generate api object from skill id. - Arguments: + Args: skill (str): skill id for target skill Returns: diff --git a/mycroft/skills/audioservice.py b/mycroft/skills/audioservice.py index e138dc4df62b..dcf170d67978 100644 --- a/mycroft/skills/audioservice.py +++ b/mycroft/skills/audioservice.py @@ -20,7 +20,7 @@ def ensure_uri(s): """Interprete paths as file:// uri's. - Arguments: + Args: s: string to be checked Returns: @@ -43,7 +43,7 @@ def ensure_uri(s): class AudioService: """AudioService class for interacting with the audio subsystem - Arguments: + Args: bus: Mycroft messagebus connection """ @@ -53,7 +53,7 @@ def __init__(self, bus): def queue(self, tracks=None): """Queue up a track to playing playlist. - Arguments: + Args: tracks: track uri or list of track uri's Each track can be added as a tuple with (uri, mime) to give a hint of the mime type to the system @@ -70,7 +70,7 @@ def queue(self, tracks=None): def play(self, tracks=None, utterance=None, repeat=None): """Start playback. - Arguments: + Args: tracks: track uri or list of track uri's Each track can be added as a tuple with (uri, mime) to give a hint of the mime type to the system @@ -114,7 +114,7 @@ def resume(self): def seek(self, seconds=1): """Seek X seconds. - Arguments: + Args: seconds (int): number of seconds to seek, if negative rewind """ if seconds < 0: @@ -125,7 +125,7 @@ def seek(self, seconds=1): def seek_forward(self, seconds=1): """Skip ahead X seconds. - Arguments: + Args: seconds (int): number of seconds to skip """ self.bus.emit(Message('mycroft.audio.service.seek_forward', @@ -134,7 +134,7 @@ def seek_forward(self, seconds=1): def seek_backward(self, seconds=1): """Rewind X seconds - Arguments: + Args: seconds (int): number of seconds to rewind """ self.bus.emit(Message('mycroft.audio.service.seek_backward', diff --git a/mycroft/skills/common_play_skill.py b/mycroft/skills/common_play_skill.py index 162810a6039b..366a25bd7694 100644 --- a/mycroft/skills/common_play_skill.py +++ b/mycroft/skills/common_play_skill.py @@ -120,7 +120,7 @@ def __calc_confidence(self, match, phrase, level): Assume the more of the words that get consumed, the better the match - Arguments: + Args: match (str): Matching string phrase (str): original input phrase level (CPSMatchLevel): match level @@ -203,7 +203,7 @@ def stop(self): def CPS_match_query_phrase(self, phrase): """Analyze phrase to see if it is a play-able phrase with this skill. - Arguments: + Args: phrase (str): User phrase uttered after "Play", e.g. "some music" Returns: @@ -233,7 +233,7 @@ def CPS_match_query_phrase(self, phrase): def CPS_start(self, phrase, data): """Begin playing whatever is specified in 'phrase' - Arguments: + Args: phrase (str): User phrase uttered after "Play", e.g. "some music" data (dict): Callback data specified in match_query_phrase() """ @@ -245,7 +245,7 @@ def CPS_extend_timeout(self, timeout=5): """Request Common Play Framework to wait another {timeout} seconds for an answer from this skill. - Arguments: + Args: timeout (int): Number of seconds """ self.bus.emit(Message('play:query.response', @@ -268,7 +268,7 @@ def CPS_send_status(self, artist='', track='', album='', image='', non-standard parameters are added, they too will be sent in the message data. - Arguments: + Args: artist (str): Current track artist track (str): Track name album (str): Album title @@ -297,7 +297,7 @@ def CPS_send_tracklist(self, tracklist): Provides track data for playlist - Arguments: + Args: tracklist (list/dict): Tracklist data """ tracklist = tracklist or [] diff --git a/mycroft/skills/common_query_skill.py b/mycroft/skills/common_query_skill.py index 790bc74afceb..4bb92d6519d0 100644 --- a/mycroft/skills/common_query_skill.py +++ b/mycroft/skills/common_query_skill.py @@ -134,7 +134,7 @@ def CQS_match_query_phrase(self, phrase): Needs to be implemented by the skill. - Arguments: + Args: phrase (str): User phrase, "What is an aardwark" Returns: diff --git a/mycroft/skills/context.py b/mycroft/skills/context.py index 598ebdf02000..1b795c171b90 100644 --- a/mycroft/skills/context.py +++ b/mycroft/skills/context.py @@ -22,7 +22,7 @@ def adds_context(context, words=''): """Decorator adding context to the Adapt context manager. - Arguments: + Args: context (str): context Keyword to insert words (str): optional string content of Keyword """ @@ -39,7 +39,7 @@ def func_wrapper(*args, **kwargs): def removes_context(context): """Decorator removing context from the Adapt context manager. - Arguments: + Args: context (str): Context keyword to remove """ def context_removes_decorator(func): diff --git a/mycroft/skills/event_scheduler.py b/mycroft/skills/event_scheduler.py index fe41ac1bedca..cc91a76c43ad 100644 --- a/mycroft/skills/event_scheduler.py +++ b/mycroft/skills/event_scheduler.py @@ -48,7 +48,7 @@ class EventScheduler(Thread): """Create an event scheduler thread. Will send messages at a predetermined time to the registered targets. - Arguments: + Args: bus: Mycroft messagebus (mycroft.messagebus) schedule_file: File to store pending events to on shutdown """ @@ -132,7 +132,7 @@ def schedule_event(self, event, sched_time, repeat=None, data=None, context=None): """Add event to pending event schedule. - Arguments: + Args: event (str): Handler for the event sched_time ([type]): [description] repeat ([type], optional): Defaults to None. [description] @@ -180,7 +180,7 @@ def schedule_event_handler(self, message): def remove_event(self, event): """Remove an event from the list of scheduled events. - Arguments: + Args: event (str): event identifier """ with self.event_lock: @@ -198,7 +198,7 @@ def update_event(self, event, data): This will only update the first call if multiple calls are registered to the same event identifier. - Arguments: + Args: event (str): event identifier data (dict): new data """ @@ -282,7 +282,7 @@ def _create_unique_name(self, name): """Return a name unique to this skill using the format [skill_id]:[name]. - Arguments: + Args: name: Name to use internally Returns: @@ -296,7 +296,7 @@ def _schedule_event(self, handler, when, data, name, Takes scheduling information and sends it off on the message bus. - Arguments: + Args: handler: method to be called when (datetime): time (in system timezone) for first calling the handler, or None to @@ -335,7 +335,7 @@ def schedule_event(self, handler, when, data=None, name=None, context=None): """Schedule a single-shot event. - Arguments: + Args: handler: method to be called when (datetime/int/float): datetime (in system timezone) or number of seconds in the future when the @@ -354,7 +354,7 @@ def schedule_repeating_event(self, handler, when, interval, data=None, name=None, context=None): """Schedule a repeating event. - Arguments: + Args: handler: method to be called when (datetime): time (in system timezone) for first calling the handler, or None to @@ -381,7 +381,7 @@ def schedule_repeating_event(self, handler, when, interval, def update_scheduled_event(self, name, data=None): """Change data of event. - Arguments: + Args: name (str): reference name of event (from original scheduling) """ data = data or {} @@ -396,7 +396,7 @@ def cancel_scheduled_event(self, name): """Cancel a pending event. The event will no longer be scheduled to be executed - Arguments: + Args: name (str): reference name of event (from original scheduling) """ unique_name = self._create_unique_name(name) @@ -410,7 +410,7 @@ def cancel_scheduled_event(self, name): def get_scheduled_event_status(self, name): """Get scheduled event data and return the amount of time left - Arguments: + Args: name (str): reference name of event (from original scheduling) Returns: diff --git a/mycroft/skills/fallback_skill.py b/mycroft/skills/fallback_skill.py index 70ef17700678..a616accbb83e 100644 --- a/mycroft/skills/fallback_skill.py +++ b/mycroft/skills/fallback_skill.py @@ -116,7 +116,7 @@ def _register_fallback(cls, handler, wrapper, priority): Lower priority gets run first 0 for high priority 100 for low priority - Arguments: + Args: handler (callable): original handler, used as a reference when removing wrapper (callable): wrapped version of handler @@ -146,7 +146,7 @@ def wrapper(*args, **kwargs): def _remove_registered_handler(cls, wrapper_to_del): """Remove a registered wrapper. - Arguments: + Args: wrapper_to_del (callable): wrapped handler to be removed Returns: @@ -166,7 +166,7 @@ def _remove_registered_handler(cls, wrapper_to_del): def remove_fallback(cls, handler_to_del): """Remove a fallback handler. - Arguments: + Args: handler_to_del: reference to handler Returns: (bool) True if at least one handler was removed, otherwise False diff --git a/mycroft/skills/intent_service.py b/mycroft/skills/intent_service.py index 1cb4e950da4e..d7d4c210f9cf 100644 --- a/mycroft/skills/intent_service.py +++ b/mycroft/skills/intent_service.py @@ -29,7 +29,7 @@ def _get_message_lang(message): """Get the language from the message or the default language. - Arguments: + Args: message: message to check for language code. Returns: @@ -47,7 +47,7 @@ def _normalize_all_utterances(utterances): will be set as the second item in the tuple, if normalization doesn't change anything the tuple will only have the "raw" original utterance. - Arguments: + Args: utterances (list): list of utterances to normalize Returns: @@ -159,7 +159,7 @@ def reset_converse(self, message): def do_converse(self, utterances, skill_id, lang, message): """Call skill and ask if they want to process the utterance. - Arguments: + Args: utterances (list of tuples): utterances paired with normalized versions. skill_id: skill to query. @@ -182,7 +182,7 @@ def do_converse(self, utterances, skill_id, lang, message): def handle_converse_error(self, message): """Handle error in converse system. - Arguments: + Args: message (Message): info about the error. """ skill_id = message.data["skill_id"] @@ -194,7 +194,7 @@ def handle_converse_error(self, message): def remove_active_skill(self, skill_id): """Remove a skill from being targetable by converse. - Arguments: + Args: skill_id (str): skill to remove """ for skill in self.active_skills: @@ -207,7 +207,7 @@ def add_active_skill(self, skill_id): The skill is added to the front of the list, if it's already in the list it's removed so there is only a single entry of it. - Arguments: + Args: skill_id (str): identifier of skill to be added. """ # search the list for an existing entry that already contains it @@ -225,7 +225,7 @@ def send_metrics(self, intent, context, stopwatch): NOTE: This only applies to those with Opt In. - Arguments: + Args: intent (IntentMatch or None): intet match info context (dict): context info about the interaction stopwatch (StopWatch): Timing info about the skill parsing. @@ -268,7 +268,7 @@ def handle_utterance(self, message): If all these fail the complete_intent_failure message will be sent and a generic info of the failure will be spoken. - Arguments: + Args: message (Message): The messagebus data """ try: @@ -318,7 +318,7 @@ def handle_utterance(self, message): def _converse(self, utterances, lang, message): """Give active skills a chance at the utterance - Arguments: + Args: utterances (list): list of utterances lang (string): 4 letter ISO language code message (Message): message to use to generate reply @@ -343,7 +343,7 @@ def _converse(self, utterances, lang, message): def send_complete_intent_failure(self, message): """Send a message that no skill could handle the utterance. - Arguments: + Args: message (Message): original message to forward from """ self.bus.emit(message.forward('complete_intent_failure')) @@ -351,7 +351,7 @@ def send_complete_intent_failure(self, message): def handle_register_vocab(self, message): """Register adapt vocabulary. - Arguments: + Args: message (Message): message containing vocab info """ start_concept = message.data.get('start') @@ -365,7 +365,7 @@ def handle_register_vocab(self, message): def handle_register_intent(self, message): """Register adapt intent. - Arguments: + Args: message (Message): message containing intent info """ intent = open_intent_envelope(message) @@ -374,7 +374,7 @@ def handle_register_intent(self, message): def handle_detach_intent(self, message): """Remover adapt intent. - Arguments: + Args: message (Message): message containing intent info """ intent_name = message.data.get('intent_name') @@ -383,7 +383,7 @@ def handle_detach_intent(self, message): def handle_detach_skill(self, message): """Remove all intents registered for a specific skill. - Arguments: + Args: message (Message): message containing intent info """ skill_id = message.data.get('skill_id') @@ -427,7 +427,7 @@ def handle_clear_context(self, _): def handle_get_intent(self, message): """Get intent from either adapt or padatious. - Arguments: + Args: message (Message): message containing utterance """ utterance = message.data["utterance"] @@ -486,7 +486,7 @@ def handle_get_active_skills(self, message): def handle_get_adapt(self, message): """handler getting the adapt response for an utterance. - Arguments: + Args: message (Message): message containing utterance """ utterance = message.data["utterance"] @@ -518,7 +518,7 @@ def handle_vocab_manifest(self, message): def handle_get_padatious(self, message): """messagebus handler for perfoming padatious parsing. - Arguments: + Args: message (Message): message triggering the method """ utterance = message.data["utterance"] @@ -534,7 +534,7 @@ def handle_get_padatious(self, message): def handle_padatious_manifest(self, message): """Messagebus handler returning the registered padatious intents. - Arguments: + Args: message (Message): message triggering the method """ self.bus.emit(message.reply( @@ -544,7 +544,7 @@ def handle_padatious_manifest(self, message): def handle_entity_manifest(self, message): """Messagebus handler returning the registered padatious entities. - Arguments: + Args: message (Message): message triggering the method """ self.bus.emit(message.reply( diff --git a/mycroft/skills/intent_service_interface.py b/mycroft/skills/intent_service_interface.py index 43453ec02940..6fe69aadcf60 100644 --- a/mycroft/skills/intent_service_interface.py +++ b/mycroft/skills/intent_service_interface.py @@ -57,7 +57,7 @@ def register_adapt_keyword(self, vocab_type, entity, aliases=None): def register_adapt_regex(self, regex): """Register a regex with the intent service. - Arguments: + Args: regex (str): Regex to be registered, (Adapt extracts keyword reference from named match group. """ @@ -75,7 +75,7 @@ def register_adapt_intent(self, name, intent_parser): def detach_intent(self, intent_name): """Remove an intent from the intent service. - Arguments: + Args: intent_name(str): Intent reference """ self.bus.emit(Message("detach_intent", {"intent_name": intent_name})) @@ -83,7 +83,7 @@ def detach_intent(self, intent_name): def set_adapt_context(self, context, word, origin): """Set an Adapt context. - Arguments: + Args: context (str): context keyword name word (str): word to register origin (str): original origin of the context (for cross context) @@ -95,7 +95,7 @@ def set_adapt_context(self, context, word, origin): def remove_adapt_context(self, context): """Remove an active Adapt context. - Arguments: + Args: context(str): name of context to remove """ self.bus.emit(Message('remove_context', {'context': context})) @@ -103,7 +103,7 @@ def remove_adapt_context(self, context): def register_padatious_intent(self, intent_name, filename): """Register a padatious intent file with Padatious. - Arguments: + Args: intent_name(str): intent identifier filename(str): complete file path for entity file """ @@ -120,7 +120,7 @@ def register_padatious_intent(self, intent_name, filename): def register_padatious_entity(self, entity_name, filename): """Register a padatious entity file with Padatious. - Arguments: + Args: entity_name(str): entity name filename(str): complete file path for entity file """ @@ -149,7 +149,7 @@ def __contains__(self, val): def get_intent(self, intent_name): """Get intent from intent_name. - Arguments: + Args: intent_name (str): name to find. Returns: diff --git a/mycroft/skills/intent_services/adapt_service.py b/mycroft/skills/intent_services/adapt_service.py index be69bd9a087a..a8049160a8a0 100644 --- a/mycroft/skills/intent_services/adapt_service.py +++ b/mycroft/skills/intent_services/adapt_service.py @@ -26,7 +26,7 @@ class AdaptIntent(IntentBuilder): """Wrapper for IntentBuilder setting a blank name. - Arguments: + Args: name (str): Optional name of intent """ def __init__(self, name=''): @@ -66,7 +66,7 @@ def clear_context(self): def remove_context(self, context_id): """Remove a specific context entry. - Arguments: + Args: context_id (str): context entry to remove """ self.frame_stack = [(f, t) for (f, t) in self.frame_stack @@ -184,7 +184,7 @@ def update_context(self, intent): def match_intent(self, utterances, _=None, __=None): """Run the Adapt engine to search for an matching intent. - Arguments: + Args: utterances (iterable): iterable of utterances, expected order [raw, normalized, other] @@ -236,7 +236,7 @@ def register_vocab(self, start_concept, end_concept, alias_of, regex_str): def register_intent(self, intent): """Register new intent with adapt engine. - Arguments: + Args: intent (IntentParser): IntentParser to register """ self.engine.register_intent_parser(intent) @@ -244,7 +244,7 @@ def register_intent(self, intent): def detach_skill(self, skill_id): """Remove all intents for skill. - Arguments: + Args: skill_id (str): skill to process """ new_parsers = [ @@ -256,7 +256,7 @@ def detach_skill(self, skill_id): def detach_intent(self, intent_name): """Detatch a single intent - Arguments: + Args: intent_name (str): Identifier for intent to remove. """ new_parsers = [ diff --git a/mycroft/skills/intent_services/fallback_service.py b/mycroft/skills/intent_services/fallback_service.py index a037fcdbe17a..03759f635c36 100644 --- a/mycroft/skills/intent_services/fallback_service.py +++ b/mycroft/skills/intent_services/fallback_service.py @@ -27,7 +27,7 @@ def __init__(self, bus): def _fallback_range(self, utterances, lang, message, fb_range): """Send fallback request for a specified priority range. - Arguments: + Args: utterances (list): List of tuples, utterances and normalized version lang (str): Langauge code diff --git a/mycroft/skills/intent_services/padatious_service.py b/mycroft/skills/intent_services/padatious_service.py index f58f86bc1953..36c527f361ed 100644 --- a/mycroft/skills/intent_services/padatious_service.py +++ b/mycroft/skills/intent_services/padatious_service.py @@ -65,7 +65,7 @@ def __init__(self, bus, config): def train(self, message=None): """Perform padatious training. - Arguments: + Args: message (Message): optional triggering message """ padatious_single_thread = Configuration.get()[ @@ -102,7 +102,7 @@ def wait_and_train(self): def __detach_intent(self, intent_name): """ Remove an intent if it has been registered. - Arguments: + Args: intent_name (str): intent identifier """ if intent_name in self.registered_intents: @@ -112,7 +112,7 @@ def __detach_intent(self, intent_name): def handle_detach_intent(self, message): """Messagebus handler for detaching padatious intent. - Arguments: + Args: message (Message): message triggering action """ self.__detach_intent(message.data.get('intent_name')) @@ -120,7 +120,7 @@ def handle_detach_intent(self, message): def handle_detach_skill(self, message): """Messagebus handler for detaching all intents for skill. - Arguments: + Args: message (Message): message triggering action """ skill_id = message.data['skill_id'] @@ -131,7 +131,7 @@ def handle_detach_skill(self, message): def _register_object(self, message, object_name, register_func): """Generic method for registering a padatious object. - Arguments: + Args: message (Message): trigger for action object_name (str): type of entry to register register_func (callable): function to call for registration @@ -152,7 +152,7 @@ def _register_object(self, message, object_name, register_func): def register_intent(self, message): """Messagebus handler for registering intents. - Arguments: + Args: message (Message): message triggering action """ self.registered_intents.append(message.data['name']) @@ -161,7 +161,7 @@ def register_intent(self, message): def register_entity(self, message): """Messagebus handler for registering entities. - Arguments: + Args: message (Message): message triggering action """ self.registered_entities.append(message.data) @@ -170,7 +170,7 @@ def register_entity(self, message): def _match_level(self, utterances, limit): """Match intent and make sure a certain level of confidence is reached. - Arguments: + Args: utterances (list of tuples): Utterances to parse, originals paired with optional normalized version. limit (float): required confidence level. @@ -199,7 +199,7 @@ def _match_level(self, utterances, limit): def match_high(self, utterances, _=None, __=None): """Intent matcher for high confidence. - Arguments: + Args: utterances (list of tuples): Utterances to parse, originals paired with optional normalized version. """ @@ -208,7 +208,7 @@ def match_high(self, utterances, _=None, __=None): def match_medium(self, utterances, _=None, __=None): """Intent matcher for medium confidence. - Arguments: + Args: utterances (list of tuples): Utterances to parse, originals paired with optional normalized version. """ @@ -217,7 +217,7 @@ def match_medium(self, utterances, _=None, __=None): def match_low(self, utterances, _=None, __=None): """Intent matcher for low confidence. - Arguments: + Args: utterances (list of tuples): Utterances to parse, originals paired with optional normalized version. """ @@ -234,7 +234,7 @@ def calc_intent(self, utt): (PadatiousService), but we can live with that since it is used as a singleton. - Arguments: + Args: utt (str): utterance to calculate best intent for """ return self.container.calc_intent(utt) diff --git a/mycroft/skills/mycroft_skill/event_container.py b/mycroft/skills/mycroft_skill/event_container.py index f6114392a92c..60c0a4c0d22d 100644 --- a/mycroft/skills/mycroft_skill/event_container.py +++ b/mycroft/skills/mycroft_skill/event_container.py @@ -9,7 +9,7 @@ def unmunge_message(message, skill_id): """Restore message keywords by removing the Letterified skill ID. - Arguments: + Args: message (Message): Intent result message skill_id (str): skill identifier Returns: @@ -29,7 +29,7 @@ def unmunge_message(message, skill_id): def get_handler_name(handler): """Name (including class if available) of handler function. - Arguments: + Args: handler (function): Function to be named Returns: @@ -94,7 +94,7 @@ def create_basic_wrapper(handler, on_error=None): This wrapper handles things like metrics, reporting handler start/stop and errors. - Arguments: + Args: handler (callable): method/function to call on_error (function): function to call to report error. @@ -130,7 +130,7 @@ def set_bus(self, bus): def add(self, name, handler, once=False): """Create event handler for executing intent or other event. - Arguments: + Args: name (string): IntentParser name handler (func): Method to call once (bool, optional): Event handler will be removed after it has diff --git a/mycroft/skills/mycroft_skill/mycroft_skill.py b/mycroft/skills/mycroft_skill/mycroft_skill.py index 31db7d5381a1..4302b49b4ce4 100644 --- a/mycroft/skills/mycroft_skill/mycroft_skill.py +++ b/mycroft/skills/mycroft_skill/mycroft_skill.py @@ -66,7 +66,7 @@ def simple_trace(stack_trace): """Generate a simplified traceback. - Arguments: + Args: stack_trace: Stack trace to simplify Returns: (str) Simplified stack trace. @@ -84,8 +84,8 @@ def get_non_properties(obj): Will return members of object class along with bases down to MycroftSkill. - Arguments: - obj: object to scan + Args: + obj: object to scan Returns: Set of attributes that are not a property. @@ -111,7 +111,7 @@ class MycroftSkill: For information on how to get started with creating mycroft skills see https://mycroft.ai/documentation/skills/introduction-developing-skills/ - Arguments: + Args: name (str): skill name bus (MycroftWebsocketClient): Optional bus connection use_settings (bool): Set to false to not use skill settings at all @@ -247,7 +247,7 @@ def lang(self): def bind(self, bus): """Register messagebus emitter with skill. - Arguments: + Args: bus: Mycroft messagebus connection """ if bus: @@ -401,7 +401,7 @@ def converse(self, message=None): utterances and lang are depreciated - Arguments: + Args: message: a message object containing a message type with an optional JSON data packet @@ -442,19 +442,25 @@ def get_response(self, dialog='', data=None, validator=None, The response can optionally be validated before returning. - Example: + Example:: + color = self.get_response('ask.favorite.color') - Arguments: + Args: dialog (str): Optional dialog to speak to the user data (dict): Data used to render the dialog - validator (any): Function with following signature + validator (any): Function with following signature:: + def validator(utterance): return utterance != "red" - on_fail (any): Dialog or function returning literal string - to speak on invalid input. For example: - def on_fail(utterance): - return "nobody likes the color red, pick another" + + on_fail (any): + Dialog or function returning literal string to speak on + invalid input. For example:: + + def on_fail(utterance): + return "nobody likes the color red, pick another" + num_retries (int): Times to ask user for input, -1 for infinite NOTE: User can not respond and timeout or say "cancel" to stop @@ -494,7 +500,7 @@ def _wait_response(self, is_cancel, validator, on_fail, num_retries): """Loop until a valid response is received from the user or the retry limit is reached. - Arguments: + Args: is_cancel (callable): function checking cancel criteria validator (callbale): function checking for a valid response on_fail (callable): function handling retries @@ -555,12 +561,13 @@ def ask_selection(self, options, dialog='', This automatically deals with fuzzy matching and selection by number e.g. - "first option" - "last option" - "second option" - "option number four" - Arguments: + * "first option" + * "last option" + * "second option" + * "option number four" + + Args: options (list): list of options to present user dialog (str): a dialog id or string to read AFTER all options data (dict): Data used to render the dialog @@ -616,7 +623,7 @@ def voc_match(self, utt, voc_filename, lang=None, exact=False): in the "res/text" folder of mycroft-core. The result is cached to avoid hitting the disk each time the method is called. - Arguments: + Args: utt (str): Utterance to be tested voc_filename (str): Name of vocabulary file (e.g. 'yes' for 'res/text/en-us/yes.voc') @@ -656,7 +663,7 @@ def voc_match(self, utt, voc_filename, lang=None, exact=False): def report_metric(self, name, data): """Report a skill metric to the Mycroft servers. - Arguments: + Args: name (str): Name of metric. Must use only letters and hyphens data (dict): JSON dictionary to report. Must be valid JSON """ @@ -665,7 +672,7 @@ def report_metric(self, name, data): def send_email(self, title, body): """Send an email to the registered user's email. - Arguments: + Args: title (str): Title of email body (str): HTML body of email. This supports simple HTML like bold and italics @@ -740,11 +747,12 @@ def translate(self, text, data=None): """Load a translatable single string resource The string is loaded from a file in the skill's dialog subdirectory - 'dialog//.dialog' + 'dialog//.dialog' + The string is randomly chosen from the file and rendered, replacing mustache placeholders with values found in the data dictionary. - Arguments: + Args: text (str): The base filename (no extension needed) data (dict, optional): a JSON dictionary @@ -754,18 +762,25 @@ def translate(self, text, data=None): return self.dialog_renderer.render(text, data or {}) def find_resource(self, res_name, res_dirname=None): - """Find a resource file + """Find a resource file. Searches for the given filename using this scheme: - 1) Search the resource lang directory: - /// - 2) Search the resource directory: - // - 3) Search the locale lang directory or other subdirectory: - /locale// or - /locale//.../ - - Arguments: + + 1. Search the resource lang directory: + + /// + + 2. Search the resource directory: + + // + + 3. Search the locale lang directory or other subdirectory: + + /locale// or + + /locale//.../ + + Args: res_name (string): The resource name to be found res_dirname (string, optional): A skill resource directory, such 'dialog', 'vocab', 'regex' or 'ui'. @@ -814,7 +829,7 @@ def translate_namedvalues(self, name, delim=','): The name is the first list item, the value is the second. Lines prefixed with # or // get ignored - Arguments: + Args: name (str): name of the .value file, no extension needed delim (char): delimiter character used, default is ',' @@ -837,11 +852,12 @@ def translate_template(self, template_name, data=None): The strings are loaded from a template file in the skill's dialog subdirectory. - 'dialog//.template' + 'dialog//.template' + The strings are loaded and rendered, replacing mustache placeholders with values found in the data dictionary. - Arguments: + Args: template_name (str): The base filename (no extension needed) data (dict, optional): a JSON dictionary @@ -855,11 +871,12 @@ def translate_list(self, list_name, data=None): The strings are loaded from a list file in the skill's dialog subdirectory. - 'dialog//.list' + 'dialog//.list' + The strings are loaded and rendered, replacing mustache placeholders with values found in the data dictionary. - Arguments: + Args: list_name (str): The base filename (no extension needed) data (dict, optional): a JSON dictionary @@ -877,7 +894,7 @@ def __translate_file(self, name, data): def add_event(self, name, handler, handler_info=None, once=False): """Create event handler for executing intent or other event. - Arguments: + Args: name (string): IntentParser name handler (func): Method to call handler_info (string): Base message when reporting skill event @@ -932,7 +949,7 @@ def remove_event(self, name): def _register_adapt_intent(self, intent_parser, handler): """Register an adapt intent. - Arguments: + Args: intent_parser: Intent object to parse utterance for the handler. handler (func): function to register with intent """ @@ -947,7 +964,7 @@ def _register_adapt_intent(self, intent_parser, handler): def register_intent(self, intent_parser, handler): """Register an Intent with the intent service. - Arguments: + Args: intent_parser: Intent, IntentBuilder object or padatious intent file to parse utterance for the handler. handler (func): function to register with intent @@ -983,7 +1000,7 @@ def register_intent_file(self, intent_file, handler): (Order | Grab) some {food} (from {place} | ). I'm hungry. - Arguments: + Args: intent_file: name of file that contains example queries that should activate the intent. Must end with '.intent' @@ -1042,7 +1059,7 @@ def handle_disable_intent(self, message): def disable_intent(self, intent_name): """Disable a registered intent if it belongs to this skill. - Arguments: + Args: intent_name (string): name of the intent to be disabled Returns: @@ -1061,7 +1078,7 @@ def disable_intent(self, intent_name): def enable_intent(self, intent_name): """(Re)Enable a registered intent if it belongs to this skill. - Arguments: + Args: intent_name: name of the intent to be enabled Returns: @@ -1084,7 +1101,7 @@ def enable_intent(self, intent_name): def set_context(self, context, word='', origin=''): """Add context to intent service - Arguments: + Args: context: Keyword word: word connected to keyword origin: origin of context @@ -1113,7 +1130,7 @@ def handle_remove_cross_context(self, message): def set_cross_skill_context(self, context, word=''): """Tell all skills to add a context to intent service - Arguments: + Args: context: Keyword word: word connected to keyword """ @@ -1138,7 +1155,7 @@ def remove_context(self, context): def register_vocabulary(self, entity, entity_type): """ Register a word to a keyword - Arguments: + Args: entity: word to register entity_type: Intent handler entity to tie the word to """ @@ -1148,7 +1165,7 @@ def register_vocabulary(self, entity, entity_type): def register_regex(self, regex_str): """Register a new regex. - Arguments: + Args: regex_str: Regex string """ self.log.debug('registering regex string: ' + regex_str) @@ -1159,7 +1176,7 @@ def register_regex(self, regex_str): def speak(self, utterance, expect_response=False, wait=False, meta=None): """Speak a sentence. - Arguments: + Args: utterance (str): sentence mycroft should speak expect_response (bool): set to True if Mycroft should listen for a response immediately after @@ -1186,7 +1203,7 @@ def speak(self, utterance, expect_response=False, wait=False, meta=None): def speak_dialog(self, key, data=None, expect_response=False, wait=False): """ Speak a random sentence from a dialog file. - Arguments: + Args: key (str): dialog file key (e.g. "hello" to speak from the file "locale/en-us/hello.dialog") data (dict): information used to populate sentence @@ -1241,7 +1258,7 @@ def init_dialog(self, root_directory): def load_data_files(self, root_directory=None): """Called by the skill loader to load intents, dialogs, etc. - Arguments: + Args: root_directory (str): root folder to use when loading files. """ root_directory = root_directory or self.root_dir @@ -1252,7 +1269,7 @@ def load_data_files(self, root_directory=None): def load_vocab_files(self, root_directory): """ Load vocab files found under root_directory. - Arguments: + Args: root_directory (str): root folder to use when loading files """ keywords = [] @@ -1277,7 +1294,7 @@ def load_vocab_files(self, root_directory): def load_regex_files(self, root_directory): """ Load regex files found under the skill directory. - Arguments: + Args: root_directory (str): root folder to use when loading files """ regexes = [] @@ -1364,7 +1381,7 @@ def schedule_event(self, handler, when, data=None, name=None, context=None): """Schedule a single-shot event. - Arguments: + Args: handler: method to be called when (datetime/int/float): datetime (in system timezone) or number of seconds in the future when the @@ -1387,7 +1404,7 @@ def schedule_repeating_event(self, handler, when, frequency, data=None, name=None, context=None): """Schedule a repeating event. - Arguments: + Args: handler: method to be called when (datetime): time (in system timezone) for first calling the handler, or None to @@ -1414,7 +1431,7 @@ def schedule_repeating_event(self, handler, when, frequency, def update_scheduled_event(self, name, data=None): """Change data of event. - Arguments: + Args: name (str): reference name of event (from original scheduling) data (dict): event data """ @@ -1424,7 +1441,7 @@ def cancel_scheduled_event(self, name): """Cancel a pending event. The event will no longer be scheduled to be executed - Arguments: + Args: name (str): reference name of event (from original scheduling) """ return self.event_scheduler.cancel_scheduled_event(name) @@ -1432,7 +1449,7 @@ def cancel_scheduled_event(self, name): def get_scheduled_event_status(self, name): """Get scheduled event data and return the amount of time left - Arguments: + Args: name (str): reference name of event (from original scheduling) Returns: diff --git a/mycroft/skills/settings.py b/mycroft/skills/settings.py index c48416afac04..051d5896ae5c 100644 --- a/mycroft/skills/settings.py +++ b/mycroft/skills/settings.py @@ -335,7 +335,7 @@ def load_remote_settings_cache(): def save_remote_settings_cache(remote_settings): """Save updated remote settings to cache file. - Arguments: + Args: remote_settings (dict): downloaded remote settings. """ try: diff --git a/mycroft/skills/skill_data.py b/mycroft/skills/skill_data.py index e150a8520f3d..047086585c4c 100644 --- a/mycroft/skills/skill_data.py +++ b/mycroft/skills/skill_data.py @@ -32,7 +32,7 @@ def read_vocab_file(path): parentheses. It returns each line as a list of all expanded alternatives. - Arguments: + Args: path (str): path to vocab file. Returns: @@ -74,7 +74,7 @@ def load_regex_from_file(path, skill_id): def load_vocabulary(basedir, skill_id): """Load vocabulary from all files in the specified directory. - Arguments: + Args: basedir (str): path of directory to load from (will recurse) skill_id: skill the data belongs to Returns: @@ -189,7 +189,7 @@ def read_value_file(filename, delim): The value file is a simple csv structure with a key and value. - Arguments: + Args: filename (str): file to read delim (str): csv delimiter @@ -215,7 +215,7 @@ def read_value_file(filename, delim): def read_translated_file(filename, data): """Read a file inserting data. - Arguments: + Args: filename (str): file to read data (dict): dictionary with data to insert into file diff --git a/mycroft/skills/skill_loader.py b/mycroft/skills/skill_loader.py index 8581b92ed9fa..bcc4517bb12c 100644 --- a/mycroft/skills/skill_loader.py +++ b/mycroft/skills/skill_loader.py @@ -37,7 +37,7 @@ def remove_submodule_refs(module_name): dictionary to bypass loading if a module is already in memory. To make sure skills are completely reloaded these references are deleted. - Arguments: + Args: module_name: name of skill module. """ submodules = [] @@ -58,7 +58,7 @@ def load_skill_module(path, skill_id): This function handles the differences between python 3.4 and 3.5+ as well as makes sure the module is inserted into the sys.modules dict. - Arguments: + Args: path: Path to the skill main file (__init__.py) skill_id: skill_id used as skill identifier in the module list """ @@ -76,7 +76,7 @@ def load_skill_module(path, skill_id): def _bad_mod_times(mod_times): """Return all entries with modification time in the future. - Arguments: + Args: mod_times (dict): dict mapping file paths to modification times. Returns: @@ -92,7 +92,7 @@ def _get_last_modified_time(path): Exclude compiled python files, hidden directories and the settings.json file. - Arguments: + Args: path: skill directory to check Returns: diff --git a/mycroft/skills/skill_manager.py b/mycroft/skills/skill_manager.py index acfbc5c92dda..9beebe9b6cd6 100644 --- a/mycroft/skills/skill_manager.py +++ b/mycroft/skills/skill_manager.py @@ -92,7 +92,7 @@ def _shutdown_skill(instance): Call the default_shutdown method of the skill, will produce a warning if the shutdown process takes longer than 1 second. - Arguments: + Args: instance (MycroftSkill): Skill instance to shutdown """ try: @@ -115,7 +115,7 @@ class SkillManager(Thread): def __init__(self, bus, watchdog=None): """Constructor - Arguments: + Args: bus (event emitter): Mycroft messagebus connection watchdog (callable): optional watchdog function """ diff --git a/mycroft/stt/__init__.py b/mycroft/stt/__init__.py index 5028f20ea1d1..3f2c8404b19b 100644 --- a/mycroft/stt/__init__.py +++ b/mycroft/stt/__init__.py @@ -56,7 +56,7 @@ def execute(self, audio, language=None): The method gets passed audio and optionally a language code and is expected to return a text string. - Arguments: + Args: audio (AudioData): audio recorded by mycroft. language (str): optional language code @@ -342,7 +342,7 @@ class StreamThread(Thread, metaclass=ABCMeta): This class reads audio chunks from a queue and sends it to a parsing STT engine. - Arguments: + Args: queue (Queue): Input Queue language (str): language code for the current language. """ @@ -392,7 +392,7 @@ def stream_start(self, language=None): This creates a new thread for handling the incomming audio stream as it's collected by Mycroft. - Arguments: + Args: language (str): optional language code for the new stream. """ self.stream_stop() @@ -404,7 +404,7 @@ def stream_start(self, language=None): def stream_data(self, data): """Receiver of audio data. - Arguments: + Args: data (bytes): raw audio data. """ self.queue.put(data) @@ -607,7 +607,7 @@ def get_response(self, response): def load_stt_plugin(module_name): """Wrapper function for loading stt plugin. - Arguments: + Args: module_name (str): Mycroft stt module name from config Returns: class: STT plugin class diff --git a/mycroft/tts/cache.py b/mycroft/tts/cache.py index 1494c695f228..7cff5d457d22 100644 --- a/mycroft/tts/cache.py +++ b/mycroft/tts/cache.py @@ -46,7 +46,7 @@ def _get_mimic2_audio(sentence: str, url: str) -> Tuple[bytes, str]: """Use the Mimic2 API to retrieve the audio for a sentence. - Arguments: + Args: sentence: The sentence to be cached """ LOG.debug("Retrieving Mimic2 audio for sentence \"{}\'".format(sentence)) @@ -62,7 +62,7 @@ def _get_mimic2_audio(sentence: str, url: str) -> Tuple[bytes, str]: def hash_sentence(sentence: str): """Convert the sentence into a hash value used for the file name - Arguments: + Args: sentence: The sentence to be cached """ encoded_sentence = sentence.encode("utf-8", "ignore") @@ -76,7 +76,7 @@ def hash_from_path(path: Path) -> str: Simply removes extension and folder structure leaving the hash. - Arguments: + Args: path: path to get hash from Returns: @@ -93,7 +93,7 @@ def __init__(self, cache_dir: Path, sentence_hash: str, file_type: str): def save(self, audio: bytes): """Write a TTS cache file containing the audio to be spoken. - Arguments: + Args: audio: TTS inference of a sentence """ try: @@ -123,7 +123,7 @@ def load(self) -> List: def save(self, phonemes): """Write a TTS cache file containing the phoneme to be displayed. - Arguments: + Args: phonemes: instructions for how to make the mouth on a device move """ try: @@ -228,7 +228,7 @@ def _parse_dialogs(dialogs: List[str]) -> Set[str]: punctuation example : <<< LOADING <<< - Arguments: + Args: dialogs: a list of the records in the dialog resource files """ sentences = set() diff --git a/mycroft/tts/espeak_tts.py b/mycroft/tts/espeak_tts.py index 643524ca92e8..51bf83e4bc6d 100644 --- a/mycroft/tts/espeak_tts.py +++ b/mycroft/tts/espeak_tts.py @@ -25,7 +25,7 @@ def __init__(self, lang, config): def get_tts(self, sentence, wav_file): """Generate WAV from sentence, phonemes aren't supported. - Arguments: + Args: sentence (str): sentence to generate audio for wav_file (str): output file diff --git a/mycroft/tts/google_tts.py b/mycroft/tts/google_tts.py index f1dbde0b170b..be755f8786f1 100755 --- a/mycroft/tts/google_tts.py +++ b/mycroft/tts/google_tts.py @@ -85,7 +85,7 @@ def google_lang(self): def get_tts(self, sentence, wav_file): """Fetch tts audio using gTTS. - Arguments: + Args: sentence (str): Sentence to generate audio for wav_file (str): output file path Returns: diff --git a/mycroft/tts/mimic2_tts.py b/mycroft/tts/mimic2_tts.py index cd20eff2c19f..eecdd6771b1c 100644 --- a/mycroft/tts/mimic2_tts.py +++ b/mycroft/tts/mimic2_tts.py @@ -38,7 +38,7 @@ def _break_chunks(l, n): """Yield successive n-sized chunks - Arguments: + Args: l (list): text (str) to split chunk_size (int): chunk size """ @@ -49,7 +49,7 @@ def _break_chunks(l, n): def _split_by_chunk_size(text, chunk_size): """Split text into word chunks by chunk_size size - Arguments: + Args: text (str): text to split chunk_size (int): chunk size @@ -87,7 +87,7 @@ def _split_by_punctuation(chunks, puncs): """Splits text by various punctionations e.g. hello, world => [hello, world] - Arguments: + Args: chunks (list or str): text (str) to split puncs (list): list of punctuations used to split text @@ -128,7 +128,7 @@ def _sentence_chunker(text): NOTE: The smaller chunks are needed due to current Mimic2 TTS limitations. This stage can be removed once Mimic2 can generate longer sentences. - Arguments: + Args: text (str): text to split chunk_size (int): size of each chunk split_by_punc (bool, optional): Defaults to True. @@ -176,7 +176,7 @@ def __init__(self, lang, config): def _requests(self, sentence): """Create asynchronous request list - Arguments: + Args: chunks (list): list of text to synthesize Returns: @@ -189,7 +189,7 @@ def _requests(self, sentence): def viseme(self, phonemes): """Maps phonemes to appropriate viseme encoding - Arguments: + Args: phonemes (list): list of tuples (phoneme, time_start) Returns: @@ -216,7 +216,7 @@ def _preprocess_sentence(self, sentence): def get_tts(self, sentence, wav_file): """Generate (remotely) and play mimic2 WAV audio - Arguments: + Args: sentence (str): Phrase to synthesize to audio with mimic2 wav_file (str): Location to write audio output """ @@ -240,7 +240,7 @@ def get_tts(self, sentence, wav_file): def save_phonemes(self, key, phonemes): """Cache phonemes - Arguments: + Args: key: Hash key for the sentence phonemes: phoneme string to save """ @@ -255,7 +255,7 @@ def save_phonemes(self, key, phonemes): def load_phonemes(self, key): """Load phonemes from cache file. - Arguments: + Args: Key: Key identifying phoneme cache """ pho_file = os.path.join(get_cache_directory("tts/" + self.tts_name), diff --git a/mycroft/tts/mimic_tts.py b/mycroft/tts/mimic_tts.py index 47d1d185bdae..eda94bf662f1 100644 --- a/mycroft/tts/mimic_tts.py +++ b/mycroft/tts/mimic_tts.py @@ -161,7 +161,7 @@ def args(self): def get_tts(self, sentence, wav_file): """Generate WAV and phonemes. - Arguments: + Args: sentence (str): sentence to generate audio for wav_file (str): output file @@ -175,7 +175,7 @@ def get_tts(self, sentence, wav_file): def viseme(self, phoneme_pairs): """Convert phoneme string to visemes. - Arguments: + Args: phoneme_pairs (list): Phoneme output from mimic Returns: diff --git a/mycroft/tts/tts.py b/mycroft/tts/tts.py index 867131d9362e..a3f878dd0172 100644 --- a/mycroft/tts/tts.py +++ b/mycroft/tts/tts.py @@ -128,7 +128,7 @@ def run(self): def show_visemes(self, pairs): """Send viseme data to enclosure - Arguments: + Args: pairs (list): Visime and timing pair Returns: @@ -158,7 +158,7 @@ class TTS(metaclass=ABCMeta): It aggregates the minimum required parameters and exposes ``execute(sentence)`` and ``validate_ssml(sentence)`` functions. - Arguments: + Args: lang (str): config (dict): Configuration for this specific tts engine validator (TTSValidator): Used to verify proper installation @@ -219,7 +219,7 @@ def end_audio(self, listen=False): if it has been requested. It also checks if cache directory needs cleaning to free up disk space. - Arguments: + Args: listen (bool): indication if listening trigger should be sent. """ @@ -234,7 +234,7 @@ def end_audio(self, listen=False): def init(self, bus): """Performs intial setup of TTS object. - Arguments: + Args: bus: Mycroft messagebus connection """ self.bus = bus @@ -247,7 +247,7 @@ def get_tts(self, sentence, wav_file): Should get data from tts. - Arguments: + Args: sentence(str): Sentence to synthesize wav_file(str): output file @@ -259,7 +259,7 @@ def get_tts(self, sentence, wav_file): def modify_tag(self, tag): """Override to modify each supported ssml tag. - Arguments: + Args: tag (str): SSML tag to check and possibly transform. """ return tag @@ -268,7 +268,7 @@ def modify_tag(self, tag): def remove_ssml(text): """Removes SSML tags from a string. - Arguments: + Args: text (str): input string Returns: @@ -281,7 +281,7 @@ def validate_ssml(self, utterance): Remove unsupported / invalid tags - Arguments: + Args: utterance (str): Sentence to validate Returns: @@ -310,7 +310,7 @@ def _preprocess_sentence(self, sentence): This method can be overridden to create chunks suitable to the TTS engine in question. - Arguments: + Args: sentence (str): sentence to preprocess Returns: @@ -324,7 +324,7 @@ def execute(self, sentence, ident=None, listen=False): The method caches results if possible using the hash of the sentence. - Arguments: + Args: sentence: (str) Sentence to be spoken ident: (str) Id reference to current interaction listen: (bool) True if listen should be triggered at the end @@ -399,7 +399,7 @@ def viseme(self, phonemes): May be implemented to convert TTS phonemes into Mycroft mouth visuals. - Arguments: + Args: phonemes (str): String with phoneme data Returns: @@ -427,7 +427,7 @@ def clear_cache(self): def save_phonemes(self, key, phonemes): """Cache phonemes - Arguments: + Args: key (str): Hash key for the sentence phonemes (str): phoneme string to save """ @@ -445,7 +445,7 @@ def save_phonemes(self, key, phonemes): def load_phonemes(self, key): """Load phonemes from cache file. - Arguments: + Args: key (str): Key identifying phoneme cache """ # TODO: remove in 21.08 @@ -522,7 +522,7 @@ def get_tts_class(self): def load_tts_plugin(module_name): """Wrapper function for loading tts plugin. - Arguments: + Args: (str) Mycroft tts module name from config Returns: class: found tts plugin class diff --git a/mycroft/util/audio_utils.py b/mycroft/util/audio_utils.py index 712c4a563db5..dae62d1700f2 100644 --- a/mycroft/util/audio_utils.py +++ b/mycroft/util/audio_utils.py @@ -32,7 +32,7 @@ def play_audio_file(uri: str, environment=None): the file extension. The function will return directly and play the file in the background. - Arguments: + Args: uri: uri to play environment (dict): optional environment for the subprocess call @@ -75,7 +75,7 @@ def _get_pulse_environment(config): def _play_cmd(cmd, uri, config, environment): """Generic function for starting playback from a commandline and uri. - Arguments: + Args: cmd (str): commandline to execute %1 in the command line will be replaced with the provided uri. uri (str): uri to play @@ -96,7 +96,7 @@ def play_wav(uri, environment=None): and play the uri passed as argument. The function will return directly and play the file in the background. - Arguments: + Args: uri: uri to play environment (dict): optional environment for the subprocess call @@ -121,7 +121,7 @@ def play_mp3(uri, environment=None): and play the uri passed as argument. The function will return directly and play the file in the background. - Arguments: + Args: uri: uri to play environment (dict): optional environment for the subprocess call @@ -146,7 +146,7 @@ def play_ogg(uri, environment=None): and play the uri passed as argument. The function will return directly and play the file in the background. - Arguments: + Args: uri: uri to play environment (dict): optional environment for the subprocess call @@ -170,7 +170,7 @@ def record(file_path, duration, rate, channels): The recording is done in the background by the arecord commandline application. - Arguments: + Args: file_path: where to store the recorded data duration: how long to record rate: sample rate @@ -188,7 +188,7 @@ def record(file_path, duration, rate, channels): def find_input_device(device_name): """Find audio input device by name. - Arguments: + Args: device_name: device name or regex pattern to match Returns: device_index (int) or None if device wasn't found diff --git a/mycroft/util/combo_lock.py b/mycroft/util/combo_lock.py index fb7154bcf92c..d242974d8ed3 100644 --- a/mycroft/util/combo_lock.py +++ b/mycroft/util/combo_lock.py @@ -21,7 +21,7 @@ class ComboLock: """ A combined process and thread lock. - Arguments: + Args: path (str): path to the lockfile for the lock """ def __init__(self, path): @@ -37,7 +37,7 @@ def __init__(self, path): def acquire(self, blocking=True): """ Acquire lock, locks thread and process lock. - Arguments: + Args: blocking(bool): Set's blocking mode of acquire operation. Default True. diff --git a/mycroft/util/download.py b/mycroft/util/download.py index eb009900ba47..f0232aec932f 100644 --- a/mycroft/util/download.py +++ b/mycroft/util/download.py @@ -31,7 +31,7 @@ def _get_download_tmp(dest): """Get temporary file for download. - Arguments: + Args: dest (str): path to download location Returns: @@ -55,7 +55,7 @@ class Downloader(Thread): be set to true and the `.status` will indicate the HTTP status code. 200 = Success. - Arguments: + Args: url (str): Url to download dest (str): Path to save data to complete_action (callable): Function to run when download is complete @@ -83,7 +83,7 @@ def __init__(self, url, dest, complete_action=None, header=None): def perform_download(self, dest): """Handle the download through wget. - Arguments: + Args: dest (str): Save location """ cmd = ['wget', '-c', self.url, '-O', dest, @@ -113,7 +113,7 @@ def finalize(self, tmp): Move the .part file to the final destination and perform any actions that should be performed at completion. - Arguments: + Args: tmp(str): temporary file path """ os.rename(tmp, self.dest) @@ -135,7 +135,7 @@ def abort(self): def download(url, dest, complete_action=None, header=None): """Start a download or fetch an already running. - Arguments: + Args: url (str): url to download dest (str): path to save download to complete_action (callable): Optional function to call on completion diff --git a/mycroft/util/file_utils.py b/mycroft/util/file_utils.py index 120c2215c3b6..279c7d16a3e6 100644 --- a/mycroft/util/file_utils.py +++ b/mycroft/util/file_utils.py @@ -39,15 +39,18 @@ def resolve_resource_file(res_name): folder of the source code package. Example: - With mycroft running as the user 'bob', if you called - resolve_resource_file('snd/beep.wav') - it would return either '/home/bob/.mycroft/snd/beep.wav' or - '/opt/mycroft/snd/beep.wav' or '.../mycroft/res/snd/beep.wav', - where the '...' is replaced by the path where the package has - been installed. - - Arguments: + With mycroft running as the user 'bob', if you called + ``resolve_resource_file('snd/beep.wav')`` + it would return either: + '/home/bob/.mycroft/snd/beep.wav' or + '/opt/mycroft/snd/beep.wav' or + '.../mycroft/res/snd/beep.wav' + where the '...' is replaced by the path + where the package has been installed. + + Args: res_name (str): a resource path/name + Returns: (str) path to resource or None if no resource found """ @@ -80,7 +83,7 @@ def resolve_resource_file(res_name): def read_stripped_lines(filename): """Read a file and return a list of stripped lines. - Arguments: + Args: filename (str): path to file to read. Returns: @@ -106,7 +109,7 @@ def read_dict(filename, div='='): 'baz': 'bog' } - Arguments: + Args: filename (str): path to file div (str): deviders between dict keys and values @@ -124,7 +127,7 @@ def read_dict(filename, div='='): def mb_to_bytes(size): """Takes a size in MB and returns the number of bytes. - Arguments: + Args: size(int/float): size in Mega Bytes Returns: @@ -136,7 +139,7 @@ def mb_to_bytes(size): def _get_cache_entries(directory): """Get information tuple for all regular files in directory. - Arguments: + Args: directory (str): path to directory to check Returns: @@ -153,7 +156,7 @@ def _get_cache_entries(directory): def _delete_oldest(entries, bytes_needed): """Delete files with oldest modification date until space is freed. - Arguments: + Args: entries (tuple): file + file stats tuple bytes_needed (int): disk space that needs to be freed @@ -183,7 +186,7 @@ def curate_cache(directory, min_free_percent=5.0, min_free_disk=50): is below the limit. This assumes all the files in the directory can be deleted as freely. - Arguments: + Args: directory (str): directory path that holds cached files min_free_percent (float): percentage (0.0-100.0) of drive to keep free, default is 5% if not specified. @@ -221,7 +224,7 @@ def get_cache_directory(domain=None): uses these cached files must be able to fallback and regenerate the file. - Arguments: + Args: domain (str): The cache domain. Basically just a subdirectory. Returns: @@ -238,7 +241,7 @@ def get_cache_directory(domain=None): def ensure_directory_exists(directory, domain=None, permissions=0o777): """Create a directory and give access rights to all - Arguments: + Args: directory (str): Root directory domain (str): Domain. Basically a subdirectory to prevent things like overlapping signal filenames. @@ -269,7 +272,7 @@ def ensure_directory_exists(directory, domain=None, permissions=0o777): def create_file(filename): """Create the file filename and create any directories needed - Arguments: + Args: filename: Path to the file to be created """ ensure_directory_exists(os.path.dirname(filename), permissions=0o775) diff --git a/mycroft/util/monotonic_event.py b/mycroft/util/monotonic_event.py index 1618924d4583..0bb2d9bccb1c 100644 --- a/mycroft/util/monotonic_event.py +++ b/mycroft/util/monotonic_event.py @@ -37,7 +37,7 @@ def wait_timeout(self, timeout): Repeatingly wait as long the event hasn't been set and the monotonic time doesn't indicate a timeout. - Arguments: + Args: timeout: timeout of wait in seconds Returns: diff --git a/mycroft/util/parse.py b/mycroft/util/parse.py index a4089c4be5f8..cbc20c27bcd7 100644 --- a/mycroft/util/parse.py +++ b/mycroft/util/parse.py @@ -49,7 +49,7 @@ def _log_unsupported_language(language, supported_languages): """ Log a warning when a language is unsupported - Arguments: + Args: language: str The language that was supplied. supported_languages: [str] @@ -70,10 +70,13 @@ def extract_datetime(text, anchorDate="DEFAULT", lang=None, "Tuesday". Vague terminology are given arbitrary values, like: - - morning = 8 AM - - afternoon = 3 PM - - evening = 7 PM + + * morning = 8 AM + * afternoon = 3 PM + * evening = 7 PM + If a time isn't supplied or implied, the function defaults to 12 AM + Args: text (str): the text to be interpreted anchorDate (:obj:`datetime`, optional): the date to be used for @@ -82,6 +85,7 @@ def extract_datetime(text, anchorDate="DEFAULT", lang=None, lang (str): the BCP-47 code for the language to use, None uses default default_time (datetime.time): time to use if none was found in the input string. + Returns: [:obj:`datetime`, :obj:`str`]: 'datetime' is the extracted date as a datetime object in the user's local timezone. diff --git a/mycroft/util/plugins.py b/mycroft/util/plugins.py index a4a14becbac5..f87b3ebfc075 100644 --- a/mycroft/util/plugins.py +++ b/mycroft/util/plugins.py @@ -22,7 +22,7 @@ def find_plugins(plug_type): """Finds all plugins matching specific entrypoint type. - Arguments: + Args: plug_type (str): plugin entrypoint string to retrieve Returns: @@ -38,7 +38,7 @@ def find_plugins(plug_type): def load_plugin(plug_type, plug_name): """Load a specific plugin from a specific plugin type. - Arguments: + Args: plug_type: (str) plugin type name. Ex. "mycroft.plugin.tts". plug_name: (str) specific plugin name diff --git a/mycroft/util/process_utils.py b/mycroft/util/process_utils.py index 81f5c90f59dc..ae33a6ccbd2a 100644 --- a/mycroft/util/process_utils.py +++ b/mycroft/util/process_utils.py @@ -47,7 +47,7 @@ def bus_logging_status(): def _update_log_level(msg, name): """Update log level for process. - Arguments: + Args: msg (Message): Message sent to trigger the log level change name (str): Name of the current process """ @@ -80,7 +80,7 @@ def create_echo_function(name, whitelist=None): messagebus-based processes. TODO 20.08: extract log level setting thing completely from this function - Arguments: + Args: name (str): Reference name of the process whitelist (list, optional): List of "type" strings. If defined, only messages in this list will be logged. @@ -129,7 +129,7 @@ def echo(message): def start_message_bus_client(service, bus=None, whitelist=None): """Start the bus client daemon and wait for connection. - Arguments: + Args: service (str): name of the service starting the connection bus (MessageBusClient): an instance of the Mycroft MessageBusClient whitelist (list, optional): List of "type" strings. If defined, only @@ -201,7 +201,7 @@ class ProcessStatus: state changes as well as replies to messagebus queries of the process status. - Arguments: + Args: name (str): process name, will be used to create the messagebus messagetype "mycroft.{name}...". bus (MessageBusClient): Connection to the Mycroft messagebus. @@ -233,7 +233,7 @@ def _register_handlers(self): def check_alive(self, message=None): """Respond to is_alive status request. - Arguments: + Args: message: Optional message to respond to, if omitted no message is sent. @@ -251,7 +251,7 @@ def check_alive(self, message=None): def check_ready(self, message=None): """Respond to all_loaded status request. - Arguments: + Args: message: Optional message to respond to, if omitted no message is sent. diff --git a/mycroft/util/time.py b/mycroft/util/time.py index 07e06203986c..0c86e0ad8db6 100644 --- a/mycroft/util/time.py +++ b/mycroft/util/time.py @@ -57,7 +57,7 @@ def now_utc(): def now_local(tz=None): """Retrieve the current time - Arguments: + Args: tz (datetime.tzinfo, optional): Timezone, default to user's settings Returns: @@ -71,7 +71,7 @@ def now_local(tz=None): def to_utc(dt): """Convert a datetime with timezone info to a UTC datetime - Arguments: + Args: dt (datetime): A datetime (presumably in some local zone) Returns: (datetime): time converted to UTC @@ -86,7 +86,7 @@ def to_utc(dt): def to_local(dt): """Convert a datetime to the user's local timezone - Arguments: + Args: dt (datetime): A datetime (if no timezone, defaults to UTC) Returns: (datetime): time converted to the local timezone @@ -101,7 +101,7 @@ def to_local(dt): def to_system(dt): """Convert a datetime to the system's local timezone - Arguments: + Args: dt (datetime): A datetime (if no timezone, assumed to be UTC) Returns: (datetime): time converted to the operation system's timezone diff --git a/test/integrationtests/skills/runner.py b/test/integrationtests/skills/runner.py index a26f11e052c1..a63115087c48 100644 --- a/test/integrationtests/skills/runner.py +++ b/test/integrationtests/skills/runner.py @@ -50,7 +50,7 @@ def load_test_environment(skill): """Load skill's test environment if present - Arguments: + Args: skill (str): path to skill root folder Returns: diff --git a/test/integrationtests/skills/skill_tester.py b/test/integrationtests/skills/skill_tester.py index c48f78aacd74..d57f367114ad 100644 --- a/test/integrationtests/skills/skill_tester.py +++ b/test/integrationtests/skills/skill_tester.py @@ -387,7 +387,7 @@ def send_test_input(self, s, test_case): def execute_test(self, s): """ Execute test case. - Arguments: + Args: s (MycroftSkill): mycroft skill to test Returns: diff --git a/test/integrationtests/voight_kampff/features/steps/configuration.py b/test/integrationtests/voight_kampff/features/steps/configuration.py index 433d9beed901..aecde4d8eb04 100644 --- a/test/integrationtests/voight_kampff/features/steps/configuration.py +++ b/test/integrationtests/voight_kampff/features/steps/configuration.py @@ -29,7 +29,7 @@ def wait_for_config_change(context, key, expected_value): Waits until the specified key has changed into the expected value, if the change delays for too long a TimeoutError will be raised. - Arguments: + Args: context (Context): Behave context of current scenario key (str): key to verify expected_value (Object): The expected value indicating that the change @@ -45,7 +45,7 @@ def wait_for_config_change(context, key, expected_value): def reset_config(context): """Cleanup callback to reset patched configuration - Arguments: + Args: context (Context): Behave context of current scenario """ context.log.info('Resetting patched configuration...') @@ -57,7 +57,7 @@ def reset_config(context): def patch_config(context, patch): """Apply patch to config and wait for it to take effect. - Arguments: + Args: context: Behave context for test patch: patch to apply """ @@ -86,7 +86,7 @@ def patch_config(context, patch): def get_config_file_definition(configs_path, config, value): """Read config definition file and return the matching patch dict. - Arguments: + Args: configs_path: path to the configuration patch json file config: config value to fetch from the file value: predefined value to fetch @@ -102,7 +102,7 @@ def get_config_file_definition(configs_path, config, value): def get_global_config_definition(context, config, value): """Get config definitions included with Mycroft. - Arguments: + Args: context: behave test context config: config value to fetch from the file value: predefined value to fetch @@ -118,7 +118,7 @@ def get_global_config_definition(context, config, value): def get_feature_config_definition(context, config, value): """Get config feature specific config defintion - Arguments: + Args: context: behave test context config: config value to fetch from the file value: predefined value to fetch diff --git a/test/integrationtests/voight_kampff/features/steps/utterance_responses.py b/test/integrationtests/voight_kampff/features/steps/utterance_responses.py index cb703189d0cf..de4197e1741b 100644 --- a/test/integrationtests/voight_kampff/features/steps/utterance_responses.py +++ b/test/integrationtests/voight_kampff/features/steps/utterance_responses.py @@ -56,7 +56,7 @@ def load_dialog_file(dialog_path): def load_dialog_list(skill_path, dialog): """Load dialog from files into a single list. - Arguments: + Args: skill (MycroftSkill): skill to load dialog from dialog (list): Dialog names (str) to load @@ -72,7 +72,7 @@ def load_dialog_list(skill_path, dialog): def dialog_from_sentence(sentence, skill_path, lang): """Find dialog file from example sentence. - Arguments: + Args: sentence (str): Text to match skill_path (str): path to skill directory lang (str): language code to use diff --git a/test/integrationtests/voight_kampff/test_setup.py b/test/integrationtests/voight_kampff/test_setup.py index a2b6f522de9a..76a7c4f6ef95 100644 --- a/test/integrationtests/voight_kampff/test_setup.py +++ b/test/integrationtests/voight_kampff/test_setup.py @@ -120,7 +120,7 @@ def get_random_skills(msm, num_random_skills): def install_or_upgrade_skills(msm, skills): """Install needed skills if uninstalled, otherwise try to update. - Arguments: + Args: msm: msm instance to use for the operations skills: list of skills """ @@ -139,7 +139,7 @@ def install_or_upgrade_skills(msm, skills): def collect_test_cases(msm, skills): """Collect feature files and step files for each skill. - Arguments: + Args: msm: msm instance to use for the operations skills: list of skills """ @@ -180,7 +180,7 @@ def get_arguments(cmdline_args): Parses the commandline and if specified applies configuration file. - Arguments: + Args: cmdline_args (list): argv like list of arguments Returns: @@ -194,7 +194,7 @@ def get_arguments(cmdline_args): def create_skills_manager(platform, skills_dir, url, branch): """Create mycroft skills manager for the given url / branch. - Arguments: + Args: platform (str): platform to use skills_dir (str): skill directory to use url (str): skills repo url diff --git a/test/integrationtests/voight_kampff/tools.py b/test/integrationtests/voight_kampff/tools.py index 993952649ab7..8a26b044550d 100644 --- a/test/integrationtests/voight_kampff/tools.py +++ b/test/integrationtests/voight_kampff/tools.py @@ -26,7 +26,7 @@ def then_wait(msg_type, criteria_func, context, timeout=None): """Wait for a specified time for criteria to be fulfilled. - Arguments: + Args: msg_type: message type to watch criteria_func: Function to determine if a message fulfilling the test case has been found. @@ -56,7 +56,7 @@ def then_wait(msg_type, criteria_func, context, timeout=None): def then_wait_fail(msg_type, criteria_func, context, timeout=None): """Wait for a specified time, failing if criteria is fulfilled. - Arguments: + Args: msg_type: message type to watch criteria_func: Function to determine if a message fulfilling the test case has been found. @@ -73,7 +73,7 @@ def then_wait_fail(msg_type, criteria_func, context, timeout=None): def mycroft_responses(context): """Collect and format mycroft responses from context. - Arguments: + Args: context: behave context to extract messages from. Returns: (str) Mycroft responses including skill and dialog file @@ -98,7 +98,7 @@ def print_mycroft_responses(context): def emit_utterance(bus, utt): """Emit an utterance on the bus. - Arguments: + Args: bus (InterceptAllBusClient): Bus instance to listen on dialogs (list): list of acceptable dialogs """ @@ -113,7 +113,7 @@ def emit_utterance(bus, utt): def wait_for_dialog(bus, dialogs, context=None, timeout=None): """Wait for one of the dialogs given as argument. - Arguments: + Args: bus (InterceptAllBusClient): Bus instance to listen on dialogs (list): list of acceptable dialogs context (behave Context): optional context providing scenario timeout @@ -140,7 +140,7 @@ def wait_for_audio_service(context, message_type): May be play, stop, or pause messages - Arguments: + Args: context (behave Context): optional context providing scenario timeout message_type (string): final component of bus message in form `mycroft.audio.service.{type} diff --git a/test/unittests/skills/test_mycroft_skill_get_response.py b/test/unittests/skills/test_mycroft_skill_get_response.py index ecef80b0ed49..a1deba60052e 100644 --- a/test/unittests/skills/test_mycroft_skill_get_response.py +++ b/test/unittests/skills/test_mycroft_skill_get_response.py @@ -22,7 +22,7 @@ def create_converse_responder(response, skill): The function waits for the converse method to be replaced by the _wait_response logic and afterwards injects the provided response. - Arguments: + Args: response (str): Sentence to inject. skill (MycroftSkill): skill to monitor. """ diff --git a/test/unittests/skills/test_skill_api.py b/test/unittests/skills/test_skill_api.py index b142bc20a742..b9ae0de9b16e 100644 --- a/test/unittests/skills/test_skill_api.py +++ b/test/unittests/skills/test_skill_api.py @@ -46,7 +46,7 @@ def load_test_skill(): def create_skill_api_from_skill(skill): """Helper creating an api from a skill. - Arguments: + Args: skill (MycroftSkill): Skill to create api from. Returns: From e7accacdcf9bf494d96cbaaee068dfb79a667017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Wed, 5 May 2021 12:03:01 +0200 Subject: [PATCH 10/68] Fix sphinxdoc warning in common_iot_skill.py --- mycroft/skills/common_iot_skill.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mycroft/skills/common_iot_skill.py b/mycroft/skills/common_iot_skill.py index 54f04c6fa920..5f3bdef4b495 100644 --- a/mycroft/skills/common_iot_skill.py +++ b/mycroft/skills/common_iot_skill.py @@ -528,8 +528,8 @@ def can_handle(self, request: IoTRequest): An IoTRequest contains several properties (see the documentation for that class). This method should return True if and only if this skill can take the appropriate - 'action' when considering _all other properties - of the request_. In other words, a partial match, one in which + 'action' when considering all other properties + of the request. In other words, a partial match, one in which any piece of the IoTRequest is not known to this skill, and is not None, this should return (False, None). From cfc84bab27d0e5800825082afd07ac5c8ed39389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Wed, 5 May 2021 10:56:22 +0200 Subject: [PATCH 11/68] Add approprate blank lines to fix phinxdoc warnings --- mycroft/util/format.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mycroft/util/format.py b/mycroft/util/format.py index c2fea4cf9781..b37bf8fa453c 100644 --- a/mycroft/util/format.py +++ b/mycroft/util/format.py @@ -61,8 +61,9 @@ class TimeResolution(Enum): def _duration_handler(time1, lang=None, speech=True, *, time2=None, use_years=True, clock=False, resolution=TimeResolution.SECONDS): - """ Convert duration in seconds to a nice spoken timespan - Used as a handler by nice_duration and nice_duration_dt + """Convert duration in seconds to a nice spoken timespan. + + Used as a handler by nice_duration and nice_duration_dt. Accepts: datetime.timedelta, or @@ -323,6 +324,7 @@ def nice_duration(duration, lang=None, speech=True, use_years=True, TimeResolution.MINUTES TimeResolution.SECONDS TimeResolution.MILLISECONDS + NOTE: nice_duration will not produce milliseconds unless that resolution is passed. From 083463a75e599ffa5fc2f0c7679e67871bb29c43 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Fri, 7 May 2021 14:54:18 +0930 Subject: [PATCH 12/68] Clean up docstrings and CLI help info --- mycroft/client/text/text_client.py | 177 +++++++++++++++-------------- 1 file changed, 92 insertions(+), 85 deletions(-) diff --git a/mycroft/client/text/text_client.py b/mycroft/client/text/text_client.py index d39e8ccd0be6..8af229806b84 100644 --- a/mycroft/client/text/text_client.py +++ b/mycroft/client/text/text_client.py @@ -82,8 +82,8 @@ screen_mode = SCR_MAIN subscreen = 0 # for help pages, etc. -FULL_REDRAW_FREQUENCY = 10 # seconds between full redraws -last_full_redraw = time.time()-(FULL_REDRAW_FREQUENCY-1) # seed for 1s redraw +REDRAW_FREQUENCY = 10 # seconds between full redraws +last_redraw = time.time() - (REDRAW_FREQUENCY - 1) # seed for 1s redraw screen_lock = Lock() is_screen_dirty = True @@ -437,7 +437,7 @@ def handle_utterance(event): def connect(bus): """ Run the mycroft messagebus referenced by bus. - Arguments: + Args: bus: Mycroft messagebus instance """ bus.run_forever() @@ -470,19 +470,19 @@ def draw(x, y, msg, pad=None, pad_chr=None, clr=None): return if x + len(msg) > curses.COLS: - s = msg[:curses.COLS-x] + s = msg[:curses.COLS - x] else: s = msg if pad: ch = pad_chr or " " if pad is True: pad = curses.COLS # pad to edge of screen - s += ch * (pad-x-len(msg)) + s += ch * (pad - x - len(msg)) else: # pad to given length (or screen width) - if x+pad > curses.COLS: - pad = curses.COLS-x - s += ch * (pad-len(msg)) + if x + pad > curses.COLS: + pad = curses.COLS - x + s += ch * (pad - len(msg)) if not clr: clr = CLR_LOG1 @@ -622,18 +622,27 @@ def _do_gui(gui_width): clr = curses.color_pair(2) # dark red x = curses.COLS - gui_width y = 3 - draw(x, y, " "+make_titlebar("= GUI", gui_width-1)+" ", clr=CLR_HEADING) - cnt = len(gui_text)+1 - if cnt > curses.LINES-15: - cnt = curses.LINES-15 + draw( + x, + y, + " " + + make_titlebar( + "= GUI", + gui_width - + 1) + + " ", + clr=CLR_HEADING) + cnt = len(gui_text) + 1 + if cnt > curses.LINES - 15: + cnt = curses.LINES - 15 for i in range(0, cnt): - draw(x, y+1+i, " !", clr=CLR_HEADING) + draw(x, y + 1 + i, " !", clr=CLR_HEADING) if i < len(gui_text): - draw(x+2, y+1+i, gui_text[i], pad=gui_width-3) + draw(x + 2, y + 1 + i, gui_text[i], pad=gui_width - 3) else: - draw(x+2, y+1+i, "*"*(gui_width-3)) - draw(x+(gui_width-1), y+1+i, "!", clr=CLR_HEADING) - draw(x, y+cnt, " "+"-"*(gui_width-2)+" ", clr=CLR_HEADING) + draw(x + 2, y + 1 + i, "*" * (gui_width - 3)) + draw(x + (gui_width - 1), y + 1 + i, "!", clr=CLR_HEADING) + draw(x, y + cnt, " " + "-" * (gui_width - 2) + " ", clr=CLR_HEADING) def set_screen_dirty(): @@ -647,16 +656,16 @@ def set_screen_dirty(): def do_draw_main(scr): global log_line_offset global longest_visible_line - global last_full_redraw + global last_redraw global auto_scroll global size_log_area - if time.time() - last_full_redraw > FULL_REDRAW_FREQUENCY: + if time.time() - last_redraw > REDRAW_FREQUENCY: # Do a full-screen redraw periodically to clear and # noise from non-curses text that get output to the # screen (e.g. modules that do a 'print') scr.clear() - last_full_redraw = time.time() + last_redraw = time.time() else: scr.erase() @@ -689,8 +698,8 @@ def do_draw_main(scr): str(start) + "-" + str(end) + " of " + str(cLogs), CLR_HEADING) ver = " mycroft-core " + mycroft.version.CORE_VERSION_STR + " ===" - scr.addstr(1, 0, "=" * (curses.COLS-1-len(ver)), CLR_HEADING) - scr.addstr(1, curses.COLS-1-len(ver), ver, CLR_HEADING) + scr.addstr(1, 0, "=" * (curses.COLS - 1 - len(ver)), CLR_HEADING) + scr.addstr(1, curses.COLS - 1 - len(ver), ver, CLR_HEADING) y = 2 for i in range(start, end): @@ -794,7 +803,7 @@ def do_draw_main(scr): y += 1 if show_gui and curses.COLS > 20 and curses.LINES > 20: - _do_gui(curses.COLS-20) + _do_gui(curses.COLS - 20) # Command line at the bottom ln = line @@ -806,7 +815,7 @@ def do_draw_main(scr): else: prompt = "Input (':' for command, Ctrl+C to quit)" if show_last_key: - prompt += " === keycode: "+last_key + prompt += " === keycode: " + last_key scr.addstr(curses.LINES - 2, 0, make_titlebar(prompt, curses.COLS - 1), @@ -827,59 +836,59 @@ def make_titlebar(title, bar_length): # Help system -help_struct = [ - ( - 'Log Scrolling shortcuts', - [ - ("Up / Down / PgUp / PgDn", "scroll thru history"), - ("Ctrl+T / Ctrl+PgUp", "scroll to top of logs (jump to oldest)"), - ("Ctrl+B / Ctrl+PgDn", "scroll to bottom of logs" + - "(jump to newest)"), - ("Left / Right", "scroll long lines left/right"), - ("Home / End", "scroll to start/end of long lines") - ] - ), - ( - "Query History shortcuts", - [ - ("Ctrl+N / Ctrl+Right", "previous query"), - ("Ctrl+P / Ctrl+Left", "next query") - ] - ), - ( - "General Commands (type ':' to enter command mode)", - [ - (":quit or :exit", "exit the program"), - (":meter (show|hide)", "display the microphone level"), - (":keycode (show|hide)", "display typed key codes (mainly debugging)"), - (":history (# lines)", "set size of visible history buffer"), - (":clear", "flush the logs") - ] - ), - ( - "Log Manipulation Commands", - [ - (":filter 'STR'", "adds a log filter (optional quotes)"), - (":filter remove 'STR'", "removes a log filter"), - (":filter (clear|reset)", "reset filters"), - (":filter (show|list)", "display current filters"), - (":find 'STR'", "show logs containing 'str'"), - (":log level (DEBUG|INFO|ERROR)", "set logging level"), - (":log bus (on|off)", "control logging of messagebus messages") - ] - ), - ( - "Skill Debugging Commands", - [ - (":skills", "list installed skills"), - (":api SKILL", "show skill's public API"), - (":activate SKILL", "activate skill, e.g. 'activate skill-wiki'"), - (":deactivate SKILL", "deactivate skill"), - (":keep SKILL", "deactivate all skills except " + - "the indicated skill") - ] - ) -] +help_struct = [('Log Scrolling shortcuts', + [("Up / Down / PgUp / PgDn", + "scroll thru history"), + ("Ctrl+T / Ctrl+PgUp", + "scroll to top of logs (jump to oldest)"), + ("Ctrl+B / Ctrl+PgDn", + "scroll to bottom of logs" + "(jump to newest)"), + ("Left / Right", + "scroll long lines left/right"), + ("Home / End", + "scroll to start/end of long lines")]), + ("Query History shortcuts", + [("Ctrl+N / Ctrl+Right", + "previous query"), + ("Ctrl+P / Ctrl+Left", + "next query")]), + ("General Commands (type ':' to enter command mode)", + [(":quit or :exit", + "exit the program"), + (":meter (show|hide)", + "display the microphone level"), + (":keycode (show|hide)", + "display typed key codes (mainly debugging)"), + (":history (# lines)", + "set size of visible history buffer"), + (":clear", + "flush the logs")]), + ("Log Manipulation Commands", + [(":filter 'STR'", + "adds a log filter (optional quotes)"), + (":filter remove 'STR'", + "removes a log filter"), + (":filter (clear|reset)", + "reset filters"), + (":filter (show|list)", + "display current filters"), + (":find 'STR'", + "show logs containing 'str'"), + (":log level (DEBUG|INFO|ERROR)", + "set logging level"), + (":log bus (on|off)", + "control logging of messagebus messages")]), + ("Skill Debugging Commands", + [(":skills", + "list installed Skills"), + (":api SKILL", + "show Skill's public API"), + (":activate SKILL", + "activate Skill, e.g. 'activate skill-wiki'"), + (":deactivate SKILL", + "deactivate Skill"), + (":keep SKILL", + "deactivate all Skills except the indicated Skill")])] help_longest = 0 for s in help_struct: for ent in s[1]: @@ -934,7 +943,7 @@ def render_footer(page, total): ln = line[0].ljust(help_longest + 1) for w in words: if len(ln) + 1 + len(w) < curses.COLS: - ln += " "+w + ln += " " + w else: y = render_help(ln, y, i, first, last, CLR_CMDLINE) ln = " ".ljust(help_longest + 2) + w @@ -978,9 +987,7 @@ def show_next_help(): # Skill debugging def show_skills(skills): - """ - Show list of loaded skills in as many column as necessary - """ + """Show list of loaded Skills in as many column as necessary.""" global scr global screen_mode @@ -997,7 +1004,7 @@ def prepare_page(): nonlocal row nonlocal column scr.erase() - scr.addstr(0, 0, center(25) + "Loaded skills", CLR_CMDLINE) + scr.addstr(0, 0, center(25) + "Loaded Skills", CLR_CMDLINE) scr.addstr(1, 1, "=" * (curses.COLS - 2), CLR_CMDLINE) row = 2 column = 0 @@ -1037,7 +1044,7 @@ def prepare_page(): def show_skill_api(skill, data): - """Show available help on skill's API.""" + """Show available help on Skill's API.""" global scr global screen_mode @@ -1335,13 +1342,13 @@ def gui_main(stdscr): start = time.time() while c1 == -1: c1 = scr.getch() - if time.time()-start > 1: + if time.time() - start > 1: break # 1 second timeout waiting for ESC code c2 = -1 while c2 == -1: c2 = scr.getch() - if time.time()-start > 1: # 1 second timeout + if time.time() - start > 1: # 1 second timeout break # 1 second timeout waiting for ESC code if c1 == 79 and c2 == 120: From c936f2f116b6ec8296cbce9c745e52edf114ce80 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Fri, 7 May 2021 14:57:43 +0930 Subject: [PATCH 13/68] Fix incorrect CLI info --- mycroft/client/text/text_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mycroft/client/text/text_client.py b/mycroft/client/text/text_client.py index 8af229806b84..5d382cb52269 100644 --- a/mycroft/client/text/text_client.py +++ b/mycroft/client/text/text_client.py @@ -756,10 +756,10 @@ def do_draw_main(scr): if len(log_files) > 0: scr.addstr(y_log_legend + 2, curses.COLS // 2 + 2, os.path.basename(log_files[0]) + ", other", - CLR_LOG1) + CLR_LOG2) if len(log_files) > 1: scr.addstr(y_log_legend + 3, curses.COLS // 2 + 2, - os.path.basename(log_files[1]), CLR_LOG2) + os.path.basename(log_files[1]), CLR_LOG1) # Meter y_meter = y_log_legend @@ -848,9 +848,9 @@ def make_titlebar(title, bar_length): ("Home / End", "scroll to start/end of long lines")]), ("Query History shortcuts", - [("Ctrl+N / Ctrl+Right", + [("Ctrl+N / Ctrl+Left", "previous query"), - ("Ctrl+P / Ctrl+Left", + ("Ctrl+P / Ctrl+Right", "next query")]), ("General Commands (type ':' to enter command mode)", [(":quit or :exit", From 9ffd60bc439bb8cd8b9bbcdeb90924c4253e1abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Fri, 14 May 2021 18:27:22 +0200 Subject: [PATCH 14/68] Fetch Mycroft-core version from mycroft.version Uses the info block provided by the release script --- doc/conf.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index ac927bf00a59..14a3273a537c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -19,6 +19,7 @@ import sys import re import os +from os.path import dirname, join import sphinx_rtd_theme from sphinx.ext.autodoc import ( @@ -74,13 +75,33 @@ def iad_add_directive_header(self, sig): source_suffix = '.rst' master_doc = 'index' + +def get_version(): + version_file = join(dirname(__file__), + '..', 'mycroft', 'version', '__init__.py') + with open(version_file) as f: + while 'START_VERSION_BLOCK' not in f.readline(): + pass + + def safe_read_version_line(): + try: + return f.readline().split('=')[1].strip() + except Exception: + return '0' + + major = safe_read_version_line() + minor = safe_read_version_line() + build = safe_read_version_line() + return 'v' + '.'.join((major, minor, build)) + + # General Info project = 'Mycroft' copyright = '2017, Mycroft AI Inc.' author = 'Mycroft AI Inc.' -version = '0.1.0' -release = '0.1.0' # Includes alpha/beta/rc tags. +version = get_version() +release = version language = None exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] From 34e22657f60d13d2e194568431ba2d5bff5a10f4 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Wed, 26 May 2021 16:31:42 +0930 Subject: [PATCH 15/68] Upgrade Lingua Franca from 0.4.1 > 0.4.2 Adds Farsi support --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index c9ccedaf313b..fa9d9a3a8334 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -15,7 +15,7 @@ python-dateutil==2.6.0 fasteners==0.14.1 PyYAML==5.4 -lingua-franca==0.4.1 +lingua-franca==0.4.2 msm==0.8.9 msk==0.3.16 mycroft-messagebus-client==0.9.1 From 01f73b6b01caceabc450875b6f0f27ab6c2e3aac Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Thu, 27 May 2021 15:36:29 +0930 Subject: [PATCH 16/68] Update Adapt parser to v0.4.1 --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index c9ccedaf313b..5b024132fb72 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -19,7 +19,7 @@ lingua-franca==0.4.1 msm==0.8.9 msk==0.3.16 mycroft-messagebus-client==0.9.1 -adapt-parser==0.3.7 +adapt-parser==0.4.1 padatious==0.4.8 fann2==1.0.7 padaos==0.1.9 From ad410d4bfc83b9ea3d100726b1e9fb27f1852f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Thu, 1 Apr 2021 12:03:06 +0200 Subject: [PATCH 17/68] Remove registered keywords on skill shutdown Uses new drop_*() methods from adapt-parser 0.4.0 --- .../skills/intent_services/adapt_service.py | 71 ++++++++++++++++--- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/mycroft/skills/intent_services/adapt_service.py b/mycroft/skills/intent_services/adapt_service.py index be69bd9a087a..54fe565e02f3 100644 --- a/mycroft/skills/intent_services/adapt_service.py +++ b/mycroft/skills/intent_services/adapt_service.py @@ -13,6 +13,7 @@ # limitations under the License. # """An intent parsing service using the Adapt parser.""" +from threading import Lock import time from adapt.context import ContextManagerFrame @@ -23,6 +24,21 @@ from .base import IntentMatch +def _entity_skill_id(skill_id): + """Helper converting a skill id to the format used in entities. + + Arguments: + skill_id (str): skill identifier + + Returns: + (str) skill id on the format used by skill entities + """ + skill_id = skill_id[:-1] + skill_id = skill_id.replace('.', '_') + skill_id = skill_id.replace('-', '_') + return skill_id + + class AdaptIntent(IntentBuilder): """Wrapper for IntentBuilder setting a blank name. @@ -161,6 +177,7 @@ def __init__(self, config): self.context_timeout = self.config.get('timeout', 2) self.context_greedy = self.config.get('greedy', False) self.context_manager = ContextManager(self.context_timeout) + self.lock = Lock() def update_context(self, intent): """Updates context with keyword from the intent. @@ -227,11 +244,12 @@ def take_best(intent, utt): def register_vocab(self, start_concept, end_concept, alias_of, regex_str): """Register vocabulary.""" - if regex_str: - self.engine.register_regex_entity(regex_str) - else: - self.engine.register_entity( - start_concept, end_concept, alias_of=alias_of) + with self.lock: + if regex_str: + self.engine.register_regex_entity(regex_str) + else: + self.engine.register_entity( + start_concept, end_concept, alias_of=alias_of) def register_intent(self, intent): """Register new intent with adapt engine. @@ -239,7 +257,8 @@ def register_intent(self, intent): Arguments: intent (IntentParser): IntentParser to register """ - self.engine.register_intent_parser(intent) + with self.lock: + self.engine.register_intent_parser(intent) def detach_skill(self, skill_id): """Remove all intents for skill. @@ -247,11 +266,41 @@ def detach_skill(self, skill_id): Arguments: skill_id (str): skill to process """ - new_parsers = [ - p for p in self.engine.intent_parsers if - not p.name.startswith(skill_id) - ] - self.engine.intent_parsers = new_parsers + with self.lock: + skill_parsers = [ + p.name for p in self.engine.intent_parsers if + p.name.startswith(skill_id) + ] + self.engine.drop_intent_parser(skill_parsers) + self._detach_skill_keywords(skill_id) + self._detach_skill_regexes(skill_id) + + def _detach_skill_keywords(self, skill_id): + """Detach all keywords registered with a particular skill. + + Arguments: + skill_id (str): skill identifier + """ + skill_id = _entity_skill_id(skill_id) + + def match_skill_entities(data): + return data and data[1].startswith(skill_id) + + self.engine.drop_entity(match_func=match_skill_entities) + + def _detach_skill_regexes(self, skill_id): + """Detach all regexes registered with a particular skill. + + Arguments: + skill_id (str): skill identifier + """ + skill_id = _entity_skill_id(skill_id) + + def match_skill_regexes(regexp): + return any([r.startswith(skill_id) + for r in regexp.groupindex.keys()]) + + self.engine.drop_regex_entity(match_func=match_skill_regexes) def detach_intent(self, intent_name): """Detatch a single intent From 4ca656caac0de236cd31c81981da4bd8ca708f7d Mon Sep 17 00:00:00 2001 From: mathmauney Date: Fri, 23 Aug 2019 15:32:59 -0400 Subject: [PATCH 18/68] Issue-2232 Add warning when AVX is not detected and fallback. --- dev_setup.sh | 16 ++++++++++++++++ mycroft/client/speech/hotword_factory.py | 9 +++++++++ mycroft/configuration/mycroft.conf | 1 + 3 files changed, 26 insertions(+) diff --git a/dev_setup.sh b/dev_setup.sh index 4290850bda03..68b10cba3e1f 100755 --- a/dev_setup.sh +++ b/dev_setup.sh @@ -174,6 +174,22 @@ This script is designed to make working with Mycroft easy. During this first run of dev_setup we will ask you a few questions to help setup your environment.' sleep 0.5 + if ! grep -q avx /proc/cpuinfo; then + echo " +The Precise Wake Word Engine requires the AVX instruction set, which is +not supported on your CPU. Do you want to fall back to the PocketSphinx +engine? Advanced users can build the precise engine with an older +version of TensorFlow (v1.13) if desired and change use_precise to true +in mycroft.conf. + Y)es, I want to use the PocketSphinx engine or my own. + N)o, stop the installation." + if get_YN ; then + sed -i "s/\"use_precise\": true/\"use_precise\": false/" config/ + else + echo -e "$HIGHLIGHT N - quit the installation $RESET" + exit 1 + fi + fi echo " Do you want to run on 'master' or against a dev branch? Unless you are a developer modifying mycroft-core itself, you should run on the diff --git a/mycroft/client/speech/hotword_factory.py b/mycroft/client/speech/hotword_factory.py index e5385bf5ed82..1f69035f87ca 100644 --- a/mycroft/client/speech/hotword_factory.py +++ b/mycroft/client/speech/hotword_factory.py @@ -47,6 +47,10 @@ class NoModelAvailable(Exception): pass +class PreciseUnavailable(Exception): + pass + + def msec_to_sec(msecs): """Convert milliseconds to seconds. @@ -190,6 +194,8 @@ def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"): PreciseRunner, PreciseEngine, ReadWriteStream ) local_conf = LocalConf(USER_CONFIG) + if not local_conf.get('precise', {}).get('use_precise', True): + raise PreciseUnavailable if (local_conf.get('precise', {}).get('dist_url') == 'http://bootstrap.mycroft.ai/artifacts/static/daily/'): del local_conf['precise']['dist_url'] @@ -487,6 +493,9 @@ def initialize(): hotword, module )) instance = None + except PreciseUnavailable: + LOG.warning('Settings prevent Precise Engine use, falling back to default.') + instance = None except Exception: LOG.exception( 'Could not create hotword. Falling back to default.') diff --git a/mycroft/configuration/mycroft.conf b/mycroft/configuration/mycroft.conf index b07fe137e3f6..6d8e0113bb81 100644 --- a/mycroft/configuration/mycroft.conf +++ b/mycroft/configuration/mycroft.conf @@ -199,6 +199,7 @@ // Settings used for any precise wake words "precise": { + "use_precise": true, "dist_url": "https://github.com/MycroftAI/precise-data/raw/dist/{arch}/latest", "model_url": "https://raw.githubusercontent.com/MycroftAI/precise-data/models/{wake_word}.tar.gz" }, From 011c3899a94da6781b1e166f38f10324313f4119 Mon Sep 17 00:00:00 2001 From: mathmauney Date: Fri, 23 Aug 2019 15:50:02 -0400 Subject: [PATCH 19/68] Fix line too long --- mycroft/client/speech/hotword_factory.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mycroft/client/speech/hotword_factory.py b/mycroft/client/speech/hotword_factory.py index 1f69035f87ca..878c0fc5d15c 100644 --- a/mycroft/client/speech/hotword_factory.py +++ b/mycroft/client/speech/hotword_factory.py @@ -494,7 +494,8 @@ def initialize(): )) instance = None except PreciseUnavailable: - LOG.warning('Settings prevent Precise Engine use, falling back to default.') + LOG.warning('Settings prevent Precise Engine use, + falling back to default.') instance = None except Exception: LOG.exception( From e9265041579fae95dca348f9ffbc7d3002803f76 Mon Sep 17 00:00:00 2001 From: mathmauney Date: Sat, 24 Aug 2019 10:20:29 -0400 Subject: [PATCH 20/68] Ignore missing AVX for ARM systems --- dev_setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_setup.sh b/dev_setup.sh index 68b10cba3e1f..dda498595936 100755 --- a/dev_setup.sh +++ b/dev_setup.sh @@ -174,7 +174,7 @@ This script is designed to make working with Mycroft easy. During this first run of dev_setup we will ask you a few questions to help setup your environment.' sleep 0.5 - if ! grep -q avx /proc/cpuinfo; then + if ! grep -q avx /proc/cpuinfo && [[ ! $(uname -m) == 'arm'* ]]; then echo " The Precise Wake Word Engine requires the AVX instruction set, which is not supported on your CPU. Do you want to fall back to the PocketSphinx From 305b93625a8e5dc6e6c8b9b425c7a6d3c650a73e Mon Sep 17 00:00:00 2001 From: mathmauney Date: Sat, 24 Aug 2019 17:34:30 -0400 Subject: [PATCH 21/68] Update hotword_factory.py Fix long line string --- mycroft/client/speech/hotword_factory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mycroft/client/speech/hotword_factory.py b/mycroft/client/speech/hotword_factory.py index 878c0fc5d15c..af39f2eb571d 100644 --- a/mycroft/client/speech/hotword_factory.py +++ b/mycroft/client/speech/hotword_factory.py @@ -494,8 +494,8 @@ def initialize(): )) instance = None except PreciseUnavailable: - LOG.warning('Settings prevent Precise Engine use, - falling back to default.') + LOG.warning('Settings prevent Precise Engine use, ' + 'falling back to default.') instance = None except Exception: LOG.exception( From 9c09f1739b2c581fc385b5a2145369f6194d9334 Mon Sep 17 00:00:00 2001 From: mathmauney Date: Thu, 29 Aug 2019 11:28:36 -0400 Subject: [PATCH 22/68] Edit system .conf and make if needed --- dev_setup.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dev_setup.sh b/dev_setup.sh index dda498595936..a9c7b3211245 100755 --- a/dev_setup.sh +++ b/dev_setup.sh @@ -184,7 +184,13 @@ in mycroft.conf. Y)es, I want to use the PocketSphinx engine or my own. N)o, stop the installation." if get_YN ; then - sed -i "s/\"use_precise\": true/\"use_precise\": false/" config/ + if [[ ! -f /etc/mycroft/mycroft.conf ]]; then + if [[ ! -e /etc/mycroft/ ]]; then + $SUDO mkdir /etc/mycroft + fi + $SUDO cp $TOP/mycroft/configuration/mycroft.conf /etc/mycroft/ + fi + sed -i "s/\"use_precise\": true/\"use_precise\": false/" /etc/mycroft/mycroft.conf else echo -e "$HIGHLIGHT N - quit the installation $RESET" exit 1 From e846d97a244bcf3ff3863e382b97409c64184559 Mon Sep 17 00:00:00 2001 From: mathmauney Date: Thu, 29 Aug 2019 11:40:33 -0400 Subject: [PATCH 23/68] Sed needs sudo --- dev_setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_setup.sh b/dev_setup.sh index a9c7b3211245..92ee65ae6fcc 100755 --- a/dev_setup.sh +++ b/dev_setup.sh @@ -190,7 +190,7 @@ in mycroft.conf. fi $SUDO cp $TOP/mycroft/configuration/mycroft.conf /etc/mycroft/ fi - sed -i "s/\"use_precise\": true/\"use_precise\": false/" /etc/mycroft/mycroft.conf + $SUDO sed -i "s/\"use_precise\": true/\"use_precise\": false/" /etc/mycroft/mycroft.conf else echo -e "$HIGHLIGHT N - quit the installation $RESET" exit 1 From 771b8aa36b89882e485d1fefe521a9c0e2c24b02 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Wed, 26 May 2021 19:10:42 +0930 Subject: [PATCH 24/68] Improve writing of config under different conditions --- dev_setup.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dev_setup.sh b/dev_setup.sh index 92ee65ae6fcc..b9ba0ed4d627 100755 --- a/dev_setup.sh +++ b/dev_setup.sh @@ -185,16 +185,18 @@ in mycroft.conf. N)o, stop the installation." if get_YN ; then if [[ ! -f /etc/mycroft/mycroft.conf ]]; then - if [[ ! -e /etc/mycroft/ ]]; then - $SUDO mkdir /etc/mycroft - fi - $SUDO cp $TOP/mycroft/configuration/mycroft.conf /etc/mycroft/ + $SUDO mkdir -p /etc/mycroft + $SUDO touch /etc/mycroft/mycroft.conf + $SUDO bash -c 'echo "{ \"use_precise\": true }" > /etc/mycroft/mycroft.conf' + else + $SUDO bash -c 'jq ". + { \"use_precise\": true }" /etc/mycroft/mycroft.conf > tmp.mycroft.conf' + $SUDO mv -f tmp.mycroft.conf /etc/mycroft/mycroft.conf fi - $SUDO sed -i "s/\"use_precise\": true/\"use_precise\": false/" /etc/mycroft/mycroft.conf else echo -e "$HIGHLIGHT N - quit the installation $RESET" exit 1 fi + echo fi echo " Do you want to run on 'master' or against a dev branch? Unless you are From ff2db272b88d31ad3c53f1aea12f286676ec5b11 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Mon, 31 May 2021 08:19:18 +0930 Subject: [PATCH 25/68] Add comment explaining no check for AVX on ARM --- dev_setup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev_setup.sh b/dev_setup.sh index b9ba0ed4d627..3bbfabc2470c 100755 --- a/dev_setup.sh +++ b/dev_setup.sh @@ -174,6 +174,8 @@ This script is designed to make working with Mycroft easy. During this first run of dev_setup we will ask you a few questions to help setup your environment.' sleep 0.5 + # The AVX instruction set is an x86 construct + # ARM has a range of equivalents, unsure which are (un)supported by TF. if ! grep -q avx /proc/cpuinfo && [[ ! $(uname -m) == 'arm'* ]]; then echo " The Precise Wake Word Engine requires the AVX instruction set, which is From 2a9f63c1730f95c10fe2bf85bb2d5bc5afc4c91a Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Sat, 5 Jun 2021 12:15:02 -0500 Subject: [PATCH 26/68] Fixed a bug where the highest confidence from the Adapt parser is different than the highest confidence from the Adapt intent matcher. --- mycroft/skills/intent_services/adapt_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mycroft/skills/intent_services/adapt_service.py b/mycroft/skills/intent_services/adapt_service.py index 4148efbb21ec..a74dd6b97b86 100644 --- a/mycroft/skills/intent_services/adapt_service.py +++ b/mycroft/skills/intent_services/adapt_service.py @@ -227,7 +227,8 @@ def take_best(intent, utt): include_tags=True, context_manager=self.context_manager)] if intents: - take_best(intents[0], utt_tup[0]) + utt_best = max(intents, key=lambda x: x.get('confidence', 0.0)) + take_best(utt_best, utt_tup[0]) except Exception as err: LOG.exception(err) From 4d7ed0f25ecd468838b3fabbf21e1e3880c6a1c8 Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Sat, 5 Jun 2021 13:39:57 -0500 Subject: [PATCH 27/68] Added to docstring to explain why the method took a list of utterances instead of a single utterance. --- mycroft/skills/intent_services/adapt_service.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mycroft/skills/intent_services/adapt_service.py b/mycroft/skills/intent_services/adapt_service.py index a74dd6b97b86..9025d2b775a6 100644 --- a/mycroft/skills/intent_services/adapt_service.py +++ b/mycroft/skills/intent_services/adapt_service.py @@ -202,8 +202,12 @@ def match_intent(self, utterances, _=None, __=None): """Run the Adapt engine to search for an matching intent. Args: - utterances (iterable): iterable of utterances, expected order - [raw, normalized, other] + utterances (iterable): utterances for consideration in intent + matching. As a practical matter, a single utterance will be + passed in most cases. But there are instances, such as + streaming STT that could pass multiple. Each utterance + is represented as a tuple containing the raw, normalized, and + possibly other variations of the utterance. Returns: Intent structure, or None if no match was found. @@ -227,7 +231,9 @@ def take_best(intent, utt): include_tags=True, context_manager=self.context_manager)] if intents: - utt_best = max(intents, key=lambda x: x.get('confidence', 0.0)) + utt_best = max( + intents, key=lambda x: x.get('confidence', 0.0) + ) take_best(utt_best, utt_tup[0]) except Exception as err: From dd13d97d15cfa432a7cb5d7e387e248ca3eae49f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jun 2021 19:22:19 +0000 Subject: [PATCH 28/68] Bump pillow from 8.1.1 to 8.2.0 Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.1.1 to 8.2.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/8.1.1...8.2.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 5b024132fb72..55951a7fc990 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -10,7 +10,7 @@ pyserial==3.0 psutil==5.6.6 pocketsphinx==0.1.0 inflection==0.3.1 -pillow==8.1.1 +pillow==8.2.0 python-dateutil==2.6.0 fasteners==0.14.1 PyYAML==5.4 From 5d3393b935c8e15c8bd0cc3165cbae3858a16250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Wed, 28 Apr 2021 22:45:20 +0200 Subject: [PATCH 29/68] Make TTSCahce safer Add __contains__ method to TTSCache, The cache contains a SHA if the SHA is known and all expected files exists on disk. This is handles unexpected system events in a more consistent manner and will still be fast for the case where a new sentence needs to be synthetisized. --- mycroft/tts/cache.py | 16 ++++++++++++++++ mycroft/tts/tts.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/mycroft/tts/cache.py b/mycroft/tts/cache.py index 7cff5d457d22..2f7402ce8efb 100644 --- a/mycroft/tts/cache.py +++ b/mycroft/tts/cache.py @@ -102,6 +102,9 @@ def save(self, audio: bytes): except Exception: LOG.exception("Failed to write {} to cache".format(self.name)) + def exists(self): + return self.path.exists() + class PhonemeFile: def __init__(self, cache_dir: Path, sentence_hash: str): @@ -133,6 +136,9 @@ def save(self, phonemes): except Exception: LOG.exception("Failed to write {} to cache".format(self.name)) + def exists(self): + return self.path.exists() + class TextToSpeechCache: """Class for all persistent and temporary caching operations.""" @@ -156,6 +162,16 @@ def __init__(self, tts_config, tts_name, audio_file_type): str(self.temporary_cache_dir), permissions=0o755 ) + def __contains__(self, sha): + """The cache contains a SHA if it knows of it and it exists on disk.""" + if sha not in self.cached_sentences: + return False # Doesn't know of it + else: + # Audio file must exist, phonemes are optional. + audio, phonemes = self.cached_sentences[sha] + return (audio.exists() and + (phonemes is None or phonemes.exists())) + def load_persistent_cache(self): """Load the contents of dialog files to the persistent cache directory. diff --git a/mycroft/tts/tts.py b/mycroft/tts/tts.py index a3f878dd0172..8b894f8e04bd 100644 --- a/mycroft/tts/tts.py +++ b/mycroft/tts/tts.py @@ -355,7 +355,7 @@ def _execute(self, sentence, ident, listen): for sentence, l in chunks: sentence_hash = hash_sentence(sentence) - if sentence_hash in self.cache.cached_sentences: + if sentence_hash in self.cache: audio_file, phoneme_file = self._get_sentence_from_cache( sentence_hash ) From 4799caa696344eb87c72f5732baeb645d0545102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Thu, 10 Jun 2021 07:37:06 +0200 Subject: [PATCH 30/68] Add test cases for tts cache __contains__ --- test/unittests/tts/test_cache.py | 50 +++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/test/unittests/tts/test_cache.py b/test/unittests/tts/test_cache.py index f03753e7f897..4db5ebbf3cdf 100644 --- a/test/unittests/tts/test_cache.py +++ b/test/unittests/tts/test_cache.py @@ -2,7 +2,7 @@ from pathlib import Path from tempfile import mkdtemp from unittest import TestCase -from unittest.mock import MagicMock, patch +from unittest.mock import Mock, MagicMock, patch from mycroft.tts.cache import hash_sentence, TextToSpeechCache @@ -121,3 +121,51 @@ def test_curate_cache(self, curate_mock): # dict of hashes. self.assertEqual(tts_cache.cached_sentences, {'fozzie': (files['fozzie'], None)}) + + +class MockFile(Mock): + def __init__(self, exists, *args, **kwargs): + super().__init__(args, kwargs) + self._exists = exists + + def exists(self): + return self._exists + + +class TestCacheContains(TestCase): + """Verify the `"X" in tts_cache` functionality.""" + def setUp(self): + self.cache_dir = Path(mkdtemp()) + self.tts_cache = TextToSpeechCache( + tts_config=dict(preloaded_cache=self.cache_dir), + tts_name="Test", + audio_file_type="wav" + ) + files = { + 'kermit': (MockFile(exists=True), MockFile(exists=True)), + 'fozzie': (MockFile(exists=True), None), + 'gobo': (MockFile(exists=False), None), + 'piggy': (MockFile(exists=True), MockFile(exists=False)) + } + for sentence_hash, (audio_file, phoneme_file) in files.items(): + self.tts_cache.cached_sentences[sentence_hash] = ( + audio_file, phoneme_file + ) + + def tearDown(self): + for file_path in self.cache_dir.iterdir(): + file_path.unlink() + self.cache_dir.rmdir() + + def test_hash_not_listed(self): + self.assertFalse('animal' in self.tts_cache) + + def test_hash_exists_and_files_ok(self): + self.assertTrue('kermit' in self.tts_cache) + + def test_only_audio_hash_exists_and_files_ok(self): + self.assertTrue('fozzie' in self.tts_cache) + + def test_hash_exists_and_files_bad(self): + self.assertFalse('piggy' in self.tts_cache) + self.assertFalse('gobo' in self.tts_cache) From 63e7b6083707064181836403fd82c0204e0a7473 Mon Sep 17 00:00:00 2001 From: el-tocino Date: Fri, 18 Jun 2021 00:52:52 -0500 Subject: [PATCH 31/68] Update start-mycroft.sh --- start-mycroft.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start-mycroft.sh b/start-mycroft.sh index a84b0beadd71..4c6e453a3a50 100755 --- a/start-mycroft.sh +++ b/start-mycroft.sh @@ -190,7 +190,7 @@ if [[ "${1}" == "restart" ]] || [[ "${_opt}" == "restart" ]] ; then fi _params=$@ -if ! grep -i cli <<< "${_params}" ; then +if [[ ! "${_opt}" == "cli" ]] ; then check-dependencies fi From a211441acc1799023d33c2787ac8ae879148fcfa Mon Sep 17 00:00:00 2001 From: ChanceNCounter Date: Wed, 23 Jun 2021 21:56:19 -0700 Subject: [PATCH 32/68] stop passing lang=None to Lingua Franca LF's only breaking change over the past two versions has been the deprecation of `lang=None` as a valid parameter. This is because the new language loading paradigm wants to load certain functions on the fly, which it cannot do when it is explicitly told to look for a null lang. I've addressed this by passing `lingua_franca.get_default_lang()` where the `lang=None` call remained. Bonus: Gets rid of over 200 DeprecationWarnings in unit tests! --- mycroft/util/format.py | 2 ++ mycroft/util/parse.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/mycroft/util/format.py b/mycroft/util/format.py index b37bf8fa453c..de2e52073a7d 100644 --- a/mycroft/util/format.py +++ b/mycroft/util/format.py @@ -31,6 +31,7 @@ from enum import Enum # These are the main functions we are using lingua franca to provide +from lingua_franca import get_default_loc # TODO 21.08 - move nice_duration methods to Lingua Franca. from lingua_franca.format import ( join_list, @@ -104,6 +105,7 @@ def _duration_handler(time1, lang=None, speech=True, *, time2=None, Returns: str: timespan as a string """ + lang = lang or get_default_loc() _leapdays = 0 _input_resolution = resolution milliseconds = 0 diff --git a/mycroft/util/parse.py b/mycroft/util/parse.py index cbc20c27bcd7..8feaeff513ed 100644 --- a/mycroft/util/parse.py +++ b/mycroft/util/parse.py @@ -30,6 +30,7 @@ from difflib import SequenceMatcher from warnings import warn +from lingua_franca import get_default_loc from lingua_franca.parse import ( extract_duration, extract_number, @@ -115,4 +116,7 @@ def extract_datetime(text, anchorDate="DEFAULT", lang=None, "deprecated. This parameter can be omitted.")) if anchorDate is None or anchorDate == "DEFAULT": anchorDate = now_local() - return _extract_datetime(text, anchorDate, lang, default_time) + return _extract_datetime(text, + anchorDate, + lang or get_default_loc(), + default_time) From 21eabceb29bde0408f72d3f4b921371969b70aca Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Fri, 25 Jun 2021 14:26:14 +0930 Subject: [PATCH 33/68] Do not create a 'None' cache directory The persistent_cache_dir was assigned None if no preloaded_cache existed. This was then created as a directory when it's clearly not needed. --- mycroft/tts/cache.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mycroft/tts/cache.py b/mycroft/tts/cache.py index 2f7402ce8efb..572b3595dcd3 100644 --- a/mycroft/tts/cache.py +++ b/mycroft/tts/cache.py @@ -147,20 +147,20 @@ def __init__(self, tts_config, tts_name, audio_file_type): self.tts_name = tts_name if "preloaded_cache" in self.config: self.persistent_cache_dir = Path(self.config["preloaded_cache"]) + ensure_directory_exists( + str(self.persistent_cache_dir), permissions=0o755 + ) else: self.persistent_cache_dir = None self.temporary_cache_dir = Path( get_cache_directory("tts/" + tts_name) ) - self.audio_file_type = audio_file_type - self.resource_dir = Path(__file__).parent.parent.joinpath("res") - self.cached_sentences = dict() - ensure_directory_exists( - str(self.persistent_cache_dir), permissions=0o755 - ) ensure_directory_exists( str(self.temporary_cache_dir), permissions=0o755 ) + self.audio_file_type = audio_file_type + self.resource_dir = Path(__file__).parent.parent.joinpath("res") + self.cached_sentences = dict() def __contains__(self, sha): """The cache contains a SHA if it knows of it and it exists on disk.""" From cfe03a4285ff5d81088fc99d2fb0c8d71a1584d8 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Mon, 28 Jun 2021 16:55:03 +0930 Subject: [PATCH 34/68] Remove docs encouraging Skill config via mycroft.conf --- README.md | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 01958ef2c736..5e620404687f 100644 --- a/README.md +++ b/README.md @@ -99,28 +99,18 @@ If you do not wish to use the Mycroft Home service, before starting Mycroft for } ``` -Mycroft will then be unable to perform speech-to-text conversion, so you'll need to set that up as well, using one of the [STT engines Mycroft supports](https://mycroft-ai.gitbook.io/docs/using-mycroft-ai/customizations/stt-engine). - -You may insert your own API keys into the configuration files listed above in Configuration. For example, to insert the API key for the Weather skill, create a new JSON key in the configuration file like so: - -``` -{ - // other configuration settings... - // - "WeatherSkill": { - "api_key": "" - } -} -``` - ### API Key Services -These are the keys currently used in Mycroft Core: +The Mycroft backend provides access to a range of API keys for specific services. Without pairing with the Mycroft backend, you will need to add your own API keys, install a different Skill or Plugin to perform that function, or not have access to that functionality. + +These are the keys currently used in Mycroft Core through the Mycroft backend: - [STT API, Google STT, Google Cloud Speech](http://www.chromium.org/developers/how-tos/api-keys) + - [A range of STT services](https://mycroft-ai.gitbook.io/docs/using-mycroft-ai/customizations/stt-engine) are available for use with Mycroft. - [Weather Skill API, OpenWeatherMap](http://openweathermap.org/api) - [Wolfram-Alpha Skill](http://products.wolframalpha.com/api/) + ### Using Mycroft behind a proxy Many schools, universities and workplaces run a `proxy` on their network. If you need to type in a username and password to access the external internet, then you are likely behind a `proxy`. From 0394568704479a600a576a4f596e1df41eca0293 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Mon, 28 Jun 2021 11:54:39 +0930 Subject: [PATCH 35/68] Use returned TTS audio file path This reverts an unintentional breaking change. A TTS engine may return a different file path than was requested. This again uses the returned path but adds a deprecation warning that this behaviour will no longer be supported in an upcoming release. Fixes #2929 --- mycroft/tts/tts.py | 14 +++++++++++++- test/unittests/tts/test_tts.py | 7 +++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/mycroft/tts/tts.py b/mycroft/tts/tts.py index 8b894f8e04bd..1360c1cb3647 100644 --- a/mycroft/tts/tts.py +++ b/mycroft/tts/tts.py @@ -19,6 +19,7 @@ from abc import ABCMeta, abstractmethod from threading import Thread from time import time +from warnings import warn import os.path from os.path import dirname, exists, isdir, join @@ -370,7 +371,18 @@ def _execute(self, sentence, ident, listen): # of the TTS cache. But this requires changing the public # API of the get_tts method in each engine. audio_file = self.cache.define_audio_file(sentence_hash) - _, phonemes = self.get_tts(sentence, str(audio_file.path)) + # TODO 21.08: remove mutation of audio_file.path. + returned_file, phonemes = self.get_tts( + sentence, str(audio_file.path)) + if returned_file.path != audio_file.path: + warn( + DeprecationWarning( + f"{self.tts_name} is saving files " + "to a different path than requested. If you are " + "the maintainer of this plugin, please adhere to " + "the file path argument provided. Modified paths " + "will be ignored in a future release.")) + audio_file = returned_file if phonemes: phoneme_file = self.cache.define_phoneme_file( sentence_hash diff --git a/test/unittests/tts/test_tts.py b/test/unittests/tts/test_tts.py index 2f4b562ccab9..35577be30a1e 100644 --- a/test/unittests/tts/test_tts.py +++ b/test/unittests/tts/test_tts.py @@ -96,11 +96,14 @@ def test_execute(self, mock_playback_thread): with mock.patch('mycroft.tts.tts.open') as mock_open: tts.cache.temporary_cache_dir = Path('/tmp/dummy') tts.execute('Oh no, not again', 42) - self.assertTrue(tts.get_tts.called) + tts.get_tts.assert_called_with( + 'Oh no, not again', + '/tmp/dummy/8da7f22aeb16bc3846ad07b644d59359.wav' + ) tts.queue.put.assert_called_with( ( 'wav', - '/tmp/dummy/8da7f22aeb16bc3846ad07b644d59359.wav', + str(mock_audio.path), mock_viseme, 42, False From 395926772699888a1a3a2f68442d7f89962385b4 Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Thu, 1 Jul 2021 22:31:22 -0500 Subject: [PATCH 36/68] Reordered wait_while_speaking and sleep to fix a race condition that was occurring with the converse logic in MycroftSkill.get_response() --- .../voight_kampff/features/steps/utterance_responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integrationtests/voight_kampff/features/steps/utterance_responses.py b/test/integrationtests/voight_kampff/features/steps/utterance_responses.py index de4197e1741b..b0d118a3284c 100644 --- a/test/integrationtests/voight_kampff/features/steps/utterance_responses.py +++ b/test/integrationtests/voight_kampff/features/steps/utterance_responses.py @@ -235,8 +235,8 @@ def check_contains(message): @then('the user replies "{text}"') @then('the user says "{text}"') def then_user_follow_up(context, text): - time.sleep(2) wait_while_speaking() + time.sleep(2) context.bus.emit(Message('recognizer_loop:utterance', data={'utterances': [text], 'lang': context.lang, From 0ae02b5939bd7ed8d110561f76e528e8d8c4006c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Fri, 2 Jul 2021 08:08:30 +0200 Subject: [PATCH 37/68] Fix TTS using the returned path --- mycroft/tts/tts.py | 7 +++++-- test/unittests/tts/test_tts.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/mycroft/tts/tts.py b/mycroft/tts/tts.py index 1360c1cb3647..fc79e91dbd34 100644 --- a/mycroft/tts/tts.py +++ b/mycroft/tts/tts.py @@ -17,6 +17,7 @@ import random import re from abc import ABCMeta, abstractmethod +from pathlib import Path from threading import Thread from time import time from warnings import warn @@ -374,7 +375,9 @@ def _execute(self, sentence, ident, listen): # TODO 21.08: remove mutation of audio_file.path. returned_file, phonemes = self.get_tts( sentence, str(audio_file.path)) - if returned_file.path != audio_file.path: + # Convert to Path as needed + returned_file = Path(returned_file) + if returned_file != audio_file.path: warn( DeprecationWarning( f"{self.tts_name} is saving files " @@ -382,7 +385,7 @@ def _execute(self, sentence, ident, listen): "the maintainer of this plugin, please adhere to " "the file path argument provided. Modified paths " "will be ignored in a future release.")) - audio_file = returned_file + audio_file.path = returned_file if phonemes: phoneme_file = self.cache.define_phoneme_file( sentence_hash diff --git a/test/unittests/tts/test_tts.py b/test/unittests/tts/test_tts.py index 35577be30a1e..dd711aeeda7e 100644 --- a/test/unittests/tts/test_tts.py +++ b/test/unittests/tts/test_tts.py @@ -8,7 +8,7 @@ import mycroft.tts mock_phoneme = mock.Mock(name='phoneme') -mock_audio = mock.Mock(name='audio') +mock_audio = "/tmp/mock_path" mock_viseme = mock.Mock(name='viseme') @@ -103,7 +103,32 @@ def test_execute(self, mock_playback_thread): tts.queue.put.assert_called_with( ( 'wav', - str(mock_audio.path), + mock_audio, + mock_viseme, + 42, + False + ) + ) + + def test_execute_path_returned(self, mock_playback_thread): + tts = MockTTS("en-US", {}, MockTTSValidator(None)) + tts.get_tts.return_value = (Path(mock_audio), mock_viseme) + bus_mock = mock.Mock() + tts.init(bus_mock) + self.assertTrue(tts.bus is bus_mock) + + tts.queue = mock.Mock() + with mock.patch('mycroft.tts.tts.open') as mock_open: + tts.cache.temporary_cache_dir = Path('/tmp/dummy') + tts.execute('Oh no, not again', 42) + tts.get_tts.assert_called_with( + 'Oh no, not again', + '/tmp/dummy/8da7f22aeb16bc3846ad07b644d59359.wav' + ) + tts.queue.put.assert_called_with( + ( + 'wav', + mock_audio, mock_viseme, 42, False From 28c52fef756eea25f886a249820e20c7cff6464f Mon Sep 17 00:00:00 2001 From: HKalbasi <45197576+HKalbasi@users.noreply.github.com> Date: Mon, 5 Jul 2021 05:32:15 +0430 Subject: [PATCH 38/68] fa-ir localization initialized (#2778) ==== Localization Notes ==== fa-ir: 35 strings added --- mycroft/res/text/fa-ir/and.word | 1 + mycroft/res/text/fa-ir/backend.down.dialog | 4 ++++ mycroft/res/text/fa-ir/cancel.voc | 3 +++ mycroft/res/text/fa-ir/checking for updates.dialog | 2 ++ mycroft/res/text/fa-ir/day.word | 1 + mycroft/res/text/fa-ir/days.word | 1 + mycroft/res/text/fa-ir/hour.word | 1 + mycroft/res/text/fa-ir/hours.word | 1 + mycroft/res/text/fa-ir/i didn't catch that.dialog | 4 ++++ mycroft/res/text/fa-ir/last.voc | 3 +++ mycroft/res/text/fa-ir/learning disabled.dialog | 1 + mycroft/res/text/fa-ir/learning enabled.dialog | 1 + mycroft/res/text/fa-ir/message_loading.skills.dialog | 1 + mycroft/res/text/fa-ir/message_rebooting.dialog | 1 + mycroft/res/text/fa-ir/message_synching.clock.dialog | 1 + mycroft/res/text/fa-ir/message_updating.dialog | 1 + mycroft/res/text/fa-ir/minute.word | 1 + mycroft/res/text/fa-ir/minutes.word | 1 + mycroft/res/text/fa-ir/mycroft.intro.dialog | 1 + mycroft/res/text/fa-ir/no.voc | 4 ++++ mycroft/res/text/fa-ir/not connected to the internet.dialog | 4 ++++ mycroft/res/text/fa-ir/not.loaded.dialog | 1 + mycroft/res/text/fa-ir/or.word | 1 + mycroft/res/text/fa-ir/phonetic_spellings.txt | 5 +++++ mycroft/res/text/fa-ir/reset to factory defaults.dialog | 1 + mycroft/res/text/fa-ir/second.word | 1 + mycroft/res/text/fa-ir/seconds.word | 1 + mycroft/res/text/fa-ir/skill.error.dialog | 1 + mycroft/res/text/fa-ir/skills updated.dialog | 2 ++ .../fa-ir/sorry I couldn't install default skills.dialog | 1 + mycroft/res/text/fa-ir/ssh disabled.dialog | 1 + mycroft/res/text/fa-ir/ssh enabled.dialog | 1 + mycroft/res/text/fa-ir/time.changed.reboot.dialog | 1 + mycroft/res/text/fa-ir/yes.voc | 5 +++++ 34 files changed, 60 insertions(+) create mode 100644 mycroft/res/text/fa-ir/and.word create mode 100644 mycroft/res/text/fa-ir/backend.down.dialog create mode 100644 mycroft/res/text/fa-ir/cancel.voc create mode 100644 mycroft/res/text/fa-ir/checking for updates.dialog create mode 100644 mycroft/res/text/fa-ir/day.word create mode 100644 mycroft/res/text/fa-ir/days.word create mode 100644 mycroft/res/text/fa-ir/hour.word create mode 100644 mycroft/res/text/fa-ir/hours.word create mode 100644 mycroft/res/text/fa-ir/i didn't catch that.dialog create mode 100644 mycroft/res/text/fa-ir/last.voc create mode 100644 mycroft/res/text/fa-ir/learning disabled.dialog create mode 100644 mycroft/res/text/fa-ir/learning enabled.dialog create mode 100644 mycroft/res/text/fa-ir/message_loading.skills.dialog create mode 100644 mycroft/res/text/fa-ir/message_rebooting.dialog create mode 100644 mycroft/res/text/fa-ir/message_synching.clock.dialog create mode 100644 mycroft/res/text/fa-ir/message_updating.dialog create mode 100644 mycroft/res/text/fa-ir/minute.word create mode 100644 mycroft/res/text/fa-ir/minutes.word create mode 100644 mycroft/res/text/fa-ir/mycroft.intro.dialog create mode 100644 mycroft/res/text/fa-ir/no.voc create mode 100644 mycroft/res/text/fa-ir/not connected to the internet.dialog create mode 100644 mycroft/res/text/fa-ir/not.loaded.dialog create mode 100644 mycroft/res/text/fa-ir/or.word create mode 100644 mycroft/res/text/fa-ir/phonetic_spellings.txt create mode 100644 mycroft/res/text/fa-ir/reset to factory defaults.dialog create mode 100644 mycroft/res/text/fa-ir/second.word create mode 100644 mycroft/res/text/fa-ir/seconds.word create mode 100644 mycroft/res/text/fa-ir/skill.error.dialog create mode 100644 mycroft/res/text/fa-ir/skills updated.dialog create mode 100644 mycroft/res/text/fa-ir/sorry I couldn't install default skills.dialog create mode 100644 mycroft/res/text/fa-ir/ssh disabled.dialog create mode 100644 mycroft/res/text/fa-ir/ssh enabled.dialog create mode 100644 mycroft/res/text/fa-ir/time.changed.reboot.dialog create mode 100644 mycroft/res/text/fa-ir/yes.voc diff --git a/mycroft/res/text/fa-ir/and.word b/mycroft/res/text/fa-ir/and.word new file mode 100644 index 000000000000..438fc2d5c604 --- /dev/null +++ b/mycroft/res/text/fa-ir/and.word @@ -0,0 +1 @@ +و \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/backend.down.dialog b/mycroft/res/text/fa-ir/backend.down.dialog new file mode 100644 index 000000000000..f8a3ed6bfe97 --- /dev/null +++ b/mycroft/res/text/fa-ir/backend.down.dialog @@ -0,0 +1,4 @@ +من در برقراریِ ارتباط با سِروِر مشکلاتی دارم. لطفا به من چند دقیقه زمان بده قبل از این که با من حرف بزنی. +من در برقراریِ ارتباط با سِروِر مشکلاتی دارم. لطفا چند دقیقه صبر کن قبل از این که با من حرف بزنی. +به نظر می رِسه که نمی تونم به سِروِر وصل شم. لطفا به من چند دقیقه زمان بده قبل از این که با من حرف بزنی. +به نظر می رِسه که نمی تونم به سِروِر وصل شم. لطفا چند دقیقه صبر کن قبل از این که با من حرف بزنی. \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/cancel.voc b/mycroft/res/text/fa-ir/cancel.voc new file mode 100644 index 000000000000..8c2cd7d017d9 --- /dev/null +++ b/mycroft/res/text/fa-ir/cancel.voc @@ -0,0 +1,3 @@ +کنسله +بیخیال +فراموشِش کن diff --git a/mycroft/res/text/fa-ir/checking for updates.dialog b/mycroft/res/text/fa-ir/checking for updates.dialog new file mode 100644 index 000000000000..a5c04f1703b4 --- /dev/null +++ b/mycroft/res/text/fa-ir/checking for updates.dialog @@ -0,0 +1,2 @@ +چک کردنِ آپدیت ها +یه لحظه صبر کن تا خودمو آپدیت کنم diff --git a/mycroft/res/text/fa-ir/day.word b/mycroft/res/text/fa-ir/day.word new file mode 100644 index 000000000000..dfc15b79dda3 --- /dev/null +++ b/mycroft/res/text/fa-ir/day.word @@ -0,0 +1 @@ +روز \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/days.word b/mycroft/res/text/fa-ir/days.word new file mode 100644 index 000000000000..24e107a87799 --- /dev/null +++ b/mycroft/res/text/fa-ir/days.word @@ -0,0 +1 @@ +روز ها \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/hour.word b/mycroft/res/text/fa-ir/hour.word new file mode 100644 index 000000000000..3f2b7b1625b0 --- /dev/null +++ b/mycroft/res/text/fa-ir/hour.word @@ -0,0 +1 @@ +ساعت \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/hours.word b/mycroft/res/text/fa-ir/hours.word new file mode 100644 index 000000000000..38aa65010dad --- /dev/null +++ b/mycroft/res/text/fa-ir/hours.word @@ -0,0 +1 @@ +ساعت ها \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/i didn't catch that.dialog b/mycroft/res/text/fa-ir/i didn't catch that.dialog new file mode 100644 index 000000000000..505ebdad5160 --- /dev/null +++ b/mycroft/res/text/fa-ir/i didn't catch that.dialog @@ -0,0 +1,4 @@ +ببخشید، متوجه نشدم +می ترسم متوجه نشده باشم +می تونی دوباره بگی؟ +می تونی تکرارش کنی؟ diff --git a/mycroft/res/text/fa-ir/last.voc b/mycroft/res/text/fa-ir/last.voc new file mode 100644 index 000000000000..d7c93164e55f --- /dev/null +++ b/mycroft/res/text/fa-ir/last.voc @@ -0,0 +1,3 @@ +موردِ آخر +گزینه ی آخر +آخری diff --git a/mycroft/res/text/fa-ir/learning disabled.dialog b/mycroft/res/text/fa-ir/learning disabled.dialog new file mode 100644 index 000000000000..f598d45fab55 --- /dev/null +++ b/mycroft/res/text/fa-ir/learning disabled.dialog @@ -0,0 +1 @@ +اطلاعات دیگه به شرکتِ مایکرافت فرستاده نمی شِه. diff --git a/mycroft/res/text/fa-ir/learning enabled.dialog b/mycroft/res/text/fa-ir/learning enabled.dialog new file mode 100644 index 000000000000..9a74482831d6 --- /dev/null +++ b/mycroft/res/text/fa-ir/learning enabled.dialog @@ -0,0 +1 @@ +از این به بعد من اطلاعاتِ تعاملی رو به شرکتِ مایکرافت می فرستم تا باهوش تر بشم. در حالِ حاضر این اطلاعات شامل کلمه بیدار شدن می‌شِه. diff --git a/mycroft/res/text/fa-ir/message_loading.skills.dialog b/mycroft/res/text/fa-ir/message_loading.skills.dialog new file mode 100644 index 000000000000..24aa713302bc --- /dev/null +++ b/mycroft/res/text/fa-ir/message_loading.skills.dialog @@ -0,0 +1 @@ +< < < بارگیری < < < \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/message_rebooting.dialog b/mycroft/res/text/fa-ir/message_rebooting.dialog new file mode 100644 index 000000000000..4d5303543dea --- /dev/null +++ b/mycroft/res/text/fa-ir/message_rebooting.dialog @@ -0,0 +1 @@ +در حال ری استارت شدن... \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/message_synching.clock.dialog b/mycroft/res/text/fa-ir/message_synching.clock.dialog new file mode 100644 index 000000000000..b140f1b6316f --- /dev/null +++ b/mycroft/res/text/fa-ir/message_synching.clock.dialog @@ -0,0 +1 @@ +< < < همگام سازی < < < \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/message_updating.dialog b/mycroft/res/text/fa-ir/message_updating.dialog new file mode 100644 index 000000000000..aca660696cb9 --- /dev/null +++ b/mycroft/res/text/fa-ir/message_updating.dialog @@ -0,0 +1 @@ +< < < به روز رسانی < < < \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/minute.word b/mycroft/res/text/fa-ir/minute.word new file mode 100644 index 000000000000..1e9a05d81d7e --- /dev/null +++ b/mycroft/res/text/fa-ir/minute.word @@ -0,0 +1 @@ +دقیقه \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/minutes.word b/mycroft/res/text/fa-ir/minutes.word new file mode 100644 index 000000000000..63ff81502e52 --- /dev/null +++ b/mycroft/res/text/fa-ir/minutes.word @@ -0,0 +1 @@ +دقیقه ها \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/mycroft.intro.dialog b/mycroft/res/text/fa-ir/mycroft.intro.dialog new file mode 100644 index 000000000000..8f4884920966 --- /dev/null +++ b/mycroft/res/text/fa-ir/mycroft.intro.dialog @@ -0,0 +1 @@ +سلام من مایکرافت هستم، دستیارِ جدیدِ شما. برای کمک به شما من نیاز دارم که به اینترنت متصل بشم. شما می تونید که کابلِ شبکه رو به من وصل کنید یا از وای فای استفاده کنید. برای استفاده از وای فای این دستور العمل رو دنبال کنید: \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/no.voc b/mycroft/res/text/fa-ir/no.voc new file mode 100644 index 000000000000..531017a2d648 --- /dev/null +++ b/mycroft/res/text/fa-ir/no.voc @@ -0,0 +1,4 @@ +نه +خیر +نوچ +منفی \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/not connected to the internet.dialog b/mycroft/res/text/fa-ir/not connected to the internet.dialog new file mode 100644 index 000000000000..c1b69e653f6c --- /dev/null +++ b/mycroft/res/text/fa-ir/not connected to the internet.dialog @@ -0,0 +1,4 @@ +من نمی تونم به اینترنت وصل شم. لطفا اتصالِ اینترنت رو بررسی کن. +من نمی تونم به اینترنت وصل شم. لطفا اتصالِ اینترنت رو بررسی کن. +من نمی تونم به اینترنت وصل شم. لطفا اتصالِ اینترنت رو بررسی کن. +من نمی تونم به اینترنت وصل شم. لطفا اتصالِ اینترنت رو بررسی کن. diff --git a/mycroft/res/text/fa-ir/not.loaded.dialog b/mycroft/res/text/fa-ir/not.loaded.dialog new file mode 100644 index 000000000000..4beb1398d0e8 --- /dev/null +++ b/mycroft/res/text/fa-ir/not.loaded.dialog @@ -0,0 +1 @@ +چند لحظه صبر کن تا کاملا روشن بشم. diff --git a/mycroft/res/text/fa-ir/or.word b/mycroft/res/text/fa-ir/or.word new file mode 100644 index 000000000000..aa43ee0c9bf9 --- /dev/null +++ b/mycroft/res/text/fa-ir/or.word @@ -0,0 +1 @@ +یا \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/phonetic_spellings.txt b/mycroft/res/text/fa-ir/phonetic_spellings.txt new file mode 100644 index 000000000000..dfa3cb9a674e --- /dev/null +++ b/mycroft/res/text/fa-ir/phonetic_spellings.txt @@ -0,0 +1,5 @@ +jalepeno: hallipeenyo +ai: A.I. +mycroftai: mycroft A.I. +spotify: spot-ify +corgi: core-gee diff --git a/mycroft/res/text/fa-ir/reset to factory defaults.dialog b/mycroft/res/text/fa-ir/reset to factory defaults.dialog new file mode 100644 index 000000000000..82ebde4e4fdf --- /dev/null +++ b/mycroft/res/text/fa-ir/reset to factory defaults.dialog @@ -0,0 +1 @@ +من به تنظیماتِ کارخانه برگشتم. diff --git a/mycroft/res/text/fa-ir/second.word b/mycroft/res/text/fa-ir/second.word new file mode 100644 index 000000000000..3d2bee65fc3c --- /dev/null +++ b/mycroft/res/text/fa-ir/second.word @@ -0,0 +1 @@ +ثانیه \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/seconds.word b/mycroft/res/text/fa-ir/seconds.word new file mode 100644 index 000000000000..a79aaf778679 --- /dev/null +++ b/mycroft/res/text/fa-ir/seconds.word @@ -0,0 +1 @@ +ثانیه ها \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/skill.error.dialog b/mycroft/res/text/fa-ir/skill.error.dialog new file mode 100644 index 000000000000..0c410d93cd4e --- /dev/null +++ b/mycroft/res/text/fa-ir/skill.error.dialog @@ -0,0 +1 @@ +خطایی هنگامِ پردازشِ درخواستی برای {{skill}} رخ داد. \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/skills updated.dialog b/mycroft/res/text/fa-ir/skills updated.dialog new file mode 100644 index 000000000000..6c51a3e0db33 --- /dev/null +++ b/mycroft/res/text/fa-ir/skills updated.dialog @@ -0,0 +1,2 @@ +من به روز شدم. +مهارتم به روز شد. آماده ام که بِهِت کمک کنم. diff --git a/mycroft/res/text/fa-ir/sorry I couldn't install default skills.dialog b/mycroft/res/text/fa-ir/sorry I couldn't install default skills.dialog new file mode 100644 index 000000000000..3aea94b94e68 --- /dev/null +++ b/mycroft/res/text/fa-ir/sorry I couldn't install default skills.dialog @@ -0,0 +1 @@ +خطایی هنگامِ به روز رسانیِ مهارت رخ داد. diff --git a/mycroft/res/text/fa-ir/ssh disabled.dialog b/mycroft/res/text/fa-ir/ssh disabled.dialog new file mode 100644 index 000000000000..106c93761f4f --- /dev/null +++ b/mycroft/res/text/fa-ir/ssh disabled.dialog @@ -0,0 +1 @@ +اس اس اچ غیرفعال شد. \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/ssh enabled.dialog b/mycroft/res/text/fa-ir/ssh enabled.dialog new file mode 100644 index 000000000000..a0623af49abe --- /dev/null +++ b/mycroft/res/text/fa-ir/ssh enabled.dialog @@ -0,0 +1 @@ +اس اس اچ فعال شد. \ No newline at end of file diff --git a/mycroft/res/text/fa-ir/time.changed.reboot.dialog b/mycroft/res/text/fa-ir/time.changed.reboot.dialog new file mode 100644 index 000000000000..cb965ebb0a42 --- /dev/null +++ b/mycroft/res/text/fa-ir/time.changed.reboot.dialog @@ -0,0 +1 @@ +من نیاز دارم که ری استارت بشم بعدِ این که ساعتم با اینترنت همگام شد. منتظر باش. diff --git a/mycroft/res/text/fa-ir/yes.voc b/mycroft/res/text/fa-ir/yes.voc new file mode 100644 index 000000000000..6069193e7012 --- /dev/null +++ b/mycroft/res/text/fa-ir/yes.voc @@ -0,0 +1,5 @@ +بله +چشم +حتما +انجام می شه +اطاعت می شه From d14e6b65d73010fd7b383d15c41c7930d13af64f Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Tue, 6 Jul 2021 12:41:28 +0930 Subject: [PATCH 39/68] Use unique paths for temp file storage When transferring the Allure report and the Mycroft logs to the report host, the zip files were being written to a common directory. In the event that multiple jobs were writing to or reading from this directory at the same time, conflicts could occur. This ensures that both zip files are written to unique paths and cleaned up afterward. --- Jenkinsfile | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index be7ea9d8fa08..a2ae2a18b1f3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -107,13 +107,20 @@ pipeline { sh 'rmdir $HOME/core/$BRANCH_ALIAS' sh ( label: 'Publish Report to Web Server', - script: '''scp allure-report.zip root@157.245.127.234:~; - ssh root@157.245.127.234 "unzip -o ~/allure-report.zip"; + script: ''' + ssh root@157.245.127.234 "mkdir -p ~/allure-reports/core/${BRANCH_ALIAS}"; + scp allure-report.zip root@157.245.127.234:~/allure-reports/core/${BRANCH_ALIAS}; + ssh root@157.245.127.234 "unzip -o ~/allure-reports/core/${BRANCH_ALIAS}/allure-report.zip -d ~/allure-reports/core/${BRANCH_ALIAS}/"; ssh root@157.245.127.234 "rm -rf /var/www/voight-kampff/core/${BRANCH_ALIAS}"; - ssh root@157.245.127.234 "mv allure-report /var/www/voight-kampff/core/${BRANCH_ALIAS}" - scp mycroft-logs.zip root@157.245.127.234:~; + ssh root@157.245.127.234 "mv ~/allure-reports/core/${BRANCH_ALIAS}/allure-report /var/www/voight-kampff/core/${BRANCH_ALIAS}" + ssh root@157.245.127.234 "rm ~/allure-reports/core/${BRANCH_ALIAS}/allure-report.zip"; + ssh root@157.245.127.234 "rmdir ~/allure-reports/core/${BRANCH_ALIAS}"; + ssh root@157.245.127.234 "mkdir -p ~/mycroft-logs/core/${BRANCH_ALIAS}"; + scp mycroft-logs.zip root@157.245.127.234:~/mycroft-logs/core/${BRANCH_ALIAS}/; ssh root@157.245.127.234 "mkdir -p /var/www/voight-kampff/core/${BRANCH_ALIAS}/logs" - ssh root@157.245.127.234 "unzip -oj ~/mycroft-logs.zip -d /var/www/voight-kampff/core/${BRANCH_ALIAS}/logs/"; + ssh root@157.245.127.234 "unzip -oj ~/mycroft-logs/core/${BRANCH_ALIAS}/mycroft-logs.zip -d /var/www/voight-kampff/core/${BRANCH_ALIAS}/logs/"; + ssh root@157.245.127.234 "rm ~/mycroft-logs/core/${BRANCH_ALIAS}/mycroft-logs.zip"; + ssh root@157.245.127.234 "rmdir ~/mycroft-logs/core/${BRANCH_ALIAS}"; ''' ) echo 'Report Published' From 18cb28088d5da217eecd0bc39a0d83db88cc53b9 Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Tue, 6 Jul 2021 13:48:52 -0500 Subject: [PATCH 40/68] Added comments to document race condition --- mycroft/skills/mycroft_skill/mycroft_skill.py | 11 ++++++++++- .../features/steps/utterance_responses.py | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/mycroft/skills/mycroft_skill/mycroft_skill.py b/mycroft/skills/mycroft_skill/mycroft_skill.py index 4302b49b4ce4..67bfac529f8a 100644 --- a/mycroft/skills/mycroft_skill/mycroft_skill.py +++ b/mycroft/skills/mycroft_skill/mycroft_skill.py @@ -411,7 +411,16 @@ def converse(self, message=None): return False def __get_response(self): - """Helper to get a reponse from the user + """Helper to get a response from the user + + NOTE: There is a race condition here. There is a small amount of + time between the end of the device speaking and the converse method + being overridden in this method. If an utterance is injected during + this time, the wrong converse method is executed. The condition is + hidden during normal use due to the amount of time it takes a user + to speak a response. The condition is revealed when an automated + process injects an utterance quicker than this method can flip the + converse methods. Returns: str: user's response or None on a timeout diff --git a/test/integrationtests/voight_kampff/features/steps/utterance_responses.py b/test/integrationtests/voight_kampff/features/steps/utterance_responses.py index b0d118a3284c..7fd1c1991263 100644 --- a/test/integrationtests/voight_kampff/features/steps/utterance_responses.py +++ b/test/integrationtests/voight_kampff/features/steps/utterance_responses.py @@ -235,6 +235,12 @@ def check_contains(message): @then('the user replies "{text}"') @then('the user says "{text}"') def then_user_follow_up(context, text): + """Send a user response after being prompted by device. + + The sleep after the device is finished speaking is to address a race + condition in the MycroftSkill base class conversational code. It can + be removed when the race condition is addressed. + """ wait_while_speaking() time.sleep(2) context.bus.emit(Message('recognizer_loop:utterance', From 105a5b4be4b9d0b9518e780559bda9cdda3c0fab Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Tue, 6 Jul 2021 14:26:23 -0500 Subject: [PATCH 41/68] Improve the speed of waiting for dialogs by exiting the loop after a match is spoken. Also provide error handling for when a match is not found. --- .../voight_kampff/__init__.py | 16 +++- test/integrationtests/voight_kampff/tools.py | 95 +++++++++++++++---- 2 files changed, 91 insertions(+), 20 deletions(-) diff --git a/test/integrationtests/voight_kampff/__init__.py b/test/integrationtests/voight_kampff/__init__.py index a14b910eef82..67f5d1f33ef8 100644 --- a/test/integrationtests/voight_kampff/__init__.py +++ b/test/integrationtests/voight_kampff/__init__.py @@ -12,7 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -from .tools import (emit_utterance, wait_for_dialog, then_wait, - then_wait_fail, mycroft_responses, - print_mycroft_responses, wait_for_audio_service) +"""Public API into the voight_kampff package.""" +from .tools import ( + emit_utterance, + format_dialog_match_error, + mycroft_responses, + print_mycroft_responses, + then_wait, + then_wait_fail, + wait_for_audio_service, + wait_for_dialog, + wait_for_dialog_match, +) diff --git a/test/integrationtests/voight_kampff/tools.py b/test/integrationtests/voight_kampff/tools.py index 8a26b044550d..8b09204a2868 100644 --- a/test/integrationtests/voight_kampff/tools.py +++ b/test/integrationtests/voight_kampff/tools.py @@ -12,15 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. # - """Common tools to use when creating step files for behave tests.""" import time +from mycroft.audio.utils import wait_while_speaking from mycroft.messagebus import Message -TIMEOUT = 10 +DEFAULT_TIMEOUT = 10 def then_wait(msg_type, criteria_func, context, timeout=None): @@ -67,7 +67,7 @@ def then_wait_fail(msg_type, criteria_func, context, timeout=None): tuple (bool, str) test status and debug output """ status, debug = then_wait(msg_type, criteria_func, context, timeout) - return (not status, debug) + return not status, debug def mycroft_responses(context): @@ -95,15 +95,50 @@ def print_mycroft_responses(context): print(mycroft_responses(context)) -def emit_utterance(bus, utt): - """Emit an utterance on the bus. +def format_dialog_match_error(potential_matches, speak_messages): + """Format error message to be displayed when an expected + + This is similar to the mycroft_responses function above. The difference + is that here the expected responses are passed in instead of making + a second loop through message bus messages. + + Args: + potential_matches (list): one of the dialog files in this list were + expected to be spoken + speak_messages (list): "speak" event messages from the message bus + that don't match the list of potential matches. + + Returns: (str) Message detailing the error to the user + """ + error_message = ( + 'Expected Mycroft to respond with one of:\n' + f"\t{', '.join(potential_matches)}\n" + "Actual response(s):\n" + ) + if speak_messages: + for message in speak_messages: + meta = message.data.get("meta") + if meta is not None: + if 'dialog' in meta: + error_message += f"\tDialog: {meta['dialog']}" + if 'skill' in meta: + error_message += f" (from {meta['skill']} skill)\n" + error_message += f"\t\tUtterance: {message.data['utterance']}\n" + else: + error_message += "\tMycroft didn't respond" + + return error_message + + +def emit_utterance(bus, utterance): + """Emit an utterance event on the message bus. Args: bus (InterceptAllBusClient): Bus instance to listen on - dialogs (list): list of acceptable dialogs + utterance (str): list of acceptable dialogs """ bus.emit(Message('recognizer_loop:utterance', - data={'utterances': [utt], + data={'utterances': [utterance], 'lang': 'en-us', 'session': '', 'ident': time.time()}, @@ -121,18 +156,46 @@ def wait_for_dialog(bus, dialogs, context=None, timeout=None): provided by context or 10 seconds """ if context: - timeout = timeout or context.step_timeout + timeout_duration = timeout or context.step_timeout else: - timeout = timeout or TIMEOUT - start_time = time.monotonic() - while time.monotonic() < start_time + timeout: + timeout_duration = timeout or DEFAULT_TIMEOUT + wait_for_dialog_match(bus, dialogs, timeout_duration) + + +def wait_for_dialog_match(bus, dialogs, timeout=DEFAULT_TIMEOUT): + """Match dialogs spoken to the specified list of expected dialogs. + + Only one of the dialogs in the provided list need to match for this + check to be successful. + + Args: + bus (InterceptAllBusClient): Bus instance to listen on + dialogs (list): list of acceptable dialogs + timeout (int): how long to wait for the message, defaults to timeout + provided by context or 10 seconds + + Returns: + A boolean indicating if a match was found and the list of "speak" + events found on the message bus during the matching process. + """ + match_found = False + speak_messages = list() + timeout_time = time.monotonic() + timeout + while time.monotonic() < timeout_time: for message in bus.get_messages('speak'): + speak_messages.append(message) dialog = message.data.get('meta', {}).get('dialog') + print('dialog: ', dialog, '\tutterance: ', message.data['utterance']) if dialog in dialogs: - bus.clear_messages() - return - bus.new_message_available.wait(0.5) - bus.clear_messages() + wait_while_speaking() + match_found = True + break + bus.clear_messages() + if match_found: + break + time.sleep(1) + + return match_found, speak_messages def wait_for_audio_service(context, message_type): @@ -148,7 +211,7 @@ def wait_for_audio_service(context, message_type): msg_type = 'mycroft.audio.service.{}'.format(message_type) def check_for_msg(message): - return (message.msg_type == msg_type, '') + return message.msg_type == msg_type, '' passed, debug = then_wait(msg_type, check_for_msg, context) From cf355360addb7aa496fe6bfbe29a1177461533bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Fri, 9 Jul 2021 16:08:05 +0200 Subject: [PATCH 42/68] Fix message race when using clear_messages() This handles a scenario that a message arrives between a call to get_messages() and clear_messages(). clear_messages() will only clear the messages that has been evaluated atleast once. A new method clear_all_messages() has been added to clear the entire message stack and is used between scenarios to reset the list. --- .../voight_kampff/features/environment.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/integrationtests/voight_kampff/features/environment.py b/test/integrationtests/voight_kampff/features/environment.py index 6debde17653c..1dbe59151ead 100644 --- a/test/integrationtests/voight_kampff/features/environment.py +++ b/test/integrationtests/voight_kampff/features/environment.py @@ -43,6 +43,7 @@ def __init__(self): self.messages = [] self.message_lock = Lock() self.new_message_available = Event() + self._processed_messages = 0 def on_message(self, message): with self.message_lock: @@ -52,6 +53,7 @@ def on_message(self, message): def get_messages(self, msg_type): with self.message_lock: + self._processed_messages = len(self.messages) if msg_type is None: return [m for m in self.messages] else: @@ -59,11 +61,24 @@ def get_messages(self, msg_type): def remove_message(self, msg): with self.message_lock: + if msg not in self.messages: + raise ValueError(f'{msg} was not found in ' + 'the list of messages.') + # Update processed message count if a read message was removed + if self.messages.index(msg) < self._processed_messages: + self._processed_messages -= 1 + self.messages.remove(msg) def clear_messages(self): + with self.message_lock: + self.messages = self.messages[:self._processed_messages] + self._processed_messages = 0 + + def clear_all_messages(self): with self.message_lock: self.messages = [] + self._processed_messages = 0 def before_all(context): @@ -119,6 +134,6 @@ def after_scenario(context, scenario): # TODO wait for skill handler complete sleep(0.5) wait_while_speaking() - context.bus.clear_messages() + context.bus.clear_all_messages() context.matched_message = None context.step_timeout = 10 # Reset the step_timeout to 10 seconds From 4b66fb1dd1c8407eb3996561136dbea131334f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Fri, 9 Jul 2021 16:47:30 +0200 Subject: [PATCH 43/68] Add docstrings to InterceptAllBusClient --- .../voight_kampff/features/environment.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/integrationtests/voight_kampff/features/environment.py b/test/integrationtests/voight_kampff/features/environment.py index 1dbe59151ead..c1db4b3d34f9 100644 --- a/test/integrationtests/voight_kampff/features/environment.py +++ b/test/integrationtests/voight_kampff/features/environment.py @@ -38,6 +38,10 @@ def create_voight_kampff_logger(): class InterceptAllBusClient(MessageBusClient): + """Bus Client storing all messages recieved. + + This allows readback of older messages and non-event-driven operation. + """ def __init__(self): super().__init__() self.messages = [] @@ -46,12 +50,23 @@ def __init__(self): self._processed_messages = 0 def on_message(self, message): + """Extends normal operation by storing the recieved message. + + Args: + message (Message): message from the Mycroft bus + """ with self.message_lock: self.messages.append(Message.deserialize(message)) self.new_message_available.set() super().on_message(message) def get_messages(self, msg_type): + """Get messages from received list of messages. + + Args: + msg_type (None,str): string filter for messagetype to extract. + if None all messages will be returned. + """ with self.message_lock: self._processed_messages = len(self.messages) if msg_type is None: @@ -60,6 +75,11 @@ def get_messages(self, msg_type): return [m for m in self.messages if m.msg_type == msg_type] def remove_message(self, msg): + """Remove a specific message from the list of messages. + + Args: + msg (Message): message to remove from the list + """ with self.message_lock: if msg not in self.messages: raise ValueError(f'{msg} was not found in ' @@ -71,11 +91,13 @@ def remove_message(self, msg): self.messages.remove(msg) def clear_messages(self): + """Clear all messages that has been fetched atleast once.""" with self.message_lock: self.messages = self.messages[:self._processed_messages] self._processed_messages = 0 def clear_all_messages(self): + """Clear all messages.""" with self.message_lock: self.messages = [] self._processed_messages = 0 From 6fd97b4e85b0cb2057b166f930c68e51ca85138d Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Fri, 9 Jul 2021 15:45:07 -0500 Subject: [PATCH 44/68] fixed PEP8 issue --- test/integrationtests/voight_kampff/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integrationtests/voight_kampff/tools.py b/test/integrationtests/voight_kampff/tools.py index 8b09204a2868..3269f56ad9c8 100644 --- a/test/integrationtests/voight_kampff/tools.py +++ b/test/integrationtests/voight_kampff/tools.py @@ -185,7 +185,8 @@ def wait_for_dialog_match(bus, dialogs, timeout=DEFAULT_TIMEOUT): for message in bus.get_messages('speak'): speak_messages.append(message) dialog = message.data.get('meta', {}).get('dialog') - print('dialog: ', dialog, '\tutterance: ', message.data['utterance']) + print('dialog: ', dialog, + '\tutterance: ', message.data['utterance']) if dialog in dialogs: wait_while_speaking() match_found = True From 8f41d176d069b751adbf5a3d61951963dda4d237 Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Fri, 9 Jul 2021 16:39:49 -0500 Subject: [PATCH 45/68] remove print statement used for testing --- test/integrationtests/voight_kampff/tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/integrationtests/voight_kampff/tools.py b/test/integrationtests/voight_kampff/tools.py index 3269f56ad9c8..1c1ddc63a991 100644 --- a/test/integrationtests/voight_kampff/tools.py +++ b/test/integrationtests/voight_kampff/tools.py @@ -185,8 +185,6 @@ def wait_for_dialog_match(bus, dialogs, timeout=DEFAULT_TIMEOUT): for message in bus.get_messages('speak'): speak_messages.append(message) dialog = message.data.get('meta', {}).get('dialog') - print('dialog: ', dialog, - '\tutterance: ', message.data['utterance']) if dialog in dialogs: wait_while_speaking() match_found = True From cbd17a8dc34f4bcbbed61dbca8275df7a9d73b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Thu, 22 Jul 2021 17:15:12 +0200 Subject: [PATCH 46/68] Remove mycroft-stock from install list --- test/integrationtests/voight_kampff/default.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integrationtests/voight_kampff/default.yml b/test/integrationtests/voight_kampff/default.yml index 7ffe2f5ee176..1b3a9f901dc4 100644 --- a/test/integrationtests/voight_kampff/default.yml +++ b/test/integrationtests/voight_kampff/default.yml @@ -12,7 +12,6 @@ test_skills: - mycroft-npr-news - mycroft-installer - mycroft-singing -- mycroft-stock - mycroft-mark-1 - fallback-unknown - fallback-query From 45d5d9e4787e86519d06ec6054843422ff1250f6 Mon Sep 17 00:00:00 2001 From: Aditya Mehra Date: Fri, 23 Jul 2021 15:14:06 +0930 Subject: [PATCH 47/68] Add feature request functionality to webviews --- mycroft/res/ui/FeatureRequest.qml | 123 ++++++++++++++++++++++++++++ mycroft/res/ui/RequestHandler.qml | 35 ++++++++ mycroft/res/ui/WebViewHtmlFrame.qml | 14 ++++ mycroft/res/ui/WebViewUrlFrame.qml | 13 +++ 4 files changed, 185 insertions(+) create mode 100644 mycroft/res/ui/FeatureRequest.qml create mode 100644 mycroft/res/ui/RequestHandler.qml diff --git a/mycroft/res/ui/FeatureRequest.qml b/mycroft/res/ui/FeatureRequest.qml new file mode 100644 index 000000000000..1e83d97feaba --- /dev/null +++ b/mycroft/res/ui/FeatureRequest.qml @@ -0,0 +1,123 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtWebEngine 1.7 +import QtWebChannel 1.0 +import QtQuick.Layouts 1.12 +import org.kde.kirigami 2.11 as Kirigami + +Item { + property var requestedFeature; + property url securityOrigin; + + width: parent.width + height: parent.height + + onRequestedFeatureChanged: { + message.text = securityOrigin + " has requested access to your " + + message.textForFeature(requestedFeature); + } + + RowLayout { + anchors.fill: parent + + Label { + id: message + Layout.fillWidth: true + Layout.leftMargin: Kirigami.Units.largeSpacing + wrapMode: Text.WordWrap + maximumLineCount: 2 + elide: Text.ElideRight + + function textForFeature(feature) { + if (feature === WebEngineView.MediaAudioCapture) + return "microphone" + if (feature === WebEngineView.MediaVideoCapture) + return "camera" + if (feature === WebEngineView.MediaAudioVideoCapture) + return "camera and microphone" + if (feature === WebEngineView.Geolocation) + return "location" + } + } + + Button { + id: acceptButton + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: parent.width * 0.18 + + background: Rectangle { + color: acceptButton.activeFocus ? Kirigami.Theme.highlightColor : Qt.lighter(Kirigami.Theme.backgroundColor, 1.2) + border.color: Kirigami.Theme.disabledTextColor + radius: 20 + } + + contentItem: Item { + Kirigami.Heading { + level: 3 + font.pixelSize: parent.width * 0.075 + anchors.centerIn: parent + text: "Accept" + } + } + + onClicked: { + webview.grantFeaturePermission(securityOrigin, + requestedFeature, true); + interactionBar.isRequested = false; + } + } + + Button { + id: denyButton + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: parent.width * 0.18 + + background: Rectangle { + color: denyButton.activeFocus ? Kirigami.Theme.highlightColor : Qt.lighter(Kirigami.Theme.backgroundColor, 1.2) + border.color: Kirigami.Theme.disabledTextColor + radius: 20 + } + + contentItem: Item { + Kirigami.Heading { + level: 3 + font.pixelSize: parent.width * 0.075 + anchors.centerIn: parent + text: "Deny" + } + } + + onClicked: { + webview.grantFeaturePermission(securityOrigin, + requestedFeature, false); + interactionBar.isRequested = false + } + } + + Button { + id: closeButton + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: Kirigami.Units.iconSizes.large - (Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing) + Layout.preferredHeight: Kirigami.Units.iconSizes.large - (Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing) + Layout.leftMargin: Kirigami.Units.largeSpacing + Layout.rightMargin: Kirigami.Units.largeSpacing + + background: Rectangle { + color: denyButton.activeFocus ? Kirigami.Theme.highlightColor : Qt.lighter(Kirigami.Theme.backgroundColor, 1.2) + border.color: Kirigami.Theme.disabledTextColor + radius: 200 + } + + Kirigami.Icon { + anchors.centerIn: parent + width: Kirigami.Units.iconSizes.medium + height: Kirigami.Units.iconSizes.medium + source: "window-close" + } + + onClicked: { + interactionBar.isRequested = false + } + } + } +} diff --git a/mycroft/res/ui/RequestHandler.qml b/mycroft/res/ui/RequestHandler.qml new file mode 100644 index 000000000000..995151072fc1 --- /dev/null +++ b/mycroft/res/ui/RequestHandler.qml @@ -0,0 +1,35 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtWebEngine 1.7 +import QtWebChannel 1.0 +import QtQuick.Layouts 1.12 +import org.kde.kirigami 2.11 as Kirigami + +Rectangle { + property bool isRequested: false + property alias source: interactionLoader.source + property alias interactionItem: interactionLoader.item + + visible: isRequested + enabled: isRequested + width: parent.width + height: isRequested ? Kirigami.Units.gridUnit * 6 : 0 + color: Kirigami.Theme.backgroundColor + + function setSource(interactionSource){ + interactionLoader.setSource(interactionSource) + } + + Keys.onEscapePressed: { + isRequested = false; + } + + Keys.onBackPressed: { + isRequested = false; + } + + Loader { + id: interactionLoader + anchors.fill: parent + } +} diff --git a/mycroft/res/ui/WebViewHtmlFrame.qml b/mycroft/res/ui/WebViewHtmlFrame.qml index ec080afb2de5..67710441de68 100644 --- a/mycroft/res/ui/WebViewHtmlFrame.qml +++ b/mycroft/res/ui/WebViewHtmlFrame.qml @@ -19,6 +19,12 @@ Item { } } + RequestHandler { + id: interactionBar + anchors.top: parent.top + z: 1001 + } + WebEngineView { id: webview anchors.fill: parent @@ -47,6 +53,14 @@ Item { onJavaScriptDialogRequested: function(request) { request.accepted = true; } + + onFeaturePermissionRequested: { + interactionBar.setSource("FeatureRequest.qml") + interactionBar.interactionItem.securityOrigin = securityOrigin; + interactionBar.interactionItem.requestedFeature = feature; + interactionBar.isRequested = true; + } + } Popup { diff --git a/mycroft/res/ui/WebViewUrlFrame.qml b/mycroft/res/ui/WebViewUrlFrame.qml index c87d33f4add4..6a1d5210213e 100644 --- a/mycroft/res/ui/WebViewUrlFrame.qml +++ b/mycroft/res/ui/WebViewUrlFrame.qml @@ -14,6 +14,12 @@ Item { } } + RequestHandler { + id: interactionBar + anchors.top: parent.top + z: 1001 + } + WebEngineView { id: webview anchors.fill: parent @@ -42,6 +48,13 @@ Item { onJavaScriptDialogRequested: function(request) { request.accepted = true; } + + onFeaturePermissionRequested: { + interactionBar.setSource("FeatureRequest.qml") + interactionBar.interactionItem.securityOrigin = securityOrigin; + interactionBar.interactionItem.requestedFeature = feature; + interactionBar.isRequested = true; + } } Popup { From 24c4ba4a057c335045d70a2c734f93375ee8f034 Mon Sep 17 00:00:00 2001 From: jarbasal Date: Thu, 22 Jul 2021 20:31:23 +0100 Subject: [PATCH 48/68] Fix loading of audioservice plugins --- mycroft/audio/audioservice.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mycroft/audio/audioservice.py b/mycroft/audio/audioservice.py index a706f2e6add3..ea41b1148376 100644 --- a/mycroft/audio/audioservice.py +++ b/mycroft/audio/audioservice.py @@ -111,6 +111,7 @@ def setup_service(service_module, config, bus): except Exception as e: LOG.error('Failed to load service. ' + repr(e)) else: + LOG.error('Failed to load service. loading function not found') return None @@ -160,9 +161,10 @@ def load_plugins(config, bus): List of started services """ plugin_services = [] - plugins = find_plugins('mycroft.plugin.audioservice') - for plug in plugins: - service = setup_service(plug, config, bus) + found_plugins = find_plugins('mycroft.plugin.audioservice') + for plugin_name, plugin_module in found_plugins.items(): + LOG.info(f'Loading audio service plugin: {plugin_name}') + service = setup_service(plugin_module, config, bus) if service: plugin_services += service return plugin_services From 578a3ec439accb15bf70752e8302223ea57d2137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Sun, 11 Jul 2021 19:40:28 +0200 Subject: [PATCH 49/68] Use event to capture messages in then_wait() Instead of busily polling the bus for new messages an event handler is registered (and teared down after check is complete) --- test/integrationtests/voight_kampff/tools.py | 143 ++++++++++++++++--- 1 file changed, 127 insertions(+), 16 deletions(-) diff --git a/test/integrationtests/voight_kampff/tools.py b/test/integrationtests/voight_kampff/tools.py index 8a26b044550d..3732f3437a82 100644 --- a/test/integrationtests/voight_kampff/tools.py +++ b/test/integrationtests/voight_kampff/tools.py @@ -15,6 +15,7 @@ """Common tools to use when creating step files for behave tests.""" +from threading import Event import time from mycroft.messagebus import Message @@ -23,8 +24,129 @@ TIMEOUT = 10 +class CriteriaWaiter: + """Wait for a message to meet a certain criteria. + + Args: + msg_type: message type to watch + criteria_func: Function to determine if a message fulfilling the + test case has been found. + context: behave context + """ + def __init__(self, msg_type, criteria_func, context): + self.msg_type = msg_type + self.criteria_func = criteria_func + self.context = context + self.result = Event() + + def reset(self): + """Reset the wait state.""" + self.result.clear() + + # TODO: Remove in 21.08 + def wait_unspecific(self, timeout): + """ + Wait for a specified time for criteria to be fulfilled by any message. + + This use case is deprecated and only for backward compatibility + + Args: + timeout: Time allowance for a message fulfilling the criteria, if + provided will override the normal normal step timeout. + + Returns: + tuple (bool, str) test status and debug output + """ + timeout = timeout or self.context.step_timeout + start_time = time.monotonic() + debug = '' + while time.monotonic() < start_time + timeout: + for message in self.context.bus.get_messages(None): + status, test_dbg = self.criteria_func(message) + debug += test_dbg + if status: + self.context.matched_message = message + self.context.bus.remove_message(message) + return True, debug + self.context.bus.new_message_available.wait(0.5) + # Timed out return debug from test + return False, debug + + def _check_historical_messages(self): + """Search through the already received messages for a match. + + Returns: + tuple (bool, str) test status and debug output + + """ + debug = '' + for message in self.context.bus.get_messages(self.msg_type): + status, test_dbg = self.criteria_func(message) + debug += test_dbg + if status: + self.context.matched_message = message + self.context.bus.remove_message(message) + self.result.set() + break + return debug + + def wait_specific(self, timeout=None): + """Wait for a specific message type to fullfil a criteria. + + Uses an event-handler to not repeatedly loop. + + Args: + timeout: Time allowance for a message fulfilling the criteria, if + provided will override the normal normal step timeout. + + Returns: + tuple (bool, str) test status and debug output + """ + timeout = timeout or self.context.step_timeout + + debug = '' + + def on_message(message): + nonlocal debug + status, test_dbg = self.criteria_func(message) + debug += test_dbg + if status: + self.context.matched_message = message + self.result.set() + + self.context.bus.on(self.msg_type, on_message) + # Check historical messages + historical_debug = self._check_historical_messages() + + # If no matching message was already caught, wait for it + if not self.result.is_set(): + self.result.wait(timeout=timeout) + self.context.bus.remove(self.msg_type, on_message) + return self.result.is_set(), historical_debug + debug + + def wait(self, timeout=None): + """Wait for a specific message type to fullfil a criteria. + + Uses an event-handler to not repeatedly loop. + + Args: + timeout: Time allowance for a message fulfilling the criteria, if + provided will override the normal normal step timeout. + + Returns: + (result (bool), debug (str)) Result containing status and debug + message. + """ + if self.msg_type is None: + return self.wait_unspecific(timeout) + else: + return self.wait_specific(timeout) + + def then_wait(msg_type, criteria_func, context, timeout=None): - """Wait for a specified time for criteria to be fulfilled. + """Wait for a specific message type to fullfil a criteria. + + Uses an event-handler to not repeatedly loop. Args: msg_type: message type to watch @@ -35,22 +157,11 @@ def then_wait(msg_type, criteria_func, context, timeout=None): provided will override the normal normal step timeout. Returns: - tuple (bool, str) test status and debug output + (result (bool), debug (str)) Result containing status and debug + message. """ - timeout = timeout or context.step_timeout - start_time = time.monotonic() - debug = '' - while time.monotonic() < start_time + timeout: - for message in context.bus.get_messages(msg_type): - status, test_dbg = criteria_func(message) - debug += test_dbg - if status: - context.matched_message = message - context.bus.remove_message(message) - return True, debug - context.bus.new_message_available.wait(0.5) - # Timed out return debug from test - return False, debug + waiter = CriteriaWaiter(msg_type, criteria_func, context) + return waiter.wait(timeout) def then_wait_fail(msg_type, criteria_func, context, timeout=None): From 171d3840f3ba2e696280d5d00dcb7908dc9c5b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Thu, 22 Jul 2021 17:08:50 +0200 Subject: [PATCH 50/68] Remove sleeps from end of scenarios and features --- test/integrationtests/voight_kampff/features/environment.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/integrationtests/voight_kampff/features/environment.py b/test/integrationtests/voight_kampff/features/environment.py index 6debde17653c..54ad39d0c947 100644 --- a/test/integrationtests/voight_kampff/features/environment.py +++ b/test/integrationtests/voight_kampff/features/environment.py @@ -111,13 +111,11 @@ def after_all(context): def after_feature(context, feature): context.log.info('Result: {} ({:.2f}s)'.format(str(feature.status.name), feature.duration)) - sleep(1) def after_scenario(context, scenario): """Wait for mycroft completion and reset any changed state.""" # TODO wait for skill handler complete - sleep(0.5) wait_while_speaking() context.bus.clear_messages() context.matched_message = None From b40fcf0e938f726c9e5b427c6295ca029052468a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Thu, 22 Jul 2021 21:07:02 +0200 Subject: [PATCH 51/68] Update VK then step for checking messagetype Simplify the function and use the standard then_wait() for the heavy lifting, this makes it utilize the new event driven functionality. --- .../features/steps/utterance_responses.py | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/test/integrationtests/voight_kampff/features/steps/utterance_responses.py b/test/integrationtests/voight_kampff/features/steps/utterance_responses.py index 7fd1c1991263..e7422712b261 100644 --- a/test/integrationtests/voight_kampff/features/steps/utterance_responses.py +++ b/test/integrationtests/voight_kampff/features/steps/utterance_responses.py @@ -30,10 +30,6 @@ then_wait_fail) -TIMEOUT = 10 -SLEEP_LENGTH = 0.25 - - def find_dialog(skill_path, dialog, lang): """Check the usual location for dialogs. @@ -253,13 +249,10 @@ def then_user_follow_up(context, text): @then('mycroft should send the message "{message_type}"') def then_messagebus_message(context, message_type): - """Set a timeout for the current Scenario.""" - cnt = 0 - while context.bus.get_messages(message_type) == []: - if cnt > int(TIMEOUT * (1.0 / SLEEP_LENGTH)): - assert False, "Message not found" - break - else: - cnt += 1 - - time.sleep(SLEEP_LENGTH) + """Verify a specific message is sent.""" + def check_dummy(message): + """We are just interested in the message data, just the type.""" + return True, "" + + message_found, _ = then_wait(message_type, check_dummy, context) + assert message_found, "No matching message received." From b7a0853f3cb66ae12fe2df51be7cdfc2e9983594 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Tue, 27 Jul 2021 23:06:43 +0930 Subject: [PATCH 52/68] Upgrade Adapt to v0.5.1 - Fix removal of regex entities - Update trie dosctrings - Guarantee sorted results from IntentDeterminationEngine - Enumerate all possible parse results if context or regex entities are in play. - Explicit test to assert results are sorted - Fix name of LICENSE file in Adapt package --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 2a37a2ced8b8..0c7c8197a2dd 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -19,7 +19,7 @@ lingua-franca==0.4.2 msm==0.8.9 msk==0.3.16 mycroft-messagebus-client==0.9.1 -adapt-parser==0.4.1 +adapt-parser==0.5.1 padatious==0.4.8 fann2==1.0.7 padaos==0.1.9 From a794db0c9a4b54c134a84cc209c7c9ff42134039 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Mon, 2 Aug 2021 15:13:06 +0930 Subject: [PATCH 53/68] Make network tests configurable Previously test URIs were hardcoded. They can now be configured in mycroft.conf --- mycroft/configuration/mycroft.conf | 9 ++++ mycroft/util/network_utils.py | 34 ++++++++++++--- test/unittests/util/test_network_utils.py | 52 +++++++++++++++++++++++ 3 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 test/unittests/util/test_network_utils.py diff --git a/mycroft/configuration/mycroft.conf b/mycroft/configuration/mycroft.conf index 6d8e0113bb81..1b63c4654230 100644 --- a/mycroft/configuration/mycroft.conf +++ b/mycroft/configuration/mycroft.conf @@ -146,6 +146,15 @@ "ssl": false }, + // URIs to use for testing network connection. + "network_tests": { + "dns_primary": "8.8.8.8", + "dns_secondary": "8.8.4.4", + "web_url": "https://www.google.com", + "ncsi_endpoint": "http://www.msftncsi.com/ncsi.txt", + "ncsi_expected_text": "Microsoft NCSI" + }, + // Settings used by the wake-up-word listener // Override: REMOTE "listener": { diff --git a/mycroft/util/network_utils.py b/mycroft/util/network_utils.py index 9a06ac4bae7a..d4607aa34f2a 100644 --- a/mycroft/util/network_utils.py +++ b/mycroft/util/network_utils.py @@ -6,6 +6,14 @@ from .log import LOG +def _get_network_tests_config(): + """Get network_tests object from mycroft.configuration.""" + # Wrapped to avoid circular import errors. + from mycroft.configuration import Configuration + config = Configuration.get() + return config.get('network_tests', {}) + + def connected(): """Check connection by connecting to 8.8.8.8 and if google.com is reachable if this fails, Check Microsoft NCSI is used as a backup. @@ -27,16 +35,19 @@ def _connected_ncsi(): Returns: True if internet connection can be detected """ + config = _get_network_tests_config() + ncsi_endpoint = config.get('ncsi_endpoint') + expected_text = config.get('ncsi_expected_text') try: - r = requests.get('http://www.msftncsi.com/ncsi.txt') - if r.text == 'Microsoft NCSI': + r = requests.get(ncsi_endpoint) + if r.text == expected_text: return True except Exception: - pass + LOG.error("Unable to verify connection via NCSI endpoint.") return False -def _connected_dns(host="8.8.8.8", port=53, timeout=3): +def _connected_dns(host=None, port=53, timeout=3): """Check internet connection by connecting to DNS servers Returns: @@ -46,18 +57,25 @@ def _connected_dns(host="8.8.8.8", port=53, timeout=3): # Host: 8.8.8.8 (google-public-dns-a.google.com) # OpenPort: 53/tcp # Service: domain (DNS/TCP) + config = _get_network_tests_config() + if host is None: + host = config.get('dns_primary') try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(timeout) s.connect((host, port)) return True except IOError: + LOG.error("Unable to connect to primary DNS server, " + "trying secondary...") try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(timeout) - s.connect(("8.8.4.4", port)) + dns_secondary = config.get('dns_secondary') + s.connect((dns_secondary, port)) return True except IOError: + LOG.error("Unable to connect to secondary DNS server.") return False @@ -67,10 +85,12 @@ def _connected_google(): True if connection attempt succeeded """ connect_success = False + config = _get_network_tests_config() + url = config.get('web_url') try: - urlopen('https://www.google.com', timeout=3) + urlopen(url, timeout=3) except URLError as ue: - LOG.debug('Attempt to connect to internet failed: ' + str(ue.reason)) + LOG.error('Attempt to connect to internet failed: ' + str(ue.reason)) else: connect_success = True diff --git a/test/unittests/util/test_network_utils.py b/test/unittests/util/test_network_utils.py new file mode 100644 index 000000000000..76e86fe76289 --- /dev/null +++ b/test/unittests/util/test_network_utils.py @@ -0,0 +1,52 @@ +from unittest import TestCase, mock + +from mycroft.util.network_utils import connected + + +class TestNetworkConnected(TestCase): + def test_default_config_succeeds(self): + """Check that happy path succeeds""" + self.assertTrue(connected()) + + +@mock.patch('mycroft.configuration.Configuration') +class TestNetworkFailure(TestCase): + + def test_dns_and_ncsi_fail(self, mock_conf): + """Check that DNS and NCSI failure results in False response""" + mock_conf.get.return_value = { + "network_tests": { + "dns_primary": "127.0.0.1", + "dns_secondary": "127.0.0.1", + "web_url": "https://www.google.com", + "ncsi_endpoint": "http://www.msftncsi.com/ncsi.txt", + "ncsi_expected_text": "Unexpected text" + } + } + self.assertFalse(connected()) + + def test_secondary_dns_succeeds(self, mock_conf): + """Check that only primary DNS failing still succeeds""" + mock_conf.get.return_value = { + "network_tests": { + "dns_primary": "127.0.0.1", + "dns_secondary": "8.8.4.4", + "web_url": "https://www.google.com", + "ncsi_endpoint": "http://www.msftncsi.com/ncsi.txt", + "ncsi_expected_text": "Microsoft NCSI" + } + } + self.assertTrue(connected()) + + def test_dns_success_url_fail(self, mock_conf): + """Check that URL connection failure results in False response""" + mock_conf.get.return_value = { + "network_tests": { + "dns_primary": "8.8.8.8", + "dns_secondary": "8.8.4.4", + "web_url": "https://test.invalid", + "ncsi_endpoint": "http://www.msftncsi.com/ncsi.txt", + "ncsi_expected_text": "Microsoft NCSI" + } + } + self.assertFalse(connected()) From ff9f8e898dc11fe38a8731f9240e037b5cae1129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Fri, 8 Jan 2021 21:40:22 +0100 Subject: [PATCH 54/68] Update identity location for VK test Moved from .mycroft to XDG folder --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 9888f3fb4fe9..30b69a6e7a76 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -57,7 +57,7 @@ pipeline { sh 'mkdir -p $HOME/core/$BRANCH_ALIAS/allure' sh 'mkdir -p $HOME/core/$BRANCH_ALIAS/mycroft-logs' sh 'docker run \ - -v "$HOME/voight-kampff/identity:/root/.mycroft/identity" \ + -v "$HOME/voight-kampff/identity:/root/.config/mycroft/identity" \ -v "$HOME/core/$BRANCH_ALIAS/allure:/root/allure" \ -v "$HOME/core/$BRANCH_ALIAS/mycroft-logs:/var/log/mycroft" \ --label build=${JOB_NAME} \ From e20443b82458a1185743fe82ced5bf1788b8d3f0 Mon Sep 17 00:00:00 2001 From: Bart Ribbers Date: Fri, 8 May 2020 21:32:33 +0200 Subject: [PATCH 55/68] Use XDG Base directories for settings, cache and runtime data Improve deprecation warning message --- README.md | 8 +-- bin/mycroft-config | 6 +- mycroft/api/__init__.py | 17 ++---- mycroft/client/enclosure/__main__.py | 6 +- mycroft/client/enclosure/mark1/__init__.py | 5 ++ mycroft/client/speech/hotword_factory.py | 34 +++++++++-- mycroft/client/text/text_client.py | 36 ++++++++++- mycroft/configuration/__init__.py | 1 - mycroft/configuration/config.py | 66 +++++++++++++++++---- mycroft/configuration/locations.py | 7 ++- mycroft/configuration/mycroft.conf | 8 +-- mycroft/filesystem/__init__.py | 10 +++- mycroft/messagebus/send_func.py | 7 +-- mycroft/skills/event_scheduler.py | 11 +++- mycroft/skills/skill_updater.py | 6 +- mycroft/util/file_utils.py | 19 ++++-- mycroft/util/log.py | 11 ++-- test/unittests/skills/test_skill_updater.py | 6 +- test/unittests/util/commented.json | 2 +- test/unittests/util/plain.json | 2 +- 20 files changed, 197 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 5e620404687f..d34d08e3474b 100644 --- a/README.md +++ b/README.md @@ -73,20 +73,20 @@ Mycroft is nothing without skills. There are a handful of default skills that a ### Pairing Information Pairing information generated by registering with Home is stored in: -`~/.mycroft/identity/identity2.json` <-- DO NOT SHARE THIS WITH OTHERS! +`~/.config/mycroft/identity/identity2.json` <-- DO NOT SHARE THIS WITH OTHERS! ### Configuration Mycroft's configuration consists of 4 possible locations: - `mycroft-core/mycroft/configuration/mycroft.conf`(Defaults) - [Mycroft Home](https://home.mycroft.ai) (Remote) -- `/etc/mycroft/mycroft.conf`(Machine) -- `$HOME/.mycroft/mycroft.conf`(User) +- `/etc/mycroft/mycroft.conf` (Machine) +- `$XDG_CONFIG_DIR/mycroft/mycroft.conf` (which is by default `$HOME/.config/mycroft/mycroft.conf`) (USER) When the configuration loader starts, it looks in these locations in this order, and loads ALL configurations. Keys that exist in multiple configuration files will be overridden by the last file to contain the value. This process results in a minimal amount being written for a specific device and user, without modifying default distribution files. ### Using Mycroft Without Home -If you do not wish to use the Mycroft Home service, before starting Mycroft for the first time, create `$HOME/.mycroft/mycroft.conf` with the following contents: +If you do not wish to use the Mycroft Home service, before starting Mycroft for the first time, create `$HOME/.config/mycroft/mycroft.conf` with the following contents: ``` { diff --git a/bin/mycroft-config b/bin/mycroft-config index a1271b328dfc..981f2c5d0c65 100755 --- a/bin/mycroft-config +++ b/bin/mycroft-config @@ -95,13 +95,13 @@ function validate_config_file() { return $result } -_conf_file="~/.mycroft/mycroft.conf" +_conf_file="~/.config/mycroft/mycroft.conf" function name_to_path() { case ${1} in "system") _conf_file="/etc/mycroft/mycroft.conf" ;; - "user") _conf_file=$(readlink -f ~/.mycroft/mycroft.conf) ;; + "user") _conf_file=$(readlink -f ~/.config/mycroft/mycroft.conf) ;; "default") _conf_file="$DIR/../mycroft/configuration/mycroft.conf" ;; - "remote") _conf_file="/var/tmp/mycroft_web_cache.json" ;; + "remote") _conf_file="$HOME/.cache/mycroft/web_cache.json" ;; *) echo "ERROR: Unknown name '${1}'." diff --git a/mycroft/api/__init__.py b/mycroft/api/__init__.py index 2e74268bebc1..9da1ca0b5803 100644 --- a/mycroft/api/__init__.py +++ b/mycroft/api/__init__.py @@ -20,8 +20,6 @@ from requests import HTTPError, RequestException from mycroft.configuration import Configuration -from mycroft.configuration.config import DEFAULT_CONFIG, SYSTEM_CONFIG, \ - USER_CONFIG from mycroft.identity import IdentityManager, identity_lock from mycroft.version import VersionManager from mycroft.util import get_arch, connected, LOG @@ -49,12 +47,9 @@ class Api: def __init__(self, path): self.path = path - # Load the config, skipping the REMOTE_CONFIG since we are + # Load the config, skipping the remote config since we are # getting the info needed to get to it! - config = Configuration.get([DEFAULT_CONFIG, - SYSTEM_CONFIG, - USER_CONFIG], - cache=False) + config = Configuration.get(cache=False, remote=False) config_server = config.get("server") self.url = config_server.get("url") self.version = config_server.get("version") @@ -239,9 +234,7 @@ def activate(self, state, token): platform_build = "" # load just the local configs to get platform info - config = Configuration.get([SYSTEM_CONFIG, - USER_CONFIG], - cache=False) + config = Configuration.get(cache=False, remote=False) if "enclosure" in config: platform = config.get("enclosure").get("platform", "unknown") platform_build = config.get("enclosure").get("platform_build", "") @@ -263,9 +256,7 @@ def update_version(self): platform_build = "" # load just the local configs to get platform info - config = Configuration.get([SYSTEM_CONFIG, - USER_CONFIG], - cache=False) + config = Configuration.get(cache=False, remote=False) if "enclosure" in config: platform = config.get("enclosure").get("platform", "unknown") platform_build = config.get("enclosure").get("platform_build", "") diff --git a/mycroft/client/enclosure/__main__.py b/mycroft/client/enclosure/__main__.py index f7bdb5dde9b6..0ce5b361c12b 100644 --- a/mycroft/client/enclosure/__main__.py +++ b/mycroft/client/enclosure/__main__.py @@ -17,7 +17,7 @@ This provides any "enclosure" specific functionality, for example GUI or control over the Mark-1 Faceplate. """ -from mycroft.configuration import LocalConf, SYSTEM_CONFIG +from mycroft.configuration import Configuration from mycroft.util.log import LOG from mycroft.util import wait_for_exit_signal, reset_sigint_handler @@ -70,8 +70,8 @@ def main(ready_hook=on_ready, error_hook=on_error, stopping_hook=on_stopping): only the GUI bus will be started. """ # Read the system configuration - system_config = LocalConf(SYSTEM_CONFIG) - platform = system_config.get("enclosure", {}).get("platform") + config = Configuration.get(remote=False) + platform = config.get("enclosure", {}).get("platform") enclosure = create_enclosure(platform) if enclosure: diff --git a/mycroft/client/enclosure/mark1/__init__.py b/mycroft/client/enclosure/mark1/__init__.py index 2741aa0eed1c..2095945705d0 100644 --- a/mycroft/client/enclosure/mark1/__init__.py +++ b/mycroft/client/enclosure/mark1/__init__.py @@ -15,8 +15,10 @@ import subprocess import time import sys +import os from alsaaudio import Mixer from threading import Thread, Timer +from xdg import BaseDirectory import serial @@ -164,6 +166,9 @@ def process(self, data): if "unit.factory-reset" in data: self.bus.emit(Message("speak", { 'utterance': mycroft.dialog.get("reset to factory defaults")})) + subprocess.call( + 'rm ~/.config/mycroft/identity/identity2.json', + shell=True) subprocess.call( 'rm ~/.mycroft/identity/identity2.json', shell=True) diff --git a/mycroft/client/speech/hotword_factory.py b/mycroft/client/speech/hotword_factory.py index 91ca8947ac66..72a5e0ee3775 100644 --- a/mycroft/client/speech/hotword_factory.py +++ b/mycroft/client/speech/hotword_factory.py @@ -26,13 +26,15 @@ from threading import Timer, Thread from time import time, sleep from urllib.error import HTTPError +from xdg import BaseDirectory from petact import install_package import requests -from mycroft.configuration import Configuration, LocalConf, USER_CONFIG -from mycroft.util.monotonic_event import MonotonicEvent +from mycroft.configuration import Configuration, LocalConf +from mycroft.configuration.locations import OLD_USER_CONFIG from mycroft.util.log import LOG +from mycroft.util.monotonic_event import MonotonicEvent from mycroft.util.plugins import load_plugin RECOGNIZER_DIR = join(abspath(dirname(__file__)), "recognizer") @@ -193,9 +195,30 @@ def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"): from precise_runner import ( PreciseRunner, PreciseEngine, ReadWriteStream ) - local_conf = LocalConf(USER_CONFIG) + + # We need to save to a writeable location, but the key we need + # might be stored in a different, unwriteable, location + # Make sure we pick the key we need from wherever it's located, + # but save to a writeable location only + local_conf = LocalConf(join(BaseDirectory.save_config_path('mycroft'), + 'mycroft.conf')) + + for dir in BaseDirectory.load_config_paths('mycroft'): + conf = LocalConf(join(dir, 'mycroft.conf')) + # If the current config contains the precise key use it, + # otherwise continue to the next file + if conf.get('precise', None) is not None: + local_conf['precise'] = conf.get('precise', None) + break + + # If the key is not found yet, it might still exist on the old + # (deprecated) location + if local_conf.get('precise', None) is None: + local_conf = LocalConf(OLD_USER_CONFIG) + if not local_conf.get('precise', {}).get('use_precise', True): raise PreciseUnavailable + if (local_conf.get('precise', {}).get('dist_url') == 'http://bootstrap.mycroft.ai/artifacts/static/daily/'): del local_conf['precise']['dist_url'] @@ -253,7 +276,10 @@ def update_precise(self, precise_config): @property def folder(self): - return join(expanduser('~'), '.mycroft', 'precise') + old_path = join(expanduser('~'), '.mycroft', 'precise') + if os.path.isdir(old_path): + return old_path + return join(BaseDirectory.save_data_path('mycroft', 'precise')) @property def install_destination(self): diff --git a/mycroft/client/text/text_client.py b/mycroft/client/text/text_client.py index 5d382cb52269..8b2ca3d3ea4d 100644 --- a/mycroft/client/text/text_client.py +++ b/mycroft/client/text/text_client.py @@ -15,6 +15,7 @@ import sys import io from math import ceil +from xdg import BaseDirectory from .gui_server import start_qml_gui @@ -142,7 +143,7 @@ def handleNonAscii(text): ############################################################################## # Settings -config_file = os.path.join(os.path.expanduser("~"), ".mycroft_cli.conf") +filename = "mycroft_cli.conf" def load_mycroft_config(bus): @@ -171,6 +172,35 @@ def load_settings(): global max_log_lines global show_meter + config_file = None + + # Old location + path = os.path.join(os.path.expanduser("~"), ".mycroft_cli.conf") + if os.path.isfile(path): + LOG.warning(" ===============================================") + LOG.warning(" == DEPRECATION WARNING ==") + LOG.warning(" ===============================================") + LOG.warning(" You still have a config file at " + + path) + LOG.warning(" Note that this location is deprecated and will" + + " not be used in the future") + LOG.warning(" Please move it to " + + os.path.join(BaseDirectory.save_config_path('mycroft'), + filename)) + config_file = path + + # Check XDG_CONFIG_DIR + if config_file is None: + for dir in BaseDirectory.load_config_paths('mycroft'): + file = os.path.join(dir, filename) + if os.path.isfile(file): + config_file = file + break + + # Check /etc/mycroft + if config_file is None: + config_file = os.path.join("/etc/mycroft", filename) + try: with io.open(config_file, 'r') as f: config = json.load(f) @@ -196,6 +226,10 @@ def save_settings(): config["show_last_key"] = show_last_key config["max_log_lines"] = max_log_lines config["show_meter"] = show_meter + + config_file = os.path.join( + BaseDirectory.save_config_path("mycroft"), filename) + with io.open(config_file, 'w') as f: f.write(str(json.dumps(config, ensure_ascii=False))) diff --git a/mycroft/configuration/__init__.py b/mycroft/configuration/__init__.py index c7981df30958..8f7d1a4ed607 100644 --- a/mycroft/configuration/__init__.py +++ b/mycroft/configuration/__init__.py @@ -14,4 +14,3 @@ # from .config import Configuration, LocalConf, RemoteConf from .locale import set_default_lf_lang -from .locations import SYSTEM_CONFIG, USER_CONFIG diff --git a/mycroft/configuration/config.py b/mycroft/configuration/config.py index 3ff92912f025..029c19bdb11d 100644 --- a/mycroft/configuration/config.py +++ b/mycroft/configuration/config.py @@ -17,14 +17,15 @@ import re import json import inflection -from os.path import exists, isfile +from os.path import exists, isfile, join, expanduser, dirname from requests import RequestException +from xdg import BaseDirectory from mycroft.util.json_helper import load_commented_json, merge_dict from mycroft.util.log import LOG -from .locations import (DEFAULT_CONFIG, SYSTEM_CONFIG, USER_CONFIG, - WEB_CONFIG_CACHE) +from .locations import DEFAULT_CONFIG, OLD_USER_CONFIG, USER_CONFIG +from .locations import SYSTEM_CONFIG def is_remote_list(values): @@ -127,7 +128,8 @@ class RemoteConf(LocalConf): def __init__(self, cache=None): super(RemoteConf, self).__init__(None) - cache = cache or WEB_CONFIG_CACHE + cache = cache or join(BaseDirectory.save_cache_path('mycroft'), + 'web_cache.json') from mycroft.api import is_paired if not is_paired(): self.load_local(cache) @@ -174,7 +176,7 @@ class Configuration: __patch = {} # Patch config that skills can update to override config @staticmethod - def get(configs=None, cache=True): + def get(configs=None, cache=True, remote=True): """Get configuration Returns cached instance if available otherwise builds a new @@ -183,6 +185,9 @@ def get(configs=None, cache=True): Args: configs (list): List of configuration dicts cache (boolean): True if the result should be cached + remote (boolean): False if the Mycroft Home settings shouldn't + be loaded + Returns: (dict) configuration dictionary. @@ -190,23 +195,62 @@ def get(configs=None, cache=True): if Configuration.__config: return Configuration.__config else: - return Configuration.load_config_stack(configs, cache) + return Configuration.load_config_stack(configs, cache, remote) @staticmethod - def load_config_stack(configs=None, cache=False): + def load_config_stack(configs=None, cache=False, remote=True): """Load a stack of config dicts into a single dict Args: configs (list): list of dicts to load cache (boolean): True if result should be cached - + remote (boolean): False if the Mycroft Home settings shouldn't + be loaded Returns: (dict) merged dict of all configuration files """ if not configs: - configs = [LocalConf(DEFAULT_CONFIG), RemoteConf(), - LocalConf(SYSTEM_CONFIG), LocalConf(USER_CONFIG), - Configuration.__patch] + configs = configs or [] + + # First use the patched config + configs.append(Configuration.__patch) + + # Then use XDG config + # This includes both the user config and + # /etc/xdg/mycroft/mycroft.conf + for dir in BaseDirectory.load_config_paths('mycroft'): + configs.append(LocalConf(join(dir, 'mycroft.conf'))) + + # Then check the old user config + if isfile(OLD_USER_CONFIG): + LOG.warning(" ===============================================") + LOG.warning(" == DEPRECATION WARNING ==") + LOG.warning(" ===============================================") + LOG.warning(" You still have a config file at " + + OLD_USER_CONFIG) + LOG.warning(" Note that this location is deprecated and will" + + " not be used in the future") + LOG.warning(" Please move it to " + + BaseDirectory.save_config_path('mycroft')) + configs.append(LocalConf(OLD_USER_CONFIG)) + + # Then check the XDG user config + if isfile(USER_CONFIG): + configs.append(LocalConf(USER_CONFIG)) + + # Then use remote config + if remote: + configs.append(RemoteConf()) + + # Then use the system config (/etc/mycroft/mycroft.conf) + configs.append(LocalConf(SYSTEM_CONFIG)) + + # Then use the config that comes with the package + configs.append(LocalConf(DEFAULT_CONFIG)) + + # Make sure we reverse the array, as merge_dict will put every new + # file on top of the previous one + configs = reversed(configs) else: # Handle strings in stack for index, item in enumerate(configs): diff --git a/mycroft/configuration/locations.py b/mycroft/configuration/locations.py index 28f57a2894d7..77177736b244 100644 --- a/mycroft/configuration/locations.py +++ b/mycroft/configuration/locations.py @@ -13,11 +13,16 @@ # limitations under the License. import os from os.path import join, dirname, expanduser, exists +from xdg import BaseDirectory DEFAULT_CONFIG = join(dirname(__file__), 'mycroft.conf') SYSTEM_CONFIG = os.environ.get('MYCROFT_SYSTEM_CONFIG', '/etc/mycroft/mycroft.conf') -USER_CONFIG = join(expanduser('~'), '.mycroft/mycroft.conf') +# Make sure we support the old location still +# Deprecated and will be removed eventually +OLD_USER_CONFIG = join(expanduser('~'), '.mycroft/mycroft.conf') +USER_CONFIG = join(BaseDirectory.save_config_path('mycroft'), + 'mycroft.conf') REMOTE_CONFIG = "mycroft.ai" WEB_CONFIG_CACHE = os.environ.get('MYCROFT_WEB_CACHE', '/var/tmp/mycroft_web_cache.json') diff --git a/mycroft/configuration/mycroft.conf b/mycroft/configuration/mycroft.conf index 1b63c4654230..b6922259aa43 100644 --- a/mycroft/configuration/mycroft.conf +++ b/mycroft/configuration/mycroft.conf @@ -5,7 +5,7 @@ // overridden at the REMOTE level (set by the user via // https://home.mycroft.ai), at the SYSTEM level (typically in the file // '/etc/mycroft/mycroft.conf'), or at the USER level (typically in the - // file '~/.mycroft/mycroft.conf'). + // file '~/.config/mycroft/mycroft.conf'). // // The load order of settings is: // DEFAULT @@ -107,7 +107,7 @@ }, "upload_skill_manifest": true, // Directory to look for user skills - "directory": "~/.mycroft/skills", + "directory": "~/.local/share/mycroft/skills", // Enable auto update by msm "auto_update": true, // blacklisted skills to not load @@ -221,7 +221,7 @@ "threshold": 1e-90, "lang": "en-us" // Specify custom model via: - // "local_model_file": "~/.mycroft/precise/models/something.pb" + // "local_model_file": "~/.local/share/mycroft/precise/models/something.pb" // Precise options: // "sensitivity": 0.5, // Higher = more sensitive // "trigger_level": 3 // Higher = more delay & less sensitive @@ -324,7 +324,7 @@ }, "padatious": { - "intent_cache": "~/.mycroft/intent_cache", + "intent_cache": "~/.local/share/mycroft/intent_cache", "train_delay": 4, "single_thread": false }, diff --git a/mycroft/filesystem/__init__.py b/mycroft/filesystem/__init__.py index df6776f0e9b2..c0e5c4ae1cdc 100644 --- a/mycroft/filesystem/__init__.py +++ b/mycroft/filesystem/__init__.py @@ -13,7 +13,9 @@ # limitations under the License. # import os +import shutil from os.path import join, expanduser, isdir +from xdg import BaseDirectory class FileSystemAccess: @@ -31,7 +33,13 @@ def __init__(self, path): def __init_path(path): if not isinstance(path, str) or len(path) == 0: raise ValueError("path must be initialized as a non empty string") - path = join(expanduser('~'), '.mycroft', path) + + old_path = join(expanduser('~'), '.mycroft', path) + path = join(BaseDirectory.save_config_path('mycroft'), path) + + # Migrate from the old location if it still exists + if isdir(old_path): + shutil.move(old_path, path) if not isdir(path): os.makedirs(path) diff --git a/mycroft/messagebus/send_func.py b/mycroft/messagebus/send_func.py index 403db9f45c1d..a11a9b0a591d 100644 --- a/mycroft/messagebus/send_func.py +++ b/mycroft/messagebus/send_func.py @@ -15,8 +15,6 @@ from websocket import create_connection from mycroft.configuration import Configuration -from mycroft.configuration.locations import (DEFAULT_CONFIG, SYSTEM_CONFIG, - USER_CONFIG) from mycroft.messagebus.client import MessageBusClient from mycroft.messagebus.message import Message @@ -32,10 +30,7 @@ def send(message_to_send, data_to_send=None): data_to_send = data_to_send or {} # Calculate the standard Mycroft messagebus websocket address - config = Configuration.get([DEFAULT_CONFIG, - SYSTEM_CONFIG, - USER_CONFIG], - cache=False) + config = Configuration.get(cache=False, remote=False) config = config.get("websocket") url = MessageBusClient.build_url( config.get("host"), diff --git a/mycroft/skills/event_scheduler.py b/mycroft/skills/event_scheduler.py index cc91a76c43ad..8d1582799fed 100644 --- a/mycroft/skills/event_scheduler.py +++ b/mycroft/skills/event_scheduler.py @@ -16,10 +16,12 @@ times. """ import json +import shutil import time from datetime import datetime, timedelta from threading import Thread, Lock from os.path import isfile, join, expanduser +from xdg import BaseDirectory from mycroft.configuration import Configuration from mycroft.messagebus.message import Message @@ -54,14 +56,19 @@ class EventScheduler(Thread): """ def __init__(self, bus, schedule_file='schedule.json'): super().__init__() - data_dir = expanduser(Configuration.get()['data_dir']) self.events = {} self.event_lock = Lock() self.bus = bus self.is_running = True - self.schedule_file = join(data_dir, schedule_file) + old_schedule_path = join(expanduser(Configuration.get()['data_dir']), + schedule_file) + new_schedule_path = join( + BaseDirectory.load_first_config('mycroft'), schedule_file) + if isfile(old_schedule_path): + shutil.move(old_schedule_path, new_schedule_path) + self.schedule_file = new_schedule_path if self.schedule_file: self.load() diff --git a/mycroft/skills/skill_updater.py b/mycroft/skills/skill_updater.py index b856739a1cb1..013411ce50eb 100644 --- a/mycroft/skills/skill_updater.py +++ b/mycroft/skills/skill_updater.py @@ -17,6 +17,7 @@ import sys from datetime import datetime from time import time +from xdg import BaseDirectory from msm import MsmException @@ -97,9 +98,8 @@ def installed_skills_file_path(self): '.mycroft-skills' ) else: - self._installed_skills_file_path = os.path.expanduser( - '~/.mycroft/.mycroft-skills' - ) + self._installed_skills_file_path = os.path.join( + BaseDirectory.save_data_path('mycroft'), '.mycroft-skills') return self._installed_skills_file_path diff --git a/mycroft/util/file_utils.py b/mycroft/util/file_utils.py index 279c7d16a3e6..cfe4589d40ca 100644 --- a/mycroft/util/file_utils.py +++ b/mycroft/util/file_utils.py @@ -22,6 +22,7 @@ import psutil from stat import S_ISREG, ST_MTIME, ST_MODE, ST_SIZE import tempfile +from xdg import BaseDirectory import mycroft.configuration from .log import LOG @@ -33,10 +34,10 @@ def resolve_resource_file(res_name): Resource names are in the form: 'filename.ext' or 'path/filename.ext' - The system wil look for ~/.mycroft/res_name first, and - if not found will look at /opt/mycroft/res_name, - then finally it will look for res_name in the 'mycroft/res' - folder of the source code package. + The system wil look for $XDG_DATA_DIRS/mycroft/res_name first + (defaults to ~/.local/share/mycroft/res_name), and if not found will + look at /opt/mycroft/res_name, then finally it will look for res_name + in the 'mycroft/res' folder of the source code package. Example: With mycroft running as the user 'bob', if you called @@ -60,8 +61,14 @@ def resolve_resource_file(res_name): if os.path.isfile(res_name): return res_name - # Now look for ~/.mycroft/res_name (in user folder) - filename = os.path.expanduser("~/.mycroft/" + res_name) + # Now look for XDG_DATA_DIRS + for dir in BaseDirectory.load_data_paths('mycroft'): + filename = os.path.join(dir, res_name) + if os.path.isfile(filename): + return filename + + # Now look in the old user location + filename = os.path.join(os.path.expanduser('~'), '.mycroft', res_name) if os.path.isfile(filename): return filename diff --git a/mycroft/util/log.py b/mycroft/util/log.py index 0ea391f8f79c..788f6df1c37c 100644 --- a/mycroft/util/log.py +++ b/mycroft/util/log.py @@ -21,7 +21,7 @@ for use. The default log level of the logger created here can ONLY be set in -/etc/mycroft/mycroft.conf or ~/.mycroft/mycroft.conf +/etc/mycroft/mycroft.conf or ~/.config/mycroft/mycroft.conf The default log level can also be programatically be changed by setting the LOG.level parameter. @@ -31,10 +31,10 @@ import logging import sys -from os.path import isfile +from os.path import isfile, join +from xdg import BaseDirectory from mycroft.util.json_helper import load_commented_json, merge_dict -from mycroft.configuration.locations import SYSTEM_CONFIG, USER_CONFIG def getLogger(name="MYCROFT"): @@ -84,7 +84,10 @@ def init(cls): # Check configs manually, the Mycroft configuration system can't be # used since it uses the LOG system and would cause horrible cyclic # dependencies. - confs = [SYSTEM_CONFIG, USER_CONFIG] + confs = [] + for dir in BaseDirectory.load_config_paths('mycroft'): + confs.append(join(dir, 'mycroft.conf')) + confs.append('/etc/mycroft/mycroft.conf') config = {} for conf in confs: try: diff --git a/test/unittests/skills/test_skill_updater.py b/test/unittests/skills/test_skill_updater.py index 38d4083d7f2e..d7cc72e3e675 100644 --- a/test/unittests/skills/test_skill_updater.py +++ b/test/unittests/skills/test_skill_updater.py @@ -13,8 +13,9 @@ # limitations under the License. # """Unit tests for the SkillUpdater class.""" -from os import path +import os from time import sleep +from xdg import BaseDirectory from unittest.mock import Mock, patch, PropertyMock from mycroft.skills.skill_updater import SkillUpdater @@ -143,7 +144,8 @@ def test_installed_skills_path_not_virtual_env(self): os_patch.return_value = False updater = SkillUpdater(self.message_bus_mock) self.assertEqual( - path.expanduser('~/.mycroft/.mycroft-skills'), + os.path.join(BaseDirectory.save_data_path('mycroft'), + '.mycroft-skills'), updater.installed_skills_file_path ) diff --git a/test/unittests/util/commented.json b/test/unittests/util/commented.json index 87c7d3e2c2db..00a581f6df47 100644 --- a/test/unittests/util/commented.json +++ b/test/unittests/util/commented.json @@ -54,7 +54,7 @@ } }, "skills": { - "directory": "~/.mycroft/skills" + "directory": "~/.local/share/mycroft/skills" }, "server": { "url": "https://api.mycroft.ai", diff --git a/test/unittests/util/plain.json b/test/unittests/util/plain.json index f8c474139ce8..eec84631d886 100644 --- a/test/unittests/util/plain.json +++ b/test/unittests/util/plain.json @@ -36,7 +36,7 @@ } }, "skills": { - "directory": "~/.mycroft/skills" + "directory": "~/.local/share/mycroft/skills" }, "server": { "url": "https://api.mycroft.ai", From faf101bfcd0ab5784a88e9a62bb633ff8b72b3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Sun, 31 Jan 2021 08:56:10 +0100 Subject: [PATCH 56/68] Increase Jenkins timeout --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 30b69a6e7a76..4f67ec06813f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -52,7 +52,7 @@ pipeline { --label build=${JOB_NAME} \ -t voight-kampff-mark-1:${BRANCH_ALIAS} .' echo 'Running Mark I Voight-Kampff Test Suite' - timeout(time: 60, unit: 'MINUTES') + timeout(time: 90, unit: 'MINUTES') { sh 'mkdir -p $HOME/core/$BRANCH_ALIAS/allure' sh 'mkdir -p $HOME/core/$BRANCH_ALIAS/mycroft-logs' From 28017993c5fae3fedb0d8b8bd08d7db3de76faff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Sun, 21 Feb 2021 18:08:46 +0100 Subject: [PATCH 57/68] Restore system locations to mycroft.configuration Fixes issue with Timer skill --- mycroft/configuration/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mycroft/configuration/__init__.py b/mycroft/configuration/__init__.py index 8f7d1a4ed607..c7981df30958 100644 --- a/mycroft/configuration/__init__.py +++ b/mycroft/configuration/__init__.py @@ -14,3 +14,4 @@ # from .config import Configuration, LocalConf, RemoteConf from .locale import set_default_lf_lang +from .locations import SYSTEM_CONFIG, USER_CONFIG From 8e69d4616d7052e512ff5f90fe72fe62c62f3361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Wed, 5 May 2021 20:32:19 +0200 Subject: [PATCH 58/68] Correct resolution order for resolving log configs --- mycroft/util/log.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mycroft/util/log.py b/mycroft/util/log.py index 788f6df1c37c..460b349d889e 100644 --- a/mycroft/util/log.py +++ b/mycroft/util/log.py @@ -88,6 +88,7 @@ def init(cls): for dir in BaseDirectory.load_config_paths('mycroft'): confs.append(join(dir, 'mycroft.conf')) confs.append('/etc/mycroft/mycroft.conf') + confs = reversed(confs) config = {} for conf in confs: try: From 9029dc1f41d89d86c629ba45c750330b39755b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Mon, 5 Jul 2021 07:22:52 +0200 Subject: [PATCH 59/68] Slight cleanup - Make XDG usage more visible by using the xdg module prefix - fix overloaded python keywords - remove unused imports --- mycroft/client/enclosure/mark1/__init__.py | 5 +---- mycroft/client/speech/hotword_factory.py | 13 +++++++------ mycroft/client/text/text_client.py | 14 +++++++------- mycroft/configuration/config.py | 12 ++++++------ mycroft/configuration/locations.py | 4 ++-- mycroft/filesystem/__init__.py | 4 ++-- mycroft/skills/event_scheduler.py | 5 +++-- mycroft/skills/skill_updater.py | 5 +++-- mycroft/util/file_utils.py | 7 ++++--- mycroft/util/log.py | 6 +++--- 10 files changed, 38 insertions(+), 37 deletions(-) diff --git a/mycroft/client/enclosure/mark1/__init__.py b/mycroft/client/enclosure/mark1/__init__.py index 2095945705d0..5758ecdb694b 100644 --- a/mycroft/client/enclosure/mark1/__init__.py +++ b/mycroft/client/enclosure/mark1/__init__.py @@ -14,11 +14,8 @@ # import subprocess import time -import sys -import os from alsaaudio import Mixer from threading import Thread, Timer -from xdg import BaseDirectory import serial @@ -31,7 +28,7 @@ from mycroft.client.enclosure.mark1.mouth import EnclosureMouth from mycroft.enclosure.display_manager import \ init_display_manager_bus_connection -from mycroft.configuration import Configuration, LocalConf, USER_CONFIG +from mycroft.configuration import LocalConf, USER_CONFIG from mycroft.messagebus.message import Message from mycroft.util import play_wav, create_signal, connected, check_for_signal from mycroft.util.audio_test import record diff --git a/mycroft/client/speech/hotword_factory.py b/mycroft/client/speech/hotword_factory.py index 72a5e0ee3775..c6fb57a9bae6 100644 --- a/mycroft/client/speech/hotword_factory.py +++ b/mycroft/client/speech/hotword_factory.py @@ -26,7 +26,7 @@ from threading import Timer, Thread from time import time, sleep from urllib.error import HTTPError -from xdg import BaseDirectory +import xdg.BaseDirectory from petact import install_package import requests @@ -200,11 +200,12 @@ def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"): # might be stored in a different, unwriteable, location # Make sure we pick the key we need from wherever it's located, # but save to a writeable location only - local_conf = LocalConf(join(BaseDirectory.save_config_path('mycroft'), - 'mycroft.conf')) + local_conf = LocalConf( + join(xdg.BaseDirectory.save_config_path('mycroft'), 'mycroft.conf') + ) - for dir in BaseDirectory.load_config_paths('mycroft'): - conf = LocalConf(join(dir, 'mycroft.conf')) + for conf_dir in xdg.BaseDirectory.load_config_paths('mycroft'): + conf = LocalConf(join(conf_dir, 'mycroft.conf')) # If the current config contains the precise key use it, # otherwise continue to the next file if conf.get('precise', None) is not None: @@ -279,7 +280,7 @@ def folder(self): old_path = join(expanduser('~'), '.mycroft', 'precise') if os.path.isdir(old_path): return old_path - return join(BaseDirectory.save_data_path('mycroft', 'precise')) + return join(xdg.BaseDirectory.save_data_path('mycroft', 'precise')) @property def install_destination(self): diff --git a/mycroft/client/text/text_client.py b/mycroft/client/text/text_client.py index 8b2ca3d3ea4d..acb097d96a38 100644 --- a/mycroft/client/text/text_client.py +++ b/mycroft/client/text/text_client.py @@ -15,7 +15,7 @@ import sys import io from math import ceil -from xdg import BaseDirectory +import xdg.BaseDirectory from .gui_server import start_qml_gui @@ -185,16 +185,16 @@ def load_settings(): LOG.warning(" Note that this location is deprecated and will" + " not be used in the future") LOG.warning(" Please move it to " + - os.path.join(BaseDirectory.save_config_path('mycroft'), + os.path.join(xdg.BaseDirectory.save_config_path('mycroft'), filename)) config_file = path # Check XDG_CONFIG_DIR if config_file is None: - for dir in BaseDirectory.load_config_paths('mycroft'): - file = os.path.join(dir, filename) - if os.path.isfile(file): - config_file = file + for conf_dir in xdg.BaseDirectory.load_config_paths('mycroft'): + xdg_file = os.path.join(conf_dir, filename) + if os.path.isfile(xdg_file): + config_file = xdg_file break # Check /etc/mycroft @@ -228,7 +228,7 @@ def save_settings(): config["show_meter"] = show_meter config_file = os.path.join( - BaseDirectory.save_config_path("mycroft"), filename) + xdg.BaseDirectory.save_config_path("mycroft"), filename) with io.open(config_file, 'w') as f: f.write(str(json.dumps(config, ensure_ascii=False))) diff --git a/mycroft/configuration/config.py b/mycroft/configuration/config.py index 029c19bdb11d..9919ff3945ad 100644 --- a/mycroft/configuration/config.py +++ b/mycroft/configuration/config.py @@ -17,9 +17,9 @@ import re import json import inflection -from os.path import exists, isfile, join, expanduser, dirname +from os.path import exists, isfile, join from requests import RequestException -from xdg import BaseDirectory +import xdg.BaseDirectory from mycroft.util.json_helper import load_commented_json, merge_dict from mycroft.util.log import LOG @@ -128,7 +128,7 @@ class RemoteConf(LocalConf): def __init__(self, cache=None): super(RemoteConf, self).__init__(None) - cache = cache or join(BaseDirectory.save_cache_path('mycroft'), + cache = cache or join(xdg.BaseDirectory.save_cache_path('mycroft'), 'web_cache.json') from mycroft.api import is_paired if not is_paired(): @@ -218,8 +218,8 @@ def load_config_stack(configs=None, cache=False, remote=True): # Then use XDG config # This includes both the user config and # /etc/xdg/mycroft/mycroft.conf - for dir in BaseDirectory.load_config_paths('mycroft'): - configs.append(LocalConf(join(dir, 'mycroft.conf'))) + for conf_dir in xdg.BaseDirectory.load_config_paths('mycroft'): + configs.append(LocalConf(join(conf_dir, 'mycroft.conf'))) # Then check the old user config if isfile(OLD_USER_CONFIG): @@ -231,7 +231,7 @@ def load_config_stack(configs=None, cache=False, remote=True): LOG.warning(" Note that this location is deprecated and will" + " not be used in the future") LOG.warning(" Please move it to " + - BaseDirectory.save_config_path('mycroft')) + xdg.BaseDirectory.save_config_path('mycroft')) configs.append(LocalConf(OLD_USER_CONFIG)) # Then check the XDG user config diff --git a/mycroft/configuration/locations.py b/mycroft/configuration/locations.py index 77177736b244..b17e8b50864f 100644 --- a/mycroft/configuration/locations.py +++ b/mycroft/configuration/locations.py @@ -13,7 +13,7 @@ # limitations under the License. import os from os.path import join, dirname, expanduser, exists -from xdg import BaseDirectory +import xdg.BaseDirectory DEFAULT_CONFIG = join(dirname(__file__), 'mycroft.conf') SYSTEM_CONFIG = os.environ.get('MYCROFT_SYSTEM_CONFIG', @@ -21,7 +21,7 @@ # Make sure we support the old location still # Deprecated and will be removed eventually OLD_USER_CONFIG = join(expanduser('~'), '.mycroft/mycroft.conf') -USER_CONFIG = join(BaseDirectory.save_config_path('mycroft'), +USER_CONFIG = join(xdg.BaseDirectory.save_config_path('mycroft'), 'mycroft.conf') REMOTE_CONFIG = "mycroft.ai" WEB_CONFIG_CACHE = os.environ.get('MYCROFT_WEB_CACHE', diff --git a/mycroft/filesystem/__init__.py b/mycroft/filesystem/__init__.py index c0e5c4ae1cdc..27f84509ded6 100644 --- a/mycroft/filesystem/__init__.py +++ b/mycroft/filesystem/__init__.py @@ -15,7 +15,7 @@ import os import shutil from os.path import join, expanduser, isdir -from xdg import BaseDirectory +import xdg.BaseDirectory class FileSystemAccess: @@ -35,7 +35,7 @@ def __init_path(path): raise ValueError("path must be initialized as a non empty string") old_path = join(expanduser('~'), '.mycroft', path) - path = join(BaseDirectory.save_config_path('mycroft'), path) + path = join(xdg.BaseDirectory.save_config_path('mycroft'), path) # Migrate from the old location if it still exists if isdir(old_path): diff --git a/mycroft/skills/event_scheduler.py b/mycroft/skills/event_scheduler.py index 8d1582799fed..79f29de24ead 100644 --- a/mycroft/skills/event_scheduler.py +++ b/mycroft/skills/event_scheduler.py @@ -21,7 +21,7 @@ from datetime import datetime, timedelta from threading import Thread, Lock from os.path import isfile, join, expanduser -from xdg import BaseDirectory +import xdg.BaseDirectory from mycroft.configuration import Configuration from mycroft.messagebus.message import Message @@ -65,7 +65,8 @@ def __init__(self, bus, schedule_file='schedule.json'): old_schedule_path = join(expanduser(Configuration.get()['data_dir']), schedule_file) new_schedule_path = join( - BaseDirectory.load_first_config('mycroft'), schedule_file) + xdg.BaseDirectory.load_first_config('mycroft'), schedule_file + ) if isfile(old_schedule_path): shutil.move(old_schedule_path, new_schedule_path) self.schedule_file = new_schedule_path diff --git a/mycroft/skills/skill_updater.py b/mycroft/skills/skill_updater.py index 013411ce50eb..c4eafdfcef8c 100644 --- a/mycroft/skills/skill_updater.py +++ b/mycroft/skills/skill_updater.py @@ -17,7 +17,7 @@ import sys from datetime import datetime from time import time -from xdg import BaseDirectory +import xdg.BaseDirectory from msm import MsmException @@ -99,7 +99,8 @@ def installed_skills_file_path(self): ) else: self._installed_skills_file_path = os.path.join( - BaseDirectory.save_data_path('mycroft'), '.mycroft-skills') + xdg.BaseDirectory.save_data_path('mycroft'), + '.mycroft-skills') return self._installed_skills_file_path diff --git a/mycroft/util/file_utils.py b/mycroft/util/file_utils.py index cfe4589d40ca..ee441286f5a8 100644 --- a/mycroft/util/file_utils.py +++ b/mycroft/util/file_utils.py @@ -22,7 +22,7 @@ import psutil from stat import S_ISREG, ST_MTIME, ST_MODE, ST_SIZE import tempfile -from xdg import BaseDirectory +import xdg.BaseDirectory import mycroft.configuration from .log import LOG @@ -43,6 +43,7 @@ def resolve_resource_file(res_name): With mycroft running as the user 'bob', if you called ``resolve_resource_file('snd/beep.wav')`` it would return either: + '$XDG_DATA_DIRS/mycroft/beep.wav', '/home/bob/.mycroft/snd/beep.wav' or '/opt/mycroft/snd/beep.wav' or '.../mycroft/res/snd/beep.wav' @@ -62,8 +63,8 @@ def resolve_resource_file(res_name): return res_name # Now look for XDG_DATA_DIRS - for dir in BaseDirectory.load_data_paths('mycroft'): - filename = os.path.join(dir, res_name) + for conf_dir in xdg.BaseDirectory.load_data_paths('mycroft'): + filename = os.path.join(conf_dir, res_name) if os.path.isfile(filename): return filename diff --git a/mycroft/util/log.py b/mycroft/util/log.py index 460b349d889e..8c6c2fa010b9 100644 --- a/mycroft/util/log.py +++ b/mycroft/util/log.py @@ -32,7 +32,7 @@ import sys from os.path import isfile, join -from xdg import BaseDirectory +import xdg.BaseDirectory from mycroft.util.json_helper import load_commented_json, merge_dict @@ -85,8 +85,8 @@ def init(cls): # used since it uses the LOG system and would cause horrible cyclic # dependencies. confs = [] - for dir in BaseDirectory.load_config_paths('mycroft'): - confs.append(join(dir, 'mycroft.conf')) + for conf_dir in xdg.BaseDirectory.load_config_paths('mycroft'): + confs.append(join(conf_dir, 'mycroft.conf')) confs.append('/etc/mycroft/mycroft.conf') confs = reversed(confs) config = {} From 3fd96cf71b25aed79310cb4e9a0edf4fb185609d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Sat, 10 Jul 2021 17:40:34 +0200 Subject: [PATCH 60/68] WIP Review comments - Add TODO for 22.02 to remove the compatibility code - Make Warning a single Log statement - mycroft-config script now uses XDG-environment variable - Remove redundant code - Replace hard coded references to ~/.config - Explicitly remove new path before move of "filesystem" (if needed) --- bin/mycroft-config | 4 +-- mycroft/client/enclosure/mark1/__init__.py | 5 +++- mycroft/client/speech/hotword_factory.py | 2 +- mycroft/configuration/config.py | 33 ++++++++++------------ mycroft/configuration/locations.py | 2 ++ mycroft/filesystem/__init__.py | 3 ++ 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/bin/mycroft-config b/bin/mycroft-config index 981f2c5d0c65..6598c08cc94c 100755 --- a/bin/mycroft-config +++ b/bin/mycroft-config @@ -95,11 +95,11 @@ function validate_config_file() { return $result } -_conf_file="~/.config/mycroft/mycroft.conf" +_conf_file="${XDG_CONFIG_HOME:-$HOME/.config}/mycroft/mycroft.conf" function name_to_path() { case ${1} in "system") _conf_file="/etc/mycroft/mycroft.conf" ;; - "user") _conf_file=$(readlink -f ~/.config/mycroft/mycroft.conf) ;; + "user") _conf_file=$(readlink -f ${XDG_CONFIG_HOME:-$HOME/.config}/mycroft/mycroft.conf) ;; "default") _conf_file="$DIR/../mycroft/configuration/mycroft.conf" ;; "remote") _conf_file="$HOME/.cache/mycroft/web_cache.json" ;; diff --git a/mycroft/client/enclosure/mark1/__init__.py b/mycroft/client/enclosure/mark1/__init__.py index 5758ecdb694b..e5be8d633749 100644 --- a/mycroft/client/enclosure/mark1/__init__.py +++ b/mycroft/client/enclosure/mark1/__init__.py @@ -19,6 +19,8 @@ import serial +import xdg.BaseDirectory + import mycroft.dialog from mycroft.client.enclosure.base import Enclosure from mycroft.api import has_been_paired @@ -164,7 +166,8 @@ def process(self, data): self.bus.emit(Message("speak", { 'utterance': mycroft.dialog.get("reset to factory defaults")})) subprocess.call( - 'rm ~/.config/mycroft/identity/identity2.json', + (f'rm {xdg.BaseDirectory.save_config_path("mycroft")}' + '/mycroft/identity/identity2.json'), shell=True) subprocess.call( 'rm ~/.mycroft/identity/identity2.json', diff --git a/mycroft/client/speech/hotword_factory.py b/mycroft/client/speech/hotword_factory.py index c6fb57a9bae6..a059dfb9d2e4 100644 --- a/mycroft/client/speech/hotword_factory.py +++ b/mycroft/client/speech/hotword_factory.py @@ -280,7 +280,7 @@ def folder(self): old_path = join(expanduser('~'), '.mycroft', 'precise') if os.path.isdir(old_path): return old_path - return join(xdg.BaseDirectory.save_data_path('mycroft', 'precise')) + return xdg.BaseDirectory.save_data_path('mycroft', 'precise') @property def install_destination(self): diff --git a/mycroft/configuration/config.py b/mycroft/configuration/config.py index 9919ff3945ad..82ac29b56df6 100644 --- a/mycroft/configuration/config.py +++ b/mycroft/configuration/config.py @@ -24,7 +24,7 @@ from mycroft.util.json_helper import load_commented_json, merge_dict from mycroft.util.log import LOG -from .locations import DEFAULT_CONFIG, OLD_USER_CONFIG, USER_CONFIG +from .locations import DEFAULT_CONFIG, USER_CONFIG, OLD_USER_CONFIG from .locations import SYSTEM_CONFIG @@ -170,6 +170,17 @@ def __init__(self, cache=None): self.load_local(cache) +def _log_old_location_deprecation(): + LOG.warning("\n ===============================================\n" + " == DEPRECATION WARNING ==\n" + " ===============================================\n" + f" You still have a config file at {OLD_USER_CONFIG}\n" + " Note that this location is deprecated and will" + " not be used in the future\n" + " Please move it to " + f"{xdg.BaseDirectory.save_config_path('mycroft')}") + + class Configuration: """Namespace for operations on the configuration singleton.""" __config = {} # Cached config @@ -185,9 +196,7 @@ def get(configs=None, cache=True, remote=True): Args: configs (list): List of configuration dicts cache (boolean): True if the result should be cached - remote (boolean): False if the Mycroft Home settings shouldn't - be loaded - + remote (boolean): False if the Remote settings shouldn't be loaded Returns: (dict) configuration dictionary. @@ -210,7 +219,7 @@ def load_config_stack(configs=None, cache=False, remote=True): (dict) merged dict of all configuration files """ if not configs: - configs = configs or [] + configs = [] # First use the patched config configs.append(Configuration.__patch) @@ -223,21 +232,9 @@ def load_config_stack(configs=None, cache=False, remote=True): # Then check the old user config if isfile(OLD_USER_CONFIG): - LOG.warning(" ===============================================") - LOG.warning(" == DEPRECATION WARNING ==") - LOG.warning(" ===============================================") - LOG.warning(" You still have a config file at " + - OLD_USER_CONFIG) - LOG.warning(" Note that this location is deprecated and will" + - " not be used in the future") - LOG.warning(" Please move it to " + - xdg.BaseDirectory.save_config_path('mycroft')) + _log_old_location_deprecation() configs.append(LocalConf(OLD_USER_CONFIG)) - # Then check the XDG user config - if isfile(USER_CONFIG): - configs.append(LocalConf(USER_CONFIG)) - # Then use remote config if remote: configs.append(RemoteConf()) diff --git a/mycroft/configuration/locations.py b/mycroft/configuration/locations.py index b17e8b50864f..98c5b78f37b2 100644 --- a/mycroft/configuration/locations.py +++ b/mycroft/configuration/locations.py @@ -18,11 +18,13 @@ DEFAULT_CONFIG = join(dirname(__file__), 'mycroft.conf') SYSTEM_CONFIG = os.environ.get('MYCROFT_SYSTEM_CONFIG', '/etc/mycroft/mycroft.conf') +# TODO: remove in 22.02 # Make sure we support the old location still # Deprecated and will be removed eventually OLD_USER_CONFIG = join(expanduser('~'), '.mycroft/mycroft.conf') USER_CONFIG = join(xdg.BaseDirectory.save_config_path('mycroft'), 'mycroft.conf') + REMOTE_CONFIG = "mycroft.ai" WEB_CONFIG_CACHE = os.environ.get('MYCROFT_WEB_CACHE', '/var/tmp/mycroft_web_cache.json') diff --git a/mycroft/filesystem/__init__.py b/mycroft/filesystem/__init__.py index 27f84509ded6..c63a17f46a8f 100644 --- a/mycroft/filesystem/__init__.py +++ b/mycroft/filesystem/__init__.py @@ -38,7 +38,10 @@ def __init_path(path): path = join(xdg.BaseDirectory.save_config_path('mycroft'), path) # Migrate from the old location if it still exists + # TODO: remove in 22.02 if isdir(old_path): + if isdir(path): + shutil.rmtree(path) shutil.move(old_path, path) if not isdir(path): From 87d22d30a49891056b5659a488c37da121865f2c Mon Sep 17 00:00:00 2001 From: jarbasal Date: Wed, 7 Jul 2021 15:38:40 +0100 Subject: [PATCH 61/68] fix cyclic imports with LOG The mycroft.util.log module called LOG.init() which needs to read the config, the configuration module imports the log system to log causing cyclic import issues. This moves the LOG.init() call out of the log module making it possible to use the normal config system to init the logs and does a slight re-arrangement so that the uninited log can be used. --- mycroft/__init__.py | 3 +++ mycroft/util/log.py | 31 +++++-------------------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/mycroft/__init__.py b/mycroft/__init__.py index 90e3494c3e03..9a6e4581fedc 100644 --- a/mycroft/__init__.py +++ b/mycroft/__init__.py @@ -20,6 +20,7 @@ from mycroft.skills import (MycroftSkill, FallbackSkill, intent_handler, intent_file_handler) from mycroft.skills.intent_service import AdaptIntent +from mycroft.util.log import LOG MYCROFT_ROOT_PATH = abspath(join(dirname(__file__), '..')) @@ -33,3 +34,5 @@ 'intent_handler', 'intent_file_handler', 'AdaptIntent'] + +LOG.init() # read log level from config diff --git a/mycroft/util/log.py b/mycroft/util/log.py index 8c6c2fa010b9..b000f0ff0192 100644 --- a/mycroft/util/log.py +++ b/mycroft/util/log.py @@ -31,10 +31,7 @@ import logging import sys -from os.path import isfile, join -import xdg.BaseDirectory - -from mycroft.util.json_helper import load_commented_json, merge_dict +import mycroft def getLogger(name="MYCROFT"): @@ -65,7 +62,7 @@ class LOG: _custom_name = None handler = None - level = None + level = logging.getLevelName('INFO') # Copy actual logging methods from logging.Logger # Usage: LOG.debug(message) @@ -80,24 +77,6 @@ def init(cls): """ Initializes the class, sets the default log level and creates the required handlers. """ - - # Check configs manually, the Mycroft configuration system can't be - # used since it uses the LOG system and would cause horrible cyclic - # dependencies. - confs = [] - for conf_dir in xdg.BaseDirectory.load_config_paths('mycroft'): - confs.append(join(conf_dir, 'mycroft.conf')) - confs.append('/etc/mycroft/mycroft.conf') - confs = reversed(confs) - config = {} - for conf in confs: - try: - merge_dict(config, - load_commented_json(conf) if isfile(conf) else {}) - except Exception as e: - print('couldn\'t load {}: {}'.format(conf, str(e))) - - cls.level = logging.getLevelName(config.get('log_level', 'INFO')) log_message_format = ( '{asctime} | {levelname:8} | {process:5} | {name} | {message}' ) @@ -107,6 +86,9 @@ def init(cls): cls.handler = logging.StreamHandler(sys.stdout) cls.handler.setFormatter(formatter) + config = mycroft.configuration.Configuration.get(remote=False) + cls.level = logging.getLevelName(config.get('log_level', 'INFO')) + # Enable logging in external modules cls.create_logger('').setLevel(cls.level) @@ -148,6 +130,3 @@ def _log(cls, func, *args, **kwargs): name = 'Mycroft' func(cls.create_logger(name), *args, **kwargs) - - -LOG.init() From 4258e2f2ed7fbe8691a62ae4ad916a75999c3c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Sun, 18 Jul 2021 21:13:28 +0200 Subject: [PATCH 62/68] Reorder imports of mycroft.configuration --- mycroft/configuration/config.py | 5 +++-- mycroft/configuration/locations.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mycroft/configuration/config.py b/mycroft/configuration/config.py index 82ac29b56df6..ddfc98733b2d 100644 --- a/mycroft/configuration/config.py +++ b/mycroft/configuration/config.py @@ -14,10 +14,11 @@ # limitations under the License. # -import re -import json import inflection +import json from os.path import exists, isfile, join +import re + from requests import RequestException import xdg.BaseDirectory diff --git a/mycroft/configuration/locations.py b/mycroft/configuration/locations.py index 98c5b78f37b2..8b9590094078 100644 --- a/mycroft/configuration/locations.py +++ b/mycroft/configuration/locations.py @@ -13,6 +13,7 @@ # limitations under the License. import os from os.path import join, dirname, expanduser, exists + import xdg.BaseDirectory DEFAULT_CONFIG = join(dirname(__file__), 'mycroft.conf') From 8be5e9ba722771334b38c95d0b88be013af88678 Mon Sep 17 00:00:00 2001 From: Aditya Mehra Date: Fri, 6 Aug 2021 14:32:52 +0930 Subject: [PATCH 63/68] add fullscreen fix --- mycroft/res/ui/WebViewHtmlFrame.qml | 3 +++ mycroft/res/ui/WebViewUrlFrame.qml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/mycroft/res/ui/WebViewHtmlFrame.qml b/mycroft/res/ui/WebViewHtmlFrame.qml index 67710441de68..25a57d24023d 100644 --- a/mycroft/res/ui/WebViewHtmlFrame.qml +++ b/mycroft/res/ui/WebViewHtmlFrame.qml @@ -61,6 +61,9 @@ Item { interactionBar.isRequested = true; } + onFullScreenRequested: { + request.accept() + } } Popup { diff --git a/mycroft/res/ui/WebViewUrlFrame.qml b/mycroft/res/ui/WebViewUrlFrame.qml index 6a1d5210213e..db60ae12b330 100644 --- a/mycroft/res/ui/WebViewUrlFrame.qml +++ b/mycroft/res/ui/WebViewUrlFrame.qml @@ -55,6 +55,10 @@ Item { interactionBar.interactionItem.requestedFeature = feature; interactionBar.isRequested = true; } + + onFullScreenRequested: { + request.accept() + } } Popup { From a65a3f7770e8ec092cedf582d963c0cb58c7e350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Thu, 5 Aug 2021 17:03:34 +0200 Subject: [PATCH 64/68] Disable auto updates of skills during VK test run --- test/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Dockerfile b/test/Dockerfile index 0ae41a48485a..c76c02b68eb7 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -96,7 +96,7 @@ FROM core_builder as voight_kampff_builder ARG platform # Setup a dummy TTS backend for the audio process RUN mkdir /etc/mycroft -RUN echo '{"tts": {"module": "dummy"}}' > /etc/mycroft/mycroft.conf +RUN echo '{"tts": {"module": "dummy"}, "skills": {"auto_update": false}}' > /etc/mycroft/mycroft.conf RUN mkdir ~/.mycroft/allure-result # The behave feature files for a skill are defined within the skill's From eadb5c9985b174290fae8a258f9bf9670317b663 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Mon, 9 Aug 2021 15:55:04 +0930 Subject: [PATCH 65/68] Provide AdaptIntent from mycroft.skills This is convenience for Skill developers who can now import all standard items from a single level. Eg: from mycroft.skills import MycroftSkill, intent_handler, AdaptIntent --- mycroft/skills/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mycroft/skills/__init__.py b/mycroft/skills/__init__.py index 2deb9d2e4b3a..c6617da7b92e 100644 --- a/mycroft/skills/__init__.py +++ b/mycroft/skills/__init__.py @@ -20,6 +20,7 @@ from .mycroft_skill import (MycroftSkill, intent_handler, intent_file_handler, resting_screen_handler, skill_api_method) +from .intent_service import AdaptIntent from .fallback_skill import FallbackSkill from .common_iot_skill import CommonIoTSkill from .common_play_skill import CommonPlaySkill, CPSMatchLevel From 0f8222eef4b19374d9dfd717d1ea8eacbd859820 Mon Sep 17 00:00:00 2001 From: Philippe Coval Date: Thu, 5 Aug 2021 18:12:11 +0200 Subject: [PATCH 66/68] scripts: Support busybox chown Those extra options are not enabled in busybox (at least not the configured version in poky yocto distribution) This will also help to support systems without coreutils, Forwarded: https://github.com/MycroftAI/mycroft-core/pull/2966 Relate-to: https://github.com/MycroftAI/mycroft-core/pull/2686 Signed-off-by: Philippe Coval --- scripts/mycroft-use.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/mycroft-use.sh b/scripts/mycroft-use.sh index a00d2bc5461d..c73afb58b11d 100755 --- a/scripts/mycroft-use.sh +++ b/scripts/mycroft-use.sh @@ -88,10 +88,10 @@ function restore_init_scripts() { sudo sh -c 'cat /etc/init.d/mycroft-admin-service.original > /etc/init.d/mycroft-admin-service' sudo rm /etc/init.d/*.original chown mycroft:mycroft /home/mycroft/.mycroft/identity/identity2.json - sudo chown -Rvf mycroft:mycroft /var/log/mycroft* - sudo chown -Rvf mycroft:mycroft /tmp/mycroft - sudo chown -Rvf mycroft:mycroft /var/run/mycroft* - sudo chown -Rvf mycroft:mycroft /opt/mycroft + sudo chown -R mycroft:mycroft /var/log/mycroft* + sudo chown -R mycroft:mycroft /tmp/mycroft + sudo chown -R mycroft:mycroft /var/run/mycroft* + sudo chown -R mycroft:mycroft /opt/mycroft sudo chown mycroft:mycroft /var/tmp/mycroft_web_cache.json # reload daemon scripts @@ -144,10 +144,10 @@ function github_init_scripts() { sudo ln -s /home/mycroft/.mycroft/identity ${HOME}/.mycroft/ fi - sudo chown -Rvf ${user}:${user} /var/log/mycroft* - sudo chown -Rvf ${user}:${user} /var/run/mycroft* - sudo chown -Rvf ${user}:${user} /tmp/mycroft - sudo chown -Rvf ${user}:${user} /var/tmp/mycroft_web_cache.json + sudo chown -R ${user}:${user} /var/log/mycroft* + sudo chown -R ${user}:${user} /var/run/mycroft* + sudo chown -R ${user}:${user} /tmp/mycroft + sudo chown -R ${user}:${user} /var/tmp/mycroft_web_cache.json # reload daemon scripts sudo systemctl daemon-reload From ac17d7899a357cc21876520b95ccc6e29935c6c1 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Mon, 9 Aug 2021 06:29:20 -0400 Subject: [PATCH 67/68] Install pulseaudio-utils on Fedora Needed for `paplay`. --- dev_setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_setup.sh b/dev_setup.sh index 3bbfabc2470c..ab6c6e7a9bd7 100755 --- a/dev_setup.sh +++ b/dev_setup.sh @@ -366,7 +366,7 @@ function open_suse_install() { function fedora_install() { - $SUDO dnf install -y git python3 python3-devel python3-pip python3-setuptools python3-virtualenv pygobject3-devel libtool libffi-devel openssl-devel autoconf bison swig glib2-devel portaudio-devel mpg123 mpg123-plugins-pulseaudio screen curl pkgconfig libicu-devel automake libjpeg-turbo-devel fann-devel gcc-c++ redhat-rpm-config jq make + $SUDO dnf install -y git python3 python3-devel python3-pip python3-setuptools python3-virtualenv pygobject3-devel libtool libffi-devel openssl-devel autoconf bison swig glib2-devel portaudio-devel mpg123 mpg123-plugins-pulseaudio screen curl pkgconfig libicu-devel automake libjpeg-turbo-devel fann-devel gcc-c++ redhat-rpm-config jq make pulseaudio-utils } From 13539d3397d4577b6f1e1cfbf02c5146f5123bd6 Mon Sep 17 00:00:00 2001 From: devs-mycroft Date: Fri, 27 Aug 2021 03:02:54 +0000 Subject: [PATCH 68/68] Version bump from 21.2.0 to 21.2.1 --- mycroft/version/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mycroft/version/__init__.py b/mycroft/version/__init__.py index 4a452e86456e..43d7d57df931 100644 --- a/mycroft/version/__init__.py +++ b/mycroft/version/__init__.py @@ -25,7 +25,7 @@ # START_VERSION_BLOCK CORE_VERSION_MAJOR = 21 CORE_VERSION_MINOR = 2 -CORE_VERSION_BUILD = 0 +CORE_VERSION_BUILD = 1 # END_VERSION_BLOCK CORE_VERSION_TUPLE = (CORE_VERSION_MAJOR,