diff --git a/.github/workflows/build_debos.yml b/.github/workflows/build_debos.yml index 9f914eef7..d7ecf634b 100644 --- a/.github/workflows/build_debos.yml +++ b/.github/workflows/build_debos.yml @@ -21,13 +21,19 @@ jobs: repository: NeonGeckoCom/neon_debos path: action/neon_debos - name: Ensure LFS files are pulled - run: git lfs pull action/neon_debos + run: | + cd action/neon_debos + git lfs pull + cd ../.. - name: Export keys for image build run: | mkdir -p action/neon_debos/overlays/80-google-json-overlay/home/neon/.local/share/neon echo ${GOOGLE_KEY}>action/neon_debos/overlays/80-google-json-overlay/home/neon/.local/share/neon/google.json env: GOOGLE_KEY: ${{secrets.google_api_key}} - - name: Build and Export Image + - name: Build and Export Mk2 Image + run: | + bash action/neon_debos/run_automation.sh debian-neon-image.yml ${{ github.ref_name }} /var/www/html/app/files/neon_images/pi/mycroft_mark_2 rpi4 mark_2 + - name: Build and Export OPi5 Image run: | - bash action/neon_debos/run_automation.sh debian-neon-image-rpi4.yml ${{ github.ref_name }} /var/www/html/app/files/neon_images/pi/mycroft_mark_2 ${{ github.ref_name }} + bash action/neon_debos/run_automation.sh debian-neon-image.yml ${{ github.ref_name }} /var/www/html/app/files/neon_images/orange_pi_5/ opi5 opi5 \ No newline at end of file diff --git a/.github/workflows/license_tests.yml b/.github/workflows/license_tests.yml index f6281c814..64c6a126e 100644 --- a/.github/workflows/license_tests.yml +++ b/.github/workflows/license_tests.yml @@ -6,3 +6,5 @@ on: jobs: license_tests: uses: neongeckocom/.github/.github/workflows/license_tests.yml@master + with: + packages-exclude: '^(precise-runner|fann2|tqdm|bs4|ovos-phal-plugin|ovos-skill|neon-core|nvidia|neon-phal-plugin|bitstruct|audioread).*' diff --git a/.github/workflows/setup_tests.yml b/.github/workflows/setup_tests.yml index 62d8e68a8..bcae9e53c 100644 --- a/.github/workflows/setup_tests.yml +++ b/.github/workflows/setup_tests.yml @@ -63,6 +63,23 @@ jobs: . /core/venv/bin/activate || exit 2 neon-audio init-plugin -p coqui || exit 2 neon-speech init-plugin -p neon-stt-plugin-nemo || exit 2 + pi_image_3_11: + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - uses: actions/checkout@v2 + - name: Test chroot installation + uses: pguyot/arm-runner-action@v2 + with: + optimize_image: false + base_image: https://2222.us/app/files/neon_images/pi/debian-base-image-rpi4_2023-07-12_16_30.img.xz + cpu: cortex-a53 + copy_repository_path: /core + commands: | + bash /core/test/pi_setup_3_11.sh || exit 2 + . /core/venv/bin/activate || exit 2 + neon-audio init-plugin -p coqui || exit 2 + neon-speech init-plugin -p neon-stt-plugin-nemo || exit 2 legacy-remote: strategy: matrix: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 965567d8e..ae64f4e04 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -15,7 +15,7 @@ jobs: util_tests: strategy: matrix: - python-version: [ 3.7, 3.8, 3.9, '3.10' ] + python-version: [ 3.7, 3.8, 3.9, '3.10', '3.11' ] runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -58,7 +58,7 @@ jobs: unit_tests: strategy: matrix: - python-version: [ 3.7, 3.8, 3.9, '3.10' ] + python-version: [ 3.7, 3.8, 3.9, '3.10', '3.11' ] runs-on: ubuntu-latest timeout-minutes: 15 steps: diff --git a/docker/.env b/docker/.env index 7285c4ae6..5ba2ad6ec 100644 --- a/docker/.env +++ b/docker/.env @@ -1,2 +1,3 @@ -NEON_SKILLS_DIR=./skills/ -NEON_XDG_PATH=./xdg \ No newline at end of file +NEON_SKILLS_DIR=${PWD}/skills/ +NEON_XDG_PATH=${PWD}/xdg +XDG_RUNTIME_DIR=/run/user/1000 \ No newline at end of file diff --git a/docker_overlay/root/run.sh b/docker_overlay/root/run.sh index f6630dd61..6072770b0 100644 --- a/docker_overlay/root/run.sh +++ b/docker_overlay/root/run.sh @@ -28,6 +28,6 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Python package installation must occur in a separate thread, before module load, for the entry point to be loaded. -neon install-default-skills -neon install-skill-requirements /skills +#neon install-default-skills +#neon install-skill-requirements /skills neon run-skills \ No newline at end of file diff --git a/neon_core/__init__.py b/neon_core/__init__.py index dbc769741..e25033df2 100644 --- a/neon_core/__init__.py +++ b/neon_core/__init__.py @@ -26,16 +26,17 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import sys +# import sys from neon_core.config import setup_resolve_resource_file, get_core_version from os.path import dirname +# TODO: Deprecate below NEON_ROOT_PATH = dirname(__file__) -sys.path.append(NEON_ROOT_PATH) +# sys.path.append(NEON_ROOT_PATH) -CORE_VERSION_STR = get_core_version() +CORE_VERSION_STR = "" # get_core_version() setup_resolve_resource_file() __all__ = ['NEON_ROOT_PATH', diff --git a/neon_core/cli.py b/neon_core/cli.py index d71010d14..a1a65336e 100644 --- a/neon_core/cli.py +++ b/neon_core/cli.py @@ -83,16 +83,16 @@ def install_default_skills(): click.echo("Default Skills Installed") -@neon_core_cli.command(help= - "Install skill requirements for a specified directory") -@click.argument("skill_dir") -def install_skill_requirements(skill_dir): - from neon_core.util.skill_utils import install_local_skills - try: - installed = install_local_skills(skill_dir) - click.echo(f"Installed {len(installed)} skills from {skill_dir}") - except ValueError as e: - click.echo(e) +# @neon_core_cli.command(help= +# "Install skill requirements for a specified directory") +# @click.argument("skill_dir") +# def install_skill_requirements(skill_dir): +# from neon_core.util.skill_utils import install_local_skills +# try: +# installed = install_local_skills(skill_dir) +# click.echo(f"Installed {len(installed)} skills from {skill_dir}") +# except ValueError as e: +# click.echo(e) @neon_core_cli.command(help="Start Neon Skills module") @@ -102,14 +102,15 @@ def run_skills(install_skills): from neon_utils.configuration_utils import init_config_dir init_config_dir() - from neon_core.util.skill_utils import install_local_skills + # from neon_core.util.skill_utils import install_local_skills from neon_core.skills.__main__ import main if install_skills: - click.echo(f"Handling installation of skills in: {install_skills}") - try: - install_local_skills(install_skills) - except ValueError as e: - click.echo(f"Skill Installation Failed: {e}") + click.echo(f"Local skill installation is deprecated. " + f"Add pip specs to config.") + # try: + # install_local_skills(install_skills) + # except ValueError as e: + # click.echo(f"Skill Installation Failed: {e}") click.echo("Starting Skills Service") main() click.echo("Skills Service Shutdown") diff --git a/neon_core/config.py b/neon_core/config.py index e37cc2fa5..28e0d1c16 100644 --- a/neon_core/config.py +++ b/neon_core/config.py @@ -32,15 +32,19 @@ def get_core_version() -> str: Get the core version string. NOTE: `init_config` should be called before this method """ - # from neon_core.configuration import Configuration - # Configuration.load_all_configs({"disable_remote_config": True}) - + # TODO: Remove by 2024 + from ovos_utils.log import log_deprecation + log_deprecation("This method is deprecated; use Python built-in utilities " + "to find package versions", "23.12.1") # patch version string to allow downstream to know where it is running - import mycroft.version - core_version_str = '.'.join(map(str, - mycroft.version.CORE_VERSION_TUPLE)) + \ - "(NeonGecko)" - mycroft.version.CORE_VERSION_STR = core_version_str + try: + import mycroft.version + core_version_str = '.'.join(map(str, + mycroft.version.CORE_VERSION_TUPLE)) + \ + "(NeonGecko)" + mycroft.version.CORE_VERSION_STR = core_version_str + except (ImportError, AttributeError): + return "" return core_version_str @@ -49,14 +53,24 @@ def setup_resolve_resource_file(): Override default resolve_resource_file to include resources in neon-core. Priority: neon-utils, neon-core, ~/.local/share/neon, ~/.neon, mycroft-core """ + from ovos_utils.log import log_deprecation from neon_utils.file_utils import resolve_neon_resource_file - from mycroft.util.file_utils import resolve_resource_file + try: + from mycroft.util.file_utils import resolve_resource_file + except (ImportError, AttributeError): + resolve_resource_file = None def patched_resolve_resource_file(res_name): + log_deprecation("This method is deprecated; use " + "`ovos_utils.file_utils.resolve_resource_file.", + "23.12.1") resource = resolve_neon_resource_file(res_name) or \ - resolve_resource_file(res_name) + resolve_resource_file(res_name) return resource - import mycroft.util - mycroft.util.file_utils.resolve_resource_file = \ - patched_resolve_resource_file + try: + import mycroft.util + mycroft.util.file_utils.resolve_resource_file = \ + patched_resolve_resource_file + except (ImportError, AttributeError): + pass diff --git a/neon_core/configuration/mark_2/neon.yaml b/neon_core/configuration/mark_2/neon.yaml index 95b84c721..9f43dcab7 100644 --- a/neon_core/configuration/mark_2/neon.yaml +++ b/neon_core/configuration/mark_2/neon.yaml @@ -89,9 +89,7 @@ skills: # TODO: extra_directories here is the default, can it be removed? extra_directories: - /home/neon/.local/share/neon/skills - default_skills: - # TODO: Defaults are just patching skills not yet pip installable - - https://github.com/JarbasSkills/skill-icanhazdadjokes/tree/dev + default_skills: [] PHAL: ovos-PHAL-plugin-balena-wifi: enabled: false @@ -169,3 +167,6 @@ system: user: - gui_websocket:host - websocket:host +language: + detection_module: None + translation_module: libretranslate_plug \ No newline at end of file diff --git a/neon_core/configuration/opi5/apps/ovos.common_play/settings.json b/neon_core/configuration/opi5/apps/ovos.common_play/settings.json new file mode 100644 index 000000000..77cac38c5 --- /dev/null +++ b/neon_core/configuration/opi5/apps/ovos.common_play/settings.json @@ -0,0 +1,3 @@ +{ + "app_view_timeout_mode": "pause" +} \ No newline at end of file diff --git a/neon_core/configuration/opi5/neon.yaml b/neon_core/configuration/opi5/neon.yaml new file mode 100644 index 000000000..21041ca60 --- /dev/null +++ b/neon_core/configuration/opi5/neon.yaml @@ -0,0 +1,172 @@ +play_wav_cmdline: "play %1" +play_mp3_cmdline: "play %1" +play_ogg_cmdline: "play %1" +g2p: + module: "dummy" +tts: + module: neon-tts-plugin-coqui-remote + fallback_module: coqui + neon-tts-plugin-larynx-server: + host: "https://larynx.2022.us" + mozilla_remote: + api_url: "https://mtts.2022.us/api/tts" +Audio: + backends: + OCP: + type: ovos_common_play + active: true + dbus_type: system +audio_parsers: + blacklist: + - gender +stt: + module: neon-stt-plugin-nemo + fallback_module: ovos-stt-plugin-vosk + ovos-stt-plugin-vosk: + model: /home/neon/.local/share/neon/vosk-model-small-en-us-0.15 +confirm_listening: true +listener: + VAD: + silence_method: vad_only + module: ovos-vad-plugin-silero + mute_during_output: true + instant_listen: true + speech_begin: 0.5 + silence_end: 0.9 + utterance_chunks_to_rewind: 1 +hotwords: + wake_up: + active: false + hey_neon: + model_folder: /home/neon/.local/share/neon/vosk-model-small-en-us-0.15 + hey_mycroft: + module: ovos-ww-plugin-precise-lite + model: https://github.com/OpenVoiceOS/precise-lite-models/raw/master/wakewords/en/hey_mycroft.tflite + listen: True + expected_duration: 3 + trigger_level: 3 + sensitivity: 0.5 + +sounds: + start_listening: snd/start_listening.wav + acknowledge: snd/acknowledge.mp3 +gui_websocket: + host: 0.0.0.0 + base_port: 18181 + route: /gui + ssl: false +websocket: + host: 0.0.0.0 + port: 8181 + route: /core + ssl: false + allow_self_signed: false + ssl_cert: + ssl_key: + shared_connection: true +gui: + idle_display_skill: skill-ovos-homescreen.openvoiceos + extension: smartspeaker + run_gui_file_server: false + generic: + homescreen_supported: true +MQ: + server: mq.2023.us + port: 35672 + users: + mq_handler: + user: neon_api_utils + password: Klatchat2021 +signal: + use_signal_files: false +skills: + blacklisted_skills: + - skill-ovos-setup.openvoiceos + - skill-messaging.neongeckocom + - skill-custom_conversation.neongeckocom + - skill-instructions.neongeckocom + - skill-audio_record.neongeckocom + # TODO: extra_directories here is the default, can it be removed? + extra_directories: + - /home/neon/.local/share/neon/skills + default_skills: [] +PHAL: + ovos-PHAL-plugin-balena-wifi: + enabled: false + debug: false + ssid: Neon + psk: + color: '#ff8600' + portal: start dot neon dot ai + device: Neon Device + ovos-PHAL-plugin-system: + core_service: neon.service + neon-phal-plugin-linear-led: + mute_color: burnt_orange + sleep_color: red + utterance_animation: refill + neon-phal-plugin-fan: + min_fan_temp: 40.0 + admin: + neon-phal-plugin-device-updater: + enabled: true + initramfs_url: "https://github.com/NeonGeckoCom/neon_debos/raw/{}/overlays/02-opi5/boot/uInitrd" + initramfs_path: /opt/neon/firmware/uInitrd + initramfs_update_path: /opt/neon/uInitrd + squashfs_url: "https://2222.us/app/files/neon_images/opi5/updates/{}/" + squashfs_path: /opt/neon/update.squashfs + default_track: dev + neon-phal-plugin-linear-led-neopixel: + enabled: true + neon-phal-plugin-core-updater: + enabled: true + update_command: systemctl start neon-updater + core_module: neon_core + github_ref: NeonGeckoCom/NeonCore + neon-phal-plugin-reset: + enabled: true + default_config_url: "https://github.com/NeonGeckoCom/NeonCore/archive/refs/tags/{}.zip" + default_config_path: neon_core/configuration/opi5 + neon-phal-plugin-audio-receiver: + enabled: true +ready_settings: + - skills + - voice + - audio + - gui_service + - internet + - network_skills + - internet_skills +# - setup +server: + backend_type: offline + +# Logging Config +log_dir: /home/neon/logs/ +log_level: INFO +logs: + path: /home/neon/.local/state/neon/ + max_bytes: 50000000 + backup_count: 3 + diagnostic: False + level_overrides: + error: [] + warning: + - filelock + - botocore + info: [] + debug: [] +debug: False +system_unit: imperial +system: + protected_keys: + remote: + - gui_websocket:host + - websocket:host + - ready_settings + user: + - gui_websocket:host + - websocket:host +language: + detection_module: None + translation_module: libretranslate_plug \ No newline at end of file diff --git a/neon_core/configuration/opi5/skills/skill-demo.neongeckocom/settings.json b/neon_core/configuration/opi5/skills/skill-demo.neongeckocom/settings.json new file mode 100644 index 000000000..973298e3a --- /dev/null +++ b/neon_core/configuration/opi5/skills/skill-demo.neongeckocom/settings.json @@ -0,0 +1,5 @@ +{ + "prompt_on_start": false, + "demo_tts_engine": "ovos-tts-plugin-mimic", + "__mycroft_skill_firstrun": false +} \ No newline at end of file diff --git a/neon_core/configuration/opi5/skills/skill-fallback_unknown.neongeckocom/settings.json b/neon_core/configuration/opi5/skills/skill-fallback_unknown.neongeckocom/settings.json new file mode 100644 index 000000000..b81dc17d3 --- /dev/null +++ b/neon_core/configuration/opi5/skills/skill-fallback_unknown.neongeckocom/settings.json @@ -0,0 +1,5 @@ +{ + "emit_led": true, + "show_utterances": true, + "__mycroft_skill_firstrun": false +} \ No newline at end of file diff --git a/neon_core/configuration/opi5/skills/skill-instructions.neongeckocom/settings.json b/neon_core/configuration/opi5/skills/skill-instructions.neongeckocom/settings.json new file mode 100644 index 000000000..27b7db1e7 --- /dev/null +++ b/neon_core/configuration/opi5/skills/skill-instructions.neongeckocom/settings.json @@ -0,0 +1,4 @@ +{ + "prompt_on_start": false, + "__mycroft_skill_firstrun": false +} diff --git a/neon_core/configuration/opi5/skills/skill-local_music.neongeckocom/settings.json b/neon_core/configuration/opi5/skills/skill-local_music.neongeckocom/settings.json new file mode 100644 index 000000000..f8ee22979 --- /dev/null +++ b/neon_core/configuration/opi5/skills/skill-local_music.neongeckocom/settings.json @@ -0,0 +1,5 @@ +{ + "__mycroft_skill_firstrun": false, + "demo_url": "https://2222.us/app/files/neon_music/music.zip", + "music_dir": "/media/sdb" +} \ No newline at end of file diff --git a/neon_core/configuration/opi5/skills/skill-ovos-homescreen.openvoiceos/settings.json b/neon_core/configuration/opi5/skills/skill-ovos-homescreen.openvoiceos/settings.json new file mode 100644 index 000000000..10567fa9b --- /dev/null +++ b/neon_core/configuration/opi5/skills/skill-ovos-homescreen.openvoiceos/settings.json @@ -0,0 +1,10 @@ +{ + "weather_skill": "skill-weather.neongeckocom", + "datetime_skill": "skill-date_time.neongeckocom", + "examples_skill": "skill-about.neongeckocom", + "wallpaper": "neon_default.jpg", + "persistent_menu_hint": true, + "examples_enabled": true, + "examples_prefix": false, + "__mycroft_skill_firstrun": false +} diff --git a/neon_core/configuration/opi5/skills/skill-ovos-setup.openvoiceos/settings.json b/neon_core/configuration/opi5/skills/skill-ovos-setup.openvoiceos/settings.json new file mode 100644 index 000000000..f055b2194 --- /dev/null +++ b/neon_core/configuration/opi5/skills/skill-ovos-setup.openvoiceos/settings.json @@ -0,0 +1,55 @@ +{ + "enable_language_selection": false, + "enable_backend_selection": false, + "enable_stt_selection": false, + "enable_tts_selection": false, + "preferred_stt_engine": "deepspeech_stream_local", + "preferred_tts_engine": "coqui", + "tts_blacklist": [ + "ovos-tts-plugin-SAM", + "ovos-tts-plugin-beepspeak", + "ovos_tts_plugin_espeakng" + ], + "stt_blacklist": [ + "ovos-stt-plugin-pocketsphinx", + "ovos-stt-plugin-selene" + ], + "langs": [ + { + "name": "English", + "code": "en", + "system_code": "en_US" + }, + { + "name": "Italian", + "code": "it", + "system_code": "it_IT" + }, + { + "name": "French", + "code": "fr", + "system_code": "fr_FR" + }, + { + "name": "Spanish", + "code": "es", + "system_code": "es_ES" + }, + { + "name": "Portuguese", + "code": "pt", + "system_code": "pt_PT" + }, + { + "name": "German", + "code": "de", + "system_code": "de_DE" + }, + { + "name": "Dutch", + "code": "nl", + "system_code": "nl_NL" + } + ], + "__mycroft_skill_firstrun": false +} diff --git a/neon_core/configuration/opi5/skills/skill-user_settings.neongeckocom/settings.json b/neon_core/configuration/opi5/skills/skill-user_settings.neongeckocom/settings.json new file mode 100644 index 000000000..959adc285 --- /dev/null +++ b/neon_core/configuration/opi5/skills/skill-user_settings.neongeckocom/settings.json @@ -0,0 +1,4 @@ +{ + "__mycroft_first_run": false, + "use_geolocation": true +} \ No newline at end of file diff --git a/neon_core/configuration/rpi4/apps/ovos.common_play/settings.json b/neon_core/configuration/rpi4/apps/ovos.common_play/settings.json new file mode 100644 index 000000000..77cac38c5 --- /dev/null +++ b/neon_core/configuration/rpi4/apps/ovos.common_play/settings.json @@ -0,0 +1,3 @@ +{ + "app_view_timeout_mode": "pause" +} \ No newline at end of file diff --git a/neon_core/configuration/rpi4/neon.yaml b/neon_core/configuration/rpi4/neon.yaml new file mode 100644 index 000000000..6cd5bf837 --- /dev/null +++ b/neon_core/configuration/rpi4/neon.yaml @@ -0,0 +1,173 @@ +play_wav_cmdline: "play %1" +play_mp3_cmdline: "play %1" +play_ogg_cmdline: "play %1" +g2p: + module: "dummy" +tts: + module: neon-tts-plugin-coqui-remote + fallback_module: coqui + neon-tts-plugin-larynx-server: + host: "https://larynx.2022.us" + mozilla_remote: + api_url: "https://mtts.2022.us/api/tts" +Audio: + backends: + OCP: + type: ovos_common_play + active: true + dbus_type: system +audio_parsers: + blacklist: + - gender +stt: + module: google_cloud_streaming + fallback_module: ovos-stt-plugin-vosk + ovos-stt-plugin-vosk: + model: /home/neon/.local/share/neon/vosk-model-small-en-us-0.15 +confirm_listening: true +listener: + VAD: + silence_method: vad_only + module: ovos-vad-plugin-silero + mute_during_output: false + instant_listen: true + speech_begin: 0.5 + silence_end: 0.9 + utterance_chunks_to_rewind: 1 +hotwords: + wake_up: + active: false + hey_neon: + model_folder: /home/neon/.local/share/neon/vosk-model-small-en-us-0.15 + hey_mycroft: + module: ovos-ww-plugin-precise-lite + model: https://github.com/OpenVoiceOS/precise-lite-models/raw/master/wakewords/en/hey_mycroft.tflite + listen: True + expected_duration: 3 + trigger_level: 3 + sensitivity: 0.5 + +sounds: + start_listening: snd/start_listening.wav + acknowledge: snd/acknowledge.mp3 +gui_websocket: + host: 0.0.0.0 + base_port: 18181 + route: /gui + ssl: false +websocket: + host: 0.0.0.0 + port: 8181 + route: /core + ssl: false + allow_self_signed: false + ssl_cert: + ssl_key: + shared_connection: true +gui: + idle_display_skill: skill-ovos-homescreen.openvoiceos + extension: smartspeaker + run_gui_file_server: false + generic: + homescreen_supported: true +MQ: + server: mq.2023.us + port: 35672 + users: + mq_handler: + user: neon_api_utils + password: Klatchat2021 +signal: + use_signal_files: false +skills: + blacklisted_skills: + - skill-ovos-setup.openvoiceos + - skill-messaging.neongeckocom + - skill-custom_conversation.neongeckocom + - skill-instructions.neongeckocom + - skill-audio_record.neongeckocom + # TODO: extra_directories here is the default, can it be removed? + extra_directories: + - /home/neon/.local/share/neon/skills + default_skills: [] +PHAL: + ovos-PHAL-plugin-balena-wifi: + enabled: false + debug: false + ssid: Neon + psk: + color: '#ff8600' + portal: start dot neon dot ai + device: Neon Device + ovos-PHAL-plugin-system: + core_service: neon.service + neon-phal-plugin-linear-led: + mute_color: burnt_orange + sleep_color: red + utterance_animation: refill + neon-phal-plugin-fan: + min_fan_temp: 40.0 + admin: + neon-phal-plugin-device-updater: + enabled: true + initramfs_url: "https://github.com/NeonGeckoCom/neon_debos/raw/{}/overlays/02-rpi4/boot/firmware/initramfs" + initramfs_path: /opt/neon/firmware/initramfs + initramfs_update_path: /opt/neon/initramfs + squashfs_url: "https://2222.us/app/files/neon_images/pi/mycroft_mark_2/updates/{}/" + # TODO: Generic squashfs update path? + squashfs_path: /opt/neon/update.squashfs + default_track: dev + neon-phal-plugin-linear-led-neopixel: + enabled: true + neon-phal-plugin-core-updater: + enabled: true + update_command: systemctl start neon-updater + core_module: neon_core + github_ref: NeonGeckoCom/NeonCore + neon-phal-plugin-reset: + enabled: true + default_config_url: "https://github.com/NeonGeckoCom/NeonCore/archive/refs/tags/{}.zip" + default_config_path: neon_core/configuration/opi5 + neon-phal-plugin-audio-receiver: + enabled: true +ready_settings: + - skills + - voice + - audio + - gui_service + - internet + - network_skills + - internet_skills +# - setup +server: + backend_type: offline + +# Logging Config +log_dir: /home/neon/logs/ +log_level: INFO +logs: + path: /home/neon/.local/state/neon/ + max_bytes: 50000000 + backup_count: 3 + diagnostic: False + level_overrides: + error: [] + warning: + - filelock + - botocore + info: [] + debug: [] +debug: False +system_unit: imperial +system: + protected_keys: + remote: + - gui_websocket:host + - websocket:host + - ready_settings + user: + - gui_websocket:host + - websocket:host +language: + detection_module: None + translation_module: libretranslate_plug \ No newline at end of file diff --git a/neon_core/configuration/rpi4/skills/skill-demo.neongeckocom/settings.json b/neon_core/configuration/rpi4/skills/skill-demo.neongeckocom/settings.json new file mode 100644 index 000000000..973298e3a --- /dev/null +++ b/neon_core/configuration/rpi4/skills/skill-demo.neongeckocom/settings.json @@ -0,0 +1,5 @@ +{ + "prompt_on_start": false, + "demo_tts_engine": "ovos-tts-plugin-mimic", + "__mycroft_skill_firstrun": false +} \ No newline at end of file diff --git a/neon_core/configuration/rpi4/skills/skill-fallback_unknown.neongeckocom/settings.json b/neon_core/configuration/rpi4/skills/skill-fallback_unknown.neongeckocom/settings.json new file mode 100644 index 000000000..b81dc17d3 --- /dev/null +++ b/neon_core/configuration/rpi4/skills/skill-fallback_unknown.neongeckocom/settings.json @@ -0,0 +1,5 @@ +{ + "emit_led": true, + "show_utterances": true, + "__mycroft_skill_firstrun": false +} \ No newline at end of file diff --git a/neon_core/configuration/rpi4/skills/skill-instructions.neongeckocom/settings.json b/neon_core/configuration/rpi4/skills/skill-instructions.neongeckocom/settings.json new file mode 100644 index 000000000..27b7db1e7 --- /dev/null +++ b/neon_core/configuration/rpi4/skills/skill-instructions.neongeckocom/settings.json @@ -0,0 +1,4 @@ +{ + "prompt_on_start": false, + "__mycroft_skill_firstrun": false +} diff --git a/neon_core/configuration/rpi4/skills/skill-local_music.neongeckocom/settings.json b/neon_core/configuration/rpi4/skills/skill-local_music.neongeckocom/settings.json new file mode 100644 index 000000000..f8ee22979 --- /dev/null +++ b/neon_core/configuration/rpi4/skills/skill-local_music.neongeckocom/settings.json @@ -0,0 +1,5 @@ +{ + "__mycroft_skill_firstrun": false, + "demo_url": "https://2222.us/app/files/neon_music/music.zip", + "music_dir": "/media/sdb" +} \ No newline at end of file diff --git a/neon_core/configuration/rpi4/skills/skill-ovos-homescreen.openvoiceos/settings.json b/neon_core/configuration/rpi4/skills/skill-ovos-homescreen.openvoiceos/settings.json new file mode 100644 index 000000000..10567fa9b --- /dev/null +++ b/neon_core/configuration/rpi4/skills/skill-ovos-homescreen.openvoiceos/settings.json @@ -0,0 +1,10 @@ +{ + "weather_skill": "skill-weather.neongeckocom", + "datetime_skill": "skill-date_time.neongeckocom", + "examples_skill": "skill-about.neongeckocom", + "wallpaper": "neon_default.jpg", + "persistent_menu_hint": true, + "examples_enabled": true, + "examples_prefix": false, + "__mycroft_skill_firstrun": false +} diff --git a/neon_core/configuration/rpi4/skills/skill-ovos-setup.openvoiceos/settings.json b/neon_core/configuration/rpi4/skills/skill-ovos-setup.openvoiceos/settings.json new file mode 100644 index 000000000..f055b2194 --- /dev/null +++ b/neon_core/configuration/rpi4/skills/skill-ovos-setup.openvoiceos/settings.json @@ -0,0 +1,55 @@ +{ + "enable_language_selection": false, + "enable_backend_selection": false, + "enable_stt_selection": false, + "enable_tts_selection": false, + "preferred_stt_engine": "deepspeech_stream_local", + "preferred_tts_engine": "coqui", + "tts_blacklist": [ + "ovos-tts-plugin-SAM", + "ovos-tts-plugin-beepspeak", + "ovos_tts_plugin_espeakng" + ], + "stt_blacklist": [ + "ovos-stt-plugin-pocketsphinx", + "ovos-stt-plugin-selene" + ], + "langs": [ + { + "name": "English", + "code": "en", + "system_code": "en_US" + }, + { + "name": "Italian", + "code": "it", + "system_code": "it_IT" + }, + { + "name": "French", + "code": "fr", + "system_code": "fr_FR" + }, + { + "name": "Spanish", + "code": "es", + "system_code": "es_ES" + }, + { + "name": "Portuguese", + "code": "pt", + "system_code": "pt_PT" + }, + { + "name": "German", + "code": "de", + "system_code": "de_DE" + }, + { + "name": "Dutch", + "code": "nl", + "system_code": "nl_NL" + } + ], + "__mycroft_skill_firstrun": false +} diff --git a/neon_core/configuration/rpi4/skills/skill-user_settings.neongeckocom/settings.json b/neon_core/configuration/rpi4/skills/skill-user_settings.neongeckocom/settings.json new file mode 100644 index 000000000..959adc285 --- /dev/null +++ b/neon_core/configuration/rpi4/skills/skill-user_settings.neongeckocom/settings.json @@ -0,0 +1,4 @@ +{ + "__mycroft_first_run": false, + "use_geolocation": true +} \ No newline at end of file diff --git a/neon_core/skills/patched_common_query.py b/neon_core/skills/patched_common_query.py index 33dbef342..fd0c00f30 100644 --- a/neon_core/skills/patched_common_query.py +++ b/neon_core/skills/patched_common_query.py @@ -41,8 +41,10 @@ from mycroft.skills.intent_services.base import IntentMatch from mycroft.skills.skill_data import CoreResources + +# TODO: Timeout from config # TODO: Port to ovos-core -EXTENSION_TIME = 10 +EXTENSION_TIME = 15 MIN_RESPONSE_WAIT = 3 diff --git a/neon_core/skills/patched_plugin_loader.py b/neon_core/skills/patched_plugin_loader.py index 7e848a7b6..06637572e 100644 --- a/neon_core/skills/patched_plugin_loader.py +++ b/neon_core/skills/patched_plugin_loader.py @@ -77,8 +77,12 @@ def _create_skill_instance(self, skill_module=None): # skill_id and bus kwargs. # these skills only have skill_id and bus available in initialize, # not in __init__ - if not self.instance._is_fully_initialized: - self.instance._startup(self.bus, self.skill_id) + try: + if not self.instance.is_fully_initialized: + self.instance._startup(self.bus, self.skill_id) + except AttributeError: + if not self.instance._is_fully_initialized: + self.instance._startup(self.bus, self.skill_id) except Exception as e: LOG.exception(f'Skill __init__ failed with {e}') self.instance = None diff --git a/neon_core/skills/skill_manager.py b/neon_core/skills/skill_manager.py index a15af79be..2c820673a 100644 --- a/neon_core/skills/skill_manager.py +++ b/neon_core/skills/skill_manager.py @@ -30,23 +30,12 @@ from os.path import isdir, join, expanduser from ovos_utils.xdg_utils import xdg_data_home from ovos_utils.log import LOG -from neon_core.skills.skill_store import SkillsStore -from neon_utils.net_utils import check_online as connected from mycroft.skills.skill_manager import SkillManager class NeonSkillManager(SkillManager): - def __init__(self, *args, **kwargs): - # self.load_lock = RLock() # Prevent multiple network event handling - super().__init__(*args, **kwargs) - skill_dir = self.get_default_skills_dir() - self.skill_downloader = SkillsStore( - skills_dir=skill_dir, - config=self.config["skills"], bus=self.bus) - self.skill_downloader.skills_dir = skill_dir - def get_default_skills_dir(self): """ Go through legacy config params to locate the default skill directory @@ -72,24 +61,7 @@ def get_default_skills_dir(self): return skill_dir - def download_or_update_defaults(self): - # on launch only install if missing, updates handled separately - # if osm is disabled in .conf this does nothing - if self.config["skills"].get("auto_update"): - try: - self.skill_downloader.install_default_skills() - except Exception as e: - if connected(): - # if there is internet log the error - LOG.exception(e) - LOG.error("default skills installation failed") - else: - # if no internet just skip this update - LOG.error("no internet, skipped default skills installation") - def _load_new_skills(self, *args, **kwargs): - # with self.load_lock: - # LOG.debug(f"Loading skills: {kwargs}") # Override load method for config module checks SkillManager._load_new_skills(self, *args, **kwargs) @@ -105,10 +77,4 @@ def run(self): environ.setdefault('OVOS_CONFIG_BASE_FOLDER', "neon") environ.setdefault('OVOS_CONFIG_FILENAME', "neon.yaml") LOG.debug("set default configuration to `neon/neon.yaml`") - self.download_or_update_defaults() - # from neon_utils.net_utils import check_online - # if check_online(): - # LOG.debug("Already online, allow skills to load") - # self.bus.emit(Message("mycroft.network.connected")) - # self.bus.emit(Message("mycroft.internet.connected")) SkillManager.run(self) diff --git a/neon_core/skills/skill_store.py b/neon_core/skills/skill_store.py deleted file mode 100644 index 935fed7f1..000000000 --- a/neon_core/skills/skill_store.py +++ /dev/null @@ -1,314 +0,0 @@ -# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework -# All trademark and other rights reserved by their respective owners -# Copyright 2008-2022 Neongecko.com Inc. -# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, -# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo -# BSD-3 License -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from os import makedirs -from os.path import isdir -from typing import List, Optional, Generator, Union -from ovos_skills_manager.osm import OVOSSkillsManager -from ovos_skills_manager.skill_entry import SkillEntry -from ovos_utils.log import LOG -from neon_utils.net_utils import check_online -from neon_utils.authentication_utils import repo_is_neon -from datetime import datetime, timedelta -from neon_utils.messagebus_utils import get_messagebus - -from neon_core.util.skill_utils import get_remote_entries -from mycroft.skills.event_scheduler import EventSchedulerInterface -from ovos_config.config import Configuration - - -class SkillsStore: - def __init__(self, skills_dir, config=None, bus=None): - self.config = config or Configuration()["skills"] - self.disabled = self.config.get("disable_osm", False) - self.skills_dir = skills_dir - self.osm = self.load_osm() - self._default_skills = [] - self._alternative_skills = [] - self._essential_skills = [] - self.bus = bus or get_messagebus() - self.scheduler = EventSchedulerInterface(skill_id="osm", - bus=self.bus) - if not self.disabled or not self.config["auto_update"]: - if self.config.get("auto_update_interval"): - self.schedule_update() - if self.config.get("appstore_sync_interval"): - self.schedule_sync() - - def schedule_sync(self): - """ - Use the EventScheduler to update osm with updated appstore data - """ - # every X hours - interval = 60 * 60 * self.config["appstore_sync_interval"] - when = datetime.now() + timedelta(seconds=interval) - self.scheduler.schedule_repeating_event(self.handle_sync_appstores, - when, interval=interval, - name="appstores.sync") - - def schedule_update(self): - """ - Use the EventScheduler to update default skills - """ - # every X hours - interval = 60 * 60 * self.config["auto_update_interval"] - when = datetime.now() + timedelta(seconds=interval) - self.scheduler.schedule_repeating_event(self.handle_update, - when, interval=interval, - name="default_skills.update") - - def handle_update(self, _): - """ - Scheduled action to update installed skills - """ - try: - # TODO: Include non-default installed skills? - self.install_default_skills(update=True) - except Exception as e: - if check_online(): - # if there is internet log the error - LOG.exception(e) - LOG.error("skills update failed") - else: - # if no internet just skip this update - LOG.error("no internet, skipped skills update") - - def handle_sync_appstores(self, _): - """ - Scheduled action to update OSM appstore listings - """ - try: - self.osm.sync_appstores() - except Exception as e: - # TODO: OSM should raise more specific exceptions - if check_online(): - # if there is internet log the error - LOG.exception(e) - LOG.error("appstore sync failed") - else: - # if no internet just skip this update - LOG.error("no internet, skipped appstore sync") - - def shutdown(self): - self.scheduler.shutdown() - - def load_osm(self): - """ - Get an authenticated instance of OSM if not disabled - """ - from ovos_utils.skills.locations import get_default_skills_directory - osm_skill_dir = get_default_skills_directory() - if osm_skill_dir and osm_skill_dir != self.skills_dir: - LOG.warning(f"OSM configured local skills: {osm_skill_dir}") - if not isdir(osm_skill_dir): - makedirs(osm_skill_dir) - if self.disabled: - return None - osm = OVOSSkillsManager() - neon_token = self.config.get("neon_token") - if neon_token: - osm.set_appstore_auth_token("neon", neon_token) - return osm - - @property - def essential_skills(self): - """ - List of absolute minimum skills needed for mycroft to work properly - .conf accepts: - - an url (str) to download essential skill list - - list (list) of names (str) - - list (list) of skill_urls (str) - - NOTES: - - depending on enabled appstores / authentication this might fail - - Neon Skills need authentication - - see alternative_essential_skills - - Installing essential skills can be disabled in .conf - - these should be hardcoded in mycroft.conf and version pinned - """ - if not self._essential_skills and \ - self.config.get("install_essential", True): - self._essential_skills = self._parse_config_entry( - self.config.get("essential_skills", [])) - return self._essential_skills - - @property - def default_skills(self): - """ - Default skills for every install - .conf accepts: - - an url (str) to download essential skill list - - list (list) of names (str) - - list (list) of skill_urls (str) - - NOTES: - - depending on enabled appstores / authentication this might fail - - Neon Skills need authentication - - Installing defaults can be disabled in .conf - """ - - if not self._default_skills and \ - self.config.get("install_default", True): - self._default_skills = self._parse_config_entry( - self.config.get("default_skills", [])) - return self._default_skills - - def authenticate_neon(self): - """ - Enable and authenticate the Neon skills store - """ - self.osm.enable_appstore("neon") - neon = self.osm.get_appstore("neon") - neon_token = self.config.get("neon_token") - if neon_token: - neon.authenticate(neon_token, False) - else: - neon.authenticate(bootstrap=False) - - def deauthenticate_neon(self): - """ - Clear authentication for the Neon skills store - """ - neon = self.osm.get_appstore("neon") - neon.clear_authentication() - - def get_skill_entry(self, skill: str) -> Optional[SkillEntry]: - """ - Build a SkillEntry object from the passed skill URL or ID - :param skill: str skill to search - :returns best match of input skill or None - """ - if "http" in skill: - if "/neongeckocom/" in skill.lower(): - # TODO: This is just patching OSM updates DM - store_skill = None - else: - try: - store_skill = self.osm.search_skills_by_url(skill) - if isinstance(store_skill, SkillEntry): - return store_skill - elif isinstance(store_skill, list): - return store_skill[0] - elif isinstance(store_skill, Generator): - # Return the first item - for s in store_skill: - return s - except Exception as e: - LOG.error(f"OSM Error: {e}") - # skill is not in any appstore - if "/neon" in skill.lower() and "github" in skill: - self.authenticate_neon() - entry = SkillEntry.from_github_url(skill) - self.deauthenticate_neon() - else: - entry = SkillEntry.from_github_url(skill) - return entry - elif "." in skill: - # Return the first item - for skill in self.osm.search_skills_by_id(skill): - return skill - return None - - def get_remote_entries(self, url: str) -> List[str]: - """ - Wraps a call to `neon_core.util.skill_utils.get_remote_entries` to - include authentication. - :param url: URL of skill list to parse (one skill per line) - :returns: list of skills by name, url, and/or ID - """ - authenticated = False - if repo_is_neon(url): - self.authenticate_neon() - authenticated = True - skills_list = get_remote_entries(url) - if authenticated: - self.deauthenticate_neon() - return skills_list - - def _parse_config_entry(self, entry: Union[list, str]) -> List[SkillEntry]: - """ - Parse a config value into a list of SkillEntry objects - :param entry: Configuration value, one of: - - str url of a skill list of skill repo urls, or skill_ids - - list of skill IDs (str) - - list of skill_urls (str) - :returns: list of parsed SkillEntry objects - """ - if self.disabled: - LOG.warning("Ignoring parse request as SkillStore is disabled") - return [] - if isinstance(entry, str): - if not entry.startswith("http"): - raise ValueError(f"passed entry not a valid URL or list: " - f"{entry}") - skills = self.get_remote_entries(entry) - elif isinstance(entry, list): - skills = entry - else: - raise ValueError(f"invalid configuration entry: {entry}") - - skill_entries = list() - for skill in skills: - entry = self.get_skill_entry(skill) - if entry: - skill_entries.append(entry) - - return skill_entries - - def install_skill(self, skill_entry: SkillEntry, - folder: Optional[str] = None, *args, **kwargs) -> bool: - """ - Install a SkillEntry to a local directory. - args/kwargs are passed to `skill_entry.install` - :param skill_entry: SkillEntry to install - :param folder: Skill installation directory (default self.skills_dir) - :returns: True if skill is installed or updated - """ - if self.disabled: - return False - self.authenticate_neon() - kwargs["folder"] = folder or self.skills_dir - updated = skill_entry.install(*args, **kwargs) - self.deauthenticate_neon() - return updated - - def install_default_skills(self, update=False): - skills = [] - if self.disabled: - return skills - for skill in self.essential_skills: - updated = self.install_skill(skill, update=update) - skills.append((skill, updated)) - for skill in self.default_skills: - try: - updated = self.install_skill(skill, update=update) - except Exception as e: - LOG.error(e) - continue # Assume install has failed and skill is not installed - skills.append((skill, updated)) - return skills diff --git a/neon_core/util/skill_utils.py b/neon_core/util/skill_utils.py index 0b9b7efbe..990d3970d 100644 --- a/neon_core/util/skill_utils.py +++ b/neon_core/util/skill_utils.py @@ -35,16 +35,11 @@ from tempfile import mkdtemp from shutil import rmtree from os.path import expanduser, join, isdir, dirname +from typing import List + from ovos_utils.xdg_utils import xdg_data_home -from ovos_skills_manager.skill_entry import SkillEntry -from ovos_skills_manager.osm import OVOSSkillsManager -from ovos_skills_manager.session import set_github_token, clear_github_token -from ovos_skills_manager.github import normalize_github_url, get_branch_from_github_url, download_url_from_github_url -from ovos_skills_manager.utils import get_skills_from_url as get_remote_entries -from ovos_skills_manager.utils import install_local_skill_dependencies as install_local_skills -from ovos_skills_manager.utils import set_osm_constraints_file from ovos_skill_installer import download_extract_zip -from ovos_utils.log import LOG +from ovos_utils.log import LOG, log_deprecation from ovos_config.config import Configuration @@ -59,6 +54,8 @@ def get_neon_skills_data(skill_meta_repository: str = :param branch: branch of repository to checkout :param repo_metadata_path: Path to repo directory containing json metadata files """ + log_deprecation("This skill repository format is deprecated; specify skills as packages") + from ovos_skills_manager.github import normalize_github_url, download_url_from_github_url skills_data = dict() temp_download_dir = mkdtemp() zip_url = download_url_from_github_url(skill_meta_repository, branch) @@ -84,7 +81,7 @@ def _write_pip_constraints_to_file(output_file: str = None): :param output_file: path to constraints file to write """ from neon_utils.packaging_utils import get_package_dependencies - + # TODO: Backwards-compat to be deprecated output_file = output_file or '/etc/mycroft/constraints.txt' if not isdir(dirname(output_file)): makedirs(dirname(output_file)) @@ -112,6 +109,10 @@ def _install_skill_osm(skill_url: str, skill_dir: str, skills_catalog: dict): :param skill_dir: Directory to install skill to :param skills_catalog: dict Neon skill information (url to dict data) """ + from ovos_skills_manager.osm import OVOSSkillsManager + from ovos_skills_manager.skill_entry import SkillEntry + from ovos_skills_manager.github import normalize_github_url, get_branch_from_github_url + log_deprecation("Update all skills to `pip`-installable specs.") osm = OVOSSkillsManager() try: normalized_url = normalize_github_url(skill_url) @@ -171,30 +172,40 @@ def install_skills_from_list(skills_to_install: list, config: dict = None): and config["directory"] != "skills" else join(xdg_data_home(), "neon", "skills")) LOG.info(f"skill_dir={skill_dir}") - skills_catalog = get_neon_skills_data() + skills_catalog = None token_set = False if config.get("neon_token"): + LOG.warning("Authenticated installation from git is deprecated. " + "Please remove `neon_token` from config") + from ovos_skills_manager.session import set_github_token token_set = True set_github_token(config["neon_token"]) LOG.info(f"Added token to request headers: {config.get('neon_token')}") + + constraints_file = '/etc/mycroft/constraints.txt' try: - _write_pip_constraints_to_file() - constraints_file = '/etc/mycroft/constraints.txt' + _write_pip_constraints_to_file(constraints_file) except PermissionError: + LOG.warning(f"Unable to write pip constraints file {constraints_file}") + from ovos_skills_manager.utils import set_osm_constraints_file constraints_file = join(xdg_data_home(), "neon", "constraints.txt") _write_pip_constraints_to_file(constraints_file) set_osm_constraints_file(constraints_file) + for url in skills_to_install: if "://" in url and "git+" not in url: + skills_catalog = skills_catalog or get_neon_skills_data() _install_skill_osm(url, skill_dir, skills_catalog) else: if not _install_skill_pip(url, constraints_file): LOG.warning(f"Pip installation failed for: {url}") + skills_catalog = skills_catalog or get_neon_skills_data() _install_skill_osm(url, skill_dir, skills_catalog) if token_set: + from ovos_skills_manager.session import clear_github_token clear_github_token() - LOG.info(f"Installed skills to: {skill_dir}") + LOG.info(f"Installed {len(skills_to_install)} skills to: {skill_dir}") def install_skills_default(config: dict = None): @@ -204,7 +215,17 @@ def install_skills_default(config: dict = None): config = config or Configuration()["skills"] skills_list = config.get("default_skills") if isinstance(skills_list, str): - skills_list = get_remote_entries(skills_list) + skills_list = _get_skills_from_remote_list(skills_list) assert isinstance(skills_list, list) - install_skills_from_list(skills_list, config) - clear_github_token() + if skills_list: + LOG.info(f"Installing configured skills: {skills_list}") + install_skills_from_list(skills_list, config) + + +def _get_skills_from_remote_list(url: str) -> List[str]: + import requests + resp = requests.get(url) + if not resp.ok: + LOG.error(f"Unable to fetch skills list from: {url} ({resp.status_code})") + return [] + return [s for s in resp.text.split("\n") if s.strip() and not s.startswith('#')] diff --git a/neon_core/version.py b/neon_core/version.py index 8853a6d59..de1c7a506 100644 --- a/neon_core/version.py +++ b/neon_core/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "23.9.7" +__version__ = "23.10.30" diff --git a/requirements/core_modules.txt b/requirements/core_modules.txt index 72f46edd5..16633d52e 100644 --- a/requirements/core_modules.txt +++ b/requirements/core_modules.txt @@ -1,6 +1,6 @@ # neon core modules neon_messagebus~=1.1 neon_enclosure~=1.6 -neon_speech~=4.1 +neon_speech~=4.2 neon_gui~=1.2,>=1.2.2 -neon_audio~=1.3,>=1.3.2 +neon_audio~=1.4 diff --git a/requirements/dev.txt b/requirements/dev.txt index 7f5b2da1a..ba711fbe7 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,2 +1,3 @@ neon-cli-client~=0.2 -neon-mana-utils~=0.2,>=0.2.1 \ No newline at end of file +neon-mana-utils~=0.2,>=0.2.1 +neon-minerva~=0.0.1 \ No newline at end of file diff --git a/requirements/local_speech_processing.txt b/requirements/local_speech_processing.txt index 4f8eea027..60404edfa 100644 --- a/requirements/local_speech_processing.txt +++ b/requirements/local_speech_processing.txt @@ -1,7 +1,7 @@ neon-stt-plugin-deepspeech-stream-local~=2.0 neon-stt-plugin-nemo~=0.0 -neon-tts-plugin-coqui~=0.7,>=0.7.1 - +neon-tts-plugin-coqui~=0.7,>=0.7.3a4 +onnxruntime!=1.16.0 # TODO: Patching https://github.com/microsoft/onnxruntime/issues/17631 # TODO: Local language plugin # Fallback diff --git a/requirements/pi.txt b/requirements/pi.txt index 46a072a38..8f1e34685 100644 --- a/requirements/pi.txt +++ b/requirements/pi.txt @@ -12,6 +12,7 @@ neon-mana-utils~=0.2,>=0.2.2 # Default plugins ovos-vad-plugin-silero~=0.0.1 +onnxruntime!=1.16.0 # TODO: Patching https://github.com/microsoft/onnxruntime/issues/17631 ovos-ww-plugin-precise~=0.1 ovos-ww-plugin-precise-lite[tflite]~=0.1,>=0.1.2 ovos-ww-plugin-vosk~=0.1,>=0.1.1 @@ -24,7 +25,7 @@ neon-stt-plugin-nemo~=0.0.4 ovos-tts-plugin-server==0.0.2a4 # Fallback plugins -neon-tts-plugin-coqui~=0.7,>=0.7.1 +neon-tts-plugin-coqui~=0.7,>=0.7.3a4 ovos-stt-plugin-vosk~=0.1,>=0.1.4 # PHAL Plugins @@ -57,3 +58,9 @@ ovos-skill-homescreen==0.0.3a6 ovos-skill-setup~=0.0.1 ovos-skill-volume~=0.0.1 skill-markII-audio-receiver>=0.0.4 + +# Pinned for stable release (pulled by skill-ddg) +ovos-classifiers==0.0.0a37 + +# Backwards-compat +ovos-skills-manager~=0.0.13 # TODO: Remove in 2024 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index f8e5ddd4a..cb7d21f8b 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,20 +1,23 @@ -# mycroft -ovos-core~=0.0.7 -ovos-workshop~=0.0.12 +# ovos-core version pinned for compat. with patches in NeonCore +ovos-core==0.0.7 # padacioso==0.1.3a2 ovos-plugin-common-play~=0.0.5 # utils -neon-utils[network]~=1.6,>=1.6.2 +neon-utils[network]~=1.7 ovos-utils~=0.0.35 -ovos-bus-client~=0.0.5 +ovos-bus-client==0.0.5 +# TODO: Above pinned to reduce logs from ovos-core 0.0.7 with ovos-bus-client 0.0.6 neon-transformers~=0.2 -ovos-config==0.0.11a9 # TODO: Pinned for stable release -ovos-skills-manager==0.0.13a4 # TODO: Pinned for stable release -ovos-plugin-manager~=0.0.21 +ovos-config~=0.0.11 +ovos-plugin-manager~=0.0.24 +# Testing latest OPM ovos-backend-client~=0.0.6 psutil~=5.6 +click~=8.0 +click-default-group~=1.2 + # Used for patching skill settings mock~=5.0 @@ -29,7 +32,11 @@ requests < 2.30.0 # TODO: Patching dependencies ovos-core[skills,skills_lgpl] extra deps adapt-parser~=0.5 padacioso~=0.1 -ovos-lingua-franca==0.4.8a6 # TODO: Pinned for stable release +# TODO: pinned for stable release +ovos-lingua-franca==0.4.8a6 ovos-phal-plugin-connectivity-events~=0.0.1 padatious~=0.4.8 -fann2==1.0.7 \ No newline at end of file +fann2==1.0.7 + +# TODO: Patching ovos-workshop alpha dependency resolution +ovos-workshop==0.0.12 \ No newline at end of file diff --git a/requirements/skills_default.txt b/requirements/skills_default.txt index 646897e34..a52581eaa 100644 --- a/requirements/skills_default.txt +++ b/requirements/skills_default.txt @@ -1,4 +1,5 @@ -skill-ddg~=0.0 +skill-ddg==0.0.2a3 +# TODO: Above pinned for stable release neon-skill-alerts~=2.0 neon-skill-caffeinewiz~=1.0 neon-skill-data_controls~=2.0 @@ -15,4 +16,4 @@ neon-skill-wikipedia~=1.0,>=1.0.1 neon-skill-free_music_archive~=1.0 neon-skill-local_music~=2.0 # neon-skill-holidays~=0.0.0a1 -neon-skill-fallback_llm~=1.0 +neon-skill-fallback_llm~=1.0,>=1.0.1 diff --git a/requirements/skills_essential.txt b/requirements/skills_essential.txt index bc179882b..d0157c13e 100644 --- a/requirements/skills_essential.txt +++ b/requirements/skills_essential.txt @@ -1,5 +1,7 @@ neon-skill-about~=1.0 neon-skill-date_time~=1.0 +# neon-skill-date_time~=1.0,>=1.0.1a2 neon-skill-demo~=1.0 +# neon-skill-demo~=1.0,>=1.0.1a1 neon-skill-device_controls~=1.0 neon-skill-ip_address~=1.0 diff --git a/requirements/skills_extended.txt b/requirements/skills_extended.txt index c8db3f78e..bcf2e080c 100644 --- a/requirements/skills_extended.txt +++ b/requirements/skills_extended.txt @@ -9,4 +9,4 @@ neon-skill-translation~=1.0 neon-skill-camera~=1.0 ovos-skill-somafm~=0.0.1 neon-homeassistant-skill~=0.0.13 -ovos-skill-jokes @ git+https://github.com/jarbasskills/skill-icanhazdadjokes \ No newline at end of file +ovos-skill-jokes @ git+https://github.com/openvoiceos/skill-ovos-icanhazdadjokes@ad20ee400c7195cfdb43596f32b1c0a4293ce941 \ No newline at end of file diff --git a/requirements/test.txt b/requirements/test.txt index 5f070af6b..8a682c42e 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,3 +1,4 @@ pytest pytest-cov -mock~=5.0 \ No newline at end of file +mock~=5.0 +ovos-skills-manager~=0.0.13 \ No newline at end of file diff --git a/test/pi_setup_3_11.sh b/test/pi_setup_3_11.sh new file mode 100644 index 000000000..2ed1bcc05 --- /dev/null +++ b/test/pi_setup_3_11.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Override DNS resolver +rm /etc/resolv.conf +echo "nameserver 1.1.1.1" | tee /etc/resolv.conf + +# install system packages +apt update +apt install -y curl +curl https://forslund.github.io/mycroft-desktop-repo/mycroft-desktop.gpg.key | apt-key add - 2> /dev/null && \ +echo "deb http://forslund.github.io/mycroft-desktop-repo bionic main" | tee /etc/apt/sources.list.d/mycroft-desktop.list +apt update +apt install -y sox gcc libfann-dev swig libssl-dev portaudio19-dev git libpulse-dev mimic espeak-ng g++ libjpeg-dev make python3.11-dev python3.11-venv || exit 1 + +cd /core || exit 10 +python3.11 -m venv "/core/venv" || exit 11 +. /core/venv/bin/activate + +pip install --upgrade pip wheel +pip install https://whl.smartgic.io/tflite_runtime-2.13.0-cp311-cp311-linux_aarch64.whl +pip install ".[core_modules,skills_required,skills_essential,skills_default,skills_extended,pi]" --extra-index-url "https://whl.smartgic.io/" || exit 11 + +cp -rf /core/test/pi_image_overlay/* / \ No newline at end of file diff --git a/test/test_skill_utils.py b/test/test_skill_utils.py index 68a613993..443e7b58c 100644 --- a/test/test_skill_utils.py +++ b/test/test_skill_utils.py @@ -26,14 +26,11 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import importlib -import json import os import shutil import sys import unittest -from mock.mock import Mock sys.path.append(os.path.dirname(os.path.dirname(__file__))) @@ -72,13 +69,13 @@ def tearDown(self) -> None: if os.path.exists(SKILL_DIR): shutil.rmtree(SKILL_DIR) - def test_get_remote_entries(self): - from neon_core.util.skill_utils import get_remote_entries - from ovos_skills_manager.session import set_github_token,\ - clear_github_token - set_github_token(SKILL_CONFIG["neon_token"]) - skills_list = get_remote_entries(SKILL_CONFIG["default_skills"]) - clear_github_token() + def test_get_skills_from_remote_list(self): + from neon_core.util.skill_utils import _get_skills_from_remote_list + # from ovos_skills_manager.session import set_github_token,\ + # clear_github_token + # set_github_token(SKILL_CONFIG["neon_token"]) + skills_list = _get_skills_from_remote_list(SKILL_CONFIG["default_skills"]) + # clear_github_token() self.assertIsInstance(skills_list, list) self.assertTrue(len(skills_list) > 0) self.assertTrue(all(skill.startswith("https://github.com") @@ -102,15 +99,15 @@ def test_install_skills_from_list_with_auth(self): def test_install_skills_default(self): from neon_core.util.skill_utils import install_skills_default,\ - get_remote_entries + _get_skills_from_remote_list install_skills_default(SKILL_CONFIG) skill_dirs = [d for d in os.listdir(SKILL_DIR) if os.path.isdir(os.path.join(SKILL_DIR, d))] self.assertEqual( len(skill_dirs), - len(get_remote_entries(SKILL_CONFIG["default_skills"])), + len(_get_skills_from_remote_list(SKILL_CONFIG["default_skills"])), f"{skill_dirs}\n\n" - f"{get_remote_entries(SKILL_CONFIG['default_skills'])}") + f"{_get_skills_from_remote_list(SKILL_CONFIG['default_skills'])}") def test_install_skills_with_pip(self): from neon_core.util.skill_utils import install_skills_from_list @@ -133,25 +130,25 @@ def test_get_neon_skills_data(self): self.assertEqual(skill, normalize_github_url(neon_skills[skill]["url"])) - def test_install_local_skills(self): - import ovos_skills_manager.requirements - import neon_core.util.skill_utils - importlib.reload(neon_core.util.skill_utils) - install_pip_deps = Mock() - install_sys_deps = Mock() - ovos_skills_manager.requirements.pip_install = install_pip_deps - ovos_skills_manager.requirements.install_system_deps = install_sys_deps - - install_local_skills = neon_core.util.skill_utils.install_local_skills - - local_skills_dir = os.path.join(os.path.dirname(__file__), - "local_skills") - - installed = install_local_skills(local_skills_dir) - num_installed = len(installed) - self.assertEqual(installed, os.listdir(local_skills_dir)) - self.assertEqual(num_installed, install_pip_deps.call_count) - self.assertEqual(num_installed, install_sys_deps.call_count) + # def test_install_local_skills(self): + # import ovos_skills_manager.requirements + # import neon_core.util.skill_utils + # importlib.reload(neon_core.util.skill_utils) + # install_pip_deps = Mock() + # install_sys_deps = Mock() + # ovos_skills_manager.requirements.pip_install = install_pip_deps + # ovos_skills_manager.requirements.install_system_deps = install_sys_deps + # + # install_local_skills = neon_core.util.skill_utils.install_local_skills + # + # local_skills_dir = os.path.join(os.path.dirname(__file__), + # "local_skills") + # + # installed = install_local_skills(local_skills_dir) + # num_installed = len(installed) + # self.assertEqual(installed, os.listdir(local_skills_dir)) + # self.assertEqual(num_installed, install_pip_deps.call_count) + # self.assertEqual(num_installed, install_sys_deps.call_count) def test_write_pip_constraints_to_file(self): from neon_core.util.skill_utils import _write_pip_constraints_to_file @@ -176,12 +173,12 @@ def test_write_pip_constraints_to_file(self): self.assertIsInstance(e, PermissionError) os.remove(test_outfile) - def test_set_osm_constraints_file(self): - import ovos_skills_manager.requirements - from neon_core.util.skill_utils import set_osm_constraints_file - set_osm_constraints_file(__file__) - self.assertEqual(ovos_skills_manager.requirements.DEFAULT_CONSTRAINTS, - __file__) + # def test_set_osm_constraints_file(self): + # import ovos_skills_manager.requirements + # from neon_core.util.skill_utils import set_osm_constraints_file + # set_osm_constraints_file(__file__) + # self.assertEqual(ovos_skills_manager.requirements.DEFAULT_CONSTRAINTS, + # __file__) def test_skill_class_patches(self): import neon_core.skills # Import to do all the patching @@ -224,7 +221,7 @@ def test_skill_class_patches(self): from neon_utils.skills import NeonFallbackSkill, NeonSkill self.assertTrue(issubclass(NeonFallbackSkill, PatchedMycroftSkill)) - self.assertTrue(issubclass(NeonSkill, PatchedMycroftSkill)) + # self.assertTrue(issubclass(NeonSkill, PatchedMycroftSkill)) self.assertTrue(issubclass(NeonFallbackSkill, OVOSSkill)) self.assertTrue(issubclass(NeonFallbackSkill, NeonSkill)) @@ -245,5 +242,6 @@ def test_skill_class_patches(self): # Class added in ovos-workwhop 0.0.12 pass + if __name__ == '__main__': unittest.main() diff --git a/test/test_skills_module.py b/test/test_skills_module.py index 3a268bc54..c61279528 100644 --- a/test/test_skills_module.py +++ b/test/test_skills_module.py @@ -80,9 +80,9 @@ def tearDownClass(cls) -> None: if os.path.exists(cls.config_dir): shutil.rmtree(cls.config_dir) - @patch("neon_core.skills.skill_store.SkillsStore.install_default_skills") + # @patch("neon_core.skills.skill_store.SkillsStore.install_default_skills") @patch("mycroft.skills.skill_manager.SkillManager.run") - def test_neon_skills_service(self, run, install_default): + def test_neon_skills_service(self, run): from neon_core.skills.service import NeonSkillService from neon_core.skills.skill_manager import NeonSkillManager # from mycroft.util.process_utils import ProcessState @@ -116,7 +116,7 @@ def ready_hook(): service.start() started.wait(30) self.assertTrue(service.config['skills']['auto_update']) - install_default.assert_called_once() + # install_default.assert_called_once() # Check mock method called run.assert_called_once() @@ -344,28 +344,28 @@ def tearDownClass(cls) -> None: if os.path.isdir(cls.config_dir): shutil.rmtree(cls.config_dir) - @patch("neon_core.skills.skill_store.SkillsStore.install_default_skills") + # @patch("neon_core.skills.skill_store.SkillsStore.install_default_skills") + # @patch("mycroft.skills.skill_manager.SkillManager.run") + # def test_download_or_update_defaults(self, patched_run, patched_installer): + # from neon_core.configuration import patch_config + # patch_config({"skills": {"auto_update": True}}) + # + # from neon_core.skills.skill_manager import NeonSkillManager + # manager = NeonSkillManager(FakeBus()) + # self.assertTrue(manager.config["skills"]["auto_update"]) + # manager.run() + # patched_run.assert_called_once() + # patched_installer.assert_called_once() + # + # patched_installer.reset_mock() + # manager.config.update({"skills": {"auto_update": False}}) + # manager.download_or_update_defaults() + # patched_installer.assert_not_called() + # manager.stop() + + # @patch("neon_core.skills.skill_store.SkillsStore.install_default_skills") @patch("mycroft.skills.skill_manager.SkillManager.run") - def test_download_or_update_defaults(self, patched_run, patched_installer): - from neon_core.configuration import patch_config - patch_config({"skills": {"auto_update": True}}) - - from neon_core.skills.skill_manager import NeonSkillManager - manager = NeonSkillManager(FakeBus()) - self.assertTrue(manager.config["skills"]["auto_update"]) - manager.run() - patched_run.assert_called_once() - patched_installer.assert_called_once() - - patched_installer.reset_mock() - manager.config.update({"skills": {"auto_update": False}}) - manager.download_or_update_defaults() - patched_installer.assert_not_called() - manager.stop() - - @patch("neon_core.skills.skill_store.SkillsStore.install_default_skills") - @patch("mycroft.skills.skill_manager.SkillManager.run") - def test_get_default_skills_dir(self, _, __): + def test_get_default_skills_dir(self, _): from neon_core.skills.skill_manager import NeonSkillManager manager = NeonSkillManager(FakeBus()) manager.config = dict(manager.config) # Override Configuration to test @@ -403,188 +403,188 @@ def test_get_default_skills_dir(self, _, __): self.assertTrue(isdir(expanduser("~/neon-skills"))) -class TestSkillStore(unittest.TestCase): - essential = ["https://github.com/OpenVoiceOS/skill-ovos-homescreen/tree/main"] - config = { - "disable_osm": False, - "auto_update": True, - "auto_update_interval": 1, - "appstore_sync_interval": 1, - "neon_token": None, - "essential_skills": essential, - "install_default": True, - "install_essential": True, - "default_skills": "https://raw.githubusercontent.com/NeonGeckoCom/" - "neon_skills/TEST_ShortSkillsList/skill_lists/" - "TEST-SHORTLIST" - } - skill_dir = join(dirname(__file__), "skill_module_skills") - bus = FakeBus() - - @classmethod - def setUpClass(cls) -> None: - import mycroft.skills.event_scheduler - mocked_scheduler = MockEventSchedulerInterface - mycroft.skills.event_scheduler.EventSchedulerInterface = \ - mocked_scheduler - import neon_core.skills.skill_store - importlib.reload(neon_core.skills.skill_store) - - from neon_core.skills.skill_store import SkillsStore - cls.skill_store = SkillsStore(cls.skill_dir, cls.config, cls.bus) - - @classmethod - def tearDownClass(cls) -> None: - cls.skill_store.shutdown() - - def test_00_store_init(self): - self.assertEqual(self.skill_store.config, self.config) - self.assertFalse(self.skill_store.disabled) - self.assertEqual(self.skill_store.skills_dir, self.skill_dir) - self.assertEqual(self.skill_store.bus, self.bus) - self.assertIsNotNone(self.skill_store.osm) - self.assertIsInstance(self.skill_store.scheduler, - MockEventSchedulerInterface) - self.assertEqual( - self.skill_store.scheduler.schedule_repeating_event.call_count, 2) - - def test_schedule_sync(self): - pass - - def test_schedule_update(self): - pass - - def test_handle_update(self): - pass - - def test_handle_sync_appstores(self): - pass - - def test_handle_load_osm(self): - from ovos_skills_manager import OVOSSkillsManager - self.skill_store.disabled = True - self.assertIsNone(self.skill_store.load_osm()) - - self.skill_store.disabled = False - self.assertIsInstance(self.skill_store.load_osm(), OVOSSkillsManager) - - def test_essential_skills(self): - self.assertFalse(self.skill_store.disabled) - self.assertEqual(len(self.skill_store.essential_skills), - len(self.essential)) - - def test_default_skills(self): - self.assertFalse(self.skill_store.disabled) - self.assertIsInstance(self.skill_store.default_skills, list) - self.assertGreater(len(self.skill_store.default_skills), 0) - - def test_authenticate_neon(self): - pass - - def test_deauthenticate_neon(self): - pass - - def test_get_skill_entry(self): - # TODO: Implement skills by ID after fixing in OSM - # TODO: Support missing branch specs - from ovos_skills_manager import SkillEntry - url = "https://github.com/OpenVoiceOS/skill-ovos-homescreen/tree/main" - # skill_id = "skill-ovos-homescreen.openvoiceos" - url_entry = self.skill_store.get_skill_entry(url) - self.assertIsInstance(url_entry, SkillEntry) - # id_entry = self.skill_store.get_skill_entry(skill_id) - # self.assertIsInstance(id_entry, SkillEntry) - # self.assertEqual(url_entry.skill_name, id_entry.skill_name) - - def test_get_remote_entries(self): - from neon_core.util.skill_utils import get_remote_entries - test_urls = { - "https://raw.githubusercontent.com/NeonGeckoCom/neon_skills/master/skill_lists/DEFAULT-SKILLS", - "https://raw.githubusercontent.com/NeonGeckoCom/neon_skills/master/skill_lists/DEFAULT-PREMIUM-SKILLS" - } - for url in test_urls: - self.assertEqual(self.skill_store.get_remote_entries(url), - get_remote_entries(url)) - - def test_parse_config_entry(self): - # TODO: Implement skills by ID after fixing in OSM - from ovos_skills_manager import SkillEntry - self.skill_store.osm.disable_appstore("local") - - valid_entry_url = self.config["default_skills"] - valid_entry_list_url = self.config["essential_skills"] - # valid_entry_list_id = ["skill-ovos-homescreen.openvoiceos", - # "caffeinewiz.neon.neongeckocom"] - - self.skill_store.disabled = True - self.assertEqual(self.skill_store._parse_config_entry(valid_entry_url), - list()) - self.skill_store.disabled = False - - # with self.assertRaises(ValueError): - # self.skill_store._parse_config_entry(valid_entry_list_id[0]) - - with self.assertRaises(ValueError): - self.skill_store._parse_config_entry(None) - - default_entries = self.skill_store._parse_config_entry(valid_entry_url) - self.assertIsInstance(default_entries, list) - self.assertTrue(all([isinstance(x, SkillEntry) - for x in default_entries]), default_entries) - - essential_entries = \ - self.skill_store._parse_config_entry(valid_entry_list_url) - self.assertIsInstance(essential_entries, list) - self.assertEqual(len(essential_entries), 1, essential_entries) - self.assertIsInstance(essential_entries[0], SkillEntry) - - # list_entries = \ - # self.skill_store._parse_config_entry(valid_entry_list_id) - # self.assertIsInstance(list_entries, list) - # self.assertEqual(len(list_entries), 2, list_entries) - # self.assertTrue(all([isinstance(x, SkillEntry) - # for x in list_entries]), list_entries) - - def test_install_skill(self): - skill_entry = Mock() - install_dir = self.skill_dir - - def skill_entry_installer(*_, **kwargs): - self.assertEqual(kwargs["folder"], install_dir) - if kwargs.get("update"): - return True - return False - - self.skill_store.disabled = True - self.assertFalse(self.skill_store.install_skill(skill_entry)) - self.skill_store.disabled = False - - skill_entry.install = skill_entry_installer - self.assertFalse(self.skill_store.install_skill(skill_entry)) - - install_dir = "/tmp" - self.assertTrue(self.skill_store.install_skill(skill_entry, "/tmp", - update=True)) - - def test_install_default_skills(self): - install_skill = Mock() - real_install_skill = self.skill_store.install_skill - self.skill_store.install_skill = install_skill - - self.skill_store.disabled = True - self.assertEqual(self.skill_store.install_default_skills(), list()) - self.assertEqual(self.skill_store.install_default_skills(True), list()) - self.skill_store.disabled = False - - install_skill.reset_mock() - skills = self.skill_store.install_default_skills(False) - self.assertEqual(install_skill.call_count, len(skills)) - - install_skill.reset_mock() - skills = self.skill_store.install_default_skills(True) - self.assertEqual(install_skill.call_count, len(skills)) - - self.skill_store.install_skill = real_install_skill +# class TestSkillStore(unittest.TestCase): +# essential = ["https://github.com/OpenVoiceOS/skill-ovos-homescreen/tree/main"] +# config = { +# "disable_osm": False, +# "auto_update": True, +# "auto_update_interval": 1, +# "appstore_sync_interval": 1, +# "neon_token": None, +# "essential_skills": essential, +# "install_default": True, +# "install_essential": True, +# "default_skills": "https://raw.githubusercontent.com/NeonGeckoCom/" +# "neon_skills/TEST_ShortSkillsList/skill_lists/" +# "TEST-SHORTLIST" +# } +# skill_dir = join(dirname(__file__), "skill_module_skills") +# bus = FakeBus() +# +# @classmethod +# def setUpClass(cls) -> None: +# import mycroft.skills.event_scheduler +# mocked_scheduler = MockEventSchedulerInterface +# mycroft.skills.event_scheduler.EventSchedulerInterface = \ +# mocked_scheduler +# import neon_core.skills.skill_store +# importlib.reload(neon_core.skills.skill_store) +# +# from neon_core.skills.skill_store import SkillsStore +# cls.skill_store = SkillsStore(cls.skill_dir, cls.config, cls.bus) +# +# @classmethod +# def tearDownClass(cls) -> None: +# cls.skill_store.shutdown() +# +# def test_00_store_init(self): +# self.assertEqual(self.skill_store.config, self.config) +# self.assertFalse(self.skill_store.disabled) +# self.assertEqual(self.skill_store.skills_dir, self.skill_dir) +# self.assertEqual(self.skill_store.bus, self.bus) +# self.assertIsNotNone(self.skill_store.osm) +# self.assertIsInstance(self.skill_store.scheduler, +# MockEventSchedulerInterface) +# self.assertEqual( +# self.skill_store.scheduler.schedule_repeating_event.call_count, 2) +# +# def test_schedule_sync(self): +# pass +# +# def test_schedule_update(self): +# pass +# +# def test_handle_update(self): +# pass +# +# def test_handle_sync_appstores(self): +# pass +# +# def test_handle_load_osm(self): +# from ovos_skills_manager import OVOSSkillsManager +# self.skill_store.disabled = True +# self.assertIsNone(self.skill_store.load_osm()) +# +# self.skill_store.disabled = False +# self.assertIsInstance(self.skill_store.load_osm(), OVOSSkillsManager) +# +# def test_essential_skills(self): +# self.assertFalse(self.skill_store.disabled) +# self.assertEqual(len(self.skill_store.essential_skills), +# len(self.essential)) +# +# def test_default_skills(self): +# self.assertFalse(self.skill_store.disabled) +# self.assertIsInstance(self.skill_store.default_skills, list) +# self.assertGreater(len(self.skill_store.default_skills), 0) +# +# def test_authenticate_neon(self): +# pass +# +# def test_deauthenticate_neon(self): +# pass +# +# def test_get_skill_entry(self): +# # TODO: Implement skills by ID after fixing in OSM +# # TODO: Support missing branch specs +# from ovos_skills_manager import SkillEntry +# url = "https://github.com/OpenVoiceOS/skill-ovos-homescreen/tree/main" +# # skill_id = "skill-ovos-homescreen.openvoiceos" +# url_entry = self.skill_store.get_skill_entry(url) +# self.assertIsInstance(url_entry, SkillEntry) +# # id_entry = self.skill_store.get_skill_entry(skill_id) +# # self.assertIsInstance(id_entry, SkillEntry) +# # self.assertEqual(url_entry.skill_name, id_entry.skill_name) +# +# def test_get_remote_entries(self): +# from neon_core.util.skill_utils import get_remote_entries +# test_urls = { +# "https://raw.githubusercontent.com/NeonGeckoCom/neon_skills/master/skill_lists/DEFAULT-SKILLS", +# "https://raw.githubusercontent.com/NeonGeckoCom/neon_skills/master/skill_lists/DEFAULT-PREMIUM-SKILLS" +# } +# for url in test_urls: +# self.assertEqual(self.skill_store.get_remote_entries(url), +# get_remote_entries(url)) +# +# def test_parse_config_entry(self): +# # TODO: Implement skills by ID after fixing in OSM +# from ovos_skills_manager import SkillEntry +# self.skill_store.osm.disable_appstore("local") +# +# valid_entry_url = self.config["default_skills"] +# valid_entry_list_url = self.config["essential_skills"] +# # valid_entry_list_id = ["skill-ovos-homescreen.openvoiceos", +# # "caffeinewiz.neon.neongeckocom"] +# +# self.skill_store.disabled = True +# self.assertEqual(self.skill_store._parse_config_entry(valid_entry_url), +# list()) +# self.skill_store.disabled = False +# +# # with self.assertRaises(ValueError): +# # self.skill_store._parse_config_entry(valid_entry_list_id[0]) +# +# with self.assertRaises(ValueError): +# self.skill_store._parse_config_entry(None) +# +# default_entries = self.skill_store._parse_config_entry(valid_entry_url) +# self.assertIsInstance(default_entries, list) +# self.assertTrue(all([isinstance(x, SkillEntry) +# for x in default_entries]), default_entries) +# +# essential_entries = \ +# self.skill_store._parse_config_entry(valid_entry_list_url) +# self.assertIsInstance(essential_entries, list) +# self.assertEqual(len(essential_entries), 1, essential_entries) +# self.assertIsInstance(essential_entries[0], SkillEntry) +# +# # list_entries = \ +# # self.skill_store._parse_config_entry(valid_entry_list_id) +# # self.assertIsInstance(list_entries, list) +# # self.assertEqual(len(list_entries), 2, list_entries) +# # self.assertTrue(all([isinstance(x, SkillEntry) +# # for x in list_entries]), list_entries) +# +# def test_install_skill(self): +# skill_entry = Mock() +# install_dir = self.skill_dir +# +# def skill_entry_installer(*_, **kwargs): +# self.assertEqual(kwargs["folder"], install_dir) +# if kwargs.get("update"): +# return True +# return False +# +# self.skill_store.disabled = True +# self.assertFalse(self.skill_store.install_skill(skill_entry)) +# self.skill_store.disabled = False +# +# skill_entry.install = skill_entry_installer +# self.assertFalse(self.skill_store.install_skill(skill_entry)) +# +# install_dir = "/tmp" +# self.assertTrue(self.skill_store.install_skill(skill_entry, "/tmp", +# update=True)) +# +# def test_install_default_skills(self): +# install_skill = Mock() +# real_install_skill = self.skill_store.install_skill +# self.skill_store.install_skill = install_skill +# +# self.skill_store.disabled = True +# self.assertEqual(self.skill_store.install_default_skills(), list()) +# self.assertEqual(self.skill_store.install_default_skills(True), list()) +# self.skill_store.disabled = False +# +# install_skill.reset_mock() +# skills = self.skill_store.install_default_skills(False) +# self.assertEqual(install_skill.call_count, len(skills)) +# +# install_skill.reset_mock() +# skills = self.skill_store.install_default_skills(True) +# self.assertEqual(install_skill.call_count, len(skills)) +# +# self.skill_store.install_skill = real_install_skill if __name__ == "__main__":