mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
Refactor nest component into thermostat component
This commit is contained in:
parent
68b712adfd
commit
8f3a3f89a7
@ -33,7 +33,8 @@ platform=wemo
|
|||||||
# Optional: hard code the hosts (comma seperated) to avoid scanning the network
|
# Optional: hard code the hosts (comma seperated) to avoid scanning the network
|
||||||
# hosts=192.168.1.9,192.168.1.12
|
# hosts=192.168.1.9,192.168.1.12
|
||||||
|
|
||||||
[nest]
|
[thermostat]
|
||||||
|
platform=nest
|
||||||
# Required: username and password that are used to login to the Nest thermostat.
|
# Required: username and password that are used to login to the Nest thermostat.
|
||||||
username=myemail@mydomain.com
|
username=myemail@mydomain.com
|
||||||
password=mypassword
|
password=mypassword
|
||||||
|
@ -1,167 +0,0 @@
|
|||||||
"""
|
|
||||||
homeassistant.components.nest
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Provides functionality to interact with Nest Thermostats.
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
import homeassistant.util as util
|
|
||||||
from homeassistant.helpers import validate_config, ToggleDevice
|
|
||||||
from homeassistant.const import (ATTR_ENTITY_PICTURE, ATTR_UNIT_OF_MEASUREMENT,
|
|
||||||
ATTR_FRIENDLY_NAME, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID,
|
|
||||||
ATTR_NEW_TARGET_TEMPERATURE, SERVICE_SET_TARGET_TEMPERATURE)
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
# The domain of your component. Should be equal to the name of your component
|
|
||||||
DOMAIN = "nest"
|
|
||||||
ENTITY_AWAY_NAME = "state away"
|
|
||||||
ENTITY_TEMP_INSIDE_ID = "nest_get.temperature_inside"
|
|
||||||
ENTITY_TEMP_TARGET_ID = "nest_get.temperature_target"
|
|
||||||
ENTITY_TEMP_TARGET_SET = "nest_set.temperature_target"
|
|
||||||
|
|
||||||
ENTITY_AWAY_ID_FORMAT = DOMAIN + '.{}'
|
|
||||||
|
|
||||||
# Configuration key for the entity id we are targeting
|
|
||||||
CONF_USERNAME = 'username'
|
|
||||||
CONF_PASSWORD = 'password'
|
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DEPENDENCIES = []
|
|
||||||
|
|
||||||
def is_on(hass, entity_id=None):
|
|
||||||
|
|
||||||
return hass.states.is_state(entity_id, STATE_ON)
|
|
||||||
|
|
||||||
|
|
||||||
def turn_on(hass, entity_id=None):
|
|
||||||
""" Turns all or specified switch on. """
|
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
|
||||||
|
|
||||||
|
|
||||||
def turn_off(hass, entity_id=None):
|
|
||||||
""" Turns all or specified switch off. """
|
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
|
||||||
|
|
||||||
def set_temperature(hass, entity_id=None, new_temp=None):
|
|
||||||
""" Set new target temperature. """
|
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
|
||||||
if new_temp:
|
|
||||||
data[ATTR_NEW_TARGET_TEMPERATURE] = new_temp
|
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_SET_TARGET_TEMPERATURE, data)
|
|
||||||
|
|
||||||
def setup(hass, config):
|
|
||||||
""" Setup NEST thermostat. """
|
|
||||||
|
|
||||||
# Validate that all required config options are given
|
|
||||||
if not validate_config(config, {DOMAIN: [CONF_USERNAME, CONF_PASSWORD]}, _LOGGER):
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
import homeassistant.external.pynest.nest as pynest
|
|
||||||
except ImportError:
|
|
||||||
logging.getLogger(__name__).exception((
|
|
||||||
"Failed to import pynest. "))
|
|
||||||
return False
|
|
||||||
|
|
||||||
username = config[DOMAIN][CONF_USERNAME]
|
|
||||||
password = config[DOMAIN][CONF_PASSWORD]
|
|
||||||
|
|
||||||
thermostat = NestThermostat(pynest.Nest(username, password, None))
|
|
||||||
thermostat.entity_id = ENTITY_AWAY_ID_FORMAT.format(util.slugify(ENTITY_AWAY_NAME))
|
|
||||||
thermostat.nest.login()
|
|
||||||
|
|
||||||
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
|
|
||||||
def update_nest_state(now):
|
|
||||||
""" Update nest state. """
|
|
||||||
|
|
||||||
logging.getLogger(__name__).info("Update nest state")
|
|
||||||
|
|
||||||
thermostat.nest.get_status()
|
|
||||||
thermostat.update_ha_state(hass)
|
|
||||||
|
|
||||||
# Update state every 30 seconds
|
|
||||||
hass.track_time_change(update_nest_state, second=[0])
|
|
||||||
update_nest_state(None)
|
|
||||||
|
|
||||||
def handle_nest_service(service):
|
|
||||||
""" Handles calls to the nest services. """
|
|
||||||
if service.service == SERVICE_TURN_ON:
|
|
||||||
thermostat.turn_on()
|
|
||||||
else:
|
|
||||||
thermostat.turn_off()
|
|
||||||
|
|
||||||
thermostat.nest.get_status()
|
|
||||||
thermostat.update_ha_state(hass)
|
|
||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_nest_service)
|
|
||||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_nest_service)
|
|
||||||
|
|
||||||
def handle_nest_set_temperature(service):
|
|
||||||
if service.data[ATTR_NEW_TARGET_TEMPERATURE]:
|
|
||||||
new_temp = float(service.data[ATTR_NEW_TARGET_TEMPERATURE])
|
|
||||||
thermostat.nest.set_temperature(new_temp)
|
|
||||||
thermostat.nest.get_status()
|
|
||||||
nest_temp(datetime.now())
|
|
||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_SET_TARGET_TEMPERATURE, handle_nest_set_temperature)
|
|
||||||
|
|
||||||
def nest_temp(time):
|
|
||||||
""" Method to get the current inside and target temperatures. """
|
|
||||||
|
|
||||||
#thermostat.nest.get_status()
|
|
||||||
current_temperature = thermostat.nest.get_curtemp()
|
|
||||||
target_temperature = thermostat.nest.get_tartemp()
|
|
||||||
|
|
||||||
|
|
||||||
hass.states.set(ENTITY_TEMP_INSIDE_ID, current_temperature, {ATTR_UNIT_OF_MEASUREMENT: thermostat.nest.get_units(), ATTR_ENTITY_PICTURE:
|
|
||||||
"https://cdn2.iconfinder.com/data/icons/windows-8-metro-ui-weather-report/512/Temperature.png"})
|
|
||||||
|
|
||||||
hass.states.set(ENTITY_TEMP_TARGET_ID, target_temperature, {ATTR_UNIT_OF_MEASUREMENT: thermostat.nest.get_units(), ATTR_ENTITY_PICTURE:
|
|
||||||
"http://d1hwvnnkb0v1bo.cloudfront.net/content/art/app/icons/target_icon.jpg"})
|
|
||||||
|
|
||||||
hass.track_time_change(nest_temp, second=[10])
|
|
||||||
nest_temp(datetime.now())
|
|
||||||
|
|
||||||
# Tells the bootstrapper that the component was succesfully initialized
|
|
||||||
return True
|
|
||||||
|
|
||||||
class NestThermostat(ToggleDevice):
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, nest):
|
|
||||||
self.nest = nest
|
|
||||||
self.state_attr = {ATTR_FRIENDLY_NAME: ENTITY_AWAY_NAME, ATTR_ENTITY_PICTURE:
|
|
||||||
"http://support-assets.nest.com/images/tpzimages/app-energy-history-basic-away-icon.png"}
|
|
||||||
|
|
||||||
def get_name(self):
|
|
||||||
""" Returns the name of the switch if any. """
|
|
||||||
return ENTITY_AWAY_NAME
|
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
|
||||||
""" Turns away on. """
|
|
||||||
self.nest.set_away("away")
|
|
||||||
|
|
||||||
def turn_off(self):
|
|
||||||
""" Turns away off. """
|
|
||||||
self.nest.set_away("here")
|
|
||||||
|
|
||||||
def is_on(self):
|
|
||||||
""" True if away is on. """
|
|
||||||
return self.nest.is_away()
|
|
||||||
|
|
||||||
def get_state_attributes(self):
|
|
||||||
""" Returns optional state attributes. """
|
|
||||||
return self.state_attr
|
|
||||||
|
|
||||||
def set_temperature(self, temperature):
|
|
||||||
""" Set new target temperature """
|
|
||||||
self.nest.set_temperature(temperature)
|
|
||||||
|
|
169
homeassistant/components/thermostat/__init__.py
Normal file
169
homeassistant/components/thermostat/__init__.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.thermostat
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Provides functionality to interact with thermostats.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.helpers import (
|
||||||
|
extract_entity_ids, platform_devices_from_config)
|
||||||
|
import homeassistant.util as util
|
||||||
|
from homeassistant.helpers import Device
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
STATE_ON, STATE_OFF)
|
||||||
|
|
||||||
|
DOMAIN = "thermostat"
|
||||||
|
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||||
|
|
||||||
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
|
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
SERVICE_TURN_AWAY_MODE_ON = "turn_away_mode_on"
|
||||||
|
SERVICE_TURN_AWAY_MODE_OFF = "turn_away_mode_off"
|
||||||
|
SERVICE_SET_TEMPERATURE = "set_temperature"
|
||||||
|
|
||||||
|
ATTR_TARGET_TEMPERATURE = "target_temperature"
|
||||||
|
ATTR_AWAY_MODE = "away_mode"
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def turn_away_mode_on(hass, entity_id=None):
|
||||||
|
""" Turn all or specified thermostat away mode on. """
|
||||||
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_TURN_AWAY_MODE_ON, data)
|
||||||
|
|
||||||
|
|
||||||
|
def turn_away_mode_off(hass, entity_id=None):
|
||||||
|
""" Turn all or specified thermostat away mode off. """
|
||||||
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_TURN_AWAY_MODE_OFF, data)
|
||||||
|
|
||||||
|
|
||||||
|
def set_temperature(hass, temperature, entity_id=None):
|
||||||
|
""" Set new target temperature. """
|
||||||
|
data = {ATTR_TEMPERATURE: temperature}
|
||||||
|
|
||||||
|
if entity_id is not None:
|
||||||
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, data)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Setup thermostats. """
|
||||||
|
|
||||||
|
thermostats = platform_devices_from_config(
|
||||||
|
config, DOMAIN, hass, ENTITY_ID_FORMAT, _LOGGER)
|
||||||
|
|
||||||
|
if not thermostats:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
|
def update_state(now):
|
||||||
|
""" Update thermostat state. """
|
||||||
|
logging.getLogger(__name__).info("Update nest state")
|
||||||
|
|
||||||
|
for thermostat in thermostats.values():
|
||||||
|
thermostat.update_ha_state(hass, True)
|
||||||
|
|
||||||
|
# Update state every minute
|
||||||
|
hass.track_time_change(update_state, second=[0])
|
||||||
|
update_state(None)
|
||||||
|
|
||||||
|
def thermostat_service(service):
|
||||||
|
""" Handles calls to the services. """
|
||||||
|
# Convert the entity ids to valid light ids
|
||||||
|
target_thermostats = [thermostats[entity_id] for entity_id
|
||||||
|
in extract_entity_ids(hass, service)
|
||||||
|
if entity_id in thermostats]
|
||||||
|
|
||||||
|
if not target_thermostats:
|
||||||
|
target_thermostats = thermostats.values()
|
||||||
|
|
||||||
|
if service.service == SERVICE_TURN_AWAY_MODE_ON:
|
||||||
|
for thermostat in target_thermostats:
|
||||||
|
thermostat.turn_away_mode_on()
|
||||||
|
|
||||||
|
elif service.service == SERVICE_TURN_AWAY_MODE_OFF:
|
||||||
|
for thermostat in target_thermostats:
|
||||||
|
thermostat.turn_away_mode_off()
|
||||||
|
|
||||||
|
elif service.service == SERVICE_SET_TEMPERATURE:
|
||||||
|
temperature = util.convert(
|
||||||
|
service.data.get(ATTR_TEMPERATURE), float)
|
||||||
|
|
||||||
|
if temperature is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for thermostat in target_thermostats:
|
||||||
|
thermostat.nest.set_temperature(temperature)
|
||||||
|
|
||||||
|
for thermostat in target_thermostats:
|
||||||
|
thermostat.update_ha_state(hass, True)
|
||||||
|
|
||||||
|
hass.services.register(
|
||||||
|
DOMAIN, SERVICE_TURN_AWAY_MODE_OFF, thermostat_service)
|
||||||
|
|
||||||
|
hass.services.register(
|
||||||
|
DOMAIN, SERVICE_TURN_AWAY_MODE_ON, thermostat_service)
|
||||||
|
|
||||||
|
hass.services.register(
|
||||||
|
DOMAIN, SERVICE_SET_TEMPERATURE, thermostat_service)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class ThermostatDevice(Device):
|
||||||
|
""" Represents a thermostat within Home Assistant. """
|
||||||
|
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
|
||||||
|
def set_temperate(self, temperature):
|
||||||
|
""" Set new target temperature. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def turn_away_mode_on(self):
|
||||||
|
""" Turns away mode on. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def turn_away_mode_off(self):
|
||||||
|
""" Turns away mode off. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_away_mode_on(self):
|
||||||
|
""" Returns if away mode is on. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_target_temperature(self):
|
||||||
|
""" Returns the temperature we try to reach. """
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_unit_of_measurement(self):
|
||||||
|
""" Returns the unit of measurement. """
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_device_state_attributes(self):
|
||||||
|
""" Returns device specific state attributes. """
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_state_attributes(self):
|
||||||
|
""" Returns optional state attributes. """
|
||||||
|
data = {
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: self.get_unit_of_measurement(),
|
||||||
|
ATTR_AWAY_MODE: STATE_ON if self.is_away_mode_on() else STATE_OFF
|
||||||
|
}
|
||||||
|
|
||||||
|
target_temp = self.get_target_temperature()
|
||||||
|
|
||||||
|
if target_temp is not None:
|
||||||
|
data[ATTR_TARGET_TEMPERATURE] = target_temp
|
||||||
|
|
||||||
|
return data
|
81
homeassistant/components/thermostat/nest.py
Normal file
81
homeassistant/components/thermostat/nest.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"""
|
||||||
|
Adds support for Nest thermostats.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.thermostat import ThermostatDevice
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def get_devices(hass, config):
|
||||||
|
""" Gets Nest thermostats. """
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
username = config.get(CONF_USERNAME)
|
||||||
|
password = config.get(CONF_PASSWORD)
|
||||||
|
|
||||||
|
if username is None or password is None:
|
||||||
|
logger.error("Missing required configuration items %s or %s",
|
||||||
|
CONF_USERNAME, CONF_PASSWORD)
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
# pylint: disable=no-name-in-module, unused-variable
|
||||||
|
import homeassistant.external.pynest.nest as pynest # noqa
|
||||||
|
except ImportError:
|
||||||
|
logger.exception("Error while importing dependency phue.")
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
thermostat = NestThermostat(username, password)
|
||||||
|
|
||||||
|
return [thermostat]
|
||||||
|
|
||||||
|
|
||||||
|
class NestThermostat(ThermostatDevice):
|
||||||
|
""" Represents a Nest thermostat within Home Assistant. """
|
||||||
|
|
||||||
|
def __init__(self, username, password):
|
||||||
|
# pylint: disable=no-name-in-module, import-error
|
||||||
|
import homeassistant.external.pynest.nest as pynest
|
||||||
|
|
||||||
|
self.nest = pynest.Nest(username, password, None)
|
||||||
|
self.nest.login()
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
""" Returns the name of the nest, if any. """
|
||||||
|
return "Nest" # TODO Possible to get actual name from Nest device?
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
""" Returns the current temperature. """
|
||||||
|
return self.nest.get_curtemp()
|
||||||
|
|
||||||
|
def set_temperature(self, temperature):
|
||||||
|
""" Set new target temperature """
|
||||||
|
self.nest.set_temperature(temperature)
|
||||||
|
|
||||||
|
def turn_away_mode_on(self):
|
||||||
|
""" Turns away on. """
|
||||||
|
self.nest.set_away("away")
|
||||||
|
|
||||||
|
def turn_away_mode_off(self):
|
||||||
|
""" Turns away off. """
|
||||||
|
self.nest.set_away("here")
|
||||||
|
|
||||||
|
def is_away_mode_on(self):
|
||||||
|
""" Returns if away mode is on. """
|
||||||
|
return self.nest.is_away()
|
||||||
|
|
||||||
|
def get_target_temperature(self):
|
||||||
|
""" Returns the temperature we try to reach. """
|
||||||
|
return self.nest.get_tartemp()
|
||||||
|
|
||||||
|
def get_unit_of_measurement(self):
|
||||||
|
""" Returns the unit of measurement. """
|
||||||
|
return TEMP_FAHRENHEIT if self.nest.units == 'F' else TEMP_CELCIUS
|
||||||
|
|
||||||
|
def get_device_state_attributes(self):
|
||||||
|
""" Returns device specific state attributes. """
|
||||||
|
return {}
|
@ -53,8 +53,12 @@ ATTR_ENTITY_PICTURE = "entity_picture"
|
|||||||
# The unit of measurement if applicable
|
# The unit of measurement if applicable
|
||||||
ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement"
|
ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement"
|
||||||
|
|
||||||
# New target temperature for thermostats
|
# Temperature attribute
|
||||||
ATTR_NEW_TARGET_TEMPERATURE = "temp"
|
ATTR_TEMPERATURE = "temperature"
|
||||||
|
|
||||||
|
# #### MISC ####
|
||||||
|
TEMP_CELCIUS = "°C"
|
||||||
|
TEMP_FAHRENHEIT = "°F"
|
||||||
|
|
||||||
# #### SERVICES ####
|
# #### SERVICES ####
|
||||||
SERVICE_HOMEASSISTANT_STOP = "stop"
|
SERVICE_HOMEASSISTANT_STOP = "stop"
|
||||||
@ -71,8 +75,6 @@ SERVICE_MEDIA_PAUSE = "media_pause"
|
|||||||
SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
|
SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
|
||||||
SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
|
SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
|
||||||
|
|
||||||
SERVICE_SET_TARGET_TEMPERATURE = "set_target_temperature"
|
|
||||||
|
|
||||||
# #### API / REMOTE ####
|
# #### API / REMOTE ####
|
||||||
SERVER_PORT = 8123
|
SERVER_PORT = 8123
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user