Skip to content

Commit

Permalink
Split esphome state property decorators (home-assistant#124332)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored and Gigatrappeur committed Aug 28, 2024
1 parent b47c7dd commit 245632f
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 36 deletions.
9 changes: 5 additions & 4 deletions homeassistant/components/esphome/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
from .entity import (
EsphomeEntity,
convert_api_error_ha_error,
esphome_float_state_property,
esphome_state_property,
platform_async_setup_entry,
)
Expand Down Expand Up @@ -227,7 +228,7 @@ def swing_mode(self) -> str | None:
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
Expand All @@ -241,19 +242,19 @@ def current_humidity(self) -> int | None:
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
Expand Down
29 changes: 22 additions & 7 deletions homeassistant/components/esphome/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/esphome/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from .entity import (
EsphomeEntity,
convert_api_error_ha_error,
esphome_float_state_property,
esphome_state_property,
platform_async_setup_entry,
)
Expand Down Expand Up @@ -79,7 +80,7 @@ def is_volume_muted(self) -> bool:
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
Expand Down
9 changes: 3 additions & 6 deletions homeassistant/components/esphome/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

from functools import partial
import math

from aioesphomeapi import (
EntityInfo,
Expand All @@ -19,7 +18,7 @@
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
Expand Down Expand Up @@ -57,13 +56,11 @@ def _on_static_info_update(self, static_info: EntityInfo) -> None:
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:
Expand Down
31 changes: 16 additions & 15 deletions homeassistant/components/esphome/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
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


Expand Down Expand Up @@ -93,15 +93,16 @@ def _on_static_info_update(self, static_info: EntityInfo) -> None:
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):
Expand All @@ -117,17 +118,17 @@ def _on_static_info_update(self, static_info: EntityInfo) -> None:
)

@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
4 changes: 1 addition & 3 deletions homeassistant/components/esphome/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ def _on_static_info_update(self, static_info: EntityInfo) -> None:
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:
Expand Down

0 comments on commit 245632f

Please sign in to comment.