Skip to content
This repository has been archived by the owner on Nov 20, 2024. It is now read-only.

Initial Conversion to "Lite" Homescreen #1

Merged
merged 18 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
# NOTE: additional pytest invocations should also add the --cov-append flag
# or they will overwrite previous invocations' coverage reports
# (for an example, see OVOS Skill Manager's workflow)
- name: Upload coverage
env:
CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
uses: codecov/codecov-action@v2
# - name: Upload coverage
# env:
# CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
# uses: codecov/codecov-action@v2
110 changes: 50 additions & 60 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import datetime
import os
import time
import requests
import json

from os import path, listdir
from mycroft_bus_client import Message
from ovos_utils.log import LOG
from ovos_utils.skills import get_skills_folder
from ovos_utils.skills.locations import get_default_skills_directory

from mycroft.skills.core import resting_screen_handler, intent_file_handler, MycroftSkill
from mycroft.skills.skill_loader import load_skill_module
from mycroft.skills.skill_manager import SkillManager
from mycroft.skills.core import resting_screen_handler, intent_file_handler,\
MycroftSkill
from mycroft.skills.api import SkillApi


class OVOSHomescreenSkill(MycroftSkill):
# The constructor of the skill, which calls MycroftSkill's constructor
def __init__(self):
super(OVOSHomescreenSkill, self).__init__(name="OVOSHomescreen")
self.skill_manager = None
# self.skill_manager = None
self.notifications_storage_model = []
self.def_wallpaper_folder = path.dirname(__file__) + '/ui/wallpapers/'
self.loc_wallpaper_folder = None
self.selected_wallpaper = None # Get from config after __init__ is done
self.selected_wallpaper = None # Get from config after __init__ is done
self.wallpaper_collection = []
self.rtlMode = None # Get from config after __init__ is done
self.rtlMode = None # Get from config after __init__ is done

# Populate skill IDs to use for data sources
self.weather_skill = None # Get from config after __init__ is done
self.datetime_skill = None # Get from config after __init__ is done
self.skill_info_skill = None # Get from config after __init__ is done
self.weather_skill = None # Get from config after __init__ is done
self.datetime_skill = None # Get from config after __init__ is done
self.skill_info_skill = None # Get from config after __init__ is done
self.weather_api = None
self.datetime_api = None
self.skill_info_api = None
Expand All @@ -43,12 +41,7 @@ def initialize(self):
self.datetime_skill = self.settings.get("datetime_skill") or "skill-date-time.mycroftai"
self.skill_info_skill = self.settings.get("examples_skill") or "ovos-skills-info.openvoiceos"

now = datetime.datetime.now()
callback_time = datetime.datetime(
now.year, now.month, now.day, now.hour, now.minute
) + datetime.timedelta(seconds=60)
self.schedule_repeating_event(self.update_dt, callback_time, 10)
self.skill_manager = SkillManager(self.bus)
# self.skill_manager = SkillManager(self.bus)

# Handler Registration For Notifications
self.add_event("homescreen.wallpaper.set",
Expand All @@ -59,13 +52,12 @@ def initialize(self):
self.handle_notification_storage_model_update)
self.gui.register_handler("homescreen.swipe.change.wallpaper",
self.change_wallpaper)
self.add_event("mycroft.ready", self.handle_mycroft_ready)
self.add_event("mycroft.ready", self.handle_mycroft_ready, once=True)

if not self.file_system.exists("wallpapers"):
os.mkdir(path.join(self.file_system.path, "wallpapers"))

self.collect_wallpapers()
self._load_skill_apis()

self.bus.emit(Message("mycroft.device.show.idle"))

Expand All @@ -74,7 +66,7 @@ def initialize(self):

@resting_screen_handler("OVOSHomescreen")
def handle_idle(self, _):
LOG.debug('Activating Time/Date resting page')
LOG.info('Activating OVOSHomescreen resting page')
self.gui['wallpaper_path'] = self.check_wallpaper_path(self.selected_wallpaper)
self.gui['selected_wallpaper'] = self.selected_wallpaper
self.gui['notification'] = {}
Expand All @@ -89,7 +81,7 @@ def handle_idle(self, _):
self.update_weather()
self.update_examples()
except Exception as e:
LOG.error(e)
LOG.exception(e)

self.gui['rtl_mode'] = self.rtlMode
self.gui['dateFormat'] = self.config_core.get("date_format") or "DMY"
Expand Down Expand Up @@ -118,7 +110,11 @@ def update_dt(self):
self.gui["time_string"] = self.datetime_api.get_display_current_time()
self.gui["date_string"] = self.datetime_api.get_display_date()
self.gui["weekday_string"] = self.datetime_api.get_weekday()
self.gui['day_string'], self.gui["month_string"] = self._split_month_string(self.datetime_api.get_month_date())
day, month = \
self._split_month_string(self.datetime_api.get_month_date())
if day or month:
self.gui['day_string'], self.gui["month_string"] = day, month

self.gui["year_string"] = self.datetime_api.get_year()
else:
LOG.warning("No datetime_api, skipping update")
Expand All @@ -132,6 +128,9 @@ def update_weather(self):
self._load_skill_apis()
if self.weather_api:
current_weather_report = self.weather_api.get_current_weather_homescreen()
if not current_weather_report:
LOG.error("No weather report returned")
return
self.gui["weather_api_enabled"] = True
self.gui["weather_code"] = current_weather_report.get("weather_code")
self.gui["weather_temp"] = current_weather_report.get("weather_temp")
Expand Down Expand Up @@ -228,6 +227,8 @@ def shutdown(self):

def handle_mycroft_ready(self, _):
self._load_skill_apis()
interval = datetime.datetime.now() + datetime.timedelta(seconds=60)
self.schedule_repeating_event(self.update_dt, interval, 10)

def _load_skill_apis(self):
"""
Expand All @@ -243,7 +244,7 @@ def _load_skill_apis(self):
if not self.skill_info_api:
self.skill_info_api = SkillApi.get(self.skill_info_skill)
except Exception as e:
LOG.error(f"Failed To Import OVOS Info Skill: {e}")
LOG.error(f"Failed To Import Info Skill: {e}")

# Import Date Time Skill As Date Time Provider
try:
Expand All @@ -252,25 +253,15 @@ def _load_skill_apis(self):
except Exception as e:
LOG.error(f"Failed to import DateTime Skill: {e}")

# TODO: Depreciate this
if not self.datetime_api:
try:
root_dir = self.root_dir.rsplit("/", 1)[0]
time_date_path = str(root_dir) + f"/{self.datetime_skill}/__init__.py"
time_date_id = "datetimeskill"
datetimeskill = load_skill_module(time_date_path, time_date_id)
from datetimeskill import TimeSkill

self.datetime_api = TimeSkill()
except Exception as e:
LOG.error(f"Failed To Import DateTime Skill: {e}")

def _split_month_string(self, month_date: str) -> list:
"""
Splits a month+date string into month and date (i.e. "August 06" -> ["August", "06"])
:param month_date: formatted month and day of month ("August 06" or "06 August")
:return: [day, month]
"""
if not month_date:
LOG.error("No string to split")
return [None, None]
month_string = month_date.split(" ")
if self.config_core.get('date_format') == 'MDY':
day_string = month_string[1]
Expand All @@ -285,56 +276,55 @@ def _split_month_string(self, month_date: str) -> list:
# Build Voice Applications Model

def build_voice_applications_model(self):
voiceApplicationsList = []

if path.exists("/opt/mycroft/skills/"):
skill_folders = listdir("/opt/mycroft/skills/")
folder_prefix = "/opt/mycroft/skills"
else:
skill_folders = listdir(get_skills_folder())
folder_prefix = get_skills_folder()
voice_applications_list = []
skill_folder = get_default_skills_directory(self.config_core)
if not path.isdir(skill_folder):
return
skill_folders = listdir(skill_folder)

resource_app = "app.json"
resource_mobile = "android.json"

if not skill_folder:
return
for folder in skill_folders:
absolute_folder_path = path.join(folder_prefix, folder)
absolute_folder_path = path.join(skill_folder, folder)

if path.exists(path.join(absolute_folder_path, resource_app)) and path.isfile(
path.join(absolute_folder_path, resource_app)) :
if path.exists(path.join(absolute_folder_path, resource_app)) and \
path.isfile(path.join(absolute_folder_path, resource_app)):
with open(path.join(absolute_folder_path, resource_app)) as f:
expand_file = json.load(f)
folder_path = folder
if not any(d.get('folder', None) == folder_path
for d in voiceApplicationsList):
for d in voice_applications_list):
thumb = absolute_folder_path + expand_file["icon"]
voiceApplicationsList.append({"thumbnail": thumb,
voice_applications_list.append({"thumbnail": thumb,
"name": expand_file["name"],
"action": expand_file["action"],
"folder": folder_path})

elif path.exists(path.join(absolute_folder_path, resource_mobile)) and path.isfile(
path.join(absolute_folder_path, resource_mobile)) :
with open(path.join(absolute_folder_path, resource_mobile)) as f:
elif path.exists(path.join(absolute_folder_path,
resource_mobile)) and path.isfile(
path.join(absolute_folder_path, resource_mobile)):
with open(path.join(absolute_folder_path,
resource_mobile)) as f:
expand_file = json.load(f)
folder_path = folder
if not any(d.get('folder', None) == folder_path
for d in voiceApplicationsList):
for d in voice_applications_list):
thumb = absolute_folder_path + expand_file["android_icon"]
voiceApplicationsList.append({"thumbnail": thumb,
voice_applications_list.append({"thumbnail": thumb,
"name": expand_file["android_name"],
"action": expand_file["android_handler"],
"folder": folder_path})

try:
sort_on = "name"
decorated = [(dict_[sort_on], dict_)
for dict_ in voiceApplicationsList]
for dict_ in voice_applications_list]
decorated.sort()
return [dict_ for (key, dict_) in decorated]

except Exception:
return voiceApplicationsList
except Exception as e:
LOG.exception(e)
return voice_applications_list


def create_skill():
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
requests>=2.20.0,<2.26.0
requests~=2.20
pillow==7.1.2
ovos_utils>=0.0.6
ovos_utils~=0.0,>=0.0.23
14 changes: 7 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
from setuptools import setup

# skill_id=package_name:SkillClass
PLUGIN_ENTRY_POINT = 'mycroft-homescreen.mycroftai=ovos_skill_homescreen:OVOSHomescreenSkill'
PLUGIN_ENTRY_POINT = 'skill-homescreen-lite.openvoiceos=skill_homescreen_lite:OVOSHomescreenSkill'
# in this case the skill_id is defined to purposefully replace the mycroft version of the skill,
# or rather to be replaced by it in case it is present. all skill directories take precedence over plugin skills


setup(
# this is the package name that goes on pip
name='ovos-skill-homescreen',
name='ovos-skill-homescreen-lite',
version='0.0.1',
description='OVOS homescreen skill plugin',
url='https://github.com/OpenVoiceOS/skill-ovos-homescreen',
description='Minimal OVOS homescreen skill plugin',
url='https://github.com/OpenVoiceOS/skill-homescreen-lite',
author='Aix',
author_email='[email protected]',
license='Apache-2.0',
package_dir={"ovos_skill_homescreen": ""},
package_data={'ovos_skill_homescreen': ["vocab/*", "ui/*"]},
packages=['ovos_skill_homescreen'],
package_dir={"skill_homescreen_lite": ""},
package_data={'skill_homescreen_lite': ["vocab/*", "ui/*"]},
packages=['skill_homescreen_lite'],
include_package_data=True,
install_requires=["astral==1.4", "arrow==0.12.0"],
keywords='ovos skill plugin',
Expand Down
7 changes: 5 additions & 2 deletions test/license_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
'yt-dlp': "Unlicense",
'pyxdg': 'GPL-2.0',
'ptyprocess': 'ISC license',
'psutil': 'BSD3'
'psutil': 'BSD3',
"python-dateutil": "Apache-2.0",
"pyparsing": "MIT",
"exceptiongroup": "MIT"
}
# explicitly allow these packages that would fail otherwise
whitelist = []
Expand All @@ -22,7 +25,7 @@
allow_unlicense = True
allow_ambiguous = False

pkg_name = "ovos-skill-homescreen"
pkg_name = "ovos-skill-homescreen-lite"


class TestLicensing(unittest.TestCase):
Expand Down
5 changes: 3 additions & 2 deletions test/osm_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
from ovos_skills_manager import SkillEntry

branch = "dev"
url = f"https://github.com/OpenVoiceOS/ovos-skill-homescreen@{branch}"
url = f"https://github.com/OpenVoiceOS/skill-homescreen-lite@{branch}"


class TestOSM(unittest.TestCase):
@classmethod
def setUpClass(self):
self.skill_id = "ovos-skill-homescreen.OpenVoiceOS"
self.skill_id = "skill-homescreen-lite.OpenVoiceOS"

def test_osm_install(self):
skill = SkillEntry.from_github_url(url)
Expand Down
2 changes: 1 addition & 1 deletion test/plugin_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
class TestPlugin(unittest.TestCase):
@classmethod
def setUpClass(self):
self.skill_id = "ovos-skill-homescreen.OpenVoiceOS"
self.skill_id = "skill-homescreen-lite.openvoiceos"

def test_find_plugin(self):
plugins = find_skill_plugins()
Expand Down
13 changes: 4 additions & 9 deletions test/unittests/test_skill_loading.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
# write your first unittest!
import unittest
from os.path import join, dirname
import os
from ovos_utils.bracket_expansion import expand_parentheses, expand_options

from adapt.engine import IntentDeterminationEngine
from adapt.intent import IntentBuilder
from ovos_skill_homescreen import VolumeSkill, create_skill
from os.path import dirname
from skill_homescreen_lite import OVOSHomescreenSkill, create_skill
from ovos_plugin_manager.skills import find_skill_plugins
from ovos_utils.messagebus import FakeBus
from mycroft.skills.skill_loader import PluginSkillLoader, SkillLoader
Expand All @@ -15,12 +10,12 @@
class TestSkillLoading(unittest.TestCase):
@classmethod
def setUpClass(self):
self.skill_id = "ovos-skill-homescreen.OpenVoiceOS"
self.skill_id = "skill-homescreen-lite.openvoiceos"
self.path = dirname(dirname(dirname(__file__)))

def test_from_class(self):
bus = FakeBus()
skill = VolumeSkill()
skill = OVOSHomescreenSkill()
skill._startup(bus, self.skill_id)
self.assertEqual(skill.bus, bus)
self.assertEqual(skill.skill_id, self.skill_id)
Expand Down
2 changes: 1 addition & 1 deletion ui/idle.qml
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ Mycroft.CardDelegate {
return sessionData.weekday_string.substring(0,3) + " " + sessionData.day_string + " " + longShortMonth + ", " + sessionData.year_string
break
case "MDY":
return longShortMonth + " " + sessionData.weekday_string.substring(0,3) + " " + sessionData.day_string + ", " + sessionData.year_string
return sessionData.weekday_string.substring(0,3) + ", " + longShortMonth + " " + sessionData.day_string + ", " + sessionData.year_string
break
case "YMD":
return sessionData.year_string + ", " + longShortMonth + " " + sessionData.weekday_string.substring(0,3) + " " + sessionData.day_string
Expand Down