Add ecobee fan mode (#12732)
* add ability to set fan on * add tests and change "not on" status to "auto" * hound fix * more hounds * I don't understand new lines * fix linting errors * more linting fixes * change method signature * lint fixes * hopefully last lint fix * correct temp ranges according to ecobee API docs * update dependency to latest version * update tests with values from new temp logic * fix linting issue * more linting fixes * add SUPPORT_FAN_MODE to capabilities * add fan_list to attributes. restore current fan state to OFF if fan is not running. change target high/low temps from null to target temp when not in auto mode. change target temp from null to high/low temp when in auto mode change mode attribute to climate_mode for consistency with other lists. * remove unused import * simplify logic * lint fixes * revert change for target temps
This commit is contained in:
parent
022d8fb816
commit
1dcc51cbdf
4 changed files with 88 additions and 44 deletions
|
@ -14,10 +14,10 @@ from homeassistant.components.climate import (
|
|||
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW, STATE_OFF)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
|
||||
ATTR_ENTITY_ID, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_CONFIGURING = {}
|
||||
|
@ -50,7 +50,7 @@ SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
|
|||
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
|
||||
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
@ -122,6 +122,7 @@ class Thermostat(ClimateDevice):
|
|||
self._climate_list = self.climate_list
|
||||
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
|
||||
'heat', 'off']
|
||||
self._fan_list = ['auto', 'on']
|
||||
self.update_without_throttle = False
|
||||
|
||||
def update(self):
|
||||
|
@ -180,24 +181,29 @@ class Thermostat(ClimateDevice):
|
|||
return self.thermostat['runtime']['desiredCool'] / 10.0
|
||||
return None
|
||||
|
||||
@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."""
|
||||
"""Return the current fan status."""
|
||||
if 'fan' in self.thermostat['equipmentStatus']:
|
||||
return STATE_ON
|
||||
return STATE_OFF
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self.thermostat['runtime']['desiredFanMode']
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return current hold mode."""
|
||||
mode = self._current_hold_mode
|
||||
return None if mode == AWAY_MODE else mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
"""Return the available fan modes."""
|
||||
return self._fan_list
|
||||
|
||||
@property
|
||||
def _current_hold_mode(self):
|
||||
events = self.thermostat['events']
|
||||
|
@ -206,7 +212,7 @@ class Thermostat(ClimateDevice):
|
|||
if event['type'] == 'hold':
|
||||
if event['holdClimateRef'] == 'away':
|
||||
if int(event['endDate'][0:4]) - \
|
||||
int(event['startDate'][0:4]) <= 1:
|
||||
int(event['startDate'][0:4]) <= 1:
|
||||
# A temporary hold from away climate is a hold
|
||||
return 'away'
|
||||
# A permanent hold from away climate
|
||||
|
@ -228,7 +234,7 @@ class Thermostat(ClimateDevice):
|
|||
def current_operation(self):
|
||||
"""Return current operation."""
|
||||
if self.operation_mode == 'auxHeatOnly' or \
|
||||
self.operation_mode == 'heatPump':
|
||||
self.operation_mode == 'heatPump':
|
||||
return STATE_HEAT
|
||||
return self.operation_mode
|
||||
|
||||
|
@ -271,10 +277,11 @@ class Thermostat(ClimateDevice):
|
|||
operation = STATE_HEAT
|
||||
else:
|
||||
operation = status
|
||||
|
||||
return {
|
||||
"actual_humidity": self.thermostat['runtime']['actualHumidity'],
|
||||
"fan": self.fan,
|
||||
"mode": self.mode,
|
||||
"climate_mode": self.mode,
|
||||
"operation": operation,
|
||||
"climate_list": self.climate_list,
|
||||
"fan_min_on_time": self.fan_min_on_time
|
||||
|
@ -342,25 +349,46 @@ class Thermostat(ClimateDevice):
|
|||
cool_temp_setpoint, heat_temp_setpoint,
|
||||
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,
|
||||
"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_fan_mode(self, fan_mode):
|
||||
"""Set the fan mode. Valid values are "on" or "auto"."""
|
||||
if (fan_mode.lower() != STATE_ON) and (fan_mode.lower() != STATE_AUTO):
|
||||
error = "Invalid fan_mode value: Valid values are 'on' or 'auto'"
|
||||
_LOGGER.error(error)
|
||||
return
|
||||
|
||||
cool_temp = self.thermostat['runtime']['desiredCool'] / 10.0
|
||||
heat_temp = self.thermostat['runtime']['desiredHeat'] / 10.0
|
||||
self.data.ecobee.set_fan_mode(self.thermostat_index, fan_mode,
|
||||
cool_temp, heat_temp,
|
||||
self.hold_preference())
|
||||
|
||||
_LOGGER.info("Setting fan mode to: %s", fan_mode)
|
||||
|
||||
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:
|
||||
"""Set temperature hold in modes other than auto.
|
||||
|
||||
Ecobee API: It is good practice to set the heat and cool hold
|
||||
temperatures to be the same, if the thermostat is in either heat, cool,
|
||||
auxHeatOnly, or off mode. If the thermostat is in auto mode, an
|
||||
additional rule is required. The cool hold temperature must be greater
|
||||
than the heat hold temperature by at least the amount in the
|
||||
heatCoolMinDelta property.
|
||||
https://www.ecobee.com/home/developer/api/examples/ex5.shtml
|
||||
"""
|
||||
if self.current_operation == STATE_HEAT or self.current_operation == \
|
||||
STATE_COOL:
|
||||
heat_temp = temp
|
||||
cool_temp = temp + 20
|
||||
elif self.current_operation == STATE_COOL:
|
||||
heat_temp = temp - 20
|
||||
cool_temp = temp
|
||||
else:
|
||||
# In auto mode set temperature between
|
||||
heat_temp = temp - 10
|
||||
cool_temp = temp + 10
|
||||
delta = self.thermostat['settings']['heatCoolMinDelta'] / 10
|
||||
heat_temp = temp - delta
|
||||
cool_temp = temp + delta
|
||||
self.set_auto_temp_hold(heat_temp, cool_temp)
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
|
@ -369,8 +397,8 @@ class Thermostat(ClimateDevice):
|
|||
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
||||
if self.current_operation == STATE_AUTO and (low_temp is not None or
|
||||
high_temp is not None):
|
||||
if self.current_operation == STATE_AUTO and \
|
||||
(low_temp is not None or high_temp is not None):
|
||||
self.set_auto_temp_hold(low_temp, high_temp)
|
||||
elif temp is not None:
|
||||
self.set_temp_hold(temp)
|
||||
|
|
|
@ -16,7 +16,7 @@ from homeassistant.const import CONF_API_KEY
|
|||
from homeassistant.util import Throttle
|
||||
from homeassistant.util.json import save_json
|
||||
|
||||
REQUIREMENTS = ['python-ecobee-api==0.0.15']
|
||||
REQUIREMENTS = ['python-ecobee-api==0.0.17']
|
||||
|
||||
_CONFIGURING = {}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
|
|
@ -911,7 +911,7 @@ python-clementine-remote==1.0.1
|
|||
python-digitalocean==1.13.2
|
||||
|
||||
# homeassistant.components.ecobee
|
||||
python-ecobee-api==0.0.15
|
||||
python-ecobee-api==0.0.17
|
||||
|
||||
# homeassistant.components.climate.eq3btsmart
|
||||
# python-eq3bt==0.1.9
|
||||
|
|
|
@ -3,6 +3,7 @@ import unittest
|
|||
from unittest import mock
|
||||
import homeassistant.const as const
|
||||
import homeassistant.components.climate.ecobee as ecobee
|
||||
from homeassistant.components.climate import STATE_OFF
|
||||
|
||||
|
||||
class TestEcobee(unittest.TestCase):
|
||||
|
@ -23,6 +24,7 @@ class TestEcobee(unittest.TestCase):
|
|||
'desiredFanMode': 'on'},
|
||||
'settings': {'hvacMode': 'auto',
|
||||
'fanMinOnTime': 10,
|
||||
'heatCoolMinDelta': 50,
|
||||
'holdAction': 'nextTransition'},
|
||||
'equipmentStatus': 'fan',
|
||||
'events': [{'name': 'Event1',
|
||||
|
@ -81,17 +83,17 @@ class TestEcobee(unittest.TestCase):
|
|||
|
||||
def test_desired_fan_mode(self):
|
||||
"""Test desired fan mode property."""
|
||||
self.assertEqual('on', self.thermostat.desired_fan_mode)
|
||||
self.assertEqual('on', self.thermostat.current_fan_mode)
|
||||
self.ecobee['runtime']['desiredFanMode'] = 'auto'
|
||||
self.assertEqual('auto', self.thermostat.desired_fan_mode)
|
||||
self.assertEqual('auto', self.thermostat.current_fan_mode)
|
||||
|
||||
def test_fan(self):
|
||||
"""Test fan property."""
|
||||
self.assertEqual(const.STATE_ON, self.thermostat.fan)
|
||||
self.ecobee['equipmentStatus'] = ''
|
||||
self.assertEqual(const.STATE_OFF, self.thermostat.fan)
|
||||
self.assertEqual(STATE_OFF, self.thermostat.fan)
|
||||
self.ecobee['equipmentStatus'] = 'heatPump, heatPump2'
|
||||
self.assertEqual(const.STATE_OFF, self.thermostat.fan)
|
||||
self.assertEqual(STATE_OFF, self.thermostat.fan)
|
||||
|
||||
def test_current_hold_mode_away_temporary(self):
|
||||
"""Test current hold mode when away."""
|
||||
|
@ -180,7 +182,7 @@ class TestEcobee(unittest.TestCase):
|
|||
'climate_list': ['Climate1', 'Climate2'],
|
||||
'fan': 'off',
|
||||
'fan_min_on_time': 10,
|
||||
'mode': 'Climate1',
|
||||
'climate_mode': 'Climate1',
|
||||
'operation': 'heat'},
|
||||
self.thermostat.device_state_attributes)
|
||||
|
||||
|
@ -189,7 +191,7 @@ class TestEcobee(unittest.TestCase):
|
|||
'climate_list': ['Climate1', 'Climate2'],
|
||||
'fan': 'off',
|
||||
'fan_min_on_time': 10,
|
||||
'mode': 'Climate1',
|
||||
'climate_mode': 'Climate1',
|
||||
'operation': 'heat'},
|
||||
self.thermostat.device_state_attributes)
|
||||
self.ecobee['equipmentStatus'] = 'compCool1'
|
||||
|
@ -197,7 +199,7 @@ class TestEcobee(unittest.TestCase):
|
|||
'climate_list': ['Climate1', 'Climate2'],
|
||||
'fan': 'off',
|
||||
'fan_min_on_time': 10,
|
||||
'mode': 'Climate1',
|
||||
'climate_mode': 'Climate1',
|
||||
'operation': 'cool'},
|
||||
self.thermostat.device_state_attributes)
|
||||
self.ecobee['equipmentStatus'] = ''
|
||||
|
@ -205,7 +207,7 @@ class TestEcobee(unittest.TestCase):
|
|||
'climate_list': ['Climate1', 'Climate2'],
|
||||
'fan': 'off',
|
||||
'fan_min_on_time': 10,
|
||||
'mode': 'Climate1',
|
||||
'climate_mode': 'Climate1',
|
||||
'operation': 'idle'},
|
||||
self.thermostat.device_state_attributes)
|
||||
|
||||
|
@ -214,7 +216,7 @@ class TestEcobee(unittest.TestCase):
|
|||
'climate_list': ['Climate1', 'Climate2'],
|
||||
'fan': 'off',
|
||||
'fan_min_on_time': 10,
|
||||
'mode': 'Climate1',
|
||||
'climate_mode': 'Climate1',
|
||||
'operation': 'Unknown'},
|
||||
self.thermostat.device_state_attributes)
|
||||
|
||||
|
@ -321,7 +323,7 @@ class TestEcobee(unittest.TestCase):
|
|||
self.assertFalse(self.data.ecobee.delete_vacation.called)
|
||||
self.assertFalse(self.data.ecobee.resume_program.called)
|
||||
self.data.ecobee.set_hold_temp.assert_has_calls(
|
||||
[mock.call(1, 40.0, 20.0, 'nextTransition')])
|
||||
[mock.call(1, 35.0, 25.0, 'nextTransition')])
|
||||
self.assertFalse(self.data.ecobee.set_climate_hold.called)
|
||||
|
||||
def test_set_auto_temp_hold(self):
|
||||
|
@ -337,21 +339,21 @@ class TestEcobee(unittest.TestCase):
|
|||
self.data.reset_mock()
|
||||
self.thermostat.set_temp_hold(30.0)
|
||||
self.data.ecobee.set_hold_temp.assert_has_calls(
|
||||
[mock.call(1, 40.0, 20.0, 'nextTransition')])
|
||||
[mock.call(1, 35.0, 25.0, 'nextTransition')])
|
||||
|
||||
# Heat mode
|
||||
self.data.reset_mock()
|
||||
self.ecobee['settings']['hvacMode'] = 'heat'
|
||||
self.thermostat.set_temp_hold(30)
|
||||
self.data.ecobee.set_hold_temp.assert_has_calls(
|
||||
[mock.call(1, 50, 30, 'nextTransition')])
|
||||
[mock.call(1, 30, 30, 'nextTransition')])
|
||||
|
||||
# Cool mode
|
||||
self.data.reset_mock()
|
||||
self.ecobee['settings']['hvacMode'] = 'cool'
|
||||
self.thermostat.set_temp_hold(30)
|
||||
self.data.ecobee.set_hold_temp.assert_has_calls(
|
||||
[mock.call(1, 30, 10, 'nextTransition')])
|
||||
[mock.call(1, 30, 30, 'nextTransition')])
|
||||
|
||||
def test_set_temperature(self):
|
||||
"""Test set temperature."""
|
||||
|
@ -366,21 +368,21 @@ class TestEcobee(unittest.TestCase):
|
|||
self.data.reset_mock()
|
||||
self.thermostat.set_temperature(temperature=20)
|
||||
self.data.ecobee.set_hold_temp.assert_has_calls(
|
||||
[mock.call(1, 30, 10, 'nextTransition')])
|
||||
[mock.call(1, 25, 15, 'nextTransition')])
|
||||
|
||||
# Cool -> Hold
|
||||
self.data.reset_mock()
|
||||
self.ecobee['settings']['hvacMode'] = 'cool'
|
||||
self.thermostat.set_temperature(temperature=20.5)
|
||||
self.data.ecobee.set_hold_temp.assert_has_calls(
|
||||
[mock.call(1, 20.5, 0.5, 'nextTransition')])
|
||||
[mock.call(1, 20.5, 20.5, 'nextTransition')])
|
||||
|
||||
# Heat -> Hold
|
||||
self.data.reset_mock()
|
||||
self.ecobee['settings']['hvacMode'] = 'heat'
|
||||
self.thermostat.set_temperature(temperature=20)
|
||||
self.data.ecobee.set_hold_temp.assert_has_calls(
|
||||
[mock.call(1, 40, 20, 'nextTransition')])
|
||||
[mock.call(1, 20, 20, 'nextTransition')])
|
||||
|
||||
# Heat -> Auto
|
||||
self.data.reset_mock()
|
||||
|
@ -450,3 +452,17 @@ class TestEcobee(unittest.TestCase):
|
|||
"""Test climate list property."""
|
||||
self.assertEqual(['Climate1', 'Climate2'],
|
||||
self.thermostat.climate_list)
|
||||
|
||||
def test_set_fan_mode_on(self):
|
||||
"""Test set fan mode to on."""
|
||||
self.data.reset_mock()
|
||||
self.thermostat.set_fan_mode('on')
|
||||
self.data.ecobee.set_fan_mode.assert_has_calls(
|
||||
[mock.call(1, 'on', 20, 40, 'nextTransition')])
|
||||
|
||||
def test_set_fan_mode_auto(self):
|
||||
"""Test set fan mode to auto."""
|
||||
self.data.reset_mock()
|
||||
self.thermostat.set_fan_mode('auto')
|
||||
self.data.ecobee.set_fan_mode.assert_has_calls(
|
||||
[mock.call(1, 'auto', 20, 40, 'nextTransition')])
|
||||
|
|
Loading…
Add table
Reference in a new issue