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
This commit is contained in:
pbalogh77 2020-09-07 04:03:03 +02:00 committed by GitHub
parent d0af3339fc
commit 3a36a789ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 25 deletions

View File

@ -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

View File

@ -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):