From bcef89b46274cd8db6cc3dce839c9b6b34233052 Mon Sep 17 00:00:00 2001 From: zim514 Date: Fri, 22 Dec 2023 22:09:39 -0500 Subject: [PATCH] New daytime logic and loop --- script.service.hue/addon.xml | 8 +- .../resource.language.en_gb/strings.po | 12 ++- script.service.hue/resources/lib/__init__.py | 2 + script.service.hue/resources/lib/core.py | 99 ++++++++++--------- script.service.hue/resources/lib/language.py | 5 +- .../resources/lib/lightgroup.py | 18 ++-- script.service.hue/resources/lib/menu.py | 28 +++--- script.service.hue/resources/settings.xml | 8 ++ 8 files changed, 106 insertions(+), 74 deletions(-) diff --git a/script.service.hue/addon.xml b/script.service.hue/addon.xml index 72a5a64f..d82786b9 100644 --- a/script.service.hue/addon.xml +++ b/script.service.hue/addon.xml @@ -1,7 +1,7 @@ - + - + @@ -12,6 +12,7 @@ executable + true all @@ -24,7 +25,8 @@ https://forum.kodi.tv/showthread.php?tid=344886 v2.0 - Initial support for Hue API V2 -- Rewrote sunset check +- Refactor execution loop +- V2 API Sunset support. Sunrise is now manually configured (Default 8AM) Automatitza les llums Hue amb la reproducció de Kodi Automatizace Hue světel s přehráváním Kodi diff --git a/script.service.hue/resources/language/resource.language.en_gb/strings.po b/script.service.hue/resources/language/resource.language.en_gb/strings.po index ee584863..897d0ca2 100644 --- a/script.service.hue/resources/language/resource.language.en_gb/strings.po +++ b/script.service.hue/resources/language/resource.language.en_gb/strings.po @@ -82,8 +82,8 @@ msgid "End time:" msgstr "End time:" msgctxt "#30508" -msgid "Disable during daylight" -msgstr "Disable during daylight" +msgid "Disable at sunset" +msgstr "Disable at sunset" msgctxt "#30509" msgid "Activate during playback at sunset" @@ -430,8 +430,8 @@ msgid "Settings" msgstr "Settings" msgctxt "#30063" -msgid "Disabled by daylight" -msgstr "Disabled by daylight" +msgid "Disabled by daytime" +msgstr "Disabled by daytime" msgctxt "#30064" msgid "ERROR: Scene not found" @@ -552,3 +552,7 @@ msgstr "" msgctxt "#30033" msgid "Reconnected" msgstr "" + +msgctxt "#31334" +msgid "Re-enable time" +msgstr "" \ No newline at end of file diff --git a/script.service.hue/resources/lib/__init__.py b/script.service.hue/resources/lib/__init__.py index cf9a7f03..7da6fd0c 100644 --- a/script.service.hue/resources/lib/__init__.py +++ b/script.service.hue/resources/lib/__init__.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: MIT # See LICENSE.TXT for more information. +import datetime import functools import time from collections import deque @@ -25,6 +26,7 @@ # ADDONDIR = xbmcvfs.translatePath(ADDON.getAddonInfo('profile')) ADDONPATH = xbmcvfs.translatePath(ADDON.getAddonInfo("path")) ADDONVERSION = ADDON.getAddonInfo('version') +ADDONSETTINGS = ADDON.getSettings() KODIVERSION = xbmc.getInfoLabel('System.BuildVersion') diff --git a/script.service.hue/resources/lib/core.py b/script.service.hue/resources/lib/core.py index 94ff1cea..5ebfccaf 100644 --- a/script.service.hue/resources/lib/core.py +++ b/script.service.hue/resources/lib/core.py @@ -9,7 +9,7 @@ import xbmc -from . import ADDON, SETTINGS_CHANGED, ADDONID, AMBI_RUNNING +from . import ADDON, SETTINGS_CHANGED, ADDONID, AMBI_RUNNING, ADDONSETTINGS from . import ambigroup, lightgroup, kodiutils, hueconnection from .hueconnection import HueConnection from .kodiutils import validate_settings, notification, cache_set, cache_get @@ -41,8 +41,7 @@ def _commands(monitor, command): ADDON.openSettings() elif command == "createHueScene": - hue_connection = hueconnection.HueConnection(monitor, silent=True, - discover=False) # don't rediscover, proceed silently + hue_connection = hueconnection.HueConnection(monitor, silent=True, discover=False) # don't rediscover, proceed silently if hue_connection.connected: hue_connection.create_hue_scene() else: @@ -50,8 +49,7 @@ def _commands(monitor, command): notification(_("Hue Service"), _("Check Hue Bridge configuration")) elif command == "deleteHueScene": - hue_connection = hueconnection.HueConnection(monitor, silent=True, - discover=False) # don't rediscover, proceed silently + hue_connection = hueconnection.HueConnection(monitor, silent=True, discover=False) # don't rediscover, proceed silently if hue_connection.connected: hue_connection.delete_hue_scene() else: @@ -63,8 +61,7 @@ def _commands(monitor, command): action = sys.argv[3] # xbmc.log(f"[script.service.hue] sceneSelect: light_group: {light_group}, action: {action}") - hue_connection = hueconnection.HueConnection(monitor, silent=True, - discover=False) # don't rediscover, proceed silently + hue_connection = hueconnection.HueConnection(monitor, silent=True, discover=False) # don't rediscover, proceed silently if hue_connection.connected: hue_connection.configure_scene(light_group, action) else: @@ -75,8 +72,7 @@ def _commands(monitor, command): light_group = sys.argv[2] # xbmc.log(f"[script.service.hue] ambiLightSelect light_group_id: {light_group}") - hue_connection = hueconnection.HueConnection(monitor, silent=True, - discover=False) # don't rediscover, proceed silently # don't rediscover, proceed silently + hue_connection = hueconnection.HueConnection(monitor, silent=True, discover=False) # don't rediscover, proceed silently # don't rediscover, proceed silently if hue_connection.connected: hue_connection.configure_ambilights(light_group) else: @@ -90,17 +86,17 @@ def _commands(monitor, command): def _service(monitor): service_enabled = cache_get("service_enabled") - #### V1 Connection - reliable discovery and config + # V1 Connection - reliable discovery and config hue_connection = HueConnection(monitor, silent=ADDON.getSettingBool("disableConnectionMessage"), discover=False) - #### V2 Connection - this has no proper bridge discovery, and missing all kinds of error checking + # V2 Connection - this has no proper bridge discovery, and missing all kinds of error checking bridge = HueAPIv2(monitor, ip=ADDON.getSetting("bridgeIP"), key=ADDON.getSetting("bridgeUser")) if bridge.connected and hue_connection.connected: # light groups still expect a V1 bridge object light_groups = [lightgroup.LightGroup(0, hue_connection, lightgroup.VIDEO), lightgroup.LightGroup(1, hue_connection, lightgroup.AUDIO), ambigroup.AmbiGroup(3, hue_connection)] - #start sunset and midnight timers + # start sunset and midnight timers timers = Timers(monitor, bridge, light_groups) timers.start() @@ -193,49 +189,64 @@ def activate(light_groups): class Timers(threading.Thread): def __init__(self, monitor, bridge, light_groups): - super().__init__() self.monitor = monitor self.bridge = bridge self.light_groups = light_groups + self.morning_time = datetime.datetime.strptime(ADDONSETTINGS.getString("morningTime"), "%H:%M").time() + self._set_daytime() + super().__init__() def run(self): - self._schedule() + self._task_loop() - def _run_midnight(self): - # get new day's sunset time after midnight + def _run_morning(self): + cache_set("daytime", True) self.bridge.update_sunset() - xbmc.log(f"[script.service.hue] in run_midnight(): 1 minute past midnight, new sunset time: {self.bridge.sunset}") + xbmc.log(f"[script.service.hue] run_morning(): new sunset: {self.bridge.sunset}") def _run_sunset(self): # The function you want to run at sunset + cache_set("daytime", False) activate(self.light_groups) xbmc.log(f"[script.service.hue] in run_sunset(): Sunset. ") - @staticmethod - def _calculate_initial_delay(target_time): - # Calculate the delay until the target time - now = datetime.datetime.now().time() - target_datetime = datetime.datetime.combine(datetime.date.today(), target_time) - if target_time < now: - target_datetime += datetime.timedelta(days=1) - delay = (target_datetime - datetime.datetime.now()).total_seconds() - return delay - - def _schedule(self): + def _set_daytime(self): + now = datetime.datetime.now() + + if self.morning_time <= now.time() <= self.bridge.sunset: + cache_set("daytime", True) + else: + cache_set("daytime", False) + + def _task_loop(self): + while not self.monitor.abortRequested(): - # Calculate delay for sunset and wait - delay_sunset = self._calculate_initial_delay(self.bridge.sunset) - xbmc.log(f"[script.service.hue] in schedule(): Sunset will run in {delay_sunset} seconds") - if self.monitor.waitForAbort(delay_sunset): - # Abort was requested while waiting. We should exit - break - self._run_sunset() - - # Calculate delay for midnight and wait - midnight_plus_one = (datetime.datetime.now() + datetime.timedelta(days=1)).replace(hour=0, minute=1, second=0, microsecond=0).time() - delay_midnight = self._calculate_initial_delay(midnight_plus_one) - xbmc.log(f"[script.service.hue] in schedule(): Midnight will run in {delay_midnight} seconds") - if self.monitor.waitForAbort(delay_midnight): - # Abort was requested while waiting. We should exit - break - self._run_midnight() + + now = datetime.datetime.now() + self.morning_time = datetime.datetime.strptime(ADDONSETTINGS.getString("morningTime"), "%H:%M").time() + + time_to_sunset = self._time_until(now, self.bridge.sunset) + time_to_morning = self._time_until(now, self.morning_time) + + if time_to_sunset < time_to_morning: + # Sunset is next + wait_time = time_to_sunset + xbmc.log(f"[script.service.hue] Timers: Sunset is next. wait_time: {wait_time}") + if self.monitor.waitForAbort(wait_time): + break + self._run_sunset() + + else: + # Morning is next + wait_time = time_to_morning + xbmc.log(f"[script.service.hue] Timers: Morning is next. wait_time: {wait_time}") + if self.monitor.waitForAbort(wait_time): + break + self._run_morning() + + @staticmethod + def _time_until(current, target): + # Calculates remaining time from current to target + now = datetime.datetime(1, 1, 1, current.hour, current.minute, current.second) + then = datetime.datetime(1, 1, 1, target.hour, target.minute, target.second) + return (then - now).seconds diff --git a/script.service.hue/resources/lib/language.py b/script.service.hue/resources/lib/language.py index 79a73fd3..82685ab3 100644 --- a/script.service.hue/resources/lib/language.py +++ b/script.service.hue/resources/lib/language.py @@ -41,7 +41,7 @@ def get_string(t): _strings['enable schedule (24h format)'] = 30505 _strings['start time:'] = 30506 _strings['end time:'] = 30507 -_strings['disable during daylight'] = 30508 +_strings['disable at sunset'] = 30508 _strings['activate during playback at sunset'] = 30509 _strings['general'] = 30510 _strings['schedule'] = 30511 @@ -128,7 +128,6 @@ def get_string(t): _strings['play'] = 30060 _strings['hue status: '] = 30061 _strings['settings'] = 30062 -_strings['disabled by daylight'] = 30063 _strings['error: scene not found'] = 30064 _strings['the following error occurred:'] = 30080 _strings['automatically report this error?'] = 30081 @@ -159,3 +158,5 @@ def get_string(t): _strings['bridge outdated. please update your bridge.'] = 30019 _strings['error: scene or light not found, it may have changed or been deleted. check your configuration.'] = 30003 _strings['reconnected'] = 30033 +_strings['re-enable time'] = 31334 +_strings['disabled by daytime'] = 30063 diff --git a/script.service.hue/resources/lib/lightgroup.py b/script.service.hue/resources/lib/lightgroup.py index e07a3e16..62acc86b 100644 --- a/script.service.hue/resources/lib/lightgroup.py +++ b/script.service.hue/resources/lib/lightgroup.py @@ -54,7 +54,8 @@ def __repr__(self): def onAVStarted(self): if self.enabled: xbmc.log( - f"[script.service.hue] In LightGroup[{self.light_group_id}], onPlaybackStarted. Group enabled: {self.enabled},startBehavior: {self.start_behavior} , isPlayingVideo: {self.isPlayingVideo()}, isPlayingAudio: {self.isPlayingAudio()}, self.mediaType: {self.media_type},self.playbackType(): {self.playback_type()}") + f"[script.service.hue] In LightGroup[{self.light_group_id}], onPlaybackStarted. Group enabled: {self.enabled},startBehavior: {self.start_behavior} , isPlayingVideo: {self.isPlayingVideo()}, isPlayingAudio: {self.isPlayingAudio()}, self.mediaType: {self.media_type},self.playbackType(): {self.playback_type()}" + ) self.state = STATE_PLAYING self.last_media_type = self.playback_type() @@ -81,7 +82,8 @@ def onPlayBackPaused(self): self.state = STATE_PAUSED if self.media_type == VIDEO and not self.check_video_activation( - self.video_info_tag): # If video group, check video activation. Otherwise it's audio so we ignore this and continue + self.video_info_tag + ): # If video group, check video activation. Otherwise it's audio so we ignore this and continue return if (self.check_active_time() or self.check_already_active(self.pause_scene)) and self.check_keep_lights_off_rule(self.pause_scene) and self.pause_behavior and self.media_type == self.playback_type(): @@ -154,12 +156,14 @@ def playback_type(self): @staticmethod def check_active_time(): - daylight = cache_get("daylight") #TODO: get daylight from HueAPIv2 - xbmc.log("[script.service.hue] Schedule: {}, daylightDisable: {}, daylight: {}, startTime: {}, endTime: {}".format(ADDON.getSettingBool("enableSchedule"), ADDON.getSettingBool("daylightDisable"), daylight, - ADDON.getSettingString("startTime"), ADDON.getSettingString("endTime"))) + daytime = cache_get("daytime") # TODO: get daytime from HueAPIv2 + xbmc.log("[script.service.hue] Schedule: {}, daylightDisable: {}, daytime: {}, startTime: {}, endTime: {}".format(ADDON.getSettingBool("enableSchedule"), ADDON.getSettingBool("daylightDisable"), daytime, + ADDON.getSettingString("startTime"), ADDON.getSettingString("endTime") + ) + ) - if ADDON.getSettingBool("daylightDisable") and daylight: - xbmc.log("[script.service.hue] Disabled by daylight") + if ADDON.getSettingBool("daylightDisable") and daytime: + xbmc.log("[script.service.hue] Disabled by daytime") return False if ADDON.getSettingBool("enableSchedule"): diff --git a/script.service.hue/resources/lib/menu.py b/script.service.hue/resources/lib/menu.py index 8c7cccd5..6d493e9b 100644 --- a/script.service.hue/resources/lib/menu.py +++ b/script.service.hue/resources/lib/menu.py @@ -32,15 +32,15 @@ def menu(): xbmc.executebuiltin('Container.Refresh') elif command == "toggle": - if cache_get("service_enabled") and _get_status() != "Disabled by daylight": + if cache_get("service_enabled") and _get_status() != "Disabled by daytime": xbmc.log("[script.service.hue] Disable service") cache_set("service_enabled", False) - elif _get_status() != "Disabled by daylight": + elif _get_status() != "Disabled by daytime": xbmc.log("[script.service.hue] Enable service") cache_set("service_enabled", True) else: - xbmc.log("[script.service.hue] Disabled by daylight, ignoring") + xbmc.log("[script.service.hue] Disabled by daytime, ignoring") xbmc.executebuiltin('Container.Refresh') @@ -82,13 +82,13 @@ def build_menu(base_url, addon_handle): def _get_status(): enabled = cache_get("service_enabled") - daylight = cache_get("daylight") #TODO: get daylight from bridge v2 - daylight_disable = ADDON.getSettingBool("daylightDisable") - xbmc.log(f"[script.service.hue] _get_status enabled: {enabled} - {type(enabled)}, daylight: {daylight}, daylight_disable: {daylight_disable}") + daytime = cache_get("daytime") #TODO: get daytime from bridge v2 + daytime_disable = ADDON.getSettingBool("daylightDisable") #Legacy setting name + xbmc.log(f"[script.service.hue] _get_status enabled: {enabled} - {type(enabled)}, daytime: {daytime}, daytime_disable: {daytime_disable}") - # xbmc.log("[script.service.hue] Current status: {}".format(daylight_disable)) - if daylight and daylight_disable: - return _("Disabled by daylight") + # xbmc.log("[script.service.hue] Current status: {}".format(daytime_disable)) + if daytime and daytime_disable: + return _("Disabled by daytime") if enabled: return _("Enabled") elif not enabled: @@ -99,11 +99,11 @@ def _get_status(): def _get_status_icon(): enabled = cache_get("service_enabled") - daylight = cache_get("daylight") #TODO: get daylight from bridge v2 - daylight_disable = ADDON.getSettingBool("daylightDisable") - # xbmc.log("[script.service.hue] Current status: {}".format(daylight_disable)) - if daylight and daylight_disable: - return xbmcvfs.makeLegalFilename(ADDONPATH + "resources/icons/daylight.png") # Disabled by Daylight + daytime = cache_get("daytime") #TODO: get daytime from bridge v2 + daytime_disable = ADDON.getSettingBool("daylightDisable") + # xbmc.log("[script.service.hue] Current status: {}".format(daytime_disable)) + if daytime and daytime_disable: + return xbmcvfs.makeLegalFilename(ADDONPATH + "resources/icons/daylight.png") # Disabled by daytime, legacy icon name elif enabled: return xbmcvfs.makeLegalFilename(ADDONPATH + "resources/icons/enabled.png") # Enabled return xbmcvfs.makeLegalFilename(ADDONPATH + "resources/icons/disabled.png") # Disabled diff --git a/script.service.hue/resources/settings.xml b/script.service.hue/resources/settings.xml index 8524c1cb..6b8e4fd8 100644 --- a/script.service.hue/resources/settings.xml +++ b/script.service.hue/resources/settings.xml @@ -731,6 +731,14 @@ False + + 2 + 08:00 + + 31334 + + + 1 False