Fix NaN values in Modbus slaves sensors (#139969)

* Fix NaN values in Modbus slaves sensors

* fixXbdraco
This commit is contained in:
Claudio Ruggeri - CR-Tech 2025-05-26 21:04:38 +02:00 committed by GitHub
parent 2dc2b0ffac
commit b667fb2728
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 28 deletions

View File

@ -285,10 +285,10 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
v_result = []
for entry in val:
v_temp = self.__process_raw_value(entry)
if v_temp is None:
v_result.append("0")
else:
if self._data_type != DataType.CUSTOM:
v_result.append(str(v_temp))
else:
v_result.append(str(v_temp) if v_temp is not None else "0")
return ",".join(map(str, v_result))
# Apply scale, precision, limits to floats and ints

View File

@ -73,7 +73,9 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity):
super().__init__(hass, hub, entry)
if slave_count:
self._count = self._count * (slave_count + 1)
self._coordinator: DataUpdateCoordinator[list[float] | None] | None = None
self._coordinator: DataUpdateCoordinator[list[float | None] | None] | None = (
None
)
self._attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT)
self._attr_state_class = entry.get(CONF_STATE_CLASS)
self._attr_device_class = entry.get(CONF_DEVICE_CLASS)
@ -120,37 +122,45 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity):
self._coordinator.async_set_updated_data(None)
self.async_write_ha_state()
return
self._attr_available = True
result = self.unpack_structure_result(raw_result.registers)
if self._coordinator:
result_array: list[float | None] = []
if result:
result_array = list(
map(
float if not self._value_is_int else int,
result.split(","),
)
for i in result.split(","):
if i != "None":
result_array.append(
float(i) if not self._value_is_int else int(i)
)
else:
result_array.append(None)
self._attr_native_value = result_array[0]
self._coordinator.async_set_updated_data(result_array)
else:
self._attr_native_value = None
self._coordinator.async_set_updated_data(None)
result_array = (self._slave_count + 1) * [None]
self._coordinator.async_set_updated_data(result_array)
else:
self._attr_native_value = result
self._attr_available = self._attr_native_value is not None
self.async_write_ha_state()
class SlaveSensor(
CoordinatorEntity[DataUpdateCoordinator[list[float] | None]],
CoordinatorEntity[DataUpdateCoordinator[list[float | None] | None]],
RestoreSensor,
SensorEntity,
):
"""Modbus slave register sensor."""
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._attr_available
def __init__(
self,
coordinator: DataUpdateCoordinator[list[float] | None],
coordinator: DataUpdateCoordinator[list[float | None] | None],
idx: int,
entry: dict[str, Any],
) -> None:
@ -178,4 +188,5 @@ class SlaveSensor(
"""Handle updated data from the coordinator."""
result = self.coordinator.data
self._attr_native_value = result[self._idx] if result else None
self._attr_available = result is not None
super()._handle_coordinator_update()

View File

@ -428,7 +428,7 @@ async def test_config_wrong_struct_sensor(
},
[0x89AB],
False,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
),
(
{
@ -631,7 +631,7 @@ async def test_config_wrong_struct_sensor(
},
[0x8000, 0x0000],
False,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
),
(
{
@ -742,7 +742,7 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
int.from_bytes(struct.pack(">f", float("nan"))[2:4]),
],
False,
["34899771392.0", "0.0"],
["34899771392.0", STATE_UNKNOWN],
),
(
{
@ -757,7 +757,7 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
int.from_bytes(struct.pack(">f", float("nan"))[2:4]),
],
False,
["34899771392.0", "0.0"],
["34899771392.0", STATE_UNKNOWN],
),
(
{
@ -802,7 +802,11 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
},
[0x0102, 0x0304, 0x0403, 0x0201, 0x0403],
False,
[STATE_UNAVAILABLE, STATE_UNKNOWN, STATE_UNKNOWN],
[
STATE_UNKNOWN,
STATE_UNKNOWN,
STATE_UNKNOWN,
],
),
(
{
@ -857,7 +861,7 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
},
[0x0102, 0x0304, 0x0403, 0x0201],
True,
[STATE_UNAVAILABLE, STATE_UNKNOWN],
[STATE_UNAVAILABLE, STATE_UNAVAILABLE],
),
(
{
@ -866,7 +870,7 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
},
[0x0102, 0x0304, 0x0403, 0x0201],
True,
[STATE_UNAVAILABLE, STATE_UNKNOWN],
[STATE_UNAVAILABLE, STATE_UNAVAILABLE],
),
(
{
@ -875,7 +879,7 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
},
[],
False,
[STATE_UNAVAILABLE, STATE_UNKNOWN],
[STATE_UNKNOWN, STATE_UNKNOWN],
),
(
{
@ -884,7 +888,35 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
},
[],
False,
[STATE_UNAVAILABLE, STATE_UNKNOWN],
[STATE_UNKNOWN, STATE_UNKNOWN],
),
(
{
CONF_VIRTUAL_COUNT: 4,
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
CONF_DATA_TYPE: DataType.INT32,
CONF_NAN_VALUE: "0x800000",
},
[
0x0,
0x35,
0x0,
0x38,
0x80,
0x0,
0x80,
0x0,
0xFFFF,
0xFFF6,
],
False,
[
"53",
"56",
STATE_UNKNOWN,
STATE_UNKNOWN,
"-10",
],
),
],
)
@ -1103,7 +1135,7 @@ async def test_virtual_swap_sensor(
)
async def test_wrong_unpack(hass: HomeAssistant, mock_do_cycle) -> None:
"""Run test for sensor."""
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
assert hass.states.get(ENTITY_ID).state == STATE_UNKNOWN
@pytest.mark.parametrize(
@ -1131,14 +1163,14 @@ async def test_wrong_unpack(hass: HomeAssistant, mock_do_cycle) -> None:
int.from_bytes(struct.pack(">f", float("nan"))[0:2]),
int.from_bytes(struct.pack(">f", float("nan"))[2:4]),
],
STATE_UNAVAILABLE,
STATE_UNKNOWN,
),
(
{
CONF_DATA_TYPE: DataType.FLOAT32,
},
[0x6E61, 0x6E00],
STATE_UNAVAILABLE,
STATE_UNKNOWN,
),
(
{
@ -1147,7 +1179,7 @@ async def test_wrong_unpack(hass: HomeAssistant, mock_do_cycle) -> None:
CONF_STRUCTURE: "4s",
},
[0x6E61, 0x6E00],
STATE_UNAVAILABLE,
STATE_UNKNOWN,
),
(
{