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)
|
||||
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):
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue