Home Assistant Unofficial Reference 2024.12.1
trigger_template_entity.py
Go to the documentation of this file.
1 """TemplateEntity utility class."""
2 
3 from __future__ import annotations
4 
5 import contextlib
6 import logging
7 from typing import Any
8 
9 import voluptuous as vol
10 
12  CONF_STATE_CLASS,
13  DEVICE_CLASSES_SCHEMA,
14  STATE_CLASSES_SCHEMA,
15  SensorEntity,
16 )
17 from homeassistant.const import (
18  ATTR_ENTITY_PICTURE,
19  ATTR_FRIENDLY_NAME,
20  ATTR_ICON,
21  CONF_DEVICE_CLASS,
22  CONF_ICON,
23  CONF_NAME,
24  CONF_UNIQUE_ID,
25  CONF_UNIT_OF_MEASUREMENT,
26 )
27 from homeassistant.core import HomeAssistant, State, callback
28 from homeassistant.exceptions import TemplateError
29 from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads
30 
31 from . import config_validation as cv
32 from .entity import Entity
33 from .template import TemplateStateFromEntityId, render_complex
34 from .typing import ConfigType
35 
36 CONF_AVAILABILITY = "availability"
37 CONF_ATTRIBUTES = "attributes"
38 CONF_PICTURE = "picture"
39 
40 CONF_TO_ATTRIBUTE = {
41  CONF_ICON: ATTR_ICON,
42  CONF_NAME: ATTR_FRIENDLY_NAME,
43  CONF_PICTURE: ATTR_ENTITY_PICTURE,
44 }
45 
46 TEMPLATE_ENTITY_BASE_SCHEMA = vol.Schema(
47  {
48  vol.Optional(CONF_ICON): cv.template,
49  vol.Optional(CONF_NAME): cv.template,
50  vol.Optional(CONF_PICTURE): cv.template,
51  vol.Optional(CONF_UNIQUE_ID): cv.string,
52  }
53 )
54 
55 
56 def make_template_entity_base_schema(default_name: str) -> vol.Schema:
57  """Return a schema with default name."""
58  return vol.Schema(
59  {
60  vol.Optional(CONF_ICON): cv.template,
61  vol.Optional(CONF_NAME, default=default_name): cv.template,
62  vol.Optional(CONF_PICTURE): cv.template,
63  vol.Optional(CONF_UNIQUE_ID): cv.string,
64  }
65  )
66 
67 
68 TEMPLATE_SENSOR_BASE_SCHEMA = vol.Schema(
69  {
70  vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
71  vol.Optional(CONF_STATE_CLASS): STATE_CLASSES_SCHEMA,
72  vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
73  }
74 ).extend(TEMPLATE_ENTITY_BASE_SCHEMA.schema)
75 
76 
78  """Template Base entity based on trigger data."""
79 
80  domain: str
81  extra_template_keys: tuple[str, ...] | None = None
82  extra_template_keys_complex: tuple[str, ...] | None = None
83  _unique_id: str | None
84 
85  def __init__(
86  self,
87  hass: HomeAssistant,
88  config: ConfigType,
89  ) -> None:
90  """Initialize the entity."""
91  self.hasshasshass = hass
92 
93  self._set_unique_id_set_unique_id(config.get(CONF_UNIQUE_ID))
94 
95  self._config_config = config
96 
97  self._static_rendered_static_rendered = {}
98  self._to_render_simple: list[str] = []
99  self._to_render_complex: list[str] = []
100 
101  for itm in (
102  CONF_AVAILABILITY,
103  CONF_ICON,
104  CONF_NAME,
105  CONF_PICTURE,
106  ):
107  if itm not in config or config[itm] is None:
108  continue
109  if config[itm].is_static:
110  self._static_rendered_static_rendered[itm] = config[itm].template
111  else:
112  self._to_render_simple.append(itm)
113 
114  if self.extra_template_keys is not None:
115  self._to_render_simple.extend(self.extra_template_keys)
116 
117  if self.extra_template_keys_complex is not None:
118  self._to_render_complex.extend(self.extra_template_keys_complex)
119 
120  # We make a copy so our initial render is 'unknown' and not 'unavailable'
121  self._rendered_rendered = dict(self._static_rendered_static_rendered)
122  self._parse_result_parse_result = {CONF_AVAILABILITY}
123  self._attr_device_class_attr_device_class = config.get(CONF_DEVICE_CLASS)
124 
125  @property
126  def name(self) -> str | None:
127  """Name of the entity."""
128  return self._rendered_rendered.get(CONF_NAME)
129 
130  @property
131  def unique_id(self) -> str | None:
132  """Return unique ID of the entity."""
133  return self._unique_id_unique_id
134 
135  @property
136  def icon(self) -> str | None:
137  """Return icon."""
138  return self._rendered_rendered.get(CONF_ICON)
139 
140  @property
141  def entity_picture(self) -> str | None:
142  """Return entity picture."""
143  return self._rendered_rendered.get(CONF_PICTURE)
144 
145  @property
146  def available(self) -> bool:
147  """Return availability of the entity."""
148  return (
149  self._rendered_rendered is not self._static_rendered_static_rendered
150  and
151  # Check against False so `None` is ok
152  self._rendered_rendered.get(CONF_AVAILABILITY) is not False
153  )
154 
155  @property
156  def extra_state_attributes(self) -> dict[str, Any] | None:
157  """Return extra attributes."""
158  return self._rendered_rendered.get(CONF_ATTRIBUTES)
159 
160  def _set_unique_id(self, unique_id: str | None) -> None:
161  """Set unique id."""
162  self._unique_id_unique_id = unique_id
163 
164  def restore_attributes(self, last_state: State) -> None:
165  """Restore attributes."""
166  for conf_key, attr in CONF_TO_ATTRIBUTE.items():
167  if conf_key not in self._config_config or attr not in last_state.attributes:
168  continue
169  self._rendered_rendered[conf_key] = last_state.attributes[attr]
170 
171  if CONF_ATTRIBUTES in self._config_config:
172  extra_state_attributes = {}
173  for attr in self._config_config[CONF_ATTRIBUTES]:
174  if attr not in last_state.attributes:
175  continue
176  extra_state_attributes[attr] = last_state.attributes[attr]
177  self._rendered_rendered[CONF_ATTRIBUTES] = extra_state_attributes
178 
179  def _render_templates(self, variables: dict[str, Any]) -> None:
180  """Render templates."""
181  try:
182  rendered = dict(self._static_rendered_static_rendered)
183 
184  for key in self._to_render_simple:
185  rendered[key] = self._config_config[key].async_render(
186  variables,
187  parse_result=key in self._parse_result_parse_result,
188  )
189 
190  for key in self._to_render_complex:
191  rendered[key] = render_complex(
192  self._config_config[key],
193  variables,
194  )
195 
196  if CONF_ATTRIBUTES in self._config_config:
197  rendered[CONF_ATTRIBUTES] = render_complex(
198  self._config_config[CONF_ATTRIBUTES],
199  variables,
200  )
201 
202  self._rendered_rendered = rendered
203  except TemplateError as err:
204  logging.getLogger(f"{__package__}.{self.entity_id.split('.')[0]}").error(
205  "Error rendering %s template for %s: %s", key, self.entity_identity_id, err
206  )
207  self._rendered_rendered = self._static_rendered_static_rendered
208 
209 
211  """Template entity based on manual trigger data."""
212 
213  def __init__(
214  self,
215  hass: HomeAssistant,
216  config: ConfigType,
217  ) -> None:
218  """Initialize the entity."""
219  TriggerBaseEntity.__init__(self, hass, config)
220  # Need initial rendering on `name` as it influence the `entity_id`
221  self._rendered_rendered[CONF_NAME] = config[CONF_NAME].async_render(
222  {},
223  parse_result=CONF_NAME in self._parse_result_parse_result,
224  )
225 
226  @callback
227  def _process_manual_data(self, value: Any | None = None) -> None:
228  """Process new data manually.
229 
230  Implementing class should call this last in update method to render templates.
231  Ex: self._process_manual_data(payload)
232  """
233 
234  run_variables: dict[str, Any] = {"value": value}
235  # Silently try if variable is a json and store result in `value_json` if it is.
236  with contextlib.suppress(*JSON_DECODE_EXCEPTIONS):
237  run_variables["value_json"] = json_loads(run_variables["value"])
238  variables = {
239  "this": TemplateStateFromEntityId(self.hasshasshass, self.entity_identity_id),
240  **(run_variables or {}),
241  }
242 
243  self._render_templates_render_templates(variables)
244 
245 
247  """Template entity based on manual trigger data for sensor."""
248 
249  def __init__(
250  self,
251  hass: HomeAssistant,
252  config: ConfigType,
253  ) -> None:
254  """Initialize the sensor entity."""
255  ManualTriggerEntity.__init__(self, hass, config)
256  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT)
257  self._attr_state_class_attr_state_class = config.get(CONF_STATE_CLASS)
None __init__(self, HomeAssistant hass, ConfigType config)
None __init__(self, HomeAssistant hass, ConfigType config)
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
Any render_complex(Any value, TemplateVarsType variables=None, bool limited=False, bool parse_result=True)
Definition: template.py:240
vol.Schema make_template_entity_base_schema(str default_name)