mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 10:17:09 +00:00
Limit precision when stringifying float states (#48822)
* Limit precision when stringifying float states * Add test * Fix typing * Move StateType * Update * Move conversion to entity helper * Address review comments * Tweak precision * Tweak * Make _stringify_state an instance method
This commit is contained in:
parent
5e00fdccfd
commit
d2fd504442
@ -7,6 +7,8 @@ from collections.abc import Awaitable, Iterable, Mapping
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import functools as ft
|
import functools as ft
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
from timeit import default_timer as timer
|
from timeit import default_timer as timer
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -43,6 +45,10 @@ DATA_ENTITY_SOURCE = "entity_info"
|
|||||||
SOURCE_CONFIG_ENTRY = "config_entry"
|
SOURCE_CONFIG_ENTRY = "config_entry"
|
||||||
SOURCE_PLATFORM_CONFIG = "platform_config"
|
SOURCE_PLATFORM_CONFIG = "platform_config"
|
||||||
|
|
||||||
|
# Used when converting float states to string: limit precision according to machine
|
||||||
|
# epsilon to make the string representation readable
|
||||||
|
FLOAT_PRECISION = abs(int(math.floor(math.log10(abs(sys.float_info.epsilon))))) - 1
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@bind_hass
|
@bind_hass
|
||||||
@ -327,6 +333,19 @@ class Entity(ABC):
|
|||||||
|
|
||||||
self._async_write_ha_state()
|
self._async_write_ha_state()
|
||||||
|
|
||||||
|
def _stringify_state(self) -> str:
|
||||||
|
"""Convert state to string."""
|
||||||
|
if not self.available:
|
||||||
|
return STATE_UNAVAILABLE
|
||||||
|
state = self.state
|
||||||
|
if state is None:
|
||||||
|
return STATE_UNKNOWN
|
||||||
|
if isinstance(state, float):
|
||||||
|
# If the entity's state is a float, limit precision according to machine
|
||||||
|
# epsilon to make the string representation readable
|
||||||
|
return f"{state:.{FLOAT_PRECISION}}"
|
||||||
|
return str(state)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_write_ha_state(self) -> None:
|
def _async_write_ha_state(self) -> None:
|
||||||
"""Write the state to the state machine."""
|
"""Write the state to the state machine."""
|
||||||
@ -346,11 +365,8 @@ class Entity(ABC):
|
|||||||
attr = self.capability_attributes
|
attr = self.capability_attributes
|
||||||
attr = dict(attr) if attr else {}
|
attr = dict(attr) if attr else {}
|
||||||
|
|
||||||
if not self.available:
|
state = self._stringify_state()
|
||||||
state = STATE_UNAVAILABLE
|
if self.available:
|
||||||
else:
|
|
||||||
sstate = self.state
|
|
||||||
state = STATE_UNKNOWN if sstate is None else str(sstate)
|
|
||||||
attr.update(self.state_attributes or {})
|
attr.update(self.state_attributes or {})
|
||||||
extra_state_attributes = self.extra_state_attributes
|
extra_state_attributes = self.extra_state_attributes
|
||||||
# Backwards compatibility for "device_state_attributes" deprecated in 2021.4
|
# Backwards compatibility for "device_state_attributes" deprecated in 2021.4
|
||||||
|
@ -162,6 +162,26 @@ async def test_increment(hass):
|
|||||||
assert float(state.state) == 51
|
assert float(state.state) == 51
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rounding(hass):
|
||||||
|
"""Test increment introducing floating point error is rounded."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{DOMAIN: {"test_2": {"initial": 2.4, "min": 0, "max": 51, "step": 1.2}}},
|
||||||
|
)
|
||||||
|
entity_id = "input_number.test_2"
|
||||||
|
assert 2.4 + 1.2 != 3.6
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert float(state.state) == 2.4
|
||||||
|
|
||||||
|
await increment(hass, entity_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert float(state.state) == 3.6
|
||||||
|
|
||||||
|
|
||||||
async def test_decrement(hass):
|
async def test_decrement(hass):
|
||||||
"""Test decrement method."""
|
"""Test decrement method."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
@ -774,3 +774,17 @@ async def test_get_supported_features_raises_on_unknown(hass):
|
|||||||
"""Test get_supported_features raises on unknown entity_id."""
|
"""Test get_supported_features raises on unknown entity_id."""
|
||||||
with pytest.raises(HomeAssistantError):
|
with pytest.raises(HomeAssistantError):
|
||||||
entity.get_supported_features(hass, "hello.world")
|
entity.get_supported_features(hass, "hello.world")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_float_conversion(hass):
|
||||||
|
"""Test conversion of float state to string rounds."""
|
||||||
|
assert 2.4 + 1.2 != 3.6
|
||||||
|
with patch.object(entity.Entity, "state", PropertyMock(return_value=2.4 + 1.2)):
|
||||||
|
ent = entity.Entity()
|
||||||
|
ent.hass = hass
|
||||||
|
ent.entity_id = "hello.world"
|
||||||
|
ent.async_write_ha_state()
|
||||||
|
|
||||||
|
state = hass.states.get("hello.world")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "3.6"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user