"""Support for Nest thermostats.""" import logging import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, FAN_AUTO, FAN_ON, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_PRESET_MODE, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, PRESET_AWAY, PRESET_ECO) from homeassistant.const import ( ATTR_TEMPERATURE, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import DATA_NEST, DOMAIN as NEST_DOMAIN, SIGNAL_NEST_UPDATE _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_SCAN_INTERVAL): vol.All(vol.Coerce(int), vol.Range(min=1)), }) NEST_MODE_HEAT_COOL = 'heat-cool' NEST_MODE_ECO = 'eco' NEST_MODE_HEAT = 'heat' NEST_MODE_COOL = 'cool' NEST_MODE_OFF = 'off' PRESET_MODES = [PRESET_AWAY, PRESET_ECO] def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Nest thermostat. No longer in use. """ async def async_setup_entry(hass, entry, async_add_entities): """Set up the Nest climate device based on a config entry.""" temp_unit = hass.config.units.temperature_unit thermostats = await hass.async_add_job(hass.data[DATA_NEST].thermostats) all_devices = [NestThermostat(structure, device, temp_unit) for structure, device in thermostats] async_add_entities(all_devices, True) class NestThermostat(ClimateDevice): """Representation of a Nest thermostat.""" def __init__(self, structure, device, temp_unit): """Initialize the thermostat.""" self._unit = temp_unit self.structure = structure self.device = device self._fan_modes = [FAN_ON, FAN_AUTO] # Set the default supported features self._support_flags = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE) # Not all nest devices support cooling and heating remove unused self._operation_list = [] if self.device.can_heat and self.device.can_cool: self._operation_list.append(HVAC_MODE_AUTO) self._support_flags = (self._support_flags | SUPPORT_TARGET_TEMPERATURE_RANGE) # Add supported nest thermostat features if self.device.can_heat: self._operation_list.append(HVAC_MODE_HEAT) if self.device.can_cool: self._operation_list.append(HVAC_MODE_COOL) self._operation_list.append(HVAC_MODE_OFF) # feature of device self._has_fan = self.device.has_fan if self._has_fan: self._support_flags = (self._support_flags | SUPPORT_FAN_MODE) # data attributes self._away = None self._location = None self._name = None self._humidity = None self._target_temperature = None self._temperature = None self._temperature_scale = None self._mode = None self._fan = None self._eco_temperature = None self._is_locked = None self._locked_temperature = None self._min_temperature = None self._max_temperature = None @property def should_poll(self): """Do not need poll thanks using Nest streaming API.""" return False async def async_added_to_hass(self): """Register update signal handler.""" async def async_update_state(): """Update device state.""" await self.async_update_ha_state(True) async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE, async_update_state) @property def supported_features(self): """Return the list of supported features.""" return self._support_flags @property def unique_id(self): """Return unique ID for this device.""" return self.device.serial @property def device_info(self): """Return information about the device.""" return { 'identifiers': { (NEST_DOMAIN, self.device.device_id), }, 'name': self.device.name_long, 'manufacturer': 'Nest Labs', 'model': "Thermostat", 'sw_version': self.device.software_version, } @property def name(self): """Return the name of the nest, if any.""" return self._name @property def temperature_unit(self): """Return the unit of measurement.""" return self._temperature_scale @property def current_temperature(self): """Return the current temperature.""" return self._temperature @property def hvac_mode(self): """Return current operation ie. heat, cool, idle.""" if self._mode in \ (NEST_MODE_HEAT, NEST_MODE_COOL, NEST_MODE_OFF): return self._mode if self._mode == NEST_MODE_ECO: # We assume the first operation in operation list is the main one return self._operation_list[0] if self._mode == NEST_MODE_HEAT_COOL: return HVAC_MODE_AUTO return None @property def target_temperature(self): """Return the temperature we try to reach.""" if self._mode not in (NEST_MODE_HEAT_COOL, NEST_MODE_ECO): return self._target_temperature return None @property def target_temperature_low(self): """Return the lower bound temperature we try to reach.""" if self._mode == NEST_MODE_ECO: return self._eco_temperature[0] if self._mode == NEST_MODE_HEAT_COOL: return self._target_temperature[0] return None @property def target_temperature_high(self): """Return the upper bound temperature we try to reach.""" if self._mode == NEST_MODE_ECO: return self._eco_temperature[1] if self._mode == NEST_MODE_HEAT_COOL: return self._target_temperature[1] return None def set_temperature(self, **kwargs): """Set new target temperature.""" import nest temp = None target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) if self._mode == NEST_MODE_HEAT_COOL: if target_temp_low is not None and target_temp_high is not None: temp = (target_temp_low, target_temp_high) _LOGGER.debug("Nest set_temperature-output-value=%s", temp) else: temp = kwargs.get(ATTR_TEMPERATURE) _LOGGER.debug("Nest set_temperature-output-value=%s", temp) try: if temp is not None: self.device.target = temp except nest.nest.APIError as api_error: _LOGGER.error("An error occurred while setting temperature: %s", api_error) # restore target temperature self.schedule_update_ha_state(True) def set_hvac_mode(self, hvac_mode): """Set operation mode.""" if hvac_mode in (HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF): device_mode = hvac_mode elif hvac_mode == HVAC_MODE_AUTO: device_mode = NEST_MODE_HEAT_COOL else: device_mode = HVAC_MODE_OFF _LOGGER.error( "An error occurred while setting device mode. " "Invalid operation mode: %s", hvac_mode) self.device.mode = device_mode @property def hvac_modes(self): """List of available operation modes.""" return self._operation_list @property def preset_mode(self): """Return current preset mode.""" if self._away: return PRESET_AWAY if self._mode == NEST_MODE_ECO: return PRESET_ECO return None @property def preset_modes(self): """Return preset modes.""" return PRESET_MODES def set_preset_mode(self, preset_mode): """Set preset mode.""" if preset_mode == self.preset_mode: return if self._away: self.structure.away = False elif preset_mode == PRESET_AWAY: self.structure.away = True if self.preset_mode == PRESET_ECO: self.device.mode = self._operation_list[0] elif preset_mode == PRESET_ECO: self.device.mode = NEST_MODE_ECO @property def fan_mode(self): """Return whether the fan is on.""" if self._has_fan: # Return whether the fan is on return FAN_ON if self._fan else FAN_AUTO # No Fan available so disable slider return None @property def fan_modes(self): """List of available fan modes.""" if self._has_fan: return self._fan_modes return None def set_fan_mode(self, fan_mode): """Turn fan on/off.""" if self._has_fan: self.device.fan = fan_mode.lower() @property def min_temp(self): """Identify min_temp in Nest API or defaults if not available.""" return self._min_temperature @property def max_temp(self): """Identify max_temp in Nest API or defaults if not available.""" return self._max_temperature def update(self): """Cache value from Python-nest.""" self._location = self.device.where self._name = self.device.name self._humidity = self.device.humidity self._temperature = self.device.temperature self._mode = self.device.mode self._target_temperature = self.device.target self._fan = self.device.fan self._away = self.structure.away == 'away' self._eco_temperature = self.device.eco_temperature self._locked_temperature = self.device.locked_temperature self._min_temperature = self.device.min_temperature self._max_temperature = self.device.max_temperature self._is_locked = self.device.is_locked if self.device.temperature_scale == 'C': self._temperature_scale = TEMP_CELSIUS else: self._temperature_scale = TEMP_FAHRENHEIT