1 """Support for exposing a templated binary sensor."""
3 from __future__
import annotations
5 from dataclasses
import dataclass
6 from datetime
import datetime, timedelta
7 from functools
import partial
9 from typing
import Any, Self
11 import voluptuous
as vol
14 DEVICE_CLASSES_SCHEMA,
15 DOMAIN
as BINARY_SENSOR_DOMAIN,
17 PLATFORM_SCHEMA
as BINARY_SENSOR_PLATFORM_SCHEMA,
26 CONF_ENTITY_PICTURE_TEMPLATE,
28 CONF_FRIENDLY_NAME_TEMPLATE,
35 CONF_UNIT_OF_MEASUREMENT,
53 from .
import TriggerUpdateCoordinator
57 CONF_AVAILABILITY_TEMPLATE,
61 from .template_entity
import (
62 TEMPLATE_ENTITY_COMMON_SCHEMA,
64 rewrite_common_legacy_to_modern_conf,
66 from .trigger_entity
import TriggerEntity
68 CONF_DELAY_ON =
"delay_on"
69 CONF_DELAY_OFF =
"delay_off"
70 CONF_AUTO_OFF =
"auto_off"
71 CONF_ATTRIBUTE_TEMPLATES =
"attribute_templates"
74 CONF_ICON_TEMPLATE: CONF_ICON,
75 CONF_ENTITY_PICTURE_TEMPLATE: CONF_PICTURE,
76 CONF_AVAILABILITY_TEMPLATE: CONF_AVAILABILITY,
77 CONF_ATTRIBUTE_TEMPLATES: CONF_ATTRIBUTES,
78 CONF_FRIENDLY_NAME_TEMPLATE: CONF_NAME,
79 CONF_FRIENDLY_NAME: CONF_NAME,
80 CONF_VALUE_TEMPLATE: CONF_STATE,
83 BINARY_SENSOR_SCHEMA = vol.Schema(
85 vol.Optional(CONF_AUTO_OFF): vol.Any(cv.positive_time_period, cv.template),
86 vol.Optional(CONF_DELAY_OFF): vol.Any(cv.positive_time_period, cv.template),
87 vol.Optional(CONF_DELAY_ON): vol.Any(cv.positive_time_period, cv.template),
88 vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
89 vol.Required(CONF_STATE): cv.template,
90 vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
92 ).extend(TEMPLATE_ENTITY_COMMON_SCHEMA.schema)
94 BINARY_SENSOR_CONFIG_SCHEMA = BINARY_SENSOR_SCHEMA.extend(
96 vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
100 LEGACY_BINARY_SENSOR_SCHEMA = vol.All(
101 cv.deprecated(ATTR_ENTITY_ID),
104 vol.Required(CONF_VALUE_TEMPLATE): cv.template,
105 vol.Optional(CONF_ICON_TEMPLATE): cv.template,
106 vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template,
107 vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template,
108 vol.Optional(CONF_ATTRIBUTE_TEMPLATES): vol.Schema(
109 {cv.string: cv.template}
111 vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
112 vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
113 vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
114 vol.Optional(CONF_DELAY_ON): vol.Any(cv.positive_time_period, cv.template),
115 vol.Optional(CONF_DELAY_OFF): vol.Any(cv.positive_time_period, cv.template),
116 vol.Optional(CONF_UNIQUE_ID): cv.string,
123 hass: HomeAssistant, cfg: dict[str, dict]
125 """Rewrite legacy binary sensor definitions to modern ones."""
128 for object_id, entity_cfg
in cfg.items():
129 entity_cfg = {**entity_cfg, CONF_OBJECT_ID: object_id}
132 hass, entity_cfg, LEGACY_FIELDS
135 if CONF_NAME
not in entity_cfg:
136 entity_cfg[CONF_NAME] = template.Template(object_id, hass)
138 sensors.append(entity_cfg)
143 PLATFORM_SCHEMA = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
145 vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(
146 LEGACY_BINARY_SENSOR_SCHEMA
154 async_add_entities: AddEntitiesCallback,
156 definitions: list[dict],
157 unique_id_prefix: str |
None,
159 """Create the template binary sensors."""
162 for entity_conf
in definitions:
163 unique_id = entity_conf.get(CONF_UNIQUE_ID)
165 if unique_id
and unique_id_prefix:
166 unique_id = f
"{unique_id_prefix}-{unique_id}"
182 async_add_entities: AddEntitiesCallback,
183 discovery_info: DiscoveryInfoType |
None =
None,
185 """Set up the template binary sensors."""
186 if discovery_info
is None:
195 if "coordinator" in discovery_info:
198 for config
in discovery_info[
"entities"]
205 discovery_info[
"entities"],
206 discovery_info[
"unique_id"],
212 config_entry: ConfigEntry,
213 async_add_entities: AddEntitiesCallback,
215 """Initialize config entry."""
216 _options =
dict(config_entry.options)
217 _options.pop(
"template_type")
226 hass: HomeAssistant, name: str, config: dict[str, Any]
227 ) -> BinarySensorTemplate:
228 """Create a preview sensor."""
234 """A virtual binary sensor that triggers from another sensor."""
236 _attr_should_poll =
False
241 config: dict[str, Any],
242 unique_id: str |
None,
244 """Initialize the Template binary sensor."""
245 super().
__init__(hass, config=config, unique_id=unique_id)
246 if (object_id := config.get(CONF_OBJECT_ID))
is not None:
248 ENTITY_ID_FORMAT, object_id, hass=hass
260 config.get(CONF_DEVICE_ID),
268 and last_state.state
not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
275 """Set up templates."""
283 "_delay_on", self.
_delay_on_raw_delay_on_raw, cv.positive_time_period
291 "_delay_off", self.
_delay_off_raw_delay_off_raw, cv.positive_time_period
306 if isinstance(result, TemplateError)
307 else template.result_as_boolean(result)
316 or (state
and not self.
_delay_on_delay_on)
317 or (
not state
and not self.
_delay_off_delay_off)
324 """Set state of template binary sensor."""
334 """Sensor entity based on trigger data."""
336 domain = BINARY_SENSOR_DOMAIN
337 extra_template_keys = (CONF_STATE,)
342 coordinator: TriggerUpdateCoordinator,
345 """Initialize the entity."""
346 super().
__init__(hass, coordinator, config)
348 for key
in (CONF_DELAY_ON, CONF_DELAY_OFF, CONF_AUTO_OFF):
349 if isinstance(config.get(key), template.Template):
350 self._to_render_simple.append(key)
351 self._parse_result.
add(key)
358 """Restore last state."""
364 and last_state.state
not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
370 self.restore_attributes(last_state)
372 if CONF_AUTO_OFF
not in self._config:
376 auto_off_time := extra_data.auto_off_time
377 )
is not None and auto_off_time <= dt_util.utcnow():
381 if self.
_attr_is_on_attr_is_on
and auto_off_time
is not None:
386 """Handle update of the data."""
402 raw = self._rendered.
get(CONF_STATE)
403 state = template.result_as_boolean(raw)
405 key = CONF_DELAY_ON
if state
else CONF_DELAY_OFF
406 delay = self._rendered.
get(key)
or self._config.
get(key)
409 if self.
_attr_is_on_attr_is_on == state
or state
is None or delay
is None:
413 if not isinstance(delay, timedelta):
415 delay = cv.positive_time_period(delay)
416 except vol.Invalid
as err:
417 logging.getLogger(__name__).warning(
418 "Error rendering %s template: %s", key, err
424 self.
hasshass, delay.total_seconds(), partial(self.
_set_state_set_state, state)
429 """Set up auto off."""
437 auto_off_delay = self._rendered.
get(CONF_AUTO_OFF)
or self._config.
get(
441 if auto_off_delay
is None:
444 if not isinstance(auto_off_delay, timedelta):
446 auto_off_delay = cv.positive_time_period(auto_off_delay)
447 except vol.Invalid
as err:
448 logging.getLogger(__name__).warning(
449 "Error rendering %s template: %s", CONF_AUTO_OFF, err
453 auto_off_time = dt_util.utcnow() + auto_off_delay
459 """Reset state of template binary sensor."""
470 """Return specific state data to be restored."""
475 ) -> AutoOffExtraStoredData | None:
476 """Restore auto_off_time."""
479 return AutoOffExtraStoredData.from_dict(restored_last_extra_data.as_dict())
484 """Object to hold extra stored data."""
486 auto_off_time: datetime |
None
489 """Return a dict representation of additional data."""
490 auto_off_time: datetime | dict[str, str] |
None = self.auto_off_time
491 if isinstance(auto_off_time, datetime):
493 "__type":
str(type(auto_off_time)),
494 "isoformat": auto_off_time.isoformat(),
497 "auto_off_time": auto_off_time,
501 def from_dict(cls, restored: dict[str, Any]) -> Self |
None:
502 """Initialize a stored binary sensor state from a dict."""
504 auto_off_time = restored[
"auto_off_time"]
508 type_ = auto_off_time[
"__type"]
509 if type_ ==
"<class 'datetime.datetime'>":
510 auto_off_time = dt_util.parse_datetime(auto_off_time[
"isoformat"])
518 return cls(auto_off_time)
None _async_setup_templates(self)
None async_added_to_hass(self)
None __init__(self, HomeAssistant hass, dict[str, Any] config, str|None unique_id)
def _update_state(self, result)
None __init__(self, HomeAssistant hass, TriggerUpdateCoordinator coordinator, dict config)
AutoOffExtraStoredData extra_restore_state_data(self)
None _set_auto_off(self, datetime auto_off_time)
AutoOffExtraStoredData|None async_get_last_binary_sensor_data(self)
None async_added_to_hass(self)
None _handle_coordinator_update(self)
def _set_state(self, state, _=None)
None _update_state(self, str|TemplateError result)
None add_template_attribute(self, str attribute, Template template, Callable[[Any], Any]|None validator=None, Callable[[Any], None]|None on_update=None, bool none_on_template_error=False)
None async_write_ha_state(self)
None async_set_context(self, Context context)
State|None async_get_last_state(self)
ExtraStoredData|None async_get_last_extra_data(self)
bool add(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
None _async_create_template_tracking_entities(AddEntitiesCallback async_add_entities, HomeAssistant hass, list[dict] definitions, str|None unique_id_prefix)
BINARY_SENSOR_CONFIG_SCHEMA
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
list[dict] rewrite_legacy_to_modern_conf(HomeAssistant hass, dict[str, dict] cfg)
BinarySensorTemplate async_create_preview_binary_sensor(HomeAssistant hass, str name, dict[str, Any] config)
dict[str, Any] rewrite_common_legacy_to_modern_conf(HomeAssistant hass, dict[str, Any] entity_cfg, dict[str, str]|None extra_legacy_fields=None)
dr.DeviceInfo|None async_device_info_to_link_from_device_id(HomeAssistant hass, str|None device_id)
str async_generate_entity_id(str entity_id_format, str|None name, Iterable[str]|None current_ids=None, HomeAssistant|None hass=None)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
CALLBACK_TYPE async_track_point_in_utc_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)