"""Support for esphome sensors.""" from __future__ import annotations from datetime import date, datetime import math from aioesphomeapi import ( EntityInfo, SensorInfo, SensorState, SensorStateClass as EsphomeSensorStateClass, TextSensorInfo, TextSensorState, ) from aioesphomeapi.model import LastResetType from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.util import dt as dt_util from homeassistant.util.enum import try_parse_enum from .entity import EsphomeEntity, platform_async_setup_entry from .enum_mapper import EsphomeEnumMapper PARALLEL_UPDATES = 0 async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up esphome sensors based on a config entry.""" await platform_async_setup_entry( hass, entry, async_add_entities, info_type=SensorInfo, entity_type=EsphomeSensor, state_type=SensorState, ) await platform_async_setup_entry( hass, entry, async_add_entities, info_type=TextSensorInfo, entity_type=EsphomeTextSensor, state_type=TextSensorState, ) _STATE_CLASSES: EsphomeEnumMapper[EsphomeSensorStateClass, SensorStateClass | None] = ( EsphomeEnumMapper( { EsphomeSensorStateClass.NONE: None, EsphomeSensorStateClass.MEASUREMENT: SensorStateClass.MEASUREMENT, EsphomeSensorStateClass.TOTAL_INCREASING: SensorStateClass.TOTAL_INCREASING, EsphomeSensorStateClass.TOTAL: SensorStateClass.TOTAL, } ) ) class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity): """A sensor implementation for esphome.""" @callback def _on_static_info_update(self, static_info: EntityInfo) -> None: """Set attrs from static info.""" super()._on_static_info_update(static_info) static_info = self._static_info self._attr_force_update = static_info.force_update # protobuf doesn't support nullable strings so we need to check # if the string is empty if unit_of_measurement := static_info.unit_of_measurement: self._attr_native_unit_of_measurement = unit_of_measurement self._attr_device_class = try_parse_enum( SensorDeviceClass, static_info.device_class ) if not (state_class := static_info.state_class): return if ( state_class == EsphomeSensorStateClass.MEASUREMENT and static_info.last_reset_type == LastResetType.AUTO ): # Legacy, last_reset_type auto was the equivalent to the # TOTAL_INCREASING state class self._attr_state_class = SensorStateClass.TOTAL_INCREASING else: self._attr_state_class = _STATE_CLASSES.from_esphome(state_class) @property def native_value(self) -> datetime | str | None: """Return the state of the entity.""" if not self._has_state or (state := self._state).missing_state: return None state_float = state.state if not math.isfinite(state_float): return None if self.device_class is SensorDeviceClass.TIMESTAMP: return dt_util.utc_from_timestamp(state_float) return f"{state_float:.{self._static_info.accuracy_decimals}f}" class EsphomeTextSensor(EsphomeEntity[TextSensorInfo, TextSensorState], SensorEntity): """A text sensor implementation for ESPHome.""" @callback def _on_static_info_update(self, static_info: EntityInfo) -> None: """Set attrs from static info.""" super()._on_static_info_update(static_info) static_info = self._static_info self._attr_device_class = try_parse_enum( SensorDeviceClass, static_info.device_class ) @property def native_value(self) -> str | datetime | date | None: """Return the state of the entity.""" if not self._has_state or (state := self._state).missing_state: return None state_str = state.state device_class = self.device_class if device_class is SensorDeviceClass.TIMESTAMP: return dt_util.parse_datetime(state_str) if ( device_class is SensorDeviceClass.DATE and (value := dt_util.parse_datetime(state_str)) is not None ): return value.date() return state_str