Handle explicit Modbus NaN values (#90800)
Co-authored-by: jan iversen <jancasacondor@gmail.com>
This commit is contained in:
parent
0511071757
commit
c4a5373976
6 changed files with 57 additions and 2 deletions
|
@ -82,6 +82,7 @@ from .const import ( # noqa: F401
|
|||
CONF_MIN_TEMP,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_MSG_WAIT,
|
||||
CONF_NAN_VALUE,
|
||||
CONF_PARITY,
|
||||
CONF_PRECISION,
|
||||
CONF_RETRIES,
|
||||
|
@ -123,6 +124,7 @@ from .modbus import ModbusHub, async_modbus_setup
|
|||
from .validators import (
|
||||
duplicate_entity_validator,
|
||||
duplicate_modbus_validator,
|
||||
nan_validator,
|
||||
number_validator,
|
||||
scan_interval_validator,
|
||||
struct_validator,
|
||||
|
@ -298,6 +300,7 @@ SENSOR_SCHEMA = vol.All(
|
|||
vol.Optional(CONF_SLAVE_COUNT, default=0): cv.positive_int,
|
||||
vol.Optional(CONF_MIN_VALUE): number_validator,
|
||||
vol.Optional(CONF_MAX_VALUE): number_validator,
|
||||
vol.Optional(CONF_NAN_VALUE): nan_validator,
|
||||
vol.Optional(CONF_ZERO_SUPPRESS): number_validator,
|
||||
}
|
||||
),
|
||||
|
|
|
@ -22,6 +22,7 @@ from homeassistant.const import (
|
|||
CONF_STRUCTURE,
|
||||
CONF_UNIQUE_ID,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
@ -46,6 +47,7 @@ from .const import (
|
|||
CONF_LAZY_ERROR,
|
||||
CONF_MAX_VALUE,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_NAN_VALUE,
|
||||
CONF_PRECISION,
|
||||
CONF_SCALE,
|
||||
CONF_STATE_OFF,
|
||||
|
@ -101,6 +103,7 @@ class BasePlatform(Entity):
|
|||
|
||||
self._min_value = get_optional_numeric_config(CONF_MIN_VALUE)
|
||||
self._max_value = get_optional_numeric_config(CONF_MAX_VALUE)
|
||||
self._nan_value = entry.get(CONF_NAN_VALUE, None)
|
||||
self._zero_suppress = get_optional_numeric_config(CONF_ZERO_SUPPRESS)
|
||||
|
||||
@abstractmethod
|
||||
|
@ -173,8 +176,10 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
|||
registers.reverse()
|
||||
return registers
|
||||
|
||||
def __process_raw_value(self, entry: float | int) -> float | int:
|
||||
"""Process value from sensor with scaling, offset, min/max etc."""
|
||||
def __process_raw_value(self, entry: float | int | str) -> float | int | str:
|
||||
"""Process value from sensor with NaN handling, scaling, offset, min/max etc."""
|
||||
if self._nan_value and entry in (self._nan_value, -self._nan_value):
|
||||
return STATE_UNAVAILABLE
|
||||
val: float | int = self._scale * entry + self._offset
|
||||
if self._min_value is not None and val < self._min_value:
|
||||
return self._min_value
|
||||
|
@ -225,6 +230,10 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
|||
# the conversion only when it's absolutely necessary.
|
||||
if isinstance(val_result, int) and self._precision == 0:
|
||||
return str(val_result)
|
||||
if isinstance(val_result, str):
|
||||
if val_result == "nan":
|
||||
val_result = STATE_UNAVAILABLE # pragma: no cover
|
||||
return val_result
|
||||
return f"{float(val_result):.{self._precision}f}"
|
||||
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ CONF_MAX_VALUE = "max_value"
|
|||
CONF_MIN_TEMP = "min_temp"
|
||||
CONF_MIN_VALUE = "min_value"
|
||||
CONF_MSG_WAIT = "message_wait_milliseconds"
|
||||
CONF_NAN_VALUE = "nan_value"
|
||||
CONF_PARITY = "parity"
|
||||
CONF_REGISTER = "register"
|
||||
CONF_REGISTER_TYPE = "register_type"
|
||||
|
|
|
@ -139,6 +139,20 @@ def number_validator(value: Any) -> int | float:
|
|||
raise vol.Invalid(f"invalid number {value}") from err
|
||||
|
||||
|
||||
def nan_validator(value: Any) -> int:
|
||||
"""Convert nan string to number (can be hex string or int)."""
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
try:
|
||||
return int(value, 16)
|
||||
except (TypeError, ValueError) as err:
|
||||
raise vol.Invalid(f"invalid number {value}") from err
|
||||
|
||||
|
||||
def scan_interval_validator(config: dict) -> dict:
|
||||
"""Control scan_interval."""
|
||||
for hub in config:
|
||||
|
|
|
@ -64,6 +64,7 @@ from homeassistant.components.modbus.const import (
|
|||
from homeassistant.components.modbus.validators import (
|
||||
duplicate_entity_validator,
|
||||
duplicate_modbus_validator,
|
||||
nan_validator,
|
||||
number_validator,
|
||||
struct_validator,
|
||||
)
|
||||
|
@ -141,6 +142,23 @@ async def test_number_validator() -> None:
|
|||
pytest.fail("Number_validator not throwing exception")
|
||||
|
||||
|
||||
async def test_nan_validator() -> None:
|
||||
"""Test number validator."""
|
||||
|
||||
for value, value_type in (
|
||||
(15, int),
|
||||
("15", int),
|
||||
("abcdef", int),
|
||||
("0xabcdef", int),
|
||||
):
|
||||
assert isinstance(nan_validator(value), value_type)
|
||||
|
||||
with pytest.raises(vol.Invalid):
|
||||
nan_validator("x15")
|
||||
with pytest.raises(vol.Invalid):
|
||||
nan_validator("not a hex string")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"do_config",
|
||||
[
|
||||
|
|
|
@ -9,6 +9,7 @@ from homeassistant.components.modbus.const import (
|
|||
CONF_LAZY_ERROR,
|
||||
CONF_MAX_VALUE,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_NAN_VALUE,
|
||||
CONF_PRECISION,
|
||||
CONF_SCALE,
|
||||
CONF_SLAVE_COUNT,
|
||||
|
@ -558,6 +559,15 @@ async def test_config_wrong_struct_sensor(
|
|||
False,
|
||||
str(int(0x02010404)),
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_DATA_TYPE: DataType.INT32,
|
||||
CONF_NAN_VALUE: "0x80000000",
|
||||
},
|
||||
[0x8000, 0x0000],
|
||||
False,
|
||||
STATE_UNAVAILABLE,
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_DATA_TYPE: DataType.INT32,
|
||||
|
|
Loading…
Add table
Reference in a new issue