Wait for switch startup in generic_thermostat (#45253)

* Better status control on restore

* Better status control on restore

* fix code coverage

* Rollback hvac_mode initialization

I think I have better understood the handling of the `hvac_mode`.
I change the approach. Now the thermostat doesn't initialize until the switch is available.

* fix pyupgrade

* fix black

* Delete test_turn_on_while_restarting

HVAC mode should not be modified by the switch.
IMHO, this test does not make sense because if the switch is turned on the thermostat is not turning on (and not changing HVAC_MODE)

* Re add turn off if HVAC is off

If HVAC_MODE is off thermostat will not control heater switch. This can be because `initial_hvac_mode`, because state defaults to or because old_state.
IMHO it is preferable to be excessively cautious.

* Update climate.py

* Change warning message

* Fix black

* Fix black
This commit is contained in:
javicalle 2021-03-19 15:42:45 +01:00 committed by GitHub
parent c820dd4cb5
commit e798f415a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 102 additions and 1 deletions

View file

@ -266,6 +266,14 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
if not self._hvac_mode:
self._hvac_mode = HVAC_MODE_OFF
# Prevent the device from keep running if HVAC_MODE_OFF
if self._hvac_mode == HVAC_MODE_OFF and self._is_device_active:
await self._async_heater_turn_off()
_LOGGER.warning(
"The climate mode is OFF, but the switch device is ON. Turning off device %s",
self.heater_entity_id,
)
@property
def should_poll(self):
"""Return the polling state."""
@ -418,7 +426,11 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
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, self._target_temp):
if not self._active and None not in (
self._cur_temp,
self._target_temp,
self._is_device_active,
):
self._active = True
_LOGGER.info(
"Obtained current and target temperature. "
@ -480,6 +492,9 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
@property
def _is_device_active(self):
"""If the toggleable device is currently active."""
if not self.hass.states.get(self.heater_entity_id):
return None
return self.hass.states.is_state(self.heater_entity_id, STATE_ON)
@property

View file

@ -1249,6 +1249,92 @@ async def test_no_restore_state(hass):
assert state.state == HVAC_MODE_OFF
async def test_initial_hvac_off_force_heater_off(hass):
"""Ensure that restored state is coherent with real situation.
'initial_hvac_mode: off' will force HVAC status, but we must be sure
that heater don't keep on.
"""
# switch is on
calls = _setup_switch(hass, True)
assert hass.states.get(ENT_SWITCH).state == STATE_ON
_setup_sensor(hass, 16)
await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test_thermostat",
"heater": ENT_SWITCH,
"target_sensor": ENT_SENSOR,
"target_temp": 20,
"initial_hvac_mode": HVAC_MODE_OFF,
}
},
)
await hass.async_block_till_done()
state = hass.states.get("climate.test_thermostat")
# 'initial_hvac_mode' will force state but must prevent heather keep working
assert state.state == HVAC_MODE_OFF
# heater must be switched off
assert len(calls) == 1
call = calls[0]
assert call.domain == HASS_DOMAIN
assert call.service == SERVICE_TURN_OFF
assert call.data["entity_id"] == ENT_SWITCH
async def test_restore_will_turn_off_(hass):
"""Ensure that restored state is coherent with real situation.
Thermostat status must trigger heater event if temp raises the target .
"""
heater_switch = "input_boolean.test"
mock_restore_cache(
hass,
(
State(
"climate.test_thermostat",
HVAC_MODE_HEAT,
{ATTR_TEMPERATURE: "18", ATTR_PRESET_MODE: PRESET_NONE},
),
State(heater_switch, STATE_ON, {}),
),
)
hass.state = CoreState.starting
assert await async_setup_component(
hass, input_boolean.DOMAIN, {"input_boolean": {"test": None}}
)
await hass.async_block_till_done()
assert hass.states.get(heater_switch).state == STATE_ON
_setup_sensor(hass, 22)
await async_setup_component(
hass,
DOMAIN,
{
"climate": {
"platform": "generic_thermostat",
"name": "test_thermostat",
"heater": heater_switch,
"target_sensor": ENT_SENSOR,
"target_temp": 20,
}
},
)
await hass.async_block_till_done()
state = hass.states.get("climate.test_thermostat")
assert state.attributes[ATTR_TEMPERATURE] == 20
assert state.state == HVAC_MODE_HEAT
assert hass.states.get(heater_switch).state == STATE_ON
async def test_restore_state_uncoherence_case(hass):
"""
Test restore from a strange state.