mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
Add trigger humidifier.mode_changed (#166241)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
This commit is contained in:
@@ -67,6 +67,9 @@
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"mode_changed": {
|
||||
"trigger": "mdi:air-humidifier"
|
||||
},
|
||||
"started_drying": {
|
||||
"trigger": "mdi:arrow-down-bold"
|
||||
},
|
||||
|
||||
@@ -201,6 +201,20 @@
|
||||
},
|
||||
"title": "Humidifier",
|
||||
"triggers": {
|
||||
"mode_changed": {
|
||||
"description": "Triggers after the operation mode of one or more humidifiers changes.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::humidifier::common::trigger_behavior_description%]",
|
||||
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
|
||||
},
|
||||
"mode": {
|
||||
"description": "The operation modes to trigger on.",
|
||||
"name": "Mode"
|
||||
}
|
||||
},
|
||||
"name": "Humidifier mode changed"
|
||||
},
|
||||
"started_drying": {
|
||||
"description": "Triggers after one or more humidifiers start drying.",
|
||||
"fields": {
|
||||
|
||||
@@ -1,13 +1,65 @@
|
||||
"""Provides triggers for humidifiers."""
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.trigger import Trigger, make_entity_target_state_trigger
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_MODE, CONF_OPTIONS, 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.entity import get_supported_features
|
||||
from homeassistant.helpers.trigger import (
|
||||
ENTITY_STATE_TRIGGER_SCHEMA_FIRST_LAST,
|
||||
EntityTargetStateTriggerBase,
|
||||
Trigger,
|
||||
TriggerConfig,
|
||||
make_entity_target_state_trigger,
|
||||
)
|
||||
|
||||
from .const import ATTR_ACTION, DOMAIN, HumidifierAction, HumidifierEntityFeature
|
||||
|
||||
CONF_MODE = "mode"
|
||||
|
||||
MODE_CHANGED_TRIGGER_SCHEMA = ENTITY_STATE_TRIGGER_SCHEMA_FIRST_LAST.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 ModeChangedTrigger(EntityTargetStateTriggerBase):
|
||||
"""Trigger for humidifier mode changes."""
|
||||
|
||||
_domain_specs = {DOMAIN: DomainSpec(value_source=ATTR_MODE)}
|
||||
_schema = MODE_CHANGED_TRIGGER_SCHEMA
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
|
||||
"""Initialize the mode trigger."""
|
||||
super().__init__(hass, config)
|
||||
self._to_states = set(self._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, DOMAIN, HumidifierAction
|
||||
|
||||
TRIGGERS: dict[str, type[Trigger]] = {
|
||||
"mode_changed": ModeChangedTrigger,
|
||||
"started_drying": make_entity_target_state_trigger(
|
||||
{DOMAIN: DomainSpec(value_source=ATTR_ACTION)}, HumidifierAction.DRYING
|
||||
),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.trigger_common: &trigger_common
|
||||
target:
|
||||
target: &trigger_humidifier_target
|
||||
entity:
|
||||
domain: humidifier
|
||||
fields:
|
||||
behavior:
|
||||
behavior: &trigger_behavior
|
||||
required: true
|
||||
default: any
|
||||
selector:
|
||||
@@ -18,3 +18,16 @@ started_drying: *trigger_common
|
||||
started_humidifying: *trigger_common
|
||||
turned_on: *trigger_common
|
||||
turned_off: *trigger_common
|
||||
|
||||
mode_changed:
|
||||
target: *trigger_humidifier_target
|
||||
fields:
|
||||
behavior: *trigger_behavior
|
||||
mode:
|
||||
context:
|
||||
filter_target: target
|
||||
required: true
|
||||
selector:
|
||||
state:
|
||||
attribute: available_modes
|
||||
multiple: true
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
"""Test humidifier trigger."""
|
||||
|
||||
from contextlib import AbstractContextManager, nullcontext as does_not_raise
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.humidifier.const import ATTR_ACTION, HumidifierAction
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.components.humidifier.const import (
|
||||
ATTR_ACTION,
|
||||
HumidifierAction,
|
||||
HumidifierEntityFeature,
|
||||
)
|
||||
from homeassistant.components.humidifier.trigger import CONF_MODE
|
||||
from homeassistant.const import (
|
||||
ATTR_MODE,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_OPTIONS,
|
||||
CONF_TARGET,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.trigger import async_validate_trigger_config
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
@@ -29,6 +45,7 @@ async def target_humidifiers(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
@pytest.mark.parametrize(
|
||||
"trigger_key",
|
||||
[
|
||||
"humidifier.mode_changed",
|
||||
"humidifier.started_drying",
|
||||
"humidifier.started_humidifying",
|
||||
"humidifier.turned_off",
|
||||
@@ -103,6 +120,21 @@ async def test_humidifier_state_trigger_behavior_any(
|
||||
target_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.HUMIDIFYING})],
|
||||
other_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.IDLE})],
|
||||
),
|
||||
*parametrize_trigger_states(
|
||||
trigger="humidifier.mode_changed",
|
||||
trigger_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
|
||||
},
|
||||
trigger_from_none=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_humidifier_state_attribute_trigger_behavior_any(
|
||||
@@ -189,6 +221,21 @@ async def test_humidifier_state_trigger_behavior_first(
|
||||
target_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.HUMIDIFYING})],
|
||||
other_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.IDLE})],
|
||||
),
|
||||
*parametrize_trigger_states(
|
||||
trigger="humidifier.mode_changed",
|
||||
trigger_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
|
||||
},
|
||||
trigger_from_none=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_humidifier_state_attribute_trigger_behavior_first(
|
||||
@@ -275,6 +322,21 @@ async def test_humidifier_state_trigger_behavior_last(
|
||||
target_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.HUMIDIFYING})],
|
||||
other_states=[(STATE_ON, {ATTR_ACTION: HumidifierAction.IDLE})],
|
||||
),
|
||||
*parametrize_trigger_states(
|
||||
trigger="humidifier.mode_changed",
|
||||
trigger_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
|
||||
},
|
||||
trigger_from_none=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_humidifier_state_attribute_trigger_behavior_last(
|
||||
@@ -298,3 +360,53 @@ async def test_humidifier_state_attribute_trigger_behavior_last(
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@pytest.mark.parametrize(
|
||||
("trigger", "trigger_options", "expected_result"),
|
||||
[
|
||||
# Valid configurations
|
||||
(
|
||||
"humidifier.mode_changed",
|
||||
{CONF_MODE: ["eco", "sleep"]},
|
||||
does_not_raise(),
|
||||
),
|
||||
(
|
||||
"humidifier.mode_changed",
|
||||
{CONF_MODE: "eco"},
|
||||
does_not_raise(),
|
||||
),
|
||||
# Invalid configurations
|
||||
(
|
||||
"humidifier.mode_changed",
|
||||
# Empty mode list
|
||||
{CONF_MODE: []},
|
||||
pytest.raises(vol.Invalid),
|
||||
),
|
||||
(
|
||||
"humidifier.mode_changed",
|
||||
# Missing CONF_MODE
|
||||
{},
|
||||
pytest.raises(vol.Invalid),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_humidifier_mode_changed_trigger_validation(
|
||||
hass: HomeAssistant,
|
||||
trigger: str,
|
||||
trigger_options: dict[str, Any],
|
||||
expected_result: AbstractContextManager,
|
||||
) -> None:
|
||||
"""Test humidifier mode_changed trigger config validation."""
|
||||
with expected_result:
|
||||
await async_validate_trigger_config(
|
||||
hass,
|
||||
[
|
||||
{
|
||||
"platform": trigger,
|
||||
CONF_TARGET: {CONF_ENTITY_ID: "humidifier.test"},
|
||||
CONF_OPTIONS: trigger_options,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user