Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Numeric integration of data coming from a source sensor over time."""
2 
3 from __future__ import annotations
4 
5 from abc import ABC, abstractmethod
6 from dataclasses import dataclass
7 from datetime import UTC, datetime, timedelta
8 from decimal import Decimal, InvalidOperation
9 from enum import Enum
10 import logging
11 from typing import TYPE_CHECKING, Any, Final, Self
12 
13 import voluptuous as vol
14 
16  DEVICE_CLASS_UNITS,
17  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
18  RestoreSensor,
19  SensorDeviceClass,
20  SensorExtraStoredData,
21  SensorStateClass,
22 )
23 from homeassistant.config_entries import ConfigEntry
24 from homeassistant.const import (
25  ATTR_DEVICE_CLASS,
26  ATTR_UNIT_OF_MEASUREMENT,
27  CONF_METHOD,
28  CONF_NAME,
29  CONF_UNIQUE_ID,
30  STATE_UNAVAILABLE,
31  UnitOfTime,
32 )
33 from homeassistant.core import (
34  CALLBACK_TYPE,
35  Event,
36  EventStateChangedData,
37  EventStateReportedData,
38  HomeAssistant,
39  State,
40  callback,
41 )
42 from homeassistant.helpers import config_validation as cv, entity_registry as er
43 from homeassistant.helpers.device import async_device_info_to_link_from_entity
44 from homeassistant.helpers.device_registry import DeviceInfo
45 from homeassistant.helpers.entity_platform import AddEntitiesCallback
46 from homeassistant.helpers.event import (
47  async_call_later,
48  async_track_state_change_event,
49  async_track_state_report_event,
50 )
51 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
52 
53 from .const import (
54  CONF_MAX_SUB_INTERVAL,
55  CONF_ROUND_DIGITS,
56  CONF_SOURCE_SENSOR,
57  CONF_UNIT_OF_MEASUREMENT,
58  CONF_UNIT_PREFIX,
59  CONF_UNIT_TIME,
60  INTEGRATION_METHODS,
61  METHOD_LEFT,
62  METHOD_RIGHT,
63  METHOD_TRAPEZOIDAL,
64 )
65 
66 _LOGGER = logging.getLogger(__name__)
67 
68 ATTR_SOURCE_ID: Final = "source"
69 
70 # SI Metric prefixes
71 UNIT_PREFIXES = {None: 1, "k": 10**3, "M": 10**6, "G": 10**9, "T": 10**12}
72 
73 # SI Time prefixes
74 UNIT_TIME = {
75  UnitOfTime.SECONDS: 1,
76  UnitOfTime.MINUTES: 60,
77  UnitOfTime.HOURS: 60 * 60,
78  UnitOfTime.DAYS: 24 * 60 * 60,
79 }
80 
81 DEVICE_CLASS_MAP = {
82  SensorDeviceClass.POWER: SensorDeviceClass.ENERGY,
83 }
84 
85 DEFAULT_ROUND = 3
86 
87 PLATFORM_SCHEMA = vol.All(
88  cv.removed(CONF_UNIT_OF_MEASUREMENT),
89  SENSOR_PLATFORM_SCHEMA.extend(
90  {
91  vol.Optional(CONF_NAME): cv.string,
92  vol.Optional(CONF_UNIQUE_ID): cv.string,
93  vol.Required(CONF_SOURCE_SENSOR): cv.entity_id,
94  vol.Optional(CONF_ROUND_DIGITS, default=DEFAULT_ROUND): vol.Any(
95  None, vol.Coerce(int)
96  ),
97  vol.Optional(CONF_UNIT_PREFIX): vol.In(UNIT_PREFIXES),
98  vol.Optional(CONF_UNIT_TIME, default=UnitOfTime.HOURS): vol.In(UNIT_TIME),
99  vol.Remove(CONF_UNIT_OF_MEASUREMENT): cv.string,
100  vol.Optional(CONF_MAX_SUB_INTERVAL): cv.positive_time_period,
101  vol.Optional(CONF_METHOD, default=METHOD_TRAPEZOIDAL): vol.In(
102  INTEGRATION_METHODS
103  ),
104  }
105  ),
106 )
107 
108 
110  @staticmethod
111  def from_name(method_name: str) -> _IntegrationMethod:
112  return _NAME_TO_INTEGRATION_METHOD[method_name]()
113 
114  @abstractmethod
115  def validate_states(self, left: str, right: str) -> tuple[Decimal, Decimal] | None:
116  """Check state requirements for integration."""
117 
118  @abstractmethod
120  self, elapsed_time: Decimal, left: Decimal, right: Decimal
121  ) -> Decimal:
122  """Calculate area given two states."""
123 
125  self, elapsed_time: Decimal, constant_state: Decimal
126  ) -> Decimal:
127  return constant_state * elapsed_time
128 
129 
132  self, elapsed_time: Decimal, left: Decimal, right: Decimal
133  ) -> Decimal:
134  return elapsed_time * (left + right) / 2
135 
136  def validate_states(self, left: str, right: str) -> tuple[Decimal, Decimal] | None:
137  if (left_dec := _decimal_state(left)) is None or (
138  right_dec := _decimal_state(right)
139  ) is None:
140  return None
141  return (left_dec, right_dec)
142 
143 
146  self, elapsed_time: Decimal, left: Decimal, right: Decimal
147  ) -> Decimal:
148  return self.calculate_area_with_one_statecalculate_area_with_one_state(elapsed_time, left)
149 
150  def validate_states(self, left: str, right: str) -> tuple[Decimal, Decimal] | None:
151  if (left_dec := _decimal_state(left)) is None:
152  return None
153  return (left_dec, left_dec)
154 
155 
158  self, elapsed_time: Decimal, left: Decimal, right: Decimal
159  ) -> Decimal:
160  return self.calculate_area_with_one_statecalculate_area_with_one_state(elapsed_time, right)
161 
162  def validate_states(self, left: str, right: str) -> tuple[Decimal, Decimal] | None:
163  if (right_dec := _decimal_state(right)) is None:
164  return None
165  return (right_dec, right_dec)
166 
167 
168 def _decimal_state(state: str) -> Decimal | None:
169  try:
170  return Decimal(state)
171  except (InvalidOperation, TypeError):
172  return None
173 
174 
175 _NAME_TO_INTEGRATION_METHOD: dict[str, type[_IntegrationMethod]] = {
176  METHOD_LEFT: _Left,
177  METHOD_RIGHT: _Right,
178  METHOD_TRAPEZOIDAL: _Trapezoidal,
179 }
180 
181 
183  StateEvent = "state_event"
184  TimeElapsed = "time_elapsed"
185 
186 
187 @dataclass
189  """Object to hold extra stored data."""
190 
191  source_entity: str | None
192  last_valid_state: Decimal | None
193 
194  def as_dict(self) -> dict[str, Any]:
195  """Return a dict representation of the utility sensor data."""
196  data = super().as_dict()
197  data["source_entity"] = self.source_entity
198  data["last_valid_state"] = (
199  str(self.last_valid_state) if self.last_valid_state else None
200  )
201  return data
202 
203  @classmethod
204  def from_dict(cls, restored: dict[str, Any]) -> Self | None:
205  """Initialize a stored sensor state from a dict."""
206  extra = SensorExtraStoredData.from_dict(restored)
207  if extra is None:
208  return None
209 
210  source_entity = restored.get(ATTR_SOURCE_ID)
211 
212  try:
213  last_valid_state = (
214  Decimal(str(restored.get("last_valid_state")))
215  if restored.get("last_valid_state")
216  else None
217  )
218  except InvalidOperation:
219  # last_period is corrupted
220  _LOGGER.error("Could not use last_valid_state")
221  return None
222 
223  if last_valid_state is None:
224  return None
225 
226  return cls(
227  extra.native_value,
228  extra.native_unit_of_measurement,
229  source_entity,
230  last_valid_state,
231  )
232 
233 
235  hass: HomeAssistant,
236  config_entry: ConfigEntry,
237  async_add_entities: AddEntitiesCallback,
238 ) -> None:
239  """Initialize Integration - Riemann sum integral config entry."""
240  registry = er.async_get(hass)
241  # Validate + resolve entity registry id to entity_id
242  source_entity_id = er.async_validate_entity_id(
243  registry, config_entry.options[CONF_SOURCE_SENSOR]
244  )
245 
247  hass,
248  source_entity_id,
249  )
250 
251  if (unit_prefix := config_entry.options.get(CONF_UNIT_PREFIX)) == "none":
252  # Before we had support for optional selectors, "none" was used for selecting nothing
253  unit_prefix = None
254 
255  if max_sub_interval_dict := config_entry.options.get(CONF_MAX_SUB_INTERVAL, None):
256  max_sub_interval = cv.time_period(max_sub_interval_dict)
257  else:
258  max_sub_interval = None
259 
260  round_digits = config_entry.options.get(CONF_ROUND_DIGITS)
261  if round_digits:
262  round_digits = int(round_digits)
263 
264  integral = IntegrationSensor(
265  integration_method=config_entry.options[CONF_METHOD],
266  name=config_entry.title,
267  round_digits=round_digits,
268  source_entity=source_entity_id,
269  unique_id=config_entry.entry_id,
270  unit_prefix=unit_prefix,
271  unit_time=config_entry.options[CONF_UNIT_TIME],
272  device_info=device_info,
273  max_sub_interval=max_sub_interval,
274  )
275 
276  async_add_entities([integral])
277 
278 
280  hass: HomeAssistant,
281  config: ConfigType,
282  async_add_entities: AddEntitiesCallback,
283  discovery_info: DiscoveryInfoType | None = None,
284 ) -> None:
285  """Set up the integration sensor."""
286  integral = IntegrationSensor(
287  integration_method=config[CONF_METHOD],
288  name=config.get(CONF_NAME),
289  round_digits=config.get(CONF_ROUND_DIGITS),
290  source_entity=config[CONF_SOURCE_SENSOR],
291  unique_id=config.get(CONF_UNIQUE_ID),
292  unit_prefix=config.get(CONF_UNIT_PREFIX),
293  unit_time=config[CONF_UNIT_TIME],
294  max_sub_interval=config.get(CONF_MAX_SUB_INTERVAL),
295  )
296 
297  async_add_entities([integral])
298 
299 
301  """Representation of an integration sensor."""
302 
303  _attr_state_class = SensorStateClass.TOTAL
304  _attr_should_poll = False
305 
306  def __init__(
307  self,
308  *,
309  integration_method: str,
310  name: str | None,
311  round_digits: int | None,
312  source_entity: str,
313  unique_id: str | None,
314  unit_prefix: str | None,
315  unit_time: UnitOfTime,
316  max_sub_interval: timedelta | None,
317  device_info: DeviceInfo | None = None,
318  ) -> None:
319  """Initialize the integration sensor."""
320  self._attr_unique_id_attr_unique_id = unique_id
321  self._sensor_source_id_sensor_source_id = source_entity
322  self._round_digits_round_digits = round_digits
323  self._state_state: Decimal | None = None
324  self._method_method = _IntegrationMethod.from_name(integration_method)
325 
326  self._attr_name_attr_name = name if name is not None else f"{source_entity} integral"
327  self._unit_prefix_string_unit_prefix_string = "" if unit_prefix is None else unit_prefix
328  self._unit_of_measurement_unit_of_measurement: str | None = None
329  self._unit_prefix_unit_prefix = UNIT_PREFIXES[unit_prefix]
330  self._unit_time_unit_time = UNIT_TIME[unit_time]
331  self._unit_time_str_unit_time_str = unit_time
332  self._attr_icon_attr_icon = "mdi:chart-histogram"
333  self._source_entity: str = source_entity
334  self._last_valid_state_last_valid_state: Decimal | None = None
335  self._attr_device_info_attr_device_info = device_info
336  self._max_sub_interval: timedelta | None = (
337  None # disable time based integration
338  if max_sub_interval is None or max_sub_interval.total_seconds() == 0
339  else max_sub_interval
340  )
341  self._max_sub_interval_exceeded_callback_max_sub_interval_exceeded_callback: CALLBACK_TYPE = lambda *args: None
342  self._last_integration_time_last_integration_time: datetime = datetime.now(tz=UTC)
343  self._last_integration_trigger_last_integration_trigger = _IntegrationTrigger.StateEvent
344  self._attr_suggested_display_precision_attr_suggested_display_precision = round_digits or 2
345 
346  def _calculate_unit(self, source_unit: str) -> str:
347  """Multiply source_unit with time unit of the integral.
348 
349  Possibly cancelling out a time unit in the denominator of the source_unit.
350  Note that this is a heuristic string manipulation method and might not
351  transform all source units in a sensible way.
352 
353  Examples:
354  - Speed to distance: 'km/h' and 'h' will be transformed to 'km'
355  - Power to energy: 'W' and 'h' will be transformed to 'Wh'
356 
357  """
358  unit_time = self._unit_time_str_unit_time_str
359  if source_unit.endswith(f"/{unit_time}"):
360  integral_unit = source_unit[0 : (-(1 + len(unit_time)))]
361  else:
362  integral_unit = f"{source_unit}{unit_time}"
363 
364  return f"{self._unit_prefix_string}{integral_unit}"
365 
367  self,
368  source_device_class: SensorDeviceClass | None,
369  unit_of_measurement: str | None,
370  ) -> SensorDeviceClass | None:
371  """Deduce device class if possible from source device class and target unit."""
372  if source_device_class is None:
373  return None
374 
375  if (device_class := DEVICE_CLASS_MAP.get(source_device_class)) is None:
376  return None
377 
378  if unit_of_measurement not in DEVICE_CLASS_UNITS.get(device_class, set()):
379  return None
380  return device_class
381 
382  def _derive_and_set_attributes_from_state(self, source_state: State) -> None:
383  source_unit = source_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
384  if source_unit is not None:
385  self._unit_of_measurement_unit_of_measurement = self._calculate_unit_calculate_unit(source_unit)
386  else:
387  # If the source has no defined unit we cannot derive a unit for the integral
388  self._unit_of_measurement_unit_of_measurement = None
389 
390  self._attr_device_class_attr_device_class = self._calculate_device_class_calculate_device_class(
391  source_state.attributes.get(ATTR_DEVICE_CLASS), self.unit_of_measurementunit_of_measurementunit_of_measurementunit_of_measurement
392  )
393  if self._attr_device_class_attr_device_class:
394  self._attr_icon_attr_icon = None # Remove this sensors icon default and allow to fallback to the device class default
395  else:
396  self._attr_icon_attr_icon = "mdi:chart-histogram"
397 
398  def _update_integral(self, area: Decimal) -> None:
399  area_scaled = area / (self._unit_prefix_unit_prefix * self._unit_time_unit_time)
400  if isinstance(self._state_state, Decimal):
401  self._state_state += area_scaled
402  else:
403  self._state_state = area_scaled
404  _LOGGER.debug(
405  "area = %s, area_scaled = %s new state = %s", area, area_scaled, self._state_state
406  )
407  self._last_valid_state_last_valid_state = self._state_state
408 
409  async def async_added_to_hass(self) -> None:
410  """Handle entity which will be added."""
411  await super().async_added_to_hass()
412 
413  if (last_sensor_data := await self.async_get_last_sensor_dataasync_get_last_sensor_dataasync_get_last_sensor_data()) is not None:
414  self._state_state = (
415  Decimal(str(last_sensor_data.native_value))
416  if last_sensor_data.native_value
417  else last_sensor_data.last_valid_state
418  )
419  self._attr_native_value_attr_native_value = last_sensor_data.native_value
420  self._unit_of_measurement_unit_of_measurement = last_sensor_data.native_unit_of_measurement
421  self._last_valid_state_last_valid_state = last_sensor_data.last_valid_state
422 
423  _LOGGER.debug(
424  "Restored state %s and last_valid_state %s",
425  self._state_state,
426  self._last_valid_state_last_valid_state,
427  )
428 
429  if self._max_sub_interval is not None:
430  source_state = self.hasshass.states.get(self._sensor_source_id_sensor_source_id)
431  self._schedule_max_sub_interval_exceeded_if_state_is_numeric_schedule_max_sub_interval_exceeded_if_state_is_numeric(source_state)
432  self.async_on_removeasync_on_remove(self._cancel_max_sub_interval_exceeded_callback_cancel_max_sub_interval_exceeded_callback)
433  handle_state_change = self._integrate_on_state_change_with_max_sub_interval_integrate_on_state_change_with_max_sub_interval
434  handle_state_report = self._integrate_on_state_report_with_max_sub_interval_integrate_on_state_report_with_max_sub_interval
435  else:
436  handle_state_change = self._integrate_on_state_change_callback_integrate_on_state_change_callback
437  handle_state_report = self._integrate_on_state_report_callback_integrate_on_state_report_callback
438 
439  if (
440  state := self.hasshass.states.get(self._source_entity)
441  ) and state.state != STATE_UNAVAILABLE:
442  self._derive_and_set_attributes_from_state_derive_and_set_attributes_from_state(state)
443 
444  self.async_on_removeasync_on_remove(
446  self.hasshass,
447  self._sensor_source_id_sensor_source_id,
448  handle_state_change,
449  )
450  )
451  self.async_on_removeasync_on_remove(
453  self.hasshass,
454  self._sensor_source_id_sensor_source_id,
455  handle_state_report,
456  )
457  )
458 
459  @callback
461  self, event: Event[EventStateChangedData]
462  ) -> None:
463  """Handle sensor state update when sub interval is configured."""
464  self._integrate_on_state_update_with_max_sub_interval_integrate_on_state_update_with_max_sub_interval(
465  None, event.data["old_state"], event.data["new_state"]
466  )
467 
468  @callback
470  self, event: Event[EventStateReportedData]
471  ) -> None:
472  """Handle sensor state report when sub interval is configured."""
473  self._integrate_on_state_update_with_max_sub_interval_integrate_on_state_update_with_max_sub_interval(
474  event.data["old_last_reported"], None, event.data["new_state"]
475  )
476 
477  @callback
479  self,
480  old_last_reported: datetime | None,
481  old_state: State | None,
482  new_state: State | None,
483  ) -> None:
484  """Integrate based on state change and time.
485 
486  Next to doing the integration based on state change this method cancels and
487  reschedules time based integration.
488  """
489  self._cancel_max_sub_interval_exceeded_callback_cancel_max_sub_interval_exceeded_callback()
490  try:
491  self._integrate_on_state_change_integrate_on_state_change(old_last_reported, old_state, new_state)
492  self._last_integration_trigger_last_integration_trigger = _IntegrationTrigger.StateEvent
493  self._last_integration_time_last_integration_time = datetime.now(tz=UTC)
494  finally:
495  # When max_sub_interval exceeds without state change the source is assumed
496  # constant with the last known state (new_state).
497  self._schedule_max_sub_interval_exceeded_if_state_is_numeric_schedule_max_sub_interval_exceeded_if_state_is_numeric(new_state)
498 
499  @callback
501  self, event: Event[EventStateChangedData]
502  ) -> None:
503  """Handle sensor state change."""
504  return self._integrate_on_state_change_integrate_on_state_change(
505  None, event.data["old_state"], event.data["new_state"]
506  )
507 
508  @callback
510  self, event: Event[EventStateReportedData]
511  ) -> None:
512  """Handle sensor state report."""
513  return self._integrate_on_state_change_integrate_on_state_change(
514  event.data["old_last_reported"], None, event.data["new_state"]
515  )
516 
518  self,
519  old_last_reported: datetime | None,
520  old_state: State | None,
521  new_state: State | None,
522  ) -> None:
523  if new_state is None:
524  return
525 
526  if new_state.state == STATE_UNAVAILABLE:
527  self._attr_available_attr_available = False
528  self.async_write_ha_stateasync_write_ha_state()
529  return
530 
531  if old_state:
532  # state has changed, we recover old_state from the event
533  old_state_state = old_state.state
534  old_last_reported = old_state.last_reported
535  else:
536  # event state reported without any state change
537  old_state_state = new_state.state
538 
539  self._attr_available_attr_available = True
540  self._derive_and_set_attributes_from_state_derive_and_set_attributes_from_state(new_state)
541 
542  if old_last_reported is None and old_state is None:
543  self.async_write_ha_stateasync_write_ha_state()
544  return
545 
546  if not (
547  states := self._method_method.validate_states(old_state_state, new_state.state)
548  ):
549  self.async_write_ha_stateasync_write_ha_state()
550  return
551 
552  if TYPE_CHECKING:
553  assert old_last_reported is not None
554  elapsed_seconds = Decimal(
555  (new_state.last_reported - old_last_reported).total_seconds()
556  if self._last_integration_trigger_last_integration_trigger == _IntegrationTrigger.StateEvent
557  else (new_state.last_reported - self._last_integration_time_last_integration_time).total_seconds()
558  )
559 
560  area = self._method_method.calculate_area_with_two_states(elapsed_seconds, *states)
561 
562  self._update_integral_update_integral(area)
563  self.async_write_ha_stateasync_write_ha_state()
564 
566  self, source_state: State | None
567  ) -> None:
568  """Schedule possible integration using the source state and max_sub_interval.
569 
570  The callback reference is stored for possible cancellation if the source state
571  reports a change before max_sub_interval has passed.
572 
573  If the callback is executed, meaning there was no state change reported, the
574  source_state is assumed constant and integration is done using its value.
575  """
576  if (
577  self._max_sub_interval is not None
578  and source_state is not None
579  and (source_state_dec := _decimal_state(source_state.state))
580  ):
581 
582  @callback
583  def _integrate_on_max_sub_interval_exceeded_callback(now: datetime) -> None:
584  """Integrate based on time and reschedule."""
585  elapsed_seconds = Decimal(
586  (now - self._last_integration_time_last_integration_time).total_seconds()
587  )
588  self._derive_and_set_attributes_from_state_derive_and_set_attributes_from_state(source_state)
589  area = self._method_method.calculate_area_with_one_state(
590  elapsed_seconds, source_state_dec
591  )
592  self._update_integral_update_integral(area)
593  self.async_write_ha_stateasync_write_ha_state()
594 
595  self._last_integration_time_last_integration_time = datetime.now(tz=UTC)
596  self._last_integration_trigger_last_integration_trigger = _IntegrationTrigger.TimeElapsed
597 
598  self._schedule_max_sub_interval_exceeded_if_state_is_numeric_schedule_max_sub_interval_exceeded_if_state_is_numeric(
599  source_state
600  )
601 
602  self._max_sub_interval_exceeded_callback_max_sub_interval_exceeded_callback = async_call_later(
603  self.hasshass,
604  self._max_sub_interval,
605  _integrate_on_max_sub_interval_exceeded_callback,
606  )
607 
609  self._max_sub_interval_exceeded_callback_max_sub_interval_exceeded_callback()
610 
611  @property
612  def native_value(self) -> Decimal | None:
613  """Return the state of the sensor."""
614  if isinstance(self._state_state, Decimal) and self._round_digits_round_digits:
615  return round(self._state_state, self._round_digits_round_digits)
616  return self._state_state
617 
618  @property
619  def native_unit_of_measurement(self) -> str | None:
620  """Return the unit the value is expressed in."""
621  return self._unit_of_measurement_unit_of_measurement
622 
623  @property
624  def extra_state_attributes(self) -> dict[str, str] | None:
625  """Return the state attributes of the sensor."""
626  return {
627  ATTR_SOURCE_ID: self._source_entity,
628  }
629 
630  @property
631  def extra_restore_state_data(self) -> IntegrationSensorExtraStoredData:
632  """Return sensor specific state data to be restored."""
634  self.native_valuenative_valuenative_value,
635  self.native_unit_of_measurementnative_unit_of_measurementnative_unit_of_measurement,
636  self._source_entity,
637  self._last_valid_state_last_valid_state,
638  )
639 
641  self,
642  ) -> IntegrationSensorExtraStoredData | None:
643  """Restore Utility Meter Sensor Extra Stored Data."""
644  if (restored_last_extra_data := await self.async_get_last_extra_data()) is None:
645  return None
646 
647  return IntegrationSensorExtraStoredData.from_dict(
648  restored_last_extra_data.as_dict()
649  )
None _integrate_on_state_change_with_max_sub_interval(self, Event[EventStateChangedData] event)
Definition: sensor.py:462
None _integrate_on_state_report_with_max_sub_interval(self, Event[EventStateReportedData] event)
Definition: sensor.py:471
SensorDeviceClass|None _calculate_device_class(self, SensorDeviceClass|None source_device_class, str|None unit_of_measurement)
Definition: sensor.py:370
None _integrate_on_state_report_callback(self, Event[EventStateReportedData] event)
Definition: sensor.py:511
None _integrate_on_state_change(self, datetime|None old_last_reported, State|None old_state, State|None new_state)
Definition: sensor.py:522
None _derive_and_set_attributes_from_state(self, State source_state)
Definition: sensor.py:382
None _integrate_on_state_change_callback(self, Event[EventStateChangedData] event)
Definition: sensor.py:502
IntegrationSensorExtraStoredData extra_restore_state_data(self)
Definition: sensor.py:631
None _integrate_on_state_update_with_max_sub_interval(self, datetime|None old_last_reported, State|None old_state, State|None new_state)
Definition: sensor.py:483
None __init__(self, *str integration_method, str|None name, int|None round_digits, str source_entity, str|None unique_id, str|None unit_prefix, UnitOfTime unit_time, timedelta|None max_sub_interval, DeviceInfo|None device_info=None)
Definition: sensor.py:318
None _schedule_max_sub_interval_exceeded_if_state_is_numeric(self, State|None source_state)
Definition: sensor.py:567
IntegrationSensorExtraStoredData|None async_get_last_sensor_data(self)
Definition: sensor.py:642
_IntegrationMethod from_name(str method_name)
Definition: sensor.py:111
Decimal calculate_area_with_two_states(self, Decimal elapsed_time, Decimal left, Decimal right)
Definition: sensor.py:121
tuple[Decimal, Decimal]|None validate_states(self, str left, str right)
Definition: sensor.py:115
Decimal calculate_area_with_one_state(self, Decimal elapsed_time, Decimal constant_state)
Definition: sensor.py:126
Decimal calculate_area_with_two_states(self, Decimal elapsed_time, Decimal left, Decimal right)
Definition: sensor.py:147
tuple[Decimal, Decimal]|None validate_states(self, str left, str right)
Definition: sensor.py:150
tuple[Decimal, Decimal]|None validate_states(self, str left, str right)
Definition: sensor.py:162
Decimal calculate_area_with_two_states(self, Decimal elapsed_time, Decimal left, Decimal right)
Definition: sensor.py:159
tuple[Decimal, Decimal]|None validate_states(self, str left, str right)
Definition: sensor.py:136
Decimal calculate_area_with_two_states(self, Decimal elapsed_time, Decimal left, Decimal right)
Definition: sensor.py:133
SensorExtraStoredData|None async_get_last_sensor_data(self)
Definition: __init__.py:934
StateType|date|datetime|Decimal native_value(self)
Definition: __init__.py:460
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
str|None unit_of_measurement(self)
Definition: entity.py:815
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:284
Decimal|None _decimal_state(str state)
Definition: sensor.py:168
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:238
dr.DeviceInfo|None async_device_info_to_link_from_entity(HomeAssistant hass, str entity_id_or_uuid)
Definition: device.py:28
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)
Definition: event.py:1597
CALLBACK_TYPE async_track_state_report_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateReportedData]], Any] action, HassJobType|None job_type=None)
Definition: event.py:412
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)
Definition: event.py:314