Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[script.service.hue@matrix] 2.0.12 #2655

Merged
merged 1 commit into from
Oct 10, 2024
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
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
Loading