diff --git a/homeassistant/components/binary_sensor/nest.py b/homeassistant/components/binary_sensor/nest.py index 4dfe4d58b99..65fe6041f34 100644 --- a/homeassistant/components/binary_sensor/nest.py +++ b/homeassistant/components/binary_sensor/nest.py @@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) from homeassistant.components.sensor.nest import NestSensor from homeassistant.const import (CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS) -import homeassistant.components.nest as nest +from homeassistant.components.nest import DATA_NEST import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['nest'] @@ -35,9 +35,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup Nest binary sensors.""" + nest = hass.data[DATA_NEST] + + all_sensors = [] for structure, device in nest.devices(): - add_devices([NestBinarySensor(structure, device, variable) - for variable in config[CONF_MONITORED_CONDITIONS]]) + all_sensors.extend( + [NestBinarySensor(structure, device, variable) + for variable in config[CONF_MONITORED_CONDITIONS]]) + + add_devices(all_sensors, True) class NestBinarySensor(NestSensor, BinarySensorDevice): @@ -46,4 +52,8 @@ class NestBinarySensor(NestSensor, BinarySensorDevice): @property def is_on(self): """True if the binary sensor is on.""" - return bool(getattr(self.device, self.variable)) + return self._state + + def update(self): + """Retrieve latest state.""" + self._state = bool(getattr(self.device, self.variable)) diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py index 5020bb441d5..402cc2b2498 100644 --- a/homeassistant/components/climate/nest.py +++ b/homeassistant/components/climate/nest.py @@ -5,8 +5,10 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.nest/ """ import logging + import voluptuous as vol -import homeassistant.components.nest as nest + +from homeassistant.components.nest import DATA_NEST from homeassistant.components.climate import ( STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice, PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -26,8 +28,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Nest thermostat.""" temp_unit = hass.config.units.temperature_unit - add_devices([NestThermostat(structure, device, temp_unit) - for structure, device in nest.devices()]) + add_devices( + [NestThermostat(structure, device, temp_unit) + for structure, device in hass.data[DATA_NEST].devices()], + True + ) class NestThermostat(ClimateDevice): @@ -53,18 +58,33 @@ class NestThermostat(ClimateDevice): if self.device.can_heat and self.device.can_cool: self._operation_list.append(STATE_AUTO) + # feature of device + self._has_humidifier = self.device.has_humidifier + self._has_dehumidifier = self.device.has_dehumidifier + self._has_fan = self.device.has_fan + + # data attributes + self._away = None + self._location = None + self._name = None + self._humidity = None + self._target_humidity = None + self._target_temperature = None + self._temperature = None + self._mode = None + self._fan = None + self._away_temperature = None + @property def name(self): """Return the name of the nest, if any.""" - location = self.device.where - name = self.device.name - if location is None: - return name + if self._location is None: + return self._name else: - if name == '': - return location.capitalize() + if self._name == '': + return self._location.capitalize() else: - return location.capitalize() + '(' + name + ')' + return self._location.capitalize() + '(' + self._name + ')' @property def temperature_unit(self): @@ -74,11 +94,11 @@ class NestThermostat(ClimateDevice): @property def device_state_attributes(self): """Return the device specific state attributes.""" - if self.device.has_humidifier or self.device.has_dehumidifier: + if self._has_humidifier or self._has_dehumidifier: # Move these to Thermostat Device and make them global return { - "humidity": self.device.humidity, - "target_humidity": self.device.target_humidity, + "humidity": self._humidity, + "target_humidity": self._target_humidity, } else: # No way to control humidity not show setting @@ -87,18 +107,18 @@ class NestThermostat(ClimateDevice): @property def current_temperature(self): """Return the current temperature.""" - return self.device.temperature + return self._temperature @property def current_operation(self): """Return current operation ie. heat, cool, idle.""" - if self.device.mode == 'cool': + if self._mode == 'cool': return STATE_COOL - elif self.device.mode == 'heat': + elif self._mode == 'heat': return STATE_HEAT - elif self.device.mode == 'range': + elif self._mode == 'range': return STATE_AUTO - elif self.device.mode == 'off': + elif self._mode == 'off': return STATE_OFF else: return STATE_UNKNOWN @@ -106,37 +126,37 @@ class NestThermostat(ClimateDevice): @property def target_temperature(self): """Return the temperature we try to reach.""" - if self.device.mode != 'range' and not self.is_away_mode_on: - return self.device.target + if self._mode != 'range' and not self.is_away_mode_on: + return self._target_temperature else: return None @property def target_temperature_low(self): """Return the lower bound temperature we try to reach.""" - if self.is_away_mode_on and self.device.away_temperature[0]: + if self.is_away_mode_on and self._away_temperature[0]: # away_temperature is always a low, high tuple - return self.device.away_temperature[0] - if self.device.mode == 'range': - return self.device.target[0] + return self._away_temperature[0] + if self._mode == 'range': + return self._target_temperature[0] else: return None @property def target_temperature_high(self): """Return the upper bound temperature we try to reach.""" - if self.is_away_mode_on and self.device.away_temperature[1]: + if self.is_away_mode_on and self._away_temperature[1]: # away_temperature is always a low, high tuple - return self.device.away_temperature[1] - if self.device.mode == 'range': - return self.device.target[1] + return self._away_temperature[1] + if self._mode == 'range': + return self._target_temperature[1] else: return None @property def is_away_mode_on(self): """Return if away mode is on.""" - return self.structure.away + return self._away def set_temperature(self, **kwargs): """Set new target temperature.""" @@ -144,7 +164,7 @@ class NestThermostat(ClimateDevice): target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) if target_temp_low is not None and target_temp_high is not None: - if self.device.mode == 'range': + if self._mode == 'range': temp = (target_temp_low, target_temp_high) else: temp = kwargs.get(ATTR_TEMPERATURE) @@ -178,9 +198,9 @@ class NestThermostat(ClimateDevice): @property def current_fan_mode(self): """Return whether the fan is on.""" - if self.device.has_fan: + if self._has_fan: # Return whether the fan is on - return STATE_ON if self.device.fan else STATE_AUTO + return STATE_ON if self._fan else STATE_AUTO else: # No Fan available so disable slider return None @@ -197,7 +217,7 @@ class NestThermostat(ClimateDevice): @property def min_temp(self): """Identify min_temp in Nest API or defaults if not available.""" - temp = self.device.away_temperature.low + temp = self._away_temperature[0] if temp is None: return super().min_temp else: @@ -206,12 +226,21 @@ class NestThermostat(ClimateDevice): @property def max_temp(self): """Identify max_temp in Nest API or defaults if not available.""" - temp = self.device.away_temperature.high + temp = self._away_temperature[1] if temp is None: return super().max_temp else: return temp def update(self): - """Python-nest has its own mechanism for staying up to date.""" - pass + """Cache value from Python-nest.""" + self._location = self.device.where + self._name = self.device.name + self._humidity = self.device.humidity, + self._target_humidity = self.device.target_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 + self._away_temperature = self.device.away_temperature diff --git a/homeassistant/components/nest.py b/homeassistant/components/nest.py index b8aa1d1c70a..9f766efe693 100644 --- a/homeassistant/components/nest.py +++ b/homeassistant/components/nest.py @@ -18,9 +18,8 @@ REQUIREMENTS = ['python-nest==2.11.0'] DOMAIN = 'nest' -NEST = None +DATA_NEST = 'nest' -STRUCTURES_TO_INCLUDE = None CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -31,52 +30,58 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -def devices(): - """Generator returning list of devices and their location.""" - try: - for structure in NEST.structures: - if structure.name in STRUCTURES_TO_INCLUDE: - for device in structure.devices: - yield (structure, device) - else: - _LOGGER.debug("Ignoring structure %s, not in %s", - structure.name, STRUCTURES_TO_INCLUDE) - except socket.error: - _LOGGER.error("Connection error logging into the nest web service.") - - -def protect_devices(): - """Generator returning list of protect devices.""" - try: - for structure in NEST.structures: - if structure.name in STRUCTURES_TO_INCLUDE: - for device in structure.protectdevices: - yield(structure, device) - else: - _LOGGER.info("Ignoring structure %s, not in %s", - structure.name, STRUCTURES_TO_INCLUDE) - except socket.error: - _LOGGER.error("Connection error logging into the nest web service.") - - -# pylint: disable=unused-argument def setup(hass, config): """Setup the Nest thermostat component.""" - global NEST - global STRUCTURES_TO_INCLUDE + import nest conf = config[DOMAIN] username = conf[CONF_USERNAME] password = conf[CONF_PASSWORD] - import nest + nest = nest.Nest(username, password) + hass.data[DATA_NEST] = NestDevice(hass, conf, nest) - NEST = nest.Nest(username, password) - - if CONF_STRUCTURE not in conf: - STRUCTURES_TO_INCLUDE = [s.name for s in NEST.structures] - else: - STRUCTURES_TO_INCLUDE = conf[CONF_STRUCTURE] - - _LOGGER.debug("Structures to include: %s", STRUCTURES_TO_INCLUDE) return True + + +class NestDevice(object): + """Structure Nest functions for hass.""" + + def __init__(self, hass, conf, nest): + """Init Nest Devices.""" + self.hass = hass + self.nest = nest + + if CONF_STRUCTURE not in conf: + self._structure = [s.name for s in nest.structures] + else: + self._structure = conf[CONF_STRUCTURE] + _LOGGER.debug("Structures to include: %s", self._structure) + + def devices(self): + """Generator returning list of devices and their location.""" + try: + for structure in self.nest.structures: + if structure.name in self._structure: + for device in structure.devices: + yield (structure, device) + else: + _LOGGER.debug("Ignoring structure %s, not in %s", + structure.name, self._structure) + except socket.error: + _LOGGER.error( + "Connection error logging into the nest web service.") + + def protect_devices(self): + """Generator returning list of protect devices.""" + try: + for structure in self.nest.structures: + if structure.name in self._structure: + for device in structure.protectdevices: + yield(structure, device) + else: + _LOGGER.info("Ignoring structure %s, not in %s", + structure.name, self._structure) + except socket.error: + _LOGGER.error( + "Connection error logging into the nest web service.") diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py index 98d018a7c0b..ccf8be84adc 100644 --- a/homeassistant/components/sensor/nest.py +++ b/homeassistant/components/sensor/nest.py @@ -8,7 +8,7 @@ from itertools import chain import voluptuous as vol -import homeassistant.components.nest as nest +from homeassistant.components.nest import DATA_NEST, DOMAIN from homeassistant.helpers.entity import Entity from homeassistant.const import ( TEMP_CELSIUS, CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS @@ -41,7 +41,7 @@ _VALID_SENSOR_TYPES = SENSOR_TYPES + SENSOR_TEMP_TYPES + PROTECT_VARS + \ list(WEATHER_VARS.keys()) PLATFORM_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): nest.DOMAIN, + vol.Required(CONF_PLATFORM): DOMAIN, vol.Optional(CONF_SCAN_INTERVAL): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Required(CONF_MONITORED_CONDITIONS): [vol.In(_VALID_SENSOR_TYPES)], @@ -50,6 +50,9 @@ PLATFORM_SCHEMA = vol.Schema({ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Nest Sensor.""" + nest = hass.data[DATA_NEST] + + all_sensors = [] for structure, device in chain(nest.devices(), nest.protect_devices()): sensors = [NestBasicSensor(structure, device, variable) for variable in config[CONF_MONITORED_CONDITIONS] @@ -64,8 +67,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors += [NestProtectSensor(structure, device, variable) for variable in config[CONF_MONITORED_CONDITIONS] if variable in PROTECT_VARS and is_protect(device)] + all_sensors.extend(sensors) - add_devices(sensors) + add_devices(all_sensors, True) def is_thermostat(device): @@ -87,19 +91,23 @@ class NestSensor(Entity): self.device = device self.variable = variable + # device specific + self._location = self.device.where + self._name = self.device.name + self._state = None + @property def name(self): """Return the name of the nest, if any.""" - location = self.device.where - name = self.device.name - if location is None: - return "{} {}".format(name, self.variable) + if self._location is None: + return "{} {}".format(self._name, self.variable) else: - if name == '': - return "{} {}".format(location.capitalize(), self.variable) + if self._name == '': + return "{} {}".format(self._location.capitalize(), + self.variable) else: - return "{}({}){}".format(location.capitalize(), - name, + return "{}({}){}".format(self._location.capitalize(), + self._name, self.variable) @@ -109,16 +117,20 @@ class NestBasicSensor(NestSensor): @property def state(self): """Return the state of the sensor.""" - if self.variable == 'operation_mode': - return getattr(self.device, "mode") - else: - return getattr(self.device, self.variable) + return self._state @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" return SENSOR_UNITS.get(self.variable, None) + def update(self): + """Retrieve latest state.""" + if self.variable == 'operation_mode': + self._state = getattr(self.device, "mode") + else: + self._state = getattr(self.device, self.variable) + class NestTempSensor(NestSensor): """Representation of a Nest Temperature sensor.""" @@ -131,15 +143,19 @@ class NestTempSensor(NestSensor): @property def state(self): """Return the state of the sensor.""" + return self._state + + def update(self): + """Retrieve latest state.""" temp = getattr(self.device, self.variable) if temp is None: - return None + self._state = None if isinstance(temp, tuple): low, high = temp - return "%s-%s" % (int(low), int(high)) + self._state = "%s-%s" % (int(low), int(high)) else: - return round(temp, 1) + self._state = round(temp, 1) class NestWeatherSensor(NestSensor): @@ -148,10 +164,16 @@ class NestWeatherSensor(NestSensor): @property def state(self): """Return the state of the sensor.""" + return self._state + + def update(self): + """Retrieve latest state.""" if self.variable == 'kph' or self.variable == 'direction': - return getattr(self.structure.weather.current.wind, self.variable) + self._state = getattr(self.structure.weather.current.wind, + self.variable) else: - return getattr(self.structure.weather.current, self.variable) + self._state = getattr(self.structure.weather.current, + self.variable) @property def unit_of_measurement(self): @@ -165,20 +187,24 @@ class NestProtectSensor(NestSensor): @property def state(self): """Return the state of the sensor.""" + return self._state + + def update(self): + """Retrieve latest state.""" state = getattr(self.device, self.variable) if self.variable == 'battery_level': - return getattr(self.device, self.variable) + self._state = getattr(self.device, self.variable) else: if state == 0: - return 'Ok' + self._state = 'Ok' if state == 1 or state == 2: - return 'Warning' + self._state = 'Warning' if state == 3: - return 'Emergency' + self._state = 'Emergency' - return 'Unknown' + self._state = 'Unknown' @property def name(self): """Return the name of the nest, if any.""" - return "{} {}".format(self.device.where.capitalize(), self.variable) + return "{} {}".format(self._location.capitalize(), self.variable)