""" Support for Honeywell Round Connected and Honeywell Evohome thermostats. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.honeywell/ """ import logging import socket from homeassistant.components.climate import ClimateDevice from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT) REQUIREMENTS = ['evohomeclient==0.2.5', 'somecomfort==0.2.1'] _LOGGER = logging.getLogger(__name__) CONF_AWAY_TEMP = "away_temperature" DEFAULT_AWAY_TEMP = 16 def _setup_round(username, password, config, add_devices): """Setup rounding function.""" from evohomeclient import EvohomeClient try: away_temp = float(config.get(CONF_AWAY_TEMP, DEFAULT_AWAY_TEMP)) except ValueError: _LOGGER.error("value entered for item %s should convert to a number", CONF_AWAY_TEMP) return False evo_api = EvohomeClient(username, password) try: zones = evo_api.temperatures(force_refresh=True) for i, zone in enumerate(zones): add_devices([RoundThermostat(evo_api, zone['id'], i == 0, away_temp)]) except socket.error: _LOGGER.error( "Connection error logging into the honeywell evohome web service" ) return False return True # config will be used later def _setup_us(username, password, config, add_devices): """Setup user.""" import somecomfort try: client = somecomfort.SomeComfort(username, password) except somecomfort.AuthError: _LOGGER.error('Failed to login to honeywell account %s', username) return False except somecomfort.SomeComfortError as ex: _LOGGER.error('Failed to initialize honeywell client: %s', str(ex)) return False dev_id = config.get('thermostat') loc_id = config.get('location') add_devices([HoneywellUSThermostat(client, device) for location in client.locations_by_id.values() for device in location.devices_by_id.values() if ((not loc_id or location.locationid == loc_id) and (not dev_id or device.deviceid == dev_id))]) return True def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the honeywel thermostat.""" username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) region = config.get('region', 'eu').lower() if username is None or password is None: _LOGGER.error("Missing required configuration items %s or %s", CONF_USERNAME, CONF_PASSWORD) return False if region not in ('us', 'eu'): _LOGGER.error('Region `%s` is invalid (use either us or eu)', region) return False if region == 'us': return _setup_us(username, password, config, add_devices) else: return _setup_round(username, password, config, add_devices) class RoundThermostat(ClimateDevice): """Representation of a Honeywell Round Connected thermostat.""" # pylint: disable=too-many-instance-attributes, abstract-method def __init__(self, device, zone_id, master, away_temp): """Initialize the thermostat.""" self.device = device self._current_temperature = None self._target_temperature = None self._name = "round connected" self._id = zone_id self._master = master self._is_dhw = False self._away_temp = away_temp self._away = False self.update() @property def name(self): """Return the name of the honeywell, if any.""" return self._name @property def unit_of_measurement(self): """Return the unit of measurement.""" return TEMP_CELSIUS @property def current_temperature(self): """Return the current temperature.""" return self._current_temperature @property def target_temperature(self): """Return the temperature we try to reach.""" if self._is_dhw: return None return self._target_temperature def set_temperature(self, temperature): """Set new target temperature.""" self.device.set_temperature(self._name, temperature) @property def current_operation(self: ClimateDevice) -> str: """Get the current operation of the system.""" return getattr(self.device, 'system_mode', None) @property def is_away_mode_on(self): """Return true if away mode is on.""" return self._away def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None: """Set the HVAC mode for the thermostat.""" if hasattr(self.device, 'system_mode'): self.device.system_mode = operation_mode def turn_away_mode_on(self): """Turn away on. Evohome does have a proprietary away mode, but it doesn't really work the way it should. For example: If you set a temperature manually it doesn't get overwritten when away mode is switched on. """ self._away = True self.device.set_temperature(self._name, self._away_temp) def turn_away_mode_off(self): """Turn away off.""" self._away = False self.device.cancel_temp_override(self._name) def update(self): """Get the latest date.""" try: # Only refresh if this is the "master" device, # others will pick up the cache for val in self.device.temperatures(force_refresh=self._master): if val['id'] == self._id: data = val except StopIteration: _LOGGER.error("Did not receive any temperature data from the " "evohomeclient API.") return self._current_temperature = data['temp'] self._target_temperature = data['setpoint'] if data['thermostat'] == "DOMESTIC_HOT_WATER": self._name = "Hot Water" self._is_dhw = True else: self._name = data['name'] self._is_dhw = False # pylint: disable=abstract-method class HoneywellUSThermostat(ClimateDevice): """Representation of a Honeywell US Thermostat.""" def __init__(self, client, device): """Initialize the thermostat.""" self._client = client self._device = device @property def is_fan_on(self): """Return true if fan is on.""" return self._device.fan_running @property def name(self): """Return the name of the honeywell, if any.""" return self._device.name @property def unit_of_measurement(self): """Return the unit of measurement.""" return (TEMP_CELSIUS if self._device.temperature_unit == 'C' else TEMP_FAHRENHEIT) @property def current_temperature(self): """Return the current temperature.""" self._device.refresh() return self._device.current_temperature @property def target_temperature(self): """Return the temperature we try to reach.""" if self._device.system_mode == 'cool': return self._device.setpoint_cool else: return self._device.setpoint_heat @property def current_operation(self: ClimateDevice) -> str: """Return current operation ie. heat, cool, idle.""" return getattr(self._device, 'system_mode', None) def set_temperature(self, temperature): """Set target temperature.""" import somecomfort try: if self._device.system_mode == 'cool': self._device.setpoint_cool = temperature else: self._device.setpoint_heat = temperature except somecomfort.SomeComfortError: _LOGGER.error('Temperature %.1f out of range', temperature) @property def device_state_attributes(self): """Return the device specific state attributes.""" return {'fan': (self.is_fan_on and 'running' or 'idle'), 'fanmode': self._device.fan_mode, 'system_mode': self._device.system_mode} def turn_away_mode_on(self): """Turn away on.""" pass def turn_away_mode_off(self): """Turn away off.""" pass def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None: """Set the system mode (Cool, Heat, etc).""" if hasattr(self._device, 'system_mode'): self._device.system_mode = operation_mode