Warn if numeric sensors have an invalid value (#85863)
Co-authored-by: mib1185 <mail@mib85.de>pull/86072/head
parent
ccd8bc14e0
commit
3179101fbc
|
@ -401,7 +401,12 @@ class SensorEntity(Entity):
|
|||
native_unit_of_measurement = self.native_unit_of_measurement
|
||||
unit_of_measurement = self.unit_of_measurement
|
||||
value = self.native_value
|
||||
device_class = self.device_class
|
||||
device_class: SensorDeviceClass | None = None
|
||||
with suppress(ValueError):
|
||||
# For the sake of validation, we can ignore custom device classes
|
||||
# (customization and legacy style translations)
|
||||
device_class = SensorDeviceClass(str(self.device_class))
|
||||
state_class = self.state_class
|
||||
|
||||
# Sensors with device classes indicating a non-numeric value
|
||||
# should not have a state class or unit of measurement
|
||||
|
@ -478,6 +483,29 @@ class SensorEntity(Entity):
|
|||
f"Sensor {self.entity_id} provides state value '{value}', "
|
||||
"which is not in the list of options provided"
|
||||
)
|
||||
return value
|
||||
|
||||
# If the sensor has neither a device class, a state class nor
|
||||
# a unit_of measurement then there are no further checks or conversions
|
||||
if not device_class and not state_class and not unit_of_measurement:
|
||||
return value
|
||||
|
||||
if not isinstance(value, (int, float, Decimal)):
|
||||
try:
|
||||
_ = float(value) # type: ignore[arg-type]
|
||||
except (TypeError, ValueError):
|
||||
_LOGGER.warning(
|
||||
"Sensor %s has device class %s, state class %s and unit %s "
|
||||
"thus indicating it has a numeric value; however, it has the "
|
||||
"non-numeric value: %s (%s). This will stop working in 2023.4",
|
||||
self.entity_id,
|
||||
device_class,
|
||||
state_class,
|
||||
unit_of_measurement,
|
||||
value,
|
||||
type(value),
|
||||
)
|
||||
return value
|
||||
|
||||
if (
|
||||
native_unit_of_measurement != unit_of_measurement
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
"""The test for sensor entity."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, timezone
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from pytest import approx
|
||||
|
@ -1231,3 +1234,134 @@ async def test_device_classes_with_invalid_unit_of_measurement(
|
|||
"is using native unit of measurement 'INVALID!' which is not a valid "
|
||||
f"unit for the device class ('{device_class}') it is using"
|
||||
) in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_class,state_class,unit",
|
||||
[
|
||||
(SensorDeviceClass.AQI, None, None),
|
||||
(None, SensorStateClass.MEASUREMENT, None),
|
||||
(None, None, UnitOfTemperature.CELSIUS),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"native_value,expected",
|
||||
[
|
||||
("abc", "abc"),
|
||||
("13.7.1", "13.7.1"),
|
||||
(datetime(2012, 11, 10, 7, 35, 1), "2012-11-10 07:35:01"),
|
||||
(date(2012, 11, 10), "2012-11-10"),
|
||||
],
|
||||
)
|
||||
async def test_non_numeric_validation(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
enable_custom_integrations: None,
|
||||
native_value: Any,
|
||||
expected: str,
|
||||
device_class: SensorDeviceClass | None,
|
||||
state_class: SensorStateClass | None,
|
||||
unit: str | None,
|
||||
) -> None:
|
||||
"""Test error on expected numeric entities."""
|
||||
platform = getattr(hass.components, "test.sensor")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES["0"] = platform.MockSensor(
|
||||
name="Test",
|
||||
native_value=native_value,
|
||||
device_class=device_class,
|
||||
native_unit_of_measurement=unit,
|
||||
state_class=state_class,
|
||||
)
|
||||
entity0 = platform.ENTITIES["0"]
|
||||
|
||||
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.state == expected
|
||||
|
||||
assert (
|
||||
"thus indicating it has a numeric value; "
|
||||
f"however, it has the non-numeric value: {native_value}"
|
||||
) in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_class,state_class,unit",
|
||||
[
|
||||
(SensorDeviceClass.AQI, None, None),
|
||||
(None, SensorStateClass.MEASUREMENT, None),
|
||||
(None, None, UnitOfTemperature.CELSIUS),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"native_value,expected",
|
||||
[
|
||||
(13, "13"),
|
||||
(17.50, "17.5"),
|
||||
(Decimal(18.50), "18.5"),
|
||||
("19.70", "19.70"),
|
||||
(None, STATE_UNKNOWN),
|
||||
],
|
||||
)
|
||||
async def test_numeric_validation(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
enable_custom_integrations: None,
|
||||
native_value: Any,
|
||||
expected: str,
|
||||
device_class: SensorDeviceClass | None,
|
||||
state_class: SensorStateClass | None,
|
||||
unit: str | None,
|
||||
) -> None:
|
||||
"""Test does not error on expected numeric entities."""
|
||||
platform = getattr(hass.components, "test.sensor")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES["0"] = platform.MockSensor(
|
||||
name="Test",
|
||||
native_value=native_value,
|
||||
device_class=device_class,
|
||||
native_unit_of_measurement=unit,
|
||||
state_class=state_class,
|
||||
)
|
||||
entity0 = platform.ENTITIES["0"]
|
||||
|
||||
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.state == expected
|
||||
|
||||
assert (
|
||||
"thus indicating it has a numeric value; "
|
||||
f"however, it has the non-numeric value: {native_value}"
|
||||
) not in caplog.text
|
||||
|
||||
|
||||
async def test_numeric_validation_ignores_custom_device_class(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
enable_custom_integrations: None,
|
||||
) -> None:
|
||||
"""Test does not error on expected numeric entities."""
|
||||
native_value = "Three elephants"
|
||||
platform = getattr(hass.components, "test.sensor")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES["0"] = platform.MockSensor(
|
||||
name="Test",
|
||||
native_value=native_value,
|
||||
device_class="custom__deviceclass",
|
||||
)
|
||||
entity0 = platform.ENTITIES["0"]
|
||||
|
||||
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.state == "Three elephants"
|
||||
|
||||
assert (
|
||||
"thus indicating it has a numeric value; "
|
||||
f"however, it has the non-numeric value: {native_value}"
|
||||
) not in caplog.text
|
||||
|
|
Loading…
Reference in New Issue