From 3a36a789ad0389bab3ade027c6b518faea57fe76 Mon Sep 17 00:00:00 2001 From: pbalogh77 Date: Mon, 7 Sep 2020 04:03:03 +0200 Subject: [PATCH] Improve climate support for fibaro (#39038) * Fibaro climate improvements 1, Implemented support for multinode climate devices, such as Danfoss HC10, differentiating zones based on endPointId 2, Improved recognition of temperature sensor subdevice for climate devices 3, Changed default opmode for devices without an opmode to "auto" instead of "fan_only", for better clarity and to avoid misunderstandings * pylint inspired code restructuring to reduce depth gotta love pylint --- homeassistant/components/fibaro/__init__.py | 73 +++++++++++++++------ homeassistant/components/fibaro/climate.py | 24 +++++-- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 54dd5b6234f..4b51c82469e 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -200,9 +200,26 @@ class FibaroController: if device.parentId == device_id ] - def get_siblings(self, device_id): + def get_children2(self, device_id, endpoint_id): + """Get a list of child devices for the same endpoint.""" + return [ + device + for device in self._device_map.values() + if device.parentId == device_id + and ( + "endPointId" not in device.properties + or device.properties.endPointId == endpoint_id + ) + ] + + def get_siblings(self, device): """Get the siblings of a device.""" - return self.get_children(self._device_map[device_id].parentId) + if "endPointId" in device.properties: + return self.get_children2( + self._device_map[device.id].parentId, + self._device_map[device.id].properties.endPointId, + ) + return self.get_children(self._device_map[device.id].parentId) @staticmethod def _map_device_to_type(device): @@ -262,6 +279,7 @@ class FibaroController: self._device_map = {} self.fibaro_devices = defaultdict(list) last_climate_parent = None + last_endpoint = None for device in devices: try: if "name" not in device or "id" not in device: @@ -289,23 +307,10 @@ class FibaroController: else: device.mapped_type = None dtype = device.mapped_type - if dtype: - device.unique_id_str = f"{self.hub_serial}.{device.id}" - self._device_map[device.id] = device - if dtype != "climate": - self.fibaro_devices[dtype].append(device) - else: - # if a sibling of this has been added, skip this one - # otherwise add the first visible device in the group - # which is a hack, but solves a problem with FGT having - # hidden compatibility devices before the real device - if ( - last_climate_parent != device.parentId - and "visible" in device - and device.visible - ): - self.fibaro_devices[dtype].append(device) - last_climate_parent = device.parentId + if dtype is None: + continue + device.unique_id_str = f"{self.hub_serial}.{device.id}" + self._device_map[device.id] = device _LOGGER.debug( "%s (%s, %s) -> %s %s", device.ha_id, @@ -314,6 +319,36 @@ class FibaroController: dtype, str(device), ) + if dtype != "climate": + self.fibaro_devices[dtype].append(device) + continue + # We group climate devices into groups with the same + # endPointID belonging to the same parent device. + if "endPointId" in device.properties: + _LOGGER.debug( + "climate device: %s, endPointId: %s", + device.ha_id, + device.properties.endPointId, + ) + else: + _LOGGER.debug("climate device: %s, no endPointId", device.ha_id) + # If a sibling of this device has been added, skip this one + # otherwise add the first visible device in the group + # which is a hack, but solves a problem with FGT having + # hidden compatibility devices before the real device + if last_climate_parent != device.parentId or ( + "endPointId" in device.properties + and last_endpoint != device.properties.endPointId + ): + _LOGGER.debug("Handle separately") + self.fibaro_devices[dtype].append(device) + last_climate_parent = device.parentId + if "endPointId" in device.properties: + last_endpoint = device.properties.endPointId + else: + last_endpoint = 0 + else: + _LOGGER.debug("not handling separately") except (KeyError, ValueError): pass diff --git a/homeassistant/components/fibaro/climate.py b/homeassistant/components/fibaro/climate.py index 5776a293756..eee1c08bd36 100644 --- a/homeassistant/components/fibaro/climate.py +++ b/homeassistant/components/fibaro/climate.py @@ -120,12 +120,24 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): self._preset_support = [] self._fan_support = [] - siblings = fibaro_device.fibaro_controller.get_siblings(fibaro_device.id) + siblings = fibaro_device.fibaro_controller.get_siblings(fibaro_device) + _LOGGER.debug("%s siblings: %s", fibaro_device.ha_id, siblings) tempunit = "C" for device in siblings: + # Detecting temperature device, one strong and one weak way of + # doing so, so we prefer the hard evidence, if there is such. if device.type == "com.fibaro.temperatureSensor": self._temp_sensor_device = FibaroDevice(device) tempunit = device.properties.unit + elif ( + self._temp_sensor_device is None + and "unit" in device.properties + and "value" in device.properties + and (device.properties.unit == "C" or device.properties.unit == "F") + ): + self._temp_sensor_device = FibaroDevice(device) + tempunit = device.properties.unit + if ( "setTargetLevel" in device.actions or "setThermostatSetpoint" in device.actions @@ -133,9 +145,11 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): self._target_temp_device = FibaroDevice(device) self._support_flags |= SUPPORT_TARGET_TEMPERATURE tempunit = device.properties.unit + if "setMode" in device.actions or "setOperatingMode" in device.actions: self._op_mode_device = FibaroDevice(device) self._support_flags |= SUPPORT_PRESET_MODE + if "setFanMode" in device.actions: self._fan_mode_device = FibaroDevice(device) self._support_flags |= SUPPORT_FAN_MODE @@ -188,9 +202,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): await super().async_added_to_hass() # Register update callback for child devices - siblings = self.fibaro_device.fibaro_controller.get_siblings( - self.fibaro_device.id - ) + siblings = self.fibaro_device.fibaro_controller.get_siblings(self.fibaro_device) for device in siblings: if device != self.fibaro_device: self.controller.register(device.id, self._update_callback) @@ -225,7 +237,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): def fibaro_op_mode(self): """Return the operating mode of the device.""" if not self._op_mode_device: - return 6 # Fan only + return 3 # Default to AUTO if "operatingMode" in self._op_mode_device.fibaro_device.properties: return int(self._op_mode_device.fibaro_device.properties.operatingMode) @@ -241,7 +253,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): def hvac_modes(self): """Return the list of available operation modes.""" if not self._op_mode_device: - return [HVAC_MODE_FAN_ONLY] + return [HVAC_MODE_AUTO] # Default to this return self._hvac_support def set_hvac_mode(self, hvac_mode):