diff --git a/mycroft/__init__.py b/mycroft/__init__.py index 779c86fc517d..c5ce6df995e3 100644 --- a/mycroft/__init__.py +++ b/mycroft/__init__.py @@ -17,9 +17,9 @@ from ovos_bus_client.message import Message from ovos_utils.intents import AdaptIntent, IntentBuilder, Intent -from mycroft.skills.context import adds_context, removes_context -from mycroft.skills import (MycroftSkill, FallbackSkill, - intent_handler, intent_file_handler) +from ovos_workshop.decorators import intent_handler, intent_file_handler, adds_context, removes_context +from ovos_workshop.skills import MycroftSkill +from ovos_workshop.skills.fallback import FallbackSkill from ovos_utils.log import LOG diff --git a/mycroft/client/enclosure/base.py b/mycroft/client/enclosure/base.py index 3e56e6755c2b..a0410893f1e9 100644 --- a/mycroft/client/enclosure/base.py +++ b/mycroft/client/enclosure/base.py @@ -9,4 +9,4 @@ """ -from mycroft.deprecated.enclosure.base import * +from mycroft.deprecated.enclosure.base import Enclosure diff --git a/mycroft/client/enclosure/generic/__init__.py b/mycroft/client/enclosure/generic/__init__.py index 3a702a50478f..087ca65a2c2d 100644 --- a/mycroft/client/enclosure/generic/__init__.py +++ b/mycroft/client/enclosure/generic/__init__.py @@ -8,4 +8,4 @@ to be a drop in replacement for mycroft-core """ -from mycroft.deprecated.enclosure.generic import * \ No newline at end of file +from mycroft.deprecated.enclosure.generic import EnclosureGeneric \ No newline at end of file diff --git a/mycroft/client/enclosure/mark1/__init__.py b/mycroft/client/enclosure/mark1/__init__.py index b72297c48890..6f08c455bba7 100644 --- a/mycroft/client/enclosure/mark1/__init__.py +++ b/mycroft/client/enclosure/mark1/__init__.py @@ -8,4 +8,4 @@ to be a drop in replacement for mycroft-core """ -from mycroft.deprecated.enclosure.mark1 import * +from mycroft.deprecated.enclosure.mark1 import EnclosureEyes, EnclosureArduino, EnclosureMouth, Enclosure, EnclosureReader, EnclosureWriter, EnclosureMark1 diff --git a/mycroft/client/enclosure/mark1/arduino.py b/mycroft/client/enclosure/mark1/arduino.py index ba8292271f8f..e3a7cb6be74c 100644 --- a/mycroft/client/enclosure/mark1/arduino.py +++ b/mycroft/client/enclosure/mark1/arduino.py @@ -8,4 +8,4 @@ to be a drop in replacement for mycroft-core """ -from mycroft.deprecated.enclosure.mark1.arduino import * +from mycroft.deprecated.enclosure.mark1.arduino import EnclosureArduino diff --git a/mycroft/client/enclosure/mark1/eyes.py b/mycroft/client/enclosure/mark1/eyes.py index 38754b72adf1..3e2dc159b045 100644 --- a/mycroft/client/enclosure/mark1/eyes.py +++ b/mycroft/client/enclosure/mark1/eyes.py @@ -8,4 +8,4 @@ to be a drop in replacement for mycroft-core """ -from mycroft.deprecated.enclosure.mark1.eyes import * +from mycroft.deprecated.enclosure.mark1.eyes import EnclosureEyes diff --git a/mycroft/client/enclosure/mark1/mouth.py b/mycroft/client/enclosure/mark1/mouth.py index 68d6514aa314..1354adf4b1d4 100644 --- a/mycroft/client/enclosure/mark1/mouth.py +++ b/mycroft/client/enclosure/mark1/mouth.py @@ -8,4 +8,4 @@ to be a drop in replacement for mycroft-core """ -from mycroft.deprecated.enclosure.mark1.mouth import * +from mycroft.deprecated.enclosure.mark1.mouth import EnclosureMouth diff --git a/mycroft/client/enclosure/mark2/__init__.py b/mycroft/client/enclosure/mark2/__init__.py index ed1e08be4093..4499a34cbdb2 100644 --- a/mycroft/client/enclosure/mark2/__init__.py +++ b/mycroft/client/enclosure/mark2/__init__.py @@ -8,4 +8,4 @@ to be a drop in replacement for mycroft-core """ -from mycroft.deprecated.enclosure.mark2 import * +from mycroft.deprecated.enclosure.mark2 import EnclosureMark2 diff --git a/mycroft/configuration/config.py b/mycroft/configuration/config.py index 3371ad7621e4..6a2347249431 100644 --- a/mycroft/configuration/config.py +++ b/mycroft/configuration/config.py @@ -1,4 +1,4 @@ # backwards compat - moved to own python package -from ovos_config.config import * -from ovos_config.locations import * +from ovos_config.config import Configuration, MycroftUserConfig, MycroftDefaultConfig, MycroftSystemConfig, RemoteConf, LocalConf +from ovos_config.locations import OLD_USER_CONFIG, DEFAULT_CONFIG, SYSTEM_CONFIG, REMOTE_CONFIG, USER_CONFIG, WEB_CONFIG_CACHE diff --git a/mycroft/configuration/locale.py b/mycroft/configuration/locale.py index 89a6dba7fd8c..f6bc6556b29c 100644 --- a/mycroft/configuration/locale.py +++ b/mycroft/configuration/locale.py @@ -1,2 +1,2 @@ # backwards compat - moved to own python package -from ovos_config.locale import * +from ovos_config.locale import setup_locale, set_default_tz, set_default_lang, set_default_lf_lang, load_language, load_languages, get_default_lang, get_default_tz, get_config_tz, get_primary_lang_code diff --git a/mycroft/configuration/locations.py b/mycroft/configuration/locations.py index b41348586a65..53bc36c40147 100644 --- a/mycroft/configuration/locations.py +++ b/mycroft/configuration/locations.py @@ -1,2 +1,2 @@ # backwards compat - moved to own python package -from ovos_config.locations import * +from ovos_config.locations import DEFAULT_CONFIG, OLD_USER_CONFIG, SYSTEM_CONFIG, REMOTE_CONFIG, USER_CONFIG, WEB_CONFIG_CACHE diff --git a/mycroft/configuration/mycroft.conf b/mycroft/configuration/mycroft.conf deleted file mode 100644 index db6f331354df..000000000000 --- a/mycroft/configuration/mycroft.conf +++ /dev/null @@ -1,624 +0,0 @@ -{ - // Definition and documentation of all variables used by mycroft-core. - // - // Settings seen here are considered DEFAULT. Settings can also be - // 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 '~/.config/mycroft/mycroft.conf'). - // - // The load order of settings is: - // DEFAULT - // REMOTE - // SYSTEM - // USER - // - // The Override: comments below indicates where these settings are generally - // set outside of this file. The load order is always followed, so an - // individual systems can still apply changes at the SYSTEM or USER levels. - - // Language used for speech-to-text and text-to-speech. - // Code is a BCP-47 identifier (https://tools.ietf.org/html/bcp47), lowercased - // TODO: save unmodified, lowercase upon demand - "lang": "en-us", - - // Measurement units, either 'metric' or 'english' - // Override: REMOTE - "system_unit": "metric", - - // Time format, either 'half' (e.g. "11:37 pm") or 'full' (e.g. "23:37") - // Override: REMOTE - "time_format": "half", - - // Date format, either 'MDY' (e.g. "11-29-1978") or 'DMY' (e.g. "29-11-1978") - // Override: REMOTE - "date_format": "MDY", - - // Whether to opt in to data collection - // Override: REMOTE - "opt_in": false, - - // Play a beep when system begins to listen? - "confirm_listening": true, - - // File locations of sounds to play for system events - "sounds": { - "start_listening": "snd/start_listening.wav", - "end_listening": "snd/end_listening.wav", - "acknowledge": "snd/acknowledge.mp3", - "error": "snd/error.mp3" - }, - - // Mechanism used to play WAV audio files - // Override: SYSTEM - "play_wav_cmdline": "paplay %1 --stream-name=mycroft-voice", - - // Mechanism used to play MP3 audio files - // Override: SYSTEM - "play_mp3_cmdline": "mpg123 %1", - - // Mechanism used to play OGG audio files - // Override: SYSTEM - "play_ogg_cmdline": "ogg123 -q %1", - - // Location where the system resides - // NOTE: Although this is set here, an Enclosure can override the value. - // For example a mycroft-core running in a car could use the GPS. - // Override: REMOTE - "location": { - "city": { - "code": "Lawrence", - "name": "Lawrence", - "state": { - "code": "KS", - "name": "Kansas", - "country": { - "code": "US", - "name": "United States" - } - } - }, - "coordinate": { - "latitude": 38.971669, - "longitude": -95.23525 - }, - "timezone": { - "code": "America/Chicago", - "name": "Central Standard Time", - "dstOffset": 3600000, - "offset": -21600000 - } - }, - - // default to $XDG_DATA_DIRS/mycroft - // "data_dir": "/opt/mycroft", - - // by default, files persist across reboots, but be careful with space usage! - // TIP: use "/dev/shm/mycroft/cache" if you want to keep the cache in RAM or - // use "/tmp/mycroft/cache" to remove files upon reboot - // default to $XDG_DATA_DIRS/$BASE_FOLDER where BASE_FOLDER is read from ovos.conf (default "mycroft") - // "cache_path": "/tmp/mycroft/cache", - - # emit mycroft.ready signal when all these conditions are met - # different setups will have different needs - # eg, a server does not care about audio - # pairing -> device is paired - # internet -> device is connected to the internet - NOT IMPLEMENTED - # skills -> skills reported ready - # speech -> stt reported ready - # audio -> audio playback reported ready - # gui -> gui websocket reported ready - NOT IMPLEMENTED - # enclosure -> enclosure/HAL reported ready - NOT IMPLEMENTED - # network_skills -> skills with network requirements - # internet_skills -> skills with internet requirements - "ready_settings": ["skills"], - - // General skill values - "skills": { - - // don't start loading skills until internet is detected - // this config value is not present in mycroft-core (internet is required) - // ovos-core expects that some instances will be running fully offline - // DEPRECATED: specify skill loading requirements in individual skills instead - "wait_for_internet": false, - - // relative to "data_dir" - "directory": "skills", - - // used by selene for marketplace integration in web interface - // NOTE: selene seems to work fine without this data - // no need to advertise installed skills if they dont have settings - // this data was tightly coupled to msm and is now mostly useless for selene - "upload_skill_manifest": false, - - // if "sync_skill_settings" is enabled in "server" section - // should skill settingg changes on device be upload to selene? - // NOTE: this should be true, but it was removed (bug ?) in selene at some point - // old endpoints are however still available so functionality has been restored here - // it is only configurable in case you want to retain "old" mycroft-core behaviour - // or in case selene deprecates the old endpoint - "sync2way": true, - // values in skill settings missing in settingsmeta wont show up in selene - // this flag auto generates settingsmeta on the fly - "autogen_meta": true, - - // blacklisted skills to not load - // NB: This is the basename() of the directory where the skill lives, so if - // the skill you want to blacklist is in /usr/share/mycroft/skills/mycroft-alarm.mycroftai/ - // then you should write `["mycroft-alarm.mycroftai"]` below. - "blacklisted_skills": [], - - // priority skills to be loaded first - // DEPRECATED: specify skill loading requirements in individual skills instead - // This setting does not affect skills installed via setup.py - "priority_skills": [], - - // fallback skill configuration - "fallbacks": { - // you can add skill_id: priority to override the developer defined - // priority of those skills, this allows customization - // of unknown intent handling for default_skills + user preferences - "fallback_priorities": { - // "skill_id": 10 - }, - // fallback skill handling has 3 modes of operations: - // - "accept_all" # default mycroft-core behavior - // - "whitelist" # only call fallback for skills in "fallback_whitelist" - // - "blacklist" # only call fallback for skills NOT in "fallback_blacklist" - "fallback_mode": "accept_all", - "fallback_whitelist": [], - "fallback_blacklist": [] - }, - - // converse stage configuration - "converse": { - // the default number of seconds a skill remains active - // if the user does not interact with the skill in this timespan it - // will be deactivated, default 5 minutes (same as mycroft) - "timeout": 300, - // override of "skill_timeouts" per skill_id - "skill_timeouts": {}, - - // conversational mode has 3 modes of operations: - // - "accept_all" # default mycroft-core behavior - // - "whitelist" # only call converse for skills in "converse_whitelist" - // - "blacklist" # only call converse for skills NOT in "converse_blacklist" - "converse_mode": "accept_all", - "converse_whitelist": [], - "converse_blacklist": [], - - // converse activation has 4 modes of operations: - // - "accept_all" # default mycroft-core behavior, any skill can - // # activate itself unconditionally - // - "priority" # skills can only activate themselves if no skill with - // # higher priority is active - // - "whitelist" # only skills in "converse_whitelist" can activate themselves - // - "blacklist" # only skills NOT in converse "converse_blacklist" can activate themselves - // NOTE: this does not apply for regular skill activation, only to skill - // initiated activation requests - "converse_activation": "accept_all", - - // number of consecutive times a skill is allowed to activate itself - // per minute, -1 for no limit (default), 0 to disable self-activation - "max_activations": -1, - // override of "max_activations" per skill_id - "skill_activations": {}, - - // if false only skills can activate themselves - // if true any skill can activate any other skill - "cross_activation": true, - - // if false only skills can deactivate themselves - // if true any skill can deactivate any other skill - // NOTE: skill deactivation is not yet implemented - "cross_deactivation": true, - - // you can add skill_id: priority to override the developer defined - // priority of those skills, currently there is no api for skills to - // define their default priority, it is assumed to be 50, the only current - // canonical source for converse priorities is this setting - "converse_priorities": { - // "skill_id": 10 - } - } - - }, - - // system administrators can define different constraints in how configurations are loaded - // this is a mechanism to require root to change these config options - "system": { - // do not allow users to tamper with settings at all - "disable_user_config": false, - // do not allow remote backend to tamper with settings at all - "disable_remote_config": false, - // protected keys are individual settings that can not be changed at remote/user level - // nested dictionary keys can be defined with "key1:key2" syntax, - // eg. {"a": {"b": True, "c": False}} - // to protect "c" you would enter "a:c" in the section below - "protected_keys": { - // NOTE: selene backend expects "opt_in" to be changeable in their web ui - // that effectively gives them a means to enable spying without your input - // Mycroft AI can be trusted, but you dont need to anymore! - // The other keys are not currently populated by the remote backend - // they are defined for protection against bugs and for future proofing - // (what if facebook buys mycroft tomorrow?) - "remote": [ - "enclosure", - "server", - "system", - "websocket", - "gui_websocket", - "network_tests", - "listener:wake_word_upload:disable", - // NOTE: selene returns listener settings as part of ww config - // they are protected because selene has no clue about your mic setup - "listener:channels", - "listener:sample_rate", - "listener:multiplier", - "listener:energy_ratio", - "skills:upload_skill_manifest", - "skills:auto_update", - "skills:priority_skills", - "skills:blacklisted_skills", - // NOTE: selene exposes this in web_ui, this has been disabled - // if you unprotect this key selene can enable opt_in behind your back - "opt_in" - ], - "user": [] - } - }, - - // Address of the REMOTE server - // Needs to be explicitly enabled, also see "protected_keys" under "system" above - // Possible backends - // - https://github.com/OpenVoiceOS/OVOS-local-backend - // - https://mycroft-ai.gitbook.io/docs/using-mycroft-ai/pairing-your-device - "server": { - // Valid types: offline, selene, personal, neon, ovos - "backend_type": "offline", - // url for selene -> https://api.mycroft.ai - "url": "", - "version": "v1", - "update": false, - "metrics": true, - "sync_skill_settings": true - }, - - // This section controls what providers should be used by each 3rd party API - "microservices": { - // auto == backend from "server" section above - // auto / wolfram / selene / personal / ovos / neon - "wolfram_provider": "auto", - // auto / owm / selene / personal / ovos / neon - "weather_provider": "auto", - // auto / osm / selene / personal / ovos / neon - "geolocation_provider": "auto", - - // secret keys for offline usage - "wolfram_key": "", - "owm_key": "", - "email": { - // by default send emails here - "recipient": "", - "smtp": { - "username": "", - "password": "", - "host": "smtp.mailprovider.com", - "port": 465 - } - } - }, - - // The mycroft-core messagebus websocket - "websocket": { - "host": "0.0.0.0", - "port": 8181, - "route": "/core", - "ssl": false, - // in mycroft-core all skills share a bus, this allows malicious skills - // to manipulate it and affect other skills, this option ensures each skill - // gets it's own websocket connection - "shared_connection": true - }, - - // The GUI messagebus websocket. Once port is created per connected GUI - "gui_websocket": { - "host": "0.0.0.0", - "base_port": 18181, - "route": "/gui", - "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": { - "sample_rate": 16000, - - // if enabled the noise level is saved to a ipc file, useful for - // debuging if microphone is working but writes a lot to disk, - // recommended that you set "ipc_path" to a tmpfs - "mic_meter_ipc": true, - - // Set 'save_path' to configure the location of files stored if - // 'record_wake_words' and/or 'save_utterances' are set to 'true'. - // WARNING: Make sure that user 'mycroft' has write-access on the - // directory! - // "save_path": "/tmp", - // Set 'record_wake_words' to save a copy of wake word triggers - // as .wav files under: /'save_path'/mycroft_wake_words - "record_wake_words": false, - // Set 'save_utterances' to save each sentence sent to STT -- by default - // they are only kept briefly in-memory. This can be useful for for - // debugging or other custom purposes. Recordings are saved - // under: /'save_path'/mycroft_utterances/.wav - "save_utterances": false, - "wake_word_upload": { - "disable": true, - // official mycroft endpoint disabled, enable if you want to collect your own - // eg, eltocino localcroft or personal backend - "url": "" - }, - - // Voice Activity Detection is used to determine when speech ended - "VAD": { - // silence method defined the main vad strategy - // valid values: - // VAD_ONLY - Only use vad - // RATIO_ONLY - Only use max/current energy ratio threshold - // CURRENT_ONLY - Only use current energy threshold - // VAD_AND_RATIO - Use vad and max/current energy ratio threshold - // VAD_AND_CURRENT - Use vad and current energy threshold - // ALL - Use vad, max/current energy ratio, and current energy threshold - // NOTE: if a vad plugin is not available method will fallback to RATIO_ONLY - "silence_method": "vad_and_ratio", - // Seconds of speech before voice command has begun - "speech_seconds": 0.1, - // Seconds of silence before a voice command has finished - "silence_seconds": 0.5, - // Seconds of audio to keep before voice command has begun - "before_seconds": 0.5, - // Minimum length of voice command (seconds) - // NOTE: max_seconds uses recording_timeout listener setting - "min_seconds": 1, - // Ratio of max/current energy below which audio is considered speech - "max_current_ratio_threshold": 2, - // Energy threshold above which audio is considered speech - // NOTE: this is dynamic, only defining start value - "initial_energy_threshold": 1000.0, - // vad module can be any plugin, by default it is not used - // recommended plugin: "ovos-vad-plugin-silero" - "module": "ovos-vad-plugin-webrtcvad", - "ovos-vad-plugin-silero": {"threshold": 0.2}, - "ovos-vad-plugin-webrtcvad": {"vad_mode": 3} - }, - - // Override as SYSTEM or USER to select a specific microphone input instead of - // the PortAudio default input. - // "device_name": "somename", // can be regex pattern or substring - // or - // "device_index": 12, - - // Retry microphone initialization infinitely on startup - "retry_mic_init" : true, - - // Stop listing to the microphone during playback to prevent accidental triggering - // This is enabled by default, but instances with good microphone noise cancellation - // can disable this to listen all the time, allowing 'barge in' functionality. - "mute_during_output" : true, - - // How much (if at all) to 'duck' the speaker output during listening. A - // setting of 0.0 will not duck at all. A 1.0 will completely mute output - // while in a listening state. Values in between will lower the volume - // partially (this is optional behavior, depending on the enclosure). - "duck_while_listening" : 0.3, - - // In milliseconds - "phoneme_duration": 120, - "multiplier": 1.0, - "energy_ratio": 1.5, - - // NOTE, multiple hotwords are supported now, these fields define the main wake_word, - // this is equivalent to setting "active": true in the "hotwords" section below IF "active" is missing - // this field is also used to get a speakable string of main wake word, ie, mycrofts name - // this is set by selene and used in naptime skill - "wake_word": "hey_mycroft", - "stand_up_word": "wake_up", - - // Settings used by microphone to set recording timeout - "recording_timeout": 10.0, - "recording_timeout_with_silence": 3.0, - - // instant listen is an experimental setting, it removes the need for - // the pause between "hey mycroft" and starting to speak the utterance, - // this setting might slightly downgrade STT accuracy depending on engine used - "instant_listen": false, - - // continuous listen is an experimental setting, it removes the need for - // wake words and uses VAD only, a streaming STT is strongly recommended - // this setting might downgrade STT accuracy depending on engine used - "continuous_listen": false, - - // hybrid listen is an experimental setting, - // it will not require a wake word for X seconds after a user interaction - // this means you dont need to say "hey mycroft" for follow up questions - // NOTE: depending on hardware this may cause mycroft to hear its own TTS responses as questions, - // in devices like the mark2 this should be safe to turn on - "hybrid_listen": false, - // number of seconds to wait for an interaction before requiring wake word again - "listen_timeout": 45 - }, - - // DEPRECATED - 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" - }, - - // Hotword configurations - "hotwords": { - "hey_mycroft": { - "module": "ovos-ww-plugin-precise-lite", - "model": "https://github.com/OpenVoiceOS/precise-lite-models/raw/master/wakewords/en/hey_mycroft.tflite", - "expected_duration": 3, - "trigger_level": 3, - "sensitivity": 0.5, - "listen": true, - "fallback_ww": "hey_mycroft_precise" - }, - "hey_mycroft_precise": { - "module": "ovos-ww-plugin-precise", - "version": "0.3", - "model": "https://github.com/MycroftAI/precise-data/raw/models-dev/hey-mycroft.tar.gz", - "expected_duration": 3, - "trigger_level": 3, - "sensitivity": 0.5, - "listen": true, - "fallback_ww": "hey_mycroft_vosk" - }, - "hey_mycroft_vosk": { - "module": "ovos-ww-plugin-vosk", - "samples": ["hey mycroft", "hey microsoft", "hey mike roft", "hey minecraft"], - "rule": "fuzzy", - "listen": true, - "fallback_ww": "hey_mycroft_pocketsphinx" - }, - "hey_mycroft_pocketsphinx": { - "module": "ovos-ww-plugin-pocketsphinx", - "phonemes": "HH EY . M AY K R AO F T", - "threshold": 1e-90, - "lang": "en-us", - "listen": true - }, - "wake_up": { - "module": "ovos-ww-plugin-pocketsphinx", - "phonemes": "W EY K . AH P", - "threshold": 1e-20, - "lang": "en-us", - // wakeupwords are only used in SLEEPING mode - "wakeup": true - } - }, - - // DEPRECATED: the concept of enclosure will no longer exist in ovos-core - // this has been replaced with PHAL - "enclosure": { - // Platform name - // Override: SYSTEM (set by specific enclosures) - "platform": "PHAL", - - // The NTP sync should only forced on Raspberry Pi based devices. - "ntp_sync_on_boot": false, - - // for backwards compat NTP sync is automatically enabled for - // ('mycroft_mark_1', 'picroft', 'mycroft_mark_2pi') - // to disable forced ntp_sync in official mycroft platforms - // set this to false - "force_mycroft_ntp": true - }, - - "gui": { - // Override: SYSTEM (set by specific enclosures) - // Uncomment or add "idle_display_skill" to set initial homescreen - // "idle_display_skill": "skill-ovos-homescreen.openvoiceos", - - // Extensions provide additional GUI platform support for specific devices - // Currently supported devices: smartspeaker, bigscreen or generic - "extension": "generic", - - // Generic extension can additionaly provide homescreen functionality - // homescreen support is disabled by default for generic extension - "generic": { - "homescreen_supported": false - } - }, - - // Level of logs to store, one of "CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG" - // NOTE: This configuration setting is special and can only be changed in the - // SYSTEM or USER configuration file, it will not be read if defined in the - // DEFAULT (here) or in the REMOTE mycroft config. - // If not defined, the default log level is INFO. - //"log_level": "INFO", - - // Messagebus types that will NOT be output to logs - "ignore_logs": ["enclosure.mouth.viseme", "enclosure.mouth.display"], - - // Settings related to remote sessions - // Overrride: none - "session": { - // Time To Live, in seconds - "ttl": 180 - }, - - // Speech to Text parameters - // Override: REMOTE - "stt": { - // select a STT plugin as described in the respective readme - "module": "ovos-stt-plugin-server", - "fallback_module": "", - // the default instance is hosted by a OpenVoiceOS member - // it is a google proxy equivalent to mycroft selene - "ovos-stt-plugin-server": {"url": "https://stt.openvoiceos.com/stt"} - }, - - // Text to Speech parameters - // Override: REMOTE - "tts": { - "pulse_duck": false, - "module": "ovos-tts-plugin-mimic3-server", - "fallback_module": "ovos-tts-plugin-mimic", - "ovos-tts-plugin-mimic": { - "voice": "ap" - }, - "ovos-tts-plugin-mimic3-server": { - "voice": "en_UK/apope_low" - } - }, - - "padatious": { - "intent_cache": "~/.local/share/mycroft/intent_cache", - "train_delay": 4, - "single_thread": false, - // fallback settings for padacioso (pure regex) - "regex_only": false, - "fuzz": true - }, - - "Audio": { - // message.context may contains a source and destination - // native audio (playback / TTS) will only be played if a - // message destination is a native_source or if missing (considered a broadcast) - "native_sources": ["debug_cli", "audio"], - - "backends": { - "OCP": { - "type": "ovos_common_play", - "active": true - }, - "simple": { - "type": "ovos_audio_simple", - "active": true - }, - "vlc": { - "type": "ovos_vlc", - "active": true - } - }, - // DEPRECATED - this value is only used as a fallback when OCP is not installed - // OCP is a full fledged media player that handles everything from video to playlists - // it plugs into the audio service api to capture playback and provide backwards compat - // OCP will delegate to the audio backends when needed - "default-backend": "OCP" - }, - - "debug": false -} diff --git a/mycroft/deprecated/enclosure/main.py b/mycroft/deprecated/enclosure/main.py index f5f8f2dcfe49..313c17dedb09 100644 --- a/mycroft/deprecated/enclosure/main.py +++ b/mycroft/deprecated/enclosure/main.py @@ -25,7 +25,7 @@ from ovos_config.locale import setup_locale from ovos_config.config import Configuration -from mycroft.gui.service import GUIService +from ovos_gui.service import GUIService from ovos_utils.log import LOG from ovos_utils import wait_for_exit_signal from ovos_utils.process_utils import reset_sigint_handler diff --git a/mycroft/deprecated/skills/__init__.py b/mycroft/deprecated/skills/__init__.py index cb4d607d48ec..ae0076611c25 100644 --- a/mycroft/deprecated/skills/__init__.py +++ b/mycroft/deprecated/skills/__init__.py @@ -12,7 +12,7 @@ from os import walk from os.path import splitext, join from ovos_backend_client.pairing import is_paired -from mycroft.enclosure.api import EnclosureAPI +from ovos_utils.enclosure.api import EnclosureAPI from mycroft.util.format import expand_options from ovos_utils.log import LOG from ovos_utils.intents.intent_service_interface import munge_regex, to_alnum diff --git a/mycroft/enclosure/gui.py b/mycroft/enclosure/gui.py index e55bac9e8247..bd6a24c2e434 100644 --- a/mycroft/enclosure/gui.py +++ b/mycroft/enclosure/gui.py @@ -8,4 +8,4 @@ to be a drop in replacement for mycroft-core """ -from mycroft.gui import SkillGUI +from ovos_workshop.skills.base import SkillGUI diff --git a/mycroft/gui/__init__.py b/mycroft/gui/__init__.py index 9b619a0d32b3..7865e3b6e717 100644 --- a/mycroft/gui/__init__.py +++ b/mycroft/gui/__init__.py @@ -1,89 +1,3 @@ -# Copyright 2019 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -""" Interface for interacting with the Mycroft gui qml viewer. """ - -from ovos_config.config import Configuration - +# backwards compat imports from ovos_utils.gui import GUIInterface - - -class SkillGUI(GUIInterface): - """SkillGUI - Interface to the Graphical User Interface - - Values set in this class are synced to the GUI, accessible within QML - via the built-in sessionData mechanism. For example, in Python you can - write in a skill: - self.gui['temp'] = 33 - self.gui.show_page('Weather.qml') - Then in the Weather.qml you'd access the temp via code such as: - text: sessionData.time - """ - - def __init__(self, skill): - self.skill = skill - super().__init__(skill.skill_id, config=Configuration()) - - @property - def bus(self): - if self.skill: - return self.skill.bus - - @property - def skill_id(self): - return self.skill.skill_id - - def setup_default_handlers(self): - """Sets the handlers for the default messages.""" - msg_type = self.build_message_type('set') - self.skill.add_event(msg_type, self.gui_set) - - def register_handler(self, event, handler): - """Register a handler for GUI events. - - When using the triggerEvent method from Qt - triggerEvent("event", {"data": "cool"}) - - Args: - event (str): event to catch - handler: function to handle the event - """ - msg_type = self.build_message_type(event) - self.skill.add_event(msg_type, handler) - - def _pages2uri(self, page_names): - # Convert pages to full reference - page_urls = [] - for name in page_names: - page = self.skill._resources.locate_qml_file(name) - if page: - if self.remote_url: - page_urls.append(self.remote_url + "/" + page) - elif page.startswith("file://"): - page_urls.append(page) - else: - page_urls.append("file://" + page) - else: - raise FileNotFoundError(f"Unable to find page: {name}") - - return page_urls - - def shutdown(self): - """Shutdown gui interface. - - Clear pages loaded through this interface and remove the skill - reference to make ref counting warning more precise. - """ - self.release() - self.skill = None +from ovos_workshop.skills.base import SkillGUI diff --git a/mycroft/gui/__main__.py b/mycroft/gui/__main__.py index af4b92caa7a2..8f41675d7e73 100644 --- a/mycroft/gui/__main__.py +++ b/mycroft/gui/__main__.py @@ -1,5 +1,5 @@ from ovos_config.locale import setup_locale -from mycroft.gui.service import GUIService +from ovos_gui.service import GUIService from ovos_utils import wait_for_exit_signal from ovos_utils.process_utils import reset_sigint_handler diff --git a/mycroft/gui/bus.py b/mycroft/gui/bus.py index 9c4bca0d9df5..a0945c8ffb67 100644 --- a/mycroft/gui/bus.py +++ b/mycroft/gui/bus.py @@ -1,180 +1,3 @@ -# Copyright 2022 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""GUI message bus implementation - -The basic mechanism is: - 1) GUI client connects to the core messagebus - 2) Core prepares a port for a socket connection to this GUI - 3) The availability of the port is sent over the Core - 4) The GUI connects to the GUI message bus websocket - 5) Connection persists for graphical interaction indefinitely - -If the connection is lost, it must be renegotiated and restarted. -""" -import asyncio -import json -from threading import Lock - -from tornado import ioloop -from tornado.options import parse_command_line -from tornado.web import Application -from tornado.websocket import WebSocketHandler - -from ovos_config.config import Configuration -from ovos_bus_client.message import Message -from ovos_utils.log import LOG -from ovos_utils import create_daemon - -write_lock = Lock() - - -def get_gui_websocket_config(): - """Retrieves the configuration values for establishing a GUI message bus""" - config = Configuration() - websocket_config = config["gui_websocket"] - - return websocket_config - - -def create_gui_service(enclosure) -> Application: - """Initiate a websocket for communicating with the GUI service.""" - LOG.info('Starting message bus for GUI...') - websocket_config = get_gui_websocket_config() - # Disable all tornado logging so mycroft loglevel isn't overridden - parse_command_line(['--logging=None']) - - routes = [(websocket_config['route'], GUIWebsocketHandler)] - application = Application(routes) - application.enclosure = enclosure - application.listen( - websocket_config['base_port'], websocket_config['host'] - ) - - create_daemon(ioloop.IOLoop.instance().start) - LOG.info('GUI Message bus started!') - return application - - -def send_message_to_gui(message): - """Sends the supplied message to all connected GUI clients.""" - for connection in GUIWebsocketHandler.clients: - try: - connection.send(message) - except Exception as e: - LOG.exception(repr(e)) - - -def determine_if_gui_connected(): - """Returns True if any clients are connected to the GUI bus.""" - return len(GUIWebsocketHandler.clients) > 0 - - -class GUIWebsocketHandler(WebSocketHandler): - """Defines the websocket pipeline between the GUI and Mycroft.""" - clients = [] - - def open(self): - GUIWebsocketHandler.clients.append(self) - LOG.info('New Connection opened!') - self.synchronize() - - def on_close(self): - LOG.info('Closing {}'.format(id(self))) - GUIWebsocketHandler.clients.remove(self) - if len(GUIWebsocketHandler.clients) == 0: - message = Message("mycroft.gui.unavailable") - self.application.enclosure.core_bus.emit(message) - - def synchronize(self): - """ Upload namespaces, pages and data to the last connected. """ - namespace_pos = 0 - enclosure = self.application.enclosure - - for namespace in enclosure.active_namespaces: - LOG.info(f'Sync {namespace.name}') - # Insert namespace - self.send({"type": "mycroft.session.list.insert", - "namespace": "mycroft.system.active_skills", - "position": namespace_pos, - "data": [{"skill_id": namespace.name}] - }) - # Insert pages - self.send({"type": "mycroft.gui.list.insert", - "namespace": namespace.name, - "position": 0, - "data": [{"url": p.url} for p in namespace.pages] - }) - # Insert data - for key, value in namespace.data.items(): - self.send({"type": "mycroft.session.set", - "namespace": namespace.name, - "data": {key: value} - }) - namespace_pos += 1 - - def on_message(self, message): - LOG.info("Received: {message}") - msg = json.loads(message) - if (msg.get('type') == "mycroft.events.triggered" and - (msg.get('event_name') == 'page_gained_focus' or - msg.get('event_name') == 'system.gui.user.interaction')): - # System event, a page was changed - event_name = msg.get('event_name') - if event_name == 'page_gained_focus': - msg_type = 'gui.page_gained_focus' - else: - msg_type = 'gui.page_interaction' - - msg_data = {'namespace': msg['namespace'], - 'page_number': msg['parameters'].get('number'), - 'skill_id': msg['parameters'].get('skillId')} - elif msg.get('type') == "mycroft.events.triggered": - # A normal event was triggered - msg_type = '{}.{}'.format(msg['namespace'], msg['event_name']) - msg_data = msg['parameters'] - - elif msg.get('type') == 'mycroft.session.set': - # A value was changed send it back to the skill - msg_type = '{}.{}'.format(msg['namespace'], 'set') - msg_data = msg['data'] - - message = Message(msg_type, msg_data) - LOG.info('Forwarding to bus...') - self.application.enclosure.core_bus.emit(message) - LOG.info('Done!') - - def write_message(self, *arg, **kwarg): - """Wraps WebSocketHandler.write_message() with a lock. """ - try: - asyncio.get_event_loop() - except RuntimeError: - asyncio.set_event_loop(asyncio.new_event_loop()) - - with write_lock: - super().write_message(*arg, **kwarg) - - def send(self, data): - """Send the given data across the socket as JSON - - Args: - data (dict): Data to transmit - """ - s = json.dumps(data) - #LOG.info('Sending {}'.format(s)) - self.write_message(s) - - def check_origin(self, origin): - """Disable origin check to make js connections work.""" - return True +# backwards compat import +from ovos_gui.bus import GUIWebsocketHandler, determine_if_gui_connected, \ + send_message_to_gui, create_gui_service, get_gui_websocket_config \ No newline at end of file diff --git a/mycroft/gui/extensions.py b/mycroft/gui/extensions.py index 93248501036c..1adb1110a6ca 100644 --- a/mycroft/gui/extensions.py +++ b/mycroft/gui/extensions.py @@ -1,352 +1,3 @@ -import threading - -from ovos_config.config import Configuration - -from mycroft.gui.homescreen import HomescreenManager -from mycroft.gui.interfaces.mobile import MobileExtensionGuiInterface -from mycroft.gui.interfaces.smartspeaker import SmartSpeakerExtensionGuiInterface -from ovos_bus_client.message import Message -from ovos_utils.log import LOG -from ovos_backend_client.pairing import is_paired - - -class ExtensionsManager: - def __init__(self, name, bus, gui): - """ Constructor for the Extension Manager. The Extension Manager is responsible for - managing the extensions that define additional GUI behaviours for specific platforms. - - Args: - name: Name of the extension manager - bus: MessageBus instance - gui: GUI instance - """ - - self.name = name - self.bus = bus - self.gui = gui - core_config = Configuration() - enclosure_config = core_config.get("gui") or {} - self.active_extension = enclosure_config.get("extension", "generic") - - # ToDo: Add Exclusive Support For "Desktop", "Mobile" Extensions - self.supported_extensions = ["smartspeaker", "bigscreen", "generic", "mobile", "plasmoid"] - - if self.active_extension.lower() not in self.supported_extensions: - self.active_extension = "generic" - - LOG.info( - f"Extensions Manager: Initializing {self.name} with active extension {self.active_extension}") - self.activate_extension(self.active_extension.lower()) - - def activate_extension(self, extension_id): - LOG.info(f"Extensions Manager: Activating Extension {extension_id}") - - # map extension_id to class - if extension_id == "smartspeaker": - self.extension = SmartSpeakerExtension(self.bus, self.gui) - elif extension_id == "bigscreen": - self.extension = BigscreenExtension(self.bus, self.gui) - elif extension_id == "mobile": - self.extension = MobileExtension(self.bus, self.gui) - elif extension_id == "plasmoid": - self.extension = PlasmoidExtension(self.bus, self.gui) - else: - self.extension = GenericExtension(self.bus, self.gui) - - LOG.info(f"Extensions Manager: Activated Extension {extension_id}") - self.bus.emit( - Message("extension.manager.activated", {"id": extension_id})) - - def signal_available(message=None): - message = message or Message("") - self.bus.emit(message.forward("mycroft.gui.available", - {"permanent": self.extension.permanent})) - - if self.extension.preload_gui: - signal_available() - else: - self.bus.on("mycroft.gui.connected", signal_available) - - -class SmartSpeakerExtension: - """ Smart Speaker Extension: This extension is responsible for managing the Smart Speaker - specific GUI behaviours. This extension adds support for Homescreens and Homescreen Mangement. - - Args: - bus: MessageBus instance - gui: GUI instance - preload_gui (bool): load GUI skills even if gui client not connected - permanent (bool): disable unloading of GUI skills on gui client disconnections - """ - - def __init__(self, bus, gui, preload_gui=False, permanent=True): - LOG.info("SmartSpeaker: Initializing") - - self.bus = bus - self.gui = gui - self.preload_gui = preload_gui - self.permanent = permanent - self.homescreen_manager = HomescreenManager(self.bus, self.gui) - - self.homescreen_thread = threading.Thread( - target=self.homescreen_manager.run) - self.homescreen_thread.start() - - self.device_paired = is_paired() - self.backend = "unknown" - self.gui_interface = SmartSpeakerExtensionGuiInterface( - self.bus, self.homescreen_manager) - - try: - self.bus.on("ovos.pairing.process.completed", - self.start_homescreen_process) - self.bus.on("ovos.pairing.set.backend", self.set_backend_type) - self.bus.on("mycroft.gui.screen.close", - self.handle_remove_namespace) - self.bus.on("system.display.homescreen", - self.handle_system_display_homescreen) - - except Exception as e: - LOG.error(f"SmartSpeaker: Init Bus Exception: {e}") - - def set_backend_type(self, message): - backend = message.data.get("backend", "unknown") - if not backend == "unknown": - self.backend = backend - else: - backend = self._detect_backend() - self.backend = backend - - def start_homescreen_process(self, message): - self.device_paired = is_paired() - if not self.backend == "local": - self.homescreen_manager.show_homescreen() - self.bus.emit(Message("ovos.shell.status.ok")) - else: - self.bus.emit(Message("ovos.shell.status.ok")) - - def _detect_backend(self): - config = Configuration() - server_config = config.get("server") or {} - backend_config = server_config.get("url", "") - if "https://api.mycroft.ai" in backend_config: - return "remote" - else: - return "local" - - def handle_remove_namespace(self, message): - LOG.info("Got Clear Namespace Event In Skill") - get_skill_namespace = message.data.get("skill_id", "") - if get_skill_namespace: - self.bus.emit(Message("gui.clear.namespace", - {"__from": get_skill_namespace})) - - def handle_system_display_homescreen(self, message): - self.homescreen_manager.show_homescreen() - - -class BigscreenExtension: - """ Bigscreen Platform Extension: This extension is responsible for managing the Bigscreen - specific GUI behaviours. The bigscreen extension does not support Homescreens. It includes - support for Window managment and Window behaviour. - - Args: - bus: MessageBus instance - gui: GUI instance - preload_gui (bool): load GUI skills even if gui client not connected - permanent (bool): disable unloading of GUI skills on gui client disconnections - """ - - def __init__(self, bus, gui, preload_gui=False, permanent=True): - LOG.info("Bigscreen: Initializing") - - self.bus = bus - self.gui = gui - self.permanent = permanent - self.preload_gui = preload_gui - self.interaction_without_idle = True - self.interaction_skill_id = None - - try: - self.bus.on('mycroft.gui.screen.close', self.close_window_by_event) - self.bus.on('mycroft.gui.force.screenclose', - self.close_window_by_force) - self.bus.on('gui.page.show', self.on_gui_page_show) - self.bus.on('gui.page_interaction', self.on_gui_page_interaction) - self.bus.on('gui.namespace.removed', self.close_current_window) - - except Exception as e: - LOG.error(f"Bigscreen: Init Bus Exception: {e}") - - def on_gui_page_show(self, message): - override_idle = message.data.get('__idle') - if override_idle is True: - self.interaction_without_idle = True - elif isinstance(override_idle, int) and not (override_idle, bool) and override_idle is not False: - self.interaction_without_idle = True - elif (message.data['page']): - if not isinstance(override_idle, bool) or not isinstance(override_idle, int): - self.interaction_without_idle = False - - def on_gui_page_interaction(self, message): - skill_id = message.data.get('skill_id') - self.interaction_skill_id = skill_id - - def handle_remove_namespace(self, message): - get_skill_namespace = message.data.get("skill_id", "") - LOG.info(f"Got Clear Namespace Event In Skill {get_skill_namespace}") - if get_skill_namespace: - self.bus.emit(Message("gui.clear.namespace", - {"__from": get_skill_namespace})) - - def close_current_window(self, message): - skill_id = message.data.get('skill_id') - LOG.info(f"Bigscreen: Closing Current Window For Skill {skill_id}") - self.bus.emit(Message('screen.close.idle.event', - data={"skill_idle_event_id": skill_id})) - - def close_window_by_event(self, message): - self.interaction_without_idle = False - self.bus.emit(Message('screen.close.idle.event', - data={"skill_idle_event_id": self.interaction_skill_id})) - self.handle_remove_namespace(message) - - def close_window_by_force(self, message): - skill_id = message.data.get('skill_id') - self.bus.emit(Message('screen.close.idle.event', - data={"skill_idle_event_id": skill_id})) - self.handle_remove_namespace(message) - - -class GenericExtension: - """ Generic Platform Extension: This extension is responsible for managing the generic GUI behaviours - for non specific platforms. The generic extension does optionally support Homescreen and Homescreen - Management but it needs to be exclusively enabled in the configuration file. - - Args: - bus: MessageBus instance - gui: GUI instance - preload_gui (bool): load GUI skills even if gui client not connected - permanent (bool): disable unloading of GUI skills on gui client disconnections - """ - - def __init__(self, bus, gui, preload_gui=False, permanent=False): - LOG.info("Generic: Initializing") - - self.bus = bus - self.gui = gui - self.preload_gui = preload_gui - self.permanent = permanent - core_config = Configuration() - gui_config = core_config.get("gui") or {} - generic_config = gui_config.get("generic", {}) - self.homescreen_supported = generic_config.get("homescreen_supported", False) - - if self.homescreen_supported: - self.homescreen_manager = HomescreenManager(self.bus, self.gui) - self.homescreen_thread = threading.Thread( - target=self.homescreen_manager.run) - self.homescreen_thread.start() - - try: - self.bus.on("mycroft.gui.screen.close", - self.handle_remove_namespace) - - except Exception as e: - LOG.error(f"Generic: Init Bus Exception: {e}") - - def handle_remove_namespace(self, message): - LOG.info("Got Clear Namespace Event In Skill") - get_skill_namespace = message.data.get("skill_id", "") - if get_skill_namespace: - self.bus.emit(Message("gui.clear.namespace", - {"__from": get_skill_namespace})) - - -class MobileExtension: - """ Mobile Platform Extension: This extension is responsible for managing the mobile GUI behaviours. - This extension adds support for Homescreens and Homescreen Management and global page back navigation. - - Args: - bus: MessageBus instance - gui: GUI instance - preload_gui (bool): load GUI skills even if gui client not connected - permanent (bool): disable unloading of GUI skills on gui client disconnections - """ - - def __init__(self, bus, gui, preload_gui=True, permanent=True): - LOG.info("Mobile: Initializing") - - self.bus = bus - self.gui = gui - self.preload_gui = preload_gui - self.permanent = permanent - self.homescreen_manager = HomescreenManager(self.bus, self.gui) - - self.homescreen_thread = threading.Thread( - target=self.homescreen_manager.run) - self.homescreen_thread.start() - - self.gui_interface = MobileExtensionGuiInterface( - self.bus, self.homescreen_manager) - - self.bus.on('mycroft.gui.screen.close', - self.force_idle_screen) - self.bus.on('mycroft.gui.forceHome', - self.force_home) - self.bus.on('mycroft.gui.screen.request.page.back', - self.handle_page_back) - - def force_idle_screen(self, message): - self.homescreen_manager.show_homescreen() - - def force_home(self, message): - self.homescreen_manager.show_homescreen() - - def handle_page_back(self, message): - self.gui.handle_namespace_global_back({}) - - -class PlasmoidExtension: - """ Plasmoid Platform Extension: This extension is responsible for managing the generic GUI behaviours - for non specific platforms. The generic extension does optionally support Homescreen and Homescreen - Management but it needs to be exclusively enabled in the configuration file. - - Args: - bus: MessageBus instance - gui: GUI instance - preload_gui (bool): load GUI skills even if gui client not connected - permanent (bool): disable unloading of GUI skills on gui client disconnections - """ - - def __init__(self, bus, gui, preload_gui=False, permanent=True): - LOG.info("Plasmoid: Initializing") - - self.bus = bus - self.gui = gui - self.preload_gui = preload_gui - self.permanent = permanent - core_config = Configuration() - gui_config = core_config.get("gui") or {} - generic_config = gui_config.get("plasmoid", {}) - self.homescreen_supported = generic_config.get("homescreen_supported", False) - - if self.homescreen_supported: - self.homescreen_manager = HomescreenManager(self.bus, self.gui) - self.homescreen_thread = threading.Thread( - target=self.homescreen_manager.run) - self.homescreen_thread.start() - - try: - self.bus.on("mycroft.gui.screen.close", - self.handle_remove_namespace) - - except Exception as e: - LOG.error(f"Plasmoid: Init Bus Exception: {e}") - - def handle_remove_namespace(self, message): - LOG.info("Got Clear Namespace Event In Skill") - get_skill_namespace = message.data.get("skill_id", "") - if get_skill_namespace: - self.bus.emit(Message("gui.clear.namespace", - {"__from": get_skill_namespace})) +# backwards compat import +from ovos_gui.extensions import ExtensionsManager, GenericExtension, \ + SmartSpeakerExtension, BigscreenExtension, MobileExtension, PlasmoidExtension \ No newline at end of file diff --git a/mycroft/gui/homescreen.py b/mycroft/gui/homescreen.py index 3970b4c7e454..9f1a7f829174 100644 --- a/mycroft/gui/homescreen.py +++ b/mycroft/gui/homescreen.py @@ -1,139 +1,2 @@ -from ovos_bus_client.message import Message -from ovos_config.config import Configuration, LocalConf -from ovos_config.locations import USER_CONFIG -from ovos_utils.log import LOG -from .namespace import NamespaceManager - - -class HomescreenManager: - - def __init__(self, bus, gui): - self.bus = bus - self.gui = gui - self.homescreens = [] - self.mycroft_ready = False - self.bus.on('homescreen.manager.add', self.add_homescreen) - self.bus.on('homescreen.manager.remove', self.remove_homescreen) - self.bus.on('homescreen.manager.list', self.get_homescreens) - self.bus.on("homescreen.manager.get_active", - self.get_active_homescreen) - self.bus.on("homescreen.manager.set_active", - self.set_active_homescreen) - self.bus.on("homescreen.manager.disable_active", - self.disable_active_homescreen) - self.bus.on("mycroft.mark2.register_idle", - self.register_old_style_homescreen) - self.bus.on("homescreen.manager.show_active", self.show_homescreen) - self.bus.on("mycroft.ready", self.set_mycroft_ready) - - def run(self): - """Start the Manager after it has been constructed.""" - self.reload_homescreens_list() - - def add_homescreen(self, homescreen): - # if homescreen[id] not in self.homescreens then add it - homescreen_id = homescreen.data["id"] - homescreen_class = homescreen.data["class"] - LOG.info(f"Homescreen Manager: Adding Homescreen {homescreen_id}") - # check if the list is empty - if len(self.homescreens) == 0: - self.homescreens.append(homescreen.data) - else: - # check if id is in list of homescreen dicts in self.homescreens - for h in self.homescreens: - if homescreen_id != h["id"]: - self.homescreens.append(homescreen.data) - - self.show_homescreen_on_add(homescreen_id, homescreen_class) - - def remove_homescreen(self, homescreen): - homescreen_id = homescreen.data["id"] - LOG.info(f"Homescreen Manager: Removing Homescreen {homescreen_id}") - for h in self.homescreens: - if homescreen_id == h["id"]: - self.homescreens.pop(h) - - def get_homescreens(self): - return self.homescreens - - def get_active_homescreen(self): - config = Configuration() - enclosure_config = config.get("gui") or {} - active_homescreen = enclosure_config.get("idle_display_skill") - LOG.debug(f"Homescreen Manager: Active Homescreen {active_homescreen}") - for h in self.homescreens: - if h["id"] == active_homescreen: - return active_homescreen - - def set_active_homescreen(self, homescreen): - homescreen_id = homescreen.data["id"] - conf = LocalConf(USER_CONFIG) - conf["gui"] = { - "idle_display_skill": homescreen_id, - } - conf.store() - self.bus.emit(Message("configuration.patch", {"config": conf})) - - def reload_homescreens_list(self): - LOG.info("Homescreen Manager: Reloading Homescreen List") - self.collect_old_style_homescreens() - self.bus.emit(Message("homescreen.manager.reload.list")) - - def show_homescreen_on_add(self, homescreen_id, homescreen_class): - if self.mycroft_ready == True: - active_homescreen = self.get_active_homescreen() - if active_homescreen == homescreen_id: - if homescreen_class == "IdleDisplaySkill": - LOG.debug( - f"Homescreen Manager: Displaying Homescreen {active_homescreen}") - self.bus.emit(Message("homescreen.manager.activate.display", { - "homescreen_id": active_homescreen})) - elif homescreen_class == "MycroftSkill": - LOG.debug( - f"Homescreen Manager: Displaying Homescreen {active_homescreen}") - self.bus.emit(Message("{}.idle".format(homescreen_id))) - - def disable_active_homescreen(self, message): - conf = LocalConf(USER_CONFIG) - conf["gui"] = { - "idle_display_skill": None, - } - conf.store() - self.bus.emit(Message("configuration.patch", {"config": conf})) - - def show_homescreen(self, message=None): - active_homescreen = self.get_active_homescreen() - for h in self.homescreens: - if h["id"] == active_homescreen: - if h["class"] == "IdleDisplaySkill": - LOG.debug( - f"Homescreen Manager: Displaying Homescreen {active_homescreen}") - self.bus.emit(Message("homescreen.manager.activate.display", { - "homescreen_id": active_homescreen})) - elif h["class"] == "MycroftSkill": - LOG.debug( - f"Homescreen Manager: Displaying Homescreen {active_homescreen}") - self.bus.emit(Message("{}.idle".format(active_homescreen))) - - def set_mycroft_ready(self, message): - self.mycroft_ready = True - self.show_homescreen() - - # Add compabitility with older versions of the Resting Screen Class - - def collect_old_style_homescreens(self): - """Trigger collection of older resting screens.""" - self.bus.emit(Message("mycroft.mark2.collect_idle")) - - def register_old_style_homescreen(self, message): - if "name" in message.data and "id" in message.data: - super_class_name = "MycroftSkill" - super_class_object = message.data["name"] - skill_id = message.data["id"] - _homescreen_entry = {"class": super_class_name, - "name": super_class_object, "id": skill_id} - LOG.debug("Homescreen Manager: Adding OLD Homescreen {skill_id}") - self.add_homescreen( - Message("homescreen.manager.add", _homescreen_entry)) - else: - LOG.error("Malformed idle screen registration received") +# backwards compat import +from ovos_gui.homescreen import HomescreenManager diff --git a/mycroft/gui/interfaces/mobile.py b/mycroft/gui/interfaces/mobile.py index 9db4265b57d3..fcd837801530 100644 --- a/mycroft/gui/interfaces/mobile.py +++ b/mycroft/gui/interfaces/mobile.py @@ -1,21 +1,2 @@ -from ovos_utils.gui import GUIInterface - -class MobileExtensionGuiInterface(GUIInterface): - def __init__(self, bus, homescreen_manager) -> None: - super(MobileExtensionGuiInterface, self).__init__( - skill_id="MobileExtension.GuiInterface") - self.bus = bus - self.homescreen_manager = homescreen_manager - - # Initiate Bind - self.bind() - - def bind(self): - super().set_bus(self.bus) - self.register_handler("mycroft.device.show.idle", - self.handle_show_homescreen) - self.register_handler('mycroft.gui.screen.close', - self.handle_show_homescreen) - - def handle_show_homescreen(self, message): - self.homescreen_manager.show_homescreen() +# backwards compat imports +from ovos_gui.interfaces.mobile import MobileExtensionGuiInterface diff --git a/mycroft/gui/interfaces/smartspeaker.py b/mycroft/gui/interfaces/smartspeaker.py index 7c84dee08814..8bc5597fd3d0 100644 --- a/mycroft/gui/interfaces/smartspeaker.py +++ b/mycroft/gui/interfaces/smartspeaker.py @@ -1,249 +1,3 @@ -import json -import platform -from os.path import exists, join +# backwards compat imports +from ovos_gui.interfaces.smartspeaker import SmartSpeakerExtensionGuiInterface -from json_database import JsonStorage -from ovos_bus_client.message import Message -from ovos_utils.log import LOG -from mycroft.version import OVOS_VERSION_STR -from ovos_utils import network_utils -from ovos_utils.gui import GUIInterface -from ovos_utils.xdg_utils import xdg_config_home - - -class SmartSpeakerExtensionGuiInterface(GUIInterface): - def __init__(self, bus, homescreen_manager) -> None: - super(SmartSpeakerExtensionGuiInterface, self).__init__( - skill_id="SmartSpeakerExtension.GuiInterface") - self.bus = bus - self.homescreen_manager = homescreen_manager - - # Paths to find the local display config - self.display_config_path_local = join(xdg_config_home(), "OvosDisplay.conf") - self.display_config_path_system = "/etc/xdg/OvosDisplay.conf" - self.local_display_config = JsonStorage(self.display_config_path_local) - self.about_page_data = [] - - if not exists(self.display_config_path_local): - self.handle_display_config_load() - - # Initiate Bind - self.bind() - - def bind(self): - super().set_bus(self.bus) - - self.bus.on("mycroft.device.settings", self.handle_device_settings) - self.bus.on("ovos.PHAL.dashboard.status.response", - self.update_device_dashboard_status) - - self.bus.on("ovos.phal.configuration.provider.get.response", - self.display_advanced_config_for_group) - self.bus.on("ovos.phal.configuration.provider.list.groups.response", - self.display_advanced_config_groups) - self.bus.on("smartspeaker.extension.extend.about", - self.extend_about_page_data_from_event) - - self.register_handler("mycroft.device.settings", - self.handle_device_settings) - self.register_handler( - "mycroft.device.settings.homescreen", self.handle_device_homescreen_settings) - self.register_handler("mycroft.device.settings.ssh", - self.handle_device_ssh_settings) - self.register_handler( - "mycroft.device.settings.developer", self.handle_device_developer_settings) - self.register_handler("mycroft.device.enable.dash", - self.handle_device_developer_enable_dash) - self.register_handler("mycroft.device.disable.dash", - self.handle_device_developer_disable_dash) - self.register_handler("mycroft.device.show.idle", - self.handle_show_homescreen) - self.register_handler("mycroft.device.settings.customize", - self.handle_device_customize_settings) - self.register_handler("mycroft.device.settings.create.theme", - self.handle_device_create_theme) - self.register_handler("mycroft.device.settings.about.page", - self.handle_device_about_page) - self.register_handler("mycroft.device.settings.display", - self.handle_device_display_settings) - self.register_handler("mycroft.device.settings.factory", - self.handle_device_display_factory) - self.register_handler("mycroft.device.settings.wallpapers", - self.handle_device_wallpaper_settings) - - # Display settings - self.register_handler("speaker.extension.display.set.wallpaper.rotation", - self.handle_display_wallpaper_rotation_config_set) - self.register_handler("speaker.extension.display.set.auto.dim", - self.handle_display_auto_dim_config_set) - self.register_handler("speaker.extension.display.set.auto.nightmode", - self.handle_display_auto_nightmode_config_set) - - self.build_initial_about_page_data() - - def handle_device_settings(self, message): - """ Display device settings page. """ - self["state"] = "settings/settingspage" - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_device_homescreen_settings(self, message): - """ - display homescreen settings page - """ - screens = self.homescreen_manager.homescreens - self["idleScreenList"] = {"screenBlob": screens} - self["selectedScreen"] = self.homescreen_manager.get_active_homescreen() - self["state"] = "settings/homescreen_settings" - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_device_ssh_settings(self, message): - """ - display ssh settings page - """ - self["state"] = "settings/ssh_settings" - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_set_homescreen(self, message): - """ - Set the homescreen to the selected screen - """ - homescreen_id = message.data.get("homescreen_id", "") - if homescreen_id: - self.homescreen_manager.set_active_homescreen(homescreen_id) - - def handle_show_homescreen(self, message): - self.homescreen_manager.show_homescreen() - - def handle_device_developer_settings(self, message): - self['state'] = 'settings/developer_settings' - self.handle_get_dash_status() - - def handle_device_developer_enable_dash(self, message): - self.bus.emit(Message("ovos.PHAL.dashboard.enable")) - - def handle_device_developer_disable_dash(self, message): - self.bus.emit(Message("ovos.PHAL.dashboard.disable")) - - def update_device_dashboard_status(self, message): - call_check = message.data.get("status", False) - dash_security_pass = message.data.get("password", "") - dash_security_user = message.data.get("username", "") - dash_url = message.data.get("url", "") - if call_check: - self["dashboard_enabled"] = call_check - self["dashboard_url"] = dash_url - self["dashboard_user"] = dash_security_user - self["dashboard_password"] = dash_security_pass - else: - self["dashboard_enabled"] = call_check - self["dashboard_url"] = "" - self["dashboard_user"] = "" - self["dashboard_password"] = "" - - def handle_device_customize_settings(self, message): - self['state'] = 'settings/customize_settings' - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_device_create_theme(self, message): - self['state'] = 'settings/customize_theme' - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_device_display_factory(self, message): - self['state'] = 'settings/factory_settings' - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_device_display_settings(self, message): - LOG.info("Display settings") - LOG.info(self.local_display_config) - - self['state'] = 'settings/display_settings' - self['display_wallpaper_rotation'] = self.local_display_config.get("wallpaper_rotation", False) - self['display_auto_dim'] = self.local_display_config.get("auto_dim", False) - self['display_auto_nightmode'] = self.local_display_config.get("auto_nightmode", False) - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_device_about_page(self, message): - # TODO: Move `system_information` generation to util method - uname_info = platform.uname() - system_information = {"display_list": self.about_page_data} - self['state'] = 'settings/about_page' - self['system_info'] = system_information - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_display_wallpaper_rotation_config_set(self, message): - wallpaper_rotation = message.data.get("wallpaper_rotation", False) - if wallpaper_rotation: - self.bus.emit(Message("ovos.wallpaper.manager.enable.auto.rotation", {"rotation_time": 30})) - else: - self.bus.emit(Message("ovos.wallpaper.manager.disable.auto.rotation")) - - def handle_display_auto_dim_config_set(self, message): - auto_dim = message.data.get("auto_dim", False) - self.local_display_config["auto_dim"] = auto_dim - self.local_display_config.store() - self.bus.emit(Message("speaker.extension.display.auto.dim.changed")) - - def handle_display_auto_nightmode_config_set(self, message): - auto_nightmode = message.data.get("auto_nightmode", False) - self.local_display_config["auto_nightmode"] = auto_nightmode - self.local_display_config.store() - self.bus.emit(Message("speaker.extension.display.auto.nightmode.changed")) - - def handle_display_config_load(self): - if exists(self.display_config_path_system): - LOG.info("Loading display config from system") - with open(self.display_config_path_system, "r") as f: - writeable_conf = json.load(f) - self.local_display_config["wallpaper_rotation"] = writeable_conf["wallpaper_rotation"] - self.local_display_config["auto_dim"] = writeable_conf["auto_dim"] - self.local_display_config["auto_nightmode"] = writeable_conf["auto_nightmode"] - self.local_display_config.store() - - def display_advanced_config_for_group(self, message=None): - group_meta = message.data.get("settingsMetaData") - group_name = message.data.get("groupName") - self["groupName"] = group_name - self["groupConfigurationData"] = group_meta - self['state'] = 'settings/configuration_generator_display' - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def display_advanced_config_groups(self, message=None): - groups_list = message.data.get("groups") - self["groupList"] = groups_list - self['state'] = 'settings/configuration_groups_display' - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_device_wallpaper_settings(self, message=None): - LOG.info("GOT WALLPAPER REQUEST HERE") - self['state'] = 'settings/wallpaper_settings' - self.show_page("SYSTEM_AdditionalSettings.qml", override_idle=True) - - def handle_get_dash_status(self): - self.bus.emit(Message("ovos.PHAL.dashboard.get.status")) - - def build_initial_about_page_data(self): - uname_info = platform.uname() - self.about_page_data.append({"display_key": "Kernel Version", "display_value": uname_info[2]}) - self.about_page_data.append({"display_key": "Core Version", "display_value": OVOS_VERSION_STR}) - self.about_page_data.append({"display_key": "Python Version", "display_value": platform.python_version()}) - self.about_page_data.append({"display_key": "Local Address", "display_value": network_utils.get_ip()}) - - def check_about_page_data_contains_key(self, key): - for item in self.about_page_data: - if item["display_key"] == key: - return True - return False - - def add_about_page_data(self, key, value): - if not self.check_about_page_data_contains_key(key): - self.about_page_data.append({"display_key": key, "display_value": value}) - else: - for item in self.about_page_data: - if item["display_key"] == key: - item["display_value"] = value - break - - def extend_about_page_data_from_event(self, message=None): - extended_list = message.data.get("display_list") - for item in extended_list: - self.add_about_page_data(item["display_key"], item["display_value"]) diff --git a/mycroft/gui/namespace.py b/mycroft/gui/namespace.py index 8a1e9e2a4e7c..37b0371165cb 100644 --- a/mycroft/gui/namespace.py +++ b/mycroft/gui/namespace.py @@ -1,801 +1,2 @@ -# Copyright 2022 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""Defines the API for the QT GUI. - -Manages what is displayed on a device with a touch screen using a LIFO stack -of "active" namespaces (e.g. skills). At the bottom of the stack is the -namespace for the idle screen skill (if one is specified in the device -configuration). The namespace for the idle screen skill should never be -removed from the stack. - -When a skill with a GUI is triggered by the user, the namespace for that skill -is placed at the top of the stack. The namespace at the top of the stack -represents the namespace that is visible on the device. When the skill is -finished displaying information on the screen, it is removed from the top of -the stack. This will result in the previously active namespace being -displayed. - -The persistence of a namespace indicates how long that namespace stays in the -active stack. A persistence expressed using a number represents how many -seconds the namespace will be active. A persistence expressed with a True -value will be active until the skill issues a command to remove the namespace. -If a skill with a numeric persistence replaces a namespace at the top of the -stack that also has a numeric persistence, the namespace being replaced will -be removed from the active namespace stack. - -The state of the active namespace stack is maintained locally and in the GUI -code. Changes to namespaces, and their contents, are communicated to the GUI -over the GUI message bus. -""" -from threading import Lock, Timer -from time import time, sleep -from typing import List, Union - -from ovos_config.config import Configuration -from ovos_bus_client import Message, MessageBusClient -from ovos_utils.log import LOG -from mycroft.gui.bus import ( - create_gui_service, - determine_if_gui_connected, - get_gui_websocket_config, - send_message_to_gui -) -from mycroft.gui.page import GuiPage -import sys - -namespace_lock = Lock() - -RESERVED_KEYS = ['__from', '__idle'] - - -class Namespace: - """A grouping mechanism for related GUI pages and data. - - In the majority of cases, a namespace represents a skill. There is a - SYSTEM namespace for GUI screens that exist outside of skills. This class - defines an API to manage a namespace, its pages and its data. Actions - are communicated to the GUI message bus. - - Attributes: - name: the name of the Namespace, generally the skill ID - persistent: indicates whether or not the namespace persists for a - period of time or until the namespace is removed. - duration: if the namespace persists for a period of time, this is the - number of seconds of persistence - pages: when the namespace is active, contains all the pages that are - displayed at the same time - data: a key/value pair representing the data used to populate the GUI - """ - - def __init__(self, name: str): - self.name = name - self.persistent = False - self.duration = 30 - self.pages = list() - self.data = dict() - self.page_number = 0 - self.session_set = False - - def add(self): - """Adds a namespace to the list of active namespaces.""" - LOG.info(f"Adding \"{self.name}\" to active GUI namespaces") - message = dict( - type="mycroft.session.list.insert", - namespace="mycroft.system.active_skills", - position=0, - data=[dict(skill_id=self.name)] - ) - send_message_to_gui(message) - - def activate(self, position: int): - """Activates an namespace already in the list of active namespaces.""" - LOG.info(f"Activating GUI namespace \"{self.name}\"") - message = { - "type": "mycroft.session.list.move", - "namespace": "mycroft.system.active_skills", - "from": position, - "to": 0, - "items_number": 1 - } - send_message_to_gui(message) - - def remove(self, position: int): - """Removes a namespace from the list of active namespaces.""" - LOG.info(f"Removing {self.name} from active GUI namespaces") - - # unload the data first before removing the namespace - # use the keys of the data to unload the data - for key in self.data: - self.unload_data(key) - - message = dict( - type="mycroft.session.list.remove", - namespace="mycroft.system.active_skills", - position=position, - items_number=1 - ) - send_message_to_gui(message) - self.session_set = False - self.pages = list() - self.data = dict() - - def load_data(self, name: str, value: str): - """Adds or changes the value of a namespace data attribute. - - Args: - name: The name of the attribute - value: The attribute's value - """ - message = dict( - type="mycroft.session.set", - namespace=self.name, - data={name: value} - ) - - #LOG.info(f"Setting data {message} in GUI namespace {self.name}") - send_message_to_gui(message) - - def unload_data(self, name: str): - """ Delete data from the namespace """ - message = dict( - type="mycroft.session.delete", - property=name, - namespace=self.name - ) - #LOG.info(f"Deleting data {message} from GUI namespace {self.name}") - send_message_to_gui(message) - - def get_position_of_last_item_in_data(self): - """ Get the position of the last item """ - return len(self.data) - 1 - - def set_persistence(self, skill_type: str): - """Sets the duration of the namespace's time in the active list. - - Args: - skill_type: if skill type is idleDisplaySkill, the namespace will - always persist. Otherwise, the namespace will persist based on the - active page's persistence. - """ - # check if skill_type is idleDisplaySkill - if skill_type == "idleDisplaySkill": - self.persistent = True - self.duration = 0 - - else: - # get the active page in the namespace - active_page = self.get_active_page() - - # if type(persistence) == int: - # Get the duration of the active page if it is not persistent - if active_page is not None and not active_page.persistent: - self.persistent = False - self.duration = active_page.duration - - # elif type(persistence) == bool: - # Get the persistance of the active page - elif active_page is not None and active_page.persistent: - self.persistent = True - self.duration = 0 - - # else use the default duration of 30 seconds - else: - self.persistent = False - self.duration = 30 - - def load_pages(self, pages: List[str], show_index: None): - """Maintains a list of active pages within the active namespace. - - Skills with multiple pages of data can either show all the screens - at once, allowing the user to swipe back and forth among them, or - the pages can be loaded one at a time. The latter is represented by - a single list item, the former by multiple list items - - Args: - pages: one or more pages to be displayed - """ - new_pages = list() - - for page in pages: - if page.url not in [p.url for p in self.pages]: - new_pages.append(page) - - self.pages.extend(new_pages) - if new_pages: - self._add_pages(new_pages) - else: - page = pages[0] - - if show_index: - self._activate_page(pages[show_index]) - else: - self._activate_page(pages[0]) - - def _add_pages(self, new_pages: List[str]): - """Adds once or more pages to the active page list. - - Args: - new_pages: pages to add to the active page list - """ - LOG.info(f"Adding pages to GUI namespace {self.name}: {new_pages}") - LOG.info(f"Current pages: {self.pages}") - # print the attributes of the new pages - for page in new_pages: - LOG.info( - f"Page: {page.url}, {page.name}, {page.persistent}, {page.duration}") - - # Find position of new page in self.pages - position = self.pages.index(new_pages[0]) - - message = dict( - type="mycroft.gui.list.insert", - namespace=self.name, - position=position, - data=[dict(url=page.url) for page in new_pages] - ) - send_message_to_gui(message) - - def _activate_page(self, page: str): - """Returns focus to a page already in the active page list. - - Args: - page: the page that will gain focus - """ - LOG.info(f"Activating page {page.name} in GUI namespace {self.name}") - LOG.info(f"Current pages from _activate_page: {self.pages}") - # get the index of the page in the self.pages list - page_index = 0 - for i, p in enumerate(self.pages): - if p.url == page.url: - page_index = i - break - - self.page_number = page_index - - # set the page active attribute to True and update the self.pages list, mark all other pages as inactive - - page.active = True - - for p in self.pages: - if p != page: - p.active = False - # update the self.pages list with the page active status changes - self.pages[self.pages.index(p)] = p - - self.pages[page_index] = page - - message = dict( - type="mycroft.events.triggered", - namespace=self.name, - event_name="page_gained_focus", - data=dict(number=page_index) - ) - send_message_to_gui(message) - - def remove_pages(self, positions: List[int]): - """Deletes one or more pages from the active page list. - - Args: - positions: page position to remove - """ - LOG.info(f"Removing pages from GUI namespace {self.name}: {positions}") - for position in positions: - page = self.pages.pop(position) - LOG.info(f"Deleting {page} from GUI namespace {self.name}") - message = dict( - type="mycroft.gui.list.remove", - namespace=self.name, - position=position, - items_number=1 - ) - send_message_to_gui(message) - - def page_gained_focus(self, page_number): - """Updates the active page in self.pages - - Args: - page_number: the page number of the page that will gain focus - """ - LOG.info( - f"Page {page_number} gained focus in GUI namespace {self.name}") - self._activate_page(self.pages[page_number]) - - def page_update_interaction(self, page_number): - """Update the interaction of the page_number""" - - LOG.info( - f"Page {page_number} update interaction in GUI namespace {self.name}") - page = self.pages.index(page_number) - if not page.persistent and page.duration > 0: - page.duration = page.duration / 2 - - # update the self.pages list with the page interaction status changes - self.pages[page_number] = page - self.set_persistence(skill_type="genericSkill") - - def get_page_at_position(self, position: int): - """Returns the position of the page in the active page list.""" - return self.pages.index(position) - - def get_active_page(self): - """Returns the currently active page from self.pages where the page attribute active is true""" - for page in self.pages: - if page.active: - return page - return None - - def get_active_page_index(self): - # get the active page index in the self.pages list - active_page = self.get_active_page() - if active_page is not None: - return self.pages.index(active_page) - - def index_in_pages_list(self, index): - return(index < len(self.pages)) - - def global_back(self): - """Returns to the previous page in the active page list.""" - if self.page_number > 0: - self.remove_pages([self.page_number]) - self.page_gained_focus(self.page_number - 1) - - -def _validate_page_message(message: Message): - """Validates the contents of the message data for page add/remove messages. - - Args: - message: A core message bus message to add/remove one or more pages - from a namespace. - """ - valid = ( - "page" in message.data - and "__from" in message.data - and isinstance(message.data["page"], list) - ) - if not valid: - if message.msg_type == "gui.page.show": - action = "shown" - else: - action = "removed" - LOG.error( - f"Page will not be {action} due to malformed data in the" - f"{message.msg_type} message" - ) - - return valid - - -def _get_idle_display_config(): - """Retrieves the current value of the idle display skill configuration.""" - LOG.info("Getting Idle Skill From Config") - config = Configuration() - enclosure_config = config.get("gui") or {} - idle_display_skill = enclosure_config.get("idle_display_skill") - - return idle_display_skill - - -def _get_active_gui_extension(): - """Retrieves the current value of the gui extension configuration. """ - LOG.info("Getting GUI Extension From Config") - config = Configuration() - enclosure_config = config.get("gui") or {} - gui_extension = enclosure_config.get("extension", "generic") - - return gui_extension.lower() - - -class NamespaceManager: - """Manages the active namespace stack and the content of namespaces. - - Attributes: - core_bus: client for communicating with the core message bus - gui_bus: client for communicating with the GUI message bus - loaded_namespaces: cache of namespaces that have been introduced - active_namespaces: LIFO stack of namespaces being displayed - remove_namespace_timers: background process to remove a namespace with - a persistence expressed in seconds - idle_display_skill: skill ID of the skill that controls the idle screen - """ - - def __init__(self, core_bus: MessageBusClient): - self.core_bus = core_bus - self.gui_bus = create_gui_service(self) - self.loaded_namespaces = dict() - self.active_namespaces = list() - self.remove_namespace_timers = dict() - self.idle_display_skill = _get_idle_display_config() - self.active_extension = _get_active_gui_extension() - self._define_message_handlers() - - def _define_message_handlers(self): - """Assigns methods as handlers for specified message types.""" - self.core_bus.on("gui.clear.namespace", self.handle_clear_namespace) - self.core_bus.on("gui.event.send", self.handle_send_event) - self.core_bus.on("gui.page.delete", self.handle_delete_page) - self.core_bus.on("gui.page.show", self.handle_show_page) - self.core_bus.on("gui.status.request", self.handle_status_request) - self.core_bus.on("gui.value.set", self.handle_set_value) - self.core_bus.on("mycroft.gui.connected", self.handle_client_connected) - self.core_bus.on("gui.page_interaction", self.handle_page_interaction) - self.core_bus.on("gui.page_gained_focus", - self.handle_page_gained_focus) - - def handle_clear_namespace(self, message: Message): - """Handles a request to remove a namespace. - - Args: - message: the message requesting namespace removal - """ - try: - namespace_name = message.data['__from'] - except KeyError: - LOG.error( - "Request to delete namespace failed: no namespace specified" - ) - else: - with namespace_lock: - self._remove_namespace(namespace_name) - - @staticmethod - def handle_send_event(message: Message): - """Handles a request to send a message to the GUI message bus. - - Args: - message: the message requesting a message to be sent to the GUI - message bus. - """ - try: - message = dict( - type='mycroft.events.triggered', - namespace=message.data.get('__from'), - event_name=message.data.get('event_name'), - data=message.data.get('params') - ) - send_message_to_gui(message) - except Exception: - LOG.exception('Could not send event trigger') - - def handle_delete_page(self, message: Message): - """Handles request to remove one or more pages from a namespace. - - Args: - message: the message requesting page removal - """ - message_is_valid = _validate_page_message(message) - if message_is_valid: - namespace_name = message.data["__from"] - pages_to_remove = message.data["page"] - with namespace_lock: - self._remove_pages(namespace_name, pages_to_remove) - - def _remove_pages(self, namespace_name: str, pages_to_remove: List[str]): - """Removes one or more pages from a namespace. - - Pages are removed from the bottom of the stack. - - Args: - namespace_name: the affected namespace - pages_to_remove: names of pages to delete - """ - namespace = self.loaded_namespaces.get(namespace_name) - if namespace is not None and namespace in self.active_namespaces: - page_positions = [] - for index, page in enumerate(pages_to_remove): - # if page matches namespace.pages.url: - if page == namespace.pages[index].url: - page_positions.append(index) - - page_positions.sort(reverse=True) - namespace.remove_pages(page_positions) - - def handle_show_page(self, message: Message): - """Handles a request to show one or more pages on the screen. - - Args: - message: the message containing the page show request - """ - LOG.info("Handling page show request") - message_is_valid = _validate_page_message(message) - if message_is_valid: - namespace_name = message.data["__from"] - pages_to_show = message.data["page"] - persistence = message.data["__idle"] - show_index = message.data.get("index", None) - - pages_to_load = list() - for page in pages_to_show: - name = page.split('/')[-1] - # check if persistence is type of int or bool - if isinstance(persistence, bool): - persist = persistence - duration = 0 - - # check if persistence is type of int - elif isinstance(persistence, int): - persist = False - duration = persistence - - else: - persist = False - duration = 30 - - pages_to_load.append(GuiPage(page, name, persist, duration)) - - with namespace_lock: - self._activate_namespace(namespace_name) - self._load_pages(pages_to_load, show_index) - self._update_namespace_persistence(persistence) - - def _activate_namespace(self, namespace_name: str): - """Instructs the GUI to load a namespace and its associated data. - - Args: - namespace_name: the name of the namespace to load - """ - namespace = self._ensure_namespace_exists(namespace_name) - LOG.info(f"Activating namespace: {namespace_name}") - - if namespace in self.active_namespaces: - namespace_position = self.active_namespaces.index(namespace) - namespace.activate(namespace_position) - self.active_namespaces.insert( - 0, self.active_namespaces.pop(namespace_position) - ) - else: - namespace.add() - self.active_namespaces.insert(0, namespace) - for key, value in namespace.data.items(): - namespace.load_data(key, value) - - self._emit_namespace_displayed_event() - - def _ensure_namespace_exists(self, namespace_name: str) -> Namespace: - """Retrieves the requested namespace, creating one if it doesn't exist. - - Args: - namespace_name: the name of the namespace being retrieved - - Returns: - the requested namespace - """ - # TODO: - Update sync to match. - namespace = self.loaded_namespaces.get(namespace_name) - if namespace is None: - namespace = Namespace(namespace_name) - self.loaded_namespaces[namespace_name] = namespace - - return namespace - - def _load_pages(self, pages_to_show: str, show_index: None): - """Loads the requested pages in the namespace. - - Args: - pages_to_show: the pages requested to be loaded - """ - active_namespace = self.active_namespaces[0] - active_namespace.load_pages(pages_to_show, show_index) - - def _update_namespace_persistence(self, persistence: Union[bool, int]): - """Sets the persistence of the namespace being activated. - - A namespace's persistence is the same as the persistence of the - most recent pages added to a namespace. For example, a multi-page - namespace could show the first set of pages with a persistence of - True (show until removed) and the last page with a persistence of - 15 seconds. This would ensure that the namespace isn't removed while - the skill is showing the pages. - - Args:active_extension - persistence: length of time the namespace should be displayed - """ - LOG.debug(f"Setting namespace persistence to {persistence}") - for position, namespace in enumerate(self.active_namespaces): - if position: - if not namespace.persistent: - self._remove_namespace(namespace.name) - else: - if namespace.name == self.idle_display_skill: - namespace.set_persistence(skill_type="idleDisplaySkill") - else: - namespace.set_persistence(skill_type="genericSkill") - - # check if there is a scheduled remove_namespace_timer and cancel it - if namespace.persistent: - if namespace.name in self.remove_namespace_timers: - self.remove_namespace_timers[namespace.name].cancel( - ) - self._del_namespace_in_remove_timers( - namespace.name) - - if not namespace.persistent: - LOG.info("It is being scheduled here") - self._schedule_namespace_removal(namespace) - - def _schedule_namespace_removal(self, namespace: Namespace): - """Uses a timer thread to remove the namespace. - - Args: - namespace: the namespace to be removed - """ - # Before removing check if there isn't already a timer for this namespace - if namespace.name in self.remove_namespace_timers: - return - - remove_namespace_timer = Timer( - namespace.duration, - self._remove_namespace_via_timer, - args=(namespace.name,) - ) - LOG.debug( - f"Scheduled removal of namespace {namespace.name} in duration {namespace.duration}") - remove_namespace_timer.start() - self.remove_namespace_timers[namespace.name] = remove_namespace_timer - - def _remove_namespace_via_timer(self, namespace_name: str): - """Removes a namespace and the corresponding timer instance.""" - self._remove_namespace(namespace_name) - self._del_namespace_in_remove_timers(namespace_name) - - def _remove_namespace(self, namespace_name: str): - """Removes a namespace from the active namespace stack. - - Args: - namespace_name: namespace to remove - """ - LOG.debug("Removing namespace {namespace_name}") - - # Remove all timers associated with the namespace - if namespace_name in self.remove_namespace_timers: - self.remove_namespace_timers[namespace_name].cancel() - self._del_namespace_in_remove_timers(namespace_name) - - namespace = self.loaded_namespaces.get(namespace_name) - if namespace is not None and namespace in self.active_namespaces: - self.core_bus.emit(Message("gui.namespace.removed", data={ - "skill_id": namespace.name})) - if self.active_extension == "Bigscreen": - # wait for window management in bigscreen extension to finish - sleep(1) - namespace_position = self.active_namespaces.index(namespace) - namespace.remove(namespace_position) - self.active_namespaces.remove(namespace) - - self._emit_namespace_displayed_event() - - def _emit_namespace_displayed_event(self): - if self.active_namespaces: - displaying_namespace = self.active_namespaces[0] - message_data = dict(skill_id=displaying_namespace.name) - self.core_bus.emit( - Message("gui.namespace.displayed", data=message_data) - ) - - def handle_status_request(self, message: Message): - """Handles a GUI status request by replying with the connection status. - - Args: - message: the request for status of the GUI - """ - gui_connected = determine_if_gui_connected() - reply = message.reply( - "gui.status.request.response", dict(connected=gui_connected) - ) - self.core_bus.emit(reply) - - def handle_set_value(self, message: Message): - """Handles a request to set the value of namespace data attributes. - - Args: - message: the request to set attribute values - """ - try: - namespace_name = message.data['__from'] - except KeyError: - LOG.error( - "Request to set gui attribute value failed: no " - "namespace specified" - ) - else: - with namespace_lock: - self._update_namespace_data(namespace_name, message.data) - - def _update_namespace_data(self, namespace_name: str, data: dict): - """Updates the values of namespace data attributes, unless unchanged. - - Args: - namespace_name: the name of the namespace to update - data: the name and new value of one or more data attributes - """ - namespace = self._ensure_namespace_exists(namespace_name) - for key, value in data.items(): - if key not in RESERVED_KEYS and namespace.data.get(key) != value: - LOG.debug( - f"Setting {key} to {value} in namespace {namespace.name}") - namespace.data[key] = value - if namespace in self.active_namespaces: - namespace.load_data(key, value) - - def handle_client_connected(self, message: Message): - """Handles an event from the GUI indicating it is connected to the bus. - - Args: - message: the event sent by the GUI - """ - # GUI has announced presence - # Announce connection, the GUI should connect on it soon - gui_id = message.data.get("gui_id") - LOG.info(f"GUI with ID {gui_id} connected to core message bus") - websocket_config = get_gui_websocket_config() - port = websocket_config["base_port"] - message = Message("mycroft.gui.port", dict(port=port, gui_id=gui_id)) - self.core_bus.emit(message) - - def handle_page_interaction(self, message: Message): - """Handles an event from the GUI indicating the page has been interacted with. - - Args: - message: the event sent by the GUI - """ - # GUI has interacted with a page - # Update and increase the namespace duration and reset the remove timer - namespace_name = message.data.get("skill_id") - LOG.debug(f"GUI interacted with page in namespace {namespace_name}") - if namespace_name == self.idle_display_skill: - return - else: - namespace = self.loaded_namespaces.get(namespace_name) - if not namespace.persistent: - if self.remove_namespace_timers[namespace.name]: - self.remove_namespace_timers[namespace.name].cancel() - self._del_namespace_in_remove_timers(namespace.name) - self._schedule_namespace_removal(namespace) - - def handle_page_gained_focus(self, message: Message): - """Handles focus events from the GUI indicating the page has gained focus. - - Args: - message: the event sent by the GUI - """ - namespace_name = message.data.get("skill_id") - namespace_page_number = message.data.get("page_number") - LOG.debug(f"Page in namespace {namespace_name} gained focus") - namespace = self.loaded_namespaces.get(namespace_name) - - # first check if the namespace is already active - if namespace in self.active_namespaces: - # if the namespace is already active, check if the page number has changed - if namespace_page_number != namespace.page_number: - namespace.page_gained_focus(namespace_page_number) - - def handle_namespace_global_back(self, message: None): - """Handles global back events from the GUI. - - Args: - message: the event sent by the GUI - """ - namespace_name = self.active_namespaces[0].name - namespace = self.loaded_namespaces.get(namespace_name) - if namespace in self.active_namespaces: - namespace.global_back() - - def _del_namespace_in_remove_timers(self, namespace_name): - """ Delete namespace from remove_namespace_timers dict. - - Args: - namespace: namespace to be deleted - """ - if namespace_name in self.remove_namespace_timers: - del self.remove_namespace_timers[namespace_name] +# backwards compat import +from ovos_gui.namespace import Namespace, NamespaceManager, namespace_lock, RESERVED_KEYS diff --git a/mycroft/gui/page.py b/mycroft/gui/page.py index 49a081aceeba..b4b2c25ba181 100644 --- a/mycroft/gui/page.py +++ b/mycroft/gui/page.py @@ -1,25 +1,2 @@ -from ovos_utils.log import LOG - -class GuiPage: - """ A representation of a GUI Page - - A GuiPage represents a single GUI Display within a given namespace. A Page - has a name, a position and can have either Persistence or Duration during - which it will exist - - Attributes: - name: the name of the page that is shown in a given namespace, assigned - by the skill author - persistent: indicated weather or not the page itself should persists for a - period of time or unit the it is removed manually - duration: the duration of the page in the namespace, assigned by the skill - author if the page is not persistent - active: indicates whether the page is currently active in the namespace - """ - - def __init__(self, url: str, name: str, persistent: bool, duration: int): - self.url = url - self.name = name - self.persistent = persistent - self.duration = duration - self.active = False +# backwards compat import +from ovos_gui.page import GuiPage \ No newline at end of file diff --git a/mycroft/gui/service.py b/mycroft/gui/service.py index 1d65c094c707..d33772bd3142 100644 --- a/mycroft/gui/service.py +++ b/mycroft/gui/service.py @@ -1,57 +1,5 @@ -from ovos_bus_client.client import MessageBusClient -from mycroft.util import start_message_bus_client -from ovos_utils.log import LOG -from mycroft.gui.namespace import NamespaceManager -from mycroft.gui.extensions import ExtensionsManager -from ovos_utils.process_utils import ProcessStatus, StatusCallbackMap, ProcessState +# backwards compat imports +from ovos_gui.namespace import NamespaceManager +from ovos_gui.extensions import ExtensionsManager +from ovos_gui.service import GUIService, on_started, on_alive, on_ready, on_error, on_stopping -def on_started(): - LOG.info('Gui Service is starting up.') - - -def on_alive(): - LOG.info('Gui Service is alive.') - - -def on_ready(): - LOG.info('Gui Service is ready.') - - -def on_error(e='Unknown'): - LOG.info(f'Gui Service failed to launch ({e})') - - -def on_stopping(): - LOG.info('Gui Service is shutting down...') - - -class GUIService: - def __init__(self, alive_hook=on_alive, started_hook=on_started, ready_hook=on_ready, - error_hook=on_error, stopping_hook=on_stopping): - self.bus = MessageBusClient() - self.gui = NamespaceManager(self.bus) - callbacks = StatusCallbackMap(on_started=started_hook, - on_alive=alive_hook, - on_ready=ready_hook, - on_error=error_hook, - on_stopping=stopping_hook) - self.status = ProcessStatus('gui_service', callback_map=callbacks) - self.status.bind(self.bus) - - def run(self): - """Start the GUI after it has been constructed.""" - # Allow exceptions to be raised to the GUI Service - # if they may cause the Service to fail. - self.status.set_alive() - start_message_bus_client("GUI_SERVICE", self.bus) - extension_manager = ExtensionsManager( - "EXTENSION_SERVICE", self.bus, self.gui) - self.status.set_ready() - - def is_alive(self): - """Respond to is_alive status request.""" - return self.status.state >= ProcessState.ALIVE - - def stop(self): - """Perform any GUI shutdown processes.""" - self.status.set_stopping() diff --git a/mycroft/messagebus/__init__.py b/mycroft/messagebus/__init__.py index 03284a4e4dc0..9c6e62020532 100644 --- a/mycroft/messagebus/__init__.py +++ b/mycroft/messagebus/__init__.py @@ -16,6 +16,6 @@ from ovos_bus_client.send_func import send try: - from mycroft.messagebus.service.event_handler import MessageBusEventHandler + from ovos_messagebus.event_handler import MessageBusEventHandler except ImportError: pass # do not require tornado installed, only needed to open websocket! diff --git a/mycroft/messagebus/message.py b/mycroft/messagebus/message.py index e44fa61b3873..3469d7117f5a 100644 --- a/mycroft/messagebus/message.py +++ b/mycroft/messagebus/message.py @@ -1,5 +1,3 @@ -# Copyright 2017 Mycroft AI Inc. -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/mycroft/messagebus/service/__main__.py b/mycroft/messagebus/service/__main__.py index 622e142eb686..302a7cf5d097 100644 --- a/mycroft/messagebus/service/__main__.py +++ b/mycroft/messagebus/service/__main__.py @@ -19,8 +19,7 @@ systems to integrate with the Mycroft system. """ from ovos_bus_client.conf import load_message_bus_config -from mycroft.messagebus.service.event_handler import MessageBusEventHandler - +from ovos_messagebus.event_handler import MessageBusEventHandler from ovos_utils.process_utils import reset_sigint_handler from ovos_utils import create_daemon, wait_for_exit_signal from ovos_utils.log import LOG, init_service_logger diff --git a/mycroft/messagebus/service/event_handler.py b/mycroft/messagebus/service/event_handler.py index 2287e334776a..2dbc5d986357 100644 --- a/mycroft/messagebus/service/event_handler.py +++ b/mycroft/messagebus/service/event_handler.py @@ -1,70 +1,2 @@ -# Copyright 2017 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""Define the web socket event handler for the message bus.""" -import json -import sys -import traceback - -from tornado.websocket import WebSocketHandler -from pyee import EventEmitter - -from ovos_bus_client.message import Message -from ovos_utils.log import LOG - -client_connections = [] - - -class MessageBusEventHandler(WebSocketHandler): - def __init__(self, application, request, **kwargs): - super().__init__(application, request, **kwargs) - self.emitter = EventEmitter() - - def on(self, event_name, handler): - self.emitter.on(event_name, handler) - - def on_message(self, message): - LOG.debug(message) - try: - deserialized_message = Message.deserialize(message) - except Exception: - return - - try: - self.emitter.emit(deserialized_message.msg_type, - deserialized_message) - except Exception as e: - LOG.exception(e) - traceback.print_exc(file=sys.stdout) - pass - - for client in client_connections: - client.write_message(message) - - def open(self): - self.write_message(Message("connected").serialize()) - client_connections.append(self) - - def on_close(self): - client_connections.remove(self) - - def emit(self, channel_message): - if (hasattr(channel_message, 'serialize') and - callable(getattr(channel_message, 'serialize'))): - self.write_message(channel_message.serialize()) - else: - self.write_message(json.dumps(channel_message)) - - def check_origin(self, origin): - return True +# backwards compat import +from ovos_messagebus.event_handler import MessageBusEventHandler, client_connections \ No newline at end of file diff --git a/mycroft/metrics/__init__.py b/mycroft/metrics/__init__.py index fd54a0709279..edc07a7da76d 100644 --- a/mycroft/metrics/__init__.py +++ b/mycroft/metrics/__init__.py @@ -21,7 +21,7 @@ from ovos_backend_client.api import MetricsApi from ovos_config.config import Configuration -from mycroft.session import SessionManager +from ovos_bus_client.session import SessionManager from ovos_utils.log import LOG from mycroft.version import CORE_VERSION_STR from copy import copy diff --git a/mycroft/skills/__init__.py b/mycroft/skills/__init__.py index 91dba3a67659..6b344d9bcac6 100644 --- a/mycroft/skills/__init__.py +++ b/mycroft/skills/__init__.py @@ -17,11 +17,12 @@ These classes, decorators and functions are used to build skills for Mycroft. """ -from mycroft.skills.mycroft_skill import (MycroftSkill, intent_handler, +from mycroft.skills.mycroft_skill import (intent_handler, intent_file_handler, resting_screen_handler, skill_api_method) -from mycroft.skills.fallback_skill import FallbackSkill +from ovos_workshop.skills.fallback import FallbackSkill +from ovos_workshop.skills.mycroft_skill import MycroftSkill from mycroft.skills.common_iot_skill import CommonIoTSkill from mycroft.skills.common_play_skill import CommonPlaySkill, CPSMatchLevel from mycroft.skills.common_query_skill import CommonQuerySkill, CQSMatchLevel diff --git a/mycroft/skills/__main__.py b/mycroft/skills/__main__.py index 0e50398d6b71..8f2f7cbc42ee 100644 --- a/mycroft/skills/__main__.py +++ b/mycroft/skills/__main__.py @@ -22,7 +22,7 @@ import mycroft.lock from ovos_config.locale import setup_locale from mycroft.skills.api import SkillApi -from mycroft.skills.core import FallbackSkill +from ovos_workshop.skills.fallback import FallbackSkill from ovos_bus_client.util.scheduler import EventScheduler from mycroft.skills.intent_service import IntentService from mycroft.skills.skill_manager import SkillManager, on_error, on_stopping, on_ready, on_alive, on_started diff --git a/mycroft/skills/api.py b/mycroft/skills/api.py index a0d1ed7a44eb..82cd7950d75a 100644 --- a/mycroft/skills/api.py +++ b/mycroft/skills/api.py @@ -19,7 +19,7 @@ from ovos_bus_client.message import Message -class SkillApi(): +class SkillApi: """SkillApi providing a simple interface to exported methods from skills Methods are built from a method_dict provided when initializing the skill. diff --git a/mycroft/skills/common_iot_skill.py b/mycroft/skills/common_iot_skill.py index 54769fc0e76d..5768a1313865 100644 --- a/mycroft/skills/common_iot_skill.py +++ b/mycroft/skills/common_iot_skill.py @@ -24,7 +24,7 @@ from functools import total_ordering, wraps from itertools import count -from mycroft.skills.mycroft_skill import MycroftSkill +from ovos_workshop.skills.mycroft_skill import MycroftSkill from ovos_bus_client.message import Message, dig_for_message ENTITY = "ENTITY" diff --git a/mycroft/skills/common_play_skill.py b/mycroft/skills/common_play_skill.py index eecc5c66404e..76916d447d61 100644 --- a/mycroft/skills/common_play_skill.py +++ b/mycroft/skills/common_play_skill.py @@ -16,7 +16,7 @@ from enum import Enum, IntEnum from abc import ABC, abstractmethod from ovos_bus_client.message import Message -from mycroft.skills.mycroft_skill import MycroftSkill +from ovos_workshop.skills.mycroft_skill import MycroftSkill from mycroft.skills.audioservice import AudioService diff --git a/mycroft/skills/core.py b/mycroft/skills/core.py index 31cc266a96ef..48ec3dc89630 100644 --- a/mycroft/skills/core.py +++ b/mycroft/skills/core.py @@ -20,16 +20,8 @@ # Import moved methods for backwards compatibility # This will need to remain here for quite some time since removing it # would break most of the skills out there. -import mycroft.skills.mycroft_skill as mycroft_skill -import mycroft.skills.fallback_skill as fallback_skill -from mycroft.skills.mycroft_skill import * # noqa +from ovos_workshop.skills.mycroft_skill import MycroftSkill +from ovos_workshop.skills.fallback import FallbackSkill +from mycroft.skills.mycroft_skill import resting_screen_handler, intent_handler, intent_file_handler, skill_api_method -class MycroftSkill(mycroft_skill.MycroftSkill): - # Compatibility, needs to be kept for a while to not break every skill - pass - - -class FallbackSkill(fallback_skill.FallbackSkill): - # Compatibility, needs to be kept for a while to not break every skill - pass diff --git a/mycroft/skills/mycroft_skill/__init__.py b/mycroft/skills/mycroft_skill/__init__.py index eeb2f905a7dc..0d99907e73c6 100644 --- a/mycroft/skills/mycroft_skill/__init__.py +++ b/mycroft/skills/mycroft_skill/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from mycroft.skills.mycroft_skill.mycroft_skill import MycroftSkill +from ovos_workshop.skills.mycroft_skill import MycroftSkill from ovos_utils.messagebus import get_handler_name from mycroft.skills.mycroft_skill.decorators import (intent_handler, intent_file_handler, diff --git a/mycroft/skills/mycroft_skill/mycroft_skill.py b/mycroft/skills/mycroft_skill/mycroft_skill.py index 67b711914420..7409b4c86bf3 100644 --- a/mycroft/skills/mycroft_skill/mycroft_skill.py +++ b/mycroft/skills/mycroft_skill/mycroft_skill.py @@ -17,7 +17,7 @@ # backwards compat imports, do not delete! from ovos_utils.intents import Intent, IntentBuilder from ovos_utils.skills import get_non_properties -from mycroft.gui import SkillGUI +from ovos_workshop.skills.base import SkillGUI from ovos_bus_client.message import Message, dig_for_message from mycroft.metrics import report_metric from ovos_bus_client.util.scheduler import EventScheduler, EventSchedulerInterface diff --git a/mycroft/skills/skill_manager.py b/mycroft/skills/skill_manager.py index 9edef18896f0..808c1212a5c0 100644 --- a/mycroft/skills/skill_manager.py +++ b/mycroft/skills/skill_manager.py @@ -21,7 +21,7 @@ from ovos_utils.process_utils import ProcessStatus, StatusCallbackMap, ProcessState from ovos_backend_client.pairing import is_paired -from mycroft.enclosure.api import EnclosureAPI +from ovos_utils.enclosure.api import EnclosureAPI from ovos_config.config import Configuration from ovos_bus_client.message import Message from ovos_utils.log import LOG @@ -33,7 +33,7 @@ from mycroft.skills.skill_updater import SeleneSkillManifestUploader from ovos_bus_client.client import MessageBusClient -# do not delete - bacwards compat imports +# do not delete - backwards compat imports from ovos_workshop.skill_launcher import SKILL_MAIN_MODULE from mycroft.deprecated.skills.settings import UploadQueue, SkillSettingsDownloader from mycroft.deprecated.skills.skill_updater import SkillUpdater diff --git a/mycroft/stt/__init__.py b/mycroft/stt/__init__.py index 6f02baa71e47..476dce5846e4 100644 --- a/mycroft/stt/__init__.py +++ b/mycroft/stt/__init__.py @@ -2,4 +2,5 @@ This module contains back compat imports only logic moved into mycroft.audio module and ovos plugin manager """ -from mycroft.deprecated.stt import * +from mycroft.deprecated.stt import STTFactory, STT, StreamingSTT, StreamThread, \ + KeySTT, TokenSTT, BasicSTT, MycroftSTT, GoogleJsonSTT diff --git a/mycroft/util/audio_test.py b/mycroft/util/audio_test.py index 5f057cc02e22..2ceb09672af3 100644 --- a/mycroft/util/audio_test.py +++ b/mycroft/util/audio_test.py @@ -19,7 +19,7 @@ from speech_recognition import Recognizer -from mycroft.listener.mic import MutableMicrophone +from ovos_listener.mic import MutableMicrophone from ovos_config.config import Configuration from ovos_utils.sound import play_wav from mycroft.util.audio_utils import find_input_device diff --git a/mycroft/util/format.py b/mycroft/util/format.py index aab59ec64ca9..8b1e34bdc6b4 100644 --- a/mycroft/util/format.py +++ b/mycroft/util/format.py @@ -30,7 +30,7 @@ from calendar import leapdays from enum import Enum -from mycroft.util.bracket_expansion import expand_parentheses, expand_options +from ovos_utils.bracket_expansion import expand_parentheses, expand_options # lingua_franca is optional, individual skills may install it if they need # to use it @@ -49,7 +49,7 @@ def lingua_franca_error(*args, **kwargs): raise ImportError("lingua_franca is not installed") - from mycroft.util.bracket_expansion import expand_options + from ovos_utils.bracket_expansion import expand_options NUMBER_TUPLE, DateTimeFormat = None, None diff --git a/requirements/extra-PHAL.txt b/requirements/extra-PHAL.txt index 862bafe96761..5604245dea73 100644 --- a/requirements/extra-PHAL.txt +++ b/requirements/extra-PHAL.txt @@ -1,2 +1 @@ -ovos_PHAL~=0.0, >=0.0.2 -ovos-phal-plugin-connectivity-events~=0.0, >=0.0.1 \ No newline at end of file +ovos-PHAL~=0.0, >=0.0.5a2 \ No newline at end of file diff --git a/requirements/extra-bus.txt b/requirements/extra-bus.txt index 345b4d719d0a..2d89c137dae3 100644 --- a/requirements/extra-bus.txt +++ b/requirements/extra-bus.txt @@ -1 +1,2 @@ -tornado~=6.0, >=6.0.3 \ No newline at end of file +# messagebus service +ovos-messagebus~=0.0, >=0.0.2 \ No newline at end of file diff --git a/requirements/extra-deprecated.txt b/requirements/extra-deprecated.txt index aca94fd183d2..60265f3920a6 100644 --- a/requirements/extra-deprecated.txt +++ b/requirements/extra-deprecated.txt @@ -1,7 +1,9 @@ +# only required if importing from mycroft.deprecated module +# not used internally msm -mock_msm~=0.9 +mock_msm>=0.9 ovos_cli_client python-vlc>=1.1.2 -pyalsaaudio~=0.8 -pyserial~=3.0 -pillow~=8.3 \ No newline at end of file +pyalsaaudio>=0.8 +pyserial>=3.0 +pillow>=8.3 \ No newline at end of file diff --git a/requirements/extra-gui.txt b/requirements/extra-gui.txt index 345b4d719d0a..08616fcc8f04 100644 --- a/requirements/extra-gui.txt +++ b/requirements/extra-gui.txt @@ -1 +1,2 @@ -tornado~=6.0, >=6.0.3 \ No newline at end of file +# gui service +ovos-gui~=0.0, >=0.0.2 \ No newline at end of file diff --git a/requirements/extra-mark1.txt b/requirements/extra-mark1.txt index 681c2ab0a6d7..166980f1ccb6 100644 --- a/requirements/extra-mark1.txt +++ b/requirements/extra-mark1.txt @@ -1,3 +1,4 @@ -pyalsaaudio~=0.8 -pyserial~=3.0 -pillow~=8.3 \ No newline at end of file +# todo - replace with mk1 PHAL plugin dependency +pyalsaaudio>=0.8 +pyserial>=3.0 +pillow>=8.3 diff --git a/requirements/minimal.txt b/requirements/minimal.txt index 15a60abee78d..5a71f62c8a44 100644 --- a/requirements/minimal.txt +++ b/requirements/minimal.txt @@ -7,7 +7,7 @@ padacioso~=0.1.2 ovos_bus_client<0.1.0, >=0.0.4a3 -ovos-utils<0.1.0, >=0.0.33a1 +ovos-utils<0.1.0, >=0.0.33a3 ovos-plugin-manager<0.1.0, >=0.0.23a9 ovos-config~=0.0,>=0.0.8 ovos-lingua-franca>=0.4.7 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index d394a0380dcf..33767c0dfaa2 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -15,7 +15,7 @@ padaos~=0.1 ovos_bus_client<0.1.0, >=0.0.4a3 ovos_backend_client<0.1.0, >=0.0.6 ovos-config~=0.0,>=0.0.8 -ovos-utils<0.1.0, >=0.0.33a1 +ovos-utils<0.1.0, >=0.0.33a3 ovos-plugin-manager<0.1.0, >=0.0.23a9 ovos_workshop<0.1.0, >=0.0.12a20 ovos_PHAL<0.1.0, >=0.0.2 @@ -23,6 +23,8 @@ ovos-lingua-franca>=0.4.7 ovos-audio~=0.0, >=0.0.2a10 ovos-listener~=0.0, >=0.0.2a9 +ovos-gui~=0.0, >=0.0.2 +ovos-messagebus~=0.0, >=0.0.2 ovos-skill-volume>=0.0.1,<0.1.0 ovos-skill-fallback-unknown>=0.0.2,<0.1.0 diff --git a/test/unittests/lock/test_lock.py b/test/integrationtests/test_lock.py similarity index 95% rename from test/unittests/lock/test_lock.py rename to test/integrationtests/test_lock.py index 7458e023a84f..2816ce1e1432 100644 --- a/test/unittests/lock/test_lock.py +++ b/test/integrationtests/test_lock.py @@ -20,8 +20,8 @@ import os from os.path import exists, isfile -from mycroft.lock import Lock -from mycroft.util.file_utils import get_temp_path +from ovos_utils.process_utils import PIDLock as Lock +from ovos_utils.file_utils import get_temp_path from ovos_config.meta import get_xdg_base diff --git a/test/integrationtests/util/__init__.py b/test/integrationtests/util/__init__.py new file mode 100644 index 000000000000..051afedb75cd --- /dev/null +++ b/test/integrationtests/util/__init__.py @@ -0,0 +1 @@ +# TODO - move to ovos-utils diff --git a/test/unittests/util/commented.json b/test/integrationtests/util/commented.json similarity index 100% rename from test/unittests/util/commented.json rename to test/integrationtests/util/commented.json diff --git a/test/unittests/util/muppets.dict b/test/integrationtests/util/muppets.dict similarity index 100% rename from test/unittests/util/muppets.dict rename to test/integrationtests/util/muppets.dict diff --git a/test/unittests/util/plain.json b/test/integrationtests/util/plain.json similarity index 100% rename from test/unittests/util/plain.json rename to test/integrationtests/util/plain.json diff --git a/test/unittests/util/test_audio_utils.py b/test/integrationtests/util/test_audio_utils.py similarity index 100% rename from test/unittests/util/test_audio_utils.py rename to test/integrationtests/util/test_audio_utils.py diff --git a/test/unittests/util/test_download.py b/test/integrationtests/util/test_download.py similarity index 100% rename from test/unittests/util/test_download.py rename to test/integrationtests/util/test_download.py diff --git a/test/unittests/util/test_json_helper.py b/test/integrationtests/util/test_json_helper.py similarity index 100% rename from test/unittests/util/test_json_helper.py rename to test/integrationtests/util/test_json_helper.py diff --git a/test/unittests/util/test_log.py b/test/integrationtests/util/test_log.py similarity index 100% rename from test/unittests/util/test_log.py rename to test/integrationtests/util/test_log.py diff --git a/test/unittests/util/test_monotonic_event.py b/test/integrationtests/util/test_monotonic_event.py similarity index 100% rename from test/unittests/util/test_monotonic_event.py rename to test/integrationtests/util/test_monotonic_event.py diff --git a/test/unittests/util/test_network_utils.py b/test/integrationtests/util/test_network_utils.py similarity index 100% rename from test/unittests/util/test_network_utils.py rename to test/integrationtests/util/test_network_utils.py diff --git a/test/unittests/util/test_parse.py b/test/integrationtests/util/test_parse.py similarity index 100% rename from test/unittests/util/test_parse.py rename to test/integrationtests/util/test_parse.py diff --git a/test/unittests/util/test_platform.py b/test/integrationtests/util/test_platform.py similarity index 100% rename from test/unittests/util/test_platform.py rename to test/integrationtests/util/test_platform.py diff --git a/test/unittests/util/test_plugins.py b/test/integrationtests/util/test_plugins.py similarity index 100% rename from test/unittests/util/test_plugins.py rename to test/integrationtests/util/test_plugins.py diff --git a/test/unittests/util/test_process_utils.py b/test/integrationtests/util/test_process_utils.py similarity index 100% rename from test/unittests/util/test_process_utils.py rename to test/integrationtests/util/test_process_utils.py diff --git a/test/unittests/util/test_signal.py b/test/integrationtests/util/test_signal.py similarity index 100% rename from test/unittests/util/test_signal.py rename to test/integrationtests/util/test_signal.py diff --git a/test/unittests/util/test_string_utils.py b/test/integrationtests/util/test_string_utils.py similarity index 100% rename from test/unittests/util/test_string_utils.py rename to test/integrationtests/util/test_string_utils.py diff --git a/test/unittests/util/test_time.py b/test/integrationtests/util/test_time.py similarity index 100% rename from test/unittests/util/test_time.py rename to test/integrationtests/util/test_time.py diff --git a/test/unittests/util/test_xdg.py b/test/integrationtests/util/test_xdg.py similarity index 100% rename from test/unittests/util/test_xdg.py rename to test/integrationtests/util/test_xdg.py diff --git a/test/unittests/util/unstripped_lines.txt b/test/integrationtests/util/unstripped_lines.txt similarity index 100% rename from test/unittests/util/unstripped_lines.txt rename to test/integrationtests/util/unstripped_lines.txt diff --git a/test/unittests/dialog/test_dialog.py b/test/unittests/dialog/test_dialog.py index 0c3b11447e5d..12df199b84fe 100644 --- a/test/unittests/dialog/test_dialog.py +++ b/test/unittests/dialog/test_dialog.py @@ -21,6 +21,7 @@ from mycroft.util import resolve_resource_file +# TODO - move to ovos-workshop class DialogTest(unittest.TestCase): def setUp(self): self.stache = MustacheDialogRenderer() @@ -102,7 +103,7 @@ def test_dialog_loader_missing(self): def test_get(self): phrase = 'i didn\'t catch that' res_file = pathlib.Path('text/en-us/').joinpath(phrase + '.dialog') - print(res_file) + resource = resolve_resource_file(str(res_file)) with open(resource) as f: results = [line.strip() for line in f] diff --git a/test/unittests/enclosure/__init__.py b/test/unittests/enclosure/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/unittests/enclosure/test_gui.py b/test/unittests/enclosure/test_gui.py deleted file mode 100644 index 7c416d2b3b11..000000000000 --- a/test/unittests/enclosure/test_gui.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright 2020 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""Tests for the Enclosure GUI interface.""" - -from unittest import TestCase, mock - -from mycroft.gui import SkillGUI -from ovos_bus_client.message import Message -from mycroft.util.file_utils import resolve_resource_file - - -class TestSkillGUI(TestCase): - def setUp(self): - self.mock_skill = mock.Mock(name='Skill') - self.mock_skill.skill_id = 'fortytwo-skill' - - def find_resource(page): - return resolve_resource_file(f'ui/{page}') or f'/test/ui/{page}' - - self.mock_skill._resources.locate_qml_file = find_resource - - self.gui = SkillGUI(self.mock_skill) - - def test_show_page(self): - self.gui.show_page('meaning.qml') - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - self.assertEqual(sent_message.msg_type, 'gui.page.show') - self.assertEqual(sent_message.data['__from'], 'fortytwo-skill') - self.assertEqual(sent_message.data['page'], - ['file:///test/ui/meaning.qml']) - self.assertEqual(sent_message.data['__idle'], None) - - def test_show_page_idle_override(self): - self.gui.show_page('meaning.qml', override_idle=60) - - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - self.assertEqual(sent_message.data['__idle'], 60) - - def test_show_pages(self): - self.gui.show_pages(['meaning.qml', 'life.qml', - 'universe.qml', 'everything.qml']) - - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - - expected_pages = ['file:///test/ui/meaning.qml', - 'file:///test/ui/life.qml', - 'file:///test/ui/universe.qml', - 'file:///test/ui/everything.qml'] - self.assertEqual(sent_message.data['page'], expected_pages) - - def test_remove_page(self): - self.gui.remove_page('vogon_poetry.qml') - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - - self.assertEqual(sent_message.msg_type, 'gui.page.delete') - self.assertEqual(sent_message.data['__from'], 'fortytwo-skill') - expected_page = 'file:///test/ui/vogon_poetry.qml' - self.assertEqual(sent_message.data['page'], [expected_page]) - - def test_show_image(self): - self.gui.show_image('arthur_dent.jpg') - - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - page_path = resolve_resource_file('ui/SYSTEM_ImageFrame.qml') - page_url = 'file://{}'.format(page_path) - self.assertEqual(sent_message.data['page'], [page_url]) - self.assertEqual(self.gui['image'], 'arthur_dent.jpg') - - def test_show_animated_image(self): - self.gui.show_animated_image('dancing_zaphod.gif') - - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - page_path = resolve_resource_file('ui/SYSTEM_AnimatedImageFrame.qml') - page_url = 'file://{}'.format(page_path) - self.assertEqual(sent_message.data['page'], [page_url]) - self.assertEqual(self.gui['image'], 'dancing_zaphod.gif') - - def test_show_url(self): - page = ('https://en.wikipedia.org/wiki/' - 'The_Hitchhiker%27s_Guide_to_the_Galaxy') - self.gui.show_url(page) - - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - page_path = resolve_resource_file('ui/SYSTEM_UrlFrame.qml') - page_url = 'file://{}'.format(page_path) - self.assertEqual(sent_message.data['page'], [page_url]) - self.assertEqual(self.gui['url'], page) - - def test_show_html(self): - html = 'This Page!' - self.gui.show_html(html) - - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - page_path = resolve_resource_file('ui/SYSTEM_HtmlFrame.qml') - page_url = 'file://{}'.format(page_path) - self.assertEqual(sent_message.data['page'], [page_url]) - self.assertEqual(self.gui['html'], html) - - def test_send_event(self): - """Check that send_event sends message using the correct format.""" - params = 'Not again' - self.gui.send_event('not.again', params) - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - self.assertEqual(sent_message.msg_type, 'gui.event.send') - self.assertEqual(sent_message.data['__from'], 'fortytwo-skill') - self.assertEqual(sent_message.data['params'], params) - - def test_on_gui_change_callback(self): - """Check that the registered function gets called on message from gui. - """ - result = False - - def callback(): - nonlocal result - result = True - - self.gui.set_on_gui_changed(callback) - self.gui.gui_set(Message('dummy')) - self.assertTrue(result) - - def test_gui_set(self): - """Assert that the gui can set gui variables.""" - vars_from_gui = {'meaning': 43, 'no': 42} - self.gui.gui_set(Message('dummy', data=vars_from_gui)) - self.assertEqual(self.gui['meaning'], 43) - self.assertEqual(self.gui['no'], 42) - - def test_not_connected(self): - response = Message('dummy', data={'connected': False}) - self.mock_skill.bus.wait_for_response.return_value = response - self.assertFalse(self.gui.connected) - - def test_connected(self): - response = Message('dummy', data={'connected': True}) - self.mock_skill.bus.wait_for_response.return_value = response - self.assertTrue(self.gui.connected) - - def test_connected_no_response(self): - """Ensure that a timeout response results in not connected.""" - response = None - self.mock_skill.bus.wait_for_response.return_value = response - self.assertFalse(self.gui.connected) - - def test_get(self): - """Ensure the get method returns expected values.""" - self.gui["example"] = "value" - self.assertEqual(self.gui.get("example"), "value") - self.assertEqual(self.gui.get("nothing"), None) - self.assertEqual(self.gui.get(0), None) - self.gui[0] = "value" - self.assertEqual(self.gui.get(0), "value") - - def test_clear(self): - """Ensure that namespace is cleared.""" - self.gui["example"] = "value" - self.assertEqual(self.gui.get("example"), "value") - self.gui.clear() - self.assertEqual(self.gui.get("example"), None) - - def test_release(self): - """Ensure the correct method and data is sent to close a Skill.""" - self.gui.show_page('meaning.qml') - self.gui.release() - sent_message = self.mock_skill.bus.emit.call_args_list[-1][0][0] - self.assertEqual(sent_message.msg_type, 'mycroft.gui.screen.close') - self.assertEqual(sent_message.data['skill_id'], 'fortytwo-skill') - - def test_shutdown(self): - """Ensure the GUI is cleared and Skill ref removed on shutdown.""" - self.gui["example"] = "value" - self.gui.show_page('meaning.qml') - self.assertEqual(self.gui.skill, self.mock_skill) - self.gui.shutdown() - self.assertEqual(self.gui.get("example"), None) - self.assertEqual(self.gui.skill, None) diff --git a/test/unittests/gui/__init__.py b/test/unittests/gui/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/unittests/gui/test_bigscreen_extension.py b/test/unittests/gui/test_bigscreen_extension.py deleted file mode 100644 index 0fcfa0decf47..000000000000 --- a/test/unittests/gui/test_bigscreen_extension.py +++ /dev/null @@ -1,44 +0,0 @@ -from unittest import TestCase, mock -from unittest.mock import patch -from mycroft.gui.extensions import BigscreenExtension -from ..mocks import MessageBusMock -from mycroft.configuration import Configuration -from test.util import base_config -from ovos_bus_client.message import Message - -PATCH_MODULE = "mycroft.gui.extensions" - -# Add Unit Tests For BigscreenExtension - -class TestBigscreenExtension: - @patch.object(Configuration, 'get') - def test_bigscreen_close_current_window(self, mock_get): - config = base_config() - config.merge( - { - 'gui': { - 'extension': 'bigscreen' - } - }) - mock_get.return_value = config - bigscreen = BigscreenExtension(MessageBusMock(), MessageBusMock()) - bigscreen.close_current_window = mock.Mock() - message_data = Message("gui.namespace.removed", {'skill_id': 'foo'}) - bigscreen.close_current_window(message_data) - bigscreen.close_current_window.assert_any_call(message_data) - - @patch.object(Configuration, 'get') - def test_bigscreen_close_window_by_event(self, mock_get): - config = base_config() - config.merge( - { - 'gui': { - 'extension': 'bigscreen' - } - }) - mock_get.return_value = config - bigscreen = BigscreenExtension(MessageBusMock(), MessageBusMock()) - bigscreen.close_window_by_event = mock.Mock() - message_data = Message("mycroft.gui.screen.close", {}) - bigscreen.close_window_by_event(message_data) - bigscreen.close_window_by_event.assert_any_call(message_data) diff --git a/test/unittests/gui/test_extensions_manager.py b/test/unittests/gui/test_extensions_manager.py deleted file mode 100644 index 7024b3f5c6e0..000000000000 --- a/test/unittests/gui/test_extensions_manager.py +++ /dev/null @@ -1,29 +0,0 @@ -from unittest import TestCase, mock -from unittest.mock import patch -from mycroft.gui.extensions import ExtensionsManager -from ..mocks import MessageBusMock -from mycroft.configuration import Configuration -from test.util import base_config - -PATCH_MODULE = "mycroft.gui.extensions" - -# Add Unit Tests For ExtensionManager - -class TestExtensionManager: - @patch.object(Configuration, 'get') - def test_extension_manager_activate(self, mock_get): - config = base_config() - config.merge( - { - 'gui': { - 'extension': 'generic', - 'generic': { - 'homescreen_supported': False - } - } - }) - mock_get.return_value = config - extension_manager = ExtensionsManager("ExtensionManager", MessageBusMock(), MessageBusMock()) - extension_manager.activate_extension = mock.Mock() - extension_manager.activate_extension("generic") - extension_manager.activate_extension.assert_any_call("generic") diff --git a/test/unittests/gui/test_namespace.py b/test/unittests/gui/test_namespace.py deleted file mode 100644 index 5ff14a2beec2..000000000000 --- a/test/unittests/gui/test_namespace.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright 2022 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""Tests for the GUI namespace helper class.""" - -from unittest import TestCase, mock - -from mycroft.gui.namespace import Namespace -from mycroft.gui.page import GuiPage - -PATCH_MODULE = "mycroft.gui.namespace" - - -class TestNamespace(TestCase): - def setUp(self): - self.namespace = Namespace("foo") - - def test_add(self): - add_namespace_message = dict( - type="mycroft.session.list.insert", - namespace="mycroft.system.active_skills", - position=0, - data=[dict(skill_id="foo")] - ) - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function) as send_message_mock: - self.namespace.add() - send_message_mock.assert_called_with(add_namespace_message) - - def test_activate(self): - activate_namespace_message = { - "type": "mycroft.session.list.move", - "namespace": "mycroft.system.active_skills", - "from": 5, - "to": 0, - "items_number": 1 - } - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function) as send_message_mock: - self.namespace.activate(position=5) - send_message_mock.assert_called_with(activate_namespace_message) - - def test_remove(self): - self.namespace.data = dict(foo="bar") - self.namespace.pages = ["foo", "bar"] - remove_namespace_message = dict( - type="mycroft.session.list.remove", - namespace="mycroft.system.active_skills", - position=3, - items_number=1 - ) - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function) as send_message_mock: - self.namespace.remove(position=3) - send_message_mock.assert_called_with(remove_namespace_message) - - self.assertFalse(self.namespace.data) - self.assertFalse(self.namespace.pages) - - def test_load_data(self): - load_data_message = dict( - type="mycroft.session.set", - namespace="foo", - data=dict(foo="bar") - ) - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function) as send_message_mock: - self.namespace.load_data(name="foo", value="bar") - send_message_mock.assert_called_with(load_data_message) - - def test_set_persistence_numeric(self): - self.namespace.set_persistence("genericSkill") - self.assertEqual(self.namespace.duration, 30) - self.assertFalse(self.namespace.persistent) - - def test_set_persistence_boolean(self): - self.namespace.set_persistence("idleDisplaySkill") - self.assertEqual(self.namespace.duration, 0) - self.assertTrue(self.namespace.persistent) - - def test_load_new_pages(self): - self.namespace.pages = [GuiPage("foo", "foo.qml", True, 0), GuiPage("bar", "bar.qml", False, 30)] - new_pages = [GuiPage("foobar", "foobar.qml", False, 30)] - load_page_message = dict( - type="mycroft.events.triggered", - namespace="foo", - event_name="page_gained_focus", - data=dict(number=2) - ) - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function) as send_message_mock: - show_index = None - self.namespace.load_pages(new_pages, show_index) - send_message_mock.assert_called_with(load_page_message) - self.assertListEqual(self.namespace.pages, self.namespace.pages) - - def test_load_existing_pages(self): - self.namespace.pages = [GuiPage("foo", "foo.qml", True, 0), GuiPage("bar", "bar.qml", False, 30)] - new_pages = [GuiPage("foo", "foo.qml", True, 0)] - load_page_message = dict( - type="mycroft.events.triggered", - namespace="foo", - event_name="page_gained_focus", - data=dict(number=0) - ) - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function) as send_message_mock: - show_index = None - self.namespace.load_pages(new_pages, show_index) - send_message_mock.assert_called_with(load_page_message) - self.assertListEqual(self.namespace.pages, self.namespace.pages) - - def test_remove_pages(self): - self.namespace.pages = ["foo", "bar", "foobar"] - remove_page_message = dict( - type="mycroft.gui.list.remove", - namespace="foo", - position=2, - items_number=1 - ) - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function) as send_message_mock: - self.namespace.remove_pages([2]) - send_message_mock.assert_called_with(remove_page_message) - self.assertListEqual(["foo", "bar"], self.namespace.pages) diff --git a/test/unittests/gui/test_namespace_manager.py b/test/unittests/gui/test_namespace_manager.py deleted file mode 100644 index 954df60aa46f..000000000000 --- a/test/unittests/gui/test_namespace_manager.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2022 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""Tests for the GUI namespace manager helper class.""" - -from unittest import TestCase, mock - -from mycroft.gui.namespace import Namespace, NamespaceManager -from mycroft.gui.page import GuiPage -from ovos_bus_client.message import Message -from ..mocks import MessageBusMock - -PATCH_MODULE = "mycroft.gui.namespace" - - -class TestNamespace(TestCase): - def setUp(self): - with mock.patch(PATCH_MODULE + ".create_gui_service"): - self.namespace_manager = NamespaceManager(MessageBusMock()) - - def test_handle_clear_active_namespace(self): - namespace = Namespace("foo") - namespace.remove = mock.Mock() - self.namespace_manager.loaded_namespaces = dict(foo=namespace) - self.namespace_manager.active_namespaces = [namespace] - - message = Message("gui.clear.namespace", data={"__from": "foo"}) - self.namespace_manager.handle_clear_namespace(message) - namespace.remove.assert_called_with(0) - - def test_handle_clear_inactive_namespace(self): - message = Message("gui.clear.namespace", data={"__from": "foo"}) - namespace = Namespace("foo") - namespace.remove = mock.Mock() - self.namespace_manager.handle_clear_namespace(message) - namespace.remove.assert_not_called() - - def test_handle_send_event(self): - message_data = { - "__from": "foo", "event_name": "bar", "params": "foobar" - } - message = Message("gui.clear.namespace", data=message_data) - event_triggered_message = dict( - type='mycroft.events.triggered', - namespace="foo", - event_name="bar", - data="foobar" - ) - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function) as send_message_mock: - self.namespace_manager.handle_send_event(message) - send_message_mock.assert_called_with(event_triggered_message) - - def test_handle_delete_active_namespace_page(self): - namespace = Namespace("foo") - namespace.pages = [GuiPage("bar", "bar.qml", True, 0)] - namespace.remove_pages = mock.Mock() - self.namespace_manager.loaded_namespaces = dict(foo=namespace) - self.namespace_manager.active_namespaces = [namespace] - - message_data = {"__from": "foo", "page": ["bar"]} - message = Message("gui.clear.namespace", data=message_data) - self.namespace_manager.handle_delete_page(message) - namespace.remove_pages.assert_called_with([0]) - - def test_handle_delete_inactive_namespace_page(self): - namespace = Namespace("foo") - namespace.pages = ["bar"] - namespace.remove_pages = mock.Mock() - - message_data = {"__from": "foo", "page": ["bar"]} - message = Message("gui.clear.namespace", data=message_data) - self.namespace_manager.handle_delete_page(message) - namespace.remove_pages.assert_not_called() - - def test_handle_show_pages(self): - message_data = {"__from": "foo", "__idle": 10, "page": ["bar"]} - message = Message("gui.page.show", data=message_data) - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function): - self.namespace_manager._schedule_namespace_removal = mock.Mock() - self.namespace_manager.handle_show_page(message) - - self.assertEqual( - "foo", self.namespace_manager.active_namespaces[0].name - ) - self.assertTrue("foo" in self.namespace_manager.loaded_namespaces) - namespace = self.namespace_manager.loaded_namespaces["foo"] - self.assertListEqual(namespace.pages, namespace.pages) - - def test_handle_show_pages_invalid_message(self): - namespace = Namespace("foo") - namespace.load_pages = mock.Mock() - - message_data = {"__from": "foo"} - message = Message("gui.page.show", data=message_data) - patch_function = PATCH_MODULE + ".send_message_to_gui" - with mock.patch(patch_function): - self.namespace_manager.handle_show_page(message) - - self.assertListEqual([], self.namespace_manager.active_namespaces) - self.assertDictEqual({}, self.namespace_manager.loaded_namespaces) diff --git a/test/unittests/gui/test_smartspeaker_extension.py b/test/unittests/gui/test_smartspeaker_extension.py deleted file mode 100644 index f0ddbf2975f0..000000000000 --- a/test/unittests/gui/test_smartspeaker_extension.py +++ /dev/null @@ -1,44 +0,0 @@ -from unittest import TestCase, mock -from unittest.mock import patch -from mycroft.gui.extensions import SmartSpeakerExtension -from ..mocks import MessageBusMock -from mycroft.configuration import Configuration -from test.util import base_config -from ovos_bus_client.message import Message - -PATCH_MODULE = "mycroft.gui.extensions" - -# Add Unit Tests For SmartSpeakerExtension - -class TestSmartSpeakerExtension: - @patch.object(Configuration, 'get') - def test_smartspeaker_set_backend_type(self, mock_get): - config = base_config() - config.merge( - { - 'gui': { - 'extension': 'smartspeaker' - } - }) - mock_get.return_value = config - smartSpeaker = SmartSpeakerExtension(MessageBusMock(), MessageBusMock()) - smartSpeaker.set_backend_type = mock.Mock() - message_data = Message("ovos.pairing.set.backend", {'backend': 'unknown'}) - smartSpeaker.set_backend_type(message_data) - smartSpeaker.set_backend_type.assert_any_call(message_data) - - @patch.object(Configuration, 'get') - def test_smartspeaker_start_homescreen_process(self, mock_get): - config = base_config() - config.merge( - { - 'gui': { - 'extension': 'smartspeaker' - } - }) - mock_get.return_value = config - smartSpeaker = SmartSpeakerExtension(MessageBusMock(), MessageBusMock()) - smartSpeaker.start_homescreen_process = mock.Mock() - message_data = Message("ovos.pairing.process.completed", {}) - smartSpeaker.start_homescreen_process(message_data) - smartSpeaker.start_homescreen_process.assert_any_call(message_data) diff --git a/test/unittests/lock/__init__.py b/test/unittests/lock/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/unittests/messagebus/__init__.py b/test/unittests/messagebus/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/unittests/messagebus/client/__init__.py b/test/unittests/messagebus/client/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/unittests/messagebus/client/test_client.py b/test/unittests/messagebus/client/test_client.py deleted file mode 100644 index dad1256820f1..000000000000 --- a/test/unittests/messagebus/client/test_client.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2019 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from unittest import TestCase -from unittest.mock import patch, Mock -from ovos_config import Configuration -from ovos_bus_client.client import MessageBusClient, MessageWaiter - -WS_CONF = { - 'websocket': { - "host": "testhost", - "port": 1337, - "route": "/core", - "ssl": False - } -} - - -class TestMessageBusClient: - def test_build_url(self): - url = MessageBusClient.build_url('localhost', 1337, '/core', False) - assert url == 'ws://localhost:1337/core' - ssl_url = MessageBusClient.build_url('sslhost', 443, '/core', True) - assert ssl_url == 'wss://sslhost:443/core' - - @patch.dict(Configuration._Configuration__patch, WS_CONF) - def test_create_client(self): - mc = MessageBusClient() - assert mc.client.url == 'ws://testhost:1337/core' - - -class TestMessageWaiter(TestCase): - def test_message_wait_success(self): - bus = Mock() - waiter = MessageWaiter(bus, 'delayed.message') - bus.once.assert_called_with('delayed.message', waiter._handler) - - test_msg = Mock(name='test_msg') - waiter._handler(test_msg) # Inject response - - self.assertEqual(waiter.wait(), test_msg) - - def test_message_wait_timeout(self): - bus = Mock() - waiter = MessageWaiter(bus, 'delayed.message') - bus.once.assert_called_with('delayed.message', waiter._handler) - - self.assertEqual(waiter.wait(0.3), None) diff --git a/test/unittests/skills/test_mycroft_skill.py b/test/unittests/skills/test_mycroft_skill.py index 7acefe37d969..ee2e19f70b3a 100644 --- a/test/unittests/skills/test_mycroft_skill.py +++ b/test/unittests/skills/test_mycroft_skill.py @@ -19,12 +19,10 @@ import unittest from datetime import datetime from os.path import join, dirname, abspath -from re import error from unittest.mock import MagicMock, patch from adapt.intent import IntentBuilder -from copy import deepcopy -from mycroft.configuration import Configuration +from ovos_config import Configuration from ovos_bus_client.message import Message from mycroft.skills.core import MycroftSkill, resting_screen_handler, intent_handler from mycroft.skills.intent_service import open_intent_envelope diff --git a/test/unittests/util/__init__.py b/test/unittests/util/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000