Allow specifying template entities based on triggers (#48169)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
f815ebe9cd
commit
022f56f54d
6 changed files with 456 additions and 16 deletions
|
@ -1,11 +1,80 @@
|
||||||
"""The template component."""
|
"""The template component."""
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_SENSORS, EVENT_HOMEASSISTANT_START
|
||||||
|
from homeassistant.core import CoreState, callback
|
||||||
|
from homeassistant.helpers import (
|
||||||
|
discovery,
|
||||||
|
trigger as trigger_helper,
|
||||||
|
update_coordinator,
|
||||||
|
)
|
||||||
from homeassistant.helpers.reload import async_setup_reload_service
|
from homeassistant.helpers.reload import async_setup_reload_service
|
||||||
|
|
||||||
from .const import DOMAIN, PLATFORMS
|
from .const import CONF_TRIGGER, DOMAIN, PLATFORMS
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up the template integration."""
|
"""Set up the template integration."""
|
||||||
|
if DOMAIN in config:
|
||||||
|
for conf in config[DOMAIN]:
|
||||||
|
coordinator = TriggerUpdateCoordinator(hass, conf)
|
||||||
|
await coordinator.async_setup(config)
|
||||||
|
|
||||||
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class TriggerUpdateCoordinator(update_coordinator.DataUpdateCoordinator):
|
||||||
|
"""Class to handle incoming data."""
|
||||||
|
|
||||||
|
def __init__(self, hass, config):
|
||||||
|
"""Instantiate trigger data."""
|
||||||
|
super().__init__(
|
||||||
|
hass, logging.getLogger(__name__), name="Trigger Update Coordinator"
|
||||||
|
)
|
||||||
|
self.config = config
|
||||||
|
self._unsub_trigger = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> Optional[str]:
|
||||||
|
"""Return unique ID for the entity."""
|
||||||
|
return self.config.get("unique_id")
|
||||||
|
|
||||||
|
async def async_setup(self, hass_config):
|
||||||
|
"""Set up the trigger and create entities."""
|
||||||
|
if self.hass.state == CoreState.running:
|
||||||
|
await self._attach_triggers()
|
||||||
|
else:
|
||||||
|
self.hass.bus.async_listen_once(
|
||||||
|
EVENT_HOMEASSISTANT_START, self._attach_triggers
|
||||||
|
)
|
||||||
|
|
||||||
|
self.hass.async_create_task(
|
||||||
|
discovery.async_load_platform(
|
||||||
|
self.hass,
|
||||||
|
"sensor",
|
||||||
|
DOMAIN,
|
||||||
|
{"coordinator": self, "entities": self.config[CONF_SENSORS]},
|
||||||
|
hass_config,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _attach_triggers(self, start_event=None) -> None:
|
||||||
|
"""Attach the triggers."""
|
||||||
|
self._unsub_trigger = await trigger_helper.async_initialize_triggers(
|
||||||
|
self.hass,
|
||||||
|
self.config[CONF_TRIGGER],
|
||||||
|
self._handle_triggered,
|
||||||
|
DOMAIN,
|
||||||
|
self.name,
|
||||||
|
self.logger.log,
|
||||||
|
start_event is not None,
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _handle_triggered(self, run_variables, context=None):
|
||||||
|
self.async_set_updated_data(
|
||||||
|
{"run_variables": run_variables, "context": context}
|
||||||
|
)
|
||||||
|
|
49
homeassistant/components/template/config.py
Normal file
49
homeassistant/components/template/config.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
"""Template config validator."""
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config import async_log_exception, config_without_domain
|
||||||
|
from homeassistant.const import CONF_SENSORS, CONF_UNIQUE_ID
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.trigger import async_validate_trigger_config
|
||||||
|
|
||||||
|
from .const import CONF_TRIGGER, DOMAIN
|
||||||
|
from .sensor import SENSOR_SCHEMA
|
||||||
|
|
||||||
|
CONF_STATE = "state"
|
||||||
|
|
||||||
|
|
||||||
|
TRIGGER_ENTITY_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
|
vol.Required(CONF_TRIGGER): cv.TRIGGER_SCHEMA,
|
||||||
|
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_validate_config(hass, config):
|
||||||
|
"""Validate config."""
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return config
|
||||||
|
|
||||||
|
trigger_entity_configs = []
|
||||||
|
|
||||||
|
for cfg in cv.ensure_list(config[DOMAIN]):
|
||||||
|
try:
|
||||||
|
cfg = TRIGGER_ENTITY_SCHEMA(cfg)
|
||||||
|
cfg[CONF_TRIGGER] = await async_validate_trigger_config(
|
||||||
|
hass, cfg[CONF_TRIGGER]
|
||||||
|
)
|
||||||
|
except vol.Invalid as err:
|
||||||
|
async_log_exception(err, DOMAIN, cfg, hass)
|
||||||
|
|
||||||
|
else:
|
||||||
|
trigger_entity_configs.append(cfg)
|
||||||
|
|
||||||
|
# Create a copy of the configuration with all config for current
|
||||||
|
# component removed and add validated config back in.
|
||||||
|
config = config_without_domain(config, DOMAIN)
|
||||||
|
config[DOMAIN] = trigger_entity_configs
|
||||||
|
|
||||||
|
return config
|
|
@ -1,6 +1,8 @@
|
||||||
"""Constants for the Template Platform Components."""
|
"""Constants for the Template Platform Components."""
|
||||||
|
|
||||||
CONF_AVAILABILITY_TEMPLATE = "availability_template"
|
CONF_AVAILABILITY_TEMPLATE = "availability_template"
|
||||||
|
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
|
||||||
|
CONF_TRIGGER = "trigger"
|
||||||
|
|
||||||
DOMAIN = "template"
|
DOMAIN = "template"
|
||||||
|
|
||||||
|
|
|
@ -11,25 +11,24 @@ from homeassistant.components.sensor import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ATTR_FRIENDLY_NAME,
|
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
CONF_ENTITY_PICTURE_TEMPLATE,
|
CONF_ENTITY_PICTURE_TEMPLATE,
|
||||||
|
CONF_FRIENDLY_NAME,
|
||||||
CONF_FRIENDLY_NAME_TEMPLATE,
|
CONF_FRIENDLY_NAME_TEMPLATE,
|
||||||
CONF_ICON_TEMPLATE,
|
CONF_ICON_TEMPLATE,
|
||||||
CONF_SENSORS,
|
CONF_SENSORS,
|
||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
CONF_VALUE_TEMPLATE,
|
CONF_VALUE_TEMPLATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
import homeassistant.helpers.config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.entity import async_generate_entity_id
|
from homeassistant.helpers.entity import async_generate_entity_id
|
||||||
|
|
||||||
from .const import CONF_AVAILABILITY_TEMPLATE
|
from .const import CONF_ATTRIBUTE_TEMPLATES, CONF_AVAILABILITY_TEMPLATE, CONF_TRIGGER
|
||||||
from .template_entity import TemplateEntity
|
from .template_entity import TemplateEntity
|
||||||
|
from .trigger_entity import TriggerEntity
|
||||||
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
|
|
||||||
|
|
||||||
SENSOR_SCHEMA = vol.All(
|
SENSOR_SCHEMA = vol.All(
|
||||||
cv.deprecated(ATTR_ENTITY_ID),
|
cv.deprecated(ATTR_ENTITY_ID),
|
||||||
|
@ -43,8 +42,8 @@ SENSOR_SCHEMA = vol.All(
|
||||||
vol.Optional(CONF_ATTRIBUTE_TEMPLATES, default={}): vol.Schema(
|
vol.Optional(CONF_ATTRIBUTE_TEMPLATES, default={}): vol.Schema(
|
||||||
{cv.string: cv.template}
|
{cv.string: cv.template}
|
||||||
),
|
),
|
||||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
||||||
vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string,
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
|
@ -52,12 +51,31 @@ SENSOR_SCHEMA = vol.All(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
||||||
{vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA)}
|
def trigger_warning(val):
|
||||||
|
"""Warn if a trigger is defined."""
|
||||||
|
if CONF_TRIGGER in val:
|
||||||
|
raise vol.Invalid(
|
||||||
|
"You can only add triggers to template entities if they are defined under `template:`. "
|
||||||
|
"See the template documentation for more information: https://www.home-assistant.io/integrations/template/"
|
||||||
|
)
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = vol.All(
|
||||||
|
PLATFORM_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_TRIGGER): cv.match_all, # to raise custom warning
|
||||||
|
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
trigger_warning,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _async_create_entities(hass, config):
|
@callback
|
||||||
|
def _async_create_template_tracking_entities(hass, config):
|
||||||
"""Create the template sensors."""
|
"""Create the template sensors."""
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
|
@ -66,9 +84,9 @@ async def _async_create_entities(hass, config):
|
||||||
icon_template = device_config.get(CONF_ICON_TEMPLATE)
|
icon_template = device_config.get(CONF_ICON_TEMPLATE)
|
||||||
entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_TEMPLATE)
|
entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_TEMPLATE)
|
||||||
availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE)
|
availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE)
|
||||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
friendly_name = device_config.get(CONF_FRIENDLY_NAME, device)
|
||||||
friendly_name_template = device_config.get(CONF_FRIENDLY_NAME_TEMPLATE)
|
friendly_name_template = device_config.get(CONF_FRIENDLY_NAME_TEMPLATE)
|
||||||
unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT)
|
unit_of_measurement = device_config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||||
device_class = device_config.get(CONF_DEVICE_CLASS)
|
device_class = device_config.get(CONF_DEVICE_CLASS)
|
||||||
attribute_templates = device_config[CONF_ATTRIBUTE_TEMPLATES]
|
attribute_templates = device_config[CONF_ATTRIBUTE_TEMPLATES]
|
||||||
unique_id = device_config.get(CONF_UNIQUE_ID)
|
unique_id = device_config.get(CONF_UNIQUE_ID)
|
||||||
|
@ -95,7 +113,13 @@ async def _async_create_entities(hass, config):
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up the template sensors."""
|
"""Set up the template sensors."""
|
||||||
async_add_entities(await _async_create_entities(hass, config))
|
if discovery_info is None:
|
||||||
|
async_add_entities(_async_create_template_tracking_entities(hass, config))
|
||||||
|
else:
|
||||||
|
async_add_entities(
|
||||||
|
TriggerSensorEntity(hass, discovery_info["coordinator"], device_id, config)
|
||||||
|
for device_id, config in discovery_info["entities"].items()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SensorTemplate(TemplateEntity, SensorEntity):
|
class SensorTemplate(TemplateEntity, SensorEntity):
|
||||||
|
@ -172,3 +196,14 @@ class SensorTemplate(TemplateEntity, SensorEntity):
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
"""Return the unit_of_measurement of the device."""
|
"""Return the unit_of_measurement of the device."""
|
||||||
return self._unit_of_measurement
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
|
||||||
|
class TriggerSensorEntity(TriggerEntity, SensorEntity):
|
||||||
|
"""Sensor entity based on trigger data."""
|
||||||
|
|
||||||
|
extra_template_keys = (CONF_VALUE_TEMPLATE,)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> str | None:
|
||||||
|
"""Return state of the sensor."""
|
||||||
|
return self._rendered.get(CONF_VALUE_TEMPLATE)
|
||||||
|
|
160
homeassistant/components/template/trigger_entity.py
Normal file
160
homeassistant/components/template/trigger_entity.py
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
"""Trigger entity."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
|
CONF_ENTITY_PICTURE_TEMPLATE,
|
||||||
|
CONF_FRIENDLY_NAME,
|
||||||
|
CONF_FRIENDLY_NAME_TEMPLATE,
|
||||||
|
CONF_ICON_TEMPLATE,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_VALUE_TEMPLATE,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers import template, update_coordinator
|
||||||
|
from homeassistant.helpers.entity import async_generate_entity_id
|
||||||
|
|
||||||
|
from . import TriggerUpdateCoordinator
|
||||||
|
from .const import CONF_ATTRIBUTE_TEMPLATES, CONF_AVAILABILITY_TEMPLATE
|
||||||
|
|
||||||
|
|
||||||
|
class TriggerEntity(update_coordinator.CoordinatorEntity):
|
||||||
|
"""Template entity based on trigger data."""
|
||||||
|
|
||||||
|
domain = ""
|
||||||
|
extra_template_keys: tuple | None = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
coordinator: TriggerUpdateCoordinator,
|
||||||
|
device_id: str,
|
||||||
|
config: dict,
|
||||||
|
):
|
||||||
|
"""Initialize the entity."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
|
||||||
|
self.entity_id = async_generate_entity_id(
|
||||||
|
self.domain + ".{}", device_id, hass=hass
|
||||||
|
)
|
||||||
|
|
||||||
|
self._name = config.get(CONF_FRIENDLY_NAME, device_id)
|
||||||
|
|
||||||
|
entity_unique_id = config.get(CONF_UNIQUE_ID)
|
||||||
|
|
||||||
|
if entity_unique_id is None and coordinator.unique_id:
|
||||||
|
entity_unique_id = device_id
|
||||||
|
|
||||||
|
if entity_unique_id and coordinator.unique_id:
|
||||||
|
self._unique_id = f"{coordinator.unique_id}-{entity_unique_id}"
|
||||||
|
else:
|
||||||
|
self._unique_id = entity_unique_id
|
||||||
|
|
||||||
|
self._config = config
|
||||||
|
|
||||||
|
self._to_render = [
|
||||||
|
itm
|
||||||
|
for itm in (
|
||||||
|
CONF_VALUE_TEMPLATE,
|
||||||
|
CONF_ICON_TEMPLATE,
|
||||||
|
CONF_ENTITY_PICTURE_TEMPLATE,
|
||||||
|
CONF_FRIENDLY_NAME_TEMPLATE,
|
||||||
|
CONF_AVAILABILITY_TEMPLATE,
|
||||||
|
)
|
||||||
|
if itm in config
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.extra_template_keys is not None:
|
||||||
|
self._to_render.extend(self.extra_template_keys)
|
||||||
|
|
||||||
|
self._rendered = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Name of the entity."""
|
||||||
|
if (
|
||||||
|
self._rendered is not None
|
||||||
|
and (name := self._rendered.get(CONF_FRIENDLY_NAME_TEMPLATE)) is not None
|
||||||
|
):
|
||||||
|
return name
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return unique ID of the entity."""
|
||||||
|
return self._unique_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return device class of the entity."""
|
||||||
|
return self._config.get(CONF_DEVICE_CLASS)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self) -> str | None:
|
||||||
|
"""Return unit of measurement."""
|
||||||
|
return self._config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self) -> str | None:
|
||||||
|
"""Return icon."""
|
||||||
|
return self._rendered is not None and self._rendered.get(CONF_ICON_TEMPLATE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entity_picture(self) -> str | None:
|
||||||
|
"""Return entity picture."""
|
||||||
|
return self._rendered is not None and self._rendered.get(
|
||||||
|
CONF_ENTITY_PICTURE_TEMPLATE
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return availability of the entity."""
|
||||||
|
return (
|
||||||
|
self._rendered is not None
|
||||||
|
and
|
||||||
|
# Check against False so `None` is ok
|
||||||
|
self._rendered.get(CONF_AVAILABILITY_TEMPLATE) is not False
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any] | None:
|
||||||
|
"""Return extra attributes."""
|
||||||
|
return self._rendered.get(CONF_ATTRIBUTE_TEMPLATES)
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Handle being added to Home Assistant."""
|
||||||
|
template.attach(self.hass, self._config)
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
if self.coordinator.data is not None:
|
||||||
|
self._handle_coordinator_update()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
"""Handle updated data from the coordinator."""
|
||||||
|
try:
|
||||||
|
rendered = {}
|
||||||
|
|
||||||
|
for key in self._to_render:
|
||||||
|
rendered[key] = self._config[key].async_render(
|
||||||
|
self.coordinator.data["run_variables"], parse_result=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ATTRIBUTE_TEMPLATES in self._config:
|
||||||
|
rendered[CONF_ATTRIBUTE_TEMPLATES] = template.render_complex(
|
||||||
|
self._config[CONF_ATTRIBUTE_TEMPLATES],
|
||||||
|
self.coordinator.data["run_variables"],
|
||||||
|
)
|
||||||
|
|
||||||
|
self._rendered = rendered
|
||||||
|
except template.TemplateError as err:
|
||||||
|
logging.getLogger(f"{__package__}.{self.entity_id.split('.')[0]}").error(
|
||||||
|
"Error rendering %s template for %s: %s", key, self.entity_id, err
|
||||||
|
)
|
||||||
|
self._rendered = None
|
||||||
|
|
||||||
|
self.async_set_context(self.coordinator.data["context"])
|
||||||
|
self.async_write_ha_state()
|
|
@ -15,8 +15,10 @@ from homeassistant.const import (
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import CoreState, callback
|
from homeassistant.core import Context, CoreState, callback
|
||||||
|
from homeassistant.helpers import entity_registry
|
||||||
from homeassistant.helpers.template import Template
|
from homeassistant.helpers.template import Template
|
||||||
from homeassistant.setup import ATTR_COMPONENT, async_setup_component
|
from homeassistant.setup import ATTR_COMPONENT, async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
@ -986,3 +988,126 @@ async def test_duplicate_templates(hass):
|
||||||
state = hass.states.get("sensor.test_template_sensor")
|
state = hass.states.get("sensor.test_template_sensor")
|
||||||
assert state.attributes["friendly_name"] == "Def"
|
assert state.attributes["friendly_name"] == "Def"
|
||||||
assert state.state == "Def"
|
assert state.state == "Def"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_trigger_entity(hass):
|
||||||
|
"""Test trigger entity works."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"template",
|
||||||
|
{
|
||||||
|
"template": [
|
||||||
|
{"invalid": "config"},
|
||||||
|
# This one should still be set up
|
||||||
|
{
|
||||||
|
"unique_id": "listening-test-event",
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"sensors": {
|
||||||
|
"hello": {
|
||||||
|
"friendly_name": "Hello Name",
|
||||||
|
"unique_id": "just_a_test",
|
||||||
|
"device_class": "battery",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"value_template": "{{ trigger.event.data.beer }}",
|
||||||
|
"entity_picture_template": "{{ '/local/dogs.png' }}",
|
||||||
|
"icon_template": "{{ 'mdi:pirate' }}",
|
||||||
|
"attribute_templates": {
|
||||||
|
"plus_one": "{{ trigger.event.data.beer + 1 }}"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.hello")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
context = Context()
|
||||||
|
hass.bus.async_fire("test_event", {"beer": 2}, context=context)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.hello")
|
||||||
|
assert state.state == "2"
|
||||||
|
assert state.attributes.get("device_class") == "battery"
|
||||||
|
assert state.attributes.get("icon") == "mdi:pirate"
|
||||||
|
assert state.attributes.get("entity_picture") == "/local/dogs.png"
|
||||||
|
assert state.attributes.get("plus_one") == 3
|
||||||
|
assert state.attributes.get("unit_of_measurement") == "%"
|
||||||
|
assert state.context is context
|
||||||
|
|
||||||
|
ent_reg = entity_registry.async_get(hass)
|
||||||
|
assert len(ent_reg.entities) == 1
|
||||||
|
assert (
|
||||||
|
ent_reg.entities["sensor.hello"].unique_id == "listening-test-event-just_a_test"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_trigger_entity_render_error(hass):
|
||||||
|
"""Test trigger entity handles render error."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"template",
|
||||||
|
{
|
||||||
|
"template": {
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"sensors": {
|
||||||
|
"hello": {
|
||||||
|
"unique_id": "no-base-id",
|
||||||
|
"friendly_name": "Hello",
|
||||||
|
"value_template": "{{ non_existing + 1 }}",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.hello")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
context = Context()
|
||||||
|
hass.bus.async_fire("test_event", {"beer": 2}, context=context)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.hello")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
ent_reg = entity_registry.async_get(hass)
|
||||||
|
assert len(ent_reg.entities) == 1
|
||||||
|
assert ent_reg.entities["sensor.hello"].unique_id == "no-base-id"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_trigger_not_allowed_platform_config(hass, caplog):
|
||||||
|
"""Test we throw a helpful warning if a trigger is configured in platform config."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
sensor.DOMAIN,
|
||||||
|
{
|
||||||
|
"sensor": {
|
||||||
|
"platform": "template",
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"sensors": {
|
||||||
|
"test_template_sensor": {
|
||||||
|
"value_template": "{{ states.sensor.test_state.state }}",
|
||||||
|
"friendly_name_template": "{{ states.sensor.test_state.state }}",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.test_template_sensor")
|
||||||
|
assert state is None
|
||||||
|
assert (
|
||||||
|
"You can only add triggers to template entities if they are defined under `template:`."
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue