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:
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"is_humidifying": {
|
||||
"condition": "mdi:arrow-up-bold"
|
||||
},
|
||||
"is_mode": {
|
||||
"condition": "mdi:air-humidifier"
|
||||
},
|
||||
"is_off": {
|
||||
"condition": "mdi:air-humidifier-off"
|
||||
},
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user