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

Add condition humidifier.is_mode (#166610)

This commit is contained in:
Erik Montnemery
2026-03-26 17:14:11 +01:00
committed by GitHub
parent 7fd7b2c203
commit fb65cf48c9
5 changed files with 182 additions and 3 deletions

View File

@@ -1,15 +1,73 @@
"""Provides conditions for humidifiers."""
from homeassistant.const import PERCENTAGE, STATE_OFF, STATE_ON
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.const import ATTR_MODE, CONF_OPTIONS, PERCENTAGE, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
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,
EntityStateConditionBase,
make_entity_numerical_condition,
make_entity_state_condition,
)
from homeassistant.helpers.entity import get_supported_features
from .const import (
ATTR_ACTION,
ATTR_HUMIDITY,
DOMAIN,
HumidifierAction,
HumidifierEntityFeature,
)
CONF_MODE = "mode"
IS_MODE_CONDITION_SCHEMA = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL.extend(
{
vol.Required(CONF_OPTIONS): {
vol.Required(CONF_MODE): vol.All(cv.ensure_list, vol.Length(min=1), [str]),
},
}
)
def _supports_feature(hass: HomeAssistant, entity_id: str, features: int) -> bool:
"""Test if an entity supports the specified features."""
try:
return bool(get_supported_features(hass, entity_id) & features)
except HomeAssistantError:
return False
class IsModeCondition(EntityStateConditionBase):
"""Condition for humidifier mode."""
_domain_specs = {DOMAIN: DomainSpec(value_source=ATTR_MODE)}
_schema = IS_MODE_CONDITION_SCHEMA
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
"""Initialize the mode condition."""
super().__init__(hass, config)
if TYPE_CHECKING:
assert config.options is not None
self._states = set(config.options[CONF_MODE])
def entity_filter(self, entities: set[str]) -> set[str]:
"""Filter entities of this domain."""
entities = super().entity_filter(entities)
return {
entity_id
for entity_id in entities
if _supports_feature(self._hass, entity_id, HumidifierEntityFeature.MODES)
}
from .const import ATTR_ACTION, ATTR_HUMIDITY, DOMAIN, HumidifierAction
CONDITIONS: dict[str, type[Condition]] = {
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
@@ -20,6 +78,7 @@ CONDITIONS: dict[str, type[Condition]] = {
"is_humidifying": make_entity_state_condition(
{DOMAIN: DomainSpec(value_source=ATTR_ACTION)}, HumidifierAction.HUMIDIFYING
),
"is_mode": IsModeCondition,
"is_target_humidity": make_entity_numerical_condition(
{DOMAIN: DomainSpec(value_source=ATTR_HUMIDITY)},
valid_unit=PERCENTAGE,

View File

@@ -32,6 +32,19 @@ is_on: *condition_common
is_drying: *condition_common
is_humidifying: *condition_common
is_mode:
target: *condition_humidifier_target
fields:
behavior: *condition_behavior
mode:
context:
filter_target: target
required: true
selector:
state:
attribute: available_modes
multiple: true
is_target_humidity:
target: *condition_humidifier_target
fields:

View File

@@ -6,6 +6,9 @@
"is_humidifying": {
"condition": "mdi:arrow-up-bold"
},
"is_mode": {
"condition": "mdi:air-humidifier"
},
"is_off": {
"condition": "mdi:air-humidifier-off"
},

View File

@@ -28,6 +28,20 @@
},
"name": "Humidifier is humidifying"
},
"is_mode": {
"description": "Tests if one or more humidifiers are set to a specific mode.",
"fields": {
"behavior": {
"description": "[%key:component::humidifier::common::condition_behavior_description%]",
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
},
"mode": {
"description": "The operation modes to check for.",
"name": "Mode"
}
},
"name": "Humidifier is in mode"
},
"is_off": {
"description": "Tests if one or more humidifiers are off.",
"fields": {

View File

@@ -1,16 +1,29 @@
"""Test humidifier conditions."""
from contextlib import AbstractContextManager, nullcontext as does_not_raise
from typing import Any
import pytest
import voluptuous as vol
from homeassistant.components.humidifier.condition import CONF_MODE
from homeassistant.components.humidifier.const import (
ATTR_ACTION,
ATTR_HUMIDITY,
HumidifierAction,
HumidifierEntityFeature,
)
from homeassistant.const import (
ATTR_MODE,
ATTR_SUPPORTED_FEATURES,
CONF_ENTITY_ID,
CONF_OPTIONS,
CONF_TARGET,
STATE_OFF,
STATE_ON,
)
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.condition import async_validate_condition_config
from tests.components.common import (
ConditionStateDescription,
@@ -39,6 +52,7 @@ async def target_humidifiers(hass: HomeAssistant) -> dict[str, list[str]]:
"humidifier.is_on",
"humidifier.is_drying",
"humidifier.is_humidifying",
"humidifier.is_mode",
"humidifier.is_target_humidity",
],
)
@@ -153,6 +167,20 @@ async def test_humidifier_state_condition_behavior_all(
target_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.HUMIDIFYING})],
other_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.IDLE})],
),
*parametrize_condition_states_any(
condition="humidifier.is_mode",
condition_options={CONF_MODE: ["eco", "sleep"]},
target_states=[
(STATE_ON, {ATTR_MODE: "eco"}),
(STATE_ON, {ATTR_MODE: "sleep"}),
],
other_states=[
(STATE_ON, {ATTR_MODE: "normal"}),
],
required_filter_attributes={
ATTR_SUPPORTED_FEATURES: HumidifierEntityFeature.MODES
},
),
],
)
async def test_humidifier_attribute_condition_behavior_any(
@@ -196,6 +224,20 @@ async def test_humidifier_attribute_condition_behavior_any(
target_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.HUMIDIFYING})],
other_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.IDLE})],
),
*parametrize_condition_states_all(
condition="humidifier.is_mode",
condition_options={CONF_MODE: ["eco", "sleep"]},
target_states=[
(STATE_ON, {ATTR_MODE: "eco"}),
(STATE_ON, {ATTR_MODE: "sleep"}),
],
other_states=[
(STATE_ON, {ATTR_MODE: "normal"}),
],
required_filter_attributes={
ATTR_SUPPORTED_FEATURES: HumidifierEntityFeature.MODES
},
),
],
)
async def test_humidifier_attribute_condition_behavior_all(
@@ -291,3 +333,51 @@ async def test_humidifier_numerical_condition_behavior_all(
condition_options=condition_options,
states=states,
)
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("condition", "condition_options", "expected_result"),
[
# Valid configurations
(
"humidifier.is_mode",
{CONF_MODE: ["eco", "sleep"]},
does_not_raise(),
),
(
"humidifier.is_mode",
{CONF_MODE: "eco"},
does_not_raise(),
),
# Invalid configurations
(
"humidifier.is_mode",
# Empty mode list
{CONF_MODE: []},
pytest.raises(vol.Invalid),
),
(
"humidifier.is_mode",
# Missing CONF_MODE
{},
pytest.raises(vol.Invalid),
),
],
)
async def test_humidifier_is_mode_condition_validation(
hass: HomeAssistant,
condition: str,
condition_options: dict[str, Any],
expected_result: AbstractContextManager,
) -> None:
"""Test humidifier is_mode condition config validation."""
with expected_result:
await async_validate_condition_config(
hass,
{
"condition": condition,
CONF_TARGET: {CONF_ENTITY_ID: "humidifier.test"},
CONF_OPTIONS: condition_options,
},
)