diff --git a/changelog.txt b/changelog.txt index 61fab9b9a..809f8017a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,13 @@ +Version 0.1.73 (2021-06-06) +- Add support for HmIPW-FALMOT-C12 @Knechtie +- Fix HmIP-STHD and HmIP-STH @yschroeder +- Add tilt-support for shutters @Knechtie +- Add support vor HmIP-MIO16-PCB @Knechtie +- Add HmIPW-STH @GagaPete +- Fix HmIP-STE2-PCB @danielperna84 +- Add port-offset 40000 (Issue #393) @danielperna84 +- Minor thermostat improvement as provided by @neffs + Version 0.1.72 (2021-03-14) - Disable TLS-check in JSON-RPC (Issue #372) @danielperna84 - Improve some HmIP-remotes @SukramJ diff --git a/pyhomematic/_hm.py b/pyhomematic/_hm.py index cb2c514e4..3a54c846c 100644 --- a/pyhomematic/_hm.py +++ b/pyhomematic/_hm.py @@ -378,6 +378,7 @@ def jsonRpcPost(self, host, jsonport, method, params={}, verify=False): LOG.debug("RPCFunctions.jsonRpcPost: API-Endpoint: %s" % apiendpoint) req = urllib.request.Request(apiendpoint, payload, headers) + # pylint: disable=consider-using-with resp = urllib.request.urlopen(req, context=ctx) if resp.status == 200: try: @@ -435,7 +436,9 @@ def addDeviceNames(self, remote): interface = False if response['error'] is None and response['result']: for i in response['result']: - if i['port'] in [self.remotes[remote]['port'], self.remotes[remote]['port'] + 30000]: + if i['port'] in [self.remotes[remote]['port'], + self.remotes[remote]['port'] + 30000, + self.remotes[remote]['port'] + 40000]: interface = i['name'] break LOG.debug( @@ -480,6 +483,7 @@ def addDeviceNames(self, remote): elif self.remotes[remote]['resolvenames'] == 'xml': LOG.warning("Resolving names with the XML-API addon will be disabled in a future release. Please switch to json.") try: + # pylint: disable=consider-using-with response = urllib.request.urlopen( "http://%s%s" % (self.remotes[remote]['ip'], XML_API_URL), timeout=5) device_list = response.read().decode("ISO-8859-1") diff --git a/pyhomematic/devicetypes/actors.py b/pyhomematic/devicetypes/actors.py index bdaea782a..00c8fd664 100644 --- a/pyhomematic/devicetypes/actors.py +++ b/pyhomematic/devicetypes/actors.py @@ -377,12 +377,28 @@ def ELEMENT(self): return [2, 6, 10] return [1] -class IPWKeyBlindMulti(KeyBlind, HelperDeviceTemperature, HelperWired): +class IPWKeyBlindMulti(KeyBlind, HelperActorBlindTilt,HelperDeviceTemperature, HelperWired): """ Multi-blind actor HmIPW-DRBL4 """ def __init__(self, device_description, proxy, resolveparamsets=False): super().__init__(device_description, proxy, resolveparamsets) + self._shutter_channels = [] + self._blind_channels = [] + + # Get Operation Mode for Input Channels + for chan in self.ELEMENT: + address_channel = "%s:%i" % (self._ADDRESS, chan -1) + try: + channel_paramset = self._proxy.getParamset(address_channel, "MASTER", 0) + channel_operation_mode = channel_paramset.get("CHANNEL_OPERATION_MODE") if "CHANNEL_OPERATION_MODE" in channel_paramset else 1 + if channel_operation_mode == 0: + self._blind_channels.append(chan) + self.WRITENODE.pop("LEVEL_2", None) + else: + self._shutter_channels.append(chan) + except Exception as err: + LOG.error("IPWKeyBlindMulti: Failure to determine channel mode of IPWKeyBlindMulti %s %s", address_channel, err) # init metadata self.ATTRIBUTENODE.update({"ACTIVITY_STATE": self.ELEMENT, @@ -391,6 +407,18 @@ def __init__(self, device_description, proxy, resolveparamsets=False): self.ACTIONNODE.update({"STOP": self.ELEMENT}) self.WRITENODE.update({"LEVEL": self.ELEMENT}) + if len(self._shutter_channels) > 0: + self.WRITENODE.update({"LEVEL_2": self._shutter_channels}) + self.SENSORNODE.update({"LEVEL_2": self._shutter_channels}) + + def close_slats(self, channel=None): + """Move the shutter up all the way.""" + self.set_cover_tilt_position(0.0, channel) + + def open_slats(self, channel=None): + """Move the shutter down all the way.""" + self.set_cover_tilt_position(1.0, channel) + @property def ELEMENT(self): return [2, 6, 10, 14] @@ -945,6 +973,51 @@ def set_color_temp(self, color_temp: float, channel=None): return self.setValue(key="LEVEL", channel=self._temp_channel, value=color_temp) + +class IPMultiIOPCB(GenericSwitch, HelperRssiDevice, HelperRssiPeer): + """HmIP-MIO16-PCB""" + + def __init__(self, device_description, proxy, resolveparamsets=False): + + # Input channels (analog inputs 0-12V) + self._aic = [1, 4, 7, 10] + # Input channels (digital low active inputs) + self._dic = [13, 14, 15, 16] + # Output channels + # CH18, CH22, CH26, CH30: relay outputs (24 V/0,5 A) + # CH34, CH38, CH42, CH46: open collector outputs (30 V/0,2 A) + self._doc = [18, 22, 26, 30, 34, 38, 42, 46] + + super().__init__(device_description, proxy, resolveparamsets) + + self._keypress_event_channels = [] + self._binarysensor_channels = [] + self._channel_operation_mode = 0 + + # Get Operation Mode for Input Channels + for chan in self._dic: + try: + self._channel_operation_mode = self._proxy.getParamset("%s:%i" % (self._ADDRESS, chan), "MASTER").get("CHANNEL_OPERATION_MODE", None) + + if self._channel_operation_mode == 1: + self._keypress_event_channels.append(chan) + elif self._channel_operation_mode != 0: + self._binarysensor_channels.append(chan) + except Exception as err: + LOG.error("IPMultiIOPCB: Failure to determine input channel operations mode of IPMultiIOPCB %s:%i %s", self._ADDRESS, chan, err) + + self.BINARYNODE.update({"STATE": self._binarysensor_channels}) + self.SENSORNODE.update({"VOLTAGE": self._aic}) + # button events not successfully implemented yet (SHORT_PRESS, LOMG_PRESS) + + def get_voltage(self, channel=None): + """Return analog input in V""" + return float(self.getSensorData("VOLTAGE", channel)) + + @property + def ELEMENT(self): + return self._doc + DEVICETYPES = { "HM-LC-Bl1-SM": Blind, "HM-LC-Bl1-SM-2": Blind, @@ -1111,4 +1184,5 @@ def set_color_temp(self, color_temp: float, channel=None): "HM-DW-WM": Dimmer, "HM-LC-DW-WM": ColdWarmDimmer, "HB-UNI-RGB-LED-CTRL": ColorEffectLight, + "HmIP-MIO16-PCB": IPMultiIOPCB, } diff --git a/pyhomematic/devicetypes/sensors.py b/pyhomematic/devicetypes/sensors.py index f2cb2b983..d49d048f1 100644 --- a/pyhomematic/devicetypes/sensors.py +++ b/pyhomematic/devicetypes/sensors.py @@ -220,7 +220,9 @@ def __init__(self, device_description, proxy, resolveparamsets=False): self.SENSORNODE.update({"GAS_ENERGY_COUNTER": [1], "GAS_POWER": [1], "ENERGY_COUNTER": [1], - "POWER": [1]}) + "POWER": [1], + "IEC_ENERGY_COUNTER": [1,2], + "IEC_POWER": [1,2]}) def get_gas_counter(self, channel=None): """Return gas counter.""" @@ -238,6 +240,14 @@ def get_power(self, channel=None): """Return power counter.""" return float(self.getSensorData("POWER", channel)) + def get_iec_energy(self, channel=None): + """Return iec energy counter.""" + return float(self.getSensorData("IEC_ENERGY_COUNTER", channel)) + + def get_iec_power(self, channel=None): + """Return iec power counter.""" + return float(self.getSensorData("IEC_POWER", channel)) + class Smoke(SensorHm, HelperBinaryState): """Smoke alarm. @@ -1048,7 +1058,23 @@ def get_level(self, channel=None): @property def ELEMENT(self): - return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + +class ValveBoxW(SensorHmIPW): + """Valve Box HmIPW-FALMOT-C12""" + + def __init__(self, device_description, proxy, resolveparamsets=False): + super().__init__(device_description, proxy, resolveparamsets) + + self.SENSORNODE.update({"LEVEL": self.ELEMENT}) + + def get_level(self, channel=None): + """Return valve state from 0% to 99%""" + return float(self.getSensorData("LEVEL", channel)) + + @property + def ELEMENT(self): + return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] class IPLanRouter(HMSensor): """ HmIP Lan Router HmIP-HAP""" @@ -1073,8 +1099,8 @@ def __init__(self, device_description, proxy, resolveparamsets=False): super().__init__(device_description, proxy, resolveparamsets) # init metadata - self.SENSORNODE.update({"ACTUAL_TEMPERATURE ": self.ELEMENT, - "ACTUAL_TEMPERATURE_STATUS ": self.ELEMENT}) + self.SENSORNODE.update({"ACTUAL_TEMPERATURE": self.ELEMENT, + "ACTUAL_TEMPERATURE_STATUS": self.ELEMENT}) @property def ELEMENT(self): @@ -1185,6 +1211,7 @@ def ELEMENT(self): "HmIP-ASIR": IPAlarmSensor, "HmIP-ASIR-2": IPAlarmSensor, "HmIP-FALMOT-C12": ValveBox, + "HmIPW-FALMOT-C12": ValveBoxW, "HmIP-SRD": IPRainSensor, "HmIP-HAP": IPLanRouter, "HB-WDS40-THP-O": WeatherStation, diff --git a/pyhomematic/devicetypes/thermostats.py b/pyhomematic/devicetypes/thermostats.py index 429f80a74..99e6a57c9 100644 --- a/pyhomematic/devicetypes/thermostats.py +++ b/pyhomematic/devicetypes/thermostats.py @@ -1,7 +1,7 @@ import logging from pyhomematic.devicetypes.generic import HMDevice from pyhomematic.devicetypes.sensors import AreaThermostat, IPAreaThermostat, IPAreaThermostatNoBattery -from pyhomematic.devicetypes.helper import HelperValveState, HelperBatteryState, HelperLowBat, HelperLowBatIP, HelperRssiPeer, HelperRssiDevice +from pyhomematic.devicetypes.helper import HelperValveState, HelperBatteryState, HelperLowBat, HelperLowBatIP, HelperRssiPeer, HelperRssiDevice, HelperWired LOG = logging.getLogger(__name__) @@ -23,8 +23,6 @@ def __init__(self, device_description, proxy, resolveparamsets=False): self.LOWERING_MODE = 5 self.OFF_VALUE = 4.5 - self.mode = None - def actual_temperature(self): """ Returns the current temperature. """ return self.getSensorData("ACTUAL_TEMPERATURE") @@ -75,32 +73,32 @@ def MODE(self, setmode): @property def AUTOMODE(self): """ Return auto mode state. """ - return self.mode == self.AUTO_MODE + return self.MODE == self.AUTO_MODE @property def MANUMODE(self): """ Return manual mode state. """ - return self.mode == self.MANU_MODE + return self.MODE == self.MANU_MODE @property def PARTYMODE(self): """ Return party mode state. """ - return self.mode == self.PARTY_MODE + return self.MODE == self.PARTY_MODE @property def BOOSTMODE(self): """ Return boost state. """ - return self.mode == self.BOOST_MODE + return self.MODE == self.BOOST_MODE @property def COMFORTMODE(self): """ Return comfort state. """ - return self.mode == self.COMFORT_MODE + return self.MODE == self.COMFORT_MODE @property def LOWERINGMODE(self): """ Return lowering state. """ - return self.mode == self.LOWERING_MODE + return self.MODE == self.LOWERING_MODE class ThermostatGroup(HMThermostat): @@ -284,9 +282,9 @@ def turnoff(self): self.writeNodeData("SET_POINT_TEMPERATURE", self.OFF_VALUE) self.actionNodeData('CONTROL_MODE', self.MANU_MODE) -class IPThermostatWall(HMThermostat, IPAreaThermostat, HelperRssiDevice, HelperLowBatIP): +class IPThermostatWall230V(HMThermostat, IPAreaThermostatNoBattery, HelperRssiDevice): """ - HmIP-STHD + HmIP-BWTH, HmIP-BWTH24 ClimateControl-Wall Thermostat that measures temperature and allows to set a target temperature or use some automatic mode. """ def __init__(self, device_description, proxy, resolveparamsets=False): @@ -296,10 +294,11 @@ def __init__(self, device_description, proxy, resolveparamsets=False): self.SENSORNODE.update({"ACTUAL_TEMPERATURE": [1], "HUMIDITY": [1]}) self.WRITENODE.update({"SET_POINT_TEMPERATURE": [1]}) - self.ACTIONNODE.update({"BOOST_MODE": [1]}) - self.ATTRIBUTENODE.update({"LOW_BAT": [0], - "OPERATING_VOLTAGE": [0], - "SET_POINT_MODE": [1], + self.ACTIONNODE.update({"AUTO_MODE": [1], + "MANU_MODE": [1], + "CONTROL_MODE": [1], + "BOOST_MODE": [1]}) + self.ATTRIBUTENODE.update({"SET_POINT_MODE": [1], "BOOST_MODE": [1]}) def get_set_temperature(self): @@ -315,14 +314,32 @@ def set_temperature(self, target_temperature): return False self.writeNodeData("SET_POINT_TEMPERATURE", target_temperature) + @property + def MODE(self): + """ Return mode. """ + if self.getAttributeData("BOOST_MODE"): + return self.BOOST_MODE + else: + return self.getAttributeData("SET_POINT_MODE") + + @MODE.setter + def MODE(self, setmode): + """ Set mode. """ + if setmode == self.BOOST_MODE: + self.actionNodeData('BOOST_MODE', True) + elif setmode in [self.AUTO_MODE, self.MANU_MODE]: + if self.getAttributeData("BOOST_MODE"): + self.actionNodeData('BOOST_MODE', False) + self.actionNodeData('CONTROL_MODE', setmode) + def turnoff(self): """ Turn off Thermostat. """ self.writeNodeData("SET_POINT_TEMPERATURE", self.OFF_VALUE) self.actionNodeData('CONTROL_MODE', self.MANU_MODE) -class IPThermostatWall230V(HMThermostat, IPAreaThermostatNoBattery, HelperRssiDevice): +class IPThermostatWall2(HMThermostat, IPAreaThermostat, HelperRssiDevice, HelperLowBatIP): """ - HmIP-BWTH, HmIP-BWTH24 + HmIP-WTH, HmIP-WTH-2, HmIP-STHD, HmIP-STH ClimateControl-Wall Thermostat that measures temperature and allows to set a target temperature or use some automatic mode. """ def __init__(self, device_description, proxy, resolveparamsets=False): @@ -336,7 +353,9 @@ def __init__(self, device_description, proxy, resolveparamsets=False): "MANU_MODE": [1], "CONTROL_MODE": [1], "BOOST_MODE": [1]}) - self.ATTRIBUTENODE.update({"SET_POINT_MODE": [1], + self.ATTRIBUTENODE.update({"LOW_BAT": [0], + "OPERATING_VOLTAGE": [0], + "SET_POINT_MODE": [1], "BOOST_MODE": [1]}) def get_set_temperature(self): @@ -375,9 +394,10 @@ def turnoff(self): self.writeNodeData("SET_POINT_TEMPERATURE", self.OFF_VALUE) self.actionNodeData('CONTROL_MODE', self.MANU_MODE) -class IPThermostatWall2(HMThermostat, IPAreaThermostat, HelperRssiDevice, HelperLowBatIP): + +class IPWThermostatWall(HMThermostat, IPAreaThermostatNoBattery, HelperWired): """ - HmIP-WTH, HmIP-WTH-2 + HmIPW-STH + HmIPW-WTH ClimateControl-Wall Thermostat that measures temperature and allows to set a target temperature or use some automatic mode. """ def __init__(self, device_description, proxy, resolveparamsets=False): @@ -387,12 +407,9 @@ def __init__(self, device_description, proxy, resolveparamsets=False): self.SENSORNODE.update({"ACTUAL_TEMPERATURE": [1], "HUMIDITY": [1]}) self.WRITENODE.update({"SET_POINT_TEMPERATURE": [1]}) - self.ACTIONNODE.update({"AUTO_MODE": [1], - "MANU_MODE": [1], - "CONTROL_MODE": [1], + self.ACTIONNODE.update({"CONTROL_MODE": [1], "BOOST_MODE": [1]}) - self.ATTRIBUTENODE.update({"LOW_BAT": [0], - "OPERATING_VOLTAGE": [0], + self.ATTRIBUTENODE.update({"OPERATING_VOLTAGE": [0], "SET_POINT_MODE": [1], "BOOST_MODE": [1]}) @@ -412,10 +429,7 @@ def set_temperature(self, target_temperature): @property def MODE(self): """ Return mode. """ - if self.getAttributeData("BOOST_MODE"): - return self.BOOST_MODE - else: - return self.getAttributeData("SET_POINT_MODE") + return self.getAttributeData("SET_POINT_MODE") @MODE.setter def MODE(self, setmode): @@ -425,14 +439,13 @@ def MODE(self, setmode): elif setmode in [self.AUTO_MODE, self.MANU_MODE]: if self.getAttributeData("BOOST_MODE"): self.actionNodeData('BOOST_MODE', False) - self.actionNodeData('CONTROL_MODE', setmode) + self.actionNodeData('CONTROL_MODE', setmode) def turnoff(self): """ Turn off Thermostat. """ - self.writeNodeData("SET_POINT_TEMPERATURE", self.OFF_VALUE) + self.writeNodeData('SET_POINT_TEMPERATURE', self.OFF_VALUE) self.actionNodeData('CONTROL_MODE', self.MANU_MODE) - DEVICETYPES = { "HM-CC-VG-1": ThermostatGroup, "HM-CC-RT-DN": Thermostat, @@ -458,8 +471,8 @@ def turnoff(self): "HmIP-eTRV-C-2": IPThermostat, "Thermostat AA": IPThermostat, "Thermostat AA GB": IPThermostat, - "HmIP-STHD": IPThermostatWall, - "HmIP-STH": IPThermostatWall, + "HmIP-STHD": IPThermostatWall2, + "HmIP-STH": IPThermostatWall2, "HmIP-WTH-2": IPThermostatWall2, "HMIP-WTH-2": IPThermostatWall2, "HmIP-WTH-B": IPThermostatWall2, @@ -469,4 +482,6 @@ def turnoff(self): "HmIP-BWTH": IPThermostatWall230V, "HmIP-BWTH24": IPThermostatWall230V, "HmIP-HEATING": IPThermostat, + "HmIPW-STH": IPWThermostatWall, + "HmIPW-WTH": IPWThermostatWall, } diff --git a/setup.py b/setup.py index e86a9255f..e8baf9a80 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.72' +VERSION = '0.1.73' PACKAGES = find_packages(exclude=['dist', 'build', 'tests'])