Ensure mqtt sensor unit of measurement validation for state class measurement_angle (#145648)

This commit is contained in:
Jan Bouwhuis 2025-05-28 10:16:40 +02:00 committed by GitHub
parent ddf611bfdf
commit 192aa76cd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 68 additions and 5 deletions

View File

@ -39,6 +39,7 @@ from homeassistant.components.light import (
from homeassistant.components.sensor import (
CONF_STATE_CLASS,
DEVICE_CLASS_UNITS,
STATE_CLASS_UNITS,
SensorDeviceClass,
SensorStateClass,
)
@ -640,6 +641,13 @@ def validate_sensor_platform_config(
):
errors[CONF_UNIT_OF_MEASUREMENT] = "invalid_uom"
if (
(state_class := config.get(CONF_STATE_CLASS)) is not None
and state_class in STATE_CLASS_UNITS
and config.get(CONF_UNIT_OF_MEASUREMENT) not in STATE_CLASS_UNITS[state_class]
):
errors[CONF_UNIT_OF_MEASUREMENT] = "invalid_uom_for_state_class"
return errors
@ -676,11 +684,19 @@ class PlatformField:
@callback
def unit_of_measurement_selector(user_data: dict[str, Any | None]) -> Selector:
"""Return a context based unit of measurement selector."""
if (state_class := user_data.get(CONF_STATE_CLASS)) in STATE_CLASS_UNITS:
return SelectSelector(
SelectSelectorConfig(
options=[str(uom) for uom in STATE_CLASS_UNITS[state_class]],
sort=True,
custom_value=True,
)
)
if (
user_data is None
or (device_class := user_data.get(CONF_DEVICE_CLASS)) is None
or device_class not in DEVICE_CLASS_UNITS
):
device_class := user_data.get(CONF_DEVICE_CLASS)
) is None or device_class not in DEVICE_CLASS_UNITS:
return TEXT_SELECTOR
return SelectSelector(
SelectSelectorConfig(

View File

@ -14,6 +14,7 @@ from homeassistant.components.sensor import (
DEVICE_CLASS_UNITS,
DEVICE_CLASSES_SCHEMA,
ENTITY_ID_FORMAT,
STATE_CLASS_UNITS,
STATE_CLASSES_SCHEMA,
RestoreSensor,
SensorDeviceClass,
@ -117,6 +118,17 @@ def validate_sensor_state_and_device_class_config(config: ConfigType) -> ConfigT
f"got `{CONF_DEVICE_CLASS}` '{device_class}'"
)
if (
(state_class := config.get(CONF_STATE_CLASS)) is not None
and state_class in STATE_CLASS_UNITS
and (unit_of_measurement := config.get(CONF_UNIT_OF_MEASUREMENT))
not in STATE_CLASS_UNITS[state_class]
):
raise vol.Invalid(
f"The unit of measurement '{unit_of_measurement}' is not valid "
f"together with state class '{state_class}'"
)
if (device_class := config.get(CONF_DEVICE_CLASS)) is None or (
unit_of_measurement := config.get(CONF_UNIT_OF_MEASUREMENT)
) is None:

View File

@ -644,6 +644,7 @@
"invalid_template": "Invalid template",
"invalid_supported_color_modes": "Invalid supported color modes selection",
"invalid_uom": "The unit of measurement \"{unit_of_measurement}\" is not supported by the selected device class, please either remove the device class, select a device class which supports \"{unit_of_measurement}\", or pick a supported unit of measurement from the list",
"invalid_uom_for_state_class": "The unit of measurement \"{unit_of_measurement}\" is not supported by the selected state class, please either remove the state class, select a state class which supports \"{unit_of_measurement}\", or pick a supported unit of measurement from the list",
"invalid_url": "Invalid URL",
"last_reset_not_with_state_class_total": "The last reset value template option should be used with state class 'Total' only",
"max_below_min_kelvin": "Max Kelvin value should be greater than min Kelvin value",

View File

@ -3038,7 +3038,15 @@ async def test_migrate_of_incompatible_config_entry(
{
"state_class": "measurement",
},
(),
(
(
{
"state_class": "measurement_angle",
"unit_of_measurement": "deg",
},
{"unit_of_measurement": "invalid_uom_for_state_class"},
),
),
{
"state_topic": "test-topic",
},

View File

@ -995,6 +995,32 @@ async def test_invalid_state_class(
assert "expected SensorStateClass or one of" in caplog.text
@pytest.mark.parametrize(
"hass_config",
[
{
mqtt.DOMAIN: {
sensor.DOMAIN: {
"name": "test",
"state_topic": "test-topic",
"state_class": "measurement_angle",
"unit_of_measurement": "deg",
}
}
}
],
)
async def test_invalid_state_class_with_unit_of_measurement(
mqtt_mock_entry: MqttMockHAClientGenerator, caplog: pytest.LogCaptureFixture
) -> None:
"""Test state_class option with invalid unit of measurement."""
assert await mqtt_mock_entry()
assert (
"The unit of measurement 'deg' is not valid together with state class 'measurement_angle'"
in caplog.text
)
@pytest.mark.parametrize(
("hass_config", "error_logged"),
[