mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Migrate Thermostat and HVAC component to climate component (#2825)
* First draft for climate * Updates for thermostats
This commit is contained in:
parent
0abc50e844
commit
ada4de3ffb
@ -109,6 +109,12 @@ omit =
|
||||
homeassistant/components/camera/generic.py
|
||||
homeassistant/components/camera/mjpeg.py
|
||||
homeassistant/components/camera/rpi_camera.py
|
||||
homeassistant/components/climate/eq3btsmart.py
|
||||
homeassistant/components/climate/heatmiser.py
|
||||
homeassistant/components/climate/homematic.py
|
||||
homeassistant/components/climate/knx.py
|
||||
homeassistant/components/climate/proliphix.py
|
||||
homeassistant/components/climate/radiotherm.py
|
||||
homeassistant/components/device_tracker/actiontec.py
|
||||
homeassistant/components/device_tracker/aruba.py
|
||||
homeassistant/components/device_tracker/asuswrt.py
|
||||
|
535
homeassistant/components/climate/__init__.py
Normal file
535
homeassistant/components/climate/__init__.py
Normal file
@ -0,0 +1,535 @@
|
||||
"""
|
||||
Provides functionality to interact with climate devices.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate/
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from numbers import Number
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
import homeassistant.util as util
|
||||
from homeassistant.util.temperature import convert as convert_temperature
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN,
|
||||
TEMP_CELSIUS)
|
||||
|
||||
DOMAIN = "climate"
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||
SCAN_INTERVAL = 60
|
||||
|
||||
SERVICE_SET_AWAY_MODE = "set_away_mode"
|
||||
SERVICE_SET_AUX_HEAT = "set_aux_heat"
|
||||
SERVICE_SET_TEMPERATURE = "set_temperature"
|
||||
SERVICE_SET_FAN_MODE = "set_fan_mode"
|
||||
SERVICE_SET_OPERATION_MODE = "set_operation_mode"
|
||||
SERVICE_SET_SWING_MODE = "set_swing_mode"
|
||||
SERVICE_SET_HUMIDITY = "set_humidity"
|
||||
|
||||
STATE_HEAT = "heat"
|
||||
STATE_COOL = "cool"
|
||||
STATE_IDLE = "idle"
|
||||
STATE_AUTO = "auto"
|
||||
STATE_DRY = "dry"
|
||||
STATE_FAN_ONLY = "fan_only"
|
||||
|
||||
ATTR_CURRENT_TEMPERATURE = "current_temperature"
|
||||
ATTR_MAX_TEMP = "max_temp"
|
||||
ATTR_MIN_TEMP = "min_temp"
|
||||
ATTR_AWAY_MODE = "away_mode"
|
||||
ATTR_AUX_HEAT = "aux_heat"
|
||||
ATTR_FAN_MODE = "fan_mode"
|
||||
ATTR_FAN_LIST = "fan_list"
|
||||
ATTR_CURRENT_HUMIDITY = "current_humidity"
|
||||
ATTR_HUMIDITY = "humidity"
|
||||
ATTR_MAX_HUMIDITY = "max_humidity"
|
||||
ATTR_MIN_HUMIDITY = "min_humidity"
|
||||
ATTR_OPERATION_MODE = "operation_mode"
|
||||
ATTR_OPERATION_LIST = "operation_list"
|
||||
ATTR_SWING_MODE = "swing_mode"
|
||||
ATTR_SWING_LIST = "swing_list"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SET_AWAY_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_AWAY_MODE): cv.boolean,
|
||||
})
|
||||
SET_AUX_HEAT_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_AUX_HEAT): cv.boolean,
|
||||
})
|
||||
SET_TEMPERATURE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_TEMPERATURE): vol.Coerce(float),
|
||||
})
|
||||
SET_FAN_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_FAN_MODE): cv.string,
|
||||
})
|
||||
SET_OPERATION_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_OPERATION_MODE): cv.string,
|
||||
})
|
||||
SET_HUMIDITY_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_HUMIDITY): vol.Coerce(float),
|
||||
})
|
||||
SET_SWING_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_SWING_MODE): cv.string,
|
||||
})
|
||||
|
||||
|
||||
def set_away_mode(hass, away_mode, entity_id=None):
|
||||
"""Turn all or specified climate devices away mode on."""
|
||||
data = {
|
||||
ATTR_AWAY_MODE: away_mode
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
|
||||
|
||||
|
||||
def set_aux_heat(hass, aux_heat, entity_id=None):
|
||||
"""Turn all or specified climate devices auxillary heater on."""
|
||||
data = {
|
||||
ATTR_AUX_HEAT: aux_heat
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, 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 set_humidity(hass, humidity, entity_id=None):
|
||||
"""Set new target humidity."""
|
||||
data = {ATTR_HUMIDITY: humidity}
|
||||
|
||||
if entity_id is not None:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_HUMIDITY, data)
|
||||
|
||||
|
||||
def set_fan_mode(hass, fan, entity_id=None):
|
||||
"""Set all or specified climate devices fan mode on."""
|
||||
data = {ATTR_FAN_MODE: fan}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
|
||||
|
||||
|
||||
def set_operation_mode(hass, operation_mode, entity_id=None):
|
||||
"""Set new target operation mode."""
|
||||
data = {ATTR_OPERATION_MODE: operation_mode}
|
||||
|
||||
if entity_id is not None:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, data)
|
||||
|
||||
|
||||
def set_swing_mode(hass, swing_mode, entity_id=None):
|
||||
"""Set new target swing mode."""
|
||||
data = {ATTR_SWING_MODE: swing_mode}
|
||||
|
||||
if entity_id is not None:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data)
|
||||
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def setup(hass, config):
|
||||
"""Setup climate devices."""
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
||||
component.setup(config)
|
||||
|
||||
descriptions = load_yaml_config_file(
|
||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
def away_mode_set_service(service):
|
||||
"""Set away mode on target climate devices."""
|
||||
target_climate = component.extract_from_service(service)
|
||||
|
||||
away_mode = service.data.get(ATTR_AWAY_MODE)
|
||||
|
||||
if away_mode is None:
|
||||
_LOGGER.error(
|
||||
"Received call to %s without attribute %s",
|
||||
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
|
||||
return
|
||||
|
||||
for climate in target_climate:
|
||||
if away_mode:
|
||||
climate.turn_away_mode_on()
|
||||
else:
|
||||
climate.turn_away_mode_off()
|
||||
|
||||
if climate.should_poll:
|
||||
climate.update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SET_AWAY_MODE, away_mode_set_service,
|
||||
descriptions.get(SERVICE_SET_AWAY_MODE),
|
||||
schema=SET_AWAY_MODE_SCHEMA)
|
||||
|
||||
def aux_heat_set_service(service):
|
||||
"""Set auxillary heater on target climate devices."""
|
||||
target_climate = component.extract_from_service(service)
|
||||
|
||||
aux_heat = service.data.get(ATTR_AUX_HEAT)
|
||||
|
||||
if aux_heat is None:
|
||||
_LOGGER.error(
|
||||
"Received call to %s without attribute %s",
|
||||
SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT)
|
||||
return
|
||||
|
||||
for climate in target_climate:
|
||||
if aux_heat:
|
||||
climate.turn_aux_heat_on()
|
||||
else:
|
||||
climate.turn_aux_heat_off()
|
||||
|
||||
if climate.should_poll:
|
||||
climate.update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SET_AUX_HEAT, aux_heat_set_service,
|
||||
descriptions.get(SERVICE_SET_AUX_HEAT),
|
||||
schema=SET_AUX_HEAT_SCHEMA)
|
||||
|
||||
def temperature_set_service(service):
|
||||
"""Set temperature on the target climate devices."""
|
||||
target_climate = component.extract_from_service(service)
|
||||
|
||||
temperature = util.convert(
|
||||
service.data.get(ATTR_TEMPERATURE), float)
|
||||
|
||||
if temperature is None:
|
||||
_LOGGER.error(
|
||||
"Received call to %s without attribute %s",
|
||||
SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE)
|
||||
return
|
||||
|
||||
for climate in target_climate:
|
||||
climate.set_temperature(convert_temperature(
|
||||
temperature, hass.config.units.temperature_unit,
|
||||
climate.unit_of_measurement))
|
||||
|
||||
if climate.should_poll:
|
||||
climate.update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SET_TEMPERATURE, temperature_set_service,
|
||||
descriptions.get(SERVICE_SET_TEMPERATURE),
|
||||
schema=SET_TEMPERATURE_SCHEMA)
|
||||
|
||||
def humidity_set_service(service):
|
||||
"""Set humidity on the target climate devices."""
|
||||
target_climate = component.extract_from_service(service)
|
||||
|
||||
humidity = service.data.get(ATTR_HUMIDITY)
|
||||
|
||||
if humidity is None:
|
||||
_LOGGER.error(
|
||||
"Received call to %s without attribute %s",
|
||||
SERVICE_SET_HUMIDITY, ATTR_HUMIDITY)
|
||||
return
|
||||
|
||||
for climate in target_climate:
|
||||
climate.set_humidity(humidity)
|
||||
|
||||
if climate.should_poll:
|
||||
climate.update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SET_HUMIDITY, humidity_set_service,
|
||||
descriptions.get(SERVICE_SET_HUMIDITY),
|
||||
schema=SET_HUMIDITY_SCHEMA)
|
||||
|
||||
def fan_mode_set_service(service):
|
||||
"""Set fan mode on target climate devices."""
|
||||
target_climate = component.extract_from_service(service)
|
||||
|
||||
fan = service.data.get(ATTR_FAN_MODE)
|
||||
|
||||
if fan is None:
|
||||
_LOGGER.error(
|
||||
"Received call to %s without attribute %s",
|
||||
SERVICE_SET_FAN_MODE, ATTR_FAN_MODE)
|
||||
return
|
||||
|
||||
for climate in target_climate:
|
||||
climate.set_fan_mode(fan)
|
||||
|
||||
if climate.should_poll:
|
||||
climate.update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SET_FAN_MODE, fan_mode_set_service,
|
||||
descriptions.get(SERVICE_SET_FAN_MODE),
|
||||
schema=SET_FAN_MODE_SCHEMA)
|
||||
|
||||
def operation_set_service(service):
|
||||
"""Set operating mode on the target climate devices."""
|
||||
target_climate = component.extract_from_service(service)
|
||||
|
||||
operation_mode = service.data.get(ATTR_OPERATION_MODE)
|
||||
|
||||
if operation_mode is None:
|
||||
_LOGGER.error(
|
||||
"Received call to %s without attribute %s",
|
||||
SERVICE_SET_OPERATION_MODE, ATTR_OPERATION_MODE)
|
||||
return
|
||||
|
||||
for climate in target_climate:
|
||||
climate.set_operation_mode(operation_mode)
|
||||
|
||||
if climate.should_poll:
|
||||
climate.update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SET_OPERATION_MODE, operation_set_service,
|
||||
descriptions.get(SERVICE_SET_OPERATION_MODE),
|
||||
schema=SET_OPERATION_MODE_SCHEMA)
|
||||
|
||||
def swing_set_service(service):
|
||||
"""Set swing mode on the target climate devices."""
|
||||
target_climate = component.extract_from_service(service)
|
||||
|
||||
swing_mode = service.data.get(ATTR_SWING_MODE)
|
||||
|
||||
if swing_mode is None:
|
||||
_LOGGER.error(
|
||||
"Received call to %s without attribute %s",
|
||||
SERVICE_SET_SWING_MODE, ATTR_SWING_MODE)
|
||||
return
|
||||
|
||||
for climate in target_climate:
|
||||
climate.set_swing_mode(swing_mode)
|
||||
|
||||
if climate.should_poll:
|
||||
climate.update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SET_SWING_MODE, swing_set_service,
|
||||
descriptions.get(SERVICE_SET_SWING_MODE),
|
||||
schema=SET_SWING_MODE_SCHEMA)
|
||||
return True
|
||||
|
||||
|
||||
class ClimateDevice(Entity):
|
||||
"""Representation of a climate device."""
|
||||
|
||||
# pylint: disable=too-many-public-methods,no-self-use
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current state."""
|
||||
return self.current_operation or STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
data = {
|
||||
ATTR_CURRENT_TEMPERATURE:
|
||||
self._convert_for_display(self.current_temperature),
|
||||
ATTR_MIN_TEMP: self._convert_for_display(self.min_temp),
|
||||
ATTR_MAX_TEMP: self._convert_for_display(self.max_temp),
|
||||
ATTR_TEMPERATURE:
|
||||
self._convert_for_display(self.target_temperature),
|
||||
}
|
||||
|
||||
humidity = self.target_humidity
|
||||
if humidity is not None:
|
||||
data[ATTR_HUMIDITY] = humidity
|
||||
data[ATTR_CURRENT_HUMIDITY] = self.current_humidity
|
||||
data[ATTR_MIN_HUMIDITY] = self.min_humidity
|
||||
data[ATTR_MAX_HUMIDITY] = self.max_humidity
|
||||
|
||||
fan_mode = self.current_fan_mode
|
||||
if fan_mode is not None:
|
||||
data[ATTR_FAN_MODE] = fan_mode
|
||||
data[ATTR_FAN_LIST] = self.fan_list
|
||||
|
||||
operation_mode = self.current_operation
|
||||
if operation_mode is not None:
|
||||
data[ATTR_OPERATION_MODE] = operation_mode
|
||||
data[ATTR_OPERATION_LIST] = self.operation_list
|
||||
|
||||
swing_mode = self.current_swing_mode
|
||||
if swing_mode is not None:
|
||||
data[ATTR_SWING_MODE] = swing_mode
|
||||
data[ATTR_SWING_LIST] = self.swing_list
|
||||
|
||||
is_away = self.is_away_mode_on
|
||||
if is_away is not None:
|
||||
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
|
||||
|
||||
is_aux_heat = self.is_aux_heat_on
|
||||
if is_aux_heat is not None:
|
||||
data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
"""Return the current humidity."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_humidity(self):
|
||||
"""Return the humidity we try to reach."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of available operation modes."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
"""Return true if aux heater."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
"""List of available fan modes."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
"""List of available swing modes."""
|
||||
return None
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_humidity(self, humidity):
|
||||
"""Set new target humidity."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_fan_mode(self, fan):
|
||||
"""Set new target fan mode."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing operation."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away mode on."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away mode off."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn auxillary heater on."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn auxillary heater off."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
return convert_temperature(7, TEMP_CELSIUS, self.unit_of_measurement)
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
return convert_temperature(35, TEMP_CELSIUS, self.unit_of_measurement)
|
||||
|
||||
@property
|
||||
def min_humidity(self):
|
||||
"""Return the minimum humidity."""
|
||||
return 30
|
||||
|
||||
@property
|
||||
def max_humidity(self):
|
||||
"""Return the maximum humidity."""
|
||||
return 99
|
||||
|
||||
def _convert_for_display(self, temp):
|
||||
"""Convert temperature into preferred units for display purposes."""
|
||||
if temp is None or not isinstance(temp, Number):
|
||||
return temp
|
||||
|
||||
value = convert_temperature(temp, self.unit_of_measurement,
|
||||
self.hass.config.units.temperature_unit)
|
||||
|
||||
if self.hass.config.units.temperature_unit is TEMP_CELSIUS:
|
||||
decimal_count = 1
|
||||
else:
|
||||
# Users of fahrenheit generally expect integer units.
|
||||
decimal_count = 0
|
||||
|
||||
return round(value, decimal_count)
|
164
homeassistant/components/climate/demo.py
Normal file
164
homeassistant/components/climate/demo.py
Normal file
@ -0,0 +1,164 @@
|
||||
"""
|
||||
Demo platform that offers a fake climate device.
|
||||
|
||||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/demo/
|
||||
"""
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Demo climate devices."""
|
||||
add_devices([
|
||||
DemoClimate("HeatPump", 68, TEMP_FAHRENHEIT, None, 77, "Auto Low",
|
||||
None, None, "Auto", "Heat", None),
|
||||
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, 22, "On High",
|
||||
67, 54, "Off", "Cool", False),
|
||||
])
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-public-methods
|
||||
class DemoClimate(ClimateDevice):
|
||||
"""Representation of a demo climate device."""
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, name, target_temperature, unit_of_measurement,
|
||||
away, current_temperature, current_fan_mode,
|
||||
target_humidity, current_humidity, current_swing_mode,
|
||||
current_operation, aux):
|
||||
"""Initialize the climate device."""
|
||||
self._name = name
|
||||
self._target_temperature = target_temperature
|
||||
self._target_humidity = target_humidity
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._away = away
|
||||
self._current_temperature = current_temperature
|
||||
self._current_humidity = current_humidity
|
||||
self._current_fan_mode = current_fan_mode
|
||||
self._current_operation = current_operation
|
||||
self._aux = aux
|
||||
self._current_swing_mode = current_swing_mode
|
||||
self._fan_list = ["On Low", "On High", "Auto Low", "Auto High", "Off"]
|
||||
self._operation_list = ["Heat", "Cool", "Auto Changeover", "Off"]
|
||||
self._swing_list = ["Auto", 1, 2, 3, "Off"]
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Polling not needed for a demo climate device."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the climate device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._target_temperature
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
"""Return the current humidity."""
|
||||
return self._current_humidity
|
||||
|
||||
@property
|
||||
def target_humidity(self):
|
||||
"""Return the humidity we try to reach."""
|
||||
return self._target_humidity
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of available operation modes."""
|
||||
return self._operation_list
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return if away mode is on."""
|
||||
return self._away
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._aux
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._current_fan_mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
"""List of available fan modes."""
|
||||
return self._fan_list
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
self._target_temperature = temperature
|
||||
self.update_ha_state()
|
||||
|
||||
def set_humidity(self, humidity):
|
||||
"""Set new target temperature."""
|
||||
self._target_humidity = humidity
|
||||
self.update_ha_state()
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
"""Set new target temperature."""
|
||||
self._current_swing_mode = swing_mode
|
||||
self.update_ha_state()
|
||||
|
||||
def set_fan_mode(self, fan):
|
||||
"""Set new target temperature."""
|
||||
self._current_fan_mode = fan
|
||||
self.update_ha_state()
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target temperature."""
|
||||
self._current_operation = operation_mode
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
"""Return the swing setting."""
|
||||
return self._current_swing_mode
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
"""List of available swing modes."""
|
||||
return self._swing_list
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away mode on."""
|
||||
self._away = True
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away mode off."""
|
||||
self._away = False
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn away auxillary heater on."""
|
||||
self._aux = True
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn auxillary heater off."""
|
||||
self._aux = False
|
||||
self.update_ha_state()
|
247
homeassistant/components/climate/ecobee.py
Normal file
247
homeassistant/components/climate/ecobee.py
Normal file
@ -0,0 +1,247 @@
|
||||
"""
|
||||
Platform for Ecobee Thermostats.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.ecobee/
|
||||
"""
|
||||
import logging
|
||||
from os import path
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import ecobee
|
||||
from homeassistant.components.climate import (
|
||||
DOMAIN, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DEPENDENCIES = ['ecobee']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
ECOBEE_CONFIG_FILE = 'ecobee.conf'
|
||||
_CONFIGURING = {}
|
||||
|
||||
ATTR_FAN_MIN_ON_TIME = "fan_min_on_time"
|
||||
SERVICE_SET_FAN_MIN_ON_TIME = "ecobee_set_fan_min_on_time"
|
||||
SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int),
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Ecobee Thermostat Platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
data = ecobee.NETWORK
|
||||
hold_temp = discovery_info['hold_temp']
|
||||
_LOGGER.info(
|
||||
"Loading ecobee thermostat component with hold_temp set to %s",
|
||||
hold_temp)
|
||||
devices = [Thermostat(data, index, hold_temp)
|
||||
for index in range(len(data.ecobee.thermostats))]
|
||||
add_devices(devices)
|
||||
|
||||
def fan_min_on_time_set_service(service):
|
||||
"""Set the minimum fan on time on the target thermostats."""
|
||||
entity_id = service.data.get('entity_id')
|
||||
|
||||
if entity_id:
|
||||
target_thermostats = [device for device in devices
|
||||
if device.entity_id == entity_id]
|
||||
else:
|
||||
target_thermostats = devices
|
||||
|
||||
fan_min_on_time = service.data[ATTR_FAN_MIN_ON_TIME]
|
||||
|
||||
for thermostat in target_thermostats:
|
||||
thermostat.set_fan_min_on_time(str(fan_min_on_time))
|
||||
|
||||
thermostat.update_ha_state(True)
|
||||
|
||||
descriptions = load_yaml_config_file(
|
||||
path.join(path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SET_FAN_MIN_ON_TIME, fan_min_on_time_set_service,
|
||||
descriptions.get(SERVICE_SET_FAN_MIN_ON_TIME),
|
||||
schema=SET_FAN_MIN_ON_TIME_SCHEMA)
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods, abstract-method
|
||||
class Thermostat(ClimateDevice):
|
||||
"""A thermostat class for Ecobee."""
|
||||
|
||||
def __init__(self, data, thermostat_index, hold_temp):
|
||||
"""Initialize the thermostat."""
|
||||
self.data = data
|
||||
self.thermostat_index = thermostat_index
|
||||
self.thermostat = self.data.ecobee.get_thermostat(
|
||||
self.thermostat_index)
|
||||
self._name = self.thermostat['name']
|
||||
self.hold_temp = hold_temp
|
||||
|
||||
def update(self):
|
||||
"""Get the latest state from the thermostat."""
|
||||
self.data.update()
|
||||
self.thermostat = self.data.ecobee.get_thermostat(
|
||||
self.thermostat_index)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the Ecobee Thermostat."""
|
||||
return self.thermostat['name']
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self.thermostat['runtime']['actualTemperature'] / 10
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
if (self.operation_mode == 'heat' or
|
||||
self.operation_mode == 'auxHeatOnly'):
|
||||
return self.target_temperature_low
|
||||
elif self.operation_mode == 'cool':
|
||||
return self.target_temperature_high
|
||||
else:
|
||||
return (self.target_temperature_low +
|
||||
self.target_temperature_high) / 2
|
||||
|
||||
@property
|
||||
def target_temperature_low(self):
|
||||
"""Return the lower bound temperature we try to reach."""
|
||||
return int(self.thermostat['runtime']['desiredHeat'] / 10)
|
||||
|
||||
@property
|
||||
def target_temperature_high(self):
|
||||
"""Return the upper bound temperature we try to reach."""
|
||||
return int(self.thermostat['runtime']['desiredCool'] / 10)
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
"""Return the current humidity."""
|
||||
return self.thermostat['runtime']['actualHumidity']
|
||||
|
||||
@property
|
||||
def desired_fan_mode(self):
|
||||
"""Return the desired fan mode of operation."""
|
||||
return self.thermostat['runtime']['desiredFanMode']
|
||||
|
||||
@property
|
||||
def fan(self):
|
||||
"""Return the current fan state."""
|
||||
if 'fan' in self.thermostat['equipmentStatus']:
|
||||
return STATE_ON
|
||||
else:
|
||||
return STATE_OFF
|
||||
|
||||
@property
|
||||
def operation_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
status = self.thermostat['equipmentStatus']
|
||||
if status == '':
|
||||
return STATE_IDLE
|
||||
elif 'Cool' in status:
|
||||
return STATE_COOL
|
||||
elif 'auxHeat' in status:
|
||||
return STATE_HEAT
|
||||
elif 'heatPump' in status:
|
||||
return STATE_HEAT
|
||||
else:
|
||||
return status
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Return current mode ie. home, away, sleep."""
|
||||
return self.thermostat['program']['currentClimateRef']
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current hvac mode ie. auto, auxHeatOnly, cool, heat, off."""
|
||||
return self.thermostat['settings']['hvacMode']
|
||||
|
||||
@property
|
||||
def fan_min_on_time(self):
|
||||
"""Return current fan minimum on time."""
|
||||
return self.thermostat['settings']['fanMinOnTime']
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
# Move these to Thermostat Device and make them global
|
||||
return {
|
||||
"humidity": self.current_humidity,
|
||||
"fan": self.fan,
|
||||
"mode": self.mode,
|
||||
"operation_mode": self.current_operation,
|
||||
"fan_min_on_time": self.fan_min_on_time
|
||||
}
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
mode = self.mode
|
||||
events = self.thermostat['events']
|
||||
for event in events:
|
||||
if event['running']:
|
||||
mode = event['holdClimateRef']
|
||||
break
|
||||
return 'away' in mode
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on."""
|
||||
if self.hold_temp:
|
||||
self.data.ecobee.set_climate_hold(self.thermostat_index,
|
||||
"away", "indefinite")
|
||||
else:
|
||||
self.data.ecobee.set_climate_hold(self.thermostat_index, "away")
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self.data.ecobee.resume_program(self.thermostat_index)
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
temperature = int(temperature)
|
||||
low_temp = temperature - 1
|
||||
high_temp = temperature + 1
|
||||
if self.hold_temp:
|
||||
self.data.ecobee.set_hold_temp(self.thermostat_index, low_temp,
|
||||
high_temp, "indefinite")
|
||||
else:
|
||||
self.data.ecobee.set_hold_temp(self.thermostat_index, low_temp,
|
||||
high_temp)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
|
||||
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
|
||||
|
||||
def set_fan_min_on_time(self, fan_min_on_time):
|
||||
"""Set the minimum fan on time."""
|
||||
self.data.ecobee.set_fan_min_on_time(self.thermostat_index,
|
||||
fan_min_on_time)
|
||||
|
||||
# Home and Sleep mode aren't used in UI yet:
|
||||
|
||||
# def turn_home_mode_on(self):
|
||||
# """ Turns home mode on. """
|
||||
# self.data.ecobee.set_climate_hold(self.thermostat_index, "home")
|
||||
|
||||
# def turn_home_mode_off(self):
|
||||
# """ Turns home mode off. """
|
||||
# self.data.ecobee.resume_program(self.thermostat_index)
|
||||
|
||||
# def turn_sleep_mode_on(self):
|
||||
# """ Turns sleep mode on. """
|
||||
# self.data.ecobee.set_climate_hold(self.thermostat_index, "sleep")
|
||||
|
||||
# def turn_sleep_mode_off(self):
|
||||
# """ Turns sleep mode off. """
|
||||
# self.data.ecobee.resume_program(self.thermostat_index)
|
90
homeassistant/components/climate/eq3btsmart.py
Normal file
90
homeassistant/components/climate/eq3btsmart.py
Normal file
@ -0,0 +1,90 @@
|
||||
"""
|
||||
Support for eq3 Bluetooth Smart thermostats.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.eq3btsmart/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.const import TEMP_CELSIUS
|
||||
from homeassistant.util.temperature import convert
|
||||
|
||||
REQUIREMENTS = ['bluepy_devices==0.2.0']
|
||||
|
||||
CONF_MAC = 'mac'
|
||||
CONF_DEVICES = 'devices'
|
||||
CONF_ID = 'id'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the eq3 BLE thermostats."""
|
||||
devices = []
|
||||
|
||||
for name, device_cfg in config[CONF_DEVICES].items():
|
||||
mac = device_cfg[CONF_MAC]
|
||||
devices.append(EQ3BTSmartThermostat(mac, name))
|
||||
|
||||
add_devices(devices)
|
||||
return True
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes, import-error, abstract-method
|
||||
class EQ3BTSmartThermostat(ClimateDevice):
|
||||
"""Representation of a EQ3 Bluetooth Smart thermostat."""
|
||||
|
||||
def __init__(self, _mac, _name):
|
||||
"""Initialize the thermostat."""
|
||||
from bluepy_devices.devices import eq3btsmart
|
||||
|
||||
self._name = _name
|
||||
|
||||
self._thermostat = eq3btsmart.EQ3BTSmartThermostat(_mac)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement that is used."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Can not report temperature, so return target_temperature."""
|
||||
return self.target_temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._thermostat.target_temperature
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
self._thermostat.target_temperature = temperature
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {"mode": self._thermostat.mode,
|
||||
"mode_readable": self._thermostat.mode_readable}
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
return convert(self._thermostat.min_temp, TEMP_CELSIUS,
|
||||
self.unit_of_measurement)
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
return convert(self._thermostat.max_temp, TEMP_CELSIUS,
|
||||
self.unit_of_measurement)
|
||||
|
||||
def update(self):
|
||||
"""Update the data from the thermostat."""
|
||||
self._thermostat.update()
|
216
homeassistant/components/climate/generic_thermostat.py
Normal file
216
homeassistant/components/climate/generic_thermostat.py
Normal file
@ -0,0 +1,216 @@
|
||||
"""
|
||||
Adds support for generic thermostat units.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.generic_thermostat/
|
||||
"""
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components import switch
|
||||
from homeassistant.components.climate import (
|
||||
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice)
|
||||
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF
|
||||
from homeassistant.helpers import condition
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
|
||||
DEPENDENCIES = ['switch', 'sensor']
|
||||
|
||||
TOL_TEMP = 0.3
|
||||
|
||||
CONF_NAME = 'name'
|
||||
DEFAULT_NAME = 'Generic Thermostat'
|
||||
CONF_HEATER = 'heater'
|
||||
CONF_SENSOR = 'target_sensor'
|
||||
CONF_MIN_TEMP = 'min_temp'
|
||||
CONF_MAX_TEMP = 'max_temp'
|
||||
CONF_TARGET_TEMP = 'target_temp'
|
||||
CONF_AC_MODE = 'ac_mode'
|
||||
CONF_MIN_DUR = 'min_cycle_duration'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Required("platform"): "generic_thermostat",
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Required(CONF_HEATER): cv.entity_id,
|
||||
vol.Required(CONF_SENSOR): cv.entity_id,
|
||||
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
|
||||
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
|
||||
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
|
||||
vol.Optional(CONF_AC_MODE): vol.Coerce(bool),
|
||||
vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta),
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the generic thermostat."""
|
||||
name = config.get(CONF_NAME)
|
||||
heater_entity_id = config.get(CONF_HEATER)
|
||||
sensor_entity_id = config.get(CONF_SENSOR)
|
||||
min_temp = config.get(CONF_MIN_TEMP)
|
||||
max_temp = config.get(CONF_MAX_TEMP)
|
||||
target_temp = config.get(CONF_TARGET_TEMP)
|
||||
ac_mode = config.get(CONF_AC_MODE)
|
||||
min_cycle_duration = config.get(CONF_MIN_DUR)
|
||||
|
||||
add_devices([GenericThermostat(hass, name, heater_entity_id,
|
||||
sensor_entity_id, min_temp,
|
||||
max_temp, target_temp, ac_mode,
|
||||
min_cycle_duration)])
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes, abstract-method
|
||||
class GenericThermostat(ClimateDevice):
|
||||
"""Representation of a GenericThermostat device."""
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
|
||||
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration):
|
||||
"""Initialize the thermostat."""
|
||||
self.hass = hass
|
||||
self._name = name
|
||||
self.heater_entity_id = heater_entity_id
|
||||
self.ac_mode = ac_mode
|
||||
self.min_cycle_duration = min_cycle_duration
|
||||
|
||||
self._active = False
|
||||
self._cur_temp = None
|
||||
self._min_temp = min_temp
|
||||
self._max_temp = max_temp
|
||||
self._target_temp = target_temp
|
||||
self._unit = hass.config.units.temperature_unit
|
||||
|
||||
track_state_change(hass, sensor_entity_id, self._sensor_changed)
|
||||
|
||||
sensor_state = hass.states.get(sensor_entity_id)
|
||||
if sensor_state:
|
||||
self._update_temp(sensor_state)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the thermostat."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the sensor temperature."""
|
||||
return self._cur_temp
|
||||
|
||||
@property
|
||||
def operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if self.ac_mode:
|
||||
cooling = self._active and self._is_device_active
|
||||
return STATE_COOL if cooling else STATE_IDLE
|
||||
else:
|
||||
heating = self._active and self._is_device_active
|
||||
return STATE_HEAT if heating else STATE_IDLE
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._target_temp
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
self._target_temp = temperature
|
||||
self._control_heating()
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
# pylint: disable=no-member
|
||||
if self._min_temp:
|
||||
return self._min_temp
|
||||
else:
|
||||
# get default temp from super class
|
||||
return ClimateDevice.min_temp.fget(self)
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
# pylint: disable=no-member
|
||||
if self._min_temp:
|
||||
return self._max_temp
|
||||
else:
|
||||
# Get default temp from super class
|
||||
return ClimateDevice.max_temp.fget(self)
|
||||
|
||||
def _sensor_changed(self, entity_id, old_state, new_state):
|
||||
"""Called when temperature changes."""
|
||||
if new_state is None:
|
||||
return
|
||||
|
||||
self._update_temp(new_state)
|
||||
self._control_heating()
|
||||
self.update_ha_state()
|
||||
|
||||
def _update_temp(self, state):
|
||||
"""Update thermostat with latest state from sensor."""
|
||||
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
|
||||
try:
|
||||
self._cur_temp = self.hass.config.units.temperature(
|
||||
float(state.state), unit)
|
||||
except ValueError as ex:
|
||||
_LOGGER.error('Unable to update from sensor: %s', ex)
|
||||
|
||||
def _control_heating(self):
|
||||
"""Check if we need to turn heating on or off."""
|
||||
if not self._active and None not in (self._cur_temp,
|
||||
self._target_temp):
|
||||
self._active = True
|
||||
_LOGGER.info('Obtained current and target temperature. '
|
||||
'Generic thermostat active.')
|
||||
|
||||
if not self._active:
|
||||
return
|
||||
|
||||
if self.min_cycle_duration:
|
||||
if self._is_device_active:
|
||||
current_state = STATE_ON
|
||||
else:
|
||||
current_state = STATE_OFF
|
||||
long_enough = condition.state(self.hass, self.heater_entity_id,
|
||||
current_state,
|
||||
self.min_cycle_duration)
|
||||
if not long_enough:
|
||||
return
|
||||
|
||||
if self.ac_mode:
|
||||
too_hot = self._cur_temp - self._target_temp > TOL_TEMP
|
||||
is_cooling = self._is_device_active
|
||||
if too_hot and not is_cooling:
|
||||
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
|
||||
switch.turn_on(self.hass, self.heater_entity_id)
|
||||
elif not too_hot and is_cooling:
|
||||
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
|
||||
switch.turn_off(self.hass, self.heater_entity_id)
|
||||
else:
|
||||
too_cold = self._target_temp - self._cur_temp > TOL_TEMP
|
||||
is_heating = self._is_device_active
|
||||
|
||||
if too_cold and not is_heating:
|
||||
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
|
||||
switch.turn_on(self.hass, self.heater_entity_id)
|
||||
elif not too_cold and is_heating:
|
||||
_LOGGER.info('Turning off heater %s', self.heater_entity_id)
|
||||
switch.turn_off(self.hass, self.heater_entity_id)
|
||||
|
||||
@property
|
||||
def _is_device_active(self):
|
||||
"""If the toggleable device is currently active."""
|
||||
return switch.is_on(self.hass, self.heater_entity_id)
|
114
homeassistant/components/climate/heatmiser.py
Normal file
114
homeassistant/components/climate/heatmiser.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""
|
||||
Support for the PRT Heatmiser themostats using the V3 protocol.
|
||||
|
||||
See https://github.com/andylockran/heatmiserV3 for more info on the
|
||||
heatmiserV3 module dependency.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.heatmiser/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.const import TEMP_CELSIUS
|
||||
|
||||
CONF_IPADDRESS = 'ipaddress'
|
||||
CONF_PORT = 'port'
|
||||
CONF_TSTATS = 'tstats'
|
||||
|
||||
REQUIREMENTS = ["heatmiserV3==0.9.1"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the heatmiser thermostat."""
|
||||
from heatmiserV3 import heatmiser, connection
|
||||
|
||||
ipaddress = str(config[CONF_IPADDRESS])
|
||||
port = str(config[CONF_PORT])
|
||||
|
||||
if ipaddress is None or port is None:
|
||||
_LOGGER.error("Missing required configuration items %s or %s",
|
||||
CONF_IPADDRESS, CONF_PORT)
|
||||
return False
|
||||
|
||||
serport = connection.connection(ipaddress, port)
|
||||
serport.open()
|
||||
|
||||
tstats = []
|
||||
if CONF_TSTATS in config:
|
||||
tstats = config[CONF_TSTATS]
|
||||
|
||||
if tstats is None:
|
||||
_LOGGER.error("No thermostats configured.")
|
||||
return False
|
||||
|
||||
for tstat in tstats:
|
||||
add_devices([
|
||||
HeatmiserV3Thermostat(
|
||||
heatmiser,
|
||||
tstat.get("id"),
|
||||
tstat.get("name"),
|
||||
serport)
|
||||
])
|
||||
return
|
||||
|
||||
|
||||
class HeatmiserV3Thermostat(ClimateDevice):
|
||||
"""Representation of a HeatmiserV3 thermostat."""
|
||||
|
||||
# pylint: disable=too-many-instance-attributes, abstract-method
|
||||
def __init__(self, heatmiser, device, name, serport):
|
||||
"""Initialize the thermostat."""
|
||||
self.heatmiser = heatmiser
|
||||
self.device = device
|
||||
self.serport = serport
|
||||
self._current_temperature = None
|
||||
self._name = name
|
||||
self._id = device
|
||||
self.dcb = None
|
||||
self.update()
|
||||
self._target_temperature = int(self.dcb.get("roomset"))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the thermostat, if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement which this thermostat uses."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
if self.dcb is not None:
|
||||
low = self.dcb.get("floortemplow ")
|
||||
high = self.dcb.get("floortemphigh")
|
||||
temp = (high*256 + low)/10.0
|
||||
self._current_temperature = temp
|
||||
else:
|
||||
self._current_temperature = None
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._target_temperature
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
temperature = int(temperature)
|
||||
self.heatmiser.hmSendAddress(
|
||||
self._id,
|
||||
18,
|
||||
temperature,
|
||||
1,
|
||||
self.serport)
|
||||
self._target_temperature = int(temperature)
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data."""
|
||||
self.dcb = self.heatmiser.hmReadAddress(self._id, 'prt', self.serport)
|
90
homeassistant/components/climate/homematic.py
Normal file
90
homeassistant/components/climate/homematic.py
Normal file
@ -0,0 +1,90 @@
|
||||
"""
|
||||
Support for Homematic thermostats.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.homematic/
|
||||
"""
|
||||
import logging
|
||||
import homeassistant.components.homematic as homematic
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.util.temperature import convert
|
||||
from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN
|
||||
|
||||
DEPENDENCIES = ['homematic']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||
"""Setup the Homematic thermostat platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
return homematic.setup_hmdevice_discovery_helper(HMThermostat,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class HMThermostat(homematic.HMDevice, ClimateDevice):
|
||||
"""Representation of a Homematic thermostat."""
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement that is used."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
if not self.available:
|
||||
return None
|
||||
return self._data["ACTUAL_TEMPERATURE"]
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the target temperature."""
|
||||
if not self.available:
|
||||
return None
|
||||
return self._data["SET_TEMPERATURE"]
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
if not self.available:
|
||||
return None
|
||||
self._hmdevice.set_temperature(temperature)
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature - 4.5 means off."""
|
||||
return convert(4.5, TEMP_CELSIUS, self.unit_of_measurement)
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature - 30.5 means on."""
|
||||
return convert(30.5, TEMP_CELSIUS, self.unit_of_measurement)
|
||||
|
||||
def _check_hm_to_ha_object(self):
|
||||
"""Check if possible to use the Homematic object as this HA type."""
|
||||
from pyhomematic.devicetypes.thermostats import HMThermostat\
|
||||
as pyHMThermostat
|
||||
|
||||
# Check compatibility from HMDevice
|
||||
if not super()._check_hm_to_ha_object():
|
||||
return False
|
||||
|
||||
# Check if the Homematic device correct for this HA device
|
||||
if isinstance(self._hmdevice, pyHMThermostat):
|
||||
return True
|
||||
|
||||
_LOGGER.critical("This %s can't be use as thermostat", self._name)
|
||||
return False
|
||||
|
||||
def _init_data_struct(self):
|
||||
"""Generate a data dict (self._data) from the Homematic metadata."""
|
||||
super()._init_data_struct()
|
||||
|
||||
# Add state to data dict
|
||||
self._data.update({"CONTROL_MODE": STATE_UNKNOWN,
|
||||
"SET_TEMPERATURE": STATE_UNKNOWN,
|
||||
"ACTUAL_TEMPERATURE": STATE_UNKNOWN})
|
266
homeassistant/components/climate/honeywell.py
Normal file
266
homeassistant/components/climate/honeywell.py
Normal file
@ -0,0 +1,266 @@
|
||||
"""
|
||||
Support for Honeywell Round Connected and Honeywell Evohome thermostats.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.honeywell/
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
REQUIREMENTS = ['evohomeclient==0.2.5',
|
||||
'somecomfort==0.2.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_AWAY_TEMP = "away_temperature"
|
||||
DEFAULT_AWAY_TEMP = 16
|
||||
|
||||
|
||||
def _setup_round(username, password, config, add_devices):
|
||||
"""Setup rounding function."""
|
||||
from evohomeclient import EvohomeClient
|
||||
|
||||
try:
|
||||
away_temp = float(config.get(CONF_AWAY_TEMP, DEFAULT_AWAY_TEMP))
|
||||
except ValueError:
|
||||
_LOGGER.error("value entered for item %s should convert to a number",
|
||||
CONF_AWAY_TEMP)
|
||||
return False
|
||||
|
||||
evo_api = EvohomeClient(username, password)
|
||||
|
||||
try:
|
||||
zones = evo_api.temperatures(force_refresh=True)
|
||||
for i, zone in enumerate(zones):
|
||||
add_devices([RoundThermostat(evo_api,
|
||||
zone['id'],
|
||||
i == 0,
|
||||
away_temp)])
|
||||
except socket.error:
|
||||
_LOGGER.error(
|
||||
"Connection error logging into the honeywell evohome web service"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# config will be used later
|
||||
def _setup_us(username, password, config, add_devices):
|
||||
"""Setup user."""
|
||||
import somecomfort
|
||||
|
||||
try:
|
||||
client = somecomfort.SomeComfort(username, password)
|
||||
except somecomfort.AuthError:
|
||||
_LOGGER.error('Failed to login to honeywell account %s', username)
|
||||
return False
|
||||
except somecomfort.SomeComfortError as ex:
|
||||
_LOGGER.error('Failed to initialize honeywell client: %s', str(ex))
|
||||
return False
|
||||
|
||||
dev_id = config.get('thermostat')
|
||||
loc_id = config.get('location')
|
||||
|
||||
add_devices([HoneywellUSThermostat(client, device)
|
||||
for location in client.locations_by_id.values()
|
||||
for device in location.devices_by_id.values()
|
||||
if ((not loc_id or location.locationid == loc_id) and
|
||||
(not dev_id or device.deviceid == dev_id))])
|
||||
return True
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the honeywel thermostat."""
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
region = config.get('region', 'eu').lower()
|
||||
|
||||
if username is None or password is None:
|
||||
_LOGGER.error("Missing required configuration items %s or %s",
|
||||
CONF_USERNAME, CONF_PASSWORD)
|
||||
return False
|
||||
if region not in ('us', 'eu'):
|
||||
_LOGGER.error('Region `%s` is invalid (use either us or eu)', region)
|
||||
return False
|
||||
|
||||
if region == 'us':
|
||||
return _setup_us(username, password, config, add_devices)
|
||||
else:
|
||||
return _setup_round(username, password, config, add_devices)
|
||||
|
||||
|
||||
class RoundThermostat(ClimateDevice):
|
||||
"""Representation of a Honeywell Round Connected thermostat."""
|
||||
|
||||
# pylint: disable=too-many-instance-attributes, abstract-method
|
||||
def __init__(self, device, zone_id, master, away_temp):
|
||||
"""Initialize the thermostat."""
|
||||
self.device = device
|
||||
self._current_temperature = None
|
||||
self._target_temperature = None
|
||||
self._name = "round connected"
|
||||
self._id = zone_id
|
||||
self._master = master
|
||||
self._is_dhw = False
|
||||
self._away_temp = away_temp
|
||||
self._away = False
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the honeywell, if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
if self._is_dhw:
|
||||
return None
|
||||
return self._target_temperature
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
self.device.set_temperature(self._name, temperature)
|
||||
|
||||
@property
|
||||
def current_operation(self: ClimateDevice) -> str:
|
||||
"""Get the current operation of the system."""
|
||||
return getattr(self.device, 'system_mode', None)
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._away
|
||||
|
||||
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
|
||||
"""Set the HVAC mode for the thermostat."""
|
||||
if hasattr(self.device, 'system_mode'):
|
||||
self.device.system_mode = operation_mode
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on.
|
||||
|
||||
Evohome does have a proprietary away mode, but it doesn't really work
|
||||
the way it should. For example: If you set a temperature manually
|
||||
it doesn't get overwritten when away mode is switched on.
|
||||
"""
|
||||
self._away = True
|
||||
self.device.set_temperature(self._name, self._away_temp)
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self._away = False
|
||||
self.device.cancel_temp_override(self._name)
|
||||
|
||||
def update(self):
|
||||
"""Get the latest date."""
|
||||
try:
|
||||
# Only refresh if this is the "master" device,
|
||||
# others will pick up the cache
|
||||
for val in self.device.temperatures(force_refresh=self._master):
|
||||
if val['id'] == self._id:
|
||||
data = val
|
||||
|
||||
except StopIteration:
|
||||
_LOGGER.error("Did not receive any temperature data from the "
|
||||
"evohomeclient API.")
|
||||
return
|
||||
|
||||
self._current_temperature = data['temp']
|
||||
self._target_temperature = data['setpoint']
|
||||
if data['thermostat'] == "DOMESTIC_HOT_WATER":
|
||||
self._name = "Hot Water"
|
||||
self._is_dhw = True
|
||||
else:
|
||||
self._name = data['name']
|
||||
self._is_dhw = False
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class HoneywellUSThermostat(ClimateDevice):
|
||||
"""Representation of a Honeywell US Thermostat."""
|
||||
|
||||
def __init__(self, client, device):
|
||||
"""Initialize the thermostat."""
|
||||
self._client = client
|
||||
self._device = device
|
||||
|
||||
@property
|
||||
def is_fan_on(self):
|
||||
"""Return true if fan is on."""
|
||||
return self._device.fan_running
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the honeywell, if any."""
|
||||
return self._device.name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return (TEMP_CELSIUS if self._device.temperature_unit == 'C'
|
||||
else TEMP_FAHRENHEIT)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
self._device.refresh()
|
||||
return self._device.current_temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
if self._device.system_mode == 'cool':
|
||||
return self._device.setpoint_cool
|
||||
else:
|
||||
return self._device.setpoint_heat
|
||||
|
||||
@property
|
||||
def current_operation(self: ClimateDevice) -> str:
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return getattr(self._device, 'system_mode', None)
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set target temperature."""
|
||||
import somecomfort
|
||||
try:
|
||||
if self._device.system_mode == 'cool':
|
||||
self._device.setpoint_cool = temperature
|
||||
else:
|
||||
self._device.setpoint_heat = temperature
|
||||
except somecomfort.SomeComfortError:
|
||||
_LOGGER.error('Temperature %.1f out of range', temperature)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {'fan': (self.is_fan_on and 'running' or 'idle'),
|
||||
'fanmode': self._device.fan_mode,
|
||||
'system_mode': self._device.system_mode}
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on."""
|
||||
pass
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
pass
|
||||
|
||||
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
|
||||
"""Set the system mode (Cool, Heat, etc)."""
|
||||
if hasattr(self._device, 'system_mode'):
|
||||
self._device.system_mode = operation_mode
|
83
homeassistant/components/climate/knx.py
Normal file
83
homeassistant/components/climate/knx.py
Normal file
@ -0,0 +1,83 @@
|
||||
"""
|
||||
Support for KNX thermostats.
|
||||
|
||||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/knx/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.const import TEMP_CELSIUS
|
||||
|
||||
from homeassistant.components.knx import (
|
||||
KNXConfig, KNXMultiAddressDevice)
|
||||
|
||||
DEPENDENCIES = ["knx"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Create and add an entity based on the configuration."""
|
||||
add_entities([
|
||||
KNXThermostat(hass, KNXConfig(config))
|
||||
])
|
||||
|
||||
|
||||
class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
|
||||
"""Representation of a KNX thermostat.
|
||||
|
||||
A KNX thermostat will has the following parameters:
|
||||
- temperature (current temperature)
|
||||
- setpoint (target temperature in HASS terms)
|
||||
- operation mode selection (comfort/night/frost protection)
|
||||
|
||||
This version supports only polling. Messages from the KNX bus do not
|
||||
automatically update the state of the thermostat (to be implemented
|
||||
in future releases)
|
||||
"""
|
||||
|
||||
def __init__(self, hass, config):
|
||||
"""Initialize the thermostat based on the given configuration."""
|
||||
KNXMultiAddressDevice.__init__(self, hass, config,
|
||||
["temperature", "setpoint"],
|
||||
["mode"])
|
||||
|
||||
self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius
|
||||
self._away = False # not yet supported
|
||||
self._is_fan_on = False # not yet supported
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Polling is needed for the KNX thermostat."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
from knxip.conversion import knx2_to_float
|
||||
|
||||
return knx2_to_float(self.value("temperature"))
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
from knxip.conversion import knx2_to_float
|
||||
|
||||
return knx2_to_float(self.value("setpoint"))
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
from knxip.conversion import float_to_knx2
|
||||
|
||||
self.set_value("setpoint", float_to_knx2(temperature))
|
||||
_LOGGER.debug("Set target temperature to %s", temperature)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
raise NotImplementedError()
|
189
homeassistant/components/climate/nest.py
Normal file
189
homeassistant/components/climate/nest.py
Normal file
@ -0,0 +1,189 @@
|
||||
"""
|
||||
Support for Nest thermostats.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.nest/
|
||||
"""
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.nest as nest
|
||||
from homeassistant.components.climate import (
|
||||
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice)
|
||||
from homeassistant.const import TEMP_CELSIUS, CONF_PLATFORM, CONF_SCAN_INTERVAL
|
||||
|
||||
DEPENDENCIES = ['nest']
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): nest.DOMAIN,
|
||||
vol.Optional(CONF_SCAN_INTERVAL):
|
||||
vol.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Nest thermostat."""
|
||||
add_devices([NestThermostat(structure, device)
|
||||
for structure, device in nest.devices()])
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class NestThermostat(ClimateDevice):
|
||||
"""Representation of a Nest thermostat."""
|
||||
|
||||
def __init__(self, structure, device):
|
||||
"""Initialize the thermostat."""
|
||||
self.structure = structure
|
||||
self.device = device
|
||||
|
||||
@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
|
||||
else:
|
||||
if name == '':
|
||||
return location.capitalize()
|
||||
else:
|
||||
return location.capitalize() + '(' + name + ')'
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
# Move these to Thermostat Device and make them global
|
||||
return {
|
||||
"humidity": self.device.humidity,
|
||||
"target_humidity": self.device.target_humidity,
|
||||
"mode": self.device.mode
|
||||
}
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self.device.temperature
|
||||
|
||||
@property
|
||||
def operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if self.device.hvac_ac_state is True:
|
||||
return STATE_COOL
|
||||
elif self.device.hvac_heater_state is True:
|
||||
return STATE_HEAT
|
||||
else:
|
||||
return STATE_IDLE
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
if self.device.mode == 'range':
|
||||
low, high = self.target_temperature_low, \
|
||||
self.target_temperature_high
|
||||
if self.operation == STATE_COOL:
|
||||
temp = high
|
||||
elif self.operation == STATE_HEAT:
|
||||
temp = low
|
||||
else:
|
||||
# If the outside temp is lower than the current temp, consider
|
||||
# the 'low' temp to the target, otherwise use the high temp
|
||||
if (self.device.structure.weather.current.temperature <
|
||||
self.current_temperature):
|
||||
temp = low
|
||||
else:
|
||||
temp = high
|
||||
else:
|
||||
if self.is_away_mode_on:
|
||||
# away_temperature is a low, high tuple. Only one should be set
|
||||
# if not in range mode, the other will be None
|
||||
temp = self.device.away_temperature[0] or \
|
||||
self.device.away_temperature[1]
|
||||
else:
|
||||
temp = self.device.target
|
||||
|
||||
return temp
|
||||
|
||||
@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]:
|
||||
# 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.target_temperature
|
||||
|
||||
@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]:
|
||||
# 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.target_temperature
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return if away mode is on."""
|
||||
return self.structure.away
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
if self.device.mode == 'range':
|
||||
if self.target_temperature == self.target_temperature_low:
|
||||
temperature = (temperature, self.target_temperature_high)
|
||||
elif self.target_temperature == self.target_temperature_high:
|
||||
temperature = (self.target_temperature_low, temperature)
|
||||
self.device.target = temperature
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
self.device.mode = operation_mode
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on."""
|
||||
self.structure.away = True
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self.structure.away = False
|
||||
|
||||
@property
|
||||
def is_fan_on(self):
|
||||
"""Return whether the fan is on."""
|
||||
return self.device.fan
|
||||
|
||||
def turn_fan_on(self):
|
||||
"""Turn fan on."""
|
||||
self.device.fan = True
|
||||
|
||||
def turn_fan_off(self):
|
||||
"""Turn fan off."""
|
||||
self.device.fan = False
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Identify min_temp in Nest API or defaults if not available."""
|
||||
temp = self.device.away_temperature.low
|
||||
if temp is None:
|
||||
return super().min_temp
|
||||
else:
|
||||
return temp
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Identify max_temp in Nest API or defaults if not available."""
|
||||
temp = self.device.away_temperature.high
|
||||
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
|
90
homeassistant/components/climate/proliphix.py
Normal file
90
homeassistant/components/climate/proliphix.py
Normal file
@ -0,0 +1,90 @@
|
||||
"""
|
||||
Support for Proliphix NT10e Thermostats.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.proliphix/
|
||||
"""
|
||||
from homeassistant.components.climate import (
|
||||
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT)
|
||||
|
||||
REQUIREMENTS = ['proliphix==0.3.1']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Proliphix thermostats."""
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
host = config.get(CONF_HOST)
|
||||
|
||||
import proliphix
|
||||
|
||||
pdp = proliphix.PDP(host, username, password)
|
||||
|
||||
add_devices([
|
||||
ProliphixThermostat(pdp)
|
||||
])
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class ProliphixThermostat(ClimateDevice):
|
||||
"""Representation a Proliphix thermostat."""
|
||||
|
||||
def __init__(self, pdp):
|
||||
"""Initialize the thermostat."""
|
||||
self._pdp = pdp
|
||||
# initial data
|
||||
self._pdp.update()
|
||||
self._name = self._pdp.name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Polling needed for thermostat."""
|
||||
return True
|
||||
|
||||
def update(self):
|
||||
"""Update the data from the thermostat."""
|
||||
self._pdp.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the thermostat."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {
|
||||
"fan": self._pdp.fan_state
|
||||
}
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._pdp.cur_temp
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._pdp.setback
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return the current state of the thermostat."""
|
||||
state = self._pdp.hvac_state
|
||||
if state in (1, 2):
|
||||
return STATE_IDLE
|
||||
elif state == 3:
|
||||
return STATE_HEAT
|
||||
elif state == 6:
|
||||
return STATE_COOL
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
self._pdp.setback = temperature
|
136
homeassistant/components/climate/radiotherm.py
Normal file
136
homeassistant/components/climate/radiotherm.py
Normal file
@ -0,0 +1,136 @@
|
||||
"""
|
||||
Support for Radio Thermostat wifi-enabled home thermostats.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.radiotherm/
|
||||
"""
|
||||
import datetime
|
||||
import logging
|
||||
from urllib.error import URLError
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_OFF,
|
||||
ClimateDevice)
|
||||
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT
|
||||
|
||||
REQUIREMENTS = ['radiotherm==1.2']
|
||||
HOLD_TEMP = 'hold_temp'
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Radio Thermostat."""
|
||||
import radiotherm
|
||||
|
||||
hosts = []
|
||||
if CONF_HOST in config:
|
||||
hosts = config[CONF_HOST]
|
||||
else:
|
||||
hosts.append(radiotherm.discover.discover_address())
|
||||
|
||||
if hosts is None:
|
||||
_LOGGER.error("No radiotherm thermostats detected.")
|
||||
return False
|
||||
|
||||
hold_temp = config.get(HOLD_TEMP, False)
|
||||
tstats = []
|
||||
|
||||
for host in hosts:
|
||||
try:
|
||||
tstat = radiotherm.get_thermostat(host)
|
||||
tstats.append(RadioThermostat(tstat, hold_temp))
|
||||
except (URLError, OSError):
|
||||
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
|
||||
host)
|
||||
|
||||
add_devices(tstats)
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class RadioThermostat(ClimateDevice):
|
||||
"""Representation of a Radio Thermostat."""
|
||||
|
||||
def __init__(self, device, hold_temp):
|
||||
"""Initialize the thermostat."""
|
||||
self.device = device
|
||||
self.set_time()
|
||||
self._target_temperature = None
|
||||
self._current_temperature = None
|
||||
self._current_operation = STATE_IDLE
|
||||
self._name = None
|
||||
self.hold_temp = hold_temp
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the Radio Thermostat."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {
|
||||
"fan": self.device.fmode['human'],
|
||||
"mode": self.device.tmode['human']
|
||||
}
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return the current operation. head, cool idle."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._target_temperature
|
||||
|
||||
def update(self):
|
||||
"""Update the data from the thermostat."""
|
||||
self._current_temperature = self.device.temp['raw']
|
||||
self._name = self.device.name['raw']
|
||||
if self.device.tmode['human'] == 'Cool':
|
||||
self._target_temperature = self.device.t_cool['raw']
|
||||
self._current_operation = STATE_COOL
|
||||
elif self.device.tmode['human'] == 'Heat':
|
||||
self._target_temperature = self.device.t_heat['raw']
|
||||
self._current_operation = STATE_HEAT
|
||||
else:
|
||||
self._current_operation = STATE_IDLE
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
if self._current_operation == STATE_COOL:
|
||||
self.device.t_cool = temperature
|
||||
elif self._current_operation == STATE_HEAT:
|
||||
self.device.t_heat = temperature
|
||||
if self.hold_temp:
|
||||
self.device.hold = 1
|
||||
else:
|
||||
self.device.hold = 0
|
||||
|
||||
def set_time(self):
|
||||
"""Set device time."""
|
||||
now = datetime.datetime.now()
|
||||
self.device.time = {'day': now.weekday(),
|
||||
'hour': now.hour, 'minute': now.minute}
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode (auto, cool, heat, off)."""
|
||||
if operation_mode == STATE_OFF:
|
||||
self.device.tmode = 0
|
||||
elif operation_mode == STATE_AUTO:
|
||||
self.device.tmode = 3
|
||||
elif operation_mode == STATE_COOL:
|
||||
self.device.t_cool = self._target_temperature
|
||||
elif operation_mode == STATE_HEAT:
|
||||
self.device.t_heat = self._target_temperature
|
84
homeassistant/components/climate/services.yaml
Normal file
84
homeassistant/components/climate/services.yaml
Normal file
@ -0,0 +1,84 @@
|
||||
set_aux_heat:
|
||||
description: Turn auxillary heater on/off for climate device
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change
|
||||
example: 'climate.kitchen'
|
||||
|
||||
aux_heat:
|
||||
description: New value of axillary heater
|
||||
example: true
|
||||
|
||||
set_away_mode:
|
||||
description: Turn away mode on/off for climate device
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change
|
||||
example: 'climate.kitchen'
|
||||
|
||||
away_mode:
|
||||
description: New value of away mode
|
||||
example: true
|
||||
|
||||
set_temperature:
|
||||
description: Set target temperature of climate device
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change
|
||||
example: 'climate.kitchen'
|
||||
|
||||
temperature:
|
||||
description: New target temperature for hvac
|
||||
example: 25
|
||||
|
||||
set_humidity:
|
||||
description: Set target humidity of climate device
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change
|
||||
example: 'climate.kitchen'
|
||||
|
||||
humidity:
|
||||
description: New target humidity for climate device
|
||||
example: 60
|
||||
|
||||
set_fan_mode:
|
||||
description: Set fan operation for climate device
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change
|
||||
example: 'climate.nest'
|
||||
|
||||
fan:
|
||||
description: New value of fan mode
|
||||
example: On Low
|
||||
|
||||
set_operation_mode:
|
||||
description: Set operation mode for climate device
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change
|
||||
example: 'climet.nest'
|
||||
|
||||
operation_mode:
|
||||
description: New value of operation mode
|
||||
example: Heat
|
||||
|
||||
|
||||
set_swing_mode:
|
||||
description: Set swing operation for climate device
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change
|
||||
example: '.nest'
|
||||
|
||||
swing_mode:
|
||||
description: New value of swing mode
|
||||
example: 1
|
253
homeassistant/components/climate/zwave.py
Executable file
253
homeassistant/components/climate/zwave.py
Executable file
@ -0,0 +1,253 @@
|
||||
"""
|
||||
Support for ZWave climate devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.zwave/
|
||||
"""
|
||||
# Because we do not compile openzwave on CI
|
||||
# pylint: disable=import-error
|
||||
import logging
|
||||
from homeassistant.components.climate import DOMAIN
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.zwave import (
|
||||
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
|
||||
from homeassistant.components import zwave
|
||||
from homeassistant.const import (TEMP_FAHRENHEIT, TEMP_CELSIUS)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_NAME = 'name'
|
||||
DEFAULT_NAME = 'ZWave Climate'
|
||||
|
||||
REMOTEC = 0x5254
|
||||
REMOTEC_ZXT_120 = 0x8377
|
||||
REMOTEC_ZXT_120_THERMOSTAT = (REMOTEC, REMOTEC_ZXT_120)
|
||||
|
||||
COMMAND_CLASS_SENSOR_MULTILEVEL = 0x31
|
||||
COMMAND_CLASS_THERMOSTAT_MODE = 0x40
|
||||
COMMAND_CLASS_THERMOSTAT_SETPOINT = 0x43
|
||||
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 0x44
|
||||
COMMAND_CLASS_CONFIGURATION = 0x70
|
||||
|
||||
WORKAROUND_ZXT_120 = 'zxt_120'
|
||||
|
||||
DEVICE_MAPPINGS = {
|
||||
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120
|
||||
}
|
||||
|
||||
ZXT_120_SET_TEMP = {
|
||||
'Heat': 1,
|
||||
'Cool': 2,
|
||||
'Dry Air': 8,
|
||||
'Auto Changeover': 10
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the ZWave Climate devices."""
|
||||
if discovery_info is None or zwave.NETWORK is None:
|
||||
_LOGGER.debug("No discovery_info=%s or no NETWORK=%s",
|
||||
discovery_info, zwave.NETWORK)
|
||||
return
|
||||
|
||||
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
|
||||
value = node.values[discovery_info[ATTR_VALUE_ID]]
|
||||
value.set_change_verified(False)
|
||||
add_devices([ZWaveClimate(value)])
|
||||
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
|
||||
discovery_info, zwave.NETWORK)
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments, abstract-method
|
||||
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
"""Represents a ZWave Climate device."""
|
||||
|
||||
# pylint: disable=too-many-public-methods, too-many-instance-attributes
|
||||
def __init__(self, value):
|
||||
"""Initialize the zwave climate device."""
|
||||
from openzwave.network import ZWaveNetwork
|
||||
from pydispatch import dispatcher
|
||||
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||
self._node = value.node
|
||||
self._target_temperature = None
|
||||
self._current_temperature = None
|
||||
self._current_operation = None
|
||||
self._operation_list = None
|
||||
self._current_fan_mode = None
|
||||
self._fan_list = None
|
||||
self._current_swing_mode = None
|
||||
self._swing_list = None
|
||||
self._unit = None
|
||||
self._index = None
|
||||
self._zxt_120 = None
|
||||
self.update_properties()
|
||||
# register listener
|
||||
dispatcher.connect(
|
||||
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
||||
# Make sure that we have values for the key before converting to int
|
||||
if (value.node.manufacturer_id.strip() and
|
||||
value.node.product_id.strip()):
|
||||
specific_sensor_key = (int(value.node.manufacturer_id, 16),
|
||||
int(value.node.product_id, 16))
|
||||
|
||||
if specific_sensor_key in DEVICE_MAPPINGS:
|
||||
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120:
|
||||
_LOGGER.debug("Remotec ZXT-120 Zwave Thermostat"
|
||||
" workaround")
|
||||
self._zxt_120 = 1
|
||||
|
||||
def value_changed(self, value):
|
||||
"""Called when a value has changed on the network."""
|
||||
if self._value.value_id == value.value_id or \
|
||||
self._value.node == value.node:
|
||||
self.update_properties()
|
||||
self.update_ha_state()
|
||||
_LOGGER.debug("Value changed on network %s", value)
|
||||
|
||||
def update_properties(self):
|
||||
"""Callback on data change for the registered node/value pair."""
|
||||
# Set point
|
||||
temps = []
|
||||
for value in self._node.get_values(
|
||||
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
|
||||
temps.append(int(value.data))
|
||||
if value.index == self._index:
|
||||
self._target_temperature = int(value.data)
|
||||
self._target_temperature_high = max(temps)
|
||||
self._target_temperature_low = min(temps)
|
||||
# Operation Mode
|
||||
for value in self._node.get_values(
|
||||
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
|
||||
self._current_operation = value.data
|
||||
self._operation_list = list(value.data_items)
|
||||
_LOGGER.debug("self._operation_list=%s", self._operation_list)
|
||||
_LOGGER.debug("self._current_operation=%s",
|
||||
self._current_operation)
|
||||
# Current Temp
|
||||
for value in self._node.get_values(
|
||||
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
|
||||
if value.label == 'Temperature':
|
||||
self._current_temperature = int(value.data)
|
||||
self._unit = value.units
|
||||
# Fan Mode
|
||||
for value in self._node.get_values(
|
||||
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
|
||||
self._current_fan_mode = value.data
|
||||
self._fan_list = list(value.data_items)
|
||||
_LOGGER.debug("self._fan_list=%s", self._fan_list)
|
||||
_LOGGER.debug("self._current_fan_mode=%s",
|
||||
self._current_fan_mode)
|
||||
# Swing mode
|
||||
if self._zxt_120 == 1:
|
||||
for value in self._node.get_values(
|
||||
class_id=COMMAND_CLASS_CONFIGURATION).values():
|
||||
if value.command_class == 112 and value.index == 33:
|
||||
self._current_swing_mode = value.data
|
||||
self._swing_list = list(value.data_items)
|
||||
_LOGGER.debug("self._swing_list=%s", self._swing_list)
|
||||
_LOGGER.debug("self._current_swing_mode=%s",
|
||||
self._current_swing_mode)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling on ZWave."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
"""Return the fan speed set."""
|
||||
return self._current_fan_mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
"""List of available fan modes."""
|
||||
return self._fan_list
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
"""Return the swing mode set."""
|
||||
return self._current_swing_mode
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
"""List of available swing modes."""
|
||||
return self._swing_list
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
unit = self._unit
|
||||
if unit == 'C':
|
||||
return TEMP_CELSIUS
|
||||
elif unit == 'F':
|
||||
return TEMP_FAHRENHEIT
|
||||
else:
|
||||
_LOGGER.exception("unit_of_measurement=%s is not valid",
|
||||
unit)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return the current operation mode."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of available operation modes."""
|
||||
return self._operation_list
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._target_temperature
|
||||
|
||||
def set_temperature(self, temperature):
|
||||
"""Set new target temperature."""
|
||||
for value in self._node.get_values(
|
||||
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
|
||||
if value.command_class != 67 and value.index != self._index:
|
||||
continue
|
||||
if self._zxt_120:
|
||||
# ZXT-120 does not support get setpoint
|
||||
self._target_temperature = temperature
|
||||
if ZXT_120_SET_TEMP.get(self._current_operation) \
|
||||
!= value.index:
|
||||
continue
|
||||
_LOGGER.debug("ZXT_120_SET_TEMP=%s and"
|
||||
" self._current_operation=%s",
|
||||
ZXT_120_SET_TEMP.get(self._current_operation),
|
||||
self._current_operation)
|
||||
# ZXT-120 responds only to whole int
|
||||
value.data = int(round(temperature, 0))
|
||||
else:
|
||||
value.data = int(temperature)
|
||||
break
|
||||
|
||||
def set_fan_mode(self, fan):
|
||||
"""Set new target fan mode."""
|
||||
for value in self._node.get_values(
|
||||
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
|
||||
if value.command_class == 68 and value.index == 0:
|
||||
value.data = bytes(fan, 'utf-8')
|
||||
break
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode."""
|
||||
for value in self._node.get_values(
|
||||
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
|
||||
if value.command_class == 64 and value.index == 0:
|
||||
value.data = bytes(operation_mode, 'utf-8')
|
||||
break
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing mode."""
|
||||
if self._zxt_120 == 1:
|
||||
for value in self._node.get_values(
|
||||
class_id=COMMAND_CLASS_CONFIGURATION).values():
|
||||
if value.command_class == 112 and value.index == 33:
|
||||
value.data = bytes(swing_mode, 'utf-8')
|
||||
break
|
@ -69,7 +69,7 @@ def setup_ecobee(hass, network, config):
|
||||
|
||||
hold_temp = config[DOMAIN].get(HOLD_TEMP, False)
|
||||
|
||||
discovery.load_platform(hass, 'thermostat', DOMAIN,
|
||||
discovery.load_platform(hass, 'climate', DOMAIN,
|
||||
{'hold_temp': hold_temp}, config)
|
||||
discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
|
||||
discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config)
|
||||
|
@ -28,7 +28,7 @@ DISCOVER_LIGHTS = 'homematic.light'
|
||||
DISCOVER_SENSORS = 'homematic.sensor'
|
||||
DISCOVER_BINARY_SENSORS = 'homematic.binary_sensor'
|
||||
DISCOVER_ROLLERSHUTTER = 'homematic.rollershutter'
|
||||
DISCOVER_THERMOSTATS = 'homematic.thermostat'
|
||||
DISCOVER_THERMOSTATS = 'homematic.climate'
|
||||
|
||||
ATTR_DISCOVER_DEVICES = 'devices'
|
||||
ATTR_PARAM = 'param'
|
||||
@ -214,7 +214,7 @@ def system_callback_handler(hass, config, src, *args):
|
||||
('rollershutter', DISCOVER_ROLLERSHUTTER),
|
||||
('binary_sensor', DISCOVER_BINARY_SENSORS),
|
||||
('sensor', DISCOVER_SENSORS),
|
||||
('thermostat', DISCOVER_THERMOSTATS)):
|
||||
('climate', DISCOVER_THERMOSTATS)):
|
||||
# Get all devices of a specific type
|
||||
found_devices = _get_devices(discovery_type, key_dict)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
Support for Nest thermostats and protect smoke alarms.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/thermostat.nest/
|
||||
https://home-assistant.io/components/climate.nest/
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
|
@ -57,6 +57,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
COMMAND_CLASS_THERMOSTAT_SETPOINT)):
|
||||
return
|
||||
|
||||
if value.command_class != COMMAND_CLASS_SENSOR_MULTILEVEL and \
|
||||
value.command_class != COMMAND_CLASS_THERMOSTAT_SETPOINT:
|
||||
return
|
||||
|
||||
add_devices([ZWaveThermostat(value)])
|
||||
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
|
||||
discovery_info, zwave.NETWORK)
|
||||
|
@ -151,18 +151,6 @@ DISCOVERY_COMPONENTS = [
|
||||
[COMMAND_CLASS_SENSOR_BINARY],
|
||||
TYPE_BOOL,
|
||||
GENRE_USER),
|
||||
('thermostat',
|
||||
[GENERIC_COMMAND_CLASS_THERMOSTAT],
|
||||
[SPECIFIC_DEVICE_CLASS_WHATEVER],
|
||||
[COMMAND_CLASS_THERMOSTAT_SETPOINT],
|
||||
TYPE_WHATEVER,
|
||||
GENRE_WHATEVER),
|
||||
('hvac',
|
||||
[GENERIC_COMMAND_CLASS_THERMOSTAT],
|
||||
[SPECIFIC_DEVICE_CLASS_WHATEVER],
|
||||
[COMMAND_CLASS_THERMOSTAT_FAN_MODE],
|
||||
TYPE_WHATEVER,
|
||||
GENRE_WHATEVER),
|
||||
('lock',
|
||||
[GENERIC_COMMAND_CLASS_ENTRY_CONTROL],
|
||||
[SPECIFIC_DEVICE_CLASS_ADVANCED_DOOR_LOCK,
|
||||
@ -186,7 +174,13 @@ DISCOVERY_COMPONENTS = [
|
||||
[COMMAND_CLASS_SWITCH_BINARY,
|
||||
COMMAND_CLASS_BARRIER_OPERATOR],
|
||||
TYPE_BOOL,
|
||||
GENRE_USER)
|
||||
GENRE_USER),
|
||||
('climate',
|
||||
[GENERIC_COMMAND_CLASS_THERMOSTAT],
|
||||
[SPECIFIC_DEVICE_CLASS_WHATEVER],
|
||||
[COMMAND_CLASS_THERMOSTAT_SETPOINT],
|
||||
TYPE_WHATEVER,
|
||||
GENRE_WHATEVER),
|
||||
]
|
||||
|
||||
|
||||
|
@ -40,6 +40,7 @@ blinkstick==1.1.7
|
||||
# homeassistant.components.sensor.bitcoin
|
||||
blockchain==1.3.3
|
||||
|
||||
# homeassistant.components.climate.eq3btsmart
|
||||
# homeassistant.components.thermostat.eq3btsmart
|
||||
# bluepy_devices==0.2.0
|
||||
|
||||
@ -67,6 +68,7 @@ eliqonline==1.0.12
|
||||
# homeassistant.components.enocean
|
||||
enocean==0.31
|
||||
|
||||
# homeassistant.components.climate.honeywell
|
||||
# homeassistant.components.thermostat.honeywell
|
||||
evohomeclient==0.2.5
|
||||
|
||||
@ -103,6 +105,7 @@ ha-ffmpeg==0.4
|
||||
# homeassistant.components.mqtt.server
|
||||
hbmqtt==0.7.1
|
||||
|
||||
# homeassistant.components.climate.heatmiser
|
||||
# homeassistant.components.thermostat.heatmiser
|
||||
heatmiserV3==0.9.1
|
||||
|
||||
@ -254,6 +257,7 @@ plexapi==2.0.2
|
||||
# homeassistant.components.sensor.serial_pm
|
||||
pmsensor==0.2
|
||||
|
||||
# homeassistant.components.climate.proliphix
|
||||
# homeassistant.components.thermostat.proliphix
|
||||
proliphix==0.3.1
|
||||
|
||||
@ -388,6 +392,7 @@ pyvera==0.2.15
|
||||
# homeassistant.components.wemo
|
||||
pywemo==0.4.5
|
||||
|
||||
# homeassistant.components.climate.radiotherm
|
||||
# homeassistant.components.thermostat.radiotherm
|
||||
radiotherm==1.2
|
||||
|
||||
@ -418,6 +423,7 @@ sleekxmpp==1.3.1
|
||||
# homeassistant.components.media_player.snapcast
|
||||
snapcast==1.2.1
|
||||
|
||||
# homeassistant.components.climate.honeywell
|
||||
# homeassistant.components.thermostat.honeywell
|
||||
somecomfort==0.2.1
|
||||
|
||||
|
1
tests/components/climate/__init__.py
Normal file
1
tests/components/climate/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""The tests for climate component."""
|
166
tests/components/climate/test_demo.py
Normal file
166
tests/components/climate/test_demo.py
Normal file
@ -0,0 +1,166 @@
|
||||
"""The tests for the demo climate component."""
|
||||
import unittest
|
||||
|
||||
from homeassistant.util.unit_system import (
|
||||
METRIC_SYSTEM,
|
||||
)
|
||||
from homeassistant.components import climate
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
ENTITY_CLIMATE = 'climate.hvac'
|
||||
|
||||
|
||||
class TestDemoClimate(unittest.TestCase):
|
||||
"""Test the demo climate hvac."""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.hass.config.units = METRIC_SYSTEM
|
||||
self.assertTrue(climate.setup(self.hass, {'climate': {
|
||||
'platform': 'demo',
|
||||
}}))
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_setup_params(self):
|
||||
"""Test the inititial parameters."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual(21, state.attributes.get('temperature'))
|
||||
self.assertEqual('on', state.attributes.get('away_mode'))
|
||||
self.assertEqual(22, state.attributes.get('current_temperature'))
|
||||
self.assertEqual("On High", state.attributes.get('fan_mode'))
|
||||
self.assertEqual(67, state.attributes.get('humidity'))
|
||||
self.assertEqual(54, state.attributes.get('current_humidity'))
|
||||
self.assertEqual("Off", state.attributes.get('swing_mode'))
|
||||
self.assertEqual("Cool", state.attributes.get('operation_mode'))
|
||||
self.assertEqual('off', state.attributes.get('aux_heat'))
|
||||
|
||||
def test_default_setup_params(self):
|
||||
"""Test the setup with default parameters."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual(7, state.attributes.get('min_temp'))
|
||||
self.assertEqual(35, state.attributes.get('max_temp'))
|
||||
self.assertEqual(30, state.attributes.get('min_humidity'))
|
||||
self.assertEqual(99, state.attributes.get('max_humidity'))
|
||||
|
||||
def test_set_target_temp_bad_attr(self):
|
||||
"""Test setting the target temperature without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual(21, state.attributes.get('temperature'))
|
||||
climate.set_temperature(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(21, state.attributes.get('temperature'))
|
||||
|
||||
def test_set_target_temp(self):
|
||||
"""Test the setting of the target temperature."""
|
||||
climate.set_temperature(self.hass, 30, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual(30.0, state.attributes.get('temperature'))
|
||||
|
||||
def test_set_target_humidity_bad_attr(self):
|
||||
"""Test setting the target humidity without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual(67, state.attributes.get('humidity'))
|
||||
climate.set_humidity(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(67, state.attributes.get('humidity'))
|
||||
|
||||
def test_set_target_humidity(self):
|
||||
"""Test the setting of the target humidity."""
|
||||
climate.set_humidity(self.hass, 64, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual(64.0, state.attributes.get('humidity'))
|
||||
|
||||
def test_set_fan_mode_bad_attr(self):
|
||||
"""Test setting fan mode without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual("On High", state.attributes.get('fan_mode'))
|
||||
climate.set_fan_mode(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual("On High", state.attributes.get('fan_mode'))
|
||||
|
||||
def test_set_fan_mode(self):
|
||||
"""Test setting of new fan mode."""
|
||||
climate.set_fan_mode(self.hass, "On Low", ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual("On Low", state.attributes.get('fan_mode'))
|
||||
|
||||
def test_set_swing_mode_bad_attr(self):
|
||||
"""Test setting swing mode without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual("Off", state.attributes.get('swing_mode'))
|
||||
climate.set_swing_mode(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual("Off", state.attributes.get('swing_mode'))
|
||||
|
||||
def test_set_swing(self):
|
||||
"""Test setting of new swing mode."""
|
||||
climate.set_swing_mode(self.hass, "Auto", ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual("Auto", state.attributes.get('swing_mode'))
|
||||
|
||||
def test_set_operation_bad_attr(self):
|
||||
"""Test setting operation mode without required attribute."""
|
||||
self.assertEqual("Cool", self.hass.states.get(ENTITY_CLIMATE).state)
|
||||
climate.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual("Cool", self.hass.states.get(ENTITY_CLIMATE).state)
|
||||
|
||||
def test_set_operation(self):
|
||||
"""Test setting of new operation mode."""
|
||||
climate.set_operation_mode(self.hass, "Heat", ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual("Heat", self.hass.states.get(ENTITY_CLIMATE).state)
|
||||
|
||||
def test_set_away_mode_bad_attr(self):
|
||||
"""Test setting the away mode without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual('on', state.attributes.get('away_mode'))
|
||||
climate.set_away_mode(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual('on', state.attributes.get('away_mode'))
|
||||
|
||||
def test_set_away_mode_on(self):
|
||||
"""Test setting the away mode on/true."""
|
||||
climate.set_away_mode(self.hass, True, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual('on', state.attributes.get('away_mode'))
|
||||
|
||||
def test_set_away_mode_off(self):
|
||||
"""Test setting the away mode off/false."""
|
||||
climate.set_away_mode(self.hass, False, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual('off', state.attributes.get('away_mode'))
|
||||
|
||||
def test_set_aux_heat_bad_attr(self):
|
||||
"""Test setting the auxillary heater without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual('off', state.attributes.get('aux_heat'))
|
||||
climate.set_aux_heat(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual('off', state.attributes.get('aux_heat'))
|
||||
|
||||
def test_set_aux_heat_on(self):
|
||||
"""Test setting the axillary heater on/true."""
|
||||
climate.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual('on', state.attributes.get('aux_heat'))
|
||||
|
||||
def test_set_aux_heat_off(self):
|
||||
"""Test setting the auxillary heater off/false."""
|
||||
climate.set_aux_heat(self.hass, False, ENTITY_CLIMATE)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual('off', state.attributes.get('aux_heat'))
|
493
tests/components/climate/test_generic_thermostat.py
Normal file
493
tests/components/climate/test_generic_thermostat.py
Normal file
@ -0,0 +1,493 @@
|
||||
"""The tests for the generic_thermostat."""
|
||||
import datetime
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
|
||||
from homeassistant.bootstrap import _setup_component
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
STATE_ON,
|
||||
STATE_OFF,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
from homeassistant.components import climate
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
ENTITY = 'climate.test'
|
||||
ENT_SENSOR = 'sensor.test'
|
||||
ENT_SWITCH = 'switch.test'
|
||||
MIN_TEMP = 3.0
|
||||
MAX_TEMP = 65.0
|
||||
TARGET_TEMP = 42.0
|
||||
|
||||
|
||||
class TestSetupClimateGenericThermostat(unittest.TestCase):
|
||||
"""Test the Generic thermostat with custom config."""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_setup_missing_conf(self):
|
||||
"""Test set up heat_control with missing config values."""
|
||||
config = {
|
||||
'name': 'test',
|
||||
'target_sensor': ENT_SENSOR
|
||||
}
|
||||
self.assertFalse(_setup_component(self.hass, 'climate', {
|
||||
'climate': config}))
|
||||
|
||||
def test_valid_conf(self):
|
||||
"""Test set up genreic_thermostat with valid config values."""
|
||||
self.assertTrue(_setup_component(self.hass, 'climate',
|
||||
{'climate': {
|
||||
'platform': 'generic_thermostat',
|
||||
'name': 'test',
|
||||
'heater': ENT_SWITCH,
|
||||
'target_sensor': ENT_SENSOR}}))
|
||||
|
||||
def test_setup_with_sensor(self):
|
||||
"""Test set up heat_control with sensor to trigger update at init."""
|
||||
self.hass.states.set(ENT_SENSOR, 22.0, {
|
||||
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS
|
||||
})
|
||||
climate.setup(self.hass, {'climate': {
|
||||
'platform': 'generic_thermostat',
|
||||
'name': 'test',
|
||||
'heater': ENT_SWITCH,
|
||||
'target_sensor': ENT_SENSOR
|
||||
}})
|
||||
state = self.hass.states.get(ENTITY)
|
||||
self.assertEqual(
|
||||
TEMP_CELSIUS, state.attributes.get('unit_of_measurement'))
|
||||
self.assertEqual(22.0, state.attributes.get('current_temperature'))
|
||||
|
||||
|
||||
class TestClimateGenericThermostat(unittest.TestCase):
|
||||
"""Test the Generic thermostat."""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.hass.config.units = METRIC_SYSTEM
|
||||
climate.setup(self.hass, {'climate': {
|
||||
'platform': 'generic_thermostat',
|
||||
'name': 'test',
|
||||
'heater': ENT_SWITCH,
|
||||
'target_sensor': ENT_SENSOR
|
||||
}})
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_setup_defaults_to_unknown(self):
|
||||
"""Test the setting of defaults to unknown."""
|
||||
self.assertEqual('unknown', self.hass.states.get(ENTITY).state)
|
||||
|
||||
def test_default_setup_params(self):
|
||||
"""Test the setup with default parameters."""
|
||||
state = self.hass.states.get(ENTITY)
|
||||
self.assertEqual(7, state.attributes.get('min_temp'))
|
||||
self.assertEqual(35, state.attributes.get('max_temp'))
|
||||
self.assertEqual(None, state.attributes.get('temperature'))
|
||||
|
||||
def test_custom_setup_params(self):
|
||||
"""Test the setup with custom parameters."""
|
||||
climate.setup(self.hass, {'climate': {
|
||||
'platform': 'generic_thermostat',
|
||||
'name': 'test',
|
||||
'heater': ENT_SWITCH,
|
||||
'target_sensor': ENT_SENSOR,
|
||||
'min_temp': MIN_TEMP,
|
||||
'max_temp': MAX_TEMP,
|
||||
'target_temp': TARGET_TEMP
|
||||
}})
|
||||
state = self.hass.states.get(ENTITY)
|
||||
self.assertEqual(MIN_TEMP, state.attributes.get('min_temp'))
|
||||
self.assertEqual(MAX_TEMP, state.attributes.get('max_temp'))
|
||||
self.assertEqual(TARGET_TEMP, state.attributes.get('temperature'))
|
||||
|
||||
def test_set_target_temp(self):
|
||||
"""Test the setting of the target temperature."""
|
||||
climate.set_temperature(self.hass, 30)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ENTITY)
|
||||
self.assertEqual(30.0, state.attributes.get('temperature'))
|
||||
|
||||
def test_sensor_bad_unit(self):
|
||||
"""Test sensor that have bad unit."""
|
||||
state = self.hass.states.get(ENTITY)
|
||||
temp = state.attributes.get('current_temperature')
|
||||
unit = state.attributes.get('unit_of_measurement')
|
||||
|
||||
self._setup_sensor(22.0, unit='bad_unit')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get(ENTITY)
|
||||
self.assertEqual(unit, state.attributes.get('unit_of_measurement'))
|
||||
self.assertEqual(temp, state.attributes.get('current_temperature'))
|
||||
|
||||
def test_sensor_bad_value(self):
|
||||
"""Test sensor that have None as state."""
|
||||
state = self.hass.states.get(ENTITY)
|
||||
temp = state.attributes.get('current_temperature')
|
||||
unit = state.attributes.get('unit_of_measurement')
|
||||
|
||||
self._setup_sensor(None)
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get(ENTITY)
|
||||
self.assertEqual(unit, state.attributes.get('unit_of_measurement'))
|
||||
self.assertEqual(temp, state.attributes.get('current_temperature'))
|
||||
|
||||
def test_set_target_temp_heater_on(self):
|
||||
"""Test if target temperature turn heater on."""
|
||||
self._setup_switch(False)
|
||||
self._setup_sensor(25)
|
||||
self.hass.pool.block_till_done()
|
||||
climate.set_temperature(self.hass, 30)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_ON, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def test_set_target_temp_heater_off(self):
|
||||
"""Test if target temperature turn heater off."""
|
||||
self._setup_switch(True)
|
||||
self._setup_sensor(30)
|
||||
self.hass.pool.block_till_done()
|
||||
climate.set_temperature(self.hass, 25)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def test_set_temp_change_heater_on(self):
|
||||
"""Test if temperature change turn heater on."""
|
||||
self._setup_switch(False)
|
||||
climate.set_temperature(self.hass, 30)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(25)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_ON, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def test_temp_change_heater_off(self):
|
||||
"""Test if temperature change turn heater off."""
|
||||
self._setup_switch(True)
|
||||
climate.set_temperature(self.hass, 25)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(30)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
|
||||
"""Setup the test sensor."""
|
||||
self.hass.states.set(ENT_SENSOR, temp, {
|
||||
ATTR_UNIT_OF_MEASUREMENT: unit
|
||||
})
|
||||
|
||||
def _setup_switch(self, is_on):
|
||||
"""Setup the test switch."""
|
||||
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
|
||||
self.calls = []
|
||||
|
||||
def log_call(call):
|
||||
"""Log service calls."""
|
||||
self.calls.append(call)
|
||||
|
||||
self.hass.services.register('switch', SERVICE_TURN_ON, log_call)
|
||||
self.hass.services.register('switch', SERVICE_TURN_OFF, log_call)
|
||||
|
||||
|
||||
class TestClimateGenericThermostatACMode(unittest.TestCase):
|
||||
"""Test the Generic thermostat."""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.hass.config.temperature_unit = TEMP_CELSIUS
|
||||
climate.setup(self.hass, {'climate': {
|
||||
'platform': 'generic_thermostat',
|
||||
'name': 'test',
|
||||
'heater': ENT_SWITCH,
|
||||
'target_sensor': ENT_SENSOR,
|
||||
'ac_mode': True
|
||||
}})
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_set_target_temp_ac_off(self):
|
||||
"""Test if target temperature turn ac off."""
|
||||
self._setup_switch(True)
|
||||
self._setup_sensor(25)
|
||||
self.hass.pool.block_till_done()
|
||||
climate.set_temperature(self.hass, 30)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def test_set_target_temp_ac_on(self):
|
||||
"""Test if target temperature turn ac on."""
|
||||
self._setup_switch(False)
|
||||
self._setup_sensor(30)
|
||||
self.hass.pool.block_till_done()
|
||||
climate.set_temperature(self.hass, 25)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_ON, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def test_set_temp_change_ac_off(self):
|
||||
"""Test if temperature change turn ac off."""
|
||||
self._setup_switch(True)
|
||||
climate.set_temperature(self.hass, 30)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(25)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def test_temp_change_ac_on(self):
|
||||
"""Test if temperature change turn ac on."""
|
||||
self._setup_switch(False)
|
||||
climate.set_temperature(self.hass, 25)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(30)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_ON, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
|
||||
"""Setup the test sensor."""
|
||||
self.hass.states.set(ENT_SENSOR, temp, {
|
||||
ATTR_UNIT_OF_MEASUREMENT: unit
|
||||
})
|
||||
|
||||
def _setup_switch(self, is_on):
|
||||
"""Setup the test switch."""
|
||||
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
|
||||
self.calls = []
|
||||
|
||||
def log_call(call):
|
||||
"""Log service calls."""
|
||||
self.calls.append(call)
|
||||
|
||||
self.hass.services.register('switch', SERVICE_TURN_ON, log_call)
|
||||
self.hass.services.register('switch', SERVICE_TURN_OFF, log_call)
|
||||
|
||||
|
||||
class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase):
|
||||
"""Test the Generic Thermostat."""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.hass.config.temperature_unit = TEMP_CELSIUS
|
||||
climate.setup(self.hass, {'climate': {
|
||||
'platform': 'generic_thermostat',
|
||||
'name': 'test',
|
||||
'heater': ENT_SWITCH,
|
||||
'target_sensor': ENT_SENSOR,
|
||||
'ac_mode': True,
|
||||
'min_cycle_duration': datetime.timedelta(minutes=10)
|
||||
}})
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_temp_change_ac_trigger_on_not_long_enough(self):
|
||||
"""Test if temperature change turn ac on."""
|
||||
self._setup_switch(False)
|
||||
climate.set_temperature(self.hass, 25)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(30)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
def test_temp_change_ac_trigger_on_long_enough(self):
|
||||
"""Test if temperature change turn ac on."""
|
||||
fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11,
|
||||
tzinfo=datetime.timezone.utc)
|
||||
with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
|
||||
return_value=fake_changed):
|
||||
self._setup_switch(False)
|
||||
climate.set_temperature(self.hass, 25)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(30)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_ON, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def test_temp_change_ac_trigger_off_not_long_enough(self):
|
||||
"""Test if temperature change turn ac on."""
|
||||
self._setup_switch(True)
|
||||
climate.set_temperature(self.hass, 30)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(25)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
def test_temp_change_ac_trigger_off_long_enough(self):
|
||||
"""Test if temperature change turn ac on."""
|
||||
fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11,
|
||||
tzinfo=datetime.timezone.utc)
|
||||
with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
|
||||
return_value=fake_changed):
|
||||
self._setup_switch(True)
|
||||
climate.set_temperature(self.hass, 30)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(25)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
|
||||
"""Setup the test sensor."""
|
||||
self.hass.states.set(ENT_SENSOR, temp, {
|
||||
ATTR_UNIT_OF_MEASUREMENT: unit
|
||||
})
|
||||
|
||||
def _setup_switch(self, is_on):
|
||||
"""Setup the test switch."""
|
||||
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
|
||||
self.calls = []
|
||||
|
||||
def log_call(call):
|
||||
"""Log service calls."""
|
||||
self.calls.append(call)
|
||||
|
||||
self.hass.services.register('switch', SERVICE_TURN_ON, log_call)
|
||||
self.hass.services.register('switch', SERVICE_TURN_OFF, log_call)
|
||||
|
||||
|
||||
class TestClimateGenericThermostatMinCycle(unittest.TestCase):
|
||||
"""Test the Generic thermostat."""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.hass.config.temperature_unit = TEMP_CELSIUS
|
||||
climate.setup(self.hass, {'climate': {
|
||||
'platform': 'generic_thermostat',
|
||||
'name': 'test',
|
||||
'heater': ENT_SWITCH,
|
||||
'target_sensor': ENT_SENSOR,
|
||||
'min_cycle_duration': datetime.timedelta(minutes=10)
|
||||
}})
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_temp_change_heater_trigger_off_not_long_enough(self):
|
||||
"""Test if temp change doesn't turn heater off because of time."""
|
||||
self._setup_switch(True)
|
||||
climate.set_temperature(self.hass, 25)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(30)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
def test_temp_change_heater_trigger_on_not_long_enough(self):
|
||||
"""Test if temp change doesn't turn heater on because of time."""
|
||||
self._setup_switch(False)
|
||||
climate.set_temperature(self.hass, 30)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(25)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
def test_temp_change_heater_trigger_on_long_enough(self):
|
||||
"""Test if temperature change turn heater on after min cycle."""
|
||||
fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11,
|
||||
tzinfo=datetime.timezone.utc)
|
||||
with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
|
||||
return_value=fake_changed):
|
||||
self._setup_switch(False)
|
||||
climate.set_temperature(self.hass, 30)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(25)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_ON, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def test_temp_change_heater_trigger_off_long_enough(self):
|
||||
"""Test if temperature change turn heater off after min cycle."""
|
||||
fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11,
|
||||
tzinfo=datetime.timezone.utc)
|
||||
with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
|
||||
return_value=fake_changed):
|
||||
self._setup_switch(True)
|
||||
climate.set_temperature(self.hass, 25)
|
||||
self.hass.pool.block_till_done()
|
||||
self._setup_sensor(30)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
call = self.calls[0]
|
||||
self.assertEqual('switch', call.domain)
|
||||
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
||||
self.assertEqual(ENT_SWITCH, call.data['entity_id'])
|
||||
|
||||
def _setup_sensor(self, temp, unit=TEMP_CELSIUS):
|
||||
"""Setup the test sensor."""
|
||||
self.hass.states.set(ENT_SENSOR, temp, {
|
||||
ATTR_UNIT_OF_MEASUREMENT: unit
|
||||
})
|
||||
|
||||
def _setup_switch(self, is_on):
|
||||
"""Setup the test switch."""
|
||||
self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
|
||||
self.calls = []
|
||||
|
||||
def log_call(call):
|
||||
"""Log service calls."""
|
||||
self.calls.append(call)
|
||||
|
||||
self.hass.services.register('switch', SERVICE_TURN_ON, log_call)
|
||||
self.hass.services.register('switch', SERVICE_TURN_OFF, log_call)
|
377
tests/components/climate/test_honeywell.py
Normal file
377
tests/components/climate/test_honeywell.py
Normal file
@ -0,0 +1,377 @@
|
||||
"""The test the Honeywell thermostat module."""
|
||||
import socket
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import somecomfort
|
||||
|
||||
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD,
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
import homeassistant.components.climate.honeywell as honeywell
|
||||
|
||||
|
||||
class TestHoneywell(unittest.TestCase):
|
||||
"""A test class for Honeywell themostats."""
|
||||
|
||||
@mock.patch('somecomfort.SomeComfort')
|
||||
@mock.patch('homeassistant.components.climate.'
|
||||
'honeywell.HoneywellUSThermostat')
|
||||
def test_setup_us(self, mock_ht, mock_sc):
|
||||
"""Test for the US setup."""
|
||||
config = {
|
||||
CONF_USERNAME: 'user',
|
||||
CONF_PASSWORD: 'pass',
|
||||
'region': 'us',
|
||||
}
|
||||
bad_pass_config = {
|
||||
CONF_USERNAME: 'user',
|
||||
'region': 'us',
|
||||
}
|
||||
bad_region_config = {
|
||||
CONF_USERNAME: 'user',
|
||||
CONF_PASSWORD: 'pass',
|
||||
'region': 'un',
|
||||
}
|
||||
hass = mock.MagicMock()
|
||||
add_devices = mock.MagicMock()
|
||||
|
||||
locations = [
|
||||
mock.MagicMock(),
|
||||
mock.MagicMock(),
|
||||
]
|
||||
devices_1 = [mock.MagicMock()]
|
||||
devices_2 = [mock.MagicMock(), mock.MagicMock]
|
||||
mock_sc.return_value.locations_by_id.values.return_value = \
|
||||
locations
|
||||
locations[0].devices_by_id.values.return_value = devices_1
|
||||
locations[1].devices_by_id.values.return_value = devices_2
|
||||
|
||||
result = honeywell.setup_platform(hass, bad_pass_config, add_devices)
|
||||
self.assertFalse(result)
|
||||
result = honeywell.setup_platform(hass, bad_region_config, add_devices)
|
||||
self.assertFalse(result)
|
||||
result = honeywell.setup_platform(hass, config, add_devices)
|
||||
self.assertTrue(result)
|
||||
mock_sc.assert_called_once_with('user', 'pass')
|
||||
mock_ht.assert_has_calls([
|
||||
mock.call(mock_sc.return_value, devices_1[0]),
|
||||
mock.call(mock_sc.return_value, devices_2[0]),
|
||||
mock.call(mock_sc.return_value, devices_2[1]),
|
||||
])
|
||||
|
||||
@mock.patch('somecomfort.SomeComfort')
|
||||
def test_setup_us_failures(self, mock_sc):
|
||||
"""Test the US setup."""
|
||||
hass = mock.MagicMock()
|
||||
add_devices = mock.MagicMock()
|
||||
config = {
|
||||
CONF_USERNAME: 'user',
|
||||
CONF_PASSWORD: 'pass',
|
||||
'region': 'us',
|
||||
}
|
||||
|
||||
mock_sc.side_effect = somecomfort.AuthError
|
||||
result = honeywell.setup_platform(hass, config, add_devices)
|
||||
self.assertFalse(result)
|
||||
self.assertFalse(add_devices.called)
|
||||
|
||||
mock_sc.side_effect = somecomfort.SomeComfortError
|
||||
result = honeywell.setup_platform(hass, config, add_devices)
|
||||
self.assertFalse(result)
|
||||
self.assertFalse(add_devices.called)
|
||||
|
||||
@mock.patch('somecomfort.SomeComfort')
|
||||
@mock.patch('homeassistant.components.climate.'
|
||||
'honeywell.HoneywellUSThermostat')
|
||||
def _test_us_filtered_devices(self, mock_ht, mock_sc, loc=None, dev=None):
|
||||
"""Test for US filtered thermostats."""
|
||||
config = {
|
||||
CONF_USERNAME: 'user',
|
||||
CONF_PASSWORD: 'pass',
|
||||
'region': 'us',
|
||||
'location': loc,
|
||||
'thermostat': dev,
|
||||
}
|
||||
locations = {
|
||||
1: mock.MagicMock(locationid=mock.sentinel.loc1,
|
||||
devices_by_id={
|
||||
11: mock.MagicMock(
|
||||
deviceid=mock.sentinel.loc1dev1),
|
||||
12: mock.MagicMock(
|
||||
deviceid=mock.sentinel.loc1dev2),
|
||||
}),
|
||||
2: mock.MagicMock(locationid=mock.sentinel.loc2,
|
||||
devices_by_id={
|
||||
21: mock.MagicMock(
|
||||
deviceid=mock.sentinel.loc2dev1),
|
||||
}),
|
||||
3: mock.MagicMock(locationid=mock.sentinel.loc3,
|
||||
devices_by_id={
|
||||
31: mock.MagicMock(
|
||||
deviceid=mock.sentinel.loc3dev1),
|
||||
}),
|
||||
}
|
||||
mock_sc.return_value = mock.MagicMock(locations_by_id=locations)
|
||||
hass = mock.MagicMock()
|
||||
add_devices = mock.MagicMock()
|
||||
self.assertEqual(True,
|
||||
honeywell.setup_platform(hass, config, add_devices))
|
||||
|
||||
return mock_ht.call_args_list, mock_sc
|
||||
|
||||
def test_us_filtered_thermostat_1(self):
|
||||
"""Test for US filtered thermostats."""
|
||||
result, client = self._test_us_filtered_devices(
|
||||
dev=mock.sentinel.loc1dev1)
|
||||
devices = [x[0][1].deviceid for x in result]
|
||||
self.assertEqual([mock.sentinel.loc1dev1], devices)
|
||||
|
||||
def test_us_filtered_thermostat_2(self):
|
||||
"""Test for US filtered location."""
|
||||
result, client = self._test_us_filtered_devices(
|
||||
dev=mock.sentinel.loc2dev1)
|
||||
devices = [x[0][1].deviceid for x in result]
|
||||
self.assertEqual([mock.sentinel.loc2dev1], devices)
|
||||
|
||||
def test_us_filtered_location_1(self):
|
||||
"""Test for US filtered locations."""
|
||||
result, client = self._test_us_filtered_devices(
|
||||
loc=mock.sentinel.loc1)
|
||||
devices = [x[0][1].deviceid for x in result]
|
||||
self.assertEqual([mock.sentinel.loc1dev1,
|
||||
mock.sentinel.loc1dev2], devices)
|
||||
|
||||
def test_us_filtered_location_2(self):
|
||||
"""Test for US filtered locations."""
|
||||
result, client = self._test_us_filtered_devices(
|
||||
loc=mock.sentinel.loc2)
|
||||
devices = [x[0][1].deviceid for x in result]
|
||||
self.assertEqual([mock.sentinel.loc2dev1], devices)
|
||||
|
||||
@mock.patch('evohomeclient.EvohomeClient')
|
||||
@mock.patch('homeassistant.components.climate.honeywell.'
|
||||
'RoundThermostat')
|
||||
def test_eu_setup_full_config(self, mock_round, mock_evo):
|
||||
"""Test the EU setup wwith complete configuration."""
|
||||
config = {
|
||||
CONF_USERNAME: 'user',
|
||||
CONF_PASSWORD: 'pass',
|
||||
honeywell.CONF_AWAY_TEMP: 20,
|
||||
'region': 'eu',
|
||||
}
|
||||
mock_evo.return_value.temperatures.return_value = [
|
||||
{'id': 'foo'}, {'id': 'bar'}]
|
||||
hass = mock.MagicMock()
|
||||
add_devices = mock.MagicMock()
|
||||
self.assertTrue(honeywell.setup_platform(hass, config, add_devices))
|
||||
mock_evo.assert_called_once_with('user', 'pass')
|
||||
mock_evo.return_value.temperatures.assert_called_once_with(
|
||||
force_refresh=True)
|
||||
mock_round.assert_has_calls([
|
||||
mock.call(mock_evo.return_value, 'foo', True, 20),
|
||||
mock.call(mock_evo.return_value, 'bar', False, 20),
|
||||
])
|
||||
self.assertEqual(2, add_devices.call_count)
|
||||
|
||||
@mock.patch('evohomeclient.EvohomeClient')
|
||||
@mock.patch('homeassistant.components.climate.honeywell.'
|
||||
'RoundThermostat')
|
||||
def test_eu_setup_partial_config(self, mock_round, mock_evo):
|
||||
"""Test the EU setup with partial configuration."""
|
||||
config = {
|
||||
CONF_USERNAME: 'user',
|
||||
CONF_PASSWORD: 'pass',
|
||||
'region': 'eu',
|
||||
}
|
||||
mock_evo.return_value.temperatures.return_value = [
|
||||
{'id': 'foo'}, {'id': 'bar'}]
|
||||
hass = mock.MagicMock()
|
||||
add_devices = mock.MagicMock()
|
||||
self.assertTrue(honeywell.setup_platform(hass, config, add_devices))
|
||||
default = honeywell.DEFAULT_AWAY_TEMP
|
||||
mock_round.assert_has_calls([
|
||||
mock.call(mock_evo.return_value, 'foo', True, default),
|
||||
mock.call(mock_evo.return_value, 'bar', False, default),
|
||||
])
|
||||
|
||||
@mock.patch('evohomeclient.EvohomeClient')
|
||||
@mock.patch('homeassistant.components.climate.honeywell.'
|
||||
'RoundThermostat')
|
||||
def test_eu_setup_bad_temp(self, mock_round, mock_evo):
|
||||
"""Test the EU setup with invalid temperature."""
|
||||
config = {
|
||||
CONF_USERNAME: 'user',
|
||||
CONF_PASSWORD: 'pass',
|
||||
honeywell.CONF_AWAY_TEMP: 'ponies',
|
||||
'region': 'eu',
|
||||
}
|
||||
self.assertFalse(honeywell.setup_platform(None, config, None))
|
||||
|
||||
@mock.patch('evohomeclient.EvohomeClient')
|
||||
@mock.patch('homeassistant.components.climate.honeywell.'
|
||||
'RoundThermostat')
|
||||
def test_eu_setup_error(self, mock_round, mock_evo):
|
||||
"""Test the EU setup with errors."""
|
||||
config = {
|
||||
CONF_USERNAME: 'user',
|
||||
CONF_PASSWORD: 'pass',
|
||||
honeywell.CONF_AWAY_TEMP: 20,
|
||||
'region': 'eu',
|
||||
}
|
||||
mock_evo.return_value.temperatures.side_effect = socket.error
|
||||
add_devices = mock.MagicMock()
|
||||
hass = mock.MagicMock()
|
||||
self.assertFalse(honeywell.setup_platform(hass, config, add_devices))
|
||||
|
||||
|
||||
class TestHoneywellRound(unittest.TestCase):
|
||||
"""A test class for Honeywell Round thermostats."""
|
||||
|
||||
def setup_method(self, method):
|
||||
"""Test the setup method."""
|
||||
def fake_temperatures(force_refresh=None):
|
||||
"""Create fake temperatures."""
|
||||
temps = [
|
||||
{'id': '1', 'temp': 20, 'setpoint': 21,
|
||||
'thermostat': 'main', 'name': 'House'},
|
||||
{'id': '2', 'temp': 21, 'setpoint': 22,
|
||||
'thermostat': 'DOMESTIC_HOT_WATER'},
|
||||
]
|
||||
return temps
|
||||
|
||||
self.device = mock.MagicMock()
|
||||
self.device.temperatures.side_effect = fake_temperatures
|
||||
self.round1 = honeywell.RoundThermostat(self.device, '1',
|
||||
True, 16)
|
||||
self.round2 = honeywell.RoundThermostat(self.device, '2',
|
||||
False, 17)
|
||||
|
||||
def test_attributes(self):
|
||||
"""Test the attributes."""
|
||||
self.assertEqual('House', self.round1.name)
|
||||
self.assertEqual(TEMP_CELSIUS, self.round1.unit_of_measurement)
|
||||
self.assertEqual(20, self.round1.current_temperature)
|
||||
self.assertEqual(21, self.round1.target_temperature)
|
||||
self.assertFalse(self.round1.is_away_mode_on)
|
||||
|
||||
self.assertEqual('Hot Water', self.round2.name)
|
||||
self.assertEqual(TEMP_CELSIUS, self.round2.unit_of_measurement)
|
||||
self.assertEqual(21, self.round2.current_temperature)
|
||||
self.assertEqual(None, self.round2.target_temperature)
|
||||
self.assertFalse(self.round2.is_away_mode_on)
|
||||
|
||||
def test_away_mode(self):
|
||||
"""Test setting the away mode."""
|
||||
self.assertFalse(self.round1.is_away_mode_on)
|
||||
self.round1.turn_away_mode_on()
|
||||
self.assertTrue(self.round1.is_away_mode_on)
|
||||
self.device.set_temperature.assert_called_once_with('House', 16)
|
||||
|
||||
self.device.set_temperature.reset_mock()
|
||||
self.round1.turn_away_mode_off()
|
||||
self.assertFalse(self.round1.is_away_mode_on)
|
||||
self.device.cancel_temp_override.assert_called_once_with('House')
|
||||
|
||||
def test_set_temperature(self):
|
||||
"""Test setting the temperature."""
|
||||
self.round1.set_temperature(25)
|
||||
self.device.set_temperature.assert_called_once_with('House', 25)
|
||||
|
||||
def test_set_operation_mode(self: unittest.TestCase) -> None:
|
||||
"""Test setting the system operation."""
|
||||
self.round1.set_operation_mode('cool')
|
||||
self.assertEqual('cool', self.round1.current_operation)
|
||||
self.assertEqual('cool', self.device.system_mode)
|
||||
|
||||
self.round1.set_operation_mode('heat')
|
||||
self.assertEqual('heat', self.round1.current_operation)
|
||||
self.assertEqual('heat', self.device.system_mode)
|
||||
|
||||
|
||||
class TestHoneywellUS(unittest.TestCase):
|
||||
"""A test class for Honeywell US thermostats."""
|
||||
|
||||
def setup_method(self, method):
|
||||
"""Test the setup method."""
|
||||
self.client = mock.MagicMock()
|
||||
self.device = mock.MagicMock()
|
||||
self.honeywell = honeywell.HoneywellUSThermostat(
|
||||
self.client, self.device)
|
||||
|
||||
self.device.fan_running = True
|
||||
self.device.name = 'test'
|
||||
self.device.temperature_unit = 'F'
|
||||
self.device.current_temperature = 72
|
||||
self.device.setpoint_cool = 78
|
||||
self.device.setpoint_heat = 65
|
||||
self.device.system_mode = 'heat'
|
||||
self.device.fan_mode = 'auto'
|
||||
|
||||
def test_properties(self):
|
||||
"""Test the properties."""
|
||||
self.assertTrue(self.honeywell.is_fan_on)
|
||||
self.assertEqual('test', self.honeywell.name)
|
||||
self.assertEqual(72, self.honeywell.current_temperature)
|
||||
|
||||
def test_unit_of_measurement(self):
|
||||
"""Test the unit of measurement."""
|
||||
self.assertEqual(TEMP_FAHRENHEIT, self.honeywell.unit_of_measurement)
|
||||
self.device.temperature_unit = 'C'
|
||||
self.assertEqual(TEMP_CELSIUS, self.honeywell.unit_of_measurement)
|
||||
|
||||
def test_target_temp(self):
|
||||
"""Test the target temperature."""
|
||||
self.assertEqual(65, self.honeywell.target_temperature)
|
||||
self.device.system_mode = 'cool'
|
||||
self.assertEqual(78, self.honeywell.target_temperature)
|
||||
|
||||
def test_set_temp(self):
|
||||
"""Test setting the temperature."""
|
||||
self.honeywell.set_temperature(70)
|
||||
self.assertEqual(70, self.device.setpoint_heat)
|
||||
self.assertEqual(70, self.honeywell.target_temperature)
|
||||
|
||||
self.device.system_mode = 'cool'
|
||||
self.assertEqual(78, self.honeywell.target_temperature)
|
||||
self.honeywell.set_temperature(74)
|
||||
self.assertEqual(74, self.device.setpoint_cool)
|
||||
self.assertEqual(74, self.honeywell.target_temperature)
|
||||
|
||||
def test_set_operation_mode(self: unittest.TestCase) -> None:
|
||||
"""Test setting the operation mode."""
|
||||
self.honeywell.set_operation_mode('cool')
|
||||
self.assertEqual('cool', self.honeywell.current_operation)
|
||||
self.assertEqual('cool', self.device.system_mode)
|
||||
|
||||
self.honeywell.set_operation_mode('heat')
|
||||
self.assertEqual('heat', self.honeywell.current_operation)
|
||||
self.assertEqual('heat', self.device.system_mode)
|
||||
|
||||
def test_set_temp_fail(self):
|
||||
"""Test if setting the temperature fails."""
|
||||
self.device.setpoint_heat = mock.MagicMock(
|
||||
side_effect=somecomfort.SomeComfortError)
|
||||
self.honeywell.set_temperature(123)
|
||||
|
||||
def test_attributes(self):
|
||||
"""Test the attributes."""
|
||||
expected = {
|
||||
'fan': 'running',
|
||||
'fanmode': 'auto',
|
||||
'system_mode': 'heat',
|
||||
}
|
||||
self.assertEqual(expected, self.honeywell.device_state_attributes)
|
||||
expected['fan'] = 'idle'
|
||||
self.device.fan_running = False
|
||||
self.assertEqual(expected, self.honeywell.device_state_attributes)
|
||||
|
||||
def test_with_no_fan(self):
|
||||
"""Test if there is on fan."""
|
||||
self.device.fan_running = False
|
||||
self.device.fan_mode = None
|
||||
expected = {
|
||||
'fan': 'idle',
|
||||
'fanmode': None,
|
||||
'system_mode': 'heat',
|
||||
}
|
||||
self.assertEqual(expected, self.honeywell.device_state_attributes)
|
Loading…
x
Reference in New Issue
Block a user