1
0
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:
Erik Montnemery
2026-04-14 16:34:42 +02:00
committed by GitHub
parent 177d244b91
commit 16edfc9624
7 changed files with 231 additions and 18 deletions
@@ -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
View File
@@ -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,
)
+129
View File
@@ -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,
)