Refactory nest component/platforms (#4219)

* Refactory nest component/platforms
pull/4221/head
Pascal Vizeli 2016-11-05 01:22:47 +01:00 committed by William Scanlon
parent a3db0ec231
commit 91227d9a2e
4 changed files with 178 additions and 108 deletions

View File

@ -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))

View File

@ -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

View File

@ -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.")

View File

@ -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)