mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add support for for
to binary_sensor, light and switch device triggers (#26658)
* Add support for `for` to binary_sensor, light and switch device triggers * Add WS API device_automation/trigger/capabilities
This commit is contained in:
parent
d8c6b281b8
commit
65ce3b49c1
@ -5,6 +5,7 @@ from homeassistant.components.device_automation import (
|
||||
TRIGGER_BASE_SCHEMA,
|
||||
async_get_device_automation_platform,
|
||||
)
|
||||
from homeassistant.const import CONF_DOMAIN
|
||||
|
||||
|
||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
||||
@ -14,11 +15,15 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
async def async_validate_trigger_config(hass, config):
|
||||
"""Validate config."""
|
||||
platform = await async_get_device_automation_platform(hass, config, "trigger")
|
||||
platform = await async_get_device_automation_platform(
|
||||
hass, config[CONF_DOMAIN], "trigger"
|
||||
)
|
||||
return platform.TRIGGER_SCHEMA(config)
|
||||
|
||||
|
||||
async def async_attach_trigger(hass, config, action, automation_info):
|
||||
"""Listen for trigger."""
|
||||
platform = await async_get_device_automation_platform(hass, config, "trigger")
|
||||
platform = await async_get_device_automation_platform(
|
||||
hass, config[CONF_DOMAIN], "trigger"
|
||||
)
|
||||
return await platform.async_attach_trigger(hass, config, action, automation_info)
|
||||
|
@ -7,7 +7,7 @@ from homeassistant.components.device_automation.const import (
|
||||
CONF_TURNED_OFF,
|
||||
CONF_TURNED_ON,
|
||||
)
|
||||
from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_TYPE
|
||||
from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FOR, CONF_TYPE
|
||||
from homeassistant.helpers.entity_registry import async_entries_for_device
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
@ -175,13 +175,13 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_TYPE): vol.In(TURNED_OFF + TURNED_ON),
|
||||
vol.Optional(CONF_FOR): cv.positive_time_period_dict,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_attach_trigger(hass, config, action, automation_info):
|
||||
"""Listen for state changes based on configuration."""
|
||||
config = TRIGGER_SCHEMA(config)
|
||||
trigger_type = config[CONF_TYPE]
|
||||
if trigger_type in TURNED_ON:
|
||||
from_state = "off"
|
||||
@ -195,6 +195,8 @@ async def async_attach_trigger(hass, config, action, automation_info):
|
||||
state_automation.CONF_FROM: from_state,
|
||||
state_automation.CONF_TO: to_state,
|
||||
}
|
||||
if "for" in config:
|
||||
state_config["for"] = config["for"]
|
||||
|
||||
return await state_automation.async_attach_trigger(
|
||||
hass, state_config, action, automation_info, platform_type="device"
|
||||
@ -236,3 +238,12 @@ async def async_get_triggers(hass, device_id):
|
||||
)
|
||||
|
||||
return triggers
|
||||
|
||||
|
||||
async def async_get_trigger_capabilities(hass, trigger):
|
||||
"""List trigger capabilities."""
|
||||
return {
|
||||
"extra_fields": vol.Schema(
|
||||
{vol.Optional(CONF_FOR): cv.positive_time_period_dict}
|
||||
)
|
||||
}
|
||||
|
@ -206,8 +206,6 @@ def _get_deconz_event_from_device_id(hass, device_id):
|
||||
|
||||
async def async_attach_trigger(hass, config, action, automation_info):
|
||||
"""Listen for state changes based on configuration."""
|
||||
config = TRIGGER_SCHEMA(config)
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device = device_registry.async_get(config[CONF_DEVICE_ID])
|
||||
|
||||
|
@ -4,9 +4,11 @@ import logging
|
||||
from typing import Any, List, MutableMapping
|
||||
|
||||
import voluptuous as vol
|
||||
import voluptuous_serialize
|
||||
|
||||
from homeassistant.const import CONF_PLATFORM, CONF_DOMAIN, CONF_DEVICE_ID
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_registry import async_entries_for_device
|
||||
from homeassistant.loader import async_get_integration, IntegrationNotFound
|
||||
|
||||
@ -29,9 +31,18 @@ TRIGGER_BASE_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
TYPES = {
|
||||
"trigger": ("device_trigger", "async_get_triggers"),
|
||||
"condition": ("device_condition", "async_get_conditions"),
|
||||
"action": ("device_action", "async_get_actions"),
|
||||
# platform name, get automations function, get capabilities function
|
||||
"trigger": (
|
||||
"device_trigger",
|
||||
"async_get_triggers",
|
||||
"async_get_trigger_capabilities",
|
||||
),
|
||||
"condition": (
|
||||
"device_condition",
|
||||
"async_get_conditions",
|
||||
"async_get_condition_capabilities",
|
||||
),
|
||||
"action": ("device_action", "async_get_actions", "async_get_action_capabilities"),
|
||||
}
|
||||
|
||||
|
||||
@ -46,25 +57,26 @@ async def async_setup(hass, config):
|
||||
hass.components.websocket_api.async_register_command(
|
||||
websocket_device_automation_list_triggers
|
||||
)
|
||||
hass.components.websocket_api.async_register_command(
|
||||
websocket_device_automation_get_trigger_capabilities
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_get_device_automation_platform(hass, config, automation_type):
|
||||
async def async_get_device_automation_platform(hass, domain, automation_type):
|
||||
"""Load device automation platform for integration.
|
||||
|
||||
Throws InvalidDeviceAutomationConfig if the integration is not found or does not support device automation.
|
||||
"""
|
||||
platform_name, _ = TYPES[automation_type]
|
||||
platform_name = TYPES[automation_type][0]
|
||||
try:
|
||||
integration = await async_get_integration(hass, config[CONF_DOMAIN])
|
||||
integration = await async_get_integration(hass, domain)
|
||||
platform = integration.get_platform(platform_name)
|
||||
except IntegrationNotFound:
|
||||
raise InvalidDeviceAutomationConfig(
|
||||
f"Integration '{config[CONF_DOMAIN]}' not found"
|
||||
)
|
||||
raise InvalidDeviceAutomationConfig(f"Integration '{domain}' not found")
|
||||
except ImportError:
|
||||
raise InvalidDeviceAutomationConfig(
|
||||
f"Integration '{config[CONF_DOMAIN]}' does not support device automation {automation_type}s"
|
||||
f"Integration '{domain}' does not support device automation {automation_type}s"
|
||||
)
|
||||
|
||||
return platform
|
||||
@ -74,20 +86,14 @@ async def _async_get_device_automations_from_domain(
|
||||
hass, domain, automation_type, device_id
|
||||
):
|
||||
"""List device automations."""
|
||||
integration = None
|
||||
try:
|
||||
integration = await async_get_integration(hass, domain)
|
||||
except IntegrationNotFound:
|
||||
_LOGGER.warning("Integration %s not found", domain)
|
||||
platform = await async_get_device_automation_platform(
|
||||
hass, domain, automation_type
|
||||
)
|
||||
except InvalidDeviceAutomationConfig:
|
||||
return None
|
||||
|
||||
platform_name, function_name = TYPES[automation_type]
|
||||
|
||||
try:
|
||||
platform = integration.get_platform(platform_name)
|
||||
except ImportError:
|
||||
# The domain does not have device automations
|
||||
return None
|
||||
function_name = TYPES[automation_type][1]
|
||||
|
||||
return await getattr(platform, function_name)(hass, device_id)
|
||||
|
||||
@ -125,6 +131,35 @@ async def _async_get_device_automations(hass, automation_type, device_id):
|
||||
return automations
|
||||
|
||||
|
||||
async def _async_get_device_automation_capabilities(hass, automation_type, automation):
|
||||
"""List device automations."""
|
||||
try:
|
||||
platform = await async_get_device_automation_platform(
|
||||
hass, automation[CONF_DOMAIN], automation_type
|
||||
)
|
||||
except InvalidDeviceAutomationConfig:
|
||||
return {}
|
||||
|
||||
function_name = TYPES[automation_type][2]
|
||||
|
||||
if not hasattr(platform, function_name):
|
||||
# The device automation has no capabilities
|
||||
return {}
|
||||
|
||||
capabilities = await getattr(platform, function_name)(hass, automation)
|
||||
capabilities = capabilities.copy()
|
||||
|
||||
extra_fields = capabilities.get("extra_fields")
|
||||
if extra_fields is None:
|
||||
capabilities["extra_fields"] = []
|
||||
else:
|
||||
capabilities["extra_fields"] = voluptuous_serialize.convert(
|
||||
extra_fields, custom_serializer=cv.custom_serializer
|
||||
)
|
||||
|
||||
return capabilities
|
||||
|
||||
|
||||
@websocket_api.async_response
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
@ -165,3 +200,19 @@ async def websocket_device_automation_list_triggers(hass, connection, msg):
|
||||
device_id = msg["device_id"]
|
||||
triggers = await _async_get_device_automations(hass, "trigger", device_id)
|
||||
connection.send_result(msg["id"], triggers)
|
||||
|
||||
|
||||
@websocket_api.async_response
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "device_automation/trigger/capabilities",
|
||||
vol.Required("trigger"): dict,
|
||||
}
|
||||
)
|
||||
async def websocket_device_automation_get_trigger_capabilities(hass, connection, msg):
|
||||
"""Handle request for device trigger capabilities."""
|
||||
trigger = msg["trigger"]
|
||||
capabilities = await _async_get_device_automation_capabilities(
|
||||
hass, "trigger", trigger
|
||||
)
|
||||
connection.send_result(msg["id"], capabilities)
|
||||
|
@ -13,7 +13,13 @@ from homeassistant.components.device_automation.const import (
|
||||
CONF_TURNED_OFF,
|
||||
CONF_TURNED_ON,
|
||||
)
|
||||
from homeassistant.const import CONF_CONDITION, CONF_ENTITY_ID, CONF_PLATFORM, CONF_TYPE
|
||||
from homeassistant.const import (
|
||||
CONF_CONDITION,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_FOR,
|
||||
CONF_PLATFORM,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from homeassistant.helpers.entity_registry import async_entries_for_device
|
||||
from homeassistant.helpers import condition, config_validation as cv, service
|
||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
||||
@ -81,6 +87,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_TYPE): vol.In([CONF_TURNED_OFF, CONF_TURNED_ON]),
|
||||
vol.Optional(CONF_FOR): cv.positive_time_period_dict,
|
||||
}
|
||||
)
|
||||
|
||||
@ -93,7 +100,6 @@ async def async_call_action_from_config(
|
||||
domain: str,
|
||||
) -> None:
|
||||
"""Change state based on configuration."""
|
||||
config = ACTION_SCHEMA(config)
|
||||
action_type = config[CONF_TYPE]
|
||||
if action_type == CONF_TURN_ON:
|
||||
action = "turn_on"
|
||||
@ -149,6 +155,8 @@ async def async_attach_trigger(
|
||||
state.CONF_FROM: from_state,
|
||||
state.CONF_TO: to_state,
|
||||
}
|
||||
if "for" in config:
|
||||
state_config["for"] = config["for"]
|
||||
|
||||
return await state.async_attach_trigger(
|
||||
hass, state_config, action, automation_info, platform_type="device"
|
||||
@ -203,3 +211,12 @@ async def async_get_triggers(
|
||||
) -> List[dict]:
|
||||
"""List device triggers."""
|
||||
return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain)
|
||||
|
||||
|
||||
async def async_get_trigger_capabilities(hass: HomeAssistant, trigger: dict) -> dict:
|
||||
"""List trigger capabilities."""
|
||||
return {
|
||||
"extra_fields": vol.Schema(
|
||||
{vol.Optional(CONF_FOR): cv.positive_time_period_dict}
|
||||
)
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ async def async_call_action_from_config(
|
||||
context: Context,
|
||||
) -> None:
|
||||
"""Change state based on configuration."""
|
||||
config = ACTION_SCHEMA(config)
|
||||
await toggle_entity.async_call_action_from_config(
|
||||
hass, config, variables, context, DOMAIN
|
||||
)
|
||||
|
@ -22,7 +22,6 @@ async def async_attach_trigger(
|
||||
automation_info: dict,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Listen for state changes based on configuration."""
|
||||
config = TRIGGER_SCHEMA(config)
|
||||
return await toggle_entity.async_attach_trigger(
|
||||
hass, config, action, automation_info
|
||||
)
|
||||
@ -31,3 +30,8 @@ async def async_attach_trigger(
|
||||
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]:
|
||||
"""List device triggers."""
|
||||
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)
|
||||
|
||||
|
||||
async def async_get_trigger_capabilities(hass: HomeAssistant, trigger: dict) -> dict:
|
||||
"""List trigger capabilities."""
|
||||
return await toggle_entity.async_get_trigger_capabilities(hass, trigger)
|
||||
|
@ -19,7 +19,6 @@ async def async_call_action_from_config(
|
||||
context: Context,
|
||||
) -> None:
|
||||
"""Change state based on configuration."""
|
||||
config = ACTION_SCHEMA(config)
|
||||
await toggle_entity.async_call_action_from_config(
|
||||
hass, config, variables, context, DOMAIN
|
||||
)
|
||||
|
@ -22,7 +22,6 @@ async def async_attach_trigger(
|
||||
automation_info: dict,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Listen for state changes based on configuration."""
|
||||
config = TRIGGER_SCHEMA(config)
|
||||
return await toggle_entity.async_attach_trigger(
|
||||
hass, config, action, automation_info
|
||||
)
|
||||
@ -31,3 +30,8 @@ async def async_attach_trigger(
|
||||
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]:
|
||||
"""List device triggers."""
|
||||
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)
|
||||
|
||||
|
||||
async def async_get_trigger_capabilities(hass: HomeAssistant, trigger: dict) -> dict:
|
||||
"""List trigger capabilities."""
|
||||
return await toggle_entity.async_get_trigger_capabilities(hass, trigger)
|
||||
|
@ -49,7 +49,6 @@ async def async_call_action_from_config(
|
||||
context: Context,
|
||||
) -> None:
|
||||
"""Perform an action based on configuration."""
|
||||
config = ACTION_SCHEMA(config)
|
||||
await ZHA_ACTION_TYPES[DEVICE_ACTION_TYPES[config[CONF_TYPE]]](
|
||||
hass, config, variables, context
|
||||
)
|
||||
|
@ -23,7 +23,6 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
|
||||
|
||||
async def async_attach_trigger(hass, config, action, automation_info):
|
||||
"""Listen for state changes based on configuration."""
|
||||
config = TRIGGER_SCHEMA(config)
|
||||
trigger = (config[CONF_TYPE], config[CONF_SUBTYPE])
|
||||
zha_device = await async_get_zha_device(hass, config[CONF_DEVICE_ID])
|
||||
|
||||
|
@ -15,8 +15,9 @@ from typing import Any, Union, TypeVar, Callable, List, Dict, Optional
|
||||
from urllib.parse import urlparse
|
||||
from uuid import UUID
|
||||
|
||||
import voluptuous as vol
|
||||
from pkg_resources import parse_version
|
||||
import voluptuous as vol
|
||||
import voluptuous_serialize
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
@ -374,6 +375,9 @@ def positive_timedelta(value: timedelta) -> timedelta:
|
||||
return value
|
||||
|
||||
|
||||
positive_time_period_dict = vol.All(time_period_dict, positive_timedelta)
|
||||
|
||||
|
||||
def remove_falsy(value: List[T]) -> List[T]:
|
||||
"""Remove falsy values from a list."""
|
||||
return [v for v in value if v]
|
||||
@ -690,6 +694,14 @@ def key_dependency(key, dependency):
|
||||
return validator
|
||||
|
||||
|
||||
def custom_serializer(schema):
|
||||
"""Serialize additional types for voluptuous_serialize."""
|
||||
if schema is positive_time_period_dict:
|
||||
return {"type": "positive_time_period_dict"}
|
||||
|
||||
return voluptuous_serialize.UNSUPPORTED
|
||||
|
||||
|
||||
# Schemas
|
||||
PLATFORM_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@ -10,7 +10,12 @@ import voluptuous as vol
|
||||
|
||||
import homeassistant.components.device_automation as device_automation
|
||||
from homeassistant.core import HomeAssistant, Context, callback, CALLBACK_TYPE
|
||||
from homeassistant.const import CONF_CONDITION, CONF_DEVICE_ID, CONF_TIMEOUT
|
||||
from homeassistant.const import (
|
||||
CONF_CONDITION,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
CONF_TIMEOUT,
|
||||
)
|
||||
from homeassistant import exceptions
|
||||
from homeassistant.helpers import (
|
||||
service,
|
||||
@ -89,7 +94,7 @@ async def async_validate_action_config(
|
||||
|
||||
if action_type == ACTION_DEVICE_AUTOMATION:
|
||||
platform = await device_automation.async_get_device_automation_platform(
|
||||
hass, config, "action"
|
||||
hass, config[CONF_DOMAIN], "action"
|
||||
)
|
||||
config = platform.ACTION_SCHEMA(config)
|
||||
|
||||
@ -346,7 +351,7 @@ class Script:
|
||||
self.last_action = action.get(CONF_ALIAS, "device automation")
|
||||
self._log("Executing step %s" % self.last_action)
|
||||
platform = await device_automation.async_get_device_automation_platform(
|
||||
self.hass, action, "action"
|
||||
self.hass, action[CONF_DOMAIN], "action"
|
||||
)
|
||||
await platform.async_call_action_from_config(
|
||||
self.hass, action, variables, context
|
||||
|
@ -22,7 +22,7 @@ pyyaml==5.1.2
|
||||
requests==2.22.0
|
||||
ruamel.yaml==0.15.100
|
||||
sqlalchemy==1.3.8
|
||||
voluptuous-serialize==2.2.0
|
||||
voluptuous-serialize==2.3.0
|
||||
voluptuous==0.11.7
|
||||
zeroconf==0.23.0
|
||||
|
||||
|
@ -17,7 +17,7 @@ pyyaml==5.1.2
|
||||
requests==2.22.0
|
||||
ruamel.yaml==0.15.100
|
||||
voluptuous==0.11.7
|
||||
voluptuous-serialize==2.2.0
|
||||
voluptuous-serialize==2.3.0
|
||||
|
||||
# homeassistant.components.nuimo_controller
|
||||
--only-binary=all nuimo==0.1.0
|
||||
|
2
setup.py
2
setup.py
@ -50,7 +50,7 @@ REQUIRES = [
|
||||
"requests==2.22.0",
|
||||
"ruamel.yaml==0.15.100",
|
||||
"voluptuous==0.11.7",
|
||||
"voluptuous-serialize==2.2.0",
|
||||
"voluptuous-serialize==2.3.0",
|
||||
]
|
||||
|
||||
MIN_PY_VERSION = ".".join(map(str, hass_const.REQUIRED_PYTHON_VER))
|
||||
|
@ -56,6 +56,7 @@ from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
from homeassistant.util.async_ import run_callback_threadsafe
|
||||
from homeassistant.components.device_automation import ( # noqa
|
||||
_async_get_device_automations as async_get_device_automations,
|
||||
_async_get_device_automation_capabilities as async_get_device_automation_capabilities,
|
||||
)
|
||||
|
||||
_TEST_INSTANCE_PORT = SERVER_PORT
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""The test for binary_sensor device automation."""
|
||||
from datetime import timedelta
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN, DEVICE_CLASSES
|
||||
@ -7,13 +8,16 @@ from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.components.automation as automation
|
||||
from homeassistant.helpers import device_registry
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
async_fire_time_changed,
|
||||
async_mock_service,
|
||||
mock_device_registry,
|
||||
mock_registry,
|
||||
async_get_device_automations,
|
||||
async_get_device_automation_capabilities,
|
||||
)
|
||||
|
||||
|
||||
@ -71,6 +75,28 @@ async def test_get_triggers(hass, device_reg, entity_reg):
|
||||
assert triggers == expected_triggers
|
||||
|
||||
|
||||
async def test_get_trigger_capabilities(hass, device_reg, entity_reg):
|
||||
"""Test we get the expected capabilities from a binary_sensor trigger."""
|
||||
config_entry = MockConfigEntry(domain="test", data={})
|
||||
config_entry.add_to_hass(hass)
|
||||
device_entry = device_reg.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
|
||||
expected_capabilities = {
|
||||
"extra_fields": [
|
||||
{"name": "for", "optional": True, "type": "positive_time_period_dict"}
|
||||
]
|
||||
}
|
||||
triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
|
||||
for trigger in triggers:
|
||||
capabilities = await async_get_device_automation_capabilities(
|
||||
hass, "trigger", trigger
|
||||
)
|
||||
assert capabilities == expected_capabilities
|
||||
|
||||
|
||||
async def test_if_fires_on_state_change(hass, calls):
|
||||
"""Test for on and off triggers firing."""
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
@ -152,3 +178,61 @@ async def test_if_fires_on_state_change(hass, calls):
|
||||
assert calls[1].data["some"] == "bat_low device - {} - off - on - None".format(
|
||||
sensor1.entity_id
|
||||
)
|
||||
|
||||
|
||||
async def test_if_fires_on_state_change_with_for(hass, calls):
|
||||
"""Test for triggers firing with delay."""
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
|
||||
platform.init()
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||
|
||||
sensor1 = platform.ENTITIES["battery"]
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "device",
|
||||
"domain": DOMAIN,
|
||||
"device_id": "",
|
||||
"entity_id": sensor1.entity_id,
|
||||
"type": "turned_off",
|
||||
"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(sensor1.entity_id).state == STATE_ON
|
||||
assert len(calls) == 0
|
||||
|
||||
hass.states.async_set(sensor1.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(
|
||||
sensor1.entity_id
|
||||
)
|
||||
|
@ -164,6 +164,103 @@ async def test_websocket_get_triggers(hass, hass_ws_client, device_reg, entity_r
|
||||
assert _same_lists(triggers, expected_triggers)
|
||||
|
||||
|
||||
async def test_websocket_get_trigger_capabilities(
|
||||
hass, hass_ws_client, device_reg, entity_reg
|
||||
):
|
||||
"""Test we get the expected trigger capabilities for a light through websocket."""
|
||||
await async_setup_component(hass, "device_automation", {})
|
||||
config_entry = MockConfigEntry(domain="test", data={})
|
||||
config_entry.add_to_hass(hass)
|
||||
device_entry = device_reg.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
entity_reg.async_get_or_create("light", "test", "5678", device_id=device_entry.id)
|
||||
expected_capabilities = {
|
||||
"extra_fields": [
|
||||
{"name": "for", "optional": True, "type": "positive_time_period_dict"}
|
||||
]
|
||||
}
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": "device_automation/trigger/list",
|
||||
"device_id": device_entry.id,
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg["id"] == 1
|
||||
assert msg["type"] == TYPE_RESULT
|
||||
assert msg["success"]
|
||||
triggers = msg["result"]
|
||||
|
||||
id = 2
|
||||
for trigger in triggers:
|
||||
await client.send_json(
|
||||
{
|
||||
"id": id,
|
||||
"type": "device_automation/trigger/capabilities",
|
||||
"trigger": trigger,
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["id"] == id
|
||||
assert msg["type"] == TYPE_RESULT
|
||||
assert msg["success"]
|
||||
capabilities = msg["result"]
|
||||
assert capabilities == expected_capabilities
|
||||
id = id + 1
|
||||
|
||||
|
||||
async def test_websocket_get_bad_trigger_capabilities(
|
||||
hass, hass_ws_client, device_reg, entity_reg
|
||||
):
|
||||
"""Test we get no trigger capabilities for a non existing domain."""
|
||||
await async_setup_component(hass, "device_automation", {})
|
||||
expected_capabilities = {}
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": "device_automation/trigger/capabilities",
|
||||
"trigger": {"domain": "beer"},
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["id"] == 1
|
||||
assert msg["type"] == TYPE_RESULT
|
||||
assert msg["success"]
|
||||
capabilities = msg["result"]
|
||||
assert capabilities == expected_capabilities
|
||||
|
||||
|
||||
async def test_websocket_get_no_trigger_capabilities(
|
||||
hass, hass_ws_client, device_reg, entity_reg
|
||||
):
|
||||
"""Test we get no trigger capabilities for a domain with no device trigger capabilities."""
|
||||
await async_setup_component(hass, "device_automation", {})
|
||||
expected_capabilities = {}
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": "device_automation/trigger/capabilities",
|
||||
"trigger": {"domain": "deconz"},
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["id"] == 1
|
||||
assert msg["type"] == TYPE_RESULT
|
||||
assert msg["success"]
|
||||
capabilities = msg["result"]
|
||||
assert capabilities == expected_capabilities
|
||||
|
||||
|
||||
async def test_automation_with_non_existing_integration(hass, caplog):
|
||||
"""Test device automation with non existing integration."""
|
||||
assert await async_setup_component(
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""The test for light device automation."""
|
||||
from datetime import timedelta
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.light import DOMAIN
|
||||
@ -6,13 +7,16 @@ from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.components.automation as automation
|
||||
from homeassistant.helpers import device_registry
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
async_fire_time_changed,
|
||||
async_mock_service,
|
||||
mock_device_registry,
|
||||
mock_registry,
|
||||
async_get_device_automations,
|
||||
async_get_device_automation_capabilities,
|
||||
)
|
||||
|
||||
|
||||
@ -63,6 +67,28 @@ async def test_get_triggers(hass, device_reg, entity_reg):
|
||||
assert triggers == expected_triggers
|
||||
|
||||
|
||||
async def test_get_trigger_capabilities(hass, device_reg, entity_reg):
|
||||
"""Test we get the expected capabilities from a light trigger."""
|
||||
config_entry = MockConfigEntry(domain="test", data={})
|
||||
config_entry.add_to_hass(hass)
|
||||
device_entry = device_reg.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
|
||||
expected_capabilities = {
|
||||
"extra_fields": [
|
||||
{"name": "for", "optional": True, "type": "positive_time_period_dict"}
|
||||
]
|
||||
}
|
||||
triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
|
||||
for trigger in triggers:
|
||||
capabilities = await async_get_device_automation_capabilities(
|
||||
hass, "trigger", trigger
|
||||
)
|
||||
assert capabilities == expected_capabilities
|
||||
|
||||
|
||||
async def test_if_fires_on_state_change(hass, calls):
|
||||
"""Test for turn_on and turn_off triggers firing."""
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
@ -145,3 +171,61 @@ async def test_if_fires_on_state_change(hass, calls):
|
||||
assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format(
|
||||
ent1.entity_id
|
||||
)
|
||||
|
||||
|
||||
async def test_if_fires_on_state_change_with_for(hass, calls):
|
||||
"""Test for triggers firing with delay."""
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
|
||||
platform.init()
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||
|
||||
ent1, ent2, ent3 = platform.ENTITIES
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "device",
|
||||
"domain": DOMAIN,
|
||||
"device_id": "",
|
||||
"entity_id": ent1.entity_id,
|
||||
"type": "turned_off",
|
||||
"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
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""The test for switch device automation."""
|
||||
from datetime import timedelta
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.switch import DOMAIN
|
||||
@ -6,13 +7,16 @@ from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.components.automation as automation
|
||||
from homeassistant.helpers import device_registry
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
async_fire_time_changed,
|
||||
async_mock_service,
|
||||
mock_device_registry,
|
||||
mock_registry,
|
||||
async_get_device_automations,
|
||||
async_get_device_automation_capabilities,
|
||||
)
|
||||
|
||||
|
||||
@ -63,6 +67,28 @@ async def test_get_triggers(hass, device_reg, entity_reg):
|
||||
assert triggers == expected_triggers
|
||||
|
||||
|
||||
async def test_get_trigger_capabilities(hass, device_reg, entity_reg):
|
||||
"""Test we get the expected capabilities from a switch trigger."""
|
||||
config_entry = MockConfigEntry(domain="test", data={})
|
||||
config_entry.add_to_hass(hass)
|
||||
device_entry = device_reg.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
|
||||
expected_capabilities = {
|
||||
"extra_fields": [
|
||||
{"name": "for", "optional": True, "type": "positive_time_period_dict"}
|
||||
]
|
||||
}
|
||||
triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
|
||||
for trigger in triggers:
|
||||
capabilities = await async_get_device_automation_capabilities(
|
||||
hass, "trigger", trigger
|
||||
)
|
||||
assert capabilities == expected_capabilities
|
||||
|
||||
|
||||
async def test_if_fires_on_state_change(hass, calls):
|
||||
"""Test for turn_on and turn_off triggers firing."""
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
@ -145,3 +171,61 @@ async def test_if_fires_on_state_change(hass, calls):
|
||||
assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format(
|
||||
ent1.entity_id
|
||||
)
|
||||
|
||||
|
||||
async def test_if_fires_on_state_change_with_for(hass, calls):
|
||||
"""Test for triggers firing with delay."""
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
|
||||
platform.init()
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||
|
||||
ent1, ent2, ent3 = platform.ENTITIES
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "device",
|
||||
"domain": DOMAIN,
|
||||
"device_id": "",
|
||||
"entity_id": ent1.entity_id,
|
||||
"type": "turned_off",
|
||||
"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
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user