Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion homeassistant/components/template/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,80 @@
"""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 .const import DOMAIN, PLATFORMS
from .const import CONF_TRIGGER, DOMAIN, PLATFORMS


async def async_setup(hass, config):
"""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)

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}
)
47 changes: 47 additions & 0 deletions homeassistant/components/template/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""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]
)
trigger_entity_configs.append(cfg)
except vol.Invalid as err:
async_log_exception(err, DOMAIN, cfg, hass)
Comment thread
balloob marked this conversation as resolved.
Outdated

# 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
2 changes: 2 additions & 0 deletions homeassistant/components/template/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Constants for the Template Platform Components."""

CONF_AVAILABILITY_TEMPLATE = "availability_template"
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
CONF_TRIGGER = "trigger"

DOMAIN = "template"

Expand Down
63 changes: 49 additions & 14 deletions homeassistant/components/template/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,24 @@
)
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
ATTR_UNIT_OF_MEASUREMENT,
CONF_DEVICE_CLASS,
CONF_ENTITY_PICTURE_TEMPLATE,
CONF_FRIENDLY_NAME,
CONF_FRIENDLY_NAME_TEMPLATE,
CONF_ICON_TEMPLATE,
CONF_SENSORS,
CONF_UNIQUE_ID,
CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE_TEMPLATE,
)
from homeassistant.core import callback
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 .const import CONF_AVAILABILITY_TEMPLATE
from .const import CONF_ATTRIBUTE_TEMPLATES, CONF_AVAILABILITY_TEMPLATE, CONF_TRIGGER
from .template_entity import TemplateEntity

CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
from .trigger_entity import TriggerEntity

SENSOR_SCHEMA = vol.All(
cv.deprecated(ATTR_ENTITY_ID),
Expand All @@ -43,21 +42,40 @@
vol.Optional(CONF_ATTRIBUTE_TEMPLATES, default={}): vol.Schema(
{cv.string: cv.template}
),
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
),
)

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,
Comment thread
balloob marked this conversation as resolved.
Outdated
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."""
sensors = []

Expand All @@ -66,9 +84,9 @@ async def _async_create_entities(hass, config):
icon_template = device_config.get(CONF_ICON_TEMPLATE)
entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_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)
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)
attribute_templates = device_config[CONF_ATTRIBUTE_TEMPLATES]
unique_id = device_config.get(CONF_UNIQUE_ID)
Expand All @@ -95,7 +113,13 @@ async def _async_create_entities(hass, config):

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""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):
Expand Down Expand Up @@ -172,3 +196,14 @@ def device_class(self) -> str | None:
def unit_of_measurement(self):
"""Return the unit_of_measurement of the device."""
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)
Loading