Skip to content

Commit

Permalink
Expose minimum colour distance
Browse files Browse the repository at this point in the history
  • Loading branch information
Snapcase committed Jul 18, 2019
1 parent fc21e4a commit b7fbcf0
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 78 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,13 @@ Every selected light increases the number of necessary commands therefore influe
Performance logging can be enabled in the advanced setting to check the speed of the colour algorithm and Hue updates. However, these logs are very verbose and should be normally be disabled.

### Notes:

- [b]Hue Bridge V2 (Square) required[/b]
- Does not support multiple bridges on your network
- Only tested on LibreElec 9.0.2 & Windows 10, but no reason it shouldn't work anywhere.


### Problems?

- Make sure you update your Hue bridge to the latest version. This add-on assumes you have the latest features
- Turn on debug logging or the addon's logging (in addon_data)

Expand Down
1 change: 0 additions & 1 deletion script.service.hue/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
except ImportError:
logger.exception("Kodi Hue Remote Debug Error: You must add org.python.pydev.debug.pysrc to your PYTHONPATH, or disable DEBUG")


logger.info("Starting default.py, version {}, Kodi: {}".format(globals.ADDONVERSION, globals.KODIVERSION ))
try:
core.menu() #Run menu
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,13 @@ msgstr ""
msgctxt "#30071"
msgid "Only colour lights are supported"
msgstr ""

msgctxt "#30072"
msgid "Unsupported Hue Bridge"
msgstr ""

msgctxt "#30073"
msgid ""
"Hue Bridge V1 (Round) is unsupported. Hue Bridge V2 (Square) is required for"
" certain features."
msgstr ""
119 changes: 49 additions & 70 deletions script.service.hue/resources/lib/AmbiGroup.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
'''
Created on Jul. 2, 2019
@author: Zim514
'''

import time
# -*- coding: utf-8 -*-
from threading import Thread



from PIL import Image
from . import colorgram #https://github.com/obskyr/colorgram.py
from .rgbxy import Converter# https://github.com/benknight/hue-python-rgb-converter
from .rgbxy import ColorHelper
from .rgbxy import XYPoint
from .rgbxy import GamutA,GamutB,GamutC
from .rgbxy import Converter,ColorHelper# https://github.com/benknight/hue-python-rgb-converter
from .rgbxy import XYPoint, GamutA,GamutB,GamutC

from xbmc import RenderCapture
from xbmcgui import NOTIFICATION_WARNING
Expand All @@ -23,7 +13,6 @@
from resources.lib.KodiGroup import VIDEO,AUDIO,ALLMEDIA,STATE_IDLE,STATE_PAUSED,STATE_PLAYING
from .kodiHue import getLightGamut


from . import kodiutils
from .qhue import QhueException

Expand All @@ -32,6 +21,7 @@
from .recipes import HUE_RECIPES
from .language import get_string as _
from resources.lib.globals import timer
from sys import exc_info


class AmbiGroup(KodiGroup):
Expand Down Expand Up @@ -71,27 +61,29 @@ def onPlayBackPaused(self):
self.state = STATE_PAUSED


def readSettings(self):
def loadSettings(self):
logger.debug("AmbiGroup Load settings")

self.enabled=kodiutils.get_setting_as_bool("group{}_enabled".format(self.kgroupID))

self.updateInterval=kodiutils.get_setting_as_float("group{}_Interval".format(self.kgroupID)) /1000#
if self.updateInterval == 0:
self.updateInterval = 0.001

self.numColors=kodiutils.get_setting_as_int("group{}_NumColors".format(self.kgroupID))
self.transitionTime = kodiutils.get_setting_as_int("group{}_TransitionTime".format(self.kgroupID)) /100 #This is given as a multiple of 100ms and defaults to 4 (400ms). For example, setting transitiontime:10 will make the transition last 1 second.

self.forceOn=kodiutils.get_setting_as_bool("group{}_forceOn".format(self.kgroupID))
self.setBrightness=kodiutils.get_setting_as_bool("group{}_setBrightness".format(self.kgroupID))
self.brightness=kodiutils.get_setting_as_int("group{}_Brightness".format(self.kgroupID))*255/100#convert percentage to value 1-254
self.blackFilter=kodiutils.get_setting_as_int("group{}_BlackFilter".format(self.kgroupID))
self.whiteFilter=kodiutils.get_setting_as_int("group{}_WhiteFilter".format(self.kgroupID))
self.defaultRecipe=kodiutils.get_setting_as_int("group{}_DefaultRecipe".format(self.kgroupID))
self.captureSize=kodiutils.get_setting_as_int("group{}_CaptureSize".format(self.kgroupID))
self.minimumDistance=kodiutils.get_setting_as_float("group{}_ColorDifference".format(self.kgroupID)) / 10000 #

self.updateInterval=kodiutils.get_setting_as_float("group{}_Interval".format(self.kgroupID)) /1000#
if self.updateInterval == 0:
self.updateInterval = 0.001

self.ambiLights={}
lightIDs=kodiutils.get_setting("group{}_Lights".format(self.kgroupID)).split(",")

index=0
for L in lightIDs:
gamut=getLightGamut(self.bridge,L)
Expand All @@ -103,7 +95,6 @@ def readSettings(self):
def setup(self, monitor,bridge, kgroupID, flash=False, mediaType=VIDEO):

super(AmbiGroup,self).setup(bridge, kgroupID, flash=flash, mediaType=1)

self.monitor=monitor

try:
Expand All @@ -115,10 +106,6 @@ def setup(self, monitor,bridge, kgroupID, flash=False, mediaType=VIDEO):
logger.error("Exception: 0 update interval warning")
kodiutils.notification(_("Hue Service"), _("Recommended minimum update interval: 100ms").format(calls),time=5000,icon=NOTIFICATION_WARNING)
logger.debug("callsPerSec: lights: {},interval: {}, calls: {}".format(len(self.ambiLights),self.updateInterval,calls))


def _getColor(self):
pass


def _ambiLoop(self):
Expand All @@ -137,39 +124,37 @@ def _ambiLoop(self):

@timer
def _ambiUpdate(self,cap):
try:
cap.capture(self.captureSize, self.captureSize) #async capture request to underlying OS
capImage = cap.getImage(100) #timeout to wait for OS in ms, default 1000
image = Image.frombuffer("RGBA", (self.captureSize, self.captureSize), buffer(capImage), "raw", "BGRA")
except Exception as ex:
logger.exception("Capture exception")
return
colors = colorgram.extract(image,self.numColors)

if (colors[0].rgb.r < self.blackFilter and colors[0].rgb.g < self.blackFilter and colors[0].rgb.b <self.blackFilter) or \
(colors[0].rgb.r > self.whiteFilter and colors[0].rgb.g > self.whiteFilter and colors[0].rgb.b > self.whiteFilter):
#logger.debug("rgb filter: r,g,b: {},{},{}".format(colors[0].rgb.r,colors[0].rgb.g,colors[0].rgb.b))
xy=HUE_RECIPES[self.defaultRecipe]["xy"]
for L in self.ambiLights:
x = Thread(target=self._updateHueXY,name="updateHue", args=(xy,L,self.transitionTime))
x.daemon = True
x.start()
else:
for L in self.ambiLights:
if self.numColors == 1:
#logger.debug("AmbiUpdate 1 Color: r,g,b: {},{},{}".format(colors[0].rgb.r,colors[0].rgb.g,colors[0].rgb.b))
x = Thread(target=self._updateHueRGB,name="updateHue", args=(colors[0].rgb.r,colors[0].rgb.g,colors[0].rgb.b,L,self.transitionTime))
else:
colorIndex=self.ambiLights[L]["index"] % len(colors)
#logger.debug("AmbiUpdate Colors: {}".format(colors))
x = Thread(target=self._updateHueRGB,name="updateHue", args=(colors[colorIndex].rgb.r,colors[colorIndex].rgb.g,colors[colorIndex].rgb.b,L,self.transitionTime))
x.daemon = True
x.start()
try:
cap.capture(self.captureSize, self.captureSize) #async capture request to underlying OS
capImage = cap.getImage(100) #timeout to wait for OS in ms, default 1000
image = Image.frombuffer("RGBA", (self.captureSize, self.captureSize), buffer(capImage), "raw", "BGRA")
except Exception as ex:
logger.warning("Capture exception",exc_info=1)
return

colors = colorgram.extract(image,self.numColors)

if (colors[0].rgb.r < self.blackFilter and colors[0].rgb.g < self.blackFilter and colors[0].rgb.b <self.blackFilter) or \
(colors[0].rgb.r > self.whiteFilter and colors[0].rgb.g > self.whiteFilter and colors[0].rgb.b > self.whiteFilter):
#logger.debug("rgb filter: r,g,b: {},{},{}".format(colors[0].rgb.r,colors[0].rgb.g,colors[0].rgb.b))
xy=HUE_RECIPES[self.defaultRecipe]["xy"]
for L in self.ambiLights:
x = Thread(target=self._updateHueXY,name="updateHue", args=(xy,L,self.transitionTime))
x.daemon = True
x.start()
else:
for L in self.ambiLights:
if self.numColors == 1:
#logger.debug("AmbiUpdate 1 Color: r,g,b: {},{},{}".format(colors[0].rgb.r,colors[0].rgb.g,colors[0].rgb.b))
x = Thread(target=self._updateHueRGB,name="updateHue", args=(colors[0].rgb.r,colors[0].rgb.g,colors[0].rgb.b,L,self.transitionTime))
else:
colorIndex=self.ambiLights[L]["index"] % len(colors)
#logger.debug("AmbiUpdate Colors: {}".format(colors))
x = Thread(target=self._updateHueRGB,name="updateHue", args=(colors[colorIndex].rgb.r,colors[colorIndex].rgb.g,colors[colorIndex].rgb.b,L,self.transitionTime))
x.daemon = True
x.start()
@timer
def _updateHueRGB(self,r,g,b,light,transitionTime):
#startTime = time.time()

gamut=self.ambiLights[light].get('gamut')
prevxy=self.ambiLights[light].get('prevxy')

Expand All @@ -187,21 +172,18 @@ def _updateHueRGB(self,r,g,b,light,transitionTime):
xy=round(xy[0],4),round(xy[1],4) #Hue has a max precision of 4 decimal points.

distance=round(helper.get_distance_between_two_points(XYPoint(xy[0],xy[1]),XYPoint(prevxy[0],prevxy[1])) ,4)#only update hue if XY actually changed
if distance > 0:
if distance > self.minimumDistance:
try:
self.bridge.lights[light].state(xy=xy,transitiontime=transitionTime)
except QhueException as ex:
logger.exception("Ambi: Hue call fail:")


#endTime=time.time()
#logger.debug("time: {},distance: {}".format(int((endTime-startTime)*1000),distance))
else:
logger.debug("Distance too small: min: {}, current: {}".format(self.minimumDistance,distance))
self.ambiLights[light].update(prevxy=xy)



@timer
def _updateHueXY(self,xy,light,transitionTime):
#startTime = time.time()

gamut=self.ambiLights[light].get('gamut')
prevxy=self.ambiLights[light].get('prevxy')

Expand All @@ -211,18 +193,15 @@ def _updateHueXY(self,xy,light,transitionTime):
helper=ColorHelper(GamutB)
elif gamut == "C":
helper=ColorHelper(GamutC)


xy=(round(xy[0],4),round(xy[1],4)) #Hue has a max precision of 4 decimal points.

distance=round(helper.get_distance_between_two_points(XYPoint(xy[0],xy[1]),XYPoint(prevxy[0],prevxy[1])) ,4)#only update hue if XY actually changed
if distance > 0:
if distance > self.minimumDistance:
try:
self.bridge.lights[light].state(xy=xy,transitiontime=transitionTime)
except QhueException as ex:
logger.exception("Ambi: Hue call fail:")


#endTime=time.time()
#logger.debug("time: {},distance: {}".format(int((endTime-startTime)*1000),distance))
else:
logger.debug("Distance too small: min: {}, current: {}".format(self.minimumDistance,distance))
self.ambiLights[light].update(prevxy=xy)
6 changes: 3 additions & 3 deletions script.service.hue/resources/lib/KodiGroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ class KodiGroup(xbmc.Player):
def __init__(self):
super(xbmc.Player,self).__init__()

def readSettings(self):

def loadSettings(self):
logger.debug("KodiGroup Load settings")
self.enabled=get_setting_as_bool("group{}_enabled".format(self.kgroupID))

self.startBehavior=get_setting_as_bool("group{}_startBehavior".format(self.kgroupID))
Expand All @@ -58,7 +58,7 @@ def setup(self,bridge,kgroupID,flash = False, mediaType=VIDEO):
self.lights = bridge.lights
self.kgroupID=kgroupID

self.readSettings()
self.loadSettings()

self.groupResource=bridge.groups[0]

Expand Down
2 changes: 2 additions & 0 deletions script.service.hue/resources/lib/language.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,5 @@ def get_string(t):
_strings['recommended minimum update interval: 100ms'] = 30069
_strings['est. hue commands/sec (max 20): {}'] = 30070
_strings['only colour lights are supported'] = 30071
_strings['unsupported hue bridge'] = 30072
_strings['hue bridge v1 (round) is unsupported. hue bridge v2 (square) is required for certain features.'] = 30073
2 changes: 1 addition & 1 deletion script.service.hue/resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
<setting id="group3_Interval" type="slider" label="30065" default="100" range="0,100,1000" option="int" visible="eq(-14,true)" />
<setting id="group3_TransitionTime" type="slider" label="30066" default="100" range="0,100,500" option="int" visible="eq(-15,true)"/>
<setting id="group3_CaptureSize" type="slider" label="30067" default="150" range="50,50,500" option="int" visible="eq(-16,true)"/>
<setting id="group3_ColorDifference" type="slider" label="Minimum colour difference" default="150" range="0,0.0001,1" option="float" visible="eq(-17,true)"/>
<setting id="group3_ColorDifference" type="slider" label="Minimum colour difference" default="0" range="0,1,1000" option="int" visible="eq(-17,true)"/>

</category>
<!-- /GROUP SECTION 0-->
Expand Down
2 changes: 0 additions & 2 deletions script.service.hue/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@
import pydevd
pydevd.settrace('localhost', stdoutToServer=False, stderrToServer=False, suspend=globals.REMOTE_DBG_SUSPEND,
trace_only_current_thread=False, overwrite_prev_trace=True, patch_multiprocessing=True)

except ImportError:
logger.critical("Kodi Hue Remote Debug Error: You must add org.python.pydev.debug.pysrc to your PYTHONPATH, or disable DEBUG")


logger.info("Starting service.py, version {}, Kodi: {}".format(globals.ADDONVERSION, globals.KODIVERSION))
try:
core.service() #Run Hue service
Expand Down

0 comments on commit b7fbcf0

Please sign in to comment.