Fix issues with generic thermostat (#11805)
* Fixes for #11757 #11798 #11763 * Adjustments based on feedback
This commit is contained in:
parent
0f26ebe954
commit
c8d26d99f0
2 changed files with 49 additions and 28 deletions
|
@ -17,7 +17,8 @@ from homeassistant.components.climate import (
|
||||||
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
|
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
|
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
|
||||||
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
|
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||||
|
STATE_UNKNOWN)
|
||||||
from homeassistant.helpers import condition
|
from homeassistant.helpers import condition
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
async_track_state_change, async_track_time_interval)
|
async_track_state_change, async_track_time_interval)
|
||||||
|
@ -30,7 +31,6 @@ DEPENDENCIES = ['switch', 'sensor']
|
||||||
|
|
||||||
DEFAULT_TOLERANCE = 0.3
|
DEFAULT_TOLERANCE = 0.3
|
||||||
DEFAULT_NAME = 'Generic Thermostat'
|
DEFAULT_NAME = 'Generic Thermostat'
|
||||||
DEFAULT_AWAY_TEMP = 16
|
|
||||||
|
|
||||||
CONF_HEATER = 'heater'
|
CONF_HEATER = 'heater'
|
||||||
CONF_SENSOR = 'target_sensor'
|
CONF_SENSOR = 'target_sensor'
|
||||||
|
@ -44,7 +44,7 @@ CONF_HOT_TOLERANCE = 'hot_tolerance'
|
||||||
CONF_KEEP_ALIVE = 'keep_alive'
|
CONF_KEEP_ALIVE = 'keep_alive'
|
||||||
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
|
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
|
||||||
CONF_AWAY_TEMP = 'away_temp'
|
CONF_AWAY_TEMP = 'away_temp'
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
|
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||||
SUPPORT_OPERATION_MODE)
|
SUPPORT_OPERATION_MODE)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
@ -64,8 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
cv.time_period, cv.positive_timedelta),
|
cv.time_period, cv.positive_timedelta),
|
||||||
vol.Optional(CONF_INITIAL_OPERATION_MODE):
|
vol.Optional(CONF_INITIAL_OPERATION_MODE):
|
||||||
vol.In([STATE_AUTO, STATE_OFF]),
|
vol.In([STATE_AUTO, STATE_OFF]),
|
||||||
vol.Optional(CONF_AWAY_TEMP,
|
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float)
|
||||||
default=DEFAULT_AWAY_TEMP): vol.Coerce(float)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -119,6 +118,7 @@ class GenericThermostat(ClimateDevice):
|
||||||
self._operation_list = [STATE_HEAT, STATE_OFF]
|
self._operation_list = [STATE_HEAT, STATE_OFF]
|
||||||
if initial_operation_mode == STATE_OFF:
|
if initial_operation_mode == STATE_OFF:
|
||||||
self._enabled = False
|
self._enabled = False
|
||||||
|
self._current_operation = STATE_OFF
|
||||||
else:
|
else:
|
||||||
self._enabled = True
|
self._enabled = True
|
||||||
self._active = False
|
self._active = False
|
||||||
|
@ -127,6 +127,9 @@ class GenericThermostat(ClimateDevice):
|
||||||
self._max_temp = max_temp
|
self._max_temp = max_temp
|
||||||
self._target_temp = target_temp
|
self._target_temp = target_temp
|
||||||
self._unit = hass.config.units.temperature_unit
|
self._unit = hass.config.units.temperature_unit
|
||||||
|
self._support_flags = SUPPORT_FLAGS
|
||||||
|
if away_temp is not None:
|
||||||
|
self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE
|
||||||
self._away_temp = away_temp
|
self._away_temp = away_temp
|
||||||
self._is_away = False
|
self._is_away = False
|
||||||
|
|
||||||
|
@ -139,6 +142,10 @@ class GenericThermostat(ClimateDevice):
|
||||||
async_track_time_interval(
|
async_track_time_interval(
|
||||||
hass, self._async_keep_alive, self._keep_alive)
|
hass, self._async_keep_alive, self._keep_alive)
|
||||||
|
|
||||||
|
sensor_state = hass.states.get(sensor_entity_id)
|
||||||
|
if sensor_state and sensor_state.state != STATE_UNKNOWN:
|
||||||
|
self._async_update_temp(sensor_state)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_added_to_hass(self):
|
def async_added_to_hass(self):
|
||||||
"""Run when entity about to be added."""
|
"""Run when entity about to be added."""
|
||||||
|
@ -154,19 +161,29 @@ class GenericThermostat(ClimateDevice):
|
||||||
self._target_temp = self.max_temp
|
self._target_temp = self.max_temp
|
||||||
else:
|
else:
|
||||||
self._target_temp = self.min_temp
|
self._target_temp = self.min_temp
|
||||||
_LOGGER.warning('Undefined target temperature, \
|
_LOGGER.warning("Undefined target temperature,"
|
||||||
falling back to %s', self._target_temp)
|
"falling back to %s", self._target_temp)
|
||||||
else:
|
else:
|
||||||
self._target_temp = float(
|
self._target_temp = float(
|
||||||
old_state.attributes[ATTR_TEMPERATURE])
|
old_state.attributes[ATTR_TEMPERATURE])
|
||||||
self._is_away = True if str(
|
if old_state.attributes[ATTR_AWAY_MODE] is not None:
|
||||||
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON else False
|
self._is_away = str(
|
||||||
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF:
|
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON
|
||||||
self._current_operation = STATE_OFF
|
if (self._initial_operation_mode is None and
|
||||||
self._enabled = False
|
old_state.attributes[ATTR_OPERATION_MODE] is not None):
|
||||||
if self._initial_operation_mode is None:
|
self._current_operation = \
|
||||||
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF:
|
old_state.attributes[ATTR_OPERATION_MODE]
|
||||||
self._enabled = False
|
if self._current_operation != STATE_OFF:
|
||||||
|
self._enabled = True
|
||||||
|
else:
|
||||||
|
# No previous state, try and restore defaults
|
||||||
|
if self._target_temp is None:
|
||||||
|
if self.ac_mode:
|
||||||
|
self._target_temp = self.max_temp
|
||||||
|
else:
|
||||||
|
self._target_temp = self.min_temp
|
||||||
|
_LOGGER.warning("No previously saved temperature, setting to %s",
|
||||||
|
self._target_temp)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
|
@ -230,7 +247,7 @@ class GenericThermostat(ClimateDevice):
|
||||||
if self._is_device_active:
|
if self._is_device_active:
|
||||||
self._heater_turn_off()
|
self._heater_turn_off()
|
||||||
else:
|
else:
|
||||||
_LOGGER.error('Unrecognized operation mode: %s', operation_mode)
|
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)
|
||||||
return
|
return
|
||||||
# Ensure we updae the current operation after changing the mode
|
# Ensure we updae the current operation after changing the mode
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
@ -299,7 +316,7 @@ class GenericThermostat(ClimateDevice):
|
||||||
self._cur_temp = self.hass.config.units.temperature(
|
self._cur_temp = self.hass.config.units.temperature(
|
||||||
float(state.state), unit)
|
float(state.state), unit)
|
||||||
except ValueError as ex:
|
except ValueError as ex:
|
||||||
_LOGGER.error('Unable to update from sensor: %s', ex)
|
_LOGGER.error("Unable to update from sensor: %s", ex)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_control_heating(self):
|
def _async_control_heating(self):
|
||||||
|
@ -307,8 +324,9 @@ class GenericThermostat(ClimateDevice):
|
||||||
if not self._active and None not in (self._cur_temp,
|
if not self._active and None not in (self._cur_temp,
|
||||||
self._target_temp):
|
self._target_temp):
|
||||||
self._active = True
|
self._active = True
|
||||||
_LOGGER.info('Obtained current and target temperature. '
|
_LOGGER.info("Obtained current and target temperature. "
|
||||||
'Generic thermostat active.')
|
"Generic thermostat active. %s, %s",
|
||||||
|
self._cur_temp, self._target_temp)
|
||||||
|
|
||||||
if not self._active:
|
if not self._active:
|
||||||
return
|
return
|
||||||
|
@ -333,13 +351,13 @@ class GenericThermostat(ClimateDevice):
|
||||||
too_cold = self._target_temp - self._cur_temp >= \
|
too_cold = self._target_temp - self._cur_temp >= \
|
||||||
self._cold_tolerance
|
self._cold_tolerance
|
||||||
if too_cold:
|
if too_cold:
|
||||||
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
|
_LOGGER.info("Turning off AC %s", self.heater_entity_id)
|
||||||
self._heater_turn_off()
|
self._heater_turn_off()
|
||||||
else:
|
else:
|
||||||
too_hot = self._cur_temp - self._target_temp >= \
|
too_hot = self._cur_temp - self._target_temp >= \
|
||||||
self._hot_tolerance
|
self._hot_tolerance
|
||||||
if too_hot:
|
if too_hot:
|
||||||
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
|
_LOGGER.info("Turning on AC %s", self.heater_entity_id)
|
||||||
self._heater_turn_on()
|
self._heater_turn_on()
|
||||||
else:
|
else:
|
||||||
is_heating = self._is_device_active
|
is_heating = self._is_device_active
|
||||||
|
@ -347,14 +365,14 @@ class GenericThermostat(ClimateDevice):
|
||||||
too_hot = self._cur_temp - self._target_temp >= \
|
too_hot = self._cur_temp - self._target_temp >= \
|
||||||
self._hot_tolerance
|
self._hot_tolerance
|
||||||
if too_hot:
|
if too_hot:
|
||||||
_LOGGER.info('Turning off heater %s',
|
_LOGGER.info("Turning off heater %s",
|
||||||
self.heater_entity_id)
|
self.heater_entity_id)
|
||||||
self._heater_turn_off()
|
self._heater_turn_off()
|
||||||
else:
|
else:
|
||||||
too_cold = self._target_temp - self._cur_temp >= \
|
too_cold = self._target_temp - self._cur_temp >= \
|
||||||
self._cold_tolerance
|
self._cold_tolerance
|
||||||
if too_cold:
|
if too_cold:
|
||||||
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
|
_LOGGER.info("Turning on heater %s", self.heater_entity_id)
|
||||||
self._heater_turn_on()
|
self._heater_turn_on()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -365,7 +383,7 @@ class GenericThermostat(ClimateDevice):
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return SUPPORT_FLAGS
|
return self._support_flags
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _heater_turn_on(self):
|
def _heater_turn_on(self):
|
||||||
|
|
|
@ -160,7 +160,8 @@ class TestClimateGenericThermostat(unittest.TestCase):
|
||||||
'cold_tolerance': 2,
|
'cold_tolerance': 2,
|
||||||
'hot_tolerance': 4,
|
'hot_tolerance': 4,
|
||||||
'heater': ENT_SWITCH,
|
'heater': ENT_SWITCH,
|
||||||
'target_sensor': ENT_SENSOR
|
'target_sensor': ENT_SENSOR,
|
||||||
|
'away_temp': 16
|
||||||
}})
|
}})
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
@ -176,7 +177,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
|
||||||
state = self.hass.states.get(ENTITY)
|
state = self.hass.states.get(ENTITY)
|
||||||
self.assertEqual(7, state.attributes.get('min_temp'))
|
self.assertEqual(7, state.attributes.get('min_temp'))
|
||||||
self.assertEqual(35, state.attributes.get('max_temp'))
|
self.assertEqual(35, state.attributes.get('max_temp'))
|
||||||
self.assertEqual(None, state.attributes.get('temperature'))
|
self.assertEqual(7, state.attributes.get('temperature'))
|
||||||
|
|
||||||
def test_get_operation_modes(self):
|
def test_get_operation_modes(self):
|
||||||
"""Test that the operation list returns the correct modes."""
|
"""Test that the operation list returns the correct modes."""
|
||||||
|
@ -266,7 +267,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
climate.set_temperature(self.hass, 25)
|
climate.set_temperature(self.hass, 25)
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(2, len(self.calls))
|
||||||
call = self.calls[0]
|
call = self.calls[0]
|
||||||
self.assertEqual('homeassistant', call.domain)
|
self.assertEqual('homeassistant', call.domain)
|
||||||
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
||||||
|
@ -414,7 +415,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
climate.set_temperature(self.hass, 30)
|
climate.set_temperature(self.hass, 30)
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(2, len(self.calls))
|
||||||
call = self.calls[0]
|
call = self.calls[0]
|
||||||
self.assertEqual('homeassistant', call.domain)
|
self.assertEqual('homeassistant', call.domain)
|
||||||
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
self.assertEqual(SERVICE_TURN_OFF, call.service)
|
||||||
|
@ -750,6 +751,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase):
|
||||||
'cold_tolerance': 0.3,
|
'cold_tolerance': 0.3,
|
||||||
'hot_tolerance': 0.3,
|
'hot_tolerance': 0.3,
|
||||||
'heater': ENT_SWITCH,
|
'heater': ENT_SWITCH,
|
||||||
|
'target_temp': 25,
|
||||||
'target_sensor': ENT_SENSOR,
|
'target_sensor': ENT_SENSOR,
|
||||||
'ac_mode': True,
|
'ac_mode': True,
|
||||||
'keep_alive': datetime.timedelta(minutes=10)
|
'keep_alive': datetime.timedelta(minutes=10)
|
||||||
|
@ -841,6 +843,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase):
|
||||||
'name': 'test',
|
'name': 'test',
|
||||||
'cold_tolerance': 0.3,
|
'cold_tolerance': 0.3,
|
||||||
'hot_tolerance': 0.3,
|
'hot_tolerance': 0.3,
|
||||||
|
'target_temp': 25,
|
||||||
'heater': ENT_SWITCH,
|
'heater': ENT_SWITCH,
|
||||||
'target_sensor': ENT_SENSOR,
|
'target_sensor': ENT_SENSOR,
|
||||||
'keep_alive': datetime.timedelta(minutes=10)
|
'keep_alive': datetime.timedelta(minutes=10)
|
||||||
|
|
Loading…
Add table
Reference in a new issue