Hold mode (#5586)
* Initial commit of hold_mode feature. * Added deprecation warning for climate.away_mode * Add tests to demo environment.pull/5590/head
parent
1d4e967106
commit
b732174def
|
@ -32,6 +32,7 @@ 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_HOLD_MODE = "set_hold_mode"
|
||||
SERVICE_SET_OPERATION_MODE = "set_operation_mode"
|
||||
SERVICE_SET_SWING_MODE = "set_swing_mode"
|
||||
SERVICE_SET_HUMIDITY = "set_humidity"
|
||||
|
@ -56,6 +57,7 @@ ATTR_CURRENT_HUMIDITY = "current_humidity"
|
|||
ATTR_HUMIDITY = "humidity"
|
||||
ATTR_MAX_HUMIDITY = "max_humidity"
|
||||
ATTR_MIN_HUMIDITY = "min_humidity"
|
||||
ATTR_HOLD_MODE = "hold_mode"
|
||||
ATTR_OPERATION_MODE = "operation_mode"
|
||||
ATTR_OPERATION_LIST = "operation_list"
|
||||
ATTR_SWING_MODE = "swing_mode"
|
||||
|
@ -93,6 +95,10 @@ SET_FAN_MODE_SCHEMA = vol.Schema({
|
|||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_FAN_MODE): cv.string,
|
||||
})
|
||||
SET_HOLD_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_HOLD_MODE): cv.string,
|
||||
})
|
||||
SET_OPERATION_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_OPERATION_MODE): cv.string,
|
||||
|
@ -116,9 +122,23 @@ def set_away_mode(hass, away_mode, entity_id=None):
|
|||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
_LOGGER.warning(
|
||||
'This service has been deprecated; use climate.set_hold_mode')
|
||||
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
|
||||
|
||||
|
||||
def set_hold_mode(hass, hold_mode, entity_id=None):
|
||||
"""Set new hold mode."""
|
||||
data = {
|
||||
ATTR_HOLD_MODE: hold_mode
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_HOLD_MODE, data)
|
||||
|
||||
|
||||
def set_aux_heat(hass, aux_heat, entity_id=None):
|
||||
"""Turn all or specified climate devices auxillary heater on."""
|
||||
data = {
|
||||
|
@ -229,6 +249,8 @@ def async_setup(hass, config):
|
|||
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
|
||||
return
|
||||
|
||||
_LOGGER.warning(
|
||||
'This service has been deprecated; use climate.set_hold_mode')
|
||||
for climate in target_climate:
|
||||
if away_mode:
|
||||
yield from climate.async_turn_away_mode_on()
|
||||
|
@ -242,6 +264,23 @@ def async_setup(hass, config):
|
|||
descriptions.get(SERVICE_SET_AWAY_MODE),
|
||||
schema=SET_AWAY_MODE_SCHEMA)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_hold_mode_set_service(service):
|
||||
"""Set hold mode on target climate devices."""
|
||||
target_climate = component.async_extract_from_service(service)
|
||||
|
||||
hold_mode = service.data.get(ATTR_HOLD_MODE)
|
||||
|
||||
for climate in target_climate:
|
||||
yield from climate.async_set_hold_mode(hold_mode)
|
||||
|
||||
yield from _async_update_climate(target_climate)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SET_HOLD_MODE, async_hold_mode_set_service,
|
||||
descriptions.get(SERVICE_SET_HOLD_MODE),
|
||||
schema=SET_HOLD_MODE_SCHEMA)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_aux_heat_set_service(service):
|
||||
"""Set auxillary heater on target climate devices."""
|
||||
|
@ -446,6 +485,10 @@ class ClimateDevice(Entity):
|
|||
if self.operation_list:
|
||||
data[ATTR_OPERATION_LIST] = self.operation_list
|
||||
|
||||
is_hold = self.current_hold_mode
|
||||
if is_hold is not None:
|
||||
data[ATTR_HOLD_MODE] = is_hold
|
||||
|
||||
swing_mode = self.current_swing_mode
|
||||
if swing_mode is not None:
|
||||
data[ATTR_SWING_MODE] = swing_mode
|
||||
|
@ -517,6 +560,11 @@ class ClimateDevice(Entity):
|
|||
"""Return true if away mode is on."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return the current hold mode, e.g., home, away, temp."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
"""Return true if aux heater."""
|
||||
|
@ -626,6 +674,18 @@ class ClimateDevice(Entity):
|
|||
return self.hass.loop.run_in_executor(
|
||||
None, self.turn_away_mode_off)
|
||||
|
||||
def set_hold_mode(self, hold_mode):
|
||||
"""Set new target hold mode."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_set_hold_mode(self, hold_mode):
|
||||
"""Set new target hold mode.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.loop.run_in_executor(
|
||||
None, self.set_hold_mode, hold_mode)
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn auxillary heater on."""
|
||||
raise NotImplementedError()
|
||||
|
|
|
@ -12,11 +12,11 @@ from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
|
|||
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, None, None),
|
||||
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, 22, "On High",
|
||||
DemoClimate("HeatPump", 68, TEMP_FAHRENHEIT, None, None, 77,
|
||||
"Auto Low", None, None, "Auto", "heat", None, None, None),
|
||||
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, None, 22, "On High",
|
||||
67, 54, "Off", "cool", False, None, None),
|
||||
DemoClimate("Ecobee", None, TEMP_CELSIUS, None, 23, "Auto Low",
|
||||
DemoClimate("Ecobee", None, TEMP_CELSIUS, None, None, 23, "Auto Low",
|
||||
None, None, "Auto", "auto", None, 24, 21)
|
||||
])
|
||||
|
||||
|
@ -25,7 +25,7 @@ class DemoClimate(ClimateDevice):
|
|||
"""Representation of a demo climate device."""
|
||||
|
||||
def __init__(self, name, target_temperature, unit_of_measurement,
|
||||
away, current_temperature, current_fan_mode,
|
||||
away, hold, current_temperature, current_fan_mode,
|
||||
target_humidity, current_humidity, current_swing_mode,
|
||||
current_operation, aux, target_temp_high, target_temp_low):
|
||||
"""Initialize the climate device."""
|
||||
|
@ -34,6 +34,7 @@ class DemoClimate(ClimateDevice):
|
|||
self._target_humidity = target_humidity
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._away = away
|
||||
self._hold = hold
|
||||
self._current_temperature = current_temperature
|
||||
self._current_humidity = current_humidity
|
||||
self._current_fan_mode = current_fan_mode
|
||||
|
@ -106,6 +107,11 @@ class DemoClimate(ClimateDevice):
|
|||
"""Return if away mode is on."""
|
||||
return self._away
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return hold mode setting."""
|
||||
return self._hold
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
|
@ -171,6 +177,11 @@ class DemoClimate(ClimateDevice):
|
|||
self._away = False
|
||||
self.update_ha_state()
|
||||
|
||||
def set_hold_mode(self, hold):
|
||||
"""Update hold mode on."""
|
||||
self._hold = hold
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn away auxillary heater on."""
|
||||
self._aux = True
|
||||
|
|
|
@ -183,6 +183,19 @@ class Thermostat(ClimateDevice):
|
|||
else:
|
||||
return STATE_OFF
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return current hold mode."""
|
||||
if self.is_away_mode_on:
|
||||
hold = 'away'
|
||||
elif self.is_home_mode_on:
|
||||
hold = 'home'
|
||||
elif self.is_temp_hold_on():
|
||||
hold = 'temp'
|
||||
else:
|
||||
hold = None
|
||||
return hold
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation."""
|
||||
|
@ -236,30 +249,94 @@ class Thermostat(ClimateDevice):
|
|||
"fan_min_on_time": self.fan_min_on_time
|
||||
}
|
||||
|
||||
def is_vacation_on(self):
|
||||
"""Return true if vacation mode is on."""
|
||||
events = self.thermostat['events']
|
||||
return any(event['type'] == 'vacation' and event['running']
|
||||
for event in events)
|
||||
|
||||
def is_temp_hold_on(self):
|
||||
"""Return true if temperature hold is on."""
|
||||
events = self.thermostat['events']
|
||||
return any(event['type'] == 'hold' and event['running']
|
||||
for event in events)
|
||||
|
||||
@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['holdClimateRef'] == 'away' or \
|
||||
event['type'] == 'autoAway':
|
||||
mode = "away"
|
||||
break
|
||||
return 'away' in mode
|
||||
return any(event['holdClimateRef'] == 'away' or
|
||||
event['type'] == 'autoAway'
|
||||
for event in events)
|
||||
|
||||
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")
|
||||
self.data.ecobee.set_climate_hold(self.thermostat_index,
|
||||
"away", self.hold_preference())
|
||||
self.update_without_throttle = True
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self.data.ecobee.resume_program(self.thermostat_index)
|
||||
self.set_hold_mode(None)
|
||||
|
||||
@property
|
||||
def is_home_mode_on(self):
|
||||
"""Return true if home mode is on."""
|
||||
events = self.thermostat['events']
|
||||
return any(event['holdClimateRef'] == 'home' or
|
||||
event['type'] == 'autoHome'
|
||||
for event in events)
|
||||
|
||||
def turn_home_mode_on(self):
|
||||
"""Turn home on."""
|
||||
self.data.ecobee.set_climate_hold(self.thermostat_index,
|
||||
"home", self.hold_preference())
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_hold_mode(self, hold_mode):
|
||||
"""Set hold mode (away, home, temp)."""
|
||||
hold = self.current_hold_mode
|
||||
|
||||
if hold == hold_mode:
|
||||
return
|
||||
elif hold_mode == 'away':
|
||||
self.turn_away_mode_on()
|
||||
elif hold_mode == 'home':
|
||||
self.turn_home_mode_on()
|
||||
elif hold_mode == 'temp':
|
||||
self.set_temp_hold(int(self.current_temperature))
|
||||
else:
|
||||
self.data.ecobee.resume_program(self.thermostat_index)
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_auto_temp_hold(self, heat_temp, cool_temp):
|
||||
"""Set temperature hold in auto mode."""
|
||||
self.data.ecobee.set_hold_temp(self.thermostat_index, cool_temp,
|
||||
heat_temp, self.hold_preference())
|
||||
_LOGGER.debug("Setting ecobee hold_temp to: heat=%s, is=%s, "
|
||||
"cool=%s, is=%s", heat_temp, isinstance(
|
||||
heat_temp, (int, float)), cool_temp,
|
||||
isinstance(cool_temp, (int, float)))
|
||||
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_temp_hold(self, temp):
|
||||
"""Set temperature hold in modes other than auto."""
|
||||
# Set arbitrary range when not in auto mode
|
||||
if self.current_operation == STATE_HEAT:
|
||||
heat_temp = temp
|
||||
cool_temp = temp + 20
|
||||
elif self.current_operation == STATE_COOL:
|
||||
heat_temp = temp - 20
|
||||
cool_temp = temp
|
||||
|
||||
self.data.ecobee.set_hold_temp(self.thermostat_index, cool_temp,
|
||||
heat_temp, self.hold_preference())
|
||||
_LOGGER.debug("Setting ecobee hold_temp to: low=%s, is=%s, "
|
||||
"cool=%s, is=%s", heat_temp, isinstance(
|
||||
heat_temp, (int, float)), cool_temp,
|
||||
isinstance(cool_temp, (int, float)))
|
||||
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
|
@ -268,33 +345,14 @@ class Thermostat(ClimateDevice):
|
|||
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
||||
if self.current_operation == STATE_HEAT and temp is not None:
|
||||
low_temp = temp
|
||||
high_temp = temp + 20
|
||||
elif self.current_operation == STATE_COOL and temp is not None:
|
||||
low_temp = temp - 20
|
||||
high_temp = temp
|
||||
if low_temp is None and high_temp is None:
|
||||
if self.current_operation == STATE_AUTO and low_temp is not None \
|
||||
and high_temp is not None:
|
||||
self.set_auto_temp_hold(int(low_temp), int(high_temp))
|
||||
elif temp is not None:
|
||||
self.set_temp_hold(int(temp))
|
||||
else:
|
||||
_LOGGER.error(
|
||||
'Missing valid arguments for set_temperature in %s', kwargs)
|
||||
return
|
||||
|
||||
low_temp = int(low_temp)
|
||||
high_temp = int(high_temp)
|
||||
|
||||
if self.hold_temp:
|
||||
self.data.ecobee.set_hold_temp(
|
||||
self.thermostat_index, high_temp, low_temp, "indefinite")
|
||||
else:
|
||||
self.data.ecobee.set_hold_temp(
|
||||
self.thermostat_index, high_temp, low_temp)
|
||||
|
||||
_LOGGER.debug("Setting ecobee hold_temp to: low=%s, is=%s, "
|
||||
"high=%s, is=%s", low_temp, isinstance(
|
||||
low_temp, (int, float)), high_temp,
|
||||
isinstance(high_temp, (int, float)))
|
||||
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
|
||||
|
@ -313,15 +371,19 @@ class Thermostat(ClimateDevice):
|
|||
str(resume_all).lower())
|
||||
self.update_without_throttle = True
|
||||
|
||||
# Home and Sleep mode aren't used in UI yet:
|
||||
def hold_preference(self):
|
||||
"""Return user preference setting for hold time."""
|
||||
# Values returned from thermostat are 'useEndTime4hour',
|
||||
# 'useEndTime2hour', 'nextTransition', 'indefinite', 'askMe'
|
||||
default = self.thermostat['settings']['holdAction']
|
||||
if default == 'nextTransition':
|
||||
return default
|
||||
elif default == 'indefinite':
|
||||
return default
|
||||
else:
|
||||
return 'nextTransition'
|
||||
|
||||
# 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)
|
||||
# Sleep mode isn't used in UI yet:
|
||||
|
||||
# def turn_sleep_mode_on(self):
|
||||
# """ Turns sleep mode on. """
|
||||
|
|
|
@ -22,6 +22,18 @@ set_away_mode:
|
|||
description: New value of away mode
|
||||
example: true
|
||||
|
||||
set_hold_mode:
|
||||
description: Turn hold mode for climate device
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change
|
||||
example: 'climate.kitchen'
|
||||
|
||||
hold_mode:
|
||||
description: New value of hold mode
|
||||
example: 'away'
|
||||
|
||||
set_temperature:
|
||||
description: Set target temperature of climate device
|
||||
|
||||
|
|
|
@ -16,11 +16,11 @@ from homeassistant.components.sun import (
|
|||
from homeassistant.components.switch.mysensors import (
|
||||
ATTR_IR_CODE, SERVICE_SEND_IR_CODE)
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_FAN_MODE, ATTR_HUMIDITY,
|
||||
ATTR_OPERATION_MODE, ATTR_SWING_MODE,
|
||||
SERVICE_SET_AUX_HEAT, SERVICE_SET_AWAY_MODE, SERVICE_SET_FAN_MODE,
|
||||
SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE, SERVICE_SET_SWING_MODE,
|
||||
SERVICE_SET_TEMPERATURE)
|
||||
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_FAN_MODE, ATTR_HOLD_MODE,
|
||||
ATTR_HUMIDITY, ATTR_OPERATION_MODE, ATTR_SWING_MODE,
|
||||
SERVICE_SET_AUX_HEAT, SERVICE_SET_AWAY_MODE, SERVICE_SET_HOLD_MODE,
|
||||
SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE,
|
||||
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE)
|
||||
from homeassistant.components.climate.ecobee import (
|
||||
ATTR_FAN_MIN_ON_TIME, SERVICE_SET_FAN_MIN_ON_TIME,
|
||||
ATTR_RESUME_ALL, SERVICE_RESUME_PROGRAM)
|
||||
|
@ -57,6 +57,7 @@ SERVICE_ATTRIBUTES = {
|
|||
SERVICE_SET_TEMPERATURE: [ATTR_TEMPERATURE],
|
||||
SERVICE_SET_HUMIDITY: [ATTR_HUMIDITY],
|
||||
SERVICE_SET_SWING_MODE: [ATTR_SWING_MODE],
|
||||
SERVICE_SET_HOLD_MODE: [ATTR_HOLD_MODE],
|
||||
SERVICE_SET_OPERATION_MODE: [ATTR_OPERATION_MODE],
|
||||
SERVICE_SET_AUX_HEAT: [ATTR_AUX_HEAT],
|
||||
SERVICE_SELECT_SOURCE: [ATTR_INPUT_SOURCE],
|
||||
|
|
|
@ -208,6 +208,27 @@ class TestDemoClimate(unittest.TestCase):
|
|||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
self.assertEqual('off', state.attributes.get('away_mode'))
|
||||
|
||||
def test_set_hold_mode_home(self):
|
||||
"""Test setting the hold mode home."""
|
||||
climate.set_hold_mode(self.hass, 'home', ENTITY_ECOBEE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
self.assertEqual('home', state.attributes.get('hold_mode'))
|
||||
|
||||
def test_set_hold_mode_away(self):
|
||||
"""Test setting the hold mode away."""
|
||||
climate.set_hold_mode(self.hass, 'away', ENTITY_ECOBEE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
self.assertEqual('away', state.attributes.get('hold_mode'))
|
||||
|
||||
def test_set_hold_mode_none(self):
|
||||
"""Test setting the hold mode off/false."""
|
||||
climate.set_hold_mode(self.hass, None, ENTITY_ECOBEE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
self.assertEqual(None, state.attributes.get('hold_mode'))
|
||||
|
||||
def test_set_aux_heat_bad_attr(self):
|
||||
"""Test setting the auxillary heater without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
|
|
Loading…
Reference in New Issue