mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 07:05:48 +01:00
Update binary_sensor template platform to use new template framework (#159650)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
@@ -355,16 +355,3 @@ class TriggerAlarmControlPanelEntity(TriggerEntity, AbstractTemplateAlarmControl
|
||||
"""Restore last state."""
|
||||
await super().async_added_to_hass()
|
||||
await self._async_handle_restored_state()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle update of the data."""
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
if self.handle_rendered_result(CONF_STATE):
|
||||
self.async_set_context(self.coordinator.data["context"])
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from functools import partial
|
||||
@@ -183,8 +184,32 @@ class AbstractTemplateBinarySensor(
|
||||
|
||||
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
|
||||
self._template: template.Template = config[CONF_STATE]
|
||||
self._delay_on = None
|
||||
self._delay_off = None
|
||||
self._delay_cancel: CALLBACK_TYPE | None = None
|
||||
|
||||
self.setup_state_template(
|
||||
CONF_STATE,
|
||||
"_attr_is_on",
|
||||
on_update=self._update_state,
|
||||
)
|
||||
self._delay_on = None
|
||||
try:
|
||||
self._delay_on = cv.positive_time_period(config.get(CONF_DELAY_ON))
|
||||
except vol.Invalid:
|
||||
self.setup_template(CONF_DELAY_ON, "_delay_on", cv.positive_time_period)
|
||||
|
||||
self._delay_off = None
|
||||
try:
|
||||
self._delay_off = cv.positive_time_period(config.get(CONF_DELAY_OFF))
|
||||
except vol.Invalid:
|
||||
self.setup_template(CONF_DELAY_OFF, "_delay_off", cv.positive_time_period)
|
||||
|
||||
@callback
|
||||
@abstractmethod
|
||||
def _update_state(self, result: Any) -> None:
|
||||
"""Update the state."""
|
||||
|
||||
|
||||
class StateBinarySensorEntity(TemplateEntity, AbstractTemplateBinarySensor):
|
||||
"""A virtual binary sensor that triggers from another sensor."""
|
||||
@@ -200,17 +225,15 @@ class StateBinarySensorEntity(TemplateEntity, AbstractTemplateBinarySensor):
|
||||
"""Initialize the Template binary sensor."""
|
||||
TemplateEntity.__init__(self, hass, config, unique_id)
|
||||
AbstractTemplateBinarySensor.__init__(self, config)
|
||||
self._delay_on = None
|
||||
self._delay_on_template = config.get(CONF_DELAY_ON)
|
||||
self._delay_off = None
|
||||
self._delay_off_template = config.get(CONF_DELAY_OFF)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore state."""
|
||||
if (
|
||||
(
|
||||
self._delay_on_template is not None
|
||||
or self._delay_off_template is not None
|
||||
CONF_DELAY_ON in self._templates
|
||||
or CONF_DELAY_OFF in self._templates
|
||||
or self._delay_on is not None
|
||||
or self._delay_off is not None
|
||||
)
|
||||
and (last_state := await self.async_get_last_state()) is not None
|
||||
and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
|
||||
@@ -218,29 +241,6 @@ class StateBinarySensorEntity(TemplateEntity, AbstractTemplateBinarySensor):
|
||||
self._attr_is_on = last_state.state == STATE_ON
|
||||
await super().async_added_to_hass()
|
||||
|
||||
@callback
|
||||
def _async_setup_templates(self) -> None:
|
||||
"""Set up templates."""
|
||||
self.add_template_attribute("_state", self._template, None, self._update_state)
|
||||
|
||||
if self._delay_on_template is not None:
|
||||
try:
|
||||
self._delay_on = cv.positive_time_period(self._delay_on_template)
|
||||
except vol.Invalid:
|
||||
self.add_template_attribute(
|
||||
"_delay_on", self._delay_on_template, cv.positive_time_period
|
||||
)
|
||||
|
||||
if self._delay_off_template is not None:
|
||||
try:
|
||||
self._delay_off = cv.positive_time_period(self._delay_off_template)
|
||||
except vol.Invalid:
|
||||
self.add_template_attribute(
|
||||
"_delay_off", self._delay_off_template, cv.positive_time_period
|
||||
)
|
||||
|
||||
super()._async_setup_templates()
|
||||
|
||||
@callback
|
||||
def _update_state(self, result):
|
||||
super()._update_state(result)
|
||||
@@ -291,15 +291,11 @@ class TriggerBinarySensorEntity(TriggerEntity, AbstractTemplateBinarySensor):
|
||||
TriggerEntity.__init__(self, hass, coordinator, config)
|
||||
AbstractTemplateBinarySensor.__init__(self, config)
|
||||
|
||||
for key in (CONF_STATE, CONF_DELAY_ON, CONF_DELAY_OFF, CONF_AUTO_OFF):
|
||||
if isinstance(config.get(key), template.Template):
|
||||
self._to_render_simple.append(key)
|
||||
self._parse_result.add(key)
|
||||
|
||||
self._last_delay_from: bool | None = None
|
||||
self._last_delay_to: bool | None = None
|
||||
self._auto_off_cancel: CALLBACK_TYPE | None = None
|
||||
self._auto_off_time: datetime | None = None
|
||||
self.setup_template(CONF_AUTO_OFF, "_auto_off_time", cv.positive_time_period)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
@@ -329,26 +325,7 @@ class TriggerBinarySensorEntity(TriggerEntity, AbstractTemplateBinarySensor):
|
||||
self._set_auto_off(auto_off_time)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle update of the data."""
|
||||
self._process_data()
|
||||
|
||||
raw = self._rendered.get(CONF_STATE)
|
||||
state: bool | None = None
|
||||
if raw is not None:
|
||||
state = template.result_as_boolean(raw)
|
||||
|
||||
key = CONF_DELAY_ON if state else CONF_DELAY_OFF
|
||||
delay = self._rendered.get(key) or self._config.get(key)
|
||||
|
||||
if (
|
||||
self._delay_cancel
|
||||
and delay
|
||||
and self._attr_is_on == self._last_delay_from
|
||||
and state == self._last_delay_to
|
||||
):
|
||||
return
|
||||
|
||||
def _cancel_delays(self):
|
||||
if self._delay_cancel:
|
||||
self._delay_cancel()
|
||||
self._delay_cancel = None
|
||||
@@ -358,10 +335,27 @@ class TriggerBinarySensorEntity(TriggerEntity, AbstractTemplateBinarySensor):
|
||||
self._auto_off_cancel = None
|
||||
self._auto_off_time = None
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
@callback
|
||||
def _update_state(self, result):
|
||||
state: bool | None = None
|
||||
if result is not None:
|
||||
state = template.result_as_boolean(result)
|
||||
|
||||
if state:
|
||||
delay = self._rendered.get(CONF_DELAY_ON) or self._delay_on
|
||||
else:
|
||||
delay = self._rendered.get(CONF_DELAY_OFF) or self._delay_off
|
||||
|
||||
if (
|
||||
self._delay_cancel
|
||||
and delay
|
||||
and self._attr_is_on == self._last_delay_from
|
||||
and state == self._last_delay_to
|
||||
):
|
||||
return
|
||||
|
||||
self._cancel_delays()
|
||||
|
||||
# state without delay.
|
||||
if self._attr_is_on == state or delay is None:
|
||||
self._set_state(state)
|
||||
@@ -371,6 +365,7 @@ class TriggerBinarySensorEntity(TriggerEntity, AbstractTemplateBinarySensor):
|
||||
try:
|
||||
delay = cv.positive_time_period(delay)
|
||||
except vol.Invalid as err:
|
||||
key = CONF_DELAY_ON if state else CONF_DELAY_OFF
|
||||
logging.getLogger(__name__).warning(
|
||||
"Error rendering %s template: %s", key, err
|
||||
)
|
||||
@@ -412,6 +407,14 @@ class TriggerBinarySensorEntity(TriggerEntity, AbstractTemplateBinarySensor):
|
||||
auto_off_time = dt_util.utcnow() + auto_off_delay
|
||||
self._set_auto_off(auto_off_time)
|
||||
|
||||
def _render_availability_template(self, variables):
|
||||
available = super()._render_availability_template(variables)
|
||||
if not available:
|
||||
# Cancel any delay_on, delay_off, or auto_off when
|
||||
# the entity goes unavailable
|
||||
self._cancel_delays()
|
||||
return available
|
||||
|
||||
def _set_auto_off(self, auto_off_time: datetime) -> None:
|
||||
@callback
|
||||
def _auto_off(_):
|
||||
|
||||
@@ -500,7 +500,6 @@ class TriggerCoverEntity(TriggerEntity, AbstractTemplateCover):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
@@ -96,6 +96,30 @@ class AbstractTemplateEntity(Entity):
|
||||
) -> None:
|
||||
"""Set up a template that manages the main state of the entity."""
|
||||
|
||||
@abstractmethod
|
||||
def setup_template(
|
||||
self,
|
||||
option: str,
|
||||
attribute: str,
|
||||
validator: Callable[[Any], Any] | None = None,
|
||||
on_update: Callable[[Any], None] | None = None,
|
||||
) -> None:
|
||||
"""Set up a template that manages any property or attribute of the entity.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
option
|
||||
The configuration key provided by ConfigFlow or the yaml option
|
||||
attribute
|
||||
The name of the attribute to link to. This attribute must exist
|
||||
unless a custom on_update method is supplied.
|
||||
validator:
|
||||
Optional function that validates the rendered result.
|
||||
on_update:
|
||||
Called to store the template result rather than storing it
|
||||
the supplied attribute. Passed the result of the validator.
|
||||
"""
|
||||
|
||||
def add_template(
|
||||
self,
|
||||
option: str,
|
||||
@@ -109,7 +133,11 @@ class AbstractTemplateEntity(Entity):
|
||||
if (template := self._config.get(option)) and isinstance(template, Template):
|
||||
if add_if_static or (not template.is_static):
|
||||
self._templates[option] = EntityTemplate(
|
||||
attribute, template, validator, on_update, none_on_template_error
|
||||
attribute,
|
||||
template,
|
||||
validator,
|
||||
on_update,
|
||||
none_on_template_error,
|
||||
)
|
||||
return template
|
||||
|
||||
|
||||
@@ -224,7 +224,6 @@ class TriggerEventEntity(TriggerEntity, AbstractTemplateEvent, RestoreEntity):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
for key, updater in (
|
||||
|
||||
@@ -552,7 +552,6 @@ class TriggerFanEntity(TriggerEntity, AbstractTemplateFan):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
@@ -180,3 +180,4 @@ class TriggerImageEntity(TriggerEntity, AbstractTemplateImage):
|
||||
"""Process new data."""
|
||||
super()._process_data()
|
||||
self._handle_state(self._rendered.get(CONF_URL))
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -1123,7 +1123,6 @@ class TriggerLightEntity(TriggerEntity, AbstractTemplateLight):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
@@ -377,7 +377,6 @@ class TriggerLockEntity(TriggerEntity, AbstractTemplateLock):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
@@ -236,7 +236,6 @@ class TriggerNumberEntity(TriggerEntity, AbstractTemplateNumber):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
@@ -209,7 +209,6 @@ class TriggerSelectEntity(TriggerEntity, AbstractTemplateSelect):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
@@ -323,3 +323,4 @@ class TriggerSensorEntity(TriggerEntity, AbstractTemplateSensor):
|
||||
|
||||
rendered = self._rendered.get(CONF_STATE)
|
||||
self._handle_state(rendered)
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -281,7 +281,6 @@ class TriggerSwitchEntity(TriggerEntity, AbstractTemplateSwitch):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
@@ -303,6 +303,30 @@ class TemplateEntity(AbstractTemplateEntity):
|
||||
|
||||
self.add_template(option, attribute, on_update=_update_state)
|
||||
|
||||
def setup_template(
|
||||
self,
|
||||
option: str,
|
||||
attribute: str,
|
||||
validator: Callable[[Any], Any] | None = None,
|
||||
on_update: Callable[[Any], None] | None = None,
|
||||
):
|
||||
"""Set up a template that manages any property or attribute of the entity.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
option
|
||||
The configuration key provided by ConfigFlow or the yaml option
|
||||
attribute
|
||||
The name of the attribute to link to. This attribute must exist
|
||||
unless a custom on_update method is supplied.
|
||||
validator:
|
||||
Optional function that validates the rendered result.
|
||||
on_update:
|
||||
Called to store the template result rather than storing it
|
||||
the supplied attribute. Passed the result of the validator.
|
||||
"""
|
||||
self.add_template(option, attribute, validator, on_update, True)
|
||||
|
||||
def add_template_attribute(
|
||||
self,
|
||||
attribute: str,
|
||||
|
||||
@@ -59,10 +59,33 @@ class TriggerEntity( # pylint: disable=hass-enforce-class-module
|
||||
on_update: Callable[[Any], None] | None = None,
|
||||
) -> None:
|
||||
"""Set up a template that manages the main state of the entity."""
|
||||
if self._config.get(option):
|
||||
self._to_render_simple.append(CONF_STATE)
|
||||
self._parse_result.add(CONF_STATE)
|
||||
self.add_template(option, attribute, validator, on_update)
|
||||
if self.add_template(option, attribute, validator, on_update):
|
||||
self._to_render_simple.append(option)
|
||||
self._parse_result.add(option)
|
||||
|
||||
def setup_template(
|
||||
self,
|
||||
option: str,
|
||||
attribute: str,
|
||||
validator: Callable[[Any], Any] | None = None,
|
||||
on_update: Callable[[Any], None] | None = None,
|
||||
) -> None:
|
||||
"""Set up a template that manages any property or attribute of the entity.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
option
|
||||
The configuration key provided by ConfigFlow or the yaml option
|
||||
attribute
|
||||
The name of the attribute to link to. This attribute must exist
|
||||
unless a custom on_update method is supplied.
|
||||
validator:
|
||||
Optional function that validates the rendered result.
|
||||
on_update:
|
||||
Called to store the template result rather than storing it
|
||||
the supplied attribute. Passed the result of the validator.
|
||||
"""
|
||||
self.setup_state_template(option, attribute, validator, on_update)
|
||||
|
||||
@property
|
||||
def referenced_blueprint(self) -> str | None:
|
||||
@@ -103,21 +126,35 @@ class TriggerEntity( # pylint: disable=hass-enforce-class-module
|
||||
self._render_attributes(rendered, variables)
|
||||
self._rendered = rendered
|
||||
|
||||
def handle_rendered_result(self, key: str) -> bool:
|
||||
def _handle_rendered_results(self) -> bool:
|
||||
"""Get a rendered result and return the value."""
|
||||
if (rendered := self._rendered.get(key)) is not None:
|
||||
if (entity_template := self._templates.get(key)) is not None:
|
||||
# Handle any templates.
|
||||
for option, entity_template in self._templates.items():
|
||||
value = _SENTINEL
|
||||
if (rendered := self._rendered.get(option)) is not None:
|
||||
value = rendered
|
||||
if entity_template.validator:
|
||||
value = entity_template.validator(rendered)
|
||||
|
||||
if entity_template.on_update:
|
||||
entity_template.on_update(value)
|
||||
else:
|
||||
setattr(self, entity_template.attribute, value)
|
||||
if entity_template.validator:
|
||||
value = entity_template.validator(rendered)
|
||||
|
||||
# Capture templates that did not render a result due to an exception and
|
||||
# ensure the state object updates. _SENTINEL is used to differentiate
|
||||
# templates that render None.
|
||||
if value is _SENTINEL:
|
||||
return True
|
||||
|
||||
if entity_template.on_update:
|
||||
entity_template.on_update(value)
|
||||
else:
|
||||
setattr(self, entity_template.attribute, value)
|
||||
return True
|
||||
|
||||
if len(self._rendered) > 0:
|
||||
# In some cases, the entity may be state optimistic or
|
||||
# attribute optimistic, in these scenarios the state needs
|
||||
# to update.
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@callback
|
||||
@@ -136,13 +173,35 @@ class TriggerEntity( # pylint: disable=hass-enforce-class-module
|
||||
else:
|
||||
self._rendered_entity_variables = coordinator_variables
|
||||
variables = self._template_variables(self._rendered_entity_variables)
|
||||
|
||||
self.async_set_context(self.coordinator.data["context"])
|
||||
if self._render_availability_template(variables):
|
||||
self._render_templates(variables)
|
||||
|
||||
self.async_set_context(self.coordinator.data["context"])
|
||||
write_state = False
|
||||
# While transitioning platforms to the new framework, this
|
||||
# if-statement is necessary for backward compatibility with existing
|
||||
# trigger based platforms.
|
||||
if self._templates:
|
||||
# Handle any results that were rendered.
|
||||
write_state = self._handle_rendered_results()
|
||||
|
||||
# Check availability after rendering the results because the state
|
||||
# template could render the entity unavailable
|
||||
if not self.available:
|
||||
write_state = True
|
||||
|
||||
if write_state:
|
||||
self.async_write_ha_state()
|
||||
else:
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
"""Handle updated data from the coordinator.
|
||||
|
||||
While transitioning platforms to the new framework, this
|
||||
function is necessary for backward compatibility with existing
|
||||
trigger based platforms.
|
||||
"""
|
||||
self._process_data()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -438,7 +438,6 @@ class TriggerUpdateEntity(TriggerEntity, AbstractTemplateUpdate):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
@@ -489,7 +489,6 @@ class TriggerVacuumEntity(TriggerEntity, AbstractTemplateVacuum):
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
@@ -747,7 +747,6 @@ class TriggerWeatherEntity(TriggerEntity, AbstractTemplateWeather, RestoreEntity
|
||||
self._process_data()
|
||||
|
||||
if not self.available:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
write_ha_state = False
|
||||
|
||||
Reference in New Issue
Block a user