diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 427afe6808b..288cc0ece7a 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -9,19 +9,16 @@ from homeassistant.const import ( CONF_OPTIMISTIC, CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, - EVENT_HOMEASSISTANT_START, - MATCH_ALL, STATE_LOCKED, STATE_ON, ) from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.script import Script -from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE +from .template_entity import TemplateEntityWithAvailability _LOGGER = logging.getLogger(__name__) @@ -50,14 +47,6 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N value_template = config.get(CONF_VALUE_TEMPLATE) availability_template = config.get(CONF_AVAILABILITY_TEMPLATE) - templates = { - CONF_VALUE_TEMPLATE: value_template, - CONF_AVAILABILITY_TEMPLATE: availability_template, - } - - initialise_templates(hass, templates) - entity_ids = extract_entities(device, "lock", None, templates) - async_add_devices( [ TemplateLock( @@ -65,7 +54,6 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N device, value_template, availability_template, - entity_ids, config.get(CONF_LOCK), config.get(CONF_UNLOCK), config.get(CONF_OPTIMISTIC), @@ -75,7 +63,7 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N ) -class TemplateLock(LockEntity): +class TemplateLock(TemplateEntityWithAvailability, LockEntity): """Representation of a template lock.""" def __init__( @@ -84,58 +72,27 @@ class TemplateLock(LockEntity): name, value_template, availability_template, - entity_ids, command_lock, command_unlock, optimistic, unique_id, ): """Initialize the lock.""" + super().__init__(availability_template) self._state = None - self._hass = hass self._name = name self._state_template = value_template - self._availability_template = availability_template - self._state_entities = entity_ids domain = __name__.split(".")[-2] self._command_lock = Script(hass, command_lock, name, domain) self._command_unlock = Script(hass, command_unlock, name, domain) self._optimistic = optimistic - self._available = True self._unique_id = unique_id - async def async_added_to_hass(self): - """Register callbacks.""" - - @callback - def template_lock_state_listener(event): - """Handle target device state changes.""" - self.async_schedule_update_ha_state(True) - - @callback - def template_lock_startup(event): - """Update template on startup.""" - if self._state_entities != MATCH_ALL: - # Track state change only for valid templates - async_track_state_change_event( - self._hass, self._state_entities, template_lock_state_listener - ) - self.async_schedule_update_ha_state(True) - - self._hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, template_lock_startup - ) - @property def assumed_state(self): """Return true if we do optimistic updates.""" return self._optimistic - @property - def should_poll(self): - """No polling needed.""" - return False - @property def name(self): """Return the name of the lock.""" @@ -151,35 +108,21 @@ class TemplateLock(LockEntity): """Return true if lock is locked.""" return self._state - @property - def available(self) -> bool: - """Return if the device is available.""" - return self._available - - async def async_update(self): - """Update the state from the template.""" - try: - self._state = self._state_template.async_render().lower() in ( - "true", - STATE_ON, - STATE_LOCKED, - ) - except TemplateError as ex: + @callback + def _update_state(self, result): + super()._update_state(result) + if isinstance(result, TemplateError): self._state = None - _LOGGER.error("Could not render template %s: %s", self._name, ex) + return + self._state = result.lower() in ("true", STATE_ON, STATE_LOCKED) - if self._availability_template is not None: - try: - self._available = ( - self._availability_template.async_render().lower() == "true" - ) - except (TemplateError, ValueError) as ex: - _LOGGER.error( - "Could not render %s template %s: %s", - CONF_AVAILABILITY_TEMPLATE, - self._name, - ex, - ) + async def async_added_to_hass(self): + """Register callbacks.""" + + self.add_template_attribute( + "_state", self._state_template, None, self._update_state + ) + await super().async_added_to_hass() async def async_lock(self, **kwargs): """Lock the device.""" diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index 9c317b27647..a68833b6aa1 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -25,10 +25,9 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity, async_generate_entity_id -from homeassistant.helpers.template import result_as_boolean from .const import CONF_AVAILABILITY_TEMPLATE -from .template_entity import TemplateEntity +from .template_entity import TemplateEntityWithAvailability CONF_ATTRIBUTE_TEMPLATES = "attribute_templates" @@ -95,7 +94,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return True -class SensorTemplate(TemplateEntity, Entity): +class SensorTemplate(TemplateEntityWithAvailability, Entity): """Representation of a Template Sensor.""" def __init__( @@ -114,7 +113,7 @@ class SensorTemplate(TemplateEntity, Entity): unique_id, ): """Initialize the sensor.""" - self.hass = hass + super().__init__(availability_template) self.entity_id = async_generate_entity_id( ENTITY_ID_FORMAT, device_id, hass=hass ) @@ -125,15 +124,12 @@ class SensorTemplate(TemplateEntity, Entity): self._state = None self._icon_template = icon_template self._entity_picture_template = entity_picture_template - self._availability_template = availability_template self._icon = None self._entity_picture = None self._device_class = device_class - self._available = True self._attribute_templates = attribute_templates self._attributes = {} self._unique_id = unique_id - super().__init__() async def async_added_to_hass(self): """Register callbacks.""" @@ -149,10 +145,6 @@ class SensorTemplate(TemplateEntity, Entity): ) if self._friendly_name_template is not None: self.add_template_attribute("_name", self._friendly_name_template) - if self._availability_template is not None: - self.add_template_attribute( - "_available", self._availability_template, None, self._update_available - ) for key, value in self._attribute_templates.items(): self._add_attribute_template(key, value) @@ -171,23 +163,8 @@ class SensorTemplate(TemplateEntity, Entity): @callback def _update_state(self, result): - if isinstance(result, TemplateError): - if not self._availability_template: - self._available = False - self._state = None - return - - if not self._availability_template: - self._available = True - self._state = result - - @callback - def _update_available(self, result): - if isinstance(result, TemplateError): - self._available = True - return - - self._available = result_as_boolean(result) + super()._update_state(result) + self._state = None if isinstance(result, TemplateError) else result @property def name(self): @@ -224,17 +201,7 @@ class SensorTemplate(TemplateEntity, Entity): """Return the unit_of_measurement of the device.""" return self._unit_of_measurement - @property - def available(self) -> bool: - """Return if the device is available.""" - return self._available - @property def device_state_attributes(self): """Return the state attributes.""" return self._attributes - - @property - def should_poll(self): - """No polling needed.""" - return False diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 810b0277a58..d700ab4947e 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -10,7 +10,7 @@ from homeassistant.exceptions import TemplateError from homeassistant.helpers.config_validation import match_all from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import Event, async_track_template_result -from homeassistant.helpers.template import Template +from homeassistant.helpers.template import Template, result_as_boolean _LOGGER = logging.getLogger(__name__) @@ -127,6 +127,11 @@ class TemplateEntity(Entity): """Template Entity.""" self._template_attrs = [] + @property + def should_poll(self): + """No polling needed.""" + return False + def add_template_attribute( self, attribute: str, @@ -177,3 +182,42 @@ class TemplateEntity(Entity): for attribute in self._template_attrs: if attribute.async_update: attribute.async_update() + + +class TemplateEntityWithAvailability(TemplateEntity): + """Entity that uses templates to calculate attributes with an availability template.""" + + def __init__(self, availability_template): + """Template Entity.""" + self._availability_template = availability_template + self._available = True + super().__init__() + + @callback + def _update_available(self, result): + if isinstance(result, TemplateError): + self._available = True + return + + self._available = result_as_boolean(result) + + @callback + def _update_state(self, result): + if self._availability_template: + return + + self._available = not isinstance(result, TemplateError) + + @property + def available(self) -> bool: + """Return if the device is available.""" + return self._available + + async def async_added_to_hass(self): + """Register callbacks.""" + if self._availability_template is not None: + self.add_template_attribute( + "_available", self._availability_template, None, self._update_available + ) + + await super().async_added_to_hass() diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index 4c15babdfe2..db1be64c379 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -231,8 +231,8 @@ class TestTemplateLock: assert self.hass.states.all() == [] - def test_no_template_match_all(self, caplog): - """Test that we do not allow locks that match on all.""" + def test_template_static(self, caplog): + """Test that we allow static templates.""" with assert_setup_component(1, "lock"): assert setup.setup_component( self.hass, @@ -260,12 +260,6 @@ class TestTemplateLock: state = self.hass.states.get("lock.template_lock") assert state.state == lock.STATE_UNLOCKED - assert ( - "Template lock 'Template Lock' has no entity ids configured to track " - "nor were we able to extract the entities to track from the value " - "template(s). This entity will only be able to be updated manually" - ) in caplog.text - self.hass.states.set("lock.template_lock", lock.STATE_LOCKED) self.hass.block_till_done() state = self.hass.states.get("lock.template_lock")