Simplify template integration entities (#39083)

pull/39109/head
J. Nick Koston 2020-08-21 07:33:53 -05:00 committed by GitHub
parent 5a9246468e
commit 796d74886e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 120 additions and 157 deletions

View File

@ -189,20 +189,20 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity):
return self._code_arm_required return self._code_arm_required
@callback @callback
def _update_state(self, state): def _update_state(self, result):
if isinstance(state, TemplateError): if isinstance(result, TemplateError):
self._state = None self._state = None
return return
# Validate state # Validate state
if state in _VALID_STATES: if result in _VALID_STATES:
self._state = state self._state = result
_LOGGER.debug("Valid state - %s", state) _LOGGER.debug("Valid state - %s", result)
return return
_LOGGER.error( _LOGGER.error(
"Received invalid alarm panel state: %s. Expected: %s", "Received invalid alarm panel state: %s. Expected: %s",
state, result,
", ".join(_VALID_STATES), ", ".join(_VALID_STATES),
) )
self._state = None self._state = None

View File

@ -27,7 +27,7 @@ from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.template import result_as_boolean from homeassistant.helpers.template import result_as_boolean
from .const import CONF_AVAILABILITY_TEMPLATE from .const import CONF_AVAILABILITY_TEMPLATE
from .template_entity import TemplateEntityWithAttributesAvailabilityAndImages from .template_entity import TemplateEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -93,9 +93,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
async_add_entities(sensors) async_add_entities(sensors)
class BinarySensorTemplate( class BinarySensorTemplate(TemplateEntity, BinarySensorEntity):
TemplateEntityWithAttributesAvailabilityAndImages, BinarySensorEntity
):
"""A virtual binary sensor that triggers from another sensor.""" """A virtual binary sensor that triggers from another sensor."""
def __init__( def __init__(
@ -115,10 +113,10 @@ class BinarySensorTemplate(
): ):
"""Initialize the Template binary sensor.""" """Initialize the Template binary sensor."""
super().__init__( super().__init__(
attribute_templates, attribute_templates=attribute_templates,
availability_template, availability_template=availability_template,
icon_template, icon_template=icon_template,
entity_picture_template, entity_picture_template=entity_picture_template,
) )
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device, hass=hass) self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device, hass=hass)
self._name = friendly_name self._name = friendly_name

View File

@ -38,7 +38,7 @@ from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from .const import CONF_AVAILABILITY_TEMPLATE from .const import CONF_AVAILABILITY_TEMPLATE
from .template_entity import TemplateEntityWithAvailabilityAndImages from .template_entity import TemplateEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_VALID_STATES = [STATE_OPEN, STATE_CLOSED, "true", "false"] _VALID_STATES = [STATE_OPEN, STATE_CLOSED, "true", "false"]
@ -148,7 +148,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
async_add_entities(covers) async_add_entities(covers)
class CoverTemplate(TemplateEntityWithAvailabilityAndImages, CoverEntity): class CoverTemplate(TemplateEntity, CoverEntity):
"""Representation of a Template cover.""" """Representation of a Template cover."""
def __init__( def __init__(
@ -174,7 +174,9 @@ class CoverTemplate(TemplateEntityWithAvailabilityAndImages, CoverEntity):
): ):
"""Initialize the Template cover.""" """Initialize the Template cover."""
super().__init__( super().__init__(
availability_template, icon_template, entity_picture_template, availability_template=availability_template,
icon_template=icon_template,
entity_picture_template=entity_picture_template,
) )
self.entity_id = async_generate_entity_id( self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, device_id, hass=hass ENTITY_ID_FORMAT, device_id, hass=hass

View File

@ -35,7 +35,7 @@ from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from .const import CONF_AVAILABILITY_TEMPLATE from .const import CONF_AVAILABILITY_TEMPLATE
from .template_entity import TemplateEntityWithAvailability from .template_entity import TemplateEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -125,7 +125,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
async_add_entities(fans) async_add_entities(fans)
class TemplateFan(TemplateEntityWithAvailability, FanEntity): class TemplateFan(TemplateEntity, FanEntity):
"""A template fan component.""" """A template fan component."""
def __init__( def __init__(
@ -147,7 +147,7 @@ class TemplateFan(TemplateEntityWithAvailability, FanEntity):
unique_id, unique_id,
): ):
"""Initialize the fan.""" """Initialize the fan."""
super().__init__(availability_template) super().__init__(availability_template=availability_template)
self.hass = hass self.hass = hass
self.entity_id = async_generate_entity_id( self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, device_id, hass=hass ENTITY_ID_FORMAT, device_id, hass=hass

View File

@ -34,7 +34,7 @@ from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from .const import CONF_AVAILABILITY_TEMPLATE from .const import CONF_AVAILABILITY_TEMPLATE
from .template_entity import TemplateEntityWithAvailabilityAndImages from .template_entity import TemplateEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"] _VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"]
@ -131,7 +131,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
async_add_entities(lights) async_add_entities(lights)
class LightTemplate(TemplateEntityWithAvailabilityAndImages, LightEntity): class LightTemplate(TemplateEntity, LightEntity):
"""Representation of a templated Light, including dimmable.""" """Representation of a templated Light, including dimmable."""
def __init__( def __init__(
@ -157,7 +157,9 @@ class LightTemplate(TemplateEntityWithAvailabilityAndImages, LightEntity):
): ):
"""Initialize the light.""" """Initialize the light."""
super().__init__( super().__init__(
availability_template, icon_template, entity_picture_template, availability_template=availability_template,
icon_template=icon_template,
entity_picture_template=entity_picture_template,
) )
self.entity_id = async_generate_entity_id( self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, device_id, hass=hass ENTITY_ID_FORMAT, device_id, hass=hass

View File

@ -18,7 +18,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from .const import CONF_AVAILABILITY_TEMPLATE from .const import CONF_AVAILABILITY_TEMPLATE
from .template_entity import TemplateEntityWithAvailability from .template_entity import TemplateEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -63,7 +63,7 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N
) )
class TemplateLock(TemplateEntityWithAvailability, LockEntity): class TemplateLock(TemplateEntity, LockEntity):
"""Representation of a template lock.""" """Representation of a template lock."""
def __init__( def __init__(
@ -78,7 +78,7 @@ class TemplateLock(TemplateEntityWithAvailability, LockEntity):
unique_id, unique_id,
): ):
"""Initialize the lock.""" """Initialize the lock."""
super().__init__(availability_template) super().__init__(availability_template=availability_template)
self._state = None self._state = None
self._name = name self._name = name
self._state_template = value_template self._state_template = value_template

View File

@ -27,7 +27,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.entity import Entity, async_generate_entity_id
from .const import CONF_AVAILABILITY_TEMPLATE from .const import CONF_AVAILABILITY_TEMPLATE
from .template_entity import TemplateEntityWithAttributesAvailabilityAndImages from .template_entity import TemplateEntity
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates" CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
@ -94,7 +94,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
return True return True
class SensorTemplate(TemplateEntityWithAttributesAvailabilityAndImages, Entity): class SensorTemplate(TemplateEntity, Entity):
"""Representation of a Template Sensor.""" """Representation of a Template Sensor."""
def __init__( def __init__(
@ -114,10 +114,10 @@ class SensorTemplate(TemplateEntityWithAttributesAvailabilityAndImages, Entity):
): ):
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__( super().__init__(
attribute_templates, attribute_templates=attribute_templates,
availability_template, availability_template=availability_template,
icon_template, icon_template=icon_template,
entity_picture_template, entity_picture_template=entity_picture_template,
) )
self.entity_id = async_generate_entity_id( self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, device_id, hass=hass ENTITY_ID_FORMAT, device_id, hass=hass

View File

@ -27,7 +27,7 @@ from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from .const import CONF_AVAILABILITY_TEMPLATE from .const import CONF_AVAILABILITY_TEMPLATE
from .template_entity import TemplateEntityWithAvailabilityAndImages from .template_entity import TemplateEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"] _VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"]
@ -86,9 +86,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
async_add_entities(switches) async_add_entities(switches)
class SwitchTemplate( class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
TemplateEntityWithAvailabilityAndImages, SwitchEntity, RestoreEntity
):
"""Representation of a Template switch.""" """Representation of a Template switch."""
def __init__( def __init__(
@ -105,7 +103,11 @@ class SwitchTemplate(
unique_id, unique_id,
): ):
"""Initialize the Template switch.""" """Initialize the Template switch."""
super().__init__(availability_template, icon_template, entity_picture_template) super().__init__(
availability_template=availability_template,
icon_template=icon_template,
entity_picture_template=entity_picture_template,
)
self.entity_id = async_generate_entity_id( self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, device_id, hass=hass ENTITY_ID_FORMAT, device_id, hass=hass
) )

View File

@ -8,7 +8,6 @@ import voluptuous as vol
from homeassistant.core import EVENT_HOMEASSISTANT_START, callback from homeassistant.core import EVENT_HOMEASSISTANT_START, callback
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import match_all
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import Event, async_track_template_result from homeassistant.helpers.event import Event, async_track_template_result
from homeassistant.helpers.template import Template, result_as_boolean from homeassistant.helpers.template import Template, result_as_boolean
@ -24,7 +23,7 @@ class _TemplateAttribute:
entity: Entity, entity: Entity,
attribute: str, attribute: str,
template: Template, template: Template,
validator: Callable[[Any], Any] = match_all, validator: Callable[[Any], Any] = None,
on_update: Optional[Callable[[Any], None]] = None, on_update: Optional[Callable[[Any], None]] = None,
none_on_template_error: Optional[bool] = False, none_on_template_error: Optional[bool] = False,
): ):
@ -130,20 +129,82 @@ class _TemplateAttribute:
class TemplateEntity(Entity): class TemplateEntity(Entity):
"""Entity that uses templates to calculate attributes.""" """Entity that uses templates to calculate attributes."""
def __init__(self): def __init__(
self,
*,
availability_template=None,
icon_template=None,
entity_picture_template=None,
attribute_templates=None,
):
"""Template Entity.""" """Template Entity."""
self._template_attrs = [] self._template_attrs = []
self._attribute_templates = attribute_templates
self._attributes = {}
self._availability_template = availability_template
self._available = True
self._icon_template = icon_template
self._entity_picture_template = entity_picture_template
self._icon = None
self._entity_picture = None
@property @property
def should_poll(self): def should_poll(self):
"""No polling needed.""" """No polling needed."""
return False return False
@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
@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 device_state_attributes(self):
"""Return the state attributes."""
return self._attributes
@callback
def _add_attribute_template(self, attribute_key, attribute_template):
"""Create a template tracker for the attribute."""
def _update_attribute(result):
attr_result = None if isinstance(result, TemplateError) else result
self._attributes[attribute_key] = attr_result
self.add_template_attribute(
attribute_key, attribute_template, None, _update_attribute
)
def add_template_attribute( def add_template_attribute(
self, self,
attribute: str, attribute: str,
template: Template, template: Template,
validator: Callable[[Any], Any] = match_all, validator: Callable[[Any], Any] = None,
on_update: Optional[Callable[[Any], None]] = None, on_update: Optional[Callable[[Any], None]] = None,
none_on_template_error: bool = False, none_on_template_error: bool = False,
) -> None: ) -> None:
@ -183,79 +244,13 @@ class TemplateEntity(Entity):
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass.""" """Run when entity about to be added to hass."""
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, self._async_template_startup
)
async def async_update(self) -> None:
"""Call for forced update."""
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: if self._availability_template is not None:
self.add_template_attribute( self.add_template_attribute(
"_available", self._availability_template, None, self._update_available "_available", self._availability_template, None, self._update_available
) )
if self._attribute_templates is not None:
await super().async_added_to_hass() for key, value in self._attribute_templates.items():
self._add_attribute_template(key, value)
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: if self._icon_template is not None:
self.add_template_attribute( self.add_template_attribute(
"_icon", self._icon_template, vol.Or(cv.whitespace, cv.icon) "_icon", self._icon_template, vol.Or(cv.whitespace, cv.icon)
@ -265,47 +260,12 @@ class TemplateEntityWithAvailabilityAndImages(TemplateEntityWithAvailability):
"_entity_picture", self._entity_picture_template "_entity_picture", self._entity_picture_template
) )
await super().async_added_to_hass() self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, self._async_template_startup
class TemplateEntityWithAttributesAvailabilityAndImages(
TemplateEntityWithAvailabilityAndImages
):
"""Entity that uses templates to calculate attributes with an attributes, availability, icon, and images template."""
def __init__(
self,
attribute_templates,
availability_template,
icon_template,
entity_picture_template,
):
"""Template Entity."""
super().__init__(availability_template, icon_template, entity_picture_template)
self._attribute_templates = attribute_templates
self._attributes = {}
@callback
def _add_attribute_template(self, attribute_key, attribute_template):
"""Create a template tracker for the attribute."""
def _update_attribute(result):
attr_result = None if isinstance(result, TemplateError) else result
self._attributes[attribute_key] = attr_result
self.add_template_attribute(
attribute_key, attribute_template, None, _update_attribute
) )
@property async def async_update(self) -> None:
def device_state_attributes(self): """Call for forced update."""
"""Return the state attributes.""" for attribute in self._template_attrs:
return self._attributes if attribute.async_update:
attribute.async_update()
async def async_added_to_hass(self):
"""Register callbacks."""
for key, value in self._attribute_templates.items():
self._add_attribute_template(key, value)
await super().async_added_to_hass()

View File

@ -44,7 +44,7 @@ from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from .const import CONF_AVAILABILITY_TEMPLATE from .const import CONF_AVAILABILITY_TEMPLATE
from .template_entity import TemplateEntityWithAttributesAvailabilityAndImages from .template_entity import TemplateEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -141,9 +141,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
async_add_entities(vacuums) async_add_entities(vacuums)
class TemplateVacuum( class TemplateVacuum(TemplateEntity, StateVacuumEntity):
TemplateEntityWithAttributesAvailabilityAndImages, StateVacuumEntity
):
"""A template vacuum component.""" """A template vacuum component."""
def __init__( def __init__(
@ -168,7 +166,8 @@ class TemplateVacuum(
): ):
"""Initialize the vacuum.""" """Initialize the vacuum."""
super().__init__( super().__init__(
attribute_templates, availability_template, None, None, attribute_templates=attribute_templates,
availability_template=availability_template,
) )
self.entity_id = async_generate_entity_id( self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, device_id, hass=hass ENTITY_ID_FORMAT, device_id, hass=hass