mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +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,
|
TRIGGER_BASE_SCHEMA,
|
||||||
async_get_device_automation_platform,
|
async_get_device_automation_platform,
|
||||||
)
|
)
|
||||||
|
from homeassistant.const import CONF_DOMAIN
|
||||||
|
|
||||||
|
|
||||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
# 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):
|
async def async_validate_trigger_config(hass, config):
|
||||||
"""Validate 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)
|
return platform.TRIGGER_SCHEMA(config)
|
||||||
|
|
||||||
|
|
||||||
async def async_attach_trigger(hass, config, action, automation_info):
|
async def async_attach_trigger(hass, config, action, automation_info):
|
||||||
"""Listen for trigger."""
|
"""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)
|
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_OFF,
|
||||||
CONF_TURNED_ON,
|
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.entity_registry import async_entries_for_device
|
||||||
from homeassistant.helpers import config_validation as cv
|
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_ENTITY_ID): cv.entity_id,
|
||||||
vol.Required(CONF_TYPE): vol.In(TURNED_OFF + TURNED_ON),
|
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):
|
async def async_attach_trigger(hass, config, action, automation_info):
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
config = TRIGGER_SCHEMA(config)
|
|
||||||
trigger_type = config[CONF_TYPE]
|
trigger_type = config[CONF_TYPE]
|
||||||
if trigger_type in TURNED_ON:
|
if trigger_type in TURNED_ON:
|
||||||
from_state = "off"
|
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_FROM: from_state,
|
||||||
state_automation.CONF_TO: to_state,
|
state_automation.CONF_TO: to_state,
|
||||||
}
|
}
|
||||||
|
if "for" in config:
|
||||||
|
state_config["for"] = config["for"]
|
||||||
|
|
||||||
return await state_automation.async_attach_trigger(
|
return await state_automation.async_attach_trigger(
|
||||||
hass, state_config, action, automation_info, platform_type="device"
|
hass, state_config, action, automation_info, platform_type="device"
|
||||||
@ -236,3 +238,12 @@ async def async_get_triggers(hass, device_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return triggers
|
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):
|
async def async_attach_trigger(hass, config, action, automation_info):
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
config = TRIGGER_SCHEMA(config)
|
|
||||||
|
|
||||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||||
device = device_registry.async_get(config[CONF_DEVICE_ID])
|
device = device_registry.async_get(config[CONF_DEVICE_ID])
|
||||||
|
|
||||||
|
@ -4,9 +4,11 @@ import logging
|
|||||||
from typing import Any, List, MutableMapping
|
from typing import Any, List, MutableMapping
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
import voluptuous_serialize
|
||||||
|
|
||||||
from homeassistant.const import CONF_PLATFORM, CONF_DOMAIN, CONF_DEVICE_ID
|
from homeassistant.const import CONF_PLATFORM, CONF_DOMAIN, CONF_DEVICE_ID
|
||||||
from homeassistant.components import websocket_api
|
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.helpers.entity_registry import async_entries_for_device
|
||||||
from homeassistant.loader import async_get_integration, IntegrationNotFound
|
from homeassistant.loader import async_get_integration, IntegrationNotFound
|
||||||
|
|
||||||
@ -29,9 +31,18 @@ TRIGGER_BASE_SCHEMA = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
TYPES = {
|
TYPES = {
|
||||||
"trigger": ("device_trigger", "async_get_triggers"),
|
# platform name, get automations function, get capabilities function
|
||||||
"condition": ("device_condition", "async_get_conditions"),
|
"trigger": (
|
||||||
"action": ("device_action", "async_get_actions"),
|
"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(
|
hass.components.websocket_api.async_register_command(
|
||||||
websocket_device_automation_list_triggers
|
websocket_device_automation_list_triggers
|
||||||
)
|
)
|
||||||
|
hass.components.websocket_api.async_register_command(
|
||||||
|
websocket_device_automation_get_trigger_capabilities
|
||||||
|
)
|
||||||
return True
|
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.
|
"""Load device automation platform for integration.
|
||||||
|
|
||||||
Throws InvalidDeviceAutomationConfig if the integration is not found or does not support device automation.
|
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:
|
try:
|
||||||
integration = await async_get_integration(hass, config[CONF_DOMAIN])
|
integration = await async_get_integration(hass, domain)
|
||||||
platform = integration.get_platform(platform_name)
|
platform = integration.get_platform(platform_name)
|
||||||
except IntegrationNotFound:
|
except IntegrationNotFound:
|
||||||
raise InvalidDeviceAutomationConfig(
|
raise InvalidDeviceAutomationConfig(f"Integration '{domain}' not found")
|
||||||
f"Integration '{config[CONF_DOMAIN]}' not found"
|
|
||||||
)
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise InvalidDeviceAutomationConfig(
|
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
|
return platform
|
||||||
@ -74,20 +86,14 @@ async def _async_get_device_automations_from_domain(
|
|||||||
hass, domain, automation_type, device_id
|
hass, domain, automation_type, device_id
|
||||||
):
|
):
|
||||||
"""List device automations."""
|
"""List device automations."""
|
||||||
integration = None
|
|
||||||
try:
|
try:
|
||||||
integration = await async_get_integration(hass, domain)
|
platform = await async_get_device_automation_platform(
|
||||||
except IntegrationNotFound:
|
hass, domain, automation_type
|
||||||
_LOGGER.warning("Integration %s not found", domain)
|
)
|
||||||
|
except InvalidDeviceAutomationConfig:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
platform_name, function_name = TYPES[automation_type]
|
function_name = TYPES[automation_type][1]
|
||||||
|
|
||||||
try:
|
|
||||||
platform = integration.get_platform(platform_name)
|
|
||||||
except ImportError:
|
|
||||||
# The domain does not have device automations
|
|
||||||
return None
|
|
||||||
|
|
||||||
return await getattr(platform, function_name)(hass, device_id)
|
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
|
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.async_response
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
@ -165,3 +200,19 @@ async def websocket_device_automation_list_triggers(hass, connection, msg):
|
|||||||
device_id = msg["device_id"]
|
device_id = msg["device_id"]
|
||||||
triggers = await _async_get_device_automations(hass, "trigger", device_id)
|
triggers = await _async_get_device_automations(hass, "trigger", device_id)
|
||||||
connection.send_result(msg["id"], triggers)
|
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_OFF,
|
||||||
CONF_TURNED_ON,
|
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.entity_registry import async_entries_for_device
|
||||||
from homeassistant.helpers import condition, config_validation as cv, service
|
from homeassistant.helpers import condition, config_validation as cv, service
|
||||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
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_ENTITY_ID): cv.entity_id,
|
||||||
vol.Required(CONF_TYPE): vol.In([CONF_TURNED_OFF, CONF_TURNED_ON]),
|
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,
|
domain: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Change state based on configuration."""
|
"""Change state based on configuration."""
|
||||||
config = ACTION_SCHEMA(config)
|
|
||||||
action_type = config[CONF_TYPE]
|
action_type = config[CONF_TYPE]
|
||||||
if action_type == CONF_TURN_ON:
|
if action_type == CONF_TURN_ON:
|
||||||
action = "turn_on"
|
action = "turn_on"
|
||||||
@ -149,6 +155,8 @@ async def async_attach_trigger(
|
|||||||
state.CONF_FROM: from_state,
|
state.CONF_FROM: from_state,
|
||||||
state.CONF_TO: to_state,
|
state.CONF_TO: to_state,
|
||||||
}
|
}
|
||||||
|
if "for" in config:
|
||||||
|
state_config["for"] = config["for"]
|
||||||
|
|
||||||
return await state.async_attach_trigger(
|
return await state.async_attach_trigger(
|
||||||
hass, state_config, action, automation_info, platform_type="device"
|
hass, state_config, action, automation_info, platform_type="device"
|
||||||
@ -203,3 +211,12 @@ async def async_get_triggers(
|
|||||||
) -> List[dict]:
|
) -> List[dict]:
|
||||||
"""List device triggers."""
|
"""List device triggers."""
|
||||||
return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain)
|
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,
|
context: Context,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Change state based on configuration."""
|
"""Change state based on configuration."""
|
||||||
config = ACTION_SCHEMA(config)
|
|
||||||
await toggle_entity.async_call_action_from_config(
|
await toggle_entity.async_call_action_from_config(
|
||||||
hass, config, variables, context, DOMAIN
|
hass, config, variables, context, DOMAIN
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,6 @@ async def async_attach_trigger(
|
|||||||
automation_info: dict,
|
automation_info: dict,
|
||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
config = TRIGGER_SCHEMA(config)
|
|
||||||
return await toggle_entity.async_attach_trigger(
|
return await toggle_entity.async_attach_trigger(
|
||||||
hass, config, action, automation_info
|
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]:
|
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]:
|
||||||
"""List device triggers."""
|
"""List device triggers."""
|
||||||
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)
|
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,
|
context: Context,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Change state based on configuration."""
|
"""Change state based on configuration."""
|
||||||
config = ACTION_SCHEMA(config)
|
|
||||||
await toggle_entity.async_call_action_from_config(
|
await toggle_entity.async_call_action_from_config(
|
||||||
hass, config, variables, context, DOMAIN
|
hass, config, variables, context, DOMAIN
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,6 @@ async def async_attach_trigger(
|
|||||||
automation_info: dict,
|
automation_info: dict,
|
||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
config = TRIGGER_SCHEMA(config)
|
|
||||||
return await toggle_entity.async_attach_trigger(
|
return await toggle_entity.async_attach_trigger(
|
||||||
hass, config, action, automation_info
|
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]:
|
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]:
|
||||||
"""List device triggers."""
|
"""List device triggers."""
|
||||||
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)
|
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,
|
context: Context,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Perform an action based on configuration."""
|
"""Perform an action based on configuration."""
|
||||||
config = ACTION_SCHEMA(config)
|
|
||||||
await ZHA_ACTION_TYPES[DEVICE_ACTION_TYPES[config[CONF_TYPE]]](
|
await ZHA_ACTION_TYPES[DEVICE_ACTION_TYPES[config[CONF_TYPE]]](
|
||||||
hass, config, variables, context
|
hass, config, variables, context
|
||||||
)
|
)
|
||||||
|
@ -23,7 +23,6 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
|
|||||||
|
|
||||||
async def async_attach_trigger(hass, config, action, automation_info):
|
async def async_attach_trigger(hass, config, action, automation_info):
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
config = TRIGGER_SCHEMA(config)
|
|
||||||
trigger = (config[CONF_TYPE], config[CONF_SUBTYPE])
|
trigger = (config[CONF_TYPE], config[CONF_SUBTYPE])
|
||||||
zha_device = await async_get_zha_device(hass, config[CONF_DEVICE_ID])
|
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 urllib.parse import urlparse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
from pkg_resources import parse_version
|
from pkg_resources import parse_version
|
||||||
|
import voluptuous as vol
|
||||||
|
import voluptuous_serialize
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -374,6 +375,9 @@ def positive_timedelta(value: timedelta) -> timedelta:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
positive_time_period_dict = vol.All(time_period_dict, positive_timedelta)
|
||||||
|
|
||||||
|
|
||||||
def remove_falsy(value: List[T]) -> List[T]:
|
def remove_falsy(value: List[T]) -> List[T]:
|
||||||
"""Remove falsy values from a list."""
|
"""Remove falsy values from a list."""
|
||||||
return [v for v in value if v]
|
return [v for v in value if v]
|
||||||
@ -690,6 +694,14 @@ def key_dependency(key, dependency):
|
|||||||
return validator
|
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
|
# Schemas
|
||||||
PLATFORM_SCHEMA = vol.Schema(
|
PLATFORM_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,12 @@ import voluptuous as vol
|
|||||||
|
|
||||||
import homeassistant.components.device_automation as device_automation
|
import homeassistant.components.device_automation as device_automation
|
||||||
from homeassistant.core import HomeAssistant, Context, callback, CALLBACK_TYPE
|
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 import exceptions
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
service,
|
service,
|
||||||
@ -89,7 +94,7 @@ async def async_validate_action_config(
|
|||||||
|
|
||||||
if action_type == ACTION_DEVICE_AUTOMATION:
|
if action_type == ACTION_DEVICE_AUTOMATION:
|
||||||
platform = await device_automation.async_get_device_automation_platform(
|
platform = await device_automation.async_get_device_automation_platform(
|
||||||
hass, config, "action"
|
hass, config[CONF_DOMAIN], "action"
|
||||||
)
|
)
|
||||||
config = platform.ACTION_SCHEMA(config)
|
config = platform.ACTION_SCHEMA(config)
|
||||||
|
|
||||||
@ -346,7 +351,7 @@ class Script:
|
|||||||
self.last_action = action.get(CONF_ALIAS, "device automation")
|
self.last_action = action.get(CONF_ALIAS, "device automation")
|
||||||
self._log("Executing step %s" % self.last_action)
|
self._log("Executing step %s" % self.last_action)
|
||||||
platform = await device_automation.async_get_device_automation_platform(
|
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(
|
await platform.async_call_action_from_config(
|
||||||
self.hass, action, variables, context
|
self.hass, action, variables, context
|
||||||
|
@ -22,7 +22,7 @@ pyyaml==5.1.2
|
|||||||
requests==2.22.0
|
requests==2.22.0
|
||||||
ruamel.yaml==0.15.100
|
ruamel.yaml==0.15.100
|
||||||
sqlalchemy==1.3.8
|
sqlalchemy==1.3.8
|
||||||
voluptuous-serialize==2.2.0
|
voluptuous-serialize==2.3.0
|
||||||
voluptuous==0.11.7
|
voluptuous==0.11.7
|
||||||
zeroconf==0.23.0
|
zeroconf==0.23.0
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ pyyaml==5.1.2
|
|||||||
requests==2.22.0
|
requests==2.22.0
|
||||||
ruamel.yaml==0.15.100
|
ruamel.yaml==0.15.100
|
||||||
voluptuous==0.11.7
|
voluptuous==0.11.7
|
||||||
voluptuous-serialize==2.2.0
|
voluptuous-serialize==2.3.0
|
||||||
|
|
||||||
# homeassistant.components.nuimo_controller
|
# homeassistant.components.nuimo_controller
|
||||||
--only-binary=all nuimo==0.1.0
|
--only-binary=all nuimo==0.1.0
|
||||||
|
2
setup.py
2
setup.py
@ -50,7 +50,7 @@ REQUIRES = [
|
|||||||
"requests==2.22.0",
|
"requests==2.22.0",
|
||||||
"ruamel.yaml==0.15.100",
|
"ruamel.yaml==0.15.100",
|
||||||
"voluptuous==0.11.7",
|
"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))
|
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.util.async_ import run_callback_threadsafe
|
||||||
from homeassistant.components.device_automation import ( # noqa
|
from homeassistant.components.device_automation import ( # noqa
|
||||||
_async_get_device_automations as async_get_device_automations,
|
_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
|
_TEST_INSTANCE_PORT = SERVER_PORT
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""The test for binary_sensor device automation."""
|
"""The test for binary_sensor device automation."""
|
||||||
|
from datetime import timedelta
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import DOMAIN, DEVICE_CLASSES
|
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
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
from homeassistant.helpers import device_registry
|
from homeassistant.helpers import device_registry
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
|
async_fire_time_changed,
|
||||||
async_mock_service,
|
async_mock_service,
|
||||||
mock_device_registry,
|
mock_device_registry,
|
||||||
mock_registry,
|
mock_registry,
|
||||||
async_get_device_automations,
|
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
|
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):
|
async def test_if_fires_on_state_change(hass, calls):
|
||||||
"""Test for on and off triggers firing."""
|
"""Test for on and off triggers firing."""
|
||||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
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(
|
assert calls[1].data["some"] == "bat_low device - {} - off - on - None".format(
|
||||||
sensor1.entity_id
|
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)
|
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):
|
async def test_automation_with_non_existing_integration(hass, caplog):
|
||||||
"""Test device automation with non existing integration."""
|
"""Test device automation with non existing integration."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""The test for light device automation."""
|
"""The test for light device automation."""
|
||||||
|
from datetime import timedelta
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.light import DOMAIN
|
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
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
from homeassistant.helpers import device_registry
|
from homeassistant.helpers import device_registry
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
|
async_fire_time_changed,
|
||||||
async_mock_service,
|
async_mock_service,
|
||||||
mock_device_registry,
|
mock_device_registry,
|
||||||
mock_registry,
|
mock_registry,
|
||||||
async_get_device_automations,
|
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
|
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):
|
async def test_if_fires_on_state_change(hass, calls):
|
||||||
"""Test for turn_on and turn_off triggers firing."""
|
"""Test for turn_on and turn_off triggers firing."""
|
||||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
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(
|
assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format(
|
||||||
ent1.entity_id
|
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."""
|
"""The test for switch device automation."""
|
||||||
|
from datetime import timedelta
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.switch import DOMAIN
|
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
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
from homeassistant.helpers import device_registry
|
from homeassistant.helpers import device_registry
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
|
async_fire_time_changed,
|
||||||
async_mock_service,
|
async_mock_service,
|
||||||
mock_device_registry,
|
mock_device_registry,
|
||||||
mock_registry,
|
mock_registry,
|
||||||
async_get_device_automations,
|
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
|
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):
|
async def test_if_fires_on_state_change(hass, calls):
|
||||||
"""Test for turn_on and turn_off triggers firing."""
|
"""Test for turn_on and turn_off triggers firing."""
|
||||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
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(
|
assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format(
|
||||||
ent1.entity_id
|
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