Fix 64-bit modbus sensor register reads (#25672)

* Fix 64-bit modbus sensor register reads

When reading four 16-bit modbus registers as a sensor value,
slave output is stored first as 64-bit integer, but before returning
that value is converted to double precision floating point. This
causes rounding errors for integer values bigger than 2^53.

After this change floating point conversion is done only if user
has configured scaling or offset using floating points.

* Formatting

* Review fixes
This commit is contained in:
Tomi Lehto 2019-08-10 03:03:12 +03:00 committed by Paulus Schoutsen
parent fafd228418
commit b79f1336be
3 changed files with 392 additions and 5 deletions

View file

@ -2,6 +2,7 @@
import logging
import struct
from typing import Any, Union
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
@ -36,6 +37,26 @@ DATA_TYPE_UINT = "uint"
REGISTER_TYPE_HOLDING = "holding"
REGISTER_TYPE_INPUT = "input"
def number(value: Any) -> Union[int, float]:
"""Coerce a value to number without losing precision."""
if isinstance(value, int):
return value
if isinstance(value, str):
try:
value = int(value)
return value
except (TypeError, ValueError):
pass
try:
value = float(value)
return value
except (TypeError, ValueError):
raise vol.Invalid("invalid number {}".format(value))
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_REGISTERS): [
@ -47,13 +68,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
[DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT, DATA_TYPE_CUSTOM]
),
vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
vol.Optional(CONF_OFFSET, default=0): vol.Coerce(float),
vol.Optional(CONF_OFFSET, default=0): number,
vol.Optional(CONF_PRECISION, default=0): cv.positive_int,
vol.Optional(CONF_REGISTER_TYPE, default=REGISTER_TYPE_HOLDING): vol.In(
[REGISTER_TYPE_HOLDING, REGISTER_TYPE_INPUT]
),
vol.Optional(CONF_REVERSE_ORDER, default=False): cv.boolean,
vol.Optional(CONF_SCALE, default=1): vol.Coerce(float),
vol.Optional(CONF_SCALE, default=1): number,
vol.Optional(CONF_SLAVE): cv.positive_int,
vol.Optional(CONF_STRUCTURE): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
@ -207,6 +228,10 @@ class ModbusRegisterSensor(RestoreEntity):
return
byte_string = b"".join([x.to_bytes(2, byteorder="big") for x in registers])
val = struct.unpack(self._structure, byte_string)[0]
self._value = format(
self._scale * val + self._offset, ".{}f".format(self._precision)
)
val = self._scale * val + self._offset
if isinstance(val, int):
self._value = str(val)
if self._precision > 0:
self._value += "." + "0" * self._precision
else:
self._value = f"{val:.{self._precision}f}"