Fix issues with generic thermostat (#11805)

* Fixes for #11757 #11798 #11763

* Adjustments based on feedback
This commit is contained in:
Marius 2018-01-22 13:12:03 +02:00 committed by Martin Hjelmare
parent 0f26ebe954
commit c8d26d99f0
2 changed files with 49 additions and 28 deletions

View file

@ -17,7 +17,8 @@ from homeassistant.components.climate import (
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
from homeassistant.const import (
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.event import (
async_track_state_change, async_track_time_interval)
@ -30,7 +31,6 @@ DEPENDENCIES = ['switch', 'sensor']
DEFAULT_TOLERANCE = 0.3
DEFAULT_NAME = 'Generic Thermostat'
DEFAULT_AWAY_TEMP = 16
CONF_HEATER = 'heater'
CONF_SENSOR = 'target_sensor'
@ -44,7 +44,7 @@ CONF_HOT_TOLERANCE = 'hot_tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
CONF_AWAY_TEMP = 'away_temp'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -64,8 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_INITIAL_OPERATION_MODE):
vol.In([STATE_AUTO, STATE_OFF]),
vol.Optional(CONF_AWAY_TEMP,
default=DEFAULT_AWAY_TEMP): vol.Coerce(float)
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float)
})
@ -119,6 +118,7 @@ class GenericThermostat(ClimateDevice):
self._operation_list = [STATE_HEAT, STATE_OFF]
if initial_operation_mode == STATE_OFF:
self._enabled = False
self._current_operation = STATE_OFF
else:
self._enabled = True
self._active = False
@ -127,6 +127,9 @@ class GenericThermostat(ClimateDevice):
self._max_temp = max_temp
self._target_temp = target_temp
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._is_away = False
@ -139,6 +142,10 @@ class GenericThermostat(ClimateDevice):
async_track_time_interval(
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
def async_added_to_hass(self):
"""Run when entity about to be added."""
@ -154,19 +161,29 @@ class GenericThermostat(ClimateDevice):
self._target_temp = self.max_temp
else:
self._target_temp = self.min_temp
_LOGGER.warning('Undefined target temperature, \
falling back to %s', self._target_temp)
_LOGGER.warning("Undefined target temperature,"
"falling back to %s", self._target_temp)
else:
self._target_temp = float(
old_state.attributes[ATTR_TEMPERATURE])
self._is_away = True if str(
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON else False
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF:
self._current_operation = STATE_OFF
self._enabled = False
if self._initial_operation_mode is None:
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF:
self._enabled = False
if old_state.attributes[ATTR_AWAY_MODE] is not None:
self._is_away = str(
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON
if (self._initial_operation_mode is None and
old_state.attributes[ATTR_OPERATION_MODE] is not None):
self._current_operation = \
old_state.attributes[ATTR_OPERATION_MODE]
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
def state(self):
@ -230,7 +247,7 @@ class GenericThermostat(ClimateDevice):
if self._is_device_active:
self._heater_turn_off()
else:
_LOGGER.error('Unrecognized operation mode: %s', operation_mode)
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)
return
# Ensure we updae the current operation after changing the mode
self.schedule_update_ha_state()
@ -299,7 +316,7 @@ class GenericThermostat(ClimateDevice):
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)
_LOGGER.error("Unable to update from sensor: %s", ex)
@callback
def _async_control_heating(self):
@ -307,8 +324,9 @@ class GenericThermostat(ClimateDevice):
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.')
_LOGGER.info("Obtained current and target temperature. "
"Generic thermostat active. %s, %s",
self._cur_temp, self._target_temp)
if not self._active:
return
@ -333,13 +351,13 @@ class GenericThermostat(ClimateDevice):
too_cold = self._target_temp - self._cur_temp >= \
self._cold_tolerance
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()
else:
too_hot = self._cur_temp - self._target_temp >= \
self._hot_tolerance
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()
else:
is_heating = self._is_device_active
@ -347,14 +365,14 @@ class GenericThermostat(ClimateDevice):
too_hot = self._cur_temp - self._target_temp >= \
self._hot_tolerance
if too_hot:
_LOGGER.info('Turning off heater %s',
_LOGGER.info("Turning off heater %s",
self.heater_entity_id)
self._heater_turn_off()
else:
too_cold = self._target_temp - self._cur_temp >= \
self._cold_tolerance
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()
@property
@ -365,7 +383,7 @@ class GenericThermostat(ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
return self._support_flags
@callback
def _heater_turn_on(self):

View file

@ -160,7 +160,8 @@ class TestClimateGenericThermostat(unittest.TestCase):
'cold_tolerance': 2,
'hot_tolerance': 4,
'heater': ENT_SWITCH,
'target_sensor': ENT_SENSOR
'target_sensor': ENT_SENSOR,
'away_temp': 16
}})
def tearDown(self): # pylint: disable=invalid-name
@ -176,7 +177,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
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'))
self.assertEqual(7, state.attributes.get('temperature'))
def test_get_operation_modes(self):
"""Test that the operation list returns the correct modes."""
@ -266,7 +267,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
self.hass.block_till_done()
climate.set_temperature(self.hass, 25)
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(2, len(self.calls))
call = self.calls[0]
self.assertEqual('homeassistant', call.domain)
self.assertEqual(SERVICE_TURN_OFF, call.service)
@ -414,7 +415,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
self.hass.block_till_done()
climate.set_temperature(self.hass, 30)
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(2, len(self.calls))
call = self.calls[0]
self.assertEqual('homeassistant', call.domain)
self.assertEqual(SERVICE_TURN_OFF, call.service)
@ -750,6 +751,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase):
'cold_tolerance': 0.3,
'hot_tolerance': 0.3,
'heater': ENT_SWITCH,
'target_temp': 25,
'target_sensor': ENT_SENSOR,
'ac_mode': True,
'keep_alive': datetime.timedelta(minutes=10)
@ -841,6 +843,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase):
'name': 'test',
'cold_tolerance': 0.3,
'hot_tolerance': 0.3,
'target_temp': 25,
'heater': ENT_SWITCH,
'target_sensor': ENT_SENSOR,
'keep_alive': datetime.timedelta(minutes=10)