From d71e3c8b6cbf1a0530951d2a87cb513d790688ae Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Sun, 30 Sep 2018 22:43:30 +0200 Subject: [PATCH 01/19] Basic support for HmIP-MOD-TM --- pyhomematic/devicetypes/actors.py | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index a46852fa0..13366501a 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -412,6 +412,37 @@ def __init__(self, device_description, proxy, resolveparamsets=False): "PRESS_LONG": [1, 2]}) +class IPGarage(GenericSwitch, HMSensor): + """ + HmIP-MOD-TM Garage actor + """ + def __init__(self, device_description, proxy, resolveparamsets=False): + super().__init__(device_description, proxy, resolveparamsets) + + # init metadata + self.SENSORNODE.update({"DOOR_STATE": [self.ELEMENT]}) + + def move_up(self): + """Opens the garage""" + return self.setValue("DOOR_COMMAND", 1) + + def stop(self): + """Stop motion""" + return self.setValue("DOOR_COMMAND", 2) + + def move_down(self): + """Close the garage""" + return self.setValue("DOOR_COMMAND", 3) + + def vent(self): + """Go to ventilation position""" + return self.setValue("DOOR_COMMAND", 4) + + @property + def ELEMENT(self): + return [1] + + DEVICETYPES = { "HM-LC-Bl1-SM": Blind, "HM-LC-Bl1-SM-2": Blind, @@ -541,4 +572,5 @@ def __init__(self, device_description, proxy, resolveparamsets=False): "HM-Sen-RD-O": Rain, "ST6-SH": EcoLogic, "HM-Sec-Sir-WM": RFSiren, + "HmIP-MOD-TM": IPGarage, } From fffe6287a7379d9137e8c62e21341c0cb46da77f Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Sun, 14 Oct 2018 15:04:10 +0200 Subject: [PATCH 02/19] Bump version --- changelog.txt | 2 ++ setup.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 481ca572b..c7c018312 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,5 @@ +Version 0.1.52 (2018-) + Version 0.1.51 (2018-10-14) - Added device_descriptions.json to distributed package to allow performing tests with vccu - Add valve level to IP Thermostat attributes @hanzoh diff --git a/setup.py b/setup.py index 5fc157f7f..90b7827c8 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def readme(): PACKAGE_NAME = 'pyhomematic' HERE = os.path.abspath(os.path.dirname(__file__)) -VERSION = '0.1.51' +VERSION = '0.1.52' PACKAGES = find_packages(exclude=['tests', 'tests.*', 'dist', 'build']) From dbf090540852120ccd3494f049013e7f004d940c Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Fri, 19 Oct 2018 16:23:21 +0200 Subject: [PATCH 03/19] Add upper case HMIP-WTH-2, fixes #110 --- pyhomematic/devicetypes/thermostats.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyhomematic/devicetypes/thermostats.py b/pyhomematic/devicetypes/thermostats.py index 9734fb992..750dde07c 100644 --- a/pyhomematic/devicetypes/thermostats.py +++ b/pyhomematic/devicetypes/thermostats.py @@ -354,6 +354,7 @@ def turnoff(self): "HmIP-STHD": IPThermostatWall, "HmIP-STH": IPThermostatWall, "HmIP-WTH-2": IPThermostatWall, + "HMIP-WTH-2": IPThermostatWall, "HMIP-WTH": IPThermostatWall, "HmIP-WTH": IPThermostatWall, "HmIP-BWTH": IPThermostatWall230V, From 2a0f865b2e081ea23c547c9e22c3873a7bb2072c Mon Sep 17 00:00:00 2001 From: chr1st1ank Date: Thu, 1 Nov 2018 02:00:05 +0100 Subject: [PATCH 04/19] Implemented class RGBEffectLight and assigned device "HM-LC-RGBW-WM" --- pyhomematic/devicetypes/actors.py | 79 +++++++++++++++++++ .../devicetypes/json/device_details.json | 2 +- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index a46852fa0..810bf4c07 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -1,4 +1,5 @@ import logging +import colorsys from pyhomematic.devicetypes.generic import HMDevice from pyhomematic.devicetypes.sensors import HMSensor from pyhomematic.devicetypes.misc import HMEvent @@ -412,6 +413,83 @@ def __init__(self, device_description, proxy, resolveparamsets=False): "PRESS_LONG": [1, 2]}) +class RGBLight(Dimmer): + """ + Color light with dimmer function. + """ + _color_channel = 2 + + def get_color(self): + """ + Return the color of the light. + + Returns (red, green, blue) tuple with values in range of 0-255, representing an RGB color value. + """ + hsv = self.getValue(key="COLOR", channel=self._color_channel) + if hsv >= 0 and hsv < 200: + # HSV color: Convert to RGB + return tuple([v*255 for v in colorsys.hsv_to_rgb(hsv/199,1,1)]) + elif hsv >= 200: + # 200 is a special case (white). + # Larger values are undefined. For the sake of robustness we return "white" anyway. + return (255,255,255) + + def set_color(self, red: int, green: int, blue: int): + """ + Set a fixed color and also turn off effects in order to see the color. + + :param red: red color component in range of 0-255 + :param g: green color component in range of 0-255 + :param b: blue color component in range of 0-255 + """ + hsv = 200 + + # Convert to list and truncate to allowed range 0-255 + rgb = [min(max(v, 0), 255)/255.0 for v in (red,green,blue)] + + if sum(rgb) < 765: # = not all colors have value 255 + hsv = round(colorsys.rgb_to_hsv(*rgb)[0] * 199) + + return self.setValue(key="COLOR", channel=self._color_channel, value=int(hsv)) + + +class RGBEffectLight(RGBLight): + """ + Color light with dimmer function and color effects. + """ + _effect_channel = 3 + _light_effect_list = ['Off', 'Slow color change', 'Medium color change', 'Fast color change', 'Campfire', + 'Waterfall', 'TV simulation'] + + def get_effect_list(self) -> list: + """Return the list of supported effects.""" + return _light_effect_list + + def get_effect(self) -> str: + """Return the current color change program of the light.""" + effect_value = self.getValue(key="PROGRAM", channel=self._effect_channel) + try: + return self._light_effect_list[effect_value] + except IndexError: + LOG.error("Unexpected color effect returned by CCU") + return "Unknown" + + def set_effect(self, effect_name: str): + """Sets the color change program of the light.""" + try: + effect_index = self._light_effect_list.index(effect_name) + except ValueError: + LOG.error(f"Trying to set unknown light effect '{effect_name}'") + return False + + return self.setValue(key="PROGRAM", channel=self._effect_channel, value=effect_index) + + def set_color(self, *args, **kwargs): + """Overloading parent's function in order to turn off the color effects.""" + self.set_effect(self._light_effect_list[0]) + super(RGBEffectLight, self).set_color(*args, **kwargs) + + DEVICETYPES = { "HM-LC-Bl1-SM": Blind, "HM-LC-Bl1-SM-2": Blind, @@ -541,4 +619,5 @@ def __init__(self, device_description, proxy, resolveparamsets=False): "HM-Sen-RD-O": Rain, "ST6-SH": EcoLogic, "HM-Sec-Sir-WM": RFSiren, + "HM-LC-RGBW-WM": RGBEffectLight, } diff --git a/pyhomematic/devicetypes/json/device_details.json b/pyhomematic/devicetypes/json/device_details.json index f6a8fea5a..76e6c1f0b 100644 --- a/pyhomematic/devicetypes/json/device_details.json +++ b/pyhomematic/devicetypes/json/device_details.json @@ -61,7 +61,7 @@ "HM-RC-4-3": {}, "HM-Sec-SD-2-Team": {"supported": false}, "HmIP-FSM16": {"supported": false}, - "HM-LC-RGBW-WM": {"supported": false}, + "HM-LC-RGBW-WM": {}, "HMIP-WTH": {}, "HmIP-BSM": {}, "HmIP-SAM": {}, From 847986f44db8f55c178454a5906004d3da1a4921 Mon Sep 17 00:00:00 2001 From: chr1st1ank Date: Thu, 1 Nov 2018 02:44:50 +0100 Subject: [PATCH 05/19] Code formatting --- pyhomematic/devicetypes/actors.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index 810bf4c07..3f9432066 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -62,6 +62,7 @@ def __init__(self, device_description, proxy, resolveparamsets=False): def ELEMENT(self): return [3] + class IPKeyBlind(KeyBlind): """ Blind switch that raises and lowers homematic ip roller shutters or window blinds. @@ -71,6 +72,7 @@ class IPKeyBlind(KeyBlind): def ELEMENT(self): return [4] + class IPKeyBlindTilt(IPKeyBlind, HelperActorBlindTilt): def close_slats(self, channel=None): @@ -117,6 +119,7 @@ def __init__(self, device_description, proxy, resolveparamsets=False): # init metadata self.EVENTNODE.update({"PRESS_SHORT": [1, 2], "PRESS_LONG_RELEASE": [1, 2]}) + @property def ELEMENT(self): return [3] @@ -132,6 +135,7 @@ def __init__(self, device_description, proxy, resolveparamsets=False): # init metadata self.EVENTNODE.update({"PRESS_SHORT": [1, 2], "PRESS_LONG_RELEASE": [1, 2]}) + @property def ELEMENT(self): return [4] @@ -261,8 +265,8 @@ def __init__(self, device_description, proxy, resolveparamsets=False): # init metadata self.BINARYNODE.update({"STATE": self._dic}) - self.SENSORNODE.update({"FREQUENCY": self._fic, # mHz, from 0.0 to 350000.0 - "VALUE": self._aic}) # No specific unit, float from 0.0 to 1000.0 + self.SENSORNODE.update({"FREQUENCY": self._fic, # mHz, from 0.0 to 350000.0 + "VALUE": self._aic}) # No specific unit, float from 0.0 to 1000.0 @property def ELEMENT(self): @@ -426,26 +430,26 @@ def get_color(self): Returns (red, green, blue) tuple with values in range of 0-255, representing an RGB color value. """ hsv = self.getValue(key="COLOR", channel=self._color_channel) - if hsv >= 0 and hsv < 200: + if 0 <= hsv < 200: # HSV color: Convert to RGB - return tuple([v*255 for v in colorsys.hsv_to_rgb(hsv/199,1,1)]) + return tuple([v*255 for v in colorsys.hsv_to_rgb(hsv/199, 1, 1)]) elif hsv >= 200: # 200 is a special case (white). # Larger values are undefined. For the sake of robustness we return "white" anyway. - return (255,255,255) + return 255, 255, 255 def set_color(self, red: int, green: int, blue: int): """ Set a fixed color and also turn off effects in order to see the color. :param red: red color component in range of 0-255 - :param g: green color component in range of 0-255 - :param b: blue color component in range of 0-255 + :param green: green color component in range of 0-255 + :param blue: blue color component in range of 0-255 """ hsv = 200 # Convert to list and truncate to allowed range 0-255 - rgb = [min(max(v, 0), 255)/255.0 for v in (red,green,blue)] + rgb = [min(max(v, 0), 255)/255.0 for v in (red, green, blue)] if sum(rgb) < 765: # = not all colors have value 255 hsv = round(colorsys.rgb_to_hsv(*rgb)[0] * 199) @@ -463,7 +467,7 @@ class RGBEffectLight(RGBLight): def get_effect_list(self) -> list: """Return the list of supported effects.""" - return _light_effect_list + return self._light_effect_list def get_effect(self) -> str: """Return the current color change program of the light.""" @@ -479,7 +483,7 @@ def set_effect(self, effect_name: str): try: effect_index = self._light_effect_list.index(effect_name) except ValueError: - LOG.error(f"Trying to set unknown light effect '{effect_name}'") + LOG.error("Trying to set unknown light effect") return False return self.setValue(key="PROGRAM", channel=self._effect_channel, value=effect_index) From 416b8f21ba152b2a623bd42e9e79bf51fd9472d7 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 1 Nov 2018 16:30:31 +0100 Subject: [PATCH 06/19] Fix node --- pyhomematic/devicetypes/actors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index 13366501a..9f43d556a 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -420,7 +420,7 @@ def __init__(self, device_description, proxy, resolveparamsets=False): super().__init__(device_description, proxy, resolveparamsets) # init metadata - self.SENSORNODE.update({"DOOR_STATE": [self.ELEMENT]}) + self.SENSORNODE.update({"DOOR_STATE": self.ELEMENT}) def move_up(self): """Opens the garage""" From 3584bf4b9f6dbdd9927ce0669f7219cbb97638a5 Mon Sep 17 00:00:00 2001 From: chr1st1ank Date: Sat, 3 Nov 2018 02:06:29 +0100 Subject: [PATCH 07/19] Removed class RGBLight and put everything into RGBEffectLight. --- pyhomematic/devicetypes/actors.py | 36 +++++++++++++------------------ 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index 3f9432066..4cd947c80 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -417,11 +417,14 @@ def __init__(self, device_description, proxy, resolveparamsets=False): "PRESS_LONG": [1, 2]}) -class RGBLight(Dimmer): +class RGBEffectLight(Dimmer): """ - Color light with dimmer function. + Color light with dimmer function and color effects. """ _color_channel = 2 + _effect_channel = 3 + _light_effect_list = ['Off', 'Slow color change', 'Medium color change', 'Fast color change', 'Campfire', + 'Waterfall', 'TV simulation'] def get_color(self): """ @@ -446,24 +449,17 @@ def set_color(self, red: int, green: int, blue: int): :param green: green color component in range of 0-255 :param blue: blue color component in range of 0-255 """ - hsv = 200 + if sum((red, green, blue)) < 765: + # Truncate to allowed range 0-255 + rgb = [min(max(v, 0), 255)/255.0 for v in (red, green, blue)] - # Convert to list and truncate to allowed range 0-255 - rgb = [min(max(v, 0), 255)/255.0 for v in (red, green, blue)] - - if sum(rgb) < 765: # = not all colors have value 255 + # Calculate HSV color from RGB color hsv = round(colorsys.rgb_to_hsv(*rgb)[0] * 199) + else: + # All colors have value 255 => white + hsv = 200 - return self.setValue(key="COLOR", channel=self._color_channel, value=int(hsv)) - - -class RGBEffectLight(RGBLight): - """ - Color light with dimmer function and color effects. - """ - _effect_channel = 3 - _light_effect_list = ['Off', 'Slow color change', 'Medium color change', 'Fast color change', 'Campfire', - 'Waterfall', 'TV simulation'] + return self.turn_off_effect and self.setValue(key="COLOR", channel=self._color_channel, value=int(hsv)) def get_effect_list(self) -> list: """Return the list of supported effects.""" @@ -488,10 +484,8 @@ def set_effect(self, effect_name: str): return self.setValue(key="PROGRAM", channel=self._effect_channel, value=effect_index) - def set_color(self, *args, **kwargs): - """Overloading parent's function in order to turn off the color effects.""" - self.set_effect(self._light_effect_list[0]) - super(RGBEffectLight, self).set_color(*args, **kwargs) + def turn_off_effect(self): + return self.set_effect(self._light_effect_list[0]) DEVICETYPES = { From 8ea0fc38d4cc2517741b0c6ae1b77bc83c54694c Mon Sep 17 00:00:00 2001 From: chr1st1ank Date: Wed, 7 Nov 2018 23:58:12 +0100 Subject: [PATCH 08/19] Refactored from rgb color model to HS(V) color model. --- pyhomematic/devicetypes/actors.py | 51 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index 4cd947c80..769fe65e3 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -1,5 +1,5 @@ import logging -import colorsys + from pyhomematic.devicetypes.generic import HMDevice from pyhomematic.devicetypes.sensors import HMSensor from pyhomematic.devicetypes.misc import HMEvent @@ -417,7 +417,7 @@ def __init__(self, device_description, proxy, resolveparamsets=False): "PRESS_LONG": [1, 2]}) -class RGBEffectLight(Dimmer): +class ColorEffectLight(Dimmer): """ Color light with dimmer function and color effects. """ @@ -426,40 +426,41 @@ class RGBEffectLight(Dimmer): _light_effect_list = ['Off', 'Slow color change', 'Medium color change', 'Fast color change', 'Campfire', 'Waterfall', 'TV simulation'] - def get_color(self): + def get_hs_color(self): """ - Return the color of the light. + Return the color of the light as HSV color without the "value" component for the brightness. - Returns (red, green, blue) tuple with values in range of 0-255, representing an RGB color value. + Returns (hue, saturation) tuple with values in range of 0-1, representing the H and S component of the + HSV color system. """ - hsv = self.getValue(key="COLOR", channel=self._color_channel) - if 0 <= hsv < 200: - # HSV color: Convert to RGB - return tuple([v*255 for v in colorsys.hsv_to_rgb(hsv/199, 1, 1)]) - elif hsv >= 200: - # 200 is a special case (white). + # Get the color from homematic. In general this is just the hue parameter. + hm_color = self.getValue(key="COLOR", channel=self._color_channel) + if hm_color >= 200: + # 200 is a special case (white), so we have a saturation of 0. # Larger values are undefined. For the sake of robustness we return "white" anyway. - return 255, 255, 255 + return 0, 0 + + # For all other colors we assume saturation of 1 + return hm_color/200, 1 - def set_color(self, red: int, green: int, blue: int): + def set_hs_color(self, hue: float, saturation: float): """ Set a fixed color and also turn off effects in order to see the color. - :param red: red color component in range of 0-255 - :param green: green color component in range of 0-255 - :param blue: blue color component in range of 0-255 + :param hue: Hue component (range 0-1) + :param saturation: Saturation component (range 0-1). Yields white for values near 0, other values are + interpreted as 100% saturation. + + The input values are the components of an HSV color without the value/brightness component. """ - if sum((red, green, blue)) < 765: - # Truncate to allowed range 0-255 - rgb = [min(max(v, 0), 255)/255.0 for v in (red, green, blue)] + self.turn_off_effect() - # Calculate HSV color from RGB color - hsv = round(colorsys.rgb_to_hsv(*rgb)[0] * 199) + if saturation < 0.1: # Special case (white) + hm_color = 200 else: - # All colors have value 255 => white - hsv = 200 + hm_color = int(round(max(min(hue, 1), 0) * 199)) - return self.turn_off_effect and self.setValue(key="COLOR", channel=self._color_channel, value=int(hsv)) + self.setValue(key="COLOR", channel=self._color_channel, value=hm_color) def get_effect_list(self) -> list: """Return the list of supported effects.""" @@ -617,5 +618,5 @@ def turn_off_effect(self): "HM-Sen-RD-O": Rain, "ST6-SH": EcoLogic, "HM-Sec-Sir-WM": RFSiren, - "HM-LC-RGBW-WM": RGBEffectLight, + "HM-LC-RGBW-WM": ColorEffectLight, } From df5634229bd35d030dffdf6047c46c6242843a7e Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 8 Nov 2018 22:55:12 +0100 Subject: [PATCH 09/19] Possible fix for HmIP-MOD-TM --- pyhomematic/devicetypes/actors.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index 9f43d556a..aea3d056b 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -420,27 +420,27 @@ def __init__(self, device_description, proxy, resolveparamsets=False): super().__init__(device_description, proxy, resolveparamsets) # init metadata - self.SENSORNODE.update({"DOOR_STATE": self.ELEMENT}) + self.SENSORNODE.update({"DOOR_STATE": [1]}) def move_up(self): """Opens the garage""" - return self.setValue("DOOR_COMMAND", 1) + return self.setValue("DOOR_COMMAND", 1, channel=1), def stop(self): """Stop motion""" - return self.setValue("DOOR_COMMAND", 2) + return self.setValue("DOOR_COMMAND", 2, channel=1) def move_down(self): """Close the garage""" - return self.setValue("DOOR_COMMAND", 3) + return self.setValue("DOOR_COMMAND", 3, channel=1) def vent(self): """Go to ventilation position""" - return self.setValue("DOOR_COMMAND", 4) + return self.setValue("DOOR_COMMAND", 4, channel=1) @property def ELEMENT(self): - return [1] + return [2] DEVICETYPES = { From 20666e7b5baac9c3b4233592317d262cddfaec31 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 8 Nov 2018 22:59:26 +0100 Subject: [PATCH 10/19] Syntax --- pyhomematic/devicetypes/actors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index aea3d056b..d05d74c32 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -424,7 +424,7 @@ def __init__(self, device_description, proxy, resolveparamsets=False): def move_up(self): """Opens the garage""" - return self.setValue("DOOR_COMMAND", 1, channel=1), + return self.setValue("DOOR_COMMAND", 1, channel=1) def stop(self): """Stop motion""" From e649105ba5f3a8441ee94fc8f20f46f973329880 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Sat, 10 Nov 2018 21:49:16 +0100 Subject: [PATCH 11/19] Update changelog.txt --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index c7c018312..d58724b07 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,5 @@ Version 0.1.52 (2018-) +- Added HmIP-MOD-TM (Issue #170) @danielperna84 Version 0.1.51 (2018-10-14) - Added device_descriptions.json to distributed package to allow performing tests with vccu From f1e3c67a14121ba32c878823dd5194ebba69bb9e Mon Sep 17 00:00:00 2001 From: chr1st1ank Date: Sun, 11 Nov 2018 18:22:17 +0100 Subject: [PATCH 12/19] Value caching for HMGeneric. Used by RGBEffectLight and to replace HMGeneric._unreach attribute. --- .gitignore | 4 ++- pyhomematic/devicetypes/actors.py | 18 +++++++++++-- pyhomematic/devicetypes/generic.py | 41 ++++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 8d75914c7..fcfb2410f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ MANIFEST # Tox stuff, lint /.tox/ -/.pylint.d/ \ No newline at end of file +/.pylint.d/ +/local/ +Dockerfile \ No newline at end of file diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index 769fe65e3..9ccbdf63d 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -421,11 +421,18 @@ class ColorEffectLight(Dimmer): """ Color light with dimmer function and color effects. """ + _level_channel = 1 _color_channel = 2 _effect_channel = 3 _light_effect_list = ['Off', 'Slow color change', 'Medium color change', 'Fast color change', 'Campfire', 'Waterfall', 'TV simulation'] + def __init__(self, device_description, proxy, resolveparamsets=False): + super().__init__(device_description, proxy, resolveparamsets) + + # init metadata + self.WRITENODE.update({"COLOR": [self._color_channel], "PROGRAM": [self._effect_channel]}) + def get_hs_color(self): """ Return the color of the light as HSV color without the "value" component for the brightness. @@ -434,7 +441,8 @@ def get_hs_color(self): HSV color system. """ # Get the color from homematic. In general this is just the hue parameter. - hm_color = self.getValue(key="COLOR", channel=self._color_channel) + hm_color = self.getCachedOrUpdatedValue("COLOR", channel=self._color_channel) + if hm_color >= 200: # 200 is a special case (white), so we have a saturation of 0. # Larger values are undefined. For the sake of robustness we return "white" anyway. @@ -452,6 +460,11 @@ def set_hs_color(self, hue: float, saturation: float): interpreted as 100% saturation. The input values are the components of an HSV color without the value/brightness component. + Example colors: + * Green: set_hs_color(120/360, 1) + * Blue: set_hs_color(240/360, 1) + * Yellow: set_hs_color(60/360, 1) + * White: set_hs_color(0, 0) """ self.turn_off_effect() @@ -468,7 +481,8 @@ def get_effect_list(self) -> list: def get_effect(self) -> str: """Return the current color change program of the light.""" - effect_value = self.getValue(key="PROGRAM", channel=self._effect_channel) + effect_value = self.getCachedOrUpdatedValue("PROGRAM", channel=self._effect_channel) + try: return self._light_effect_list[effect_value] except IndexError: diff --git a/pyhomematic/devicetypes/generic.py b/pyhomematic/devicetypes/generic.py index 1157aa500..2d0b87d9d 100644 --- a/pyhomematic/devicetypes/generic.py +++ b/pyhomematic/devicetypes/generic.py @@ -27,8 +27,8 @@ def __init__(self, device_description, proxy, resolveparamsets): self._proxy = proxy self._paramsets = {} self._eventcallbacks = [] - self._unreach = None self._name = None + self._VALUES = {} # Dictionary to cache values. They are updated in the event() function. @property def ADDRESS(self): @@ -57,8 +57,9 @@ def event(self, interface_id, key, value): LOG.info( "HMGeneric.event: address=%s, interface_id=%s, key=%s, value=%s" % (self._ADDRESS, interface_id, key, value)) - if key == PARAM_UNREACH: - self._unreach = value + + self._VALUES[key] = value # Cache the value + for callback in self._eventcallbacks: LOG.debug("HMDevice.event: Using callback %s " % str(callback)) callback(self._ADDRESS, interface_id, key, value) @@ -86,7 +87,7 @@ def updateParamset(self, paramset): self._paramsets[paramset] = returnset if self.PARAMSETS: if self.PARAMSETS.get(PARAMSET_VALUES): - self._unreach = self.PARAMSETS.get(PARAMSET_VALUES).get(PARAM_UNREACH) + self._VALUES[PARAM_UNREACH] = self.PARAMSETS.get(PARAMSET_VALUES).get(PARAM_UNREACH) return True return False except Exception as err: @@ -154,6 +155,16 @@ def __init__(self, device_description, proxy, resolveparamsets=False): if resolveparamsets: self.updateParamsets() + def getCachedOrUpdatedValue(self, key): + """ Gets the device's value with the given key. + + If the key is not found in the cache, the value is queried from the host. + """ + try: + return self._VALUES[key] + except KeyError: + return self.getValue(key) + @property def PARENT(self): return self._PARENT @@ -161,9 +172,7 @@ def PARENT(self): @property def UNREACH(self): """ Returns true if children is not reachable """ - if self._unreach: - return True - return False + return bool(self._VALUES.get(PARAM_UNREACH, False)) def setEventCallback(self, callback): """ @@ -193,6 +202,7 @@ def getValue(self, key): LOG.debug("HMGeneric.getValue: address = '%s', key = '%s'" % (self._ADDRESS, key)) try: returnvalue = self._proxy.getValue(self._ADDRESS, key) + self._VALUES[key] = returnvalue return returnvalue except Exception as err: LOG.error("HMGeneric.getValue: %s on %s Exception: %s", key, @@ -240,10 +250,25 @@ def __init__(self, device_description, proxy, resolveparamsets=False): self._UPDATABLE = device_description.get('UPDATABLE') self._PARENT_TYPE = None + def getCachedOrUpdatedValue(self, key, channel=None): + """ Gets the channel's value with the given key. + + If the key is not found in the cache, the value is queried from the host. + If 'channel' is given, the respective channel's value is returned. + """ + if channel: + return self._hmchannels[channel].getCachedOrUpdatedValue(key) + + try: + return self._VALUES[key] + except KeyError: + value = self._VALUES[key] = self.getValue(key) + return value + @property def UNREACH(self): """ Returns true if the device or any children is not reachable """ - if self._unreach: + if self._VALUES.get(PARAM_UNREACH, False): return True else: for device in self._hmchannels.values(): From 0a6c21af520fc7341ea350aa384fe54f66a45825 Mon Sep 17 00:00:00 2001 From: chr1st1ank Date: Mon, 12 Nov 2018 07:50:28 +0100 Subject: [PATCH 13/19] Set default value of PARAM_UNREACH to None again. Reverted .gitignore to original state. --- .gitignore | 4 +--- pyhomematic/devicetypes/generic.py | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index fcfb2410f..8d75914c7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,4 @@ MANIFEST # Tox stuff, lint /.tox/ -/.pylint.d/ -/local/ -Dockerfile \ No newline at end of file +/.pylint.d/ \ No newline at end of file diff --git a/pyhomematic/devicetypes/generic.py b/pyhomematic/devicetypes/generic.py index 2d0b87d9d..77e7d0775 100644 --- a/pyhomematic/devicetypes/generic.py +++ b/pyhomematic/devicetypes/generic.py @@ -29,6 +29,7 @@ def __init__(self, device_description, proxy, resolveparamsets): self._eventcallbacks = [] self._name = None self._VALUES = {} # Dictionary to cache values. They are updated in the event() function. + self._VALUES[PARAM_UNREACH] = None @property def ADDRESS(self): From 2ac718ab95c6a11d4a776353611b37cbf27dc4a9 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Tue, 13 Nov 2018 01:11:26 +0100 Subject: [PATCH 14/19] Update changelog.txt --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index d58724b07..4fedc7d89 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,6 @@ Version 0.1.52 (2018-) - Added HmIP-MOD-TM (Issue #170) @danielperna84 +- Added HM-LC-RGBW-WM @chr1st1ank Version 0.1.51 (2018-10-14) - Added device_descriptions.json to distributed package to allow performing tests with vccu From 4802d7cca860c2f1497b9c2e5617d7efd6c2bd33 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Tue, 13 Nov 2018 01:12:09 +0100 Subject: [PATCH 15/19] Update changelog.txt --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 4fedc7d89..956af34c1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,7 @@ Version 0.1.52 (2018-) - Added HmIP-MOD-TM (Issue #170) @danielperna84 - Added HM-LC-RGBW-WM @chr1st1ank +- Added caching of parameters received by events @chr1st1ank Version 0.1.51 (2018-10-14) - Added device_descriptions.json to distributed package to allow performing tests with vccu From 57c37e6d1b15e6106e6b3ab1b8f7f78349cbad37 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Tue, 13 Nov 2018 22:51:07 +0100 Subject: [PATCH 16/19] Added HmIP-PCBS, fixes #178 --- changelog.txt | 1 + pyhomematic/devicetypes/actors.py | 1 + 2 files changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index 956af34c1..51917aef1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,7 @@ Version 0.1.52 (2018-) - Added HmIP-MOD-TM (Issue #170) @danielperna84 - Added HM-LC-RGBW-WM @chr1st1ank - Added caching of parameters received by events @chr1st1ank +- Added HmIP-PCBS (Issue #178) @danielperna84 Version 0.1.51 (2018-10-14) - Added device_descriptions.json to distributed package to allow performing tests with vccu diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index 006cccfb0..aa262b202 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -647,6 +647,7 @@ def turn_off_effect(self): "HMW-LC-Dim1L-DR": KeyDimmer, "HMIP-PS": IPSwitch, "HmIP-PS": IPSwitch, + "HmIP-PCBS": IPSwitch, "HMIP-PSM": IPSwitchPowermeter, "HmIP-PSM": IPSwitchPowermeter, "HmIP-PSM-CH": IPSwitchPowermeter, From 0e6017633ad670a9b780180b67b77ceb6ee32c71 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Tue, 13 Nov 2018 23:06:24 +0100 Subject: [PATCH 17/19] Fix HM-CC-TC obsolete battery, fixes #183 --- changelog.txt | 1 + pyhomematic/devicetypes/thermostats.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 51917aef1..9e0f3aaf4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,7 @@ Version 0.1.52 (2018-) - Added HM-LC-RGBW-WM @chr1st1ank - Added caching of parameters received by events @chr1st1ank - Added HmIP-PCBS (Issue #178) @danielperna84 +- Fix HM-CC-TC obsolete battery (Issue #183) @danielperna84 Version 0.1.51 (2018-10-14) - Added device_descriptions.json to distributed package to allow performing tests with vccu diff --git a/pyhomematic/devicetypes/thermostats.py b/pyhomematic/devicetypes/thermostats.py index 750dde07c..d7ec0eeb5 100644 --- a/pyhomematic/devicetypes/thermostats.py +++ b/pyhomematic/devicetypes/thermostats.py @@ -164,7 +164,7 @@ def __init__(self, device_description, proxy, resolveparamsets=False): self.ATTRIBUTENODE.update({"CONTROL_MODE": [2], "BATTERY_STATE": [2]}) -class ThermostatWall2(HMThermostat, AreaThermostat, HelperLowBat): +class ThermostatWall2(HMThermostat, AreaThermostat): """ HM-CC-TC ClimateControl-Wall Thermostat that measures temperature and allows to set a target temperature or use some automatic mode. From 16e06bbd030efdec5e125d06da381c54ae30ba1e Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 15 Nov 2018 22:57:39 +0100 Subject: [PATCH 18/19] Added HmIP-PDT, fixes #181 --- changelog.txt | 1 + pyhomematic/devicetypes/actors.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 9e0f3aaf4..a3f7b00f9 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,7 @@ Version 0.1.52 (2018-) - Added caching of parameters received by events @chr1st1ank - Added HmIP-PCBS (Issue #178) @danielperna84 - Fix HM-CC-TC obsolete battery (Issue #183) @danielperna84 +- Added HmIP-PDT (Issue #181) @danielperna84 Version 0.1.51 (2018-10-14) - Added device_descriptions.json to distributed package to allow performing tests with vccu diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index aa262b202..7b4d2ccb7 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -124,10 +124,19 @@ def ELEMENT(self): return [3] -class IPKeyDimmer(GenericDimmer, HelperWorking, HelperActionPress): +class IPDimmer(GenericDimmer): """ IP Dimmer switch that controls level of light brightness. """ + @property + def ELEMENT(self): + return [3] + + +class IPKeyDimmer(GenericDimmer, HelperWorking, HelperActionPress): + """ + IP Dimmer with buttons switch that controls level of light brightness. + """ def __init__(self, device_description, proxy, resolveparamsets=False): super().__init__(device_description, proxy, resolveparamsets) @@ -656,6 +665,7 @@ def turn_off_effect(self): "HmIP-BSM": IPKeySwitchPowermeter, "HMIP-BDT": IPKeyDimmer, "HmIP-BDT": IPKeyDimmer, + "HmIP-PDT": IPDimmer, "HM-Sec-Key": KeyMatic, "HM-Sec-Key-S": KeyMatic, "HM-Sec-Key-O": KeyMatic, From 7262e4e529e8f194aaff7ae09c0380ec836114a1 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 15 Nov 2018 23:18:16 +0100 Subject: [PATCH 19/19] Updated changelog --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index a3f7b00f9..e9a35ae2b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,4 @@ -Version 0.1.52 (2018-) +Version 0.1.52 (2018-11-15) - Added HmIP-MOD-TM (Issue #170) @danielperna84 - Added HM-LC-RGBW-WM @chr1st1ank - Added caching of parameters received by events @chr1st1ank