2019-02-13 20:21:14 +00:00
|
|
|
"""Support for the Daikin HVAC."""
|
2018-01-04 10:05:27 +00:00
|
|
|
import logging
|
|
|
|
import re
|
|
|
|
|
|
|
|
import voluptuous as vol
|
|
|
|
|
2019-03-21 05:56:46 +00:00
|
|
|
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
2019-02-14 19:34:43 +00:00
|
|
|
from homeassistant.components.climate.const import (
|
2019-05-05 20:17:01 +00:00
|
|
|
ATTR_AWAY_MODE, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE,
|
|
|
|
ATTR_OPERATION_MODE, ATTR_SWING_MODE, STATE_AUTO, STATE_COOL, STATE_DRY,
|
|
|
|
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
|
|
|
|
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE,
|
2019-03-21 05:56:46 +00:00
|
|
|
SUPPORT_TARGET_TEMPERATURE)
|
2018-01-04 10:05:27 +00:00
|
|
|
from homeassistant.const import (
|
2019-02-14 19:34:43 +00:00
|
|
|
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, STATE_OFF, TEMP_CELSIUS)
|
2018-01-21 06:35:38 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2018-01-04 10:05:27 +00:00
|
|
|
|
2019-03-21 05:56:46 +00:00
|
|
|
from . import DOMAIN as DAIKIN_DOMAIN
|
|
|
|
from .const import (
|
|
|
|
ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE)
|
|
|
|
|
2018-01-04 10:05:27 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|
|
|
vol.Required(CONF_HOST): cv.string,
|
2018-02-17 09:29:14 +00:00
|
|
|
vol.Optional(CONF_NAME): cv.string,
|
2018-01-04 10:05:27 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
HA_STATE_TO_DAIKIN = {
|
|
|
|
STATE_FAN_ONLY: 'fan',
|
|
|
|
STATE_DRY: 'dry',
|
|
|
|
STATE_COOL: 'cool',
|
|
|
|
STATE_HEAT: 'hot',
|
|
|
|
STATE_AUTO: 'auto',
|
|
|
|
STATE_OFF: 'off',
|
|
|
|
}
|
|
|
|
|
2018-10-08 08:49:54 +00:00
|
|
|
DAIKIN_TO_HA_STATE = {
|
|
|
|
'fan': STATE_FAN_ONLY,
|
|
|
|
'dry': STATE_DRY,
|
|
|
|
'cool': STATE_COOL,
|
|
|
|
'hot': STATE_HEAT,
|
|
|
|
'auto': STATE_AUTO,
|
|
|
|
'off': STATE_OFF,
|
|
|
|
}
|
|
|
|
|
2018-01-04 10:05:27 +00:00
|
|
|
HA_ATTR_TO_DAIKIN = {
|
2019-05-05 20:17:01 +00:00
|
|
|
ATTR_AWAY_MODE: 'en_hol',
|
2018-01-04 10:05:27 +00:00
|
|
|
ATTR_OPERATION_MODE: 'mode',
|
|
|
|
ATTR_FAN_MODE: 'f_rate',
|
|
|
|
ATTR_SWING_MODE: 'f_dir',
|
2018-01-24 08:39:25 +00:00
|
|
|
ATTR_INSIDE_TEMPERATURE: 'htemp',
|
|
|
|
ATTR_OUTSIDE_TEMPERATURE: 'otemp',
|
|
|
|
ATTR_TARGET_TEMPERATURE: 'stemp'
|
2018-01-04 10:05:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-01 17:01:31 +00:00
|
|
|
async def async_setup_platform(
|
|
|
|
hass, config, async_add_entities, discovery_info=None):
|
2018-12-16 15:19:18 +00:00
|
|
|
"""Old way of setting up the Daikin HVAC platform.
|
|
|
|
|
|
|
|
Can only be called when a user accidentally mentions the platform in their
|
|
|
|
config. But even in that case it would have been ignored.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_entry(hass, entry, async_add_entities):
|
|
|
|
"""Set up Daikin climate based on config_entry."""
|
|
|
|
daikin_api = hass.data[DAIKIN_DOMAIN].get(entry.entry_id)
|
|
|
|
async_add_entities([DaikinClimate(daikin_api)])
|
2018-01-04 10:05:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
class DaikinClimate(ClimateDevice):
|
|
|
|
"""Representation of a Daikin HVAC."""
|
|
|
|
|
|
|
|
def __init__(self, api):
|
|
|
|
"""Initialize the climate device."""
|
|
|
|
from pydaikin import appliance
|
|
|
|
|
|
|
|
self._api = api
|
|
|
|
self._list = {
|
2018-10-08 08:49:54 +00:00
|
|
|
ATTR_OPERATION_MODE: list(HA_STATE_TO_DAIKIN),
|
2018-01-04 10:05:27 +00:00
|
|
|
ATTR_FAN_MODE: list(
|
|
|
|
map(
|
|
|
|
str.title,
|
|
|
|
appliance.daikin_values(HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE])
|
|
|
|
)
|
|
|
|
),
|
|
|
|
ATTR_SWING_MODE: list(
|
|
|
|
map(
|
|
|
|
str.title,
|
|
|
|
appliance.daikin_values(HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE])
|
|
|
|
)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
|
2019-05-05 20:17:01 +00:00
|
|
|
self._supported_features = (SUPPORT_AWAY_MODE | SUPPORT_ON_OFF
|
|
|
|
| SUPPORT_OPERATION_MODE
|
|
|
|
| SUPPORT_TARGET_TEMPERATURE)
|
2018-01-24 08:39:25 +00:00
|
|
|
|
2018-11-20 13:14:11 +00:00
|
|
|
if self._api.device.support_fan_mode:
|
2018-01-24 08:39:25 +00:00
|
|
|
self._supported_features |= SUPPORT_FAN_MODE
|
|
|
|
|
2018-11-20 13:14:11 +00:00
|
|
|
if self._api.device.support_swing_mode:
|
2018-01-24 08:39:25 +00:00
|
|
|
self._supported_features |= SUPPORT_SWING_MODE
|
|
|
|
|
2018-01-04 10:05:27 +00:00
|
|
|
def get(self, key):
|
|
|
|
"""Retrieve device settings from API library cache."""
|
|
|
|
value = None
|
|
|
|
cast_to_float = False
|
|
|
|
|
|
|
|
if key in [ATTR_TEMPERATURE, ATTR_INSIDE_TEMPERATURE,
|
|
|
|
ATTR_CURRENT_TEMPERATURE]:
|
2018-01-24 08:39:25 +00:00
|
|
|
key = ATTR_INSIDE_TEMPERATURE
|
|
|
|
|
|
|
|
daikin_attr = HA_ATTR_TO_DAIKIN.get(key)
|
|
|
|
|
|
|
|
if key == ATTR_INSIDE_TEMPERATURE:
|
|
|
|
value = self._api.device.values.get(daikin_attr)
|
2018-01-04 10:05:27 +00:00
|
|
|
cast_to_float = True
|
2018-01-24 08:39:25 +00:00
|
|
|
elif key == ATTR_TARGET_TEMPERATURE:
|
|
|
|
value = self._api.device.values.get(daikin_attr)
|
2018-01-04 10:05:27 +00:00
|
|
|
cast_to_float = True
|
|
|
|
elif key == ATTR_OUTSIDE_TEMPERATURE:
|
2018-01-24 08:39:25 +00:00
|
|
|
value = self._api.device.values.get(daikin_attr)
|
2018-01-04 10:05:27 +00:00
|
|
|
cast_to_float = True
|
|
|
|
elif key == ATTR_FAN_MODE:
|
2018-01-24 08:39:25 +00:00
|
|
|
value = self._api.device.represent(daikin_attr)[1].title()
|
2018-01-04 10:05:27 +00:00
|
|
|
elif key == ATTR_SWING_MODE:
|
2018-01-24 08:39:25 +00:00
|
|
|
value = self._api.device.represent(daikin_attr)[1].title()
|
2018-01-04 10:05:27 +00:00
|
|
|
elif key == ATTR_OPERATION_MODE:
|
|
|
|
# Daikin can return also internal states auto-1 or auto-7
|
|
|
|
# and we need to translate them as AUTO
|
2018-10-08 08:49:54 +00:00
|
|
|
daikin_mode = re.sub(
|
|
|
|
'[^a-z]', '',
|
|
|
|
self._api.device.represent(daikin_attr)[1])
|
|
|
|
ha_mode = DAIKIN_TO_HA_STATE.get(daikin_mode)
|
|
|
|
value = ha_mode
|
2018-01-04 10:05:27 +00:00
|
|
|
|
|
|
|
if value is None:
|
2018-01-21 06:35:38 +00:00
|
|
|
_LOGGER.error("Invalid value requested for key %s", key)
|
2018-01-04 10:05:27 +00:00
|
|
|
else:
|
2018-07-17 17:34:29 +00:00
|
|
|
if value in ("-", "--"):
|
2018-01-04 10:05:27 +00:00
|
|
|
value = None
|
|
|
|
elif cast_to_float:
|
|
|
|
try:
|
|
|
|
value = float(value)
|
|
|
|
except ValueError:
|
|
|
|
value = None
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
2019-03-14 17:33:43 +00:00
|
|
|
async def _set(self, settings):
|
2018-01-04 10:05:27 +00:00
|
|
|
"""Set device settings using API."""
|
|
|
|
values = {}
|
|
|
|
|
|
|
|
for attr in [ATTR_TEMPERATURE, ATTR_FAN_MODE, ATTR_SWING_MODE,
|
|
|
|
ATTR_OPERATION_MODE]:
|
|
|
|
value = settings.get(attr)
|
|
|
|
if value is None:
|
|
|
|
continue
|
|
|
|
|
|
|
|
daikin_attr = HA_ATTR_TO_DAIKIN.get(attr)
|
|
|
|
if daikin_attr is not None:
|
2018-11-01 09:29:48 +00:00
|
|
|
if attr == ATTR_OPERATION_MODE:
|
2018-10-08 08:49:54 +00:00
|
|
|
values[daikin_attr] = HA_STATE_TO_DAIKIN[value]
|
2018-11-01 09:29:48 +00:00
|
|
|
elif value in self._list[attr]:
|
|
|
|
values[daikin_attr] = value.lower()
|
2018-01-04 10:05:27 +00:00
|
|
|
else:
|
|
|
|
_LOGGER.error("Invalid value %s for %s", attr, value)
|
|
|
|
|
|
|
|
# temperature
|
|
|
|
elif attr == ATTR_TEMPERATURE:
|
|
|
|
try:
|
|
|
|
values['stemp'] = str(int(value))
|
|
|
|
except ValueError:
|
|
|
|
_LOGGER.error("Invalid temperature %s", value)
|
|
|
|
|
|
|
|
if values:
|
2019-03-14 17:33:43 +00:00
|
|
|
await self._api.device.set(values)
|
2018-01-04 10:05:27 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_features(self):
|
|
|
|
"""Return the list of supported features."""
|
2018-01-24 08:39:25 +00:00
|
|
|
return self._supported_features
|
2018-01-04 10:05:27 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the thermostat, if any."""
|
|
|
|
return self._api.name
|
|
|
|
|
2018-11-27 14:36:55 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self):
|
|
|
|
"""Return a unique ID."""
|
|
|
|
return self._api.mac
|
|
|
|
|
2018-01-04 10:05:27 +00:00
|
|
|
@property
|
|
|
|
def temperature_unit(self):
|
|
|
|
"""Return the unit of measurement which this thermostat uses."""
|
|
|
|
return TEMP_CELSIUS
|
|
|
|
|
|
|
|
@property
|
|
|
|
def current_temperature(self):
|
|
|
|
"""Return the current temperature."""
|
|
|
|
return self.get(ATTR_CURRENT_TEMPERATURE)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def target_temperature(self):
|
|
|
|
"""Return the temperature we try to reach."""
|
|
|
|
return self.get(ATTR_TARGET_TEMPERATURE)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def target_temperature_step(self):
|
|
|
|
"""Return the supported step of target temperature."""
|
|
|
|
return 1
|
|
|
|
|
2019-03-14 17:33:43 +00:00
|
|
|
async def async_set_temperature(self, **kwargs):
|
2018-01-04 10:05:27 +00:00
|
|
|
"""Set new target temperature."""
|
2019-03-14 17:33:43 +00:00
|
|
|
await self._set(kwargs)
|
2018-01-04 10:05:27 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def current_operation(self):
|
|
|
|
"""Return current operation ie. heat, cool, idle."""
|
|
|
|
return self.get(ATTR_OPERATION_MODE)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def operation_list(self):
|
|
|
|
"""Return the list of available operation modes."""
|
|
|
|
return self._list.get(ATTR_OPERATION_MODE)
|
|
|
|
|
2019-03-14 17:33:43 +00:00
|
|
|
async def async_set_operation_mode(self, operation_mode):
|
2018-01-04 10:05:27 +00:00
|
|
|
"""Set HVAC mode."""
|
2019-03-14 17:33:43 +00:00
|
|
|
await self._set({ATTR_OPERATION_MODE: operation_mode})
|
2018-01-04 10:05:27 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def current_fan_mode(self):
|
|
|
|
"""Return the fan setting."""
|
|
|
|
return self.get(ATTR_FAN_MODE)
|
|
|
|
|
2019-03-14 17:33:43 +00:00
|
|
|
async def async_set_fan_mode(self, fan_mode):
|
2018-01-04 10:05:27 +00:00
|
|
|
"""Set fan mode."""
|
2019-03-14 17:33:43 +00:00
|
|
|
await self._set({ATTR_FAN_MODE: fan_mode})
|
2018-01-04 10:05:27 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def fan_list(self):
|
|
|
|
"""List of available fan modes."""
|
|
|
|
return self._list.get(ATTR_FAN_MODE)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def current_swing_mode(self):
|
|
|
|
"""Return the fan setting."""
|
|
|
|
return self.get(ATTR_SWING_MODE)
|
|
|
|
|
2019-03-14 17:33:43 +00:00
|
|
|
async def async_set_swing_mode(self, swing_mode):
|
2018-01-04 10:05:27 +00:00
|
|
|
"""Set new target temperature."""
|
2019-03-14 17:33:43 +00:00
|
|
|
await self._set({ATTR_SWING_MODE: swing_mode})
|
2018-01-04 10:05:27 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def swing_list(self):
|
|
|
|
"""List of available swing modes."""
|
|
|
|
return self._list.get(ATTR_SWING_MODE)
|
|
|
|
|
2019-03-14 17:33:43 +00:00
|
|
|
async def async_update(self):
|
2018-01-04 10:05:27 +00:00
|
|
|
"""Retrieve latest state."""
|
2019-03-14 17:33:43 +00:00
|
|
|
await self._api.async_update()
|
2018-12-19 07:18:40 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def device_info(self):
|
|
|
|
"""Return a device description for device registry."""
|
|
|
|
return self._api.device_info
|
2019-05-05 20:17:01 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def is_on(self):
|
|
|
|
"""Return true if on."""
|
|
|
|
return self._api.device.represent(
|
2019-05-06 11:43:35 +00:00
|
|
|
HA_ATTR_TO_DAIKIN[ATTR_OPERATION_MODE]
|
|
|
|
)[1] != HA_STATE_TO_DAIKIN[STATE_OFF]
|
2019-05-05 20:17:01 +00:00
|
|
|
|
|
|
|
async def async_turn_on(self):
|
|
|
|
"""Turn device on."""
|
|
|
|
await self._api.device.set({})
|
|
|
|
|
|
|
|
async def async_turn_off(self):
|
|
|
|
"""Turn device off."""
|
2019-05-06 11:43:35 +00:00
|
|
|
await self._api.device.set({
|
|
|
|
HA_ATTR_TO_DAIKIN[ATTR_OPERATION_MODE]:
|
|
|
|
HA_STATE_TO_DAIKIN[STATE_OFF]
|
|
|
|
})
|
2019-05-05 20:17:01 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def is_away_mode_on(self):
|
|
|
|
"""Return true if away mode is on."""
|
|
|
|
return self._api.device.represent(
|
2019-05-06 11:43:35 +00:00
|
|
|
HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]
|
|
|
|
)[1] != HA_STATE_TO_DAIKIN[STATE_OFF]
|
2019-05-05 20:17:01 +00:00
|
|
|
|
|
|
|
async def async_turn_away_mode_on(self):
|
|
|
|
"""Turn away mode on."""
|
|
|
|
await self._api.device.set({HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]: '1'})
|
|
|
|
|
|
|
|
async def async_turn_away_mode_off(self):
|
|
|
|
"""Turn away mode off."""
|
|
|
|
await self._api.device.set({HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]: '0'})
|