"""Support for esphome sensors.""" from __future__ import annotations from datetime import 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 AddEntitiesCallback from homeassistant.util import dt as dt_util from homeassistant.util.enum import try_parse_enum from .entity import ( EsphomeEntity, esphome_state_property, platform_async_setup_entry, ) from .enum_mapper import EsphomeEnumMapper async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> 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 @esphome_state_property def native_value(self) -> datetime | str | None: """Return the state of the entity.""" state = self._state if math.isnan(state.state) or state.missing_state: return None if self._attr_device_class == SensorDeviceClass.TIMESTAMP: return dt_util.utc_from_timestamp(state.state) return f"{state.state:.{self._static_info.accuracy_decimals}f}" class EsphomeTextSensor(EsphomeEntity[TextSensorInfo, TextSensorState], SensorEntity): """A text sensor implementation for ESPHome.""" @property @esphome_state_property def native_value(self) -> str | None: """Return the state of the entity.""" state = self._state return None if state.missing_state else state.state