Apply the hygrostat minimum cycle duration to keep-alive calls (#180494)

This commit is contained in:
Franck Nijhof
2026-08-28 19:22:26 +02:00
committed by GitHub
parent 72b0c4b7bc
commit 5539268067
2 changed files with 62 additions and 23 deletions
@@ -514,25 +514,6 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity):
if not self._active or not self._state:
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._switch_entity_id,
current_state,
self._min_cycle_duration,
)
if not long_enough:
return
if force:
# Ignore the tolerance when switched on manually
dry_tolerance: float = 0
@@ -552,20 +533,36 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity):
) or (
self._device_class == HumidifierDeviceClass.DEHUMIDIFIER and too_dry
):
_LOGGER.debug("Turning off humidifier %s", self._switch_entity_id)
await self._async_device_turn_off()
if self._min_cycle_duration_elapsed(force):
_LOGGER.debug(
"Turning off humidifier %s", self._switch_entity_id
)
await self._async_device_turn_off()
elif time is not None:
# The time argument is passed only in keep-alive case
await self._async_device_turn_on()
elif (
self._device_class == HumidifierDeviceClass.HUMIDIFIER and too_dry
) or (self._device_class == HumidifierDeviceClass.DEHUMIDIFIER and too_wet):
_LOGGER.debug("Turning on humidifier %s", self._switch_entity_id)
await self._async_device_turn_on()
if self._min_cycle_duration_elapsed(force):
_LOGGER.debug("Turning on humidifier %s", self._switch_entity_id)
await self._async_device_turn_on()
elif time is not None:
# The time argument is passed only in keep-alive case
await self._async_device_turn_off()
def _min_cycle_duration_elapsed(self, force: bool) -> bool:
"""Return if the device has held its state long enough to toggle."""
if force or not self._min_cycle_duration:
return True
return condition.state(
self.hass,
self._switch_entity_id,
STATE_ON if self._is_device_active else STATE_OFF,
self._min_cycle_duration,
)
@property
def _is_device_active(self) -> bool:
"""If the toggleable device is currently active."""
@@ -1272,6 +1272,48 @@ async def test_humidity_change_dry_trigger_off_long_enough_3(
assert call.data["entity_id"] == ENT_SWITCH
@pytest.mark.usefixtures("setup_comp_7")
async def test_humidity_change_dry_trigger_off_not_long_enough_keep_alive(
hass: HomeAssistant,
) -> None:
"""Test a keep-alive interval does not bypass the minimum cycle duration."""
calls = await _setup_switch(hass, True)
# Settle on a humidity that asks for no change, so the device is left running
_setup_sensor(hass, 45)
await hass.async_block_till_done()
assert len(calls) == 0
# Dry enough to want the dehumidifier off, but it only just came on
_setup_sensor(hass, 30)
await hass.async_block_till_done()
assert len(calls) == 0
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(minutes=10))
await hass.async_block_till_done()
assert len(calls) == 0
@pytest.mark.usefixtures("setup_comp_7")
async def test_humidity_change_dry_trigger_on_not_long_enough_keep_alive(
hass: HomeAssistant,
) -> None:
"""Test a keep-alive interval does not bypass the minimum cycle duration."""
calls = await _setup_switch(hass, False)
# Settle on a humidity that asks for no change, so the device is left stopped
_setup_sensor(hass, 35)
await hass.async_block_till_done()
assert len(calls) == 0
# Wet enough to want the dehumidifier on, but it only just went off
_setup_sensor(hass, 45)
await hass.async_block_till_done()
assert len(calls) == 0
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(minutes=10))
await hass.async_block_till_done()
assert len(calls) == 0
@pytest.fixture
async def setup_comp_8(hass: HomeAssistant) -> None:
"""Initialize components."""