parent
a3db0ec231
commit
91227d9a2e
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue