Skip to content

Commit 5ffe301

Browse files
authored
Add climate.is_hvac_mode condition (home-assistant#166570)
1 parent e5ad609 commit 5ffe301

5 files changed

Lines changed: 109 additions & 1 deletion

File tree

homeassistant/components/climate/condition.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
"""Provides conditions for climates."""
22

3-
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
3+
from typing import TYPE_CHECKING
4+
5+
import voluptuous as vol
6+
7+
from homeassistant.const import ATTR_TEMPERATURE, CONF_OPTIONS, UnitOfTemperature
48
from homeassistant.core import HomeAssistant, State
9+
from homeassistant.helpers import config_validation as cv
510
from homeassistant.helpers.automation import DomainSpec
611
from homeassistant.helpers.condition import (
12+
ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL,
713
Condition,
14+
ConditionConfig,
15+
EntityConditionBase,
816
EntityNumericalConditionWithUnitBase,
917
make_entity_numerical_condition,
1018
make_entity_state_condition,
@@ -13,6 +21,36 @@
1321

1422
from .const import ATTR_HUMIDITY, ATTR_HVAC_ACTION, DOMAIN, HVACAction, HVACMode
1523

24+
CONF_HVAC_MODE = "hvac_mode"
25+
26+
_HVAC_MODE_CONDITION_SCHEMA = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL.extend(
27+
{
28+
vol.Required(CONF_OPTIONS): {
29+
vol.Required(CONF_HVAC_MODE): vol.All(
30+
cv.ensure_list, vol.Length(min=1), [vol.Coerce(HVACMode)]
31+
),
32+
},
33+
}
34+
)
35+
36+
37+
class ClimateHVACModeCondition(EntityConditionBase):
38+
"""Condition for climate HVAC mode."""
39+
40+
_domain_specs = {DOMAIN: DomainSpec()}
41+
_schema = _HVAC_MODE_CONDITION_SCHEMA
42+
43+
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
44+
"""Initialize the HVAC mode condition."""
45+
super().__init__(hass, config)
46+
if TYPE_CHECKING:
47+
assert config.options is not None
48+
self._hvac_modes: set[str] = set(config.options[CONF_HVAC_MODE])
49+
50+
def is_valid_state(self, entity_state: State) -> bool:
51+
"""Check if the state matches any of the expected HVAC modes."""
52+
return entity_state.state in self._hvac_modes
53+
1654

1755
class ClimateTargetTemperatureCondition(EntityNumericalConditionWithUnitBase):
1856
"""Mixin for climate target temperature conditions with unit conversion."""
@@ -28,6 +66,7 @@ def _get_entity_unit(self, entity_state: State) -> str | None:
2866

2967

3068
CONDITIONS: dict[str, type[Condition]] = {
69+
"is_hvac_mode": ClimateHVACModeCondition,
3170
"is_off": make_entity_state_condition(DOMAIN, HVACMode.OFF),
3271
"is_on": make_entity_state_condition(
3372
DOMAIN,

homeassistant/components/climate/conditions.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ is_cooling: *condition_common
4545
is_drying: *condition_common
4646
is_heating: *condition_common
4747

48+
is_hvac_mode:
49+
target: *condition_climate_target
50+
fields:
51+
behavior: *condition_behavior
52+
hvac_mode:
53+
context:
54+
filter_target: target
55+
required: true
56+
selector:
57+
state:
58+
hide_states:
59+
- unavailable
60+
- unknown
61+
multiple: true
62+
4863
target_humidity:
4964
target: *condition_climate_target
5065
fields:

homeassistant/components/climate/icons.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"is_heating": {
1010
"condition": "mdi:fire"
1111
},
12+
"is_hvac_mode": {
13+
"condition": "mdi:thermostat"
14+
},
1215
"is_off": {
1316
"condition": "mdi:power-off"
1417
},

homeassistant/components/climate/strings.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@
4141
},
4242
"name": "Climate-control device is heating"
4343
},
44+
"is_hvac_mode": {
45+
"description": "Tests if one or more climate-control devices are set to a specific HVAC mode.",
46+
"fields": {
47+
"behavior": {
48+
"description": "[%key:component::climate::common::condition_behavior_description%]",
49+
"name": "[%key:component::climate::common::condition_behavior_name%]"
50+
},
51+
"hvac_mode": {
52+
"description": "The HVAC modes to test for.",
53+
"name": "Modes"
54+
}
55+
},
56+
"name": "Climate-control device HVAC mode"
57+
},
4458
"is_off": {
4559
"description": "Tests if one or more climate-control devices are off.",
4660
"fields": {

tests/components/climate/test_condition.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ async def target_climates(hass: HomeAssistant) -> dict[str, list[str]]:
4747
"climate.is_cooling",
4848
"climate.is_drying",
4949
"climate.is_heating",
50+
"climate.is_hvac_mode",
5051
"climate.target_humidity",
5152
"climate.target_temperature",
5253
],
@@ -83,6 +84,24 @@ async def test_climate_conditions_gated_by_labs_flag(
8384
],
8485
other_states=[HVACMode.OFF],
8586
),
87+
*(
88+
param
89+
for mode in HVACMode
90+
for param in parametrize_condition_states_any(
91+
condition="climate.is_hvac_mode",
92+
condition_options={"hvac_mode": [mode]},
93+
target_states=[mode],
94+
other_states=[m for m in HVACMode if m != mode],
95+
)
96+
),
97+
*parametrize_condition_states_any(
98+
condition="climate.is_hvac_mode",
99+
condition_options={"hvac_mode": [HVACMode.HEAT, HVACMode.COOL]},
100+
target_states=[HVACMode.HEAT, HVACMode.COOL],
101+
other_states=[
102+
m for m in HVACMode if m not in (HVACMode.HEAT, HVACMode.COOL)
103+
],
104+
),
86105
],
87106
)
88107
async def test_climate_state_condition_behavior_any(
@@ -133,6 +152,24 @@ async def test_climate_state_condition_behavior_any(
133152
],
134153
other_states=[HVACMode.OFF],
135154
),
155+
*(
156+
param
157+
for mode in HVACMode
158+
for param in parametrize_condition_states_all(
159+
condition="climate.is_hvac_mode",
160+
condition_options={"hvac_mode": [mode]},
161+
target_states=[mode],
162+
other_states=[m for m in HVACMode if m != mode],
163+
)
164+
),
165+
*parametrize_condition_states_all(
166+
condition="climate.is_hvac_mode",
167+
condition_options={"hvac_mode": [HVACMode.HEAT, HVACMode.COOL]},
168+
target_states=[HVACMode.HEAT, HVACMode.COOL],
169+
other_states=[
170+
m for m in HVACMode if m not in (HVACMode.HEAT, HVACMode.COOL)
171+
],
172+
),
136173
],
137174
)
138175
async def test_climate_state_condition_behavior_all(

0 commit comments

Comments
 (0)