Split esphome state property decorators (#124332)

This commit is contained in:
J. Nick Koston 2024-08-25 06:48:17 -10:00 committed by GitHub
parent ebc49d938a
commit 41b129e990
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 49 additions and 36 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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: