22
33from __future__ import annotations
44
5- from datetime import date , datetime
5+ from datetime import datetime
66import logging
77from typing import Any
88
1818 STATE_CLASSES_SCHEMA ,
1919 RestoreSensor ,
2020 SensorDeviceClass ,
21- SensorEntity ,
2221 SensorStateClass ,
2322)
2423from homeassistant .components .sensor .helpers import ( # pylint: disable=hass-component-root-import
3534 CONF_NAME ,
3635 CONF_SENSORS ,
3736 CONF_STATE ,
38- CONF_TRIGGER ,
39- CONF_TRIGGERS ,
4037 CONF_UNIQUE_ID ,
4138 CONF_UNIT_OF_MEASUREMENT ,
4239 CONF_VALUE_TEMPLATE ,
5451from homeassistant .util import dt as dt_util
5552
5653from . import TriggerUpdateCoordinator
54+ from .entity import AbstractTemplateEntity
5755from .helpers import (
5856 async_setup_template_entry ,
5957 async_setup_template_platform ,
@@ -136,33 +134,8 @@ def validate_last_reset(val):
136134 .extend (TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY .schema ),
137135)
138136
139-
140- def extra_validation_checks (val ):
141- """Run extra validation checks."""
142- if CONF_TRIGGERS in val or CONF_TRIGGER in val :
143- raise vol .Invalid (
144- "You can only add triggers to template entities if they are defined under"
145- " `template:`. See the template documentation for more information:"
146- " https://www.home-assistant.io/integrations/template/"
147- )
148-
149- if CONF_SENSORS not in val and SENSOR_DOMAIN not in val :
150- raise vol .Invalid (f"Required key { SENSOR_DOMAIN } not defined" )
151-
152- return val
153-
154-
155- PLATFORM_SCHEMA = vol .All (
156- SENSOR_PLATFORM_SCHEMA .extend (
157- {
158- vol .Optional (CONF_TRIGGER ): cv .match_all , # to raise custom warning
159- vol .Optional (CONF_TRIGGERS ): cv .match_all , # to raise custom warning
160- vol .Required (CONF_SENSORS ): cv .schema_with_slug_keys (
161- SENSOR_LEGACY_YAML_SCHEMA
162- ),
163- }
164- ),
165- extra_validation_checks ,
137+ PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA .extend (
138+ {vol .Required (CONF_SENSORS ): cv .schema_with_slug_keys (SENSOR_LEGACY_YAML_SCHEMA )}
166139)
167140
168141_LOGGER = logging .getLogger (__name__ )
@@ -213,7 +186,53 @@ def async_create_preview_sensor(
213186 )
214187
215188
216- class StateSensorEntity (TemplateEntity , SensorEntity ):
189+ class AbstractTemplateSensor (AbstractTemplateEntity , RestoreSensor ):
190+ """Representation of a template sensor features."""
191+
192+ _entity_id_format = ENTITY_ID_FORMAT
193+
194+ # The super init is not called because TemplateEntity and TriggerEntity will call AbstractTemplateEntity.__init__.
195+ # This ensures that the __init__ on AbstractTemplateEntity is not called twice.
196+ def __init__ (self , config : ConfigType ) -> None : # pylint: disable=super-init-not-called
197+ """Initialize the features."""
198+ self ._attr_native_unit_of_measurement = config .get (CONF_UNIT_OF_MEASUREMENT )
199+ self ._attr_device_class = config .get (CONF_DEVICE_CLASS )
200+ self ._attr_state_class = config .get (CONF_STATE_CLASS )
201+ self ._template : template .Template = config [CONF_STATE ]
202+ self ._attr_last_reset_template : template .Template | None = config .get (
203+ ATTR_LAST_RESET
204+ )
205+
206+ @callback
207+ def _update_last_reset (self , result : Any ) -> None :
208+ if isinstance (result , datetime ):
209+ self ._attr_last_reset = result
210+ return
211+
212+ parsed_timestamp = dt_util .parse_datetime (result )
213+ if parsed_timestamp is None :
214+ _LOGGER .warning (
215+ "%s rendered invalid timestamp for last_reset attribute: %s" ,
216+ self .entity_id ,
217+ result ,
218+ )
219+ else :
220+ self ._attr_last_reset = parsed_timestamp
221+
222+ def _handle_state (self , result : Any ) -> None :
223+ if result is None or self .device_class not in (
224+ SensorDeviceClass .DATE ,
225+ SensorDeviceClass .TIMESTAMP ,
226+ ):
227+ self ._attr_native_value = result
228+ return
229+
230+ self ._attr_native_value = async_parse_date_datetime (
231+ result , self .entity_id , self .device_class
232+ )
233+
234+
235+ class StateSensorEntity (TemplateEntity , AbstractTemplateSensor ):
217236 """Representation of a Template Sensor."""
218237
219238 _attr_should_poll = False
@@ -226,14 +245,8 @@ def __init__(
226245 unique_id : str | None ,
227246 ) -> None :
228247 """Initialize the sensor."""
229- super ().__init__ (hass , config , unique_id )
230- self ._attr_native_unit_of_measurement = config .get (CONF_UNIT_OF_MEASUREMENT )
231- self ._attr_device_class = config .get (CONF_DEVICE_CLASS )
232- self ._attr_state_class = config .get (CONF_STATE_CLASS )
233- self ._template : template .Template = config [CONF_STATE ]
234- self ._attr_last_reset_template : template .Template | None = config .get (
235- ATTR_LAST_RESET
236- )
248+ TemplateEntity .__init__ (self , hass , config , unique_id )
249+ AbstractTemplateSensor .__init__ (self , config )
237250
238251 @callback
239252 def _async_setup_templates (self ) -> None :
@@ -251,35 +264,20 @@ def _async_setup_templates(self) -> None:
251264
252265 super ()._async_setup_templates ()
253266
254- @callback
255- def _update_last_reset (self , result ):
256- self ._attr_last_reset = result
257-
258267 @callback
259268 def _update_state (self , result ):
260269 super ()._update_state (result )
261270 if isinstance (result , TemplateError ):
262271 self ._attr_native_value = None
263272 return
264273
265- if result is None or self .device_class not in (
266- SensorDeviceClass .DATE ,
267- SensorDeviceClass .TIMESTAMP ,
268- ):
269- self ._attr_native_value = result
270- return
271-
272- self ._attr_native_value = async_parse_date_datetime (
273- result , self .entity_id , self .device_class
274- )
274+ self ._handle_state (result )
275275
276276
277- class TriggerSensorEntity (TriggerEntity , RestoreSensor ):
277+ class TriggerSensorEntity (TriggerEntity , AbstractTemplateSensor ):
278278 """Sensor entity based on trigger data."""
279279
280- _entity_id_format = ENTITY_ID_FORMAT
281280 domain = SENSOR_DOMAIN
282- extra_template_keys = (CONF_STATE ,)
283281
284282 def __init__ (
285283 self ,
@@ -288,18 +286,18 @@ def __init__(
288286 config : ConfigType ,
289287 ) -> None :
290288 """Initialize."""
291- super ().__init__ (hass , coordinator , config )
289+ TriggerEntity .__init__ (self , hass , coordinator , config )
290+ AbstractTemplateSensor .__init__ (self , config )
292291
292+ self ._to_render_simple .append (CONF_STATE )
293293 self ._parse_result .add (CONF_STATE )
294- if (last_reset_template := config .get (ATTR_LAST_RESET )) is not None :
294+
295+ if last_reset_template := self ._attr_last_reset_template :
295296 if last_reset_template .is_static :
296297 self ._static_rendered [ATTR_LAST_RESET ] = last_reset_template .template
297298 else :
298299 self ._to_render_simple .append (ATTR_LAST_RESET )
299300
300- self ._attr_state_class = config .get (CONF_STATE_CLASS )
301- self ._attr_native_unit_of_measurement = config .get (CONF_UNIT_OF_MEASUREMENT )
302-
303301 async def async_added_to_hass (self ) -> None :
304302 """Restore last state."""
305303 await super ().async_added_to_hass ()
@@ -311,39 +309,17 @@ async def async_added_to_hass(self) -> None:
311309 # then we should not restore state
312310 and CONF_STATE not in self ._rendered
313311 ):
314- self ._rendered [ CONF_STATE ] = extra_data .native_value
312+ self ._attr_native_value = extra_data .native_value
315313 self .restore_attributes (last_state )
316314
317- @property
318- def native_value (self ) -> str | datetime | date | None :
319- """Return state of the sensor."""
320- return self ._rendered .get (CONF_STATE )
321-
322315 @callback
323316 def _process_data (self ) -> None :
324317 """Process new data."""
325318 super ()._process_data ()
326319
327320 # Update last_reset
328- if ATTR_LAST_RESET in self ._rendered :
329- parsed_timestamp = dt_util .parse_datetime (self ._rendered [ATTR_LAST_RESET ])
330- if parsed_timestamp is None :
331- _LOGGER .warning (
332- "%s rendered invalid timestamp for last_reset attribute: %s" ,
333- self .entity_id ,
334- self ._rendered .get (ATTR_LAST_RESET ),
335- )
336- else :
337- self ._attr_last_reset = parsed_timestamp
321+ if (last_reset := self ._rendered .get (ATTR_LAST_RESET )) is not None :
322+ self ._update_last_reset (last_reset )
338323
339- if (
340- state := self ._rendered .get (CONF_STATE )
341- ) is None or self .device_class not in (
342- SensorDeviceClass .DATE ,
343- SensorDeviceClass .TIMESTAMP ,
344- ):
345- return
346-
347- self ._rendered [CONF_STATE ] = async_parse_date_datetime (
348- state , self .entity_id , self .device_class
349- )
324+ rendered = self ._rendered .get (CONF_STATE )
325+ self ._handle_state (rendered )
0 commit comments