1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00

Add turned on or off device trigger to toggle entity (#61089)

* Add turned on or off device trigger to toggle entity

* Renamed changed_states trigger to toggled

* Adjust tests

* Fix homekit triggers test

* Add tests

* Adjust tests after rebase

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Erik Montnemery 2022-01-03 10:41:30 +01:00 committed by GitHub
parent 8f99cb51b5
commit e9b746e874
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 574 additions and 59 deletions

View File

@ -1,4 +1,5 @@
"""Constants for device automations."""
CONF_CHANGED_STATES = "toggled"
CONF_IS_OFF = "is_off"
CONF_IS_ON = "is_on"
CONF_TOGGLE = "toggle"

View File

@ -0,0 +1,108 @@
"""Device automation helpers for entity."""
from __future__ import annotations
from typing import Any
import voluptuous as vol
from homeassistant.components.automation import (
AutomationActionType,
AutomationTriggerInfo,
)
from homeassistant.components.device_automation.const import CONF_CHANGED_STATES
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import CONF_ENTITY_ID, CONF_FOR, CONF_PLATFORM, CONF_TYPE
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_registry import async_entries_for_device
from homeassistant.helpers.typing import ConfigType
from . import DEVICE_TRIGGER_BASE_SCHEMA
# mypy: allow-untyped-calls, allow-untyped-defs
ENTITY_TRIGGERS = [
{
# Trigger when entity is turned on or off
CONF_PLATFORM: "device",
CONF_TYPE: CONF_CHANGED_STATES,
},
]
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In([CONF_CHANGED_STATES]),
vol.Optional(CONF_FOR): cv.positive_time_period_dict,
}
)
async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: AutomationTriggerInfo,
) -> CALLBACK_TYPE:
"""Listen for state changes based on configuration."""
to_state = None
state_config = {
CONF_PLATFORM: "state",
state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_trigger.CONF_TO: to_state,
}
if CONF_FOR in config:
state_config[CONF_FOR] = config[CONF_FOR]
state_config = await state_trigger.async_validate_trigger_config(hass, state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)
async def _async_get_automations(
hass: HomeAssistant,
device_id: str,
automation_templates: list[dict[str, str]],
domain: str,
) -> list[dict[str, str]]:
"""List device automations."""
automations: list[dict[str, str]] = []
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entries = [
entry
for entry in async_entries_for_device(entity_registry, device_id)
if entry.domain == domain
]
for entry in entries:
automations.extend(
{
**template,
"device_id": device_id,
"entity_id": entry.entity_id,
"domain": domain,
}
for template in automation_templates
)
return automations
async def async_get_triggers(
hass: HomeAssistant, device_id: str, domain: str
) -> list[dict[str, Any]]:
"""List device triggers."""
return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain)
async def async_get_trigger_capabilities(
hass: HomeAssistant, config: ConfigType
) -> dict[str, vol.Schema]:
"""List trigger capabilities."""
return {
"extra_fields": vol.Schema(
{vol.Optional(CONF_FOR): cv.positive_time_period_dict}
)
}

View File

@ -23,7 +23,7 @@ from homeassistant.helpers import condition, config_validation as cv
from homeassistant.helpers.entity_registry import async_entries_for_device
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
from . import DEVICE_TRIGGER_BASE_SCHEMA
from . import DEVICE_TRIGGER_BASE_SCHEMA, entity
from .const import (
CONF_IS_OFF,
CONF_IS_ON,
@ -94,7 +94,7 @@ CONDITION_SCHEMA = cv.DEVICE_CONDITION_BASE_SCHEMA.extend(
}
)
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
_TOGGLE_TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In([CONF_TURNED_OFF, CONF_TURNED_ON]),
@ -102,6 +102,8 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
}
)
TRIGGER_SCHEMA = vol.Any(entity.TRIGGER_SCHEMA, _TOGGLE_TRIGGER_SCHEMA)
async def async_call_action_from_config(
hass: HomeAssistant,
@ -155,6 +157,9 @@ async def async_attach_trigger(
automation_info: AutomationTriggerInfo,
) -> CALLBACK_TYPE:
"""Listen for state changes based on configuration."""
if config[CONF_TYPE] not in [CONF_TURNED_ON, CONF_TURNED_OFF]:
return await entity.async_attach_trigger(hass, config, action, automation_info)
if config[CONF_TYPE] == CONF_TURNED_ON:
to_state = "on"
else:
@ -221,7 +226,11 @@ async def async_get_triggers(
hass: HomeAssistant, device_id: str, domain: str
) -> list[dict[str, Any]]:
"""List device triggers."""
return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain)
triggers = await entity.async_get_triggers(hass, device_id, domain)
triggers.extend(
await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain)
)
return triggers
async def async_get_condition_capabilities(
@ -239,6 +248,9 @@ async def async_get_trigger_capabilities(
hass: HomeAssistant, config: ConfigType
) -> dict[str, vol.Schema]:
"""List trigger capabilities."""
if config[CONF_TYPE] not in [CONF_TURNED_ON, CONF_TURNED_OFF]:
return await entity.async_get_trigger_capabilities(hass, config)
return {
"extra_fields": vol.Schema(
{vol.Optional(CONF_FOR): cv.positive_time_period_dict}

View File

@ -16,8 +16,9 @@ from homeassistant.helpers.typing import ConfigType
from . import DOMAIN
TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
TRIGGER_SCHEMA = vol.All(
toggle_entity.TRIGGER_SCHEMA,
vol.Schema({vol.Required(CONF_DOMAIN): DOMAIN}, extra=vol.ALLOW_EXTRA),
)

View File

@ -6,6 +6,7 @@
"is_off": "{entity_name} is off"
},
"trigger_type": {
"toggled": "{entity_name} turned on or off",
"turned_on": "{entity_name} turned on",
"turned_off": "{entity_name} turned off"
},

View File

@ -35,7 +35,7 @@ from . import DOMAIN
# mypy: disallow-any-generics
TARGET_TRIGGER_SCHEMA = vol.All(
HUMIDIFIER_TRIGGER_SCHEMA = vol.All(
DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
@ -48,12 +48,14 @@ TARGET_TRIGGER_SCHEMA = vol.All(
cv.has_at_least_one_key(CONF_BELOW, CONF_ABOVE),
)
TOGGLE_TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
TRIGGER_SCHEMA = vol.All(
vol.Any(
HUMIDIFIER_TRIGGER_SCHEMA,
toggle_entity.TRIGGER_SCHEMA,
),
vol.Schema({vol.Required(CONF_DOMAIN): DOMAIN}, extra=vol.ALLOW_EXTRA),
)
TRIGGER_SCHEMA = vol.Any(TARGET_TRIGGER_SCHEMA, TOGGLE_TRIGGER_SCHEMA)
async def async_get_triggers(
hass: HomeAssistant, device_id: str

View File

@ -3,6 +3,7 @@
"device_automation": {
"trigger_type": {
"target_humidity_changed": "{entity_name} target humidity changed",
"toggled": "{entity_name} turned on or off",
"turned_on": "{entity_name} turned on",
"turned_off": "{entity_name} turned off"
},

View File

@ -16,8 +16,9 @@ from homeassistant.helpers.typing import ConfigType
from . import DOMAIN
TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
TRIGGER_SCHEMA = vol.All(
toggle_entity.TRIGGER_SCHEMA,
vol.Schema({vol.Required(CONF_DOMAIN): DOMAIN}, extra=vol.ALLOW_EXTRA),
)

View File

@ -14,6 +14,7 @@
"is_off": "{entity_name} is off"
},
"trigger_type": {
"toggled": "{entity_name} turned on or off",
"turned_on": "{entity_name} turned on",
"turned_off": "{entity_name} turned off"
}

View File

@ -16,8 +16,9 @@ from homeassistant.helpers.typing import ConfigType
from . import DOMAIN
TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
TRIGGER_SCHEMA = vol.All(
toggle_entity.TRIGGER_SCHEMA,
vol.Schema({vol.Required(CONF_DOMAIN): DOMAIN}, extra=vol.ALLOW_EXTRA),
)

View File

@ -11,6 +11,7 @@
"is_off": "{entity_name} is off"
},
"trigger_type": {
"toggled": "{entity_name} turned on or off",
"turned_on": "{entity_name} turned on",
"turned_off": "{entity_name} turned off"
}

View File

@ -16,8 +16,9 @@ from homeassistant.helpers.typing import ConfigType
from . import DOMAIN
TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
TRIGGER_SCHEMA = vol.All(
toggle_entity.TRIGGER_SCHEMA,
vol.Schema({vol.Required(CONF_DOMAIN): DOMAIN}, extra=vol.ALLOW_EXTRA),
)

View File

@ -11,6 +11,7 @@
"is_off": "{entity_name} is off"
},
"trigger_type": {
"toggled": "{entity_name} turned on or off",
"turned_on": "{entity_name} turned on",
"turned_off": "{entity_name} turned off"
}

View File

@ -140,6 +140,13 @@ async def test_websocket_get_triggers(hass, hass_ws_client, device_reg, entity_r
)
entity_reg.async_get_or_create("light", "test", "5678", device_id=device_entry.id)
expected_triggers = [
{
"platform": "device",
"domain": "light",
"type": "toggled",
"device_id": device_entry.id,
"entity_id": "light.test_5678",
},
{
"platform": "device",
"domain": "light",
@ -395,14 +402,14 @@ async def test_async_get_device_automations_single_device_trigger(
hass, device_automation.DeviceAutomationType.TRIGGER, [device_entry.id]
)
assert device_entry.id in result
assert len(result[device_entry.id]) == 2
assert len(result[device_entry.id]) == 3
# Test deprecated str automation_type works, to be removed in 2022.4
result = await device_automation.async_get_device_automations(
hass, "trigger", [device_entry.id]
)
assert device_entry.id in result
assert len(result[device_entry.id]) == 2
assert len(result[device_entry.id]) == 3 # toggled, turned_on, turned_off
async def test_async_get_device_automations_all_devices_trigger(
@ -421,7 +428,7 @@ async def test_async_get_device_automations_all_devices_trigger(
hass, device_automation.DeviceAutomationType.TRIGGER
)
assert device_entry.id in result
assert len(result[device_entry.id]) == 2
assert len(result[device_entry.id]) == 3 # toggled, turned_on, turned_off
async def test_async_get_device_automations_all_devices_condition(
@ -520,7 +527,7 @@ async def test_websocket_get_trigger_capabilities(
triggers = msg["result"]
id = 2
assert len(triggers) == 2
assert len(triggers) == 3 # toggled, turned_on, turned_off
for trigger in triggers:
await client.send_json(
{

View File

@ -0,0 +1,199 @@
"""The test for device automation toggle entity helpers."""
from datetime import timedelta
import pytest
import homeassistant.components.automation as automation
from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed, async_mock_service
from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401
@pytest.fixture
def calls(hass):
"""Track calls to a mock service."""
return async_mock_service(hass, "test", "automation")
async def test_if_fires_on_state_change(hass, calls, enable_custom_integrations):
"""Test for turn_on and turn_off triggers firing.
This is a sanity test for the toggle entity device automation helper, this is
tested by each integration too.
"""
platform = getattr(hass.components, "test.switch")
platform.init()
assert await async_setup_component(
hass, "switch", {"switch": {CONF_PLATFORM: "test"}}
)
await hass.async_block_till_done()
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": "switch",
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_on",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
{
"trigger": {
"platform": "device",
"domain": "switch",
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_off",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
{
"trigger": {
"platform": "device",
"domain": "switch",
"device_id": "",
"entity_id": ent1.entity_id,
"type": "toggled",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on_or_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.states.async_set(ent1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 2
assert {calls[0].data["some"], calls[1].data["some"]} == {
f"turn_off device - {ent1.entity_id} - on - off - None",
f"turn_on_or_off device - {ent1.entity_id} - on - off - None",
}
hass.states.async_set(ent1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 4
assert {calls[2].data["some"], calls[3].data["some"]} == {
f"turn_on device - {ent1.entity_id} - off - on - None",
f"turn_on_or_off device - {ent1.entity_id} - off - on - None",
}
@pytest.mark.parametrize("trigger", ["turned_off", "toggled"])
async def test_if_fires_on_state_change_with_for(
hass, calls, enable_custom_integrations, trigger
):
"""Test for triggers firing with delay."""
platform = getattr(hass.components, "test.switch")
platform.init()
assert await async_setup_component(
hass, "switch", {"switch": {CONF_PLATFORM: "test"}}
)
await hass.async_block_till_done()
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": "switch",
"device_id": "",
"entity_id": ent1.entity_id,
"type": trigger,
"for": {"seconds": 5},
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
}
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.states.async_set(ent1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 0
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10))
await hass.async_block_till_done()
assert len(calls) == 1
await hass.async_block_till_done()
assert calls[0].data["some"] == "turn_off device - {} - on - off - 0:00:05".format(
ent1.entity_id
)

View File

@ -66,6 +66,13 @@ async def test_get_triggers(hass, device_reg, entity_reg):
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"platform": "device",
"domain": DOMAIN,
"type": "toggled",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
triggers = await async_get_device_automations(
hass, DeviceAutomationType.TRIGGER, device_entry.id
@ -144,6 +151,25 @@ async def test_if_fires_on_state_change(hass, calls):
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": "fan.entity",
"type": "toggled",
},
"action": {
"service": "test.automation",
"data_template": {
"some": (
"turn_on_or_off - {{ trigger.platform}} - "
"{{ trigger.entity_id}} - {{ trigger.from_state.state}} - "
"{{ trigger.to_state.state}} - {{ trigger.for }}"
)
},
},
},
]
},
)
@ -151,14 +177,20 @@ async def test_if_fires_on_state_change(hass, calls):
# Fake that the entity is turning on.
hass.states.async_set("fan.entity", STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "turn_on - device - fan.entity - off - on - None"
assert len(calls) == 2
assert {calls[0].data["some"], calls[1].data["some"]} == {
"turn_on - device - fan.entity - off - on - None",
"turn_on_or_off - device - fan.entity - off - on - None",
}
# Fake that the entity is turning off.
hass.states.async_set("fan.entity", STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "turn_off - device - fan.entity - on - off - None"
assert len(calls) == 4
assert {calls[2].data["some"], calls[3].data["some"]} == {
"turn_off - device - fan.entity - on - off - None",
"turn_on_or_off - device - fan.entity - on - off - None",
}
async def test_if_fires_on_state_change_with_for(hass, calls):

View File

@ -3,6 +3,7 @@
from unittest.mock import MagicMock
from homeassistant.components.device_automation import DeviceAutomationType
from homeassistant.components.homekit.const import CHAR_PROGRAMMABLE_SWITCH_EVENT
from homeassistant.components.homekit.type_triggers import DeviceTriggerAccessory
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.setup import async_setup_component
@ -50,11 +51,16 @@ async def test_programmable_switch_button_fires_on_trigger(
hk_driver.publish.reset_mock()
hass.states.async_set("light.ceiling_lights", STATE_ON)
await hass.async_block_till_done()
hk_driver.publish.assert_called_once()
assert len(hk_driver.publish.mock_calls) == 2 # one for on, one for toggle
for call in hk_driver.publish.mock_calls:
char = acc.get_characteristic(call.args[0]["aid"], call.args[0]["iid"])
assert char.display_name == CHAR_PROGRAMMABLE_SWITCH_EVENT
hk_driver.publish.reset_mock()
hass.states.async_set("light.ceiling_lights", STATE_OFF)
await hass.async_block_till_done()
hk_driver.publish.assert_called_once()
assert len(hk_driver.publish.mock_calls) == 2 # one for on, one for toggle
for call in hk_driver.publish.mock_calls:
char = acc.get_characteristic(call.args[0]["aid"], call.args[0]["iid"])
assert char.display_name == CHAR_PROGRAMMABLE_SWITCH_EVENT
await acc.stop()

View File

@ -84,6 +84,13 @@ async def test_get_triggers(hass, device_reg, entity_reg):
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"platform": "device",
"domain": DOMAIN,
"type": "toggled",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
triggers = await async_get_device_automations(
hass, DeviceAutomationType.TRIGGER, device_entry.id
@ -200,6 +207,30 @@ async def test_if_fires_on_state_change(hass, calls):
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": "humidifier.entity",
"type": "toggled",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on_or_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
@ -225,18 +256,20 @@ async def test_if_fires_on_state_change(hass, calls):
# Fake turn off
hass.states.async_set("humidifier.entity", STATE_OFF, {const.ATTR_HUMIDITY: 37})
await hass.async_block_till_done()
assert len(calls) == 4
assert (
calls[3].data["some"] == "turn_off device - humidifier.entity - on - off - None"
)
assert len(calls) == 5
assert {calls[3].data["some"], calls[4].data["some"]} == {
"turn_off device - humidifier.entity - on - off - None",
"turn_on_or_off device - humidifier.entity - on - off - None",
}
# Fake turn on
hass.states.async_set("humidifier.entity", STATE_ON, {const.ATTR_HUMIDITY: 37})
await hass.async_block_till_done()
assert len(calls) == 5
assert (
calls[4].data["some"] == "turn_on device - humidifier.entity - off - on - None"
)
assert len(calls) == 7
assert {calls[5].data["some"], calls[6].data["some"]} == {
"turn_on device - humidifier.entity - off - on - None",
"turn_on_or_off device - humidifier.entity - off - on - None",
}
async def test_invalid_config(hass, calls):

View File

@ -51,6 +51,13 @@ async def test_get_triggers(hass, device_reg, entity_reg):
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": "toggled",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"platform": "device",
"domain": DOMAIN,
@ -161,6 +168,30 @@ async def test_if_fires_on_state_change(hass, calls, enable_custom_integrations)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "toggled",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on_or_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
@ -170,17 +201,19 @@ async def test_if_fires_on_state_change(hass, calls, enable_custom_integrations)
hass.states.async_set(ent1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "turn_off device - {} - on - off - None".format(
ent1.entity_id
)
assert len(calls) == 2
assert {calls[0].data["some"], calls[1].data["some"]} == {
f"turn_off device - {ent1.entity_id} - on - off - None",
f"turn_on_or_off device - {ent1.entity_id} - on - off - None",
}
hass.states.async_set(ent1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format(
ent1.entity_id
)
assert len(calls) == 4
assert {calls[2].data["some"], calls[3].data["some"]} == {
f"turn_on device - {ent1.entity_id} - off - on - None",
f"turn_on_or_off device - {ent1.entity_id} - off - on - None",
}
async def test_if_fires_on_state_change_with_for(

View File

@ -51,6 +51,13 @@ async def test_get_triggers(hass, device_reg, entity_reg):
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": "toggled",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"platform": "device",
"domain": DOMAIN,
@ -159,6 +166,30 @@ async def test_if_fires_on_state_change(hass, calls, enable_custom_integrations)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "toggled",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on_or_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
@ -168,17 +199,19 @@ async def test_if_fires_on_state_change(hass, calls, enable_custom_integrations)
hass.states.async_set(ent1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "turn_off device - {} - on - off - None".format(
ent1.entity_id
)
assert len(calls) == 2
assert {calls[0].data["some"], calls[1].data["some"]} == {
f"turn_off device - {ent1.entity_id} - on - off - None",
f"turn_on_or_off device - {ent1.entity_id} - on - off - None",
}
hass.states.async_set(ent1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format(
ent1.entity_id
)
assert len(calls) == 4
assert {calls[2].data["some"], calls[3].data["some"]} == {
f"turn_on device - {ent1.entity_id} - off - on - None",
f"turn_on_or_off device - {ent1.entity_id} - off - on - None",
}
async def test_if_fires_on_state_change_with_for(

View File

@ -51,6 +51,13 @@ async def test_get_triggers(hass, device_reg, entity_reg):
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": "toggled",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"platform": "device",
"domain": DOMAIN,
@ -159,6 +166,30 @@ async def test_if_fires_on_state_change(hass, calls, enable_custom_integrations)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "toggled",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on_or_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
@ -168,17 +199,19 @@ async def test_if_fires_on_state_change(hass, calls, enable_custom_integrations)
hass.states.async_set(ent1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "turn_off device - {} - on - off - None".format(
ent1.entity_id
)
assert len(calls) == 2
assert {calls[0].data["some"], calls[1].data["some"]} == {
f"turn_off device - {ent1.entity_id} - on - off - None",
f"turn_on_or_off device - {ent1.entity_id} - on - off - None",
}
hass.states.async_set(ent1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format(
ent1.entity_id
)
assert len(calls) == 4
assert {calls[2].data["some"], calls[3].data["some"]} == {
f"turn_on device - {ent1.entity_id} - off - on - None",
f"turn_on_or_off device - {ent1.entity_id} - off - on - None",
}
async def test_if_fires_on_state_change_with_for(

View File

@ -66,6 +66,13 @@ async def test_get_triggers(hass, wemo_entity):
CONF_PLATFORM: "device",
CONF_TYPE: EVENT_TYPE_LONG_PRESS,
},
{
CONF_DEVICE_ID: wemo_entity.device_id,
CONF_DOMAIN: Platform.SWITCH,
CONF_ENTITY_ID: wemo_entity.entity_id,
CONF_PLATFORM: "device",
CONF_TYPE: "toggled",
},
{
CONF_DEVICE_ID: wemo_entity.device_id,
CONF_DOMAIN: Platform.SWITCH,