mirror of
https://github.com/home-assistant/core.git
synced 2026-05-15 13:01:22 +01:00
Add remote conditions (#167750)
This commit is contained in:
@@ -143,6 +143,7 @@ _EXPERIMENTAL_CONDITION_PLATFORMS = {
|
||||
"occupancy",
|
||||
"person",
|
||||
"power",
|
||||
"remote",
|
||||
"schedule",
|
||||
"select",
|
||||
"siren",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
"""Provides conditions for remotes."""
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.condition import Condition, make_entity_state_condition
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON),
|
||||
}
|
||||
|
||||
|
||||
async def async_get_conditions(hass: HomeAssistant) -> dict[str, type[Condition]]:
|
||||
"""Return the remote conditions."""
|
||||
return CONDITIONS
|
||||
@@ -0,0 +1,17 @@
|
||||
.condition_common: &condition_common
|
||||
target:
|
||||
entity:
|
||||
domain: remote
|
||||
fields:
|
||||
behavior:
|
||||
required: true
|
||||
default: any
|
||||
selector:
|
||||
select:
|
||||
translation_key: condition_behavior
|
||||
options:
|
||||
- all
|
||||
- any
|
||||
|
||||
is_off: *condition_common
|
||||
is_on: *condition_common
|
||||
@@ -1,4 +1,12 @@
|
||||
{
|
||||
"conditions": {
|
||||
"is_off": {
|
||||
"condition": "mdi:remote-off"
|
||||
},
|
||||
"is_on": {
|
||||
"condition": "mdi:remote"
|
||||
}
|
||||
},
|
||||
"entity_component": {
|
||||
"_": {
|
||||
"default": "mdi:remote",
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_name": "Condition passes if",
|
||||
"trigger_behavior_name": "Trigger when"
|
||||
},
|
||||
"conditions": {
|
||||
"is_off": {
|
||||
"description": "Tests if one or more remotes are off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::remote::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Remote is off"
|
||||
},
|
||||
"is_on": {
|
||||
"description": "Tests if one or more remotes are on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::remote::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Remote is on"
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"toggle": "[%key:common::device_automation::action_type::toggle%]",
|
||||
@@ -31,6 +52,12 @@
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"condition_behavior": {
|
||||
"options": {
|
||||
"all": "All",
|
||||
"any": "Any"
|
||||
}
|
||||
},
|
||||
"trigger_behavior": {
|
||||
"options": {
|
||||
"any": "Any",
|
||||
|
||||
+32
-18
@@ -46,13 +46,20 @@ from homeassistant.setup import async_setup_component
|
||||
from tests.common import MockConfigEntry, mock_device_registry
|
||||
|
||||
|
||||
async def target_entities(hass: HomeAssistant, domain: str) -> dict[str, list[str]]:
|
||||
async def target_entities(
|
||||
hass: HomeAssistant, domain: str, *, domain_excluded: str | None = None
|
||||
) -> dict[str, list[str]]:
|
||||
"""Create multiple entities associated with different targets.
|
||||
|
||||
If `domain_excluded` is provided, entities in excluded_entities will have this
|
||||
domain, otherwise they will have the same domain as included_entities.
|
||||
|
||||
Returns a dict with the following keys:
|
||||
- included_entities: List of entity_ids meant to be targeted.
|
||||
- excluded_entities: List of entity_ids not meant to be targeted.
|
||||
"""
|
||||
domain_excluded = domain_excluded or domain
|
||||
|
||||
config_entry = MockConfigEntry(domain="test")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
@@ -84,10 +91,10 @@ async def target_entities(hass: HomeAssistant, domain: str) -> dict[str, list[st
|
||||
)
|
||||
entity_reg.async_update_entity(entity_area.entity_id, area_id=area.id)
|
||||
entity_area_excluded = entity_reg.async_get_or_create(
|
||||
domain=domain,
|
||||
domain=domain_excluded,
|
||||
platform="test",
|
||||
unique_id=f"{domain}_area_excluded",
|
||||
suggested_object_id=f"area_{domain}_excluded",
|
||||
unique_id=f"{domain_excluded}_area_excluded",
|
||||
suggested_object_id=f"area_{domain_excluded}_excluded",
|
||||
)
|
||||
entity_reg.async_update_entity(entity_area_excluded.entity_id, area_id=area.id)
|
||||
|
||||
@@ -107,10 +114,10 @@ async def target_entities(hass: HomeAssistant, domain: str) -> dict[str, list[st
|
||||
device_id=device.id,
|
||||
)
|
||||
entity_reg.async_get_or_create(
|
||||
domain=domain,
|
||||
domain=domain_excluded,
|
||||
platform="test",
|
||||
unique_id=f"{domain}_device_excluded",
|
||||
suggested_object_id=f"device_{domain}_excluded",
|
||||
unique_id=f"{domain_excluded}_device_excluded",
|
||||
suggested_object_id=f"device_{domain_excluded}_excluded",
|
||||
device_id=device.id,
|
||||
)
|
||||
|
||||
@@ -123,10 +130,10 @@ async def target_entities(hass: HomeAssistant, domain: str) -> dict[str, list[st
|
||||
)
|
||||
entity_reg.async_update_entity(entity_label.entity_id, labels={label.label_id})
|
||||
entity_label_excluded = entity_reg.async_get_or_create(
|
||||
domain=domain,
|
||||
domain=domain_excluded,
|
||||
platform="test",
|
||||
unique_id=f"{domain}_label_excluded",
|
||||
suggested_object_id=f"label_{domain}_excluded",
|
||||
unique_id=f"{domain_excluded}_label_excluded",
|
||||
suggested_object_id=f"label_{domain_excluded}_excluded",
|
||||
)
|
||||
entity_reg.async_update_entity(
|
||||
entity_label_excluded.entity_id, labels={label.label_id}
|
||||
@@ -143,10 +150,10 @@ async def target_entities(hass: HomeAssistant, domain: str) -> dict[str, list[st
|
||||
f"{domain}.device2_{domain}",
|
||||
],
|
||||
"excluded_entities": [
|
||||
f"{domain}.standalone_{domain}_excluded",
|
||||
f"{domain}.label_{domain}_excluded",
|
||||
f"{domain}.area_{domain}_excluded",
|
||||
f"{domain}.device_{domain}_excluded",
|
||||
f"{domain_excluded}.standalone_{domain_excluded}_excluded",
|
||||
f"{domain_excluded}.label_{domain_excluded}_excluded",
|
||||
f"{domain_excluded}.area_{domain_excluded}_excluded",
|
||||
f"{domain_excluded}.device_{domain_excluded}_excluded",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -215,6 +222,7 @@ def _parametrize_condition_states(
|
||||
other_states: list[str | None | tuple[str | None, dict]],
|
||||
required_filter_attributes: dict | None,
|
||||
condition_true_if_invalid: bool,
|
||||
excluded_entities_from_other_domain: bool,
|
||||
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
||||
"""Parametrize states and expected condition evaluations.
|
||||
|
||||
@@ -227,7 +235,9 @@ def _parametrize_condition_states(
|
||||
|
||||
required_filter_attributes = required_filter_attributes or {}
|
||||
condition_options = condition_options or {}
|
||||
has_required_filter_attributes = bool(required_filter_attributes)
|
||||
add_excluded_state = excluded_entities_from_other_domain or bool(
|
||||
required_filter_attributes
|
||||
)
|
||||
|
||||
def state_with_attributes(
|
||||
state: str | None | tuple[str | None, dict],
|
||||
@@ -242,7 +252,7 @@ def _parametrize_condition_states(
|
||||
"attributes": required_filter_attributes,
|
||||
},
|
||||
"excluded_state": {
|
||||
"state": state if has_required_filter_attributes else None,
|
||||
"state": state if add_excluded_state else None,
|
||||
"attributes": {},
|
||||
},
|
||||
"condition_true": condition_true,
|
||||
@@ -254,8 +264,8 @@ def _parametrize_condition_states(
|
||||
"attributes": state[1] | required_filter_attributes,
|
||||
},
|
||||
"excluded_state": {
|
||||
"state": state[0] if has_required_filter_attributes else None,
|
||||
"attributes": state[1],
|
||||
"state": state[0] if add_excluded_state else None,
|
||||
"attributes": state[1] if add_excluded_state else {},
|
||||
},
|
||||
"condition_true": condition_true,
|
||||
"condition_true_first_entity": condition_true_first_entity,
|
||||
@@ -307,6 +317,7 @@ def parametrize_condition_states_any(
|
||||
target_states: list[str | None | tuple[str | None, dict]],
|
||||
other_states: list[str | None | tuple[str | None, dict]],
|
||||
required_filter_attributes: dict | None = None,
|
||||
excluded_entities_from_other_domain: bool = False,
|
||||
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
||||
"""Parametrize states and expected condition evaluations.
|
||||
|
||||
@@ -324,6 +335,7 @@ def parametrize_condition_states_any(
|
||||
other_states=other_states,
|
||||
required_filter_attributes=required_filter_attributes,
|
||||
condition_true_if_invalid=False,
|
||||
excluded_entities_from_other_domain=excluded_entities_from_other_domain,
|
||||
)
|
||||
|
||||
|
||||
@@ -334,6 +346,7 @@ def parametrize_condition_states_all(
|
||||
target_states: list[str | None | tuple[str | None, dict]],
|
||||
other_states: list[str | None | tuple[str | None, dict]],
|
||||
required_filter_attributes: dict | None = None,
|
||||
excluded_entities_from_other_domain: bool = False,
|
||||
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
||||
"""Parametrize states and expected condition evaluations.
|
||||
|
||||
@@ -351,6 +364,7 @@ def parametrize_condition_states_all(
|
||||
other_states=other_states,
|
||||
required_filter_attributes=required_filter_attributes,
|
||||
condition_true_if_invalid=True,
|
||||
excluded_entities_from_other_domain=excluded_entities_from_other_domain,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
"""Test remote conditions."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.components.common import (
|
||||
ConditionStateDescription,
|
||||
assert_condition_behavior_all,
|
||||
assert_condition_behavior_any,
|
||||
assert_condition_gated_by_labs_flag,
|
||||
parametrize_condition_states_all,
|
||||
parametrize_condition_states_any,
|
||||
parametrize_target_entities,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_remotes(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple remote entities associated with different targets."""
|
||||
return await target_entities(hass, "remote", domain_excluded="switch")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"condition",
|
||||
[
|
||||
"remote.is_off",
|
||||
"remote.is_on",
|
||||
],
|
||||
)
|
||||
async def test_remote_conditions_gated_by_labs_flag(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, condition: str
|
||||
) -> None:
|
||||
"""Test the remote conditions are gated by the labs flag."""
|
||||
await assert_condition_gated_by_labs_flag(hass, caplog, condition)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@pytest.mark.parametrize(
|
||||
("condition_target_config", "entity_id", "entities_in_target"),
|
||||
parametrize_target_entities("remote"),
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("condition", "condition_options", "states"),
|
||||
[
|
||||
*parametrize_condition_states_any(
|
||||
condition="remote.is_on",
|
||||
target_states=[STATE_ON],
|
||||
other_states=[STATE_OFF],
|
||||
excluded_entities_from_other_domain=True,
|
||||
),
|
||||
*parametrize_condition_states_any(
|
||||
condition="remote.is_off",
|
||||
target_states=[STATE_OFF],
|
||||
other_states=[STATE_ON],
|
||||
excluded_entities_from_other_domain=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_remote_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_remotes: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
condition: str,
|
||||
condition_options: dict[str, Any],
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the remote state condition with the 'any' behavior."""
|
||||
await assert_condition_behavior_any(
|
||||
hass,
|
||||
target_entities=target_remotes,
|
||||
condition_target_config=condition_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
condition=condition,
|
||||
condition_options=condition_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@pytest.mark.parametrize(
|
||||
("condition_target_config", "entity_id", "entities_in_target"),
|
||||
parametrize_target_entities("remote"),
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("condition", "condition_options", "states"),
|
||||
[
|
||||
*parametrize_condition_states_all(
|
||||
condition="remote.is_on",
|
||||
target_states=[STATE_ON],
|
||||
other_states=[STATE_OFF],
|
||||
excluded_entities_from_other_domain=True,
|
||||
),
|
||||
*parametrize_condition_states_all(
|
||||
condition="remote.is_off",
|
||||
target_states=[STATE_OFF],
|
||||
other_states=[STATE_ON],
|
||||
excluded_entities_from_other_domain=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_remote_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_remotes: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
condition: str,
|
||||
condition_options: dict[str, Any],
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the remote state condition with the 'all' behavior."""
|
||||
await assert_condition_behavior_all(
|
||||
hass,
|
||||
target_entities=target_remotes,
|
||||
condition_target_config=condition_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
condition=condition,
|
||||
condition_options=condition_options,
|
||||
states=states,
|
||||
)
|
||||
Reference in New Issue
Block a user