Skip to content
This repository has been archived by the owner on Feb 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #184 from danielperna84/devel
Browse files Browse the repository at this point in the history
0.1.52
  • Loading branch information
danielperna84 authored Nov 15, 2018
2 parents 5b6dcbc + 7262e4e commit 081b999
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 14 deletions.
8 changes: 8 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
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
- 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
- Add valve level to IP Thermostat attributes @hanzoh
Expand Down
140 changes: 137 additions & 3 deletions pyhomematic/devicetypes/actors.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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.
Expand All @@ -70,6 +71,7 @@ class IPKeyBlind(KeyBlind):
def ELEMENT(self):
return [4]


class IPKeyBlindTilt(IPKeyBlind, HelperActorBlindTilt):

def close_slats(self, channel=None):
Expand Down Expand Up @@ -116,21 +118,32 @@ 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]


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)

# init metadata
self.EVENTNODE.update({"PRESS_SHORT": [1, 2],
"PRESS_LONG_RELEASE": [1, 2]})

@property
def ELEMENT(self):
return [4]
Expand Down Expand Up @@ -260,8 +273,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):
Expand Down Expand Up @@ -412,6 +425,123 @@ 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": [1]})

def move_up(self):
"""Opens the garage"""
return self.setValue("DOOR_COMMAND", 1, channel=1)

def stop(self):
"""Stop motion"""
return self.setValue("DOOR_COMMAND", 2, channel=1)

def move_down(self):
"""Close the garage"""
return self.setValue("DOOR_COMMAND", 3, channel=1)

def vent(self):
"""Go to ventilation position"""
return self.setValue("DOOR_COMMAND", 4, channel=1)

@property
def ELEMENT(self):
return [2]


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.
Returns (hue, saturation) tuple with values in range of 0-1, representing the H and S component of the
HSV color system.
"""
# Get the color from homematic. In general this is just the hue parameter.
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.
return 0, 0

# For all other colors we assume saturation of 1
return hm_color/200, 1

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 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.
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()

if saturation < 0.1: # Special case (white)
hm_color = 200
else:
hm_color = int(round(max(min(hue, 1), 0) * 199))

self.setValue(key="COLOR", channel=self._color_channel, value=hm_color)

def get_effect_list(self) -> list:
"""Return the list of supported effects."""
return self._light_effect_list

def get_effect(self) -> str:
"""Return the current color change program of the light."""
effect_value = self.getCachedOrUpdatedValue("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("Trying to set unknown light effect")
return False

return self.setValue(key="PROGRAM", channel=self._effect_channel, value=effect_index)

def turn_off_effect(self):
return self.set_effect(self._light_effect_list[0])


DEVICETYPES = {
"HM-LC-Bl1-SM": Blind,
"HM-LC-Bl1-SM-2": Blind,
Expand Down Expand Up @@ -526,6 +656,7 @@ def __init__(self, device_description, proxy, resolveparamsets=False):
"HMW-LC-Dim1L-DR": KeyDimmer,
"HMIP-PS": IPSwitch,
"HmIP-PS": IPSwitch,
"HmIP-PCBS": IPSwitch,
"HMIP-PSM": IPSwitchPowermeter,
"HmIP-PSM": IPSwitchPowermeter,
"HmIP-PSM-CH": IPSwitchPowermeter,
Expand All @@ -534,11 +665,14 @@ def __init__(self, device_description, proxy, resolveparamsets=False):
"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,
"HM-Sec-Key-Generic": KeyMatic,
"HM-Sen-RD-O": Rain,
"ST6-SH": EcoLogic,
"HM-Sec-Sir-WM": RFSiren,
"HmIP-MOD-TM": IPGarage,
"HM-LC-RGBW-WM": ColorEffectLight,
}
42 changes: 34 additions & 8 deletions pyhomematic/devicetypes/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ 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.
self._VALUES[PARAM_UNREACH] = None

@property
def ADDRESS(self):
Expand Down Expand Up @@ -57,8 +58,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)
Expand Down Expand Up @@ -86,7 +88,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:
Expand Down Expand Up @@ -154,16 +156,24 @@ 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

@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):
"""
Expand Down Expand Up @@ -193,6 +203,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,
Expand Down Expand Up @@ -240,10 +251,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():
Expand Down
2 changes: 1 addition & 1 deletion pyhomematic/devicetypes/json/device_details.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {},
Expand Down
3 changes: 2 additions & 1 deletion pyhomematic/devicetypes/thermostats.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])

Expand Down

0 comments on commit 081b999

Please sign in to comment.