mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Deprecate use of invalid unit of measurement for mqtt sensor (#140164)
* Deprecate use of invalid unit of measurement for mqtt sensor * Update learn more URL to point to user docs instead * typo
This commit is contained in:
parent
e2d4e8b65d
commit
1a46edffaa
@ -11,6 +11,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.components import sensor
|
from homeassistant.components import sensor
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
CONF_STATE_CLASS,
|
CONF_STATE_CLASS,
|
||||||
|
DEVICE_CLASS_UNITS,
|
||||||
DEVICE_CLASSES_SCHEMA,
|
DEVICE_CLASSES_SCHEMA,
|
||||||
ENTITY_ID_FORMAT,
|
ENTITY_ID_FORMAT,
|
||||||
STATE_CLASSES_SCHEMA,
|
STATE_CLASSES_SCHEMA,
|
||||||
@ -33,13 +34,14 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant, State, callback
|
|||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
from homeassistant.helpers.service_info.mqtt import ReceivePayloadType
|
from homeassistant.helpers.service_info.mqtt import ReceivePayloadType
|
||||||
from homeassistant.helpers.typing import ConfigType, VolSchemaType
|
from homeassistant.helpers.typing import ConfigType, VolSchemaType
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import subscription
|
from . import subscription
|
||||||
from .config import MQTT_RO_SCHEMA
|
from .config import MQTT_RO_SCHEMA
|
||||||
from .const import CONF_OPTIONS, CONF_STATE_TOPIC, PAYLOAD_NONE
|
from .const import CONF_OPTIONS, CONF_STATE_TOPIC, DOMAIN, PAYLOAD_NONE
|
||||||
from .entity import MqttAvailabilityMixin, MqttEntity, async_setup_entity_entry_helper
|
from .entity import MqttAvailabilityMixin, MqttEntity, async_setup_entity_entry_helper
|
||||||
from .models import MqttValueTemplate, PayloadSentinel, ReceiveMessage
|
from .models import MqttValueTemplate, PayloadSentinel, ReceiveMessage
|
||||||
from .schemas import MQTT_ENTITY_COMMON_SCHEMA
|
from .schemas import MQTT_ENTITY_COMMON_SCHEMA
|
||||||
@ -63,6 +65,10 @@ MQTT_SENSOR_ATTRIBUTES_BLOCKED = frozenset(
|
|||||||
DEFAULT_NAME = "MQTT Sensor"
|
DEFAULT_NAME = "MQTT Sensor"
|
||||||
DEFAULT_FORCE_UPDATE = False
|
DEFAULT_FORCE_UPDATE = False
|
||||||
|
|
||||||
|
URL_DOCS_SUPPORTED_SENSOR_UOM = (
|
||||||
|
"https://www.home-assistant.io/integrations/sensor/#device-class"
|
||||||
|
)
|
||||||
|
|
||||||
_PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend(
|
_PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
|
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
|
||||||
@ -107,6 +113,23 @@ def validate_sensor_state_and_device_class_config(config: ConfigType) -> ConfigT
|
|||||||
f"got `{CONF_DEVICE_CLASS}` '{device_class}'"
|
f"got `{CONF_DEVICE_CLASS}` '{device_class}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (device_class := config.get(CONF_DEVICE_CLASS)) is None or (
|
||||||
|
unit_of_measurement := config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||||
|
) is None:
|
||||||
|
return config
|
||||||
|
|
||||||
|
if (
|
||||||
|
device_class in DEVICE_CLASS_UNITS
|
||||||
|
and unit_of_measurement not in DEVICE_CLASS_UNITS[device_class]
|
||||||
|
):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"The unit of measurement `%s` is not valid "
|
||||||
|
"together with device class `%s`. "
|
||||||
|
"this will stop working in HA Core 2025.7.0",
|
||||||
|
unit_of_measurement,
|
||||||
|
device_class,
|
||||||
|
)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
@ -155,8 +178,40 @@ class MqttSensor(MqttEntity, RestoreSensor):
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_check_uom(self) -> None:
|
||||||
|
"""Check if the unit of measurement is valid with the device class."""
|
||||||
|
if (
|
||||||
|
self._discovery_data is not None
|
||||||
|
or self.device_class is None
|
||||||
|
or self.native_unit_of_measurement is None
|
||||||
|
):
|
||||||
|
return
|
||||||
|
if (
|
||||||
|
self.device_class in DEVICE_CLASS_UNITS
|
||||||
|
and self.native_unit_of_measurement
|
||||||
|
not in DEVICE_CLASS_UNITS[self.device_class]
|
||||||
|
):
|
||||||
|
async_create_issue(
|
||||||
|
self.hass,
|
||||||
|
DOMAIN,
|
||||||
|
self.entity_id,
|
||||||
|
issue_domain=sensor.DOMAIN,
|
||||||
|
is_fixable=False,
|
||||||
|
severity=IssueSeverity.WARNING,
|
||||||
|
learn_more_url=URL_DOCS_SUPPORTED_SENSOR_UOM,
|
||||||
|
translation_placeholders={
|
||||||
|
"uom": self.native_unit_of_measurement,
|
||||||
|
"device_class": self.device_class.value,
|
||||||
|
"entity_id": self.entity_id,
|
||||||
|
},
|
||||||
|
translation_key="invalid_unit_of_measurement",
|
||||||
|
breaks_in_ha_version="2025.7.0",
|
||||||
|
)
|
||||||
|
|
||||||
async def mqtt_async_added_to_hass(self) -> None:
|
async def mqtt_async_added_to_hass(self) -> None:
|
||||||
"""Restore state for entities with expire_after set."""
|
"""Restore state for entities with expire_after set."""
|
||||||
|
self.async_check_uom()
|
||||||
last_state: State | None
|
last_state: State | None
|
||||||
last_sensor_data: SensorExtraStoredData | None
|
last_sensor_data: SensorExtraStoredData | None
|
||||||
if (
|
if (
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
"invalid_platform_config": {
|
"invalid_platform_config": {
|
||||||
"title": "Invalid config found for mqtt {domain} item",
|
"title": "Invalid config found for mqtt {domain} item",
|
||||||
"description": "Home Assistant detected an invalid config for a manually configured item.\n\nPlatform domain: **{domain}**\nConfiguration file: **{config_file}**\nNear line: **{line}**\nConfiguration found:\n```yaml\n{config}\n```\nError: **{error}**.\n\nMake sure the configuration is valid and [reload](/developer-tools/yaml) the manually configured MQTT items or restart Home Assistant to fix this issue."
|
"description": "Home Assistant detected an invalid config for a manually configured item.\n\nPlatform domain: **{domain}**\nConfiguration file: **{config_file}**\nNear line: **{line}**\nConfiguration found:\n```yaml\n{config}\n```\nError: **{error}**.\n\nMake sure the configuration is valid and [reload](/developer-tools/yaml) the manually configured MQTT items or restart Home Assistant to fix this issue."
|
||||||
|
},
|
||||||
|
"invalid_unit_of_measurement": {
|
||||||
|
"title": "Sensor with invalid unit of measurement",
|
||||||
|
"description": "Manual configured Sensor entity **{entity_id}** has a configured unit of measurement **{uom}** which is not valid with configured device class **{device_class}**. Make sure a valid unit of measurement is configured or remove the device class, and [reload](/developer-tools/yaml) the manually configured MQTT items or restart Home Assistant to fix this issue."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
|||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import Event, HomeAssistant, State, callback
|
from homeassistant.core import Event, HomeAssistant, State, callback
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
@ -71,6 +71,7 @@ from .test_common import (
|
|||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
|
async_capture_events,
|
||||||
async_fire_mqtt_message,
|
async_fire_mqtt_message,
|
||||||
async_fire_time_changed,
|
async_fire_time_changed,
|
||||||
mock_restore_cache_with_extra_data,
|
mock_restore_cache_with_extra_data,
|
||||||
@ -870,6 +871,71 @@ async def test_invalid_device_class(
|
|||||||
assert "expected SensorDeviceClass or one of" in caplog.text
|
assert "expected SensorDeviceClass or one of" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
sensor.DOMAIN: {
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"device_class": "energy",
|
||||||
|
"unit_of_measurement": "ppm",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_invalid_unit_of_measurement(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test device_class with invalid unit of measurement."""
|
||||||
|
events = async_capture_events(hass, ir.EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED)
|
||||||
|
assert await mqtt_mock_entry()
|
||||||
|
assert (
|
||||||
|
"The unit of measurement `ppm` is not valid together with device class `energy`"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
# A repair issue was logged
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].data["issue_id"] == "sensor.test"
|
||||||
|
# Assert the sensor works
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", "100")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("sensor.test")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "100"
|
||||||
|
|
||||||
|
caplog.clear()
|
||||||
|
|
||||||
|
discovery_payload = {
|
||||||
|
"name": "bla",
|
||||||
|
"state_topic": "test-topic2",
|
||||||
|
"device_class": "temperature",
|
||||||
|
"unit_of_measurement": "C",
|
||||||
|
}
|
||||||
|
# Now discover an other invalid sensor
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass, "homeassistant/sensor/bla/config", json.dumps(discovery_payload)
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert (
|
||||||
|
"The unit of measurement `C` is not valid together with device class `temperature`"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
# Assert the sensor works
|
||||||
|
async_fire_mqtt_message(hass, "test-topic2", "21")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("sensor.bla")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "21"
|
||||||
|
|
||||||
|
# No new issue was registered for the discovered entity
|
||||||
|
assert len(events) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"hass_config",
|
"hass_config",
|
||||||
[
|
[
|
||||||
|
Loading…
x
Reference in New Issue
Block a user