1
mirror of https://github.com/home-assistant/core synced 2024-09-25 00:41:32 +02:00

Reorganize trigger code (#38655)

This commit is contained in:
Phil Bruckner 2020-08-17 11:54:56 -05:00 committed by GitHub
parent fca071742d
commit ca9dd0c833
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 306 additions and 194 deletions

View File

@ -8,8 +8,9 @@ from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
)
from homeassistant.components.automation import AutomationActionType, state
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
@ -151,13 +152,13 @@ async def async_attach_trigger(
to_state = STATE_ALARM_ARMED_NIGHT
state_config = {
state.CONF_PLATFORM: "state",
state_trigger.CONF_PLATFORM: "state",
CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state.CONF_TO: to_state,
state_trigger.CONF_TO: to_state,
}
if from_state:
state_config[state.CONF_FROM] = from_state
state_config = state.TRIGGER_SCHEMA(state_config)
return await state.async_attach_trigger(
state_config[state_trigger.CONF_FROM] = from_state
state_config = state_trigger.TRIGGER_SCHEMA(state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)

View File

@ -1,8 +1,6 @@
"""Allow to set up simple automation rules via the config file."""
import asyncio
import importlib
import logging
from typing import Any, Awaitable, Callable, List, Optional, Set
from typing import Any, Awaitable, Callable, List, Optional, Set, cast
import voluptuous as vol
@ -46,6 +44,7 @@ from homeassistant.helpers.script import (
make_script_schema,
)
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.trigger import async_initialize_triggers
from homeassistant.helpers.typing import TemplateVarsType
from homeassistant.loader import bind_hass
from homeassistant.util.dt import parse_datetime, utcnow
@ -90,26 +89,6 @@ _LOGGER = logging.getLogger(__name__)
AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]]
def _platform_validator(config):
"""Validate it is a valid platform."""
try:
platform = importlib.import_module(f".{config[CONF_PLATFORM]}", __name__)
except ImportError:
raise vol.Invalid("Invalid platform specified") from None
return platform.TRIGGER_SCHEMA(config)
_TRIGGER_SCHEMA = vol.All(
cv.ensure_list,
[
vol.All(
vol.Schema({vol.Required(CONF_PLATFORM): str}, extra=vol.ALLOW_EXTRA),
_platform_validator,
)
],
)
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
PLATFORM_SCHEMA = vol.All(
@ -122,7 +101,7 @@ PLATFORM_SCHEMA = vol.All(
vol.Optional(CONF_DESCRIPTION): cv.string,
vol.Optional(CONF_INITIAL_STATE): cv.boolean,
vol.Optional(CONF_HIDE_ENTITY): cv.boolean,
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
vol.Required(CONF_TRIGGER): cv.TRIGGER_SCHEMA,
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
},
@ -485,36 +464,19 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
self, home_assistant_start: bool
) -> Optional[Callable[[], None]]:
"""Set up the triggers."""
info = {"name": self._name, "home_assistant_start": home_assistant_start}
triggers = []
for conf in self._trigger_config:
platform = importlib.import_module(f".{conf[CONF_PLATFORM]}", __name__)
def log_cb(level, msg):
self._logger.log(level, "%s %s", msg, self._name)
triggers.append(
platform.async_attach_trigger( # type: ignore
self.hass, conf, self.async_trigger, info
)
)
results = await asyncio.gather(*triggers)
if None in results:
self._logger.error("Error setting up trigger %s", self._name)
removes = [remove for remove in results if remove is not None]
if not removes:
return None
self._logger.info("Initialized trigger %s", self._name)
@callback
def remove_triggers():
"""Remove attached triggers."""
for remove in removes:
remove()
return remove_triggers
return await async_initialize_triggers(
cast(HomeAssistant, self.hass),
self._trigger_config,
self.async_trigger,
DOMAIN,
self._name,
log_cb,
home_assistant_start,
)
@property
def device_state_attributes(self):

View File

@ -1,6 +1,5 @@
"""Config validation helper for the automation integration."""
import asyncio
import importlib
import voluptuous as vol
@ -8,10 +7,11 @@ from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig,
)
from homeassistant.config import async_log_exception, config_without_domain
from homeassistant.const import CONF_PLATFORM
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import condition, config_per_platform
from homeassistant.helpers import config_per_platform
from homeassistant.helpers.condition import async_validate_condition_config
from homeassistant.helpers.script import async_validate_action_config
from homeassistant.helpers.trigger import async_validate_trigger_config
from homeassistant.loader import IntegrationNotFound
from . import CONF_ACTION, CONF_CONDITION, CONF_TRIGGER, DOMAIN, PLATFORM_SCHEMA
@ -24,22 +24,14 @@ async def async_validate_config_item(hass, config, full_config=None):
"""Validate config item."""
config = PLATFORM_SCHEMA(config)
triggers = []
for trigger in config[CONF_TRIGGER]:
trigger_platform = importlib.import_module(
f"..{trigger[CONF_PLATFORM]}", __name__
)
if hasattr(trigger_platform, "async_validate_trigger_config"):
trigger = await trigger_platform.async_validate_trigger_config(
hass, trigger
)
triggers.append(trigger)
config[CONF_TRIGGER] = triggers
config[CONF_TRIGGER] = await async_validate_trigger_config(
hass, config[CONF_TRIGGER]
)
if CONF_CONDITION in config:
config[CONF_CONDITION] = await asyncio.gather(
*[
condition.async_validate_condition_config(hass, cond)
async_validate_condition_config(hass, cond)
for cond in config[CONF_CONDITION]
]
)

View File

@ -1,12 +1,12 @@
"""Provides device triggers for binary sensors."""
import voluptuous as vol
from homeassistant.components.automation import state as state_automation
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.device_automation.const import (
CONF_TURNED_OFF,
CONF_TURNED_ON,
)
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FOR, CONF_TYPE
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_registry import async_entries_for_device
@ -197,16 +197,16 @@ async def async_attach_trigger(hass, config, action, automation_info):
to_state = "off"
state_config = {
state_automation.CONF_PLATFORM: "state",
state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_automation.CONF_FROM: from_state,
state_automation.CONF_TO: to_state,
state_trigger.CONF_PLATFORM: "state",
state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_trigger.CONF_FROM: from_state,
state_trigger.CONF_TO: to_state,
}
if CONF_FOR in config:
state_config[CONF_FOR] = config[CONF_FOR]
state_config = state_automation.TRIGGER_SCHEMA(state_config)
return await state_automation.async_attach_trigger(
state_config = state_trigger.TRIGGER_SCHEMA(state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)

View File

@ -3,12 +3,12 @@ from typing import List
import voluptuous as vol
from homeassistant.components.automation import (
AutomationActionType,
numeric_state as numeric_state_automation,
state as state_automation,
)
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import (
numeric_state as numeric_state_trigger,
state as state_trigger,
)
from homeassistant.const import (
CONF_ABOVE,
CONF_BELOW,
@ -36,7 +36,7 @@ HVAC_MODE_TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): "hvac_mode_changed",
vol.Required(state_automation.CONF_TO): vol.In(const.HVAC_MODES),
vol.Required(state_trigger.CONF_TO): vol.In(const.HVAC_MODES),
}
)
@ -118,34 +118,34 @@ async def async_attach_trigger(
if trigger_type == "hvac_mode_changed":
state_config = {
state_automation.CONF_PLATFORM: "state",
state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_automation.CONF_TO: config[state_automation.CONF_TO],
state_automation.CONF_FROM: [
state_trigger.CONF_PLATFORM: "state",
state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_trigger.CONF_TO: config[state_trigger.CONF_TO],
state_trigger.CONF_FROM: [
mode
for mode in const.HVAC_MODES
if mode != config[state_automation.CONF_TO]
if mode != config[state_trigger.CONF_TO]
],
}
if CONF_FOR in config:
state_config[CONF_FOR] = config[CONF_FOR]
state_config = state_automation.TRIGGER_SCHEMA(state_config)
return await state_automation.async_attach_trigger(
state_config = state_trigger.TRIGGER_SCHEMA(state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)
numeric_state_config = {
numeric_state_automation.CONF_PLATFORM: "numeric_state",
numeric_state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
numeric_state_trigger.CONF_PLATFORM: "numeric_state",
numeric_state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
}
if trigger_type == "current_temperature_changed":
numeric_state_config[
numeric_state_automation.CONF_VALUE_TEMPLATE
numeric_state_trigger.CONF_VALUE_TEMPLATE
] = "{{ state.attributes.current_temperature }}"
else:
numeric_state_config[
numeric_state_automation.CONF_VALUE_TEMPLATE
numeric_state_trigger.CONF_VALUE_TEMPLATE
] = "{{ state.attributes.current_humidity }}"
if CONF_ABOVE in config:
@ -155,8 +155,8 @@ async def async_attach_trigger(
if CONF_FOR in config:
numeric_state_config[CONF_FOR] = config[CONF_FOR]
numeric_state_config = numeric_state_automation.TRIGGER_SCHEMA(numeric_state_config)
return await numeric_state_automation.async_attach_trigger(
numeric_state_config = numeric_state_trigger.TRIGGER_SCHEMA(numeric_state_config)
return await numeric_state_trigger.async_attach_trigger(
hass, numeric_state_config, action, automation_info, platform_type="device"
)

View File

@ -3,12 +3,12 @@ from typing import List
import voluptuous as vol
from homeassistant.components.automation import (
AutomationActionType,
numeric_state as numeric_state_automation,
state as state_automation,
)
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import (
numeric_state as numeric_state_trigger,
state as state_trigger,
)
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES,
CONF_ABOVE,
@ -18,6 +18,7 @@ from homeassistant.const import (
CONF_ENTITY_ID,
CONF_PLATFORM,
CONF_TYPE,
CONF_VALUE_TEMPLATE,
STATE_CLOSED,
STATE_CLOSING,
STATE_OPEN,
@ -182,12 +183,12 @@ async def async_attach_trigger(
to_state = STATE_CLOSING
state_config = {
state_automation.CONF_PLATFORM: "state",
CONF_PLATFORM: "state",
CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_automation.CONF_TO: to_state,
state_trigger.CONF_TO: to_state,
}
state_config = state_automation.TRIGGER_SCHEMA(state_config)
return await state_automation.async_attach_trigger(
state_config = state_trigger.TRIGGER_SCHEMA(state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)
@ -200,13 +201,13 @@ async def async_attach_trigger(
value_template = f"{{{{ state.attributes.{position} }}}}"
numeric_state_config = {
numeric_state_automation.CONF_PLATFORM: "numeric_state",
numeric_state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
numeric_state_automation.CONF_BELOW: max_pos,
numeric_state_automation.CONF_ABOVE: min_pos,
numeric_state_automation.CONF_VALUE_TEMPLATE: value_template,
CONF_PLATFORM: "numeric_state",
CONF_ENTITY_ID: config[CONF_ENTITY_ID],
CONF_BELOW: max_pos,
CONF_ABOVE: min_pos,
CONF_VALUE_TEMPLATE: value_template,
}
numeric_state_config = numeric_state_automation.TRIGGER_SCHEMA(numeric_state_config)
return await numeric_state_automation.async_attach_trigger(
numeric_state_config = numeric_state_trigger.TRIGGER_SCHEMA(numeric_state_config)
return await numeric_state_trigger.async_attach_trigger(
hass, numeric_state_config, action, automation_info, platform_type="device"
)

View File

@ -1,11 +1,11 @@
"""Provides device automations for deconz events."""
import voluptuous as vol
import homeassistant.components.automation.event as event
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig,
)
from homeassistant.components.homeassistant.triggers import event as event_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
@ -432,13 +432,13 @@ async def async_attach_trigger(hass, config, action, automation_info):
event_id = deconz_event.serial
event_config = {
event.CONF_PLATFORM: "event",
event.CONF_EVENT_TYPE: CONF_DECONZ_EVENT,
event.CONF_EVENT_DATA: {CONF_UNIQUE_ID: event_id, **trigger},
event_trigger.CONF_PLATFORM: "event",
event_trigger.CONF_EVENT_TYPE: CONF_DECONZ_EVENT,
event_trigger.CONF_EVENT_DATA: {CONF_UNIQUE_ID: event_id, **trigger},
}
event_config = event.TRIGGER_SCHEMA(event_config)
return await event.async_attach_trigger(
event_config = event_trigger.TRIGGER_SCHEMA(event_config)
return await event_trigger.async_attach_trigger(
hass, event_config, action, automation_info, platform_type="device"
)

View File

@ -3,10 +3,7 @@ from typing import Any, Dict, List
import voluptuous as vol
from homeassistant.components.automation import (
AutomationActionType,
state as state_automation,
)
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation.const import (
CONF_IS_OFF,
CONF_IS_ON,
@ -16,6 +13,7 @@ from homeassistant.components.device_automation.const import (
CONF_TURNED_OFF,
CONF_TURNED_ON,
)
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_CONDITION,
@ -157,16 +155,16 @@ async def async_attach_trigger(
from_state = "on"
to_state = "off"
state_config = {
state_automation.CONF_PLATFORM: "state",
state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_automation.CONF_FROM: from_state,
state_automation.CONF_TO: to_state,
CONF_PLATFORM: "state",
state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_trigger.CONF_FROM: from_state,
state_trigger.CONF_TO: to_state,
}
if CONF_FOR in config:
state_config[CONF_FOR] = config[CONF_FOR]
state_config = state_automation.TRIGGER_SCHEMA(state_config)
return await state_automation.async_attach_trigger(
state_config = state_trigger.TRIGGER_SCHEMA(state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)

View File

@ -3,8 +3,9 @@ from typing import List
import voluptuous as vol
from homeassistant.components.automation import AutomationActionType, state
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
@ -80,12 +81,12 @@ async def async_attach_trigger(
to_state = STATE_OFF
state_config = {
state.CONF_PLATFORM: "state",
state_trigger.CONF_PLATFORM: "state",
CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state.CONF_FROM: from_state,
state.CONF_TO: to_state,
state_trigger.CONF_FROM: from_state,
state_trigger.CONF_TO: to_state,
}
state_config = state.TRIGGER_SCHEMA(state_config)
return await state.async_attach_trigger(
state_config = state_trigger.TRIGGER_SCHEMA(state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)

View File

@ -0,0 +1,23 @@
"""Home Assistant trigger dispatcher."""
import importlib
from homeassistant.const import CONF_PLATFORM
def _get_trigger_platform(config):
return importlib.import_module(f"..triggers.{config[CONF_PLATFORM]}", __name__)
async def async_validate_trigger_config(hass, config):
"""Validate config."""
platform = _get_trigger_platform(config)
if hasattr(platform, "async_validate_trigger_config"):
return await getattr(platform, "async_validate_trigger_config")(hass, config)
return platform.TRIGGER_SCHEMA(config)
async def async_attach_trigger(hass, config, action, automation_info):
"""Attach trigger of specified platform."""
platform = _get_trigger_platform(config)
return await platform.async_attach_trigger(hass, config, action, automation_info)

View File

@ -0,0 +1 @@
"""Home Assistant triggers."""

View File

@ -3,11 +3,11 @@ import logging
import voluptuous as vol
import homeassistant.components.automation.event as event
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig,
)
from homeassistant.components.homeassistant.triggers import event as event_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
@ -139,13 +139,13 @@ async def async_attach_trigger(hass, config, action, automation_info):
trigger = REMOTES[device.model][trigger]
event_config = {
event.CONF_PLATFORM: "event",
event.CONF_EVENT_TYPE: CONF_HUE_EVENT,
event.CONF_EVENT_DATA: {CONF_UNIQUE_ID: hue_event.unique_id, **trigger},
event_trigger.CONF_PLATFORM: "event",
event_trigger.CONF_EVENT_TYPE: CONF_HUE_EVENT,
event_trigger.CONF_EVENT_DATA: {CONF_UNIQUE_ID: hue_event.unique_id, **trigger},
}
event_config = event.TRIGGER_SCHEMA(event_config)
return await event.async_attach_trigger(
event_config = event_trigger.TRIGGER_SCHEMA(event_config)
return await event_trigger.async_attach_trigger(
hass, event_config, action, automation_info, platform_type="device"
)

View File

@ -3,14 +3,14 @@ from typing import List
import voluptuous as vol
from homeassistant.components.automation import (
AutomationActionType,
numeric_state as numeric_state_automation,
)
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import (
TRIGGER_BASE_SCHEMA,
toggle_entity,
)
from homeassistant.components.homeassistant.triggers import (
numeric_state as numeric_state_trigger,
)
from homeassistant.const import (
CONF_ABOVE,
CONF_BELOW,
@ -81,9 +81,9 @@ async def async_attach_trigger(
if trigger_type == "target_humidity_changed":
numeric_state_config = {
numeric_state_automation.CONF_PLATFORM: "numeric_state",
numeric_state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
numeric_state_automation.CONF_VALUE_TEMPLATE: "{{ state.attributes.humidity }}",
numeric_state_trigger.CONF_PLATFORM: "numeric_state",
numeric_state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
numeric_state_trigger.CONF_VALUE_TEMPLATE: "{{ state.attributes.humidity }}",
}
if CONF_ABOVE in config:
@ -93,10 +93,10 @@ async def async_attach_trigger(
if CONF_FOR in config:
numeric_state_config[CONF_FOR] = config[CONF_FOR]
numeric_state_config = numeric_state_automation.TRIGGER_SCHEMA(
numeric_state_config = numeric_state_trigger.TRIGGER_SCHEMA(
numeric_state_config
)
return await numeric_state_automation.async_attach_trigger(
return await numeric_state_trigger.async_attach_trigger(
hass, numeric_state_config, action, automation_info, platform_type="device"
)

View File

@ -3,8 +3,9 @@ from typing import List
import voluptuous as vol
from homeassistant.components.automation import AutomationActionType, state
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
@ -80,12 +81,12 @@ async def async_attach_trigger(
to_state = STATE_UNLOCKED
state_config = {
state.CONF_PLATFORM: "state",
CONF_PLATFORM: "state",
CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state.CONF_FROM: from_state,
state.CONF_TO: to_state,
state_trigger.CONF_FROM: from_state,
state_trigger.CONF_TO: to_state,
}
state_config = state.TRIGGER_SCHEMA(state_config)
return await state.async_attach_trigger(
state_config = state_trigger.TRIGGER_SCHEMA(state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)

View File

@ -7,7 +7,6 @@ import voluptuous as vol
from homeassistant.components import mqtt
from homeassistant.components.automation import AutomationActionType
import homeassistant.components.automation.mqtt as automation_mqtt
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
@ -27,6 +26,7 @@ from . import (
DOMAIN,
cleanup_device_registry,
debug_info,
trigger as mqtt_trigger,
)
from .discovery import MQTT_DISCOVERY_UPDATED, clear_discovery_hash
@ -83,16 +83,16 @@ class TriggerInstance:
async def async_attach_trigger(self):
"""Attach MQTT trigger."""
mqtt_config = {
automation_mqtt.CONF_TOPIC: self.trigger.topic,
automation_mqtt.CONF_ENCODING: DEFAULT_ENCODING,
automation_mqtt.CONF_QOS: self.trigger.qos,
mqtt_trigger.CONF_TOPIC: self.trigger.topic,
mqtt_trigger.CONF_ENCODING: DEFAULT_ENCODING,
mqtt_trigger.CONF_QOS: self.trigger.qos,
}
if self.trigger.payload:
mqtt_config[CONF_PAYLOAD] = self.trigger.payload
if self.remove:
self.remove()
self.remove = await automation_mqtt.async_attach_trigger(
self.remove = await mqtt_trigger.async_attach_trigger(
self.trigger.hass, mqtt_config, self.action, self.automation_info,
)

View File

@ -1,11 +1,13 @@
"""Provides device triggers for sensors."""
import voluptuous as vol
import homeassistant.components.automation.numeric_state as numeric_state_automation
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig,
)
from homeassistant.components.homeassistant.triggers import (
numeric_state as numeric_state_trigger,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_UNIT_OF_MEASUREMENT,
@ -100,18 +102,18 @@ TRIGGER_SCHEMA = vol.All(
async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
numeric_state_config = {
numeric_state_automation.CONF_PLATFORM: "numeric_state",
numeric_state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
numeric_state_trigger.CONF_PLATFORM: "numeric_state",
numeric_state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
}
if CONF_ABOVE in config:
numeric_state_config[numeric_state_automation.CONF_ABOVE] = config[CONF_ABOVE]
numeric_state_config[numeric_state_trigger.CONF_ABOVE] = config[CONF_ABOVE]
if CONF_BELOW in config:
numeric_state_config[numeric_state_automation.CONF_BELOW] = config[CONF_BELOW]
numeric_state_config[numeric_state_trigger.CONF_BELOW] = config[CONF_BELOW]
if CONF_FOR in config:
numeric_state_config[CONF_FOR] = config[CONF_FOR]
numeric_state_config = numeric_state_automation.TRIGGER_SCHEMA(numeric_state_config)
return await numeric_state_automation.async_attach_trigger(
numeric_state_config = numeric_state_trigger.TRIGGER_SCHEMA(numeric_state_config)
return await numeric_state_trigger.async_attach_trigger(
hass, numeric_state_config, action, automation_info, platform_type="device"
)

View File

@ -23,7 +23,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema(
async def async_attach_trigger(
hass, config, action, automation_info, *, platform_type="numeric_state"
hass, config, action, automation_info, *, platform_type="template"
):
"""Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE)

View File

@ -3,8 +3,9 @@ from typing import List
import voluptuous as vol
from homeassistant.components.automation import AutomationActionType, state
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
@ -77,12 +78,12 @@ async def async_attach_trigger(
to_state = STATE_DOCKED
state_config = {
state.CONF_PLATFORM: "state",
CONF_PLATFORM: "state",
CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state.CONF_FROM: from_state,
state.CONF_TO: to_state,
state_trigger.CONF_FROM: from_state,
state_trigger.CONF_TO: to_state,
}
state_config = state.TRIGGER_SCHEMA(state_config)
return await state.async_attach_trigger(
state_config = state_trigger.TRIGGER_SCHEMA(state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)

View File

@ -9,8 +9,6 @@ from homeassistant.const import CONF_PLATFORM, CONF_WEBHOOK_ID
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from . import DOMAIN as AUTOMATION_DOMAIN
# mypy: allow-untyped-defs
DEPENDENCIES = ("webhook",)
@ -39,7 +37,7 @@ async def async_attach_trigger(hass, config, action, automation_info):
"""Trigger based on incoming webhooks."""
webhook_id = config.get(CONF_WEBHOOK_ID)
hass.components.webhook.async_register(
AUTOMATION_DOMAIN,
automation_info["domain"],
automation_info["name"],
webhook_id,
partial(_handle_webhook, action),

View File

@ -1,11 +1,11 @@
"""Provides device automations for ZHA devices that emit events."""
import voluptuous as vol
import homeassistant.components.automation.event as event
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig,
)
from homeassistant.components.homeassistant.triggers import event as event_trigger
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE
from . import DOMAIN
@ -54,13 +54,13 @@ async def async_attach_trigger(hass, config, action, automation_info):
trigger = zha_device.device_automation_triggers[trigger]
event_config = {
event.CONF_PLATFORM: "event",
event.CONF_EVENT_TYPE: ZHA_EVENT,
event.CONF_EVENT_DATA: {DEVICE_IEEE: str(zha_device.ieee), **trigger},
event_trigger.CONF_PLATFORM: "event",
event_trigger.CONF_EVENT_TYPE: ZHA_EVENT,
event_trigger.CONF_EVENT_DATA: {DEVICE_IEEE: str(zha_device.ieee), **trigger},
}
event_config = event.TRIGGER_SCHEMA(event_config)
return await event.async_attach_trigger(
event_config = event_trigger.TRIGGER_SCHEMA(event_config)
return await event_trigger.async_attach_trigger(
hass, event_config, action, automation_info, platform_type="device"
)

View File

@ -995,6 +995,10 @@ CONDITION_SCHEMA: vol.Schema = key_value_schemas(
},
)
TRIGGER_SCHEMA = vol.All(
ensure_list, [vol.Schema({vol.Required(CONF_PLATFORM): str}, extra=vol.ALLOW_EXTRA)]
)
_SCRIPT_DELAY_SCHEMA = vol.Schema(
{
vol.Optional(CONF_ALIAS): string,

View File

@ -0,0 +1,92 @@
"""Triggers."""
import asyncio
import logging
from typing import Any, Callable, List, Optional
import voluptuous as vol
from homeassistant.const import CONF_PLATFORM
from homeassistant.core import CALLBACK_TYPE, callback
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.loader import IntegrationNotFound, async_get_integration
_PLATFORM_ALIASES = {
"device_automation": ("device",),
"homeassistant": ("event", "numeric_state", "state", "time_pattern", "time"),
}
async def _async_get_trigger_platform(
hass: HomeAssistantType, config: ConfigType
) -> Any:
platform = config[CONF_PLATFORM]
for alias, triggers in _PLATFORM_ALIASES.items():
if platform in triggers:
platform = alias
break
try:
integration = await async_get_integration(hass, platform)
except IntegrationNotFound:
raise vol.Invalid(f"Invalid platform '{platform}' specified") from None
try:
return integration.get_platform("trigger")
except ImportError:
raise vol.Invalid(
f"Integration '{platform}' does not provide trigger support"
) from None
async def async_validate_trigger_config(
hass: HomeAssistantType, trigger_config: List[ConfigType]
) -> List[ConfigType]:
"""Validate triggers."""
config = []
for conf in trigger_config:
platform = await _async_get_trigger_platform(hass, conf)
if hasattr(platform, "async_validate_trigger_config"):
conf = await platform.async_validate_trigger_config(hass, conf)
else:
conf = platform.TRIGGER_SCHEMA(conf)
config.append(conf)
return config
async def async_initialize_triggers(
hass: HomeAssistantType,
trigger_config: List[ConfigType],
action: Callable,
domain: str,
name: str,
log_cb: Callable,
home_assistant_start: bool = False,
) -> Optional[CALLBACK_TYPE]:
"""Initialize triggers."""
info = {
"domain": domain,
"name": name,
"home_assistant_start": home_assistant_start,
}
triggers = []
for conf in trigger_config:
platform = await _async_get_trigger_platform(hass, conf)
triggers.append(platform.async_attach_trigger(hass, conf, action, info))
removes = await asyncio.gather(*triggers)
if None in removes:
log_cb(logging.ERROR, "Error setting up trigger")
removes = list(filter(None, removes))
if not removes:
return None
log_cb(logging.INFO, "Initialized trigger")
@callback
def remove_triggers(): # type: ignore
"""Remove triggers."""
for remove in removes:
remove()
return remove_triggers

View File

@ -861,6 +861,22 @@ async def test_automation_not_trigger_on_bootstrap(hass):
assert ["hello.world"] == calls[0].data.get(ATTR_ENTITY_ID)
async def test_automation_bad_trigger(hass, caplog):
"""Test bad trigger configuration."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"alias": "hello",
"trigger": {"platform": "automation"},
"action": [],
}
},
)
assert "Integration 'automation' does not provide trigger support." in caplog.text
async def test_automation_with_error_in_script(hass, caplog):
"""Test automation with an error in script."""
assert await async_setup_component(

View File

@ -0,0 +1 @@
"""Test core triggers."""

View File

@ -5,7 +5,9 @@ import pytest
import voluptuous as vol
import homeassistant.components.automation as automation
from homeassistant.components.automation import numeric_state
from homeassistant.components.homeassistant.triggers import (
numeric_state as numeric_state_trigger,
)
from homeassistant.core import Context
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
@ -776,7 +778,7 @@ async def test_if_fails_setup_bad_for(hass, calls):
},
)
with patch.object(automation.numeric_state, "_LOGGER") as mock_logger:
with patch.object(numeric_state_trigger, "_LOGGER") as mock_logger:
hass.states.async_set("test.entity", 9)
await hass.async_block_till_done()
assert mock_logger.error.called
@ -1164,7 +1166,7 @@ async def test_invalid_for_template(hass, calls):
},
)
with patch.object(automation.numeric_state, "_LOGGER") as mock_logger:
with patch.object(numeric_state_trigger, "_LOGGER") as mock_logger:
hass.states.async_set("test.entity", 9)
await hass.async_block_till_done()
assert mock_logger.error.called
@ -1234,6 +1236,6 @@ async def test_if_fires_on_entities_change_overlap_for_template(hass, calls):
def test_below_above():
"""Test above cannot be above below."""
with pytest.raises(vol.Invalid):
numeric_state.TRIGGER_SCHEMA(
numeric_state_trigger.TRIGGER_SCHEMA(
{"platform": "numeric_state", "above": 1200, "below": 1000}
)

View File

@ -4,6 +4,7 @@ from datetime import timedelta
import pytest
import homeassistant.components.automation as automation
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.core import Context
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
@ -327,7 +328,7 @@ async def test_if_fails_setup_bad_for(hass, calls):
},
)
with patch.object(automation.state, "_LOGGER") as mock_logger:
with patch.object(state_trigger, "_LOGGER") as mock_logger:
hass.states.async_set("test.entity", "world")
await hass.async_block_till_done()
assert mock_logger.error.called
@ -942,7 +943,7 @@ async def test_invalid_for_template_1(hass, calls):
},
)
with patch.object(automation.state, "_LOGGER") as mock_logger:
with patch.object(state_trigger, "_LOGGER") as mock_logger:
hass.states.async_set("test.entity", "world")
await hass.async_block_till_done()
assert mock_logger.error.called

View File

@ -361,7 +361,7 @@ async def test_untrack_time_change(hass):
"""Test for removing tracked time changes."""
mock_track_time_change = Mock()
with patch(
"homeassistant.components.automation.time.async_track_time_change",
"homeassistant.components.homeassistant.triggers.time.async_track_time_change",
return_value=mock_track_time_change,
):
assert await async_setup_component(

View File

@ -419,6 +419,8 @@ async def test_missing_discover_abbreviations(hass, mqtt_mock, caplog):
missing = []
regex = re.compile(r"(CONF_[a-zA-Z\d_]*) *= *[\'\"]([a-zA-Z\d_]*)[\'\"]")
for fil in Path(mqtt.__file__).parent.rglob("*.py"):
if fil.name == "trigger.py":
continue
with open(fil) as file:
matches = re.findall(regex, file.read())
for match in matches:

View File

@ -5,6 +5,7 @@ from unittest import mock
import pytest
import homeassistant.components.automation as automation
from homeassistant.components.template import trigger as template_trigger
from homeassistant.core import Context, callback
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
@ -782,7 +783,7 @@ async def test_invalid_for_template_1(hass, calls):
},
)
with mock.patch.object(automation.template, "_LOGGER") as mock_logger:
with mock.patch.object(template_trigger, "_LOGGER") as mock_logger:
hass.states.async_set("test.entity", "world")
await hass.async_block_till_done()
assert mock_logger.error.called

View File

@ -0,0 +1,12 @@
"""The tests for the trigger helper."""
import pytest
import voluptuous as vol
from homeassistant.helpers.trigger import async_validate_trigger_config
async def test_bad_trigger_platform(hass):
"""Test bad trigger platform."""
with pytest.raises(vol.Invalid) as ex:
await async_validate_trigger_config(hass, [{"platform": "not_a_platform"}])
assert "Invalid platform 'not_a_platform' specified" in str(ex)