Skip to content

Commit b732174

Browse files
Duoxilianballoob
authored andcommitted
* Initial commit of hold_mode feature. * Added deprecation warning for climate.away_mode * Add tests to demo environment.
1 parent 1d4e967 commit b732174

File tree

6 files changed

+223
-56
lines changed

6 files changed

+223
-56
lines changed

homeassistant/components/climate/__init__.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
SERVICE_SET_AUX_HEAT = "set_aux_heat"
3333
SERVICE_SET_TEMPERATURE = "set_temperature"
3434
SERVICE_SET_FAN_MODE = "set_fan_mode"
35+
SERVICE_SET_HOLD_MODE = "set_hold_mode"
3536
SERVICE_SET_OPERATION_MODE = "set_operation_mode"
3637
SERVICE_SET_SWING_MODE = "set_swing_mode"
3738
SERVICE_SET_HUMIDITY = "set_humidity"
@@ -56,6 +57,7 @@
5657
ATTR_HUMIDITY = "humidity"
5758
ATTR_MAX_HUMIDITY = "max_humidity"
5859
ATTR_MIN_HUMIDITY = "min_humidity"
60+
ATTR_HOLD_MODE = "hold_mode"
5961
ATTR_OPERATION_MODE = "operation_mode"
6062
ATTR_OPERATION_LIST = "operation_list"
6163
ATTR_SWING_MODE = "swing_mode"
@@ -93,6 +95,10 @@
9395
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
9496
vol.Required(ATTR_FAN_MODE): cv.string,
9597
})
98+
SET_HOLD_MODE_SCHEMA = vol.Schema({
99+
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
100+
vol.Required(ATTR_HOLD_MODE): cv.string,
101+
})
96102
SET_OPERATION_MODE_SCHEMA = vol.Schema({
97103
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
98104
vol.Required(ATTR_OPERATION_MODE): cv.string,
@@ -116,9 +122,23 @@ def set_away_mode(hass, away_mode, entity_id=None):
116122
if entity_id:
117123
data[ATTR_ENTITY_ID] = entity_id
118124

125+
_LOGGER.warning(
126+
'This service has been deprecated; use climate.set_hold_mode')
119127
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
120128

121129

130+
def set_hold_mode(hass, hold_mode, entity_id=None):
131+
"""Set new hold mode."""
132+
data = {
133+
ATTR_HOLD_MODE: hold_mode
134+
}
135+
136+
if entity_id:
137+
data[ATTR_ENTITY_ID] = entity_id
138+
139+
hass.services.call(DOMAIN, SERVICE_SET_HOLD_MODE, data)
140+
141+
122142
def set_aux_heat(hass, aux_heat, entity_id=None):
123143
"""Turn all or specified climate devices auxillary heater on."""
124144
data = {
@@ -229,6 +249,8 @@ def async_away_mode_set_service(service):
229249
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
230250
return
231251

252+
_LOGGER.warning(
253+
'This service has been deprecated; use climate.set_hold_mode')
232254
for climate in target_climate:
233255
if away_mode:
234256
yield from climate.async_turn_away_mode_on()
@@ -242,6 +264,23 @@ def async_away_mode_set_service(service):
242264
descriptions.get(SERVICE_SET_AWAY_MODE),
243265
schema=SET_AWAY_MODE_SCHEMA)
244266

267+
@asyncio.coroutine
268+
def async_hold_mode_set_service(service):
269+
"""Set hold mode on target climate devices."""
270+
target_climate = component.async_extract_from_service(service)
271+
272+
hold_mode = service.data.get(ATTR_HOLD_MODE)
273+
274+
for climate in target_climate:
275+
yield from climate.async_set_hold_mode(hold_mode)
276+
277+
yield from _async_update_climate(target_climate)
278+
279+
hass.services.async_register(
280+
DOMAIN, SERVICE_SET_HOLD_MODE, async_hold_mode_set_service,
281+
descriptions.get(SERVICE_SET_HOLD_MODE),
282+
schema=SET_HOLD_MODE_SCHEMA)
283+
245284
@asyncio.coroutine
246285
def async_aux_heat_set_service(service):
247286
"""Set auxillary heater on target climate devices."""
@@ -446,6 +485,10 @@ def state_attributes(self):
446485
if self.operation_list:
447486
data[ATTR_OPERATION_LIST] = self.operation_list
448487

488+
is_hold = self.current_hold_mode
489+
if is_hold is not None:
490+
data[ATTR_HOLD_MODE] = is_hold
491+
449492
swing_mode = self.current_swing_mode
450493
if swing_mode is not None:
451494
data[ATTR_SWING_MODE] = swing_mode
@@ -517,6 +560,11 @@ def is_away_mode_on(self):
517560
"""Return true if away mode is on."""
518561
return None
519562

563+
@property
564+
def current_hold_mode(self):
565+
"""Return the current hold mode, e.g., home, away, temp."""
566+
return None
567+
520568
@property
521569
def is_aux_heat_on(self):
522570
"""Return true if aux heater."""
@@ -626,6 +674,18 @@ def async_turn_away_mode_off(self):
626674
return self.hass.loop.run_in_executor(
627675
None, self.turn_away_mode_off)
628676

677+
def set_hold_mode(self, hold_mode):
678+
"""Set new target hold mode."""
679+
raise NotImplementedError()
680+
681+
def async_set_hold_mode(self, hold_mode):
682+
"""Set new target hold mode.
683+
684+
This method must be run in the event loop and returns a coroutine.
685+
"""
686+
return self.hass.loop.run_in_executor(
687+
None, self.set_hold_mode, hold_mode)
688+
629689
def turn_aux_heat_on(self):
630690
"""Turn auxillary heater on."""
631691
raise NotImplementedError()

homeassistant/components/climate/demo.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
def setup_platform(hass, config, add_devices, discovery_info=None):
1313
"""Setup the Demo climate devices."""
1414
add_devices([
15-
DemoClimate("HeatPump", 68, TEMP_FAHRENHEIT, None, 77, "Auto Low",
16-
None, None, "Auto", "heat", None, None, None),
17-
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, 22, "On High",
15+
DemoClimate("HeatPump", 68, TEMP_FAHRENHEIT, None, None, 77,
16+
"Auto Low", None, None, "Auto", "heat", None, None, None),
17+
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, None, 22, "On High",
1818
67, 54, "Off", "cool", False, None, None),
19-
DemoClimate("Ecobee", None, TEMP_CELSIUS, None, 23, "Auto Low",
19+
DemoClimate("Ecobee", None, TEMP_CELSIUS, None, None, 23, "Auto Low",
2020
None, None, "Auto", "auto", None, 24, 21)
2121
])
2222

@@ -25,7 +25,7 @@ class DemoClimate(ClimateDevice):
2525
"""Representation of a demo climate device."""
2626

2727
def __init__(self, name, target_temperature, unit_of_measurement,
28-
away, current_temperature, current_fan_mode,
28+
away, hold, current_temperature, current_fan_mode,
2929
target_humidity, current_humidity, current_swing_mode,
3030
current_operation, aux, target_temp_high, target_temp_low):
3131
"""Initialize the climate device."""
@@ -34,6 +34,7 @@ def __init__(self, name, target_temperature, unit_of_measurement,
3434
self._target_humidity = target_humidity
3535
self._unit_of_measurement = unit_of_measurement
3636
self._away = away
37+
self._hold = hold
3738
self._current_temperature = current_temperature
3839
self._current_humidity = current_humidity
3940
self._current_fan_mode = current_fan_mode
@@ -106,6 +107,11 @@ def is_away_mode_on(self):
106107
"""Return if away mode is on."""
107108
return self._away
108109

110+
@property
111+
def current_hold_mode(self):
112+
"""Return hold mode setting."""
113+
return self._hold
114+
109115
@property
110116
def is_aux_heat_on(self):
111117
"""Return true if away mode is on."""
@@ -171,6 +177,11 @@ def turn_away_mode_off(self):
171177
self._away = False
172178
self.update_ha_state()
173179

180+
def set_hold_mode(self, hold):
181+
"""Update hold mode on."""
182+
self._hold = hold
183+
self.update_ha_state()
184+
174185
def turn_aux_heat_on(self):
175186
"""Turn away auxillary heater on."""
176187
self._aux = True

homeassistant/components/climate/ecobee.py

Lines changed: 108 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,19 @@ def fan(self):
183183
else:
184184
return STATE_OFF
185185

186+
@property
187+
def current_hold_mode(self):
188+
"""Return current hold mode."""
189+
if self.is_away_mode_on:
190+
hold = 'away'
191+
elif self.is_home_mode_on:
192+
hold = 'home'
193+
elif self.is_temp_hold_on():
194+
hold = 'temp'
195+
else:
196+
hold = None
197+
return hold
198+
186199
@property
187200
def current_operation(self):
188201
"""Return current operation."""
@@ -236,66 +249,111 @@ def device_state_attributes(self):
236249
"fan_min_on_time": self.fan_min_on_time
237250
}
238251

252+
def is_vacation_on(self):
253+
"""Return true if vacation mode is on."""
254+
events = self.thermostat['events']
255+
return any(event['type'] == 'vacation' and event['running']
256+
for event in events)
257+
258+
def is_temp_hold_on(self):
259+
"""Return true if temperature hold is on."""
260+
events = self.thermostat['events']
261+
return any(event['type'] == 'hold' and event['running']
262+
for event in events)
263+
239264
@property
240265
def is_away_mode_on(self):
241266
"""Return true if away mode is on."""
242-
mode = self.mode
243267
events = self.thermostat['events']
244-
for event in events:
245-
if event['holdClimateRef'] == 'away' or \
246-
event['type'] == 'autoAway':
247-
mode = "away"
248-
break
249-
return 'away' in mode
268+
return any(event['holdClimateRef'] == 'away' or
269+
event['type'] == 'autoAway'
270+
for event in events)
250271

251272
def turn_away_mode_on(self):
252273
"""Turn away on."""
253-
if self.hold_temp:
254-
self.data.ecobee.set_climate_hold(self.thermostat_index,
255-
"away", "indefinite")
256-
else:
257-
self.data.ecobee.set_climate_hold(self.thermostat_index, "away")
274+
self.data.ecobee.set_climate_hold(self.thermostat_index,
275+
"away", self.hold_preference())
258276
self.update_without_throttle = True
259277

260278
def turn_away_mode_off(self):
261279
"""Turn away off."""
262-
self.data.ecobee.resume_program(self.thermostat_index)
280+
self.set_hold_mode(None)
281+
282+
@property
283+
def is_home_mode_on(self):
284+
"""Return true if home mode is on."""
285+
events = self.thermostat['events']
286+
return any(event['holdClimateRef'] == 'home' or
287+
event['type'] == 'autoHome'
288+
for event in events)
289+
290+
def turn_home_mode_on(self):
291+
"""Turn home on."""
292+
self.data.ecobee.set_climate_hold(self.thermostat_index,
293+
"home", self.hold_preference())
263294
self.update_without_throttle = True
264295

265-
def set_temperature(self, **kwargs):
266-
"""Set new target temperature."""
267-
low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
268-
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
269-
temp = kwargs.get(ATTR_TEMPERATURE)
296+
def set_hold_mode(self, hold_mode):
297+
"""Set hold mode (away, home, temp)."""
298+
hold = self.current_hold_mode
270299

271-
if self.current_operation == STATE_HEAT and temp is not None:
272-
low_temp = temp
273-
high_temp = temp + 20
274-
elif self.current_operation == STATE_COOL and temp is not None:
275-
low_temp = temp - 20
276-
high_temp = temp
277-
if low_temp is None and high_temp is None:
278-
_LOGGER.error(
279-
'Missing valid arguments for set_temperature in %s', kwargs)
300+
if hold == hold_mode:
280301
return
302+
elif hold_mode == 'away':
303+
self.turn_away_mode_on()
304+
elif hold_mode == 'home':
305+
self.turn_home_mode_on()
306+
elif hold_mode == 'temp':
307+
self.set_temp_hold(int(self.current_temperature))
308+
else:
309+
self.data.ecobee.resume_program(self.thermostat_index)
310+
self.update_without_throttle = True
311+
312+
def set_auto_temp_hold(self, heat_temp, cool_temp):
313+
"""Set temperature hold in auto mode."""
314+
self.data.ecobee.set_hold_temp(self.thermostat_index, cool_temp,
315+
heat_temp, self.hold_preference())
316+
_LOGGER.debug("Setting ecobee hold_temp to: heat=%s, is=%s, "
317+
"cool=%s, is=%s", heat_temp, isinstance(
318+
heat_temp, (int, float)), cool_temp,
319+
isinstance(cool_temp, (int, float)))
281320

282-
low_temp = int(low_temp)
283-
high_temp = int(high_temp)
321+
self.update_without_throttle = True
284322

285-
if self.hold_temp:
286-
self.data.ecobee.set_hold_temp(
287-
self.thermostat_index, high_temp, low_temp, "indefinite")
288-
else:
289-
self.data.ecobee.set_hold_temp(
290-
self.thermostat_index, high_temp, low_temp)
323+
def set_temp_hold(self, temp):
324+
"""Set temperature hold in modes other than auto."""
325+
# Set arbitrary range when not in auto mode
326+
if self.current_operation == STATE_HEAT:
327+
heat_temp = temp
328+
cool_temp = temp + 20
329+
elif self.current_operation == STATE_COOL:
330+
heat_temp = temp - 20
331+
cool_temp = temp
291332

333+
self.data.ecobee.set_hold_temp(self.thermostat_index, cool_temp,
334+
heat_temp, self.hold_preference())
292335
_LOGGER.debug("Setting ecobee hold_temp to: low=%s, is=%s, "
293-
"high=%s, is=%s", low_temp, isinstance(
294-
low_temp, (int, float)), high_temp,
295-
isinstance(high_temp, (int, float)))
336+
"cool=%s, is=%s", heat_temp, isinstance(
337+
heat_temp, (int, float)), cool_temp,
338+
isinstance(cool_temp, (int, float)))
296339

297340
self.update_without_throttle = True
298341

342+
def set_temperature(self, **kwargs):
343+
"""Set new target temperature."""
344+
low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
345+
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
346+
temp = kwargs.get(ATTR_TEMPERATURE)
347+
348+
if self.current_operation == STATE_AUTO and low_temp is not None \
349+
and high_temp is not None:
350+
self.set_auto_temp_hold(int(low_temp), int(high_temp))
351+
elif temp is not None:
352+
self.set_temp_hold(int(temp))
353+
else:
354+
_LOGGER.error(
355+
'Missing valid arguments for set_temperature in %s', kwargs)
356+
299357
def set_operation_mode(self, operation_mode):
300358
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
301359
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
@@ -313,15 +371,19 @@ def resume_program(self, resume_all):
313371
str(resume_all).lower())
314372
self.update_without_throttle = True
315373

316-
# Home and Sleep mode aren't used in UI yet:
317-
318-
# def turn_home_mode_on(self):
319-
# """ Turns home mode on. """
320-
# self.data.ecobee.set_climate_hold(self.thermostat_index, "home")
374+
def hold_preference(self):
375+
"""Return user preference setting for hold time."""
376+
# Values returned from thermostat are 'useEndTime4hour',
377+
# 'useEndTime2hour', 'nextTransition', 'indefinite', 'askMe'
378+
default = self.thermostat['settings']['holdAction']
379+
if default == 'nextTransition':
380+
return default
381+
elif default == 'indefinite':
382+
return default
383+
else:
384+
return 'nextTransition'
321385

322-
# def turn_home_mode_off(self):
323-
# """ Turns home mode off. """
324-
# self.data.ecobee.resume_program(self.thermostat_index)
386+
# Sleep mode isn't used in UI yet:
325387

326388
# def turn_sleep_mode_on(self):
327389
# """ Turns sleep mode on. """

homeassistant/components/climate/services.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ set_away_mode:
2222
description: New value of away mode
2323
example: true
2424

25+
set_hold_mode:
26+
description: Turn hold mode for climate device
27+
28+
fields:
29+
entity_id:
30+
description: Name(s) of entities to change
31+
example: 'climate.kitchen'
32+
33+
hold_mode:
34+
description: New value of hold mode
35+
example: 'away'
36+
2537
set_temperature:
2638
description: Set target temperature of climate device
2739

0 commit comments

Comments
 (0)