Skip to content

Commit

Permalink
Remove modbus pragma no cover and solve nan (#99221)
Browse files Browse the repository at this point in the history
* Remove pragma no cover.

* Ruff !

* Review comments.

* update test.

* Review.

* review.

* Add slave test.
  • Loading branch information
janiversen authored Sep 12, 2023
1 parent 9acca1b commit c178388
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 13 deletions.
25 changes: 15 additions & 10 deletions homeassistant/components/modbus/base_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,14 @@ def _swap_registers(self, registers: list[int], slave_count: int) -> list[int]:
registers.reverse()
return registers

def __process_raw_value(self, entry: float | int | str) -> float | int | str | None:
def __process_raw_value(
self, entry: float | int | str | bytes
) -> float | int | str | bytes | None:
"""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 None
if isinstance(entry, bytes):
return entry
val: float | int = self._scale * entry + self._offset
if self._min_value is not None and val < self._min_value:
return self._min_value
Expand Down Expand Up @@ -234,14 +238,20 @@ def unpack_structure_result(self, registers: list[int]) -> str | None:
if isinstance(v_temp, int) and self._precision == 0:
v_result.append(str(v_temp))
elif v_temp is None:
v_result.append("") # pragma: no cover
v_result.append("0")
elif v_temp != v_temp: # noqa: PLR0124
# NaN float detection replace with None
v_result.append("nan") # pragma: no cover
v_result.append("0")
else:
v_result.append(f"{float(v_temp):.{self._precision}f}")
return ",".join(map(str, v_result))

# NaN float detection replace with None
if val[0] != val[0]: # noqa: PLR0124
return None
if byte_string == b"nan\x00":
return None

# Apply scale, precision, limits to floats and ints
val_result = self.__process_raw_value(val[0])

Expand All @@ -251,15 +261,10 @@ def unpack_structure_result(self, registers: list[int]) -> str | None:

if val_result is None:
return None
# NaN float detection replace with None
if val_result != val_result: # noqa: PLR0124
return None # pragma: no cover
if isinstance(val_result, int) and self._precision == 0:
return str(val_result)
if isinstance(val_result, str):
if val_result == "nan":
val_result = None # pragma: no cover
return val_result
if isinstance(val_result, bytes):
return val_result.decode()
return f"{float(val_result):.{self._precision}f}"


Expand Down
119 changes: 116 additions & 3 deletions tests/components/modbus/test_sensor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""The tests for the Modbus sensor component."""
import struct

from freezegun.api import FrozenDateTimeFactory
import pytest

Expand Down Expand Up @@ -654,6 +656,21 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
@pytest.mark.parametrize(
("config_addon", "register_words", "do_exception", "expected"),
[
(
{
CONF_SLAVE_COUNT: 1,
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
CONF_DATA_TYPE: DataType.FLOAT32,
},
[
0x5102,
0x0304,
int.from_bytes(struct.pack(">f", float("nan"))[0:2]),
int.from_bytes(struct.pack(">f", float("nan"))[2:4]),
],
False,
["34899771392", "0"],
),
(
{
CONF_SLAVE_COUNT: 0,
Expand Down Expand Up @@ -930,6 +947,65 @@ async def test_wrong_unpack(hass: HomeAssistant, mock_do_cycle) -> None:
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE


@pytest.mark.parametrize(
"do_config",
[
{
CONF_SENSORS: [
{
CONF_NAME: TEST_ENTITY_NAME,
CONF_ADDRESS: 51,
CONF_SCAN_INTERVAL: 1,
},
],
},
],
)
@pytest.mark.parametrize(
("config_addon", "register_words", "expected"),
[
(
{
CONF_DATA_TYPE: DataType.FLOAT32,
},
[
int.from_bytes(struct.pack(">f", float("nan"))[0:2]),
int.from_bytes(struct.pack(">f", float("nan"))[2:4]),
],
STATE_UNAVAILABLE,
),
(
{
CONF_DATA_TYPE: DataType.FLOAT32,
},
[0x6E61, 0x6E00],
STATE_UNAVAILABLE,
),
(
{
CONF_DATA_TYPE: DataType.CUSTOM,
CONF_COUNT: 2,
CONF_STRUCTURE: "4s",
},
[0x6E61, 0x6E00],
STATE_UNAVAILABLE,
),
(
{
CONF_DATA_TYPE: DataType.CUSTOM,
CONF_COUNT: 2,
CONF_STRUCTURE: "4s",
},
[0x6161, 0x6100],
"aaa\x00",
),
],
)
async def test_unpack_ok(hass: HomeAssistant, mock_do_cycle, expected) -> None:
"""Run test for sensor."""
assert hass.states.get(ENTITY_ID).state == expected


@pytest.mark.parametrize(
"do_config",
[
Expand Down Expand Up @@ -989,10 +1065,35 @@ async def test_lazy_error_sensor(
CONF_DATA_TYPE: DataType.CUSTOM,
CONF_STRUCTURE: ">4f",
},
# floats: 7.931250095367432, 10.600000381469727,
# floats: nan, 10.600000381469727,
# 1.000879611487865e-28, 10.566553115844727
[0x40FD, 0xCCCD, 0x4129, 0x999A, 0x10FD, 0xC0CD, 0x4129, 0x109A],
"7.93,10.60,0.00,10.57",
[
int.from_bytes(struct.pack(">f", float("nan"))[0:2]),
int.from_bytes(struct.pack(">f", float("nan"))[2:4]),
0x4129,
0x999A,
0x10FD,
0xC0CD,
0x4129,
0x109A,
],
"0,10.60,0.00,10.57",
),
(
{
CONF_COUNT: 4,
CONF_DATA_TYPE: DataType.CUSTOM,
CONF_STRUCTURE: ">2i",
CONF_NAN_VALUE: 0x0000000F,
},
# int: nan, 10,
[
0x0000,
0x000F,
0x0000,
0x000A,
],
"0,10",
),
(
{
Expand All @@ -1012,6 +1113,18 @@ async def test_lazy_error_sensor(
[0x0101],
"257",
),
(
{
CONF_COUNT: 8,
CONF_PRECISION: 2,
CONF_DATA_TYPE: DataType.CUSTOM,
CONF_STRUCTURE: ">4f",
},
# floats: 7.931250095367432, 10.600000381469727,
# 1.000879611487865e-28, 10.566553115844727
[0x40FD, 0xCCCD, 0x4129, 0x999A, 0x10FD, 0xC0CD, 0x4129, 0x109A],
"7.93,10.60,0.00,10.57",
),
],
)
async def test_struct_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
Expand Down

0 comments on commit c178388

Please sign in to comment.