diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index a68833b6aa1..3ffd99e58fd 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -27,7 +27,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity, async_generate_entity_id from .const import CONF_AVAILABILITY_TEMPLATE -from .template_entity import TemplateEntityWithAvailability +from .template_entity import TemplateEntityWithAvailabilityAndImages CONF_ATTRIBUTE_TEMPLATES = "attribute_templates" @@ -94,7 +94,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return True -class SensorTemplate(TemplateEntityWithAvailability, Entity): +class SensorTemplate(TemplateEntityWithAvailabilityAndImages, Entity): """Representation of a Template Sensor.""" def __init__( @@ -113,7 +113,7 @@ class SensorTemplate(TemplateEntityWithAvailability, Entity): unique_id, ): """Initialize the sensor.""" - super().__init__(availability_template) + super().__init__(availability_template, icon_template, entity_picture_template) self.entity_id = async_generate_entity_id( ENTITY_ID_FORMAT, device_id, hass=hass ) @@ -122,10 +122,6 @@ class SensorTemplate(TemplateEntityWithAvailability, Entity): self._unit_of_measurement = unit_of_measurement self._template = state_template self._state = None - self._icon_template = icon_template - self._entity_picture_template = entity_picture_template - self._icon = None - self._entity_picture = None self._device_class = device_class self._attribute_templates = attribute_templates self._attributes = {} @@ -135,14 +131,6 @@ class SensorTemplate(TemplateEntityWithAvailability, Entity): """Register callbacks.""" self.add_template_attribute("_state", self._template, None, self._update_state) - if self._icon_template is not None: - self.add_template_attribute( - "_icon", self._icon_template, vol.Or(cv.whitespace, cv.icon) - ) - if self._entity_picture_template is not None: - self.add_template_attribute( - "_entity_picture", self._entity_picture_template - ) if self._friendly_name_template is not None: self.add_template_attribute("_name", self._friendly_name_template) @@ -181,21 +169,11 @@ class SensorTemplate(TemplateEntityWithAvailability, Entity): """Return the state of the sensor.""" return self._state - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - return self._icon - @property def device_class(self) -> Optional[str]: """Return the device class of the sensor.""" return self._device_class - @property - def entity_picture(self): - """Return the entity_picture to use in the frontend, if any.""" - return self._entity_picture - @property def unit_of_measurement(self): """Return the unit_of_measurement of the device.""" diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 5bd5f69cc7b..a041ecfe146 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -16,8 +16,6 @@ from homeassistant.const import ( CONF_SWITCHES, CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, - EVENT_HOMEASSISTANT_START, - MATCH_ALL, STATE_OFF, STATE_ON, ) @@ -25,12 +23,11 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import Script -from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE +from .template_entity import TemplateEntityWithAvailabilityAndImages _LOGGER = logging.getLogger(__name__) _VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"] @@ -71,18 +68,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= off_action = device_config[OFF_ACTION] unique_id = device_config.get(CONF_UNIQUE_ID) - templates = { - CONF_VALUE_TEMPLATE: state_template, - CONF_ICON_TEMPLATE: icon_template, - CONF_ENTITY_PICTURE_TEMPLATE: entity_picture_template, - CONF_AVAILABILITY_TEMPLATE: availability_template, - } - - initialise_templates(hass, templates) - entity_ids = extract_entities( - device, "switch", device_config.get(ATTR_ENTITY_ID), templates - ) - switches.append( SwitchTemplate( hass, @@ -94,7 +79,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= availability_template, on_action, off_action, - entity_ids, unique_id, ) ) @@ -102,7 +86,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(switches) -class SwitchTemplate(SwitchEntity, RestoreEntity): +class SwitchTemplate( + TemplateEntityWithAvailabilityAndImages, SwitchEntity, RestoreEntity +): """Representation of a Template switch.""" def __init__( @@ -116,11 +102,10 @@ class SwitchTemplate(SwitchEntity, RestoreEntity): availability_template, on_action, off_action, - entity_ids, unique_id, ): """Initialize the Template switch.""" - self.hass = hass + super().__init__(availability_template, icon_template, entity_picture_template) self.entity_id = async_generate_entity_id( ENTITY_ID_FORMAT, device_id, hass=hass ) @@ -130,15 +115,16 @@ class SwitchTemplate(SwitchEntity, RestoreEntity): self._on_script = Script(hass, on_action, friendly_name, domain) self._off_script = Script(hass, off_action, friendly_name, domain) self._state = False - 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._entities = entity_ids - self._available = True self._unique_id = unique_id + @callback + def _update_state(self, result): + super()._update_state(result) + if isinstance(result, TemplateError): + self._state = None + return + self._state = result.lower() in ("true", STATE_ON) + async def async_added_to_hass(self): """Register callbacks.""" @@ -151,28 +137,12 @@ class SwitchTemplate(SwitchEntity, RestoreEntity): self._state = state.state == STATE_ON # no need to listen for events - return + else: + self.add_template_attribute( + "_state", self._template, None, self._update_state + ) - # set up event listening - @callback - def template_switch_state_listener(event): - """Handle target device state changes.""" - self.async_schedule_update_ha_state(True) - - @callback - def template_switch_startup(event): - """Update template on startup.""" - if self._entities != MATCH_ALL: - # Track state change only for valid templates - async_track_state_change_event( - self.hass, self._entities, template_switch_state_listener - ) - - self.async_schedule_update_ha_state(True) - - self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, template_switch_startup - ) + await super().async_added_to_hass() @property def name(self): @@ -194,21 +164,6 @@ class SwitchTemplate(SwitchEntity, RestoreEntity): """Return the polling state.""" return False - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - return self._icon - - @property - def entity_picture(self): - """Return the entity_picture to use in the frontend, if any.""" - return self._entity_picture - - @property - def available(self) -> bool: - """Return if the device is available.""" - return self._available - async def async_turn_on(self, **kwargs): """Fire the on action.""" await self._on_script.async_run(context=self._context) @@ -223,63 +178,6 @@ class SwitchTemplate(SwitchEntity, RestoreEntity): self._state = False self.async_write_ha_state() - async def async_update(self): - """Update the state from the template.""" - if self._template is None: - return - try: - state = self._template.async_render().lower() - - if state in _VALID_STATES: - self._state = state in ("true", STATE_ON) - else: - _LOGGER.error( - "Received invalid switch is_on state: %s. Expected: %s", - state, - ", ".join(_VALID_STATES), - ) - self._state = None - - except TemplateError as ex: - _LOGGER.error(ex) - self._state = None - - for property_name, template in ( - ("_icon", self._icon_template), - ("_entity_picture", self._entity_picture_template), - ("_available", self._availability_template), - ): - if template is None: - continue - - try: - value = template.async_render() - if property_name == "_available": - value = value.lower() == "true" - setattr(self, property_name, value) - except TemplateError as ex: - friendly_property_name = property_name[1:].replace("_", " ") - if ex.args and ex.args[0].startswith( - "UndefinedError: 'None' has no attribute" - ): - # Common during HA startup - so just a warning - _LOGGER.warning( - "Could not render %s template %s, the state is unknown", - friendly_property_name, - self._name, - ) - return - - try: - setattr(self, property_name, getattr(super(), property_name)) - except AttributeError: - _LOGGER.error( - "Could not render %s template %s: %s", - friendly_property_name, - self._name, - ex, - ) - @property def assumed_state(self): """State is assumed, if no template given.""" diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index d700ab4947e..748a130d064 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant.core import EVENT_HOMEASSISTANT_START, callback from homeassistant.exceptions import TemplateError +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import match_all from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import Event, async_track_template_result @@ -221,3 +222,38 @@ class TemplateEntityWithAvailability(TemplateEntity): ) await super().async_added_to_hass() + + +class TemplateEntityWithAvailabilityAndImages(TemplateEntityWithAvailability): + """Entity that uses templates to calculate attributes with an availability, icon, and images template.""" + + def __init__(self, availability_template, icon_template, entity_picture_template): + """Template Entity.""" + self._icon_template = icon_template + self._entity_picture_template = entity_picture_template + self._icon = None + self._entity_picture = None + super().__init__(availability_template) + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return self._icon + + @property + def entity_picture(self): + """Return the entity_picture to use in the frontend, if any.""" + return self._entity_picture + + async def async_added_to_hass(self): + """Register callbacks.""" + if self._icon_template is not None: + self.add_template_attribute( + "_icon", self._icon_template, vol.Or(cv.whitespace, cv.icon) + ) + if self._entity_picture_template is not None: + self.add_template_attribute( + "_entity_picture", self._entity_picture_template + ) + + await super().async_added_to_hass()