Compare commits

..

1 Commits

Author SHA1 Message Date
mib1185
3d26a6d96e add entered_home and left_home triggers 2025-12-17 19:39:00 +00:00
9 changed files with 123 additions and 115 deletions

View File

@@ -130,10 +130,10 @@ _EXPERIMENTAL_TRIGGER_PLATFORMS = {
"cover",
"device_tracker",
"fan",
"input_boolean",
"lawn_mower",
"light",
"media_player",
"person",
"switch",
"text",
"update",

View File

@@ -20,13 +20,5 @@
"turn_on": {
"service": "mdi:toggle-switch"
}
},
"triggers": {
"turned_off": {
"trigger": "mdi:toggle-switch-off"
},
"turned_on": {
"trigger": "mdi:toggle-switch"
}
}
}

View File

@@ -1,8 +1,4 @@
{
"common": {
"trigger_behavior_description": "The behavior of the targeted input booleans to trigger on.",
"trigger_behavior_name": "Behavior"
},
"entity_component": {
"_": {
"name": "[%key:component::input_boolean::title%]",
@@ -21,15 +17,6 @@
}
}
},
"selector": {
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"services": {
"reload": {
"description": "Reloads helpers from the YAML-configuration.",
@@ -48,27 +35,5 @@
"name": "[%key:common::action::turn_on%]"
}
},
"title": "Input boolean",
"triggers": {
"turned_off": {
"description": "Triggers after one or more input booleans turn off.",
"fields": {
"behavior": {
"description": "[%key:component::input_boolean::common::trigger_behavior_description%]",
"name": "[%key:component::input_boolean::common::trigger_behavior_name%]"
}
},
"name": "Input boolean turned off"
},
"turned_on": {
"description": "Triggers after one or more input booleans turn on.",
"fields": {
"behavior": {
"description": "[%key:component::input_boolean::common::trigger_behavior_description%]",
"name": "[%key:component::input_boolean::common::trigger_behavior_name%]"
}
},
"name": "Input boolean turned on"
}
}
"title": "Input boolean"
}

View File

@@ -1,17 +0,0 @@
"""Provides triggers for input booleans."""
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.trigger import Trigger, make_entity_target_state_trigger
from . import DOMAIN
TRIGGERS: dict[str, type[Trigger]] = {
"turned_on": make_entity_target_state_trigger(DOMAIN, STATE_ON),
"turned_off": make_entity_target_state_trigger(DOMAIN, STATE_OFF),
}
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for input booleans."""
return TRIGGERS

View File

@@ -11,5 +11,13 @@
"reload": {
"service": "mdi:reload"
}
},
"triggers": {
"entered_home": {
"trigger": "mdi:account-arrow-left"
},
"left_home": {
"trigger": "mdi:account-arrow-right"
}
}
}

View File

@@ -1,4 +1,8 @@
{
"common": {
"trigger_behavior_description": "The behavior of the targeted persons to trigger on.",
"trigger_behavior_name": "Behavior"
},
"entity_component": {
"_": {
"name": "[%key:component::person::title%]",
@@ -25,11 +29,42 @@
}
}
},
"selector": {
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"services": {
"reload": {
"description": "Reloads persons from the YAML-configuration.",
"name": "[%key:common::action::reload%]"
}
},
"title": "Person"
"title": "Person",
"triggers": {
"entered_home": {
"description": "Triggers when one or more persons enter home.",
"fields": {
"behavior": {
"description": "[%key:component::person::common::trigger_behavior_description%]",
"name": "[%key:component::person::common::trigger_behavior_name%]"
}
},
"name": "Entered home"
},
"left_home": {
"description": "Triggers when one or more persons leave home.",
"fields": {
"behavior": {
"description": "[%key:component::person::common::trigger_behavior_description%]",
"name": "[%key:component::person::common::trigger_behavior_name%]"
}
},
"name": "Left home"
}
}
}

View File

@@ -0,0 +1,21 @@
"""Provides triggers for persons."""
from homeassistant.const import STATE_HOME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.trigger import (
Trigger,
make_entity_origin_state_trigger,
make_entity_target_state_trigger,
)
from .const import DOMAIN
TRIGGERS: dict[str, type[Trigger]] = {
"entered_home": make_entity_target_state_trigger(DOMAIN, STATE_HOME),
"left_home": make_entity_origin_state_trigger(DOMAIN, from_state=STATE_HOME),
}
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for persons."""
return TRIGGERS

View File

@@ -1,7 +1,7 @@
.trigger_common: &trigger_common
target:
entity:
domain: input_boolean
domain: person
fields:
behavior:
required: true
@@ -14,5 +14,5 @@
- any
translation_key: trigger_behavior
turned_off: *trigger_common
turned_on: *trigger_common
entered_home: *trigger_common
left_home: *trigger_common

View File

@@ -1,12 +1,17 @@
"""Test input boolean triggers."""
"""Test person trigger."""
from collections.abc import Generator
from unittest.mock import patch
import pytest
from homeassistant.components.input_boolean import DOMAIN
from homeassistant.const import ATTR_LABEL_ID, CONF_ENTITY_ID, STATE_OFF, STATE_ON
from homeassistant.components.person.const import DOMAIN
from homeassistant.const import (
ATTR_LABEL_ID,
CONF_ENTITY_ID,
STATE_HOME,
STATE_NOT_HOME,
)
from homeassistant.core import HomeAssistant, ServiceCall
from tests.components import (
@@ -18,6 +23,8 @@ from tests.components import (
target_entities,
)
STATE_WORK_ZONE = "work"
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
@@ -35,22 +42,19 @@ def enable_experimental_triggers_conditions() -> Generator[None]:
@pytest.fixture
async def target_input_booleans(hass: HomeAssistant) -> list[str]:
"""Create multiple input_boolean entities associated with different targets."""
async def target_persons(hass: HomeAssistant) -> list[str]:
"""Create multiple persons entities associated with different targets."""
return (await target_entities(hass, DOMAIN))["included"]
@pytest.mark.parametrize(
"trigger_key",
[
"input_boolean.turned_off",
"input_boolean.turned_on",
],
["person.entered_home", "person.left_home"],
)
async def test_input_boolean_triggers_gated_by_labs_flag(
async def test_person_triggers_gated_by_labs_flag(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
) -> None:
"""Test the input_boolean triggers are gated by the labs flag."""
"""Test the person triggers are gated by the labs flag."""
await arm_trigger(hass, trigger_key, None, {ATTR_LABEL_ID: "test_label"})
assert (
"Unnamed automation failed to setup triggers and has been disabled: Trigger "
@@ -69,32 +73,32 @@ async def test_input_boolean_triggers_gated_by_labs_flag(
("trigger", "states"),
[
*parametrize_trigger_states(
trigger="input_boolean.turned_off",
target_states=[STATE_OFF],
other_states=[STATE_ON],
trigger="person.entered_home",
target_states=[STATE_HOME],
other_states=[STATE_NOT_HOME, STATE_WORK_ZONE],
),
*parametrize_trigger_states(
trigger="input_boolean.turned_on",
target_states=[STATE_ON],
other_states=[STATE_OFF],
trigger="person.left_home",
target_states=[STATE_NOT_HOME, STATE_WORK_ZONE],
other_states=[STATE_HOME],
),
],
)
async def test_input_boolean_state_trigger_behavior_any(
async def test_person_home_trigger_behavior_any(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_input_booleans: list[str],
target_persons: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
states: list[StateDescription],
) -> None:
"""Test that the input_boolean state trigger fires when any input_boolean state changes to a specific state."""
other_entity_ids = set(target_input_booleans) - {entity_id}
"""Test that the person home triggers when any person changes to a specific state."""
other_entity_ids = set(target_persons) - {entity_id}
# Set all input_booleans, including the tested one, to the initial state
for eid in target_input_booleans:
# Set all persons, including the tested person, to the initial state
for eid in target_persons:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
@@ -109,7 +113,7 @@ async def test_input_boolean_state_trigger_behavior_any(
assert service_call.data[CONF_ENTITY_ID] == entity_id
service_calls.clear()
# Check if changing other input_booleans also triggers
# Check that changing other persons also triggers
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
@@ -126,32 +130,32 @@ async def test_input_boolean_state_trigger_behavior_any(
("trigger", "states"),
[
*parametrize_trigger_states(
trigger="input_boolean.turned_off",
target_states=[STATE_OFF],
other_states=[STATE_ON],
trigger="person.entered_home",
target_states=[STATE_HOME],
other_states=[STATE_NOT_HOME, STATE_WORK_ZONE],
),
*parametrize_trigger_states(
trigger="input_boolean.turned_on",
target_states=[STATE_ON],
other_states=[STATE_OFF],
trigger="person.left_home",
target_states=[STATE_NOT_HOME, STATE_WORK_ZONE],
other_states=[STATE_HOME],
),
],
)
async def test_input_boolean_state_trigger_behavior_first(
async def test_person_state_trigger_behavior_first(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_input_booleans: list[str],
target_persons: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
states: list[StateDescription],
) -> None:
"""Test that the input_boolean state trigger fires when the first input_boolean changes to a specific state."""
other_entity_ids = set(target_input_booleans) - {entity_id}
"""Test that the person home triggers when the first person changes to a specific state."""
other_entity_ids = set(target_persons) - {entity_id}
# Set all input_booleans, including the tested one, to the initial state
for eid in target_input_booleans:
# Set all persons, including the tested person, to the initial state
for eid in target_persons:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
@@ -166,7 +170,7 @@ async def test_input_boolean_state_trigger_behavior_first(
assert service_call.data[CONF_ENTITY_ID] == entity_id
service_calls.clear()
# Triggering other input_booleans should not cause the trigger to fire again
# Triggering other persons should not cause the trigger to fire again
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
@@ -182,32 +186,32 @@ async def test_input_boolean_state_trigger_behavior_first(
("trigger", "states"),
[
*parametrize_trigger_states(
trigger="input_boolean.turned_off",
target_states=[STATE_OFF],
other_states=[STATE_ON],
trigger="person.entered_home",
target_states=[STATE_HOME],
other_states=[STATE_NOT_HOME, STATE_WORK_ZONE],
),
*parametrize_trigger_states(
trigger="input_boolean.turned_on",
target_states=[STATE_ON],
other_states=[STATE_OFF],
trigger="person.left_home",
target_states=[STATE_NOT_HOME, STATE_WORK_ZONE],
other_states=[STATE_HOME],
),
],
)
async def test_input_boolean_state_trigger_behavior_last(
async def test_person_state_trigger_behavior_last(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_input_booleans: list[str],
target_persons: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
states: list[StateDescription],
) -> None:
"""Test that the input_boolean state trigger fires when the last input_boolean changes to a specific state."""
other_entity_ids = set(target_input_booleans) - {entity_id}
"""Test that the person home triggers when the last person changes to a specific state."""
other_entity_ids = set(target_persons) - {entity_id}
# Set all input_booleans, including the tested one, to the initial state
for eid in target_input_booleans:
# Set all persons, including the tested person, to the initial state
for eid in target_persons:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()