Warn if unit_of_measurement is set on instances of SensorEntityDescription (#54867)
* Add class BaseEntityDescription without unit_of_measurement * Refactor according to review comments * Tweak * Fix offending integrations * Fix offending integrations
This commit is contained in:
parent
e134246cbd
commit
2fa07777cd
6 changed files with 57 additions and 29 deletions
|
@ -49,7 +49,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="battery_level",
|
key="battery_level",
|
||||||
name="Battery Level",
|
name="Battery Level",
|
||||||
unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
device_class=DEVICE_CLASS_BATTERY,
|
device_class=DEVICE_CLASS_BATTERY,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
|
@ -60,19 +60,19 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="temperature",
|
key="temperature",
|
||||||
name="Temperature",
|
name="Temperature",
|
||||||
unit_of_measurement=TEMP_CELSIUS,
|
native_unit_of_measurement=TEMP_CELSIUS,
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="humidity",
|
key="humidity",
|
||||||
name="Humidity",
|
name="Humidity",
|
||||||
unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
device_class=DEVICE_CLASS_HUMIDITY,
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="air_quality",
|
key="air_quality",
|
||||||
name="Air Quality",
|
name="Air Quality",
|
||||||
unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
icon="mdi:biohazard",
|
icon="mdi:biohazard",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -39,14 +39,14 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||||
key="wattsIn",
|
key="wattsIn",
|
||||||
name="Watts In",
|
name="Watts In",
|
||||||
device_class=DEVICE_CLASS_POWER,
|
device_class=DEVICE_CLASS_POWER,
|
||||||
unit_of_measurement=POWER_WATT,
|
native_unit_of_measurement=POWER_WATT,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="ampsIn",
|
key="ampsIn",
|
||||||
name="Amps In",
|
name="Amps In",
|
||||||
device_class=DEVICE_CLASS_CURRENT,
|
device_class=DEVICE_CLASS_CURRENT,
|
||||||
unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
|
@ -54,14 +54,14 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||||
key="wattsOut",
|
key="wattsOut",
|
||||||
name="Watts Out",
|
name="Watts Out",
|
||||||
device_class=DEVICE_CLASS_POWER,
|
device_class=DEVICE_CLASS_POWER,
|
||||||
unit_of_measurement=POWER_WATT,
|
native_unit_of_measurement=POWER_WATT,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="ampsOut",
|
key="ampsOut",
|
||||||
name="Amps Out",
|
name="Amps Out",
|
||||||
device_class=DEVICE_CLASS_CURRENT,
|
device_class=DEVICE_CLASS_CURRENT,
|
||||||
unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
|
@ -69,7 +69,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||||
key="whOut",
|
key="whOut",
|
||||||
name="WH Out",
|
name="WH Out",
|
||||||
device_class=DEVICE_CLASS_ENERGY,
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
unit_of_measurement=ENERGY_WATT_HOUR,
|
native_unit_of_measurement=ENERGY_WATT_HOUR,
|
||||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
|
@ -77,44 +77,44 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||||
key="whStored",
|
key="whStored",
|
||||||
name="WH Stored",
|
name="WH Stored",
|
||||||
device_class=DEVICE_CLASS_ENERGY,
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
unit_of_measurement=ENERGY_WATT_HOUR,
|
native_unit_of_measurement=ENERGY_WATT_HOUR,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="volts",
|
key="volts",
|
||||||
name="Volts",
|
name="Volts",
|
||||||
device_class=DEVICE_CLASS_VOLTAGE,
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="socPercent",
|
key="socPercent",
|
||||||
name="State of Charge Percent",
|
name="State of Charge Percent",
|
||||||
device_class=DEVICE_CLASS_BATTERY,
|
device_class=DEVICE_CLASS_BATTERY,
|
||||||
unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="timeToEmptyFull",
|
key="timeToEmptyFull",
|
||||||
name="Time to Empty/Full",
|
name="Time to Empty/Full",
|
||||||
device_class=TIME_MINUTES,
|
device_class=TIME_MINUTES,
|
||||||
unit_of_measurement=TIME_MINUTES,
|
native_unit_of_measurement=TIME_MINUTES,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="temperature",
|
key="temperature",
|
||||||
name="Temperature",
|
name="Temperature",
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
unit_of_measurement=TEMP_CELSIUS,
|
native_unit_of_measurement=TEMP_CELSIUS,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="wifiStrength",
|
key="wifiStrength",
|
||||||
name="Wifi Strength",
|
name="Wifi Strength",
|
||||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="timestamp",
|
key="timestamp",
|
||||||
name="Total Run Time",
|
name="Total Run Time",
|
||||||
unit_of_measurement=TIME_SECONDS,
|
native_unit_of_measurement=TIME_SECONDS,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
|
|
|
@ -5,6 +5,7 @@ from collections.abc import Mapping
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Final, cast, final
|
from typing import Any, Final, cast, final
|
||||||
|
|
||||||
|
@ -128,9 +129,31 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
class SensorEntityDescription(EntityDescription):
|
class SensorEntityDescription(EntityDescription):
|
||||||
"""A class that describes sensor entities."""
|
"""A class that describes sensor entities."""
|
||||||
|
|
||||||
state_class: str | None = None
|
|
||||||
last_reset: datetime | None = None # Deprecated, to be removed in 2021.11
|
last_reset: datetime | None = None # Deprecated, to be removed in 2021.11
|
||||||
native_unit_of_measurement: str | None = None
|
native_unit_of_measurement: str | None = None
|
||||||
|
state_class: str | None = None
|
||||||
|
unit_of_measurement: None = None # Type override, use native_unit_of_measurement
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
"""Post initialisation processing."""
|
||||||
|
if self.unit_of_measurement:
|
||||||
|
caller = inspect.stack()[2] # type: ignore[unreachable]
|
||||||
|
module = inspect.getmodule(caller[0])
|
||||||
|
if "custom_components" in module.__file__:
|
||||||
|
report_issue = "report it to the custom component author."
|
||||||
|
else:
|
||||||
|
report_issue = (
|
||||||
|
"create a bug report at "
|
||||||
|
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
|
||||||
|
)
|
||||||
|
_LOGGER.warning(
|
||||||
|
"%s is setting 'unit_of_measurement' on an instance of "
|
||||||
|
"SensorEntityDescription, this is not valid and will be unsupported "
|
||||||
|
"from Home Assistant 2021.11. Please %s",
|
||||||
|
module.__name__,
|
||||||
|
report_issue,
|
||||||
|
)
|
||||||
|
self.native_unit_of_measurement = self.unit_of_measurement
|
||||||
|
|
||||||
|
|
||||||
class SensorEntity(Entity):
|
class SensorEntity(Entity):
|
||||||
|
@ -220,11 +243,6 @@ class SensorEntity(Entity):
|
||||||
and self._attr_unit_of_measurement is not None
|
and self._attr_unit_of_measurement is not None
|
||||||
):
|
):
|
||||||
return self._attr_unit_of_measurement
|
return self._attr_unit_of_measurement
|
||||||
if (
|
|
||||||
hasattr(self, "entity_description")
|
|
||||||
and self.entity_description.unit_of_measurement is not None
|
|
||||||
):
|
|
||||||
return self.entity_description.unit_of_measurement
|
|
||||||
|
|
||||||
native_unit_of_measurement = self.native_unit_of_measurement
|
native_unit_of_measurement = self.native_unit_of_measurement
|
||||||
|
|
||||||
|
|
|
@ -53,35 +53,35 @@ ATTR_TOTAL_ENERGY_KWH = "total_energy_kwh"
|
||||||
ENERGY_SENSORS: Final[list[SensorEntityDescription]] = [
|
ENERGY_SENSORS: Final[list[SensorEntityDescription]] = [
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=ATTR_CURRENT_POWER_W,
|
key=ATTR_CURRENT_POWER_W,
|
||||||
unit_of_measurement=POWER_WATT,
|
native_unit_of_measurement=POWER_WATT,
|
||||||
device_class=DEVICE_CLASS_POWER,
|
device_class=DEVICE_CLASS_POWER,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
name="Current Consumption",
|
name="Current Consumption",
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=ATTR_TOTAL_ENERGY_KWH,
|
key=ATTR_TOTAL_ENERGY_KWH,
|
||||||
unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||||
device_class=DEVICE_CLASS_ENERGY,
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
name="Total Consumption",
|
name="Total Consumption",
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=ATTR_TODAY_ENERGY_KWH,
|
key=ATTR_TODAY_ENERGY_KWH,
|
||||||
unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||||
device_class=DEVICE_CLASS_ENERGY,
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
name="Today's Consumption",
|
name="Today's Consumption",
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=ATTR_VOLTAGE,
|
key=ATTR_VOLTAGE,
|
||||||
unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
||||||
device_class=DEVICE_CLASS_VOLTAGE,
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
name="Voltage",
|
name="Voltage",
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=ATTR_CURRENT_A,
|
key=ATTR_CURRENT_A,
|
||||||
unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
||||||
device_class=DEVICE_CLASS_CURRENT,
|
device_class=DEVICE_CLASS_CURRENT,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
name="Current",
|
name="Current",
|
||||||
|
|
|
@ -95,7 +95,7 @@ class InsightCurrentPower(InsightSensor):
|
||||||
name="Current Power",
|
name="Current Power",
|
||||||
device_class=DEVICE_CLASS_POWER,
|
device_class=DEVICE_CLASS_POWER,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
unit_of_measurement=POWER_WATT,
|
native_unit_of_measurement=POWER_WATT,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -115,7 +115,7 @@ class InsightTodayEnergy(InsightSensor):
|
||||||
name="Today Energy",
|
name="Today Energy",
|
||||||
device_class=DEVICE_CLASS_ENERGY,
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""The test for sensor device automation."""
|
"""The test for sensor device automation."""
|
||||||
|
from homeassistant.components.sensor import SensorEntityDescription
|
||||||
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
@ -49,3 +50,12 @@ async def test_deprecated_last_reset(hass, caplog, enable_custom_integrations):
|
||||||
"update your configuration if state_class is manually configured, otherwise "
|
"update your configuration if state_class is manually configured, otherwise "
|
||||||
"report it to the custom component author."
|
"report it to the custom component author."
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_deprecated_unit_of_measurement(hass, caplog, enable_custom_integrations):
|
||||||
|
"""Test warning on deprecated unit_of_measurement."""
|
||||||
|
SensorEntityDescription("catsensor", unit_of_measurement="cats")
|
||||||
|
assert (
|
||||||
|
"tests.components.sensor.test_init is setting 'unit_of_measurement' on an "
|
||||||
|
"instance of SensorEntityDescription"
|
||||||
|
) in caplog.text
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue