Implement suggested_display_precision for ESPHome (#147849)

This commit is contained in:
Jesse Hills 2025-07-01 19:16:23 +12:00 committed by GitHub
parent 35f0505c7b
commit 9469c6ad1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 75 additions and 4 deletions

View File

@ -81,6 +81,7 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity):
# 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_suggested_display_precision = static_info.accuracy_decimals
self._attr_device_class = try_parse_enum(
SensorDeviceClass, static_info.device_class
)
@ -97,7 +98,7 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity):
self._attr_state_class = _STATE_CLASSES.from_esphome(state_class)
@property
def native_value(self) -> datetime | str | None:
def native_value(self) -> datetime | int | float | None:
"""Return the state of the entity."""
if not self._has_state or (state := self._state).missing_state:
return None
@ -106,7 +107,7 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity):
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}"
return state_float
class EsphomeTextSensor(EsphomeEntity[TextSensorInfo, TextSensorState], SensorEntity):

View File

@ -375,7 +375,7 @@ async def test_deep_sleep_device(
assert state.state == STATE_ON
state = hass.states.get("sensor.test_my_sensor")
assert state is not None
assert state.state == "123"
assert state.state == "123.0"
await mock_device.mock_disconnect(False)
await hass.async_block_till_done()
@ -394,7 +394,7 @@ async def test_deep_sleep_device(
assert state.state == STATE_ON
state = hass.states.get("sensor.test_my_sensor")
assert state is not None
assert state.state == "123"
assert state.state == "123.0"
await mock_device.mock_disconnect(True)
await hass.async_block_till_done()

View File

@ -13,18 +13,28 @@ from aioesphomeapi import (
TextSensorInfo,
TextSensorState,
)
import pytest
from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
SensorDeviceClass,
SensorStateClass,
async_rounded_state,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT,
PERCENTAGE,
STATE_UNKNOWN,
EntityCategory,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfPower,
UnitOfPressure,
UnitOfTemperature,
UnitOfVolume,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
@ -441,3 +451,63 @@ async def test_generic_numeric_sensor_empty_string_uom(
assert state is not None
assert state.state == "123"
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
@pytest.mark.parametrize(
("device_class", "unit_of_measurement", "state_value", "expected_precision"),
[
(SensorDeviceClass.TEMPERATURE, UnitOfTemperature.CELSIUS, 23.456, 1),
(SensorDeviceClass.TEMPERATURE, UnitOfTemperature.CELSIUS, 0.1, 1),
(SensorDeviceClass.TEMPERATURE, UnitOfTemperature.CELSIUS, -25.789, 1),
(SensorDeviceClass.POWER, UnitOfPower.WATT, 1234.56, 0),
(SensorDeviceClass.POWER, UnitOfPower.WATT, 1.23456, 3),
(SensorDeviceClass.POWER, UnitOfPower.WATT, 0.123, 3),
(SensorDeviceClass.ENERGY, UnitOfEnergy.WATT_HOUR, 1234.5, 0),
(SensorDeviceClass.ENERGY, UnitOfEnergy.WATT_HOUR, 12.3456, 2),
(SensorDeviceClass.VOLTAGE, UnitOfElectricPotential.VOLT, 230.45, 1),
(SensorDeviceClass.VOLTAGE, UnitOfElectricPotential.VOLT, 3.3, 1),
(SensorDeviceClass.CURRENT, UnitOfElectricCurrent.AMPERE, 15.678, 2),
(SensorDeviceClass.CURRENT, UnitOfElectricCurrent.AMPERE, 0.015, 3),
(SensorDeviceClass.ATMOSPHERIC_PRESSURE, UnitOfPressure.HPA, 1013.25, 1),
(SensorDeviceClass.PRESSURE, UnitOfPressure.BAR, 1.01325, 3),
(SensorDeviceClass.VOLUME, UnitOfVolume.LITERS, 45.67, 1),
(SensorDeviceClass.VOLUME, UnitOfVolume.LITERS, 4567.0, 0),
(SensorDeviceClass.HUMIDITY, PERCENTAGE, 87.654, 1),
(SensorDeviceClass.HUMIDITY, PERCENTAGE, 45.2, 1),
(SensorDeviceClass.BATTERY, PERCENTAGE, 95.2, 1),
(SensorDeviceClass.BATTERY, PERCENTAGE, 100.0, 1),
],
)
async def test_suggested_display_precision_by_device_class(
hass: HomeAssistant,
mock_client: APIClient,
mock_esphome_device: MockESPHomeDeviceType,
device_class: SensorDeviceClass,
unit_of_measurement: str,
state_value: float,
expected_precision: int,
) -> None:
"""Test suggested display precision for different device classes."""
entity_info = [
SensorInfo(
object_id="mysensor",
key=1,
name="my sensor",
unique_id="my_sensor",
accuracy_decimals=expected_precision,
device_class=device_class.value,
unit_of_measurement=unit_of_measurement,
)
]
states = [SensorState(key=1, state=state_value)]
await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
states=states,
)
state = hass.states.get("sensor.test_my_sensor")
assert state is not None
assert float(
async_rounded_state(hass, "sensor.test_my_sensor", state)
) == pytest.approx(round(state_value, expected_precision))