Adapt template sensors to use the same plural trigger/condition/action definitions as automations (#127875)

* Add plurals to template entities

* Ruff

* Ruffy ruff

* Fix linters

* Fix bug introduced after merging dev

* Fix merge mistake

* Revert adding automation helper

* Revert "Fix bug introduced after merging dev"

This reverts commit 098d478f150a06546fb9ec3668865fa5d763c6b2.

* Fix blueprint validation

* Apply suggestions from code review

---------

Co-authored-by: Erik <erik@montnemery.com>
This commit is contained in:
chammp 2025-04-29 11:52:58 +02:00 committed by GitHub
parent f2838e493b
commit 8ff4d5dcbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 162 additions and 61 deletions

View File

@ -18,6 +18,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_MODE, ATTR_MODE,
ATTR_NAME, ATTR_NAME,
CONF_ACTIONS,
CONF_ALIAS, CONF_ALIAS,
CONF_CONDITIONS, CONF_CONDITIONS,
CONF_DEVICE_ID, CONF_DEVICE_ID,
@ -27,6 +28,7 @@ from homeassistant.const import (
CONF_MODE, CONF_MODE,
CONF_PATH, CONF_PATH,
CONF_PLATFORM, CONF_PLATFORM,
CONF_TRIGGERS,
CONF_VARIABLES, CONF_VARIABLES,
CONF_ZONE, CONF_ZONE,
EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STARTED,
@ -86,11 +88,9 @@ from homeassistant.util.hass_dict import HassKey
from .config import AutomationConfig, ValidationStatus from .config import AutomationConfig, ValidationStatus
from .const import ( from .const import (
CONF_ACTIONS,
CONF_INITIAL_STATE, CONF_INITIAL_STATE,
CONF_TRACE, CONF_TRACE,
CONF_TRIGGER_VARIABLES, CONF_TRIGGER_VARIABLES,
CONF_TRIGGERS,
DEFAULT_INITIAL_STATE, DEFAULT_INITIAL_STATE,
DOMAIN, DOMAIN,
LOGGER, LOGGER,

View File

@ -14,11 +14,15 @@ from homeassistant.components import blueprint
from homeassistant.components.trace import TRACE_CONFIG_SCHEMA from homeassistant.components.trace import TRACE_CONFIG_SCHEMA
from homeassistant.config import config_per_platform, config_without_domain from homeassistant.config import config_per_platform, config_without_domain
from homeassistant.const import ( from homeassistant.const import (
CONF_ACTION,
CONF_ACTIONS,
CONF_ALIAS, CONF_ALIAS,
CONF_CONDITION, CONF_CONDITION,
CONF_CONDITIONS, CONF_CONDITIONS,
CONF_DESCRIPTION, CONF_DESCRIPTION,
CONF_ID, CONF_ID,
CONF_TRIGGER,
CONF_TRIGGERS,
CONF_VARIABLES, CONF_VARIABLES,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -30,14 +34,10 @@ from homeassistant.helpers.typing import ConfigType
from homeassistant.util.yaml.input import UndefinedSubstitution from homeassistant.util.yaml.input import UndefinedSubstitution
from .const import ( from .const import (
CONF_ACTION,
CONF_ACTIONS,
CONF_HIDE_ENTITY, CONF_HIDE_ENTITY,
CONF_INITIAL_STATE, CONF_INITIAL_STATE,
CONF_TRACE, CONF_TRACE,
CONF_TRIGGER,
CONF_TRIGGER_VARIABLES, CONF_TRIGGER_VARIABLES,
CONF_TRIGGERS,
DOMAIN, DOMAIN,
LOGGER, LOGGER,
) )

View File

@ -2,10 +2,6 @@
import logging import logging
CONF_ACTION = "action"
CONF_ACTIONS = "actions"
CONF_TRIGGER = "trigger"
CONF_TRIGGERS = "triggers"
CONF_TRIGGER_VARIABLES = "trigger_variables" CONF_TRIGGER_VARIABLES = "trigger_variables"
DOMAIN = "automation" DOMAIN = "automation"

View File

@ -12,6 +12,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_NAME, CONF_NAME,
CONF_TRIGGERS,
CONF_UNIQUE_ID, CONF_UNIQUE_ID,
SERVICE_RELOAD, SERVICE_RELOAD,
) )
@ -27,7 +28,7 @@ from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import async_get_integration from homeassistant.loader import async_get_integration
from homeassistant.util.hass_dict import HassKey from homeassistant.util.hass_dict import HassKey
from .const import CONF_MAX, CONF_MIN, CONF_STEP, CONF_TRIGGER, DOMAIN, PLATFORMS from .const import CONF_MAX, CONF_MIN, CONF_STEP, DOMAIN, PLATFORMS
from .coordinator import TriggerUpdateCoordinator from .coordinator import TriggerUpdateCoordinator
from .helpers import async_get_blueprints from .helpers import async_get_blueprints
@ -136,7 +137,7 @@ async def _process_config(hass: HomeAssistant, hass_config: ConfigType) -> None:
coordinator_tasks: list[Coroutine[Any, Any, TriggerUpdateCoordinator]] = [] coordinator_tasks: list[Coroutine[Any, Any, TriggerUpdateCoordinator]] = []
for conf_section in hass_config[DOMAIN]: for conf_section in hass_config[DOMAIN]:
if CONF_TRIGGER in conf_section: if CONF_TRIGGERS in conf_section:
coordinator_tasks.append(init_coordinator(hass, conf_section)) coordinator_tasks.append(init_coordinator(hass, conf_section))
continue continue

View File

@ -3,6 +3,7 @@
from collections.abc import Callable from collections.abc import Callable
from contextlib import suppress from contextlib import suppress
import logging import logging
from typing import Any
import voluptuous as vol import voluptuous as vol
@ -10,6 +11,7 @@ from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAI
from homeassistant.components.blueprint import ( from homeassistant.components.blueprint import (
BLUEPRINT_INSTANCE_FIELDS, BLUEPRINT_INSTANCE_FIELDS,
is_blueprint_instance_config, is_blueprint_instance_config,
schemas as blueprint_schemas,
) )
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
@ -22,9 +24,15 @@ from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
from homeassistant.config import async_log_schema_error, config_without_domain from homeassistant.config import async_log_schema_error, config_without_domain
from homeassistant.const import ( from homeassistant.const import (
CONF_ACTION,
CONF_ACTIONS,
CONF_BINARY_SENSORS, CONF_BINARY_SENSORS,
CONF_CONDITION,
CONF_CONDITIONS,
CONF_NAME, CONF_NAME,
CONF_SENSORS, CONF_SENSORS,
CONF_TRIGGER,
CONF_TRIGGERS,
CONF_UNIQUE_ID, CONF_UNIQUE_ID,
CONF_VARIABLES, CONF_VARIABLES,
) )
@ -47,14 +55,7 @@ from . import (
switch as switch_platform, switch as switch_platform,
weather as weather_platform, weather as weather_platform,
) )
from .const import ( from .const import DOMAIN, PLATFORMS, TemplateConfig
CONF_ACTION,
CONF_CONDITION,
CONF_TRIGGER,
DOMAIN,
PLATFORMS,
TemplateConfig,
)
from .helpers import async_get_blueprints from .helpers import async_get_blueprints
PACKAGE_MERGE_HINT = "list" PACKAGE_MERGE_HINT = "list"
@ -67,7 +68,7 @@ def ensure_domains_do_not_have_trigger_or_action(*keys: str) -> Callable[[dict],
def validate(obj: dict): def validate(obj: dict):
options = set(obj.keys()) options = set(obj.keys())
if found_domains := domains.intersection(options): if found_domains := domains.intersection(options):
invalid = {CONF_TRIGGER, CONF_ACTION} invalid = {CONF_TRIGGERS, CONF_ACTIONS}
if found_invalid := invalid.intersection(set(obj.keys())): if found_invalid := invalid.intersection(set(obj.keys())):
raise vol.Invalid( raise vol.Invalid(
f"Unsupported option(s) found for domain {found_domains.pop()}, please remove ({', '.join(found_invalid)}) from your configuration", f"Unsupported option(s) found for domain {found_domains.pop()}, please remove ({', '.join(found_invalid)}) from your configuration",
@ -78,13 +79,22 @@ def ensure_domains_do_not_have_trigger_or_action(*keys: str) -> Callable[[dict],
return validate return validate
CONFIG_SECTION_SCHEMA = vol.Schema( def _backward_compat_schema(value: Any | None) -> Any:
vol.All( """Backward compatibility for automations."""
value = cv.renamed(CONF_TRIGGER, CONF_TRIGGERS)(value)
value = cv.renamed(CONF_ACTION, CONF_ACTIONS)(value)
return cv.renamed(CONF_CONDITION, CONF_CONDITIONS)(value)
CONFIG_SECTION_SCHEMA = vol.All(
_backward_compat_schema,
vol.Schema(
{ {
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_TRIGGER): cv.TRIGGER_SCHEMA, vol.Optional(CONF_TRIGGERS): cv.TRIGGER_SCHEMA,
vol.Optional(CONF_CONDITION): cv.CONDITIONS_SCHEMA, vol.Optional(CONF_CONDITIONS): cv.CONDITIONS_SCHEMA,
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_ACTIONS): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA, vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
vol.Optional(NUMBER_DOMAIN): vol.All( vol.Optional(NUMBER_DOMAIN): vol.All(
cv.ensure_list, [number_platform.NUMBER_SCHEMA] cv.ensure_list, [number_platform.NUMBER_SCHEMA]
@ -123,10 +133,14 @@ CONFIG_SECTION_SCHEMA = vol.Schema(
cv.ensure_list, [cover_platform.COVER_SCHEMA] cv.ensure_list, [cover_platform.COVER_SCHEMA]
), ),
}, },
ensure_domains_do_not_have_trigger_or_action( ),
BUTTON_DOMAIN, COVER_DOMAIN, LIGHT_DOMAIN ensure_domains_do_not_have_trigger_or_action(
), BUTTON_DOMAIN, COVER_DOMAIN, LIGHT_DOMAIN
) ),
)
TEMPLATE_BLUEPRINT_SCHEMA = vol.All(
_backward_compat_schema, blueprint_schemas.BLUEPRINT_SCHEMA
) )
TEMPLATE_BLUEPRINT_INSTANCE_SCHEMA = vol.Schema( TEMPLATE_BLUEPRINT_INSTANCE_SCHEMA = vol.Schema(
@ -169,7 +183,7 @@ async def _async_resolve_blueprints(
# house input results for template entities. For Trigger based template entities # house input results for template entities. For Trigger based template entities
# CONF_VARIABLES should not be removed because the variables are always # CONF_VARIABLES should not be removed because the variables are always
# executed between the trigger and action. # executed between the trigger and action.
if CONF_TRIGGER not in config and CONF_VARIABLES in config: if CONF_TRIGGERS not in config and CONF_VARIABLES in config:
config[platform][CONF_VARIABLES] = config.pop(CONF_VARIABLES) config[platform][CONF_VARIABLES] = config.pop(CONF_VARIABLES)
raw_config = dict(config) raw_config = dict(config)
@ -187,14 +201,14 @@ async def async_validate_config_section(
validated_config = await _async_resolve_blueprints(hass, config) validated_config = await _async_resolve_blueprints(hass, config)
if CONF_TRIGGER in validated_config: if CONF_TRIGGERS in validated_config:
validated_config[CONF_TRIGGER] = await async_validate_trigger_config( validated_config[CONF_TRIGGERS] = await async_validate_trigger_config(
hass, validated_config[CONF_TRIGGER] hass, validated_config[CONF_TRIGGERS]
) )
if CONF_CONDITION in validated_config: if CONF_CONDITIONS in validated_config:
validated_config[CONF_CONDITION] = await async_validate_conditions_config( validated_config[CONF_CONDITIONS] = await async_validate_conditions_config(
hass, validated_config[CONF_CONDITION] hass, validated_config[CONF_CONDITIONS]
) )
return validated_config return validated_config

View File

@ -1,22 +1,18 @@
"""Constants for the Template Platform Components.""" """Constants for the Template Platform Components."""
from homeassistant.components.blueprint import BLUEPRINT_SCHEMA
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
CONF_ACTION = "action"
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates" CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
CONF_ATTRIBUTES = "attributes" CONF_ATTRIBUTES = "attributes"
CONF_AVAILABILITY = "availability" CONF_AVAILABILITY = "availability"
CONF_AVAILABILITY_TEMPLATE = "availability_template" CONF_AVAILABILITY_TEMPLATE = "availability_template"
CONF_CONDITION = "condition"
CONF_MAX = "max" CONF_MAX = "max"
CONF_MIN = "min" CONF_MIN = "min"
CONF_OBJECT_ID = "object_id" CONF_OBJECT_ID = "object_id"
CONF_PICTURE = "picture" CONF_PICTURE = "picture"
CONF_PRESS = "press" CONF_PRESS = "press"
CONF_STEP = "step" CONF_STEP = "step"
CONF_TRIGGER = "trigger"
CONF_TURN_OFF = "turn_off" CONF_TURN_OFF = "turn_off"
CONF_TURN_ON = "turn_on" CONF_TURN_ON = "turn_on"
@ -41,8 +37,6 @@ PLATFORMS = [
Platform.WEATHER, Platform.WEATHER,
] ]
TEMPLATE_BLUEPRINT_SCHEMA = BLUEPRINT_SCHEMA
class TemplateConfig(dict): class TemplateConfig(dict):
"""Dummy class to allow adding attributes.""" """Dummy class to allow adding attributes."""

View File

@ -5,7 +5,14 @@ import logging
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any, cast
from homeassistant.components.blueprint import CONF_USE_BLUEPRINT from homeassistant.components.blueprint import CONF_USE_BLUEPRINT
from homeassistant.const import CONF_PATH, CONF_VARIABLES, EVENT_HOMEASSISTANT_START from homeassistant.const import (
CONF_ACTIONS,
CONF_CONDITIONS,
CONF_PATH,
CONF_TRIGGERS,
CONF_VARIABLES,
EVENT_HOMEASSISTANT_START,
)
from homeassistant.core import Context, CoreState, Event, HomeAssistant, callback from homeassistant.core import Context, CoreState, Event, HomeAssistant, callback
from homeassistant.helpers import condition, discovery, trigger as trigger_helper from homeassistant.helpers import condition, discovery, trigger as trigger_helper
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
@ -14,7 +21,7 @@ from homeassistant.helpers.trace import trace_get
from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.typing import ConfigType, TemplateVarsType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import CONF_ACTION, CONF_CONDITION, CONF_TRIGGER, DOMAIN, PLATFORMS from .const import DOMAIN, PLATFORMS
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -84,17 +91,17 @@ class TriggerUpdateCoordinator(DataUpdateCoordinator):
async def _attach_triggers(self, start_event: Event | None = None) -> None: async def _attach_triggers(self, start_event: Event | None = None) -> None:
"""Attach the triggers.""" """Attach the triggers."""
if CONF_ACTION in self.config: if CONF_ACTIONS in self.config:
self._script = Script( self._script = Script(
self.hass, self.hass,
self.config[CONF_ACTION], self.config[CONF_ACTIONS],
self.name, self.name,
DOMAIN, DOMAIN,
) )
if CONF_CONDITION in self.config: if CONF_CONDITIONS in self.config:
self._cond_func = await condition.async_conditions_from_config( self._cond_func = await condition.async_conditions_from_config(
self.hass, self.config[CONF_CONDITION], _LOGGER, "template entity" self.hass, self.config[CONF_CONDITIONS], _LOGGER, "template entity"
) )
if start_event is not None: if start_event is not None:
@ -107,7 +114,7 @@ class TriggerUpdateCoordinator(DataUpdateCoordinator):
self._unsub_trigger = await trigger_helper.async_initialize_triggers( self._unsub_trigger = await trigger_helper.async_initialize_triggers(
self.hass, self.hass,
self.config[CONF_TRIGGER], self.config[CONF_TRIGGERS],
action, action,
DOMAIN, DOMAIN,
self.name, self.name,

View File

@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import async_get_platforms from homeassistant.helpers.entity_platform import async_get_platforms
from homeassistant.helpers.singleton import singleton from homeassistant.helpers.singleton import singleton
from .const import DOMAIN, TEMPLATE_BLUEPRINT_SCHEMA from .const import DOMAIN
from .entity import AbstractTemplateEntity from .entity import AbstractTemplateEntity
DATA_BLUEPRINTS = "template_blueprints" DATA_BLUEPRINTS = "template_blueprints"
@ -54,6 +54,9 @@ async def _reload_blueprint_templates(hass: HomeAssistant, blueprint_path: str)
@callback @callback
def async_get_blueprints(hass: HomeAssistant) -> blueprint.DomainBlueprints: def async_get_blueprints(hass: HomeAssistant) -> blueprint.DomainBlueprints:
"""Get template blueprints.""" """Get template blueprints."""
# pylint: disable-next=import-outside-toplevel
from .config import TEMPLATE_BLUEPRINT_SCHEMA
return blueprint.DomainBlueprints( return blueprint.DomainBlueprints(
hass, hass,
DOMAIN, DOMAIN,

View File

@ -33,6 +33,8 @@ from homeassistant.const import (
CONF_NAME, CONF_NAME,
CONF_SENSORS, CONF_SENSORS,
CONF_STATE, CONF_STATE,
CONF_TRIGGER,
CONF_TRIGGERS,
CONF_UNIQUE_ID, CONF_UNIQUE_ID,
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
@ -53,12 +55,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from . import TriggerUpdateCoordinator from . import TriggerUpdateCoordinator
from .const import ( from .const import CONF_ATTRIBUTE_TEMPLATES, CONF_AVAILABILITY_TEMPLATE, CONF_OBJECT_ID
CONF_ATTRIBUTE_TEMPLATES,
CONF_AVAILABILITY_TEMPLATE,
CONF_OBJECT_ID,
CONF_TRIGGER,
)
from .template_entity import ( from .template_entity import (
TEMPLATE_ENTITY_COMMON_SCHEMA, TEMPLATE_ENTITY_COMMON_SCHEMA,
TemplateEntity, TemplateEntity,
@ -132,7 +129,7 @@ LEGACY_SENSOR_SCHEMA = vol.All(
def extra_validation_checks(val): def extra_validation_checks(val):
"""Run extra validation checks.""" """Run extra validation checks."""
if CONF_TRIGGER in val: if CONF_TRIGGERS in val or CONF_TRIGGER in val:
raise vol.Invalid( raise vol.Invalid(
"You can only add triggers to template entities if they are defined under" "You can only add triggers to template entities if they are defined under"
" `template:`. See the template documentation for more information:" " `template:`. See the template documentation for more information:"
@ -170,6 +167,7 @@ PLATFORM_SCHEMA = vol.All(
SENSOR_PLATFORM_SCHEMA.extend( SENSOR_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_TRIGGER): cv.match_all, # to raise custom warning vol.Optional(CONF_TRIGGER): cv.match_all, # to raise custom warning
vol.Optional(CONF_TRIGGERS): cv.match_all, # to raise custom warning
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(LEGACY_SENSOR_SCHEMA), vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(LEGACY_SENSOR_SCHEMA),
} }
), ),

View File

@ -115,6 +115,7 @@ SUN_EVENT_SUNRISE: Final = "sunrise"
CONF_ABOVE: Final = "above" CONF_ABOVE: Final = "above"
CONF_ACCESS_TOKEN: Final = "access_token" CONF_ACCESS_TOKEN: Final = "access_token"
CONF_ACTION: Final = "action" CONF_ACTION: Final = "action"
CONF_ACTIONS: Final = "actions"
CONF_ADDRESS: Final = "address" CONF_ADDRESS: Final = "address"
CONF_AFTER: Final = "after" CONF_AFTER: Final = "after"
CONF_ALIAS: Final = "alias" CONF_ALIAS: Final = "alias"

View File

@ -212,11 +212,16 @@ async def test_reload_template_when_blueprint_changes(hass: HomeAssistant) -> No
assert not_inverted.state == "on" assert not_inverted.state == "on"
@pytest.mark.parametrize(
("blueprint"),
["test_event_sensor.yaml", "test_event_sensor_legacy_schema.yaml"],
)
async def test_trigger_event_sensor( async def test_trigger_event_sensor(
hass: HomeAssistant, device_registry: dr.DeviceRegistry hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
blueprint: str,
) -> None: ) -> None:
"""Test event sensor blueprint.""" """Test event sensor blueprint."""
blueprint = "test_event_sensor.yaml"
assert await async_setup_component( assert await async_setup_component(
hass, hass,
"template", "template",

View File

@ -2303,6 +2303,61 @@ async def test_trigger_conditional_action(hass: HomeAssistant) -> None:
assert len(events) == 1 assert len(events) == 1
@pytest.mark.parametrize("trigger_field", ["trigger", "triggers"])
@pytest.mark.parametrize("condition_field", ["condition", "conditions"])
@pytest.mark.parametrize("action_field", ["action", "actions"])
async def test_legacy_and_new_config_schema(
hass: HomeAssistant, trigger_field: str, condition_field: str, action_field: str
) -> None:
"""Tests that both old and new config schema (singular -> plural) work."""
assert await async_setup_component(
hass,
"template",
{
"template": [
{
"unique_id": "listening-test-event",
f"{trigger_field}": {
"platform": "event",
"event_type": "beer_event",
},
f"{condition_field}": [
{
"condition": "template",
"value_template": "{{ trigger.event.data.beer >= 42 }}",
}
],
f"{action_field}": [
{"event": "test_event_by_action"},
],
"sensor": [
{
"name": "Unimportant",
"state": "Uninteresting",
}
],
},
],
},
)
await hass.async_block_till_done()
event = "test_event_by_action"
events = async_capture_events(hass, event)
hass.bus.async_fire("beer_event", {"beer": 1})
await hass.async_block_till_done()
assert len(events) == 0
hass.bus.async_fire("beer_event", {"beer": 42})
await hass.async_block_till_done()
assert len(events) == 1
async def test_device_id( async def test_device_id(
hass: HomeAssistant, hass: HomeAssistant,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,

View File

@ -14,7 +14,7 @@ blueprint:
description: The event_data for the event trigger description: The event_data for the event trigger
selector: selector:
object: object:
trigger: triggers:
- trigger: event - trigger: event
event_type: !input event_type event_type: !input event_type
event_data: !input event_data event_data: !input event_data

View File

@ -0,0 +1,27 @@
blueprint:
name: Create Sensor from Event
description: Creates a timestamp sensor from an event
domain: template
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/template/blueprints/event_sensor.yaml
input:
event_type:
name: Name of the event_type
description: The event_type for the event trigger
selector:
text:
event_data:
name: The data for the event
description: The event_data for the event trigger
selector:
object:
trigger:
- trigger: event
event_type: !input event_type
event_data: !input event_data
variables:
event_data: "{{ trigger.event.data }}"
sensor:
state: "{{ now() }}"
device_class: timestamp
attributes:
data: "{{ event_data }}"