diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index ad8875462fd..d421157c2ec 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -232,11 +232,11 @@ class GenericThermostat(ClimateDevice): if operation_mode == STATE_HEAT: self._current_operation = STATE_HEAT self._enabled = True - await self._async_control_heating() + await self._async_control_heating(force=True) elif operation_mode == STATE_COOL: self._current_operation = STATE_COOL self._enabled = True - await self._async_control_heating() + await self._async_control_heating(force=True) elif operation_mode == STATE_OFF: self._current_operation = STATE_OFF self._enabled = False @@ -262,7 +262,7 @@ class GenericThermostat(ClimateDevice): if temperature is None: return self._target_temp = temperature - await self._async_control_heating() + await self._async_control_heating(force=True) await self.async_update_ha_state() @property @@ -307,7 +307,7 @@ class GenericThermostat(ClimateDevice): except ValueError as ex: _LOGGER.error("Unable to update from sensor: %s", ex) - async def _async_control_heating(self, time=None): + async def _async_control_heating(self, time=None, force=False): """Check if we need to turn heating on or off.""" async with self._temp_lock: if not self._active and None not in (self._cur_temp, @@ -320,16 +320,21 @@ class GenericThermostat(ClimateDevice): if not self._active or not self._enabled: return - if self.min_cycle_duration: - if self._is_device_active: - current_state = STATE_ON - else: - current_state = STATE_OFF - long_enough = condition.state( - self.hass, self.heater_entity_id, current_state, - self.min_cycle_duration) - if not long_enough: - return + if not force and time is None: + # If the `force` argument is True, we + # ignore `min_cycle_duration`. + # If the `time` argument is not none, we were invoked for + # keep-alive purposes, and `min_cycle_duration` is irrelevant. + if self.min_cycle_duration: + if self._is_device_active: + current_state = STATE_ON + else: + current_state = STATE_OFF + long_enough = condition.state( + self.hass, self.heater_entity_id, current_state, + self.min_cycle_duration) + if not long_enough: + return too_cold = \ self._target_temp - self._cur_temp >= self._cold_tolerance @@ -385,7 +390,7 @@ class GenericThermostat(ClimateDevice): self._is_away = True self._saved_target_temp = self._target_temp self._target_temp = self._away_temp - await self._async_control_heating() + await self._async_control_heating(force=True) await self.async_update_ha_state() async def async_turn_away_mode_off(self): @@ -394,5 +399,5 @@ class GenericThermostat(ClimateDevice): return self._is_away = False self._target_temp = self._saved_target_temp - await self._async_control_heating() + await self._async_control_heating(force=True) await self.async_update_ha_state() diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index f87a5371773..6bdbc58e011 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -623,6 +623,38 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] + def test_mode_change_ac_trigger_off_not_long_enough(self): + """Test if mode change turns ac off despite minimum cycle.""" + self._setup_switch(True) + common.set_temperature(self.hass, 30) + self.hass.block_till_done() + self._setup_sensor(25) + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + common.set_operation_mode(self.hass, climate.STATE_OFF) + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + call = self.calls[0] + self.assertEqual('homeassistant', call.domain) + self.assertEqual(SERVICE_TURN_OFF, call.service) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) + + def test_mode_change_ac_trigger_on_not_long_enough(self): + """Test if mode change turns ac on despite minimum cycle.""" + self._setup_switch(False) + common.set_temperature(self.hass, 25) + self.hass.block_till_done() + self._setup_sensor(30) + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + common.set_operation_mode(self.hass, climate.STATE_HEAT) + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + call = self.calls[0] + self.assertEqual('homeassistant', call.domain) + self.assertEqual(SERVICE_TURN_ON, call.service) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) + def _setup_sensor(self, temp): """Set up the test sensor.""" self.hass.states.set(ENT_SENSOR, temp) @@ -714,6 +746,38 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] + def test_mode_change_heater_trigger_off_not_long_enough(self): + """Test if mode change turns heater off despite minimum cycle.""" + self._setup_switch(True) + common.set_temperature(self.hass, 25) + self.hass.block_till_done() + self._setup_sensor(30) + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + common.set_operation_mode(self.hass, climate.STATE_OFF) + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + call = self.calls[0] + self.assertEqual('homeassistant', call.domain) + self.assertEqual(SERVICE_TURN_OFF, call.service) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) + + def test_mode_change_heater_trigger_on_not_long_enough(self): + """Test if mode change turns heater on despite minimum cycle.""" + self._setup_switch(False) + common.set_temperature(self.hass, 30) + self.hass.block_till_done() + self._setup_sensor(25) + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + common.set_operation_mode(self.hass, climate.STATE_HEAT) + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + call = self.calls[0] + self.assertEqual('homeassistant', call.domain) + self.assertEqual(SERVICE_TURN_ON, call.service) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) + def _setup_sensor(self, temp): """Set up the test sensor.""" self.hass.states.set(ENT_SENSOR, temp) @@ -748,6 +812,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase): 'target_temp': 25, 'target_sensor': ENT_SENSOR, 'ac_mode': True, + 'min_cycle_duration': datetime.timedelta(minutes=15), 'keep_alive': datetime.timedelta(minutes=10) }}) @@ -838,6 +903,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase): 'target_temp': 25, 'heater': ENT_SWITCH, 'target_sensor': ENT_SENSOR, + 'min_cycle_duration': datetime.timedelta(minutes=15), 'keep_alive': datetime.timedelta(minutes=10) }})