From 86a5cc5edb2fef380edda1ebb8843344b29966e8 Mon Sep 17 00:00:00 2001 From: scheric <38077357+scheric@users.noreply.github.com> Date: Wed, 14 Jan 2026 00:20:40 +0100 Subject: [PATCH] Add keep_alive to generic_thermostat config flow (#156641) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: AbĂ­lio Costa --- .../components/generic_thermostat/climate.py | 2 +- .../generic_thermostat/config_flow.py | 4 ++ .../components/generic_thermostat/const.py | 1 + .../generic_thermostat/strings.json | 4 ++ .../generic_thermostat/test_config_flow.py | 45 +++++++++++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 76fcc4acdde..26a368bcd66 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -66,6 +66,7 @@ from .const import ( CONF_COLD_TOLERANCE, CONF_HEATER, CONF_HOT_TOLERANCE, + CONF_KEEP_ALIVE, CONF_MAX_TEMP, CONF_MIN_DUR, CONF_MIN_TEMP, @@ -81,7 +82,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Generic Thermostat" CONF_INITIAL_HVAC_MODE = "initial_hvac_mode" -CONF_KEEP_ALIVE = "keep_alive" CONF_PRECISION = "precision" CONF_TARGET_TEMP = "target_temp" CONF_TEMP_STEP = "target_temp_step" diff --git a/homeassistant/components/generic_thermostat/config_flow.py b/homeassistant/components/generic_thermostat/config_flow.py index c1045cad536..88a09013d75 100644 --- a/homeassistant/components/generic_thermostat/config_flow.py +++ b/homeassistant/components/generic_thermostat/config_flow.py @@ -21,6 +21,7 @@ from .const import ( CONF_COLD_TOLERANCE, CONF_HEATER, CONF_HOT_TOLERANCE, + CONF_KEEP_ALIVE, CONF_MAX_TEMP, CONF_MIN_DUR, CONF_MIN_TEMP, @@ -59,6 +60,9 @@ OPTIONS_SCHEMA = { vol.Optional(CONF_MIN_DUR): selector.DurationSelector( selector.DurationSelectorConfig(allow_negative=False) ), + vol.Optional(CONF_KEEP_ALIVE): selector.DurationSelector( + selector.DurationSelectorConfig(allow_negative=False) + ), vol.Optional(CONF_MIN_TEMP): selector.NumberSelector( selector.NumberSelectorConfig( mode=selector.NumberSelectorMode.BOX, unit_of_measurement=DEGREE, step=0.1 diff --git a/homeassistant/components/generic_thermostat/const.py b/homeassistant/components/generic_thermostat/const.py index f0e6f1a7d73..d4c25f698d2 100644 --- a/homeassistant/components/generic_thermostat/const.py +++ b/homeassistant/components/generic_thermostat/const.py @@ -33,4 +33,5 @@ CONF_PRESETS = { ) } CONF_SENSOR = "target_sensor" +CONF_KEEP_ALIVE = "keep_alive" DEFAULT_TOLERANCE = 0.3 diff --git a/homeassistant/components/generic_thermostat/strings.json b/homeassistant/components/generic_thermostat/strings.json index 6c8876d28eb..5257be051a2 100644 --- a/homeassistant/components/generic_thermostat/strings.json +++ b/homeassistant/components/generic_thermostat/strings.json @@ -18,6 +18,7 @@ "cold_tolerance": "Cold tolerance", "heater": "Actuator switch", "hot_tolerance": "Hot tolerance", + "keep_alive": "Keep-alive interval", "max_temp": "Maximum target temperature", "min_cycle_duration": "Minimum cycle duration", "min_temp": "Minimum target temperature", @@ -29,6 +30,7 @@ "cold_tolerance": "Minimum amount of difference between the temperature read by the temperature sensor the target temperature that must change prior to being switched on. For example, if the target temperature is 25 and the tolerance is 0.5 the heater will start when the sensor goes below 24.5.", "heater": "Switch entity used to cool or heat depending on A/C mode.", "hot_tolerance": "Minimum amount of difference between the temperature read by the temperature sensor the target temperature that must change prior to being switched off. For example, if the target temperature is 25 and the tolerance is 0.5 the heater will stop when the sensor equals or goes above 25.5.", + "keep_alive": "Trigger the heater periodically to keep devices from losing state. When set, min cycle duration is ignored.", "min_cycle_duration": "Set a minimum amount of time that the switch specified must be in its current state prior to being switched either off or on.", "target_sensor": "Temperature sensor that reflects the current temperature." }, @@ -45,6 +47,7 @@ "cold_tolerance": "[%key:component::generic_thermostat::config::step::user::data::cold_tolerance%]", "heater": "[%key:component::generic_thermostat::config::step::user::data::heater%]", "hot_tolerance": "[%key:component::generic_thermostat::config::step::user::data::hot_tolerance%]", + "keep_alive": "[%key:component::generic_thermostat::config::step::user::data::keep_alive%]", "max_temp": "[%key:component::generic_thermostat::config::step::user::data::max_temp%]", "min_cycle_duration": "[%key:component::generic_thermostat::config::step::user::data::min_cycle_duration%]", "min_temp": "[%key:component::generic_thermostat::config::step::user::data::min_temp%]", @@ -55,6 +58,7 @@ "cold_tolerance": "[%key:component::generic_thermostat::config::step::user::data_description::cold_tolerance%]", "heater": "[%key:component::generic_thermostat::config::step::user::data_description::heater%]", "hot_tolerance": "[%key:component::generic_thermostat::config::step::user::data_description::hot_tolerance%]", + "keep_alive": "[%key:component::generic_thermostat::config::step::user::data_description::keep_alive%]", "min_cycle_duration": "[%key:component::generic_thermostat::config::step::user::data_description::min_cycle_duration%]", "target_sensor": "[%key:component::generic_thermostat::config::step::user::data_description::target_sensor%]" } diff --git a/tests/components/generic_thermostat/test_config_flow.py b/tests/components/generic_thermostat/test_config_flow.py index 561870ad3d4..9fec488d449 100644 --- a/tests/components/generic_thermostat/test_config_flow.py +++ b/tests/components/generic_thermostat/test_config_flow.py @@ -11,6 +11,7 @@ from homeassistant.components.generic_thermostat.const import ( CONF_COLD_TOLERANCE, CONF_HEATER, CONF_HOT_TOLERANCE, + CONF_KEEP_ALIVE, CONF_PRESETS, CONF_SENSOR, DOMAIN, @@ -85,6 +86,7 @@ async def test_options(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None CONF_AC_MODE: False, CONF_COLD_TOLERANCE: 0.3, CONF_HOT_TOLERANCE: 0.3, + CONF_KEEP_ALIVE: {"seconds": 60}, CONF_PRESETS[PRESET_AWAY]: 20, }, title="My dehumidifier", @@ -180,3 +182,46 @@ async def test_config_flow_preset_accepts_float( "name": "My thermostat", "target_sensor": "sensor.temperature", } + + +async def test_config_flow_with_keep_alive(hass: HomeAssistant) -> None: + """Test the config flow when keep_alive is set.""" + with patch( + "homeassistant.components.generic_thermostat.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + # Keep_alive input data for test + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: "My thermostat", + CONF_HEATER: "switch.run", + CONF_SENSOR: "sensor.temperature", + CONF_AC_MODE: False, + CONF_COLD_TOLERANCE: 0.3, + CONF_HOT_TOLERANCE: 0.3, + CONF_KEEP_ALIVE: {"seconds": 60}, + }, + ) + + # Complete config flow + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_PRESETS[PRESET_AWAY]: 21, + }, + ) + + assert result["type"] == "create_entry" + + val = result["options"].get(CONF_KEEP_ALIVE) + assert val is not None + assert isinstance(val, dict) + assert val == {"seconds": 60} + + await hass.async_block_till_done() + assert len(mock_setup_entry.mock_calls) == 1