Skip to content

Commit

Permalink
[script.service.hue] 2.0.12
Browse files Browse the repository at this point in the history
  • Loading branch information
zim514 committed Oct 5, 2024
1 parent 4190487 commit 5e35c26
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 82 deletions.
13 changes: 5 additions & 8 deletions script.service.hue/addon.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<addon id="script.service.hue" name="Hue Service" provider-name="zim514" version="2.0.10">
<addon id="script.service.hue" name="Hue Service" provider-name="zim514" version="2.0.12">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.requests" version="2.31.0"/>
Expand All @@ -20,14 +20,11 @@
</assets>
<source>https://github.com/zim514/script.service.hue</source>
<forum>https://forum.kodi.tv/showthread.php?tid=344886</forum>
<news>v2.0.0
- Hue API V2 support (requires reconfiguration of scenes and ambilight)
- Now uses standard scenes
- Sunrise is now manually configured (Default 8AM)
- Removed some unnecessary settings
- Refactoring and code improvements
- Many bug fixes
<news>v2.0.12
- Fix schedule / activation checks
- Fix Music support
- Localisation updates from Weblate
- Fix Ambilight support

</news>
<summary lang="ca_ES">Automatitza les llums Hue amb la reproducció de Kodi</summary>
Expand Down
46 changes: 26 additions & 20 deletions script.service.hue/resources/lib/ambigroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,44 +39,33 @@ def __init__(self, light_group_id, settings_monitor, bridge):
self.converterC = Converter(GamutC)
self.helper = ColorHelper(GamutC) # Gamut doesn't matter for this usage

if getattr(self.settings_monitor, f"group{self.light_group_id}_enabled", False) and self.bridge.connected:

index = 0
lights = getattr(self.settings_monitor, f"group{self.light_group_id}_lights")
if len(lights) > 0:
for L in lights:
gamut = self._get_light_gamut(self.bridge, L)
if gamut == 404:
notification(header=_("Hue Service"), message=_(f"ERROR: Light not found, it may have been deleted"), icon=xbmcgui.NOTIFICATION_ERROR)
AMBI_RUNNING.clear()
ADDON.setSettingString(f"group{self.light_group_id}_Lights", "-1")
ADDON.setSettingString(f"group{self.light_group_id}_LightNames", _("Not selected"))
else:
light = {L: {'gamut': gamut, 'prev_xy': (0, 0), "index": index}}
self.ambi_lights.update(light)
index = index + 1
log(f"[SCRIPT.SERVICE.HUE] AmbiGroup[{self.light_group_id}] Lights: {self.ambi_lights}")


# convert MS to seconds

def onAVStarted(self):
self.state = STATE_PLAYING
self.last_media_type = self._playback_type()
enabled = getattr(self.settings_monitor, f"group{self.light_group_id}_enabled", False)

if getattr(self.settings_monitor, f"group{self.light_group_id}_enabled", False) and self.bridge.connected:
self._get_lights()
else:
return

log(f"[SCRIPT.SERVICE.HUE] AmbiGroup[{self.light_group_id}] onPlaybackStarted. Group enabled: {enabled}, Bridge connected: {self.bridge.connected}, mediaType: {self.media_type}")

if not enabled or not self.bridge.connected:
return

log(f"[SCRIPT.SERVICE.HUE] AmbiGroup[{self.light_group_id}] onPlaybackStarted. media_type: {self.media_type} == playback_type: {self._playback_type()}")
if self.media_type == self._playback_type() and self._playback_type() == VIDEO:
try:
self.video_info_tag = self.getVideoInfoTag()
self.info_tag = self.getVideoInfoTag()
except (AttributeError, TypeError) as x:
log(f"[SCRIPT.SERVICE.HUE] AmbiGroup{self.light_group_id}: OnAV Started: Can't read infoTag")
reporting.process_exception(x)
else:
self.video_info_tag = None
self.info_tag = None

if self.activation_check.validate():
log(f"[SCRIPT.SERVICE.HUE] AmbiGroup[{self.light_group_id}] Running Play action")
Expand Down Expand Up @@ -281,3 +270,20 @@ def _resume_all_light_states(self, states):
log(f"[SCRIPT.SERVICE.HUE] Light[{light_id}] state resumed successfully.")
else:
log(f"[SCRIPT.SERVICE.HUE] Failed to resume Light[{light_id}] state.")

def _get_lights(self):
index = 0
lights = getattr(self.settings_monitor, f"group{self.light_group_id}_lights")
if len(lights) > 0:
for L in lights:
gamut = self._get_light_gamut(self.bridge, L)
if gamut == 404:
notification(header=_("Hue Service"), message=_(f"ERROR: Light not found, it may have been deleted"), icon=xbmcgui.NOTIFICATION_ERROR)
AMBI_RUNNING.clear()
ADDON.setSettingString(f"group{self.light_group_id}_Lights", "-1")
ADDON.setSettingString(f"group{self.light_group_id}_LightNames", _("Not selected"))
else:
light = {L: {'gamut': gamut, 'prev_xy': (0, 0), "index": index}}
self.ambi_lights.update(light)
index = index + 1
log(f"[SCRIPT.SERVICE.HUE] AmbiGroup[{self.light_group_id}] Lights: {self.ambi_lights}")
7 changes: 3 additions & 4 deletions script.service.hue/resources/lib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
import threading
from datetime import datetime, timedelta, date

import xbmc

from . import ADDON, AMBI_RUNNING, BRIDGE_SETTINGS_CHANGED
from . import ADDON, AMBI_RUNNING, BRIDGE_SETTINGS_CHANGED, KODIVERSION, ADDONVERSION
from . import lightgroup, ambigroup, settings
from .hue import Hue
from .kodiutils import notification, cache_set, cache_get, log
Expand Down Expand Up @@ -41,7 +40,7 @@ def __init__(self, settings_monitor):
}

def handle_command(self, command, *args):
log(f"[SCRIPT.SERVICE.HUE] Started with {command}, Python: {sys.version}")
log(f"[SCRIPT.SERVICE.HUE] Started with {command}, Kodi: {KODIVERSION}, Addon: {ADDONVERSION}, Python: {sys.version}")
command_func = self.commands.get(command)

if command_func:
Expand Down Expand Up @@ -88,7 +87,7 @@ def __init__(self, settings_monitor):
cache_set("service_enabled", True)

def run(self):
log(f"[SCRIPT.SERVICE.HUE] Starting Hue Service, Python: {sys.version}")
log(f"[SCRIPT.SERVICE.HUE] Starting Hue Service, Kodi: {KODIVERSION}, Addon: {ADDONVERSION}, Python: {sys.version}")
self.light_groups = self.initialize_light_groups()
self.timers = Timers(self.settings_monitor, self.bridge, self)

Expand Down
6 changes: 3 additions & 3 deletions script.service.hue/resources/lib/hue.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ def connect(self):
self.session.headers.update({'hue-application-key': self.settings_monitor.key})

self.devices = self.make_api_request("GET", "device")
if self.devices is None:
log(f"[SCRIPT.SERVICE.HUE] v2 connect: Connection attempts failed. Setting connected to False")
if not isinstance(self.devices, dict):
log(f"[SCRIPT.SERVICE.HUE] v2 connect: Connection error. Setting connected to False. {type(self.devices)} : {self.devices}")
self.connected = False
return False

Expand Down Expand Up @@ -313,7 +313,7 @@ def update_sunset(self):
geolocation = self.make_api_request("GET", "geolocation")
log(f"[SCRIPT.SERVICE.HUE] v2 update_sunset(): geolocation: {geolocation}")
sunset_str = self.search_dict(geolocation, "sunset_time")
if sunset_str is None:
if sunset_str is None or sunset_str == "":
log(f"[SCRIPT.SERVICE.HUE] Sunset not found; configure Hue geolocalisation")
notification(_("Hue Service"), _("Configure Hue Home location to use Sunset time, defaulting to 19:00"), icon=xbmcgui.NOTIFICATION_ERROR)
self.sunset = convert_time("19:00")
Expand Down
96 changes: 51 additions & 45 deletions script.service.hue/resources/lib/lightgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def __init__(self, light_group_id, media_type, settings_monitor, bridge=None):
self.light_group_id = light_group_id
self.state = STATE_STOPPED
self.media_type = media_type
self.video_info_tag = xbmc.InfoTagVideo
self.info_tag = None
self.last_media_type = self.media_type
self.settings_monitor = settings_monitor

Expand All @@ -51,20 +51,23 @@ def onAVStarted(self):

log(f"[SCRIPT.SERVICE.HUE] LightGroup[{self.light_group_id}] onPlaybackStarted. play_behavior: {play_enabled}, media_type: {self.media_type} == playback_type: {self._playback_type()}")
if play_enabled and self.media_type == self._playback_type() and self._playback_type() == VIDEO:

try:
self.video_info_tag = self.getVideoInfoTag()
self.info_tag = self.getVideoInfoTag()
except (AttributeError, TypeError) as x:
log(f"[SCRIPT.SERVICE.HUE] LightGroup{self.light_group_id}: OnAV Started: Can't read infoTag")
log(f"[SCRIPT.SERVICE.HUE] LightGroup{self.light_group_id}: OnAV Started: Can't read VideoInfoTag")
reporting.process_exception(x)
elif play_enabled and self.media_type == self._playback_type() and self._playback_type() == AUDIO:
try:
self.info_tag = self.getMusicInfoTag()
except (AttributeError, TypeError) as x:
log(f"[SCRIPT.SERVICE.HUE] LightGroup{self.light_group_id}: OnAV Started: Can't read AudioInfoTag")
reporting.process_exception(x)
else:
self.video_info_tag = None

if self.activation_check.validate(play_scene):
contents = inspect.getmembers(self.video_info_tag)
contents = inspect.getmembers(self.info_tag)
log(f"[SCRIPT.SERVICE.HUE] Start InfoTag: {contents}")

log(f"[SCRIPT.SERVICE.HUE] InfoTag: {self.video_info_tag}, {self.video_info_tag.getDuration()}")
#log(f"[SCRIPT.SERVICE.HUE] InfoTag: {self.info_tag}, {self.info_tag.getDuration()}")
log(f"[SCRIPT.SERVICE.HUE] LightGroup[{self.light_group_id}] Running Play action")
self.run_action("play")

Expand Down Expand Up @@ -92,18 +95,17 @@ def onPlayBackStopped(self):

log(f"[SCRIPT.SERVICE.HUE] LightGroup[{self.light_group_id}] onPlaybackStopped. Group enabled: {enabled}, Bridge connected: {self.bridge.connected}")


if not enabled or not self.bridge.connected:
return

if stop_enabled and (self.media_type == self.last_media_type or self.media_type == self._playback_type()):
########### TODO: Remove debug block
#xbmc.sleep(5000)
contents = inspect.getmembers(self.video_info_tag)
contents = inspect.getmembers(self.info_tag)
log(f"[SCRIPT.SERVICE.HUE] Stop[{self.light_group_id}] InfoTag Inspect Contents: {contents}")

duration = self.video_info_tag.getDuration()
log(f"[SCRIPT.SERVICE.HUE] Stop[{self.light_group_id}]: {self.video_info_tag}, {duration}")
duration = self.info_tag.getDuration()
log(f"[SCRIPT.SERVICE.HUE] Stop[{self.light_group_id}]: {self.info_tag}, {duration}")
############

if self.activation_check.validate(stop_scene):
Expand Down Expand Up @@ -195,7 +197,7 @@ def _video_activation_rules(self):
other_setting = self.settings_monitor.other_setting

# Fetch video info tag
info_tag = self.light_group.video_info_tag
info_tag = self.light_group.info_tag
# Get duration in minutes
duration = info_tag.getDuration() / 60
# Get media type and file name
Expand Down Expand Up @@ -255,10 +257,8 @@ def _is_within_schedule(self):
log("[SCRIPT.SERVICE.HUE] _is_within_schedule: True, Schedule not enabled")
return True

def skip_time_check_if_light_on(self, scene_id, all_light_states):
if not self.settings_monitor.enable_if_already_active:
log("[SCRIPT.SERVICE.HUE] _is_scene_already_active: Not enabled")
return False
def _check_any_lights_on(self, scene_id, all_light_states):
""" Checks if ANY light in the current scene is on"""

# Find the current scene from the scene data
current_scene = next((scene for scene in self.light_group.bridge.scene_data['data'] if scene['id'] == scene_id), None)
Expand All @@ -277,7 +277,8 @@ def skip_time_check_if_light_on(self, scene_id, all_light_states):
log("[SCRIPT.SERVICE.HUE] _is_scene_already_active: No lights in the scene are on")
return False

def skip_scene_if_all_off(self, scene_id, all_light_states):
def _check_all_lights_off(self, scene_id, all_light_states):
""" Checks if ALL the lights in the given scene are off"""
# Find the current scene from the scene data
current_scene = next((scene for scene in self.light_group.bridge.scene_data['data'] if scene['id'] == scene_id), None)
if not current_scene:
Expand All @@ -289,9 +290,9 @@ def skip_scene_if_all_off(self, scene_id, all_light_states):
light_id = action['target']['rid']
light_state = next((state for state in all_light_states['data'] if state['id'] == light_id), None)
if light_state and 'on' in light_state and light_state['on']['on']:
log(f"[SCRIPT.SERVICE.HUE] _is_any_light_off: Light {light_id} in the scene is on")
log(f"[SCRIPT.SERVICE.HUE] _check_all_lights_off: Light {light_id} in the scene is on")
return True

log(f"[SCRIPT.SERVICE.HUE] _check_all_lights_off: All in scene {scene_id} are off")
return False

def validate(self, scene=None):
Expand All @@ -300,7 +301,7 @@ def validate(self, scene=None):
skip_time_check_if_light_on = self.settings_monitor.skip_time_check_if_light_on
skip_scene_if_all_off = self.settings_monitor.skip_scene_if_all_off

log(f"[SCRIPT.SERVICE.HUE] LightGroup[{self.light_group_id}] ActivationChecker.validate(): scene: {scene}, media_type: {self.light_group.media_type}, skip_time_check_if_light_on: {skip_time_check_if_light_on}, skip_scene_if_all_off: {skip_scene_if_all_off}")
log(f"[SCRIPT.SERVICE.HUE] Validate Activation LightGroup[{self.light_group_id}] Scene: {scene}, media_type: {self.light_group.media_type}, skip_time_check_if_light_on: {skip_time_check_if_light_on}, skip_scene_if_all_off: {skip_scene_if_all_off}")

all_light_states = None
if scene and (skip_time_check_if_light_on or skip_scene_if_all_off):
Expand All @@ -309,35 +310,40 @@ def validate(self, scene=None):
# log(f"[SCRIPT.SERVICE.HUE] validate: all_light_states {all_light_states}")

if self.light_group.media_type == VIDEO and scene:
if skip_scene_if_all_off and not skip_scene_if_all_off(scene, all_light_states):
log("[SCRIPT.SERVICE.HUE] validate: All lights are off, not activating scene")
return False
if not (self._is_within_schedule() and self._video_activation_rules()):
log("[SCRIPT.SERVICE.HUE] validate: Not within schedule or video activation rules not met, not activating scene")
# Check video activation rules with a Scene
if skip_scene_if_all_off and not self._check_all_lights_off(scene, all_light_states):
log("[SCRIPT.SERVICE.HUE] Validate video: All lights are off, not activating scene")
return False
log("[SCRIPT.SERVICE.HUE] validate: Activating scene for VIDEO")
return True
elif (skip_time_check_if_light_on and self._check_any_lights_on(scene, all_light_states)) and self._video_activation_rules():
log("[SCRIPT.SERVICE.HUE] Validate video: Some lights are on, skipping schedule check")
return True
elif self._is_within_schedule() and self._video_activation_rules():
log("[SCRIPT.SERVICE.HUE] Validate video: Scene selected, within schedule and video activation rules, activate")
return True

elif self.light_group.media_type == VIDEO: # if no scene is set, use the default activation. This is the case for ambilight.
if not (self._is_within_schedule() and self._video_activation_rules()):
log("[SCRIPT.SERVICE.HUE] validate: Not within schedule or video activation rules not met, not activating scene")
log("[SCRIPT.SERVICE.HUE] Validate Video: No valid checks passed, not activating scene")
return False

elif self.light_group.media_type == VIDEO:
# if no scene is set, use the default activation. This is the case for ambilight.
if self._is_within_schedule() and self._video_activation_rules():
log("[SCRIPT.SERVICE.HUE] Validate Video: No scene selected, within schedule and video activation rules: activate")
return True
else:
log("[SCRIPT.SERVICE.HUE] Validate Video: No scene selected, not within schedule or activation rules, ignoring")
return False
log("[SCRIPT.SERVICE.HUE] validate: Activating scene for VIDEO")
return True

elif self.light_group.media_type == AUDIO and scene:
# Check audio activation rules
if skip_scene_if_all_off and not skip_scene_if_all_off(scene, all_light_states):
log("[SCRIPT.SERVICE.HUE] validate: All lights are off, not activating scene")
return False
if not self._is_within_schedule():
log("[SCRIPT.SERVICE.HUE] validate: Not within schedule, not activating scene")
log("[SCRIPT.SERVICE.HUE] Validate Audio: All lights are off, not activating scene")
return False
log("[SCRIPT.SERVICE.HUE] validate: Activating scene for AUDIO media type")
return True
elif (skip_time_check_if_light_on and self._check_any_lights_on(scene, all_light_states)):
log("[SCRIPT.SERVICE.HUE] Validate Audio: A light in the scene is on, activating scene")
return True
elif self._is_within_schedule():
log("[SCRIPT.SERVICE.HUE] Validate Audio: Within schedule, activating scene")
return True
log("[SCRIPT.SERVICE.HUE] Validate Audio: Checks not passed, not activating")
return False

elif self.light_group.media_type == AUDIO:
if not self._is_within_schedule():
log("[SCRIPT.SERVICE.HUE] validate: Not within schedule, not activating scene")
return False
log("[SCRIPT.SERVICE.HUE] validate: Activating scene for AUDIO")
return True
4 changes: 2 additions & 2 deletions script.service.hue/resources/lib/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ def reload_settings(self):
self._validate_ambilight()

def _validate_ambilight(self):
log(f"[SCRIPT.SERVICE.HUE] Validate ambilight config. Enabled: {self.group3_enabled}, Lights: {self.group3_lights}")
log(f"[SCRIPT.SERVICE.HUE] Validate ambilight config. Enabled: {self.group3_enabled}, Lights: {type(self.group3_lights)} : {self.group3_lights}")
if self.group3_enabled:
if self.group3_lights == '-1':
if self.group3_lights == ["-1"]:
ADDON.setSettingBool('group3_enabled', False)
log('[SCRIPT.SERVICE.HUE] _validate_ambilights: No ambilights selected')
notification(_('Hue Service'), _('No lights selected for Ambilight.'), icon=xbmcgui.NOTIFICATION_ERROR)
Expand Down

0 comments on commit 5e35c26

Please sign in to comment.