From 553926806727c940c761ce2410cc3428633ead85 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 28 Aug 2026 19:22:26 +0200 Subject: [PATCH] Apply the hygrostat minimum cycle duration to keep-alive calls (#180494) --- .../generic_hygrostat/humidifier.py | 43 +++++++++---------- .../generic_hygrostat/test_humidifier.py | 42 ++++++++++++++++++ 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/generic_hygrostat/humidifier.py b/homeassistant/components/generic_hygrostat/humidifier.py index 26a11f7d6b4e..398cc5762f5b 100644 --- a/homeassistant/components/generic_hygrostat/humidifier.py +++ b/homeassistant/components/generic_hygrostat/humidifier.py @@ -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.""" diff --git a/tests/components/generic_hygrostat/test_humidifier.py b/tests/components/generic_hygrostat/test_humidifier.py index d268b14a155e..b9223b3c620e 100644 --- a/tests/components/generic_hygrostat/test_humidifier.py +++ b/tests/components/generic_hygrostat/test_humidifier.py @@ -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."""