Skip to content

Commit

Permalink
New daytime logic and loop
Browse files Browse the repository at this point in the history
  • Loading branch information
zim514 committed Dec 23, 2023
1 parent 0cc2da9 commit bcef89b
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 74 deletions.
8 changes: 5 additions & 3 deletions script.service.hue/addon.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<addon id="script.service.hue" name="Hue Service" provider-name="zim514" version="2.0~alpha1">
<addon id="script.service.hue" name="Hue Service" provider-name="zim514" version="1.5~alpha1">

<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="xbmc.python" version="3.0.1"/>
<import addon="script.module.requests" version="2.27.1"/>
<import addon="script.module.pil"/>
<import addon="script.module.pyrollbar" version="0.15.2"/>
Expand All @@ -12,6 +12,7 @@
<extension point="xbmc.python.pluginsource" library="plugin.py">
<provides>executable</provides>
</extension>

<extension point="xbmc.addon.metadata">
<reuselanguageinvoker>true</reuselanguageinvoker>
<platform>all</platform>
Expand All @@ -24,7 +25,8 @@
<forum>https://forum.kodi.tv/showthread.php?tid=344886</forum>
<news>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)
</news>
<summary lang="ca_ES">Automatitza les llums Hue amb la reproducció de Kodi</summary>
<summary lang="cs_CZ">Automatizace Hue světel s přehráváním Kodi</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -552,3 +552,7 @@ msgstr ""
msgctxt "#30033"
msgid "Reconnected"
msgstr ""

msgctxt "#31334"
msgid "Re-enable time"
msgstr ""
2 changes: 2 additions & 0 deletions script.service.hue/resources/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: MIT
# See LICENSE.TXT for more information.

import datetime
import functools
import time
from collections import deque
Expand All @@ -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')


Expand Down
99 changes: 55 additions & 44 deletions script.service.hue/resources/lib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -41,17 +41,15 @@ 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:
xbmc.log("[script.service.hue] No bridge found. createHueScene cancelled.")
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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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()

Expand Down Expand Up @@ -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
5 changes: 3 additions & 2 deletions script.service.hue/resources/lib/language.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
18 changes: 11 additions & 7 deletions script.service.hue/resources/lib/lightgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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():
Expand Down Expand Up @@ -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"):
Expand Down
28 changes: 14 additions & 14 deletions script.service.hue/resources/lib/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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:
Expand All @@ -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
8 changes: 8 additions & 0 deletions script.service.hue/resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,14 @@
<default>False</default>
<control type="toggle"/>
</setting>
<setting id="morningTime" type="time" label="31334" help="">
<level>2</level>
<default>08:00</default>
<control type="button" format="time">
<heading>31334</heading>
</control>

</setting>
<setting id="enable_if_already_active" type="boolean" label="30516" help="">
<level>1</level>
<default>False</default>
Expand Down

0 comments on commit bcef89b

Please sign in to comment.