Add modbus min/max values (#86131)

* modbus min/max values
Co-authored-by: jan iversen <jancasacondor@gmail.com>
pull/86056/head^2
GrahamJB1 2023-02-02 21:29:03 +00:00 committed by GitHub
parent 9f9873b39a
commit 8bff95014c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 78 additions and 4 deletions

View File

@ -78,7 +78,9 @@ from .const import ( # noqa: F401
CONF_INPUT_TYPE,
CONF_LAZY_ERROR,
CONF_MAX_TEMP,
CONF_MAX_VALUE,
CONF_MIN_TEMP,
CONF_MIN_VALUE,
CONF_MSG_WAIT,
CONF_PARITY,
CONF_PRECISION,
@ -104,6 +106,7 @@ from .const import ( # noqa: F401
CONF_TARGET_TEMP,
CONF_VERIFY,
CONF_WRITE_TYPE,
CONF_ZERO_SUPPRESS,
DEFAULT_HUB,
DEFAULT_SCAN_INTERVAL,
DEFAULT_TEMP_UNIT,
@ -288,6 +291,9 @@ SENSOR_SCHEMA = vol.All(
vol.Optional(CONF_STATE_CLASS): SENSOR_STATE_CLASSES_SCHEMA,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
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_ZERO_SUPPRESS): number_validator,
}
),
)

View File

@ -44,6 +44,8 @@ from .const import (
CONF_DATA_TYPE,
CONF_INPUT_TYPE,
CONF_LAZY_ERROR,
CONF_MAX_VALUE,
CONF_MIN_VALUE,
CONF_PRECISION,
CONF_SCALE,
CONF_STATE_OFF,
@ -54,6 +56,7 @@ from .const import (
CONF_SWAP_WORD_BYTE,
CONF_VERIFY,
CONF_WRITE_TYPE,
CONF_ZERO_SUPPRESS,
SIGNAL_START_ENTITY,
SIGNAL_STOP_ENTITY,
DataType,
@ -92,6 +95,18 @@ class BasePlatform(Entity):
self._lazy_error_count = entry[CONF_LAZY_ERROR]
self._lazy_errors = self._lazy_error_count
def get_optional_numeric_config(config_name: str) -> int | float | None:
if (val := entry.get(config_name)) is None:
return None
assert isinstance(
val, (float, int)
), f"Expected float or int but {config_name} was {type(val)}"
return val
self._min_value = get_optional_numeric_config(CONF_MIN_VALUE)
self._max_value = get_optional_numeric_config(CONF_MAX_VALUE)
self._zero_suppress = get_optional_numeric_config(CONF_ZERO_SUPPRESS)
@abstractmethod
async def async_update(self, now: datetime | None = None) -> None:
"""Virtual function to be overwritten."""
@ -162,6 +177,17 @@ 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."""
val: float | int = self._scale * entry + self._offset
if self._min_value is not None and val < self._min_value:
return self._min_value
if self._max_value is not None and val > self._max_value:
return self._max_value
if self._zero_suppress is not None and abs(val) <= self._zero_suppress:
return 0
return val
def unpack_structure_result(self, registers: list[int]) -> str | None:
"""Convert registers to proper result."""
@ -181,10 +207,10 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
# If unpack() returns a tuple greater than 1, don't try to process the value.
# Instead, return the values of unpack(...) separated by commas.
if len(val) > 1:
# Apply scale and precision to floats and ints
# Apply scale, precision, limits to floats and ints
v_result = []
for entry in val:
v_temp = self._scale * entry + self._offset
v_temp = self.__process_raw_value(entry)
# We could convert int to float, and the code would still work; however
# we lose some precision, and unit tests will fail. Therefore, we do
@ -195,8 +221,8 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
v_result.append(f"{float(v_temp):.{self._precision}f}")
return ",".join(map(str, v_result))
# Apply scale and precision to floats and ints
val_result: float | int = self._scale * val[0] + self._offset
# Apply scale, precision, limits to floats and ints
val_result = self.__process_raw_value(val[0])
# We could convert int to float, and the code would still work; however
# we lose some precision, and unit tests will fail. Therefore, we do

View File

@ -26,7 +26,9 @@ CONF_INPUTS = "inputs"
CONF_INPUT_TYPE = "input_type"
CONF_LAZY_ERROR = "lazy_error_count"
CONF_MAX_TEMP = "max_temp"
CONF_MAX_VALUE = "max_value"
CONF_MIN_TEMP = "min_temp"
CONF_MIN_VALUE = "min_value"
CONF_MSG_WAIT = "message_wait_milliseconds"
CONF_PARITY = "parity"
CONF_REGISTER = "register"
@ -67,6 +69,7 @@ CONF_VERIFY = "verify"
CONF_VERIFY_REGISTER = "verify_register"
CONF_VERIFY_STATE = "verify_state"
CONF_WRITE_TYPE = "write_type"
CONF_ZERO_SUPPRESS = "zero_suppress"
RTUOVERTCP = "rtuovertcp"
SERIAL = "serial"

View File

@ -7,6 +7,8 @@ from homeassistant.components.modbus.const import (
CONF_DATA_TYPE,
CONF_INPUT_TYPE,
CONF_LAZY_ERROR,
CONF_MAX_VALUE,
CONF_MIN_VALUE,
CONF_PRECISION,
CONF_SCALE,
CONF_SLAVE_COUNT,
@ -15,6 +17,7 @@ from homeassistant.components.modbus.const import (
CONF_SWAP_NONE,
CONF_SWAP_WORD,
CONF_SWAP_WORD_BYTE,
CONF_ZERO_SUPPRESS,
MODBUS_DOMAIN,
DataType,
)
@ -535,6 +538,42 @@ async def test_config_wrong_struct_sensor(hass, error_message, mock_modbus, capl
False,
str(int(0x04030201)),
),
(
{
CONF_DATA_TYPE: DataType.INT32,
CONF_MAX_VALUE: int(0x02010400),
},
[0x0201, 0x0403],
False,
str(int(0x02010400)),
),
(
{
CONF_DATA_TYPE: DataType.INT32,
CONF_MIN_VALUE: int(0x02010404),
},
[0x0201, 0x0403],
False,
str(int(0x02010404)),
),
(
{
CONF_DATA_TYPE: DataType.INT32,
CONF_ZERO_SUPPRESS: int(0x00000001),
},
[0x0000, 0x0002],
False,
str(int(0x00000002)),
),
(
{
CONF_DATA_TYPE: DataType.INT32,
CONF_ZERO_SUPPRESS: int(0x00000002),
},
[0x0000, 0x0002],
False,
str(int(0)),
),
(
{
CONF_INPUT_TYPE: CALL_TYPE_REGISTER_INPUT,