1 """Allows the creation of a sensor that breaks out state_attributes."""
3 from __future__
import annotations
5 from datetime
import date, datetime
9 import voluptuous
as vol
14 DEVICE_CLASSES_SCHEMA,
15 DOMAIN
as SENSOR_DOMAIN,
17 PLATFORM_SCHEMA
as SENSOR_PLATFORM_SCHEMA,
29 CONF_ENTITY_PICTURE_TEMPLATE,
31 CONF_FRIENDLY_NAME_TEMPLATE,
37 CONF_UNIT_OF_MEASUREMENT,
52 from .
import TriggerUpdateCoordinator
54 CONF_ATTRIBUTE_TEMPLATES,
55 CONF_AVAILABILITY_TEMPLATE,
59 from .template_entity
import (
60 TEMPLATE_ENTITY_COMMON_SCHEMA,
62 rewrite_common_legacy_to_modern_conf,
64 from .trigger_entity
import TriggerEntity
67 CONF_FRIENDLY_NAME_TEMPLATE: CONF_NAME,
68 CONF_FRIENDLY_NAME: CONF_NAME,
69 CONF_VALUE_TEMPLATE: CONF_STATE,
74 """Run extra validation checks."""
76 val.get(ATTR_LAST_RESET)
is not None
77 and val.get(CONF_STATE_CLASS) != SensorStateClass.TOTAL
80 "last_reset is only valid for template sensors with state_class 'total'"
86 SENSOR_SCHEMA = vol.All(
89 vol.Required(CONF_STATE): cv.template,
90 vol.Optional(ATTR_LAST_RESET): cv.template,
93 .extend(TEMPLATE_SENSOR_BASE_SCHEMA.schema)
94 .extend(TEMPLATE_ENTITY_COMMON_SCHEMA.schema),
99 SENSOR_CONFIG_SCHEMA = vol.All(
102 vol.Required(CONF_STATE): cv.template,
103 vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
105 ).extend(TEMPLATE_SENSOR_BASE_SCHEMA.schema),
108 LEGACY_SENSOR_SCHEMA = vol.All(
109 cv.deprecated(ATTR_ENTITY_ID),
112 vol.Required(CONF_VALUE_TEMPLATE): cv.template,
113 vol.Optional(CONF_ICON_TEMPLATE): cv.template,
114 vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template,
115 vol.Optional(CONF_FRIENDLY_NAME_TEMPLATE): cv.template,
116 vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template,
117 vol.Optional(CONF_ATTRIBUTE_TEMPLATES, default={}): vol.Schema(
118 {cv.string: cv.template}
120 vol.Optional(CONF_FRIENDLY_NAME): cv.string,
121 vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
122 vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
123 vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
124 vol.Optional(CONF_UNIQUE_ID): cv.string,
131 """Run extra validation checks."""
132 if CONF_TRIGGER
in val:
134 "You can only add triggers to template entities if they are defined under"
135 " `template:`. See the template documentation for more information:"
136 " https://www.home-assistant.io/integrations/template/"
139 if CONF_SENSORS
not in val
and SENSOR_DOMAIN
not in val:
140 raise vol.Invalid(f
"Required key {SENSOR_DOMAIN} not defined")
146 hass: HomeAssistant, cfg: dict[str, dict]
148 """Rewrite legacy sensor definitions to modern ones."""
151 for object_id, entity_cfg
in cfg.items():
152 entity_cfg = {**entity_cfg, CONF_OBJECT_ID: object_id}
155 hass, entity_cfg, LEGACY_FIELDS
158 if CONF_NAME
not in entity_cfg:
159 entity_cfg[CONF_NAME] = template.Template(object_id, hass)
161 sensors.append(entity_cfg)
166 PLATFORM_SCHEMA = vol.All(
167 SENSOR_PLATFORM_SCHEMA.extend(
169 vol.Optional(CONF_TRIGGER): cv.match_all,
170 vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(LEGACY_SENSOR_SCHEMA),
173 extra_validation_checks,
176 _LOGGER = logging.getLogger(__name__)
181 async_add_entities: AddEntitiesCallback,
183 definitions: list[dict],
184 unique_id_prefix: str |
None,
186 """Create the template sensors."""
189 for entity_conf
in definitions:
190 unique_id = entity_conf.get(CONF_UNIQUE_ID)
192 if unique_id
and unique_id_prefix:
193 unique_id = f
"{unique_id_prefix}-{unique_id}"
209 async_add_entities: AddEntitiesCallback,
210 discovery_info: DiscoveryInfoType |
None =
None,
212 """Set up the template sensors."""
213 if discovery_info
is None:
222 if "coordinator" in discovery_info:
225 for config
in discovery_info[
"entities"]
232 discovery_info[
"entities"],
233 discovery_info[
"unique_id"],
239 config_entry: ConfigEntry,
240 async_add_entities: AddEntitiesCallback,
242 """Initialize config entry."""
243 _options =
dict(config_entry.options)
244 _options.pop(
"template_type")
251 hass: HomeAssistant, name: str, config: dict[str, Any]
253 """Create a preview sensor."""
259 """Representation of a Template Sensor."""
261 _attr_should_poll =
False
266 config: dict[str, Any],
267 unique_id: str |
None,
269 """Initialize the sensor."""
270 super().
__init__(hass, config=config, fallback_name=
None, unique_id=unique_id)
274 self._template: template.Template = config[CONF_STATE]
275 self._attr_last_reset_template: template.Template |
None = config.get(
280 config.get(CONF_DEVICE_ID),
282 if (object_id := config.get(CONF_OBJECT_ID))
is not None:
284 ENTITY_ID_FORMAT, object_id, hass=hass
289 """Set up templates."""
293 if self._attr_last_reset_template
is not None:
296 self._attr_last_reset_template,
310 if isinstance(result, TemplateError):
315 SensorDeviceClass.DATE,
316 SensorDeviceClass.TIMESTAMP,
327 """Sensor entity based on trigger data."""
329 domain = SENSOR_DOMAIN
330 extra_template_keys = (CONF_STATE,)
335 coordinator: TriggerUpdateCoordinator,
339 super().
__init__(hass, coordinator, config)
341 if (last_reset_template := config.get(ATTR_LAST_RESET))
is not None:
342 if last_reset_template.is_static:
343 self._static_rendered[ATTR_LAST_RESET] = last_reset_template.template
345 self._to_render_simple.append(ATTR_LAST_RESET)
351 """Restore last state."""
354 (last_state := await self.async_get_last_state())
is not None
356 and last_state.state
not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
359 and CONF_STATE
not in self._rendered
361 self._rendered[CONF_STATE] = extra_data.native_value
362 self.restore_attributes(last_state)
366 """Return state of the sensor."""
367 return self._rendered.
get(CONF_STATE)
371 """Process new data."""
375 if ATTR_LAST_RESET
in self._rendered:
376 parsed_timestamp = dt_util.parse_datetime(self._rendered[ATTR_LAST_RESET])
377 if parsed_timestamp
is None:
379 "%s rendered invalid timestamp for last_reset attribute: %s",
381 self._rendered.
get(ATTR_LAST_RESET),
387 state := self._rendered.
get(CONF_STATE)
389 SensorDeviceClass.DATE,
390 SensorDeviceClass.TIMESTAMP,
SensorExtraStoredData|None async_get_last_sensor_data(self)
SensorDeviceClass|None device_class(self)
def _update_state(self, result)
None _async_setup_templates(self)
None __init__(self, HomeAssistant hass, dict[str, Any] config, str|None unique_id)
def _update_last_reset(self, result)
_attr_native_unit_of_measurement
str|datetime|date|None native_value(self)
None __init__(self, HomeAssistant hass, TriggerUpdateCoordinator coordinator, ConfigType config)
_attr_native_unit_of_measurement
None async_added_to_hass(self)
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)
str|None device_class(self)
web.Response get(self, web.Request request, str config_key)
datetime|date|None async_parse_date_datetime(str value, str entity_id, SensorDeviceClass|str|None device_class)
SensorTemplate async_create_preview_sensor(HomeAssistant hass, str name, dict[str, Any] config)
def extra_validation_checks(val)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
def validate_last_reset(val)
None _async_create_template_tracking_entities(AddEntitiesCallback async_add_entities, HomeAssistant hass, list[dict] definitions, str|None unique_id_prefix)
list[dict] rewrite_legacy_to_modern_conf(HomeAssistant hass, dict[str, dict] cfg)
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)