1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 00:20:30 +01:00

Add climate.is_hvac_mode condition (#166570)

This commit is contained in:
Erik Montnemery
2026-03-26 16:24:27 +01:00
committed by Franck Nijhof
parent 1b972d4adc
commit ee9d9781ee
5 changed files with 109 additions and 1 deletions

View File

@@ -1,10 +1,18 @@
"""Provides conditions for climates."""
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.const import ATTR_TEMPERATURE, CONF_OPTIONS, UnitOfTemperature
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.condition import (
ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL,
Condition,
ConditionConfig,
EntityConditionBase,
EntityNumericalConditionWithUnitBase,
make_entity_numerical_condition,
make_entity_state_condition,
@@ -13,6 +21,36 @@ from homeassistant.util.unit_conversion import TemperatureConverter
from .const import ATTR_HUMIDITY, ATTR_HVAC_ACTION, DOMAIN, HVACAction, HVACMode
CONF_HVAC_MODE = "hvac_mode"
_HVAC_MODE_CONDITION_SCHEMA = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL.extend(
{
vol.Required(CONF_OPTIONS): {
vol.Required(CONF_HVAC_MODE): vol.All(
cv.ensure_list, vol.Length(min=1), [vol.Coerce(HVACMode)]
),
},
}
)
class ClimateHVACModeCondition(EntityConditionBase):
"""Condition for climate HVAC mode."""
_domain_specs = {DOMAIN: DomainSpec()}
_schema = _HVAC_MODE_CONDITION_SCHEMA
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
"""Initialize the HVAC mode condition."""
super().__init__(hass, config)
if TYPE_CHECKING:
assert config.options is not None
self._hvac_modes: set[str] = set(config.options[CONF_HVAC_MODE])
def is_valid_state(self, entity_state: State) -> bool:
"""Check if the state matches any of the expected HVAC modes."""
return entity_state.state in self._hvac_modes
class ClimateTargetTemperatureCondition(EntityNumericalConditionWithUnitBase):
"""Mixin for climate target temperature conditions with unit conversion."""
@@ -28,6 +66,7 @@ class ClimateTargetTemperatureCondition(EntityNumericalConditionWithUnitBase):
CONDITIONS: dict[str, type[Condition]] = {
"is_hvac_mode": ClimateHVACModeCondition,
"is_off": make_entity_state_condition(DOMAIN, HVACMode.OFF),
"is_on": make_entity_state_condition(
DOMAIN,

View File

@@ -45,6 +45,21 @@ is_cooling: *condition_common
is_drying: *condition_common
is_heating: *condition_common
is_hvac_mode:
target: *condition_climate_target
fields:
behavior: *condition_behavior
hvac_mode:
context:
filter_target: target
required: true
selector:
state:
hide_states:
- unavailable
- unknown
multiple: true
target_humidity:
target: *condition_climate_target
fields:

View File

@@ -9,6 +9,9 @@
"is_heating": {
"condition": "mdi:fire"
},
"is_hvac_mode": {
"condition": "mdi:thermostat"
},
"is_off": {
"condition": "mdi:power-off"
},

View File

@@ -41,6 +41,20 @@
},
"name": "Climate-control device is heating"
},
"is_hvac_mode": {
"description": "Tests if one or more climate-control devices are set to a specific HVAC mode.",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::condition_behavior_description%]",
"name": "[%key:component::climate::common::condition_behavior_name%]"
},
"hvac_mode": {
"description": "The HVAC modes to test for.",
"name": "Modes"
}
},
"name": "Climate-control device HVAC mode"
},
"is_off": {
"description": "Tests if one or more climate-control devices are off.",
"fields": {

View File

@@ -47,6 +47,7 @@ async def target_climates(hass: HomeAssistant) -> dict[str, list[str]]:
"climate.is_cooling",
"climate.is_drying",
"climate.is_heating",
"climate.is_hvac_mode",
"climate.target_humidity",
"climate.target_temperature",
],
@@ -83,6 +84,24 @@ async def test_climate_conditions_gated_by_labs_flag(
],
other_states=[HVACMode.OFF],
),
*(
param
for mode in HVACMode
for param in parametrize_condition_states_any(
condition="climate.is_hvac_mode",
condition_options={"hvac_mode": [mode]},
target_states=[mode],
other_states=[m for m in HVACMode if m != mode],
)
),
*parametrize_condition_states_any(
condition="climate.is_hvac_mode",
condition_options={"hvac_mode": [HVACMode.HEAT, HVACMode.COOL]},
target_states=[HVACMode.HEAT, HVACMode.COOL],
other_states=[
m for m in HVACMode if m not in (HVACMode.HEAT, HVACMode.COOL)
],
),
],
)
async def test_climate_state_condition_behavior_any(
@@ -133,6 +152,24 @@ async def test_climate_state_condition_behavior_any(
],
other_states=[HVACMode.OFF],
),
*(
param
for mode in HVACMode
for param in parametrize_condition_states_all(
condition="climate.is_hvac_mode",
condition_options={"hvac_mode": [mode]},
target_states=[mode],
other_states=[m for m in HVACMode if m != mode],
)
),
*parametrize_condition_states_all(
condition="climate.is_hvac_mode",
condition_options={"hvac_mode": [HVACMode.HEAT, HVACMode.COOL]},
target_states=[HVACMode.HEAT, HVACMode.COOL],
other_states=[
m for m in HVACMode if m not in (HVACMode.HEAT, HVACMode.COOL)
],
),
],
)
async def test_climate_state_condition_behavior_all(