mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +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
|
||||
# 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.
|
||||
username=myemail@mydomain.com
|
||||
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
|
||||
ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement"
|
||||
|
||||
# New target temperature for thermostats
|
||||
ATTR_NEW_TARGET_TEMPERATURE = "temp"
|
||||
# Temperature attribute
|
||||
ATTR_TEMPERATURE = "temperature"
|
||||
|
||||
# #### MISC ####
|
||||
TEMP_CELCIUS = "°C"
|
||||
TEMP_FAHRENHEIT = "°F"
|
||||
|
||||
# #### SERVICES ####
|
||||
SERVICE_HOMEASSISTANT_STOP = "stop"
|
||||
@ -71,8 +75,6 @@ SERVICE_MEDIA_PAUSE = "media_pause"
|
||||
SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
|
||||
SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
|
||||
|
||||
SERVICE_SET_TARGET_TEMPERATURE = "set_target_temperature"
|
||||
|
||||
# #### API / REMOTE ####
|
||||
SERVER_PORT = 8123
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user