diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index da1cdfb0eab..1b9b53f24cd 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -58,6 +58,7 @@ from homeassistant.core import callback from .entity import ( EsphomeEntity, convert_api_error_ha_error, + esphome_float_state_property, esphome_state_property, platform_async_setup_entry, ) @@ -227,7 +228,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti return _SWING_MODES.from_esphome(self._state.swing_mode) @property - @esphome_state_property + @esphome_float_state_property def current_temperature(self) -> float | None: """Return the current temperature.""" return self._state.current_temperature @@ -241,19 +242,19 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti return round(self._state.current_humidity) @property - @esphome_state_property + @esphome_float_state_property def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._state.target_temperature @property - @esphome_state_property + @esphome_float_state_property def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" return self._state.target_temperature_low @property - @esphome_state_property + @esphome_float_state_property def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" return self._state.target_temperature_high diff --git a/homeassistant/components/esphome/entity.py b/homeassistant/components/esphome/entity.py index a484a988ea9..455a3f8d105 100644 --- a/homeassistant/components/esphome/entity.py +++ b/homeassistant/components/esphome/entity.py @@ -118,20 +118,35 @@ def esphome_state_property[_R, _EntityT: EsphomeEntity[Any, Any]]( ) -> Callable[[_EntityT], _R | None]: """Wrap a state property of an esphome entity. - This checks if the state object in the entity is set, and - prevents writing NAN values to the Home Assistant state machine. + This checks if the state object in the entity is set + and returns None if it is not set. """ @functools.wraps(func) def _wrapper(self: _EntityT) -> _R | None: + return func(self) if self._has_state else None + + return _wrapper + + +def esphome_float_state_property[_EntityT: EsphomeEntity[Any, Any]]( + func: Callable[[_EntityT], float | None], +) -> Callable[[_EntityT], float | None]: + """Wrap a state property of an esphome entity that returns a float. + + This checks if the state object in the entity is set, and returns + None if its not set. If also prevents writing NAN values to the + Home Assistant state machine. + """ + + @functools.wraps(func) + def _wrapper(self: _EntityT) -> float | None: if not self._has_state: return None val = func(self) - if isinstance(val, float) and not math.isfinite(val): - # Home Assistant doesn't use NaN or inf values in state machine - # (not JSON serializable) - return None - return val + # Home Assistant doesn't use NaN or inf values in state machine + # (not JSON serializable) + return None if val is None or not math.isfinite(val) else val return _wrapper diff --git a/homeassistant/components/esphome/media_player.py b/homeassistant/components/esphome/media_player.py index ec9d61fb9e7..f7c5d7011f8 100644 --- a/homeassistant/components/esphome/media_player.py +++ b/homeassistant/components/esphome/media_player.py @@ -29,6 +29,7 @@ from homeassistant.core import callback from .entity import ( EsphomeEntity, convert_api_error_ha_error, + esphome_float_state_property, esphome_state_property, platform_async_setup_entry, ) @@ -79,7 +80,7 @@ class EsphomeMediaPlayer( return self._state.muted @property - @esphome_state_property + @esphome_float_state_property def volume_level(self) -> float | None: """Volume level of the media player (0..1).""" return self._state.volume diff --git a/homeassistant/components/esphome/number.py b/homeassistant/components/esphome/number.py index 1e588c8d35e..2d74dad1bcf 100644 --- a/homeassistant/components/esphome/number.py +++ b/homeassistant/components/esphome/number.py @@ -3,7 +3,6 @@ from __future__ import annotations from functools import partial -import math from aioesphomeapi import ( EntityInfo, @@ -19,7 +18,7 @@ from homeassistant.util.enum import try_parse_enum from .entity import ( EsphomeEntity, convert_api_error_ha_error, - esphome_state_property, + esphome_float_state_property, platform_async_setup_entry, ) from .enum_mapper import EsphomeEnumMapper @@ -57,13 +56,11 @@ class EsphomeNumber(EsphomeEntity[NumberInfo, NumberState], NumberEntity): self._attr_mode = NumberMode.AUTO @property - @esphome_state_property + @esphome_float_state_property def native_value(self) -> float | None: """Return the state of the entity.""" state = self._state - if state.missing_state or not math.isfinite(state.state): - return None - return state.state + return None if state.missing_state else state.state @convert_api_error_ha_error async def async_set_native_value(self, value: float) -> None: diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 0742bebed28..670c92d291e 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -26,7 +26,7 @@ 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 .entity import EsphomeEntity, platform_async_setup_entry from .enum_mapper import EsphomeEnumMapper @@ -93,15 +93,16 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity): 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 state.missing_state or not math.isfinite(state.state): + if not self._has_state or (state := self._state).missing_state: return None - if self._attr_device_class is SensorDeviceClass.TIMESTAMP: - return dt_util.utc_from_timestamp(state.state) - return f"{state.state:.{self._static_info.accuracy_decimals}f}" + 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): @@ -117,17 +118,17 @@ class EsphomeTextSensor(EsphomeEntity[TextSensorInfo, TextSensorState], SensorEn ) @property - @esphome_state_property def native_value(self) -> str | datetime | date | None: """Return the state of the entity.""" - state = self._state - if state.missing_state: + if not self._has_state or (state := self._state).missing_state: return None - if self._attr_device_class is SensorDeviceClass.TIMESTAMP: - return dt_util.parse_datetime(state.state) + state_str = state.state + device_class = self.device_class + if device_class is SensorDeviceClass.TIMESTAMP: + return dt_util.parse_datetime(state_str) if ( - self._attr_device_class is SensorDeviceClass.DATE - and (value := dt_util.parse_datetime(state.state)) is not None + device_class is SensorDeviceClass.DATE + and (value := dt_util.parse_datetime(state_str)) is not None ): return value.date() - return state.state + return state_str diff --git a/homeassistant/components/esphome/text.py b/homeassistant/components/esphome/text.py index f9dbbbcd853..36d77aac4a0 100644 --- a/homeassistant/components/esphome/text.py +++ b/homeassistant/components/esphome/text.py @@ -43,9 +43,7 @@ class EsphomeText(EsphomeEntity[TextInfo, TextState], TextEntity): def native_value(self) -> str | None: """Return the state of the entity.""" state = self._state - if state.missing_state: - return None - return state.state + return None if state.missing_state else state.state @convert_api_error_ha_error async def async_set_value(self, value: str) -> None: