* 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
339 lines
11 KiB
Python
339 lines
11 KiB
Python
"""Support for Fibaro thermostats."""
|
|
import logging
|
|
|
|
from homeassistant.components.climate import ClimateEntity
|
|
from homeassistant.components.climate.const import (
|
|
HVAC_MODE_AUTO,
|
|
HVAC_MODE_COOL,
|
|
HVAC_MODE_DRY,
|
|
HVAC_MODE_FAN_ONLY,
|
|
HVAC_MODE_HEAT,
|
|
HVAC_MODE_OFF,
|
|
PRESET_AWAY,
|
|
PRESET_BOOST,
|
|
SUPPORT_FAN_MODE,
|
|
SUPPORT_PRESET_MODE,
|
|
SUPPORT_TARGET_TEMPERATURE,
|
|
)
|
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
|
|
|
from . import FIBARO_DEVICES, FibaroDevice
|
|
|
|
PRESET_RESUME = "resume"
|
|
PRESET_MOIST = "moist"
|
|
PRESET_FURNACE = "furnace"
|
|
PRESET_CHANGEOVER = "changeover"
|
|
PRESET_ECO_HEAT = "eco_heat"
|
|
PRESET_ECO_COOL = "eco_cool"
|
|
PRESET_FORCE_OPEN = "force_open"
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
|
|
# Table 128, Thermostat Fan Mode Set version 4::Fan Mode encoding
|
|
FANMODES = {
|
|
0: "off",
|
|
1: "low",
|
|
2: "auto_high",
|
|
3: "medium",
|
|
4: "auto_medium",
|
|
5: "high",
|
|
6: "circulation",
|
|
7: "humidity_circulation",
|
|
8: "left_right",
|
|
9: "up_down",
|
|
10: "quiet",
|
|
128: "auto",
|
|
}
|
|
|
|
HA_FANMODES = {v: k for k, v in FANMODES.items()}
|
|
|
|
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
|
|
# Table 130, Thermostat Mode Set version 3::Mode encoding.
|
|
# 4 AUXILIARY
|
|
OPMODES_PRESET = {
|
|
5: PRESET_RESUME,
|
|
7: PRESET_FURNACE,
|
|
9: PRESET_MOIST,
|
|
10: PRESET_CHANGEOVER,
|
|
11: PRESET_ECO_HEAT,
|
|
12: PRESET_ECO_COOL,
|
|
13: PRESET_AWAY,
|
|
15: PRESET_BOOST,
|
|
31: PRESET_FORCE_OPEN,
|
|
}
|
|
|
|
HA_OPMODES_PRESET = {v: k for k, v in OPMODES_PRESET.items()}
|
|
|
|
OPMODES_HVAC = {
|
|
0: HVAC_MODE_OFF,
|
|
1: HVAC_MODE_HEAT,
|
|
2: HVAC_MODE_COOL,
|
|
3: HVAC_MODE_AUTO,
|
|
4: HVAC_MODE_HEAT,
|
|
5: HVAC_MODE_AUTO,
|
|
6: HVAC_MODE_FAN_ONLY,
|
|
7: HVAC_MODE_HEAT,
|
|
8: HVAC_MODE_DRY,
|
|
9: HVAC_MODE_DRY,
|
|
10: HVAC_MODE_AUTO,
|
|
11: HVAC_MODE_HEAT,
|
|
12: HVAC_MODE_COOL,
|
|
13: HVAC_MODE_AUTO,
|
|
15: HVAC_MODE_AUTO,
|
|
31: HVAC_MODE_HEAT,
|
|
}
|
|
|
|
HA_OPMODES_HVAC = {
|
|
HVAC_MODE_OFF: 0,
|
|
HVAC_MODE_HEAT: 1,
|
|
HVAC_MODE_COOL: 2,
|
|
HVAC_MODE_AUTO: 3,
|
|
HVAC_MODE_FAN_ONLY: 6,
|
|
}
|
|
|
|
|
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
|
"""Perform the setup for Fibaro controller devices."""
|
|
if discovery_info is None:
|
|
return
|
|
|
|
add_entities(
|
|
[FibaroThermostat(device) for device in hass.data[FIBARO_DEVICES]["climate"]],
|
|
True,
|
|
)
|
|
|
|
|
|
class FibaroThermostat(FibaroDevice, ClimateEntity):
|
|
"""Representation of a Fibaro Thermostat."""
|
|
|
|
def __init__(self, fibaro_device):
|
|
"""Initialize the Fibaro device."""
|
|
super().__init__(fibaro_device)
|
|
self._temp_sensor_device = None
|
|
self._target_temp_device = None
|
|
self._op_mode_device = None
|
|
self._fan_mode_device = None
|
|
self._support_flags = 0
|
|
self.entity_id = f"climate.{self.ha_id}"
|
|
self._hvac_support = []
|
|
self._preset_support = []
|
|
self._fan_support = []
|
|
|
|
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
|
|
):
|
|
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
|
|
|
|
if tempunit == "F":
|
|
self._unit_of_temp = TEMP_FAHRENHEIT
|
|
else:
|
|
self._unit_of_temp = TEMP_CELSIUS
|
|
|
|
if self._fan_mode_device:
|
|
fan_modes = (
|
|
self._fan_mode_device.fibaro_device.properties.supportedModes.split(",")
|
|
)
|
|
for mode in fan_modes:
|
|
mode = int(mode)
|
|
if mode not in FANMODES:
|
|
_LOGGER.warning("%d unknown fan mode", mode)
|
|
continue
|
|
self._fan_support.append(FANMODES[int(mode)])
|
|
|
|
if self._op_mode_device:
|
|
prop = self._op_mode_device.fibaro_device.properties
|
|
if "supportedOperatingModes" in prop:
|
|
op_modes = prop.supportedOperatingModes.split(",")
|
|
elif "supportedModes" in prop:
|
|
op_modes = prop.supportedModes.split(",")
|
|
for mode in op_modes:
|
|
mode = int(mode)
|
|
if mode in OPMODES_HVAC:
|
|
mode_ha = OPMODES_HVAC[mode]
|
|
if mode_ha not in self._hvac_support:
|
|
self._hvac_support.append(mode_ha)
|
|
if mode in OPMODES_PRESET:
|
|
self._preset_support.append(OPMODES_PRESET[mode])
|
|
|
|
async def async_added_to_hass(self):
|
|
"""Call when entity is added to hass."""
|
|
_LOGGER.debug(
|
|
"Climate %s\n"
|
|
"- _temp_sensor_device %s\n"
|
|
"- _target_temp_device %s\n"
|
|
"- _op_mode_device %s\n"
|
|
"- _fan_mode_device %s",
|
|
self.ha_id,
|
|
self._temp_sensor_device.ha_id if self._temp_sensor_device else "None",
|
|
self._target_temp_device.ha_id if self._target_temp_device else "None",
|
|
self._op_mode_device.ha_id if self._op_mode_device else "None",
|
|
self._fan_mode_device.ha_id if self._fan_mode_device else "None",
|
|
)
|
|
await super().async_added_to_hass()
|
|
|
|
# Register update callback for child devices
|
|
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)
|
|
|
|
@property
|
|
def supported_features(self):
|
|
"""Return the list of supported features."""
|
|
return self._support_flags
|
|
|
|
@property
|
|
def fan_modes(self):
|
|
"""Return the list of available fan modes."""
|
|
if not self._fan_mode_device:
|
|
return None
|
|
return self._fan_support
|
|
|
|
@property
|
|
def fan_mode(self):
|
|
"""Return the fan setting."""
|
|
if not self._fan_mode_device:
|
|
return None
|
|
mode = int(self._fan_mode_device.fibaro_device.properties.mode)
|
|
return FANMODES[mode]
|
|
|
|
def set_fan_mode(self, fan_mode):
|
|
"""Set new target fan mode."""
|
|
if not self._fan_mode_device:
|
|
return
|
|
self._fan_mode_device.action("setFanMode", HA_FANMODES[fan_mode])
|
|
|
|
@property
|
|
def fibaro_op_mode(self):
|
|
"""Return the operating mode of the device."""
|
|
if not self._op_mode_device:
|
|
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)
|
|
|
|
return int(self._op_mode_device.fibaro_device.properties.mode)
|
|
|
|
@property
|
|
def hvac_mode(self):
|
|
"""Return current operation ie. heat, cool, idle."""
|
|
return OPMODES_HVAC[self.fibaro_op_mode]
|
|
|
|
@property
|
|
def hvac_modes(self):
|
|
"""Return the list of available operation modes."""
|
|
if not self._op_mode_device:
|
|
return [HVAC_MODE_AUTO] # Default to this
|
|
return self._hvac_support
|
|
|
|
def set_hvac_mode(self, hvac_mode):
|
|
"""Set new target operation mode."""
|
|
if not self._op_mode_device:
|
|
return
|
|
if self.preset_mode:
|
|
return
|
|
|
|
if "setOperatingMode" in self._op_mode_device.fibaro_device.actions:
|
|
self._op_mode_device.action("setOperatingMode", HA_OPMODES_HVAC[hvac_mode])
|
|
elif "setMode" in self._op_mode_device.fibaro_device.actions:
|
|
self._op_mode_device.action("setMode", HA_OPMODES_HVAC[hvac_mode])
|
|
|
|
@property
|
|
def preset_mode(self):
|
|
"""Return the current preset mode, e.g., home, away, temp.
|
|
|
|
Requires SUPPORT_PRESET_MODE.
|
|
"""
|
|
if not self._op_mode_device:
|
|
return None
|
|
|
|
if "operatingMode" in self._op_mode_device.fibaro_device.properties:
|
|
mode = int(self._op_mode_device.fibaro_device.properties.operatingMode)
|
|
else:
|
|
mode = int(self._op_mode_device.fibaro_device.properties.mode)
|
|
|
|
if mode not in OPMODES_PRESET:
|
|
return None
|
|
return OPMODES_PRESET[mode]
|
|
|
|
@property
|
|
def preset_modes(self):
|
|
"""Return a list of available preset modes.
|
|
|
|
Requires SUPPORT_PRESET_MODE.
|
|
"""
|
|
if not self._op_mode_device:
|
|
return None
|
|
return self._preset_support
|
|
|
|
def set_preset_mode(self, preset_mode: str) -> None:
|
|
"""Set new preset mode."""
|
|
if self._op_mode_device is None:
|
|
return
|
|
if "setOperatingMode" in self._op_mode_device.fibaro_device.actions:
|
|
self._op_mode_device.action(
|
|
"setOperatingMode", HA_OPMODES_PRESET[preset_mode]
|
|
)
|
|
elif "setMode" in self._op_mode_device.fibaro_device.actions:
|
|
self._op_mode_device.action("setMode", HA_OPMODES_PRESET[preset_mode])
|
|
|
|
@property
|
|
def temperature_unit(self):
|
|
"""Return the unit of measurement."""
|
|
return self._unit_of_temp
|
|
|
|
@property
|
|
def current_temperature(self):
|
|
"""Return the current temperature."""
|
|
if self._temp_sensor_device:
|
|
device = self._temp_sensor_device.fibaro_device
|
|
return float(device.properties.value)
|
|
return None
|
|
|
|
@property
|
|
def target_temperature(self):
|
|
"""Return the temperature we try to reach."""
|
|
if self._target_temp_device:
|
|
device = self._target_temp_device.fibaro_device
|
|
return float(device.properties.targetLevel)
|
|
return None
|
|
|
|
def set_temperature(self, **kwargs):
|
|
"""Set new target temperatures."""
|
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
|
target = self._target_temp_device
|
|
if temperature is not None:
|
|
if "setThermostatSetpoint" in target.fibaro_device.actions:
|
|
target.action("setThermostatSetpoint", self.fibaro_op_mode, temperature)
|
|
else:
|
|
target.action("setTargetLevel", temperature)
|