mirror of
https://github.com/esphome/aioesphomeapi.git
synced 2025-01-31 23:11:43 +01:00
Fix number rounding for protobuf messages (#93)
* Fix number rounding for protobuf messages * Switch to converter_field
This commit is contained in:
parent
4981b60f95
commit
738346c9cb
@ -14,6 +14,8 @@ from typing import (
|
||||
cast,
|
||||
)
|
||||
|
||||
from .util import fix_float_single_double_conversion
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .api_pb2 import HomeassistantServiceMap # type: ignore
|
||||
|
||||
@ -407,9 +409,15 @@ class ClimateInfo(EntityInfo):
|
||||
supported_modes: List[ClimateMode] = converter_field(
|
||||
default_factory=list, converter=ClimateMode.convert_list
|
||||
)
|
||||
visual_min_temperature: float = 0.0
|
||||
visual_max_temperature: float = 0.0
|
||||
visual_temperature_step: float = 0.0
|
||||
visual_min_temperature: float = converter_field(
|
||||
default=0.0, converter=fix_float_single_double_conversion
|
||||
)
|
||||
visual_max_temperature: float = converter_field(
|
||||
default=0.0, converter=fix_float_single_double_conversion
|
||||
)
|
||||
visual_temperature_step: float = converter_field(
|
||||
default=0.0, converter=fix_float_single_double_conversion
|
||||
)
|
||||
legacy_supports_away: bool = False
|
||||
supports_action: bool = False
|
||||
supported_fan_modes: List[ClimateFanMode] = converter_field(
|
||||
@ -446,10 +454,18 @@ class ClimateState(EntityState):
|
||||
action: Optional[ClimateAction] = converter_field(
|
||||
default=ClimateAction.OFF, converter=ClimateAction.convert
|
||||
)
|
||||
current_temperature: float = 0.0
|
||||
target_temperature: float = 0.0
|
||||
target_temperature_low: float = 0.0
|
||||
target_temperature_high: float = 0.0
|
||||
current_temperature: float = converter_field(
|
||||
default=0.0, converter=fix_float_single_double_conversion
|
||||
)
|
||||
target_temperature: float = converter_field(
|
||||
default=0.0, converter=fix_float_single_double_conversion
|
||||
)
|
||||
target_temperature_low: float = converter_field(
|
||||
default=0.0, converter=fix_float_single_double_conversion
|
||||
)
|
||||
target_temperature_high: float = converter_field(
|
||||
default=0.0, converter=fix_float_single_double_conversion
|
||||
)
|
||||
legacy_away: bool = False
|
||||
fan_mode: Optional[ClimateFanMode] = converter_field(
|
||||
default=ClimateFanMode.ON, converter=ClimateFanMode.convert
|
||||
@ -475,12 +491,16 @@ class NumberInfo(EntityInfo):
|
||||
icon: str = ""
|
||||
min_value: float = 0.0
|
||||
max_value: float = 0.0
|
||||
step: float = 0.0
|
||||
step: float = converter_field(
|
||||
default=0.0, converter=fix_float_single_double_conversion
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NumberState(EntityState):
|
||||
state: float = 0.0
|
||||
state: float = converter_field(
|
||||
default=0.0, converter=fix_float_single_double_conversion
|
||||
)
|
||||
missing_state: bool = False
|
||||
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import math
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@ -26,3 +27,26 @@ def bytes_to_varuint(value: bytes) -> Optional[int]:
|
||||
if (val & 0x80) == 0:
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
def fix_float_single_double_conversion(value: float) -> float:
|
||||
"""Fix precision for single-precision floats and return what was probably
|
||||
meant as a float.
|
||||
|
||||
In ESPHome we work with single-precision floats internally for performance.
|
||||
But python uses double-precision floats, and when protobuf reads the message
|
||||
it's auto-converted to a double (which is possible losslessly).
|
||||
|
||||
Unfortunately the float representation of 0.1 converted to a double is not the
|
||||
double representation of 0.1, but 0.10000000149011612.
|
||||
|
||||
This methods tries to round to the closest decimal value that a float of this
|
||||
magnitude can accurately represent.
|
||||
"""
|
||||
if value == 0 or not math.isfinite(value):
|
||||
return value
|
||||
abs_val = abs(value)
|
||||
# assume ~7 decimals of precision for floats to be safe
|
||||
l10 = math.ceil(math.log10(abs_val))
|
||||
prec = 7 - l10
|
||||
return round(value, prec)
|
||||
|
@ -1,3 +1,5 @@
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from aioesphomeapi import util
|
||||
@ -20,3 +22,29 @@ def test_varuint_to_bytes(val, encoded):
|
||||
@pytest.mark.parametrize("val, encoded", VARUINT_TESTCASES)
|
||||
def test_bytes_to_varuint(val, encoded):
|
||||
assert util.bytes_to_varuint(encoded) == val
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input, output",
|
||||
[
|
||||
(0, 0),
|
||||
(float("inf"), float("inf")),
|
||||
(float("-inf"), float("-inf")),
|
||||
(0.1, 0.1),
|
||||
(-0.0, -0.0),
|
||||
(0.10000000149011612, 0.1),
|
||||
(1, 1),
|
||||
(-1, -1),
|
||||
(-0.10000000149011612, -0.1),
|
||||
(-152198557936981706463557226105667584, -152198600000000000000000000000000000),
|
||||
(-0.0030539485160261, -0.003053949),
|
||||
(0.5, 0.5),
|
||||
(0.0000000000000019, 0.0000000000000019),
|
||||
],
|
||||
)
|
||||
def test_fix_float_single_double_conversion(input, output):
|
||||
assert util.fix_float_single_double_conversion(input) == output
|
||||
|
||||
|
||||
def test_fix_float_single_double_conversion_nan():
|
||||
assert math.isnan(util.fix_float_single_double_conversion(float("nan")))
|
||||
|
Loading…
Reference in New Issue
Block a user