Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
59 changes: 56 additions & 3 deletions homeassistant/components/template/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,76 @@
import logging
from typing import Optional

import voluptuous as vol

from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START
from homeassistant.core import CoreState, callback
from homeassistant.helpers import (
config_validation as cv,
discovery,
entity_component,
trigger as trigger_helper,
update_coordinator,
)
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.service import async_register_admin_service

from .const import CONF_TRIGGER, DOMAIN, PLATFORMS

ATTR_VARIABLES = "variables"
SERVICE_TRIGGER = "trigger"


async def async_setup(hass, config):
"""Set up the template integration."""
coordinators = []

if DOMAIN in config:
for conf in config[DOMAIN]:
coordinator = TriggerUpdateCoordinator(hass, conf)
await coordinator.async_setup(config)
coordinators.append(coordinator)

async def trigger_coordinator(service_call):
"""Trigger an update coordinator."""
entity_id = service_call.data[ATTR_ENTITY_ID]

found = None

for coordinator in coordinators:
if entity_id in coordinator.entity_ids:
found = coordinator
break

if found is None:
if ATTR_VARIABLES in service_call.data:
raise vol.Invalid(
"Passing variables to state machine based template entities is not allowed"
)
await entity_component.async_update_entity(hass, entity_id)
return

found.handle_triggered(
{
**service_call.data.get(ATTR_VARIABLES, {}),
"trigger": {"platform": None},
},
context=service_call.context,
)

async_register_admin_service(
hass,
DOMAIN,
SERVICE_TRIGGER,
trigger_coordinator,
vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Optional(ATTR_VARIABLES): dict,
}
),
)

await async_setup_reload_service(hass, DOMAIN, PLATFORMS)

Expand All @@ -37,6 +88,7 @@ def __init__(self, hass, config):
)
self.config = config
self._unsub_trigger = None
self.entity_ids = set()

@property
def unique_id(self) -> Optional[str]:
Expand Down Expand Up @@ -68,15 +120,16 @@ async def _attach_triggers(self, start_event=None) -> None:
self._unsub_trigger = await trigger_helper.async_initialize_triggers(
self.hass,
self.config[CONF_TRIGGER],
self._handle_triggered,
self.handle_triggered,
DOMAIN,
self.name,
self.logger.log,
start_event is not None,
)

@callback
def _handle_triggered(self, run_variables, context=None):
def handle_triggered(self, run_variables, context=None):
"""Handle a trigger firing."""
self.async_set_updated_data(
{"run_variables": run_variables, "context": context}
)
13 changes: 13 additions & 0 deletions homeassistant/components/template/services.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
reload:
description: Reload all template entities.

trigger:
name: "Trigger updating an entity"
description: Trigger an update of a template entity. If the entity updates based on a trigger, the whole section that configured this entity will be updated.
target:
entity:
integration: template
fields:
variables:
name: Variables
description: Variables to make available to the trigger-based entity.
required: false
selector:
object:
2 changes: 2 additions & 0 deletions homeassistant/components/template/trigger_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ def extra_state_attributes(self) -> dict[str, Any] | None:
async def async_added_to_hass(self) -> None:
"""Handle being added to Home Assistant."""
template.attach(self.hass, self._config)
self.coordinator.entity_ids.add(self.entity_id)
self.async_on_remove(lambda: self.coordinator.entity_ids.remove(self.entity_id))
await super().async_added_to_hass()
if self.coordinator.data is not None:
self._handle_coordinator_update()
Expand Down
69 changes: 69 additions & 0 deletions tests/components/template/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
from os import path
from unittest.mock import patch

import pytest
import voluptuous as vol

from homeassistant import config
from homeassistant.components.template import DOMAIN
from homeassistant.core import Context
from homeassistant.helpers.reload import SERVICE_RELOAD
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
Expand Down Expand Up @@ -305,3 +309,68 @@ async def test_reload_sensors_that_reference_other_template_sensors(hass):

def _get_fixtures_base_path():
return path.dirname(path.dirname(path.dirname(__file__)))


async def test_manual_trigger_entity(hass):
"""Test manually triggering an entity."""
assert await async_setup_component(
hass,
"template",
{
"template": {
"trigger": [],
"sensors": {
"trigger_based": {
"value_template": "{{ my_var.count }}",
}
},
},
"sensor": {
"platform": "template",
"sensors": {
"state_based": {
"value_template": "{{ now() }}",
}
},
},
},
)
await hass.async_block_till_done()

orig_state_sensor = hass.states.get("sensor.state_based")

# Update trigger one
context = Context()
await hass.services.async_call(
"template",
"trigger",
{"entity_id": "sensor.trigger_based", "variables": {"my_var": {"count": 5}}},
context=context,
blocking=True,
)

state = hass.states.get("sensor.trigger_based")
assert state is not None
assert state.state == "5"
assert state.context is context

# Update state machine one
with pytest.raises(vol.Invalid):
await hass.services.async_call(
"template",
"trigger",
{"entity_id": "sensor.state_based", "variables": {}},
context=context,
blocking=True,
)

await hass.services.async_call(
"template",
"trigger",
{"entity_id": "sensor.state_based"},
context=context,
blocking=True,
)

state = hass.states.get("sensor.state_based")
assert state.state != orig_state_sensor.state