mirror of
https://github.com/home-assistant/core.git
synced 2025-08-03 18:48:22 +00:00
Allow sensor ambiguous native_unit_of_measurement for compatibility
This commit is contained in:
parent
2d4265fbe1
commit
6e52d0ddb1
@ -34,6 +34,7 @@ from homeassistant.util.enum import try_parse_enum
|
|||||||
from homeassistant.util.hass_dict import HassKey
|
from homeassistant.util.hass_dict import HassKey
|
||||||
|
|
||||||
from .const import ( # noqa: F401
|
from .const import ( # noqa: F401
|
||||||
|
AMBIGUOUS_UNITS,
|
||||||
ATTR_LAST_RESET,
|
ATTR_LAST_RESET,
|
||||||
ATTR_OPTIONS,
|
ATTR_OPTIONS,
|
||||||
ATTR_STATE_CLASS,
|
ATTR_STATE_CLASS,
|
||||||
@ -314,7 +315,7 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
return _numeric_state_expected(
|
return _numeric_state_expected(
|
||||||
try_parse_enum(SensorDeviceClass, self.device_class),
|
try_parse_enum(SensorDeviceClass, self.device_class),
|
||||||
self.state_class,
|
self.state_class,
|
||||||
self.native_unit_of_measurement,
|
self.__native_unit_of_measurement_compat,
|
||||||
self.suggested_display_precision,
|
self.suggested_display_precision,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -366,7 +367,8 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
# Make sure we can convert the units
|
# Make sure we can convert the units
|
||||||
if (
|
if (
|
||||||
(unit_converter := UNIT_CONVERTERS.get(self.device_class)) is None
|
(unit_converter := UNIT_CONVERTERS.get(self.device_class)) is None
|
||||||
or self.native_unit_of_measurement not in unit_converter.VALID_UNITS
|
or self.__native_unit_of_measurement_compat
|
||||||
|
not in unit_converter.VALID_UNITS
|
||||||
or suggested_unit_of_measurement not in unit_converter.VALID_UNITS
|
or suggested_unit_of_measurement not in unit_converter.VALID_UNITS
|
||||||
):
|
):
|
||||||
if not self._invalid_suggested_unit_of_measurement_reported:
|
if not self._invalid_suggested_unit_of_measurement_reported:
|
||||||
@ -387,7 +389,7 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
if suggested_unit_of_measurement is None:
|
if suggested_unit_of_measurement is None:
|
||||||
# Fallback to unit suggested by the unit conversion rules from device class
|
# Fallback to unit suggested by the unit conversion rules from device class
|
||||||
suggested_unit_of_measurement = self.hass.config.units.get_converted_unit(
|
suggested_unit_of_measurement = self.hass.config.units.get_converted_unit(
|
||||||
self.device_class, self.native_unit_of_measurement
|
self.device_class, self.__native_unit_of_measurement_compat
|
||||||
)
|
)
|
||||||
|
|
||||||
if suggested_unit_of_measurement is None and (
|
if suggested_unit_of_measurement is None and (
|
||||||
@ -396,7 +398,7 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
# If the device class is not known by the unit system but has a unit converter,
|
# If the device class is not known by the unit system but has a unit converter,
|
||||||
# fall back to the unit suggested by the unit converter's unit class.
|
# fall back to the unit suggested by the unit converter's unit class.
|
||||||
suggested_unit_of_measurement = self.hass.config.units.get_converted_unit(
|
suggested_unit_of_measurement = self.hass.config.units.get_converted_unit(
|
||||||
unit_converter.UNIT_CLASS, self.native_unit_of_measurement
|
unit_converter.UNIT_CLASS, self.__native_unit_of_measurement_compat
|
||||||
)
|
)
|
||||||
|
|
||||||
if suggested_unit_of_measurement is None:
|
if suggested_unit_of_measurement is None:
|
||||||
@ -468,6 +470,14 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
return self.entity_description.native_unit_of_measurement
|
return self.entity_description.native_unit_of_measurement
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def __native_unit_of_measurement_compat(self) -> str | None:
|
||||||
|
"""Process ambiguous units."""
|
||||||
|
native_unit_of_measurement = self.native_unit_of_measurement
|
||||||
|
return AMBIGUOUS_UNITS.get(
|
||||||
|
native_unit_of_measurement, native_unit_of_measurement
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def suggested_unit_of_measurement(self) -> str | None:
|
def suggested_unit_of_measurement(self) -> str | None:
|
||||||
"""Return the unit which should be used for the sensor's state.
|
"""Return the unit which should be used for the sensor's state.
|
||||||
@ -503,7 +513,7 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
if self._sensor_option_unit_of_measurement is not UNDEFINED:
|
if self._sensor_option_unit_of_measurement is not UNDEFINED:
|
||||||
return self._sensor_option_unit_of_measurement
|
return self._sensor_option_unit_of_measurement
|
||||||
|
|
||||||
native_unit_of_measurement = self.native_unit_of_measurement
|
native_unit_of_measurement = self.__native_unit_of_measurement_compat
|
||||||
|
|
||||||
# Second priority, for non registered entities: unit suggested by integration
|
# Second priority, for non registered entities: unit suggested by integration
|
||||||
if not self.registry_entry and (
|
if not self.registry_entry and (
|
||||||
@ -541,7 +551,7 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
@override
|
@override
|
||||||
def state(self) -> Any:
|
def state(self) -> Any:
|
||||||
"""Return the state of the sensor and perform unit conversions, if needed."""
|
"""Return the state of the sensor and perform unit conversions, if needed."""
|
||||||
native_unit_of_measurement = self.native_unit_of_measurement
|
native_unit_of_measurement = self.__native_unit_of_measurement_compat
|
||||||
unit_of_measurement = self.unit_of_measurement
|
unit_of_measurement = self.unit_of_measurement
|
||||||
value = self.native_value
|
value = self.native_value
|
||||||
# For the sake of validation, we can ignore custom device classes
|
# For the sake of validation, we can ignore custom device classes
|
||||||
@ -763,7 +773,8 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
return display_precision
|
return display_precision
|
||||||
|
|
||||||
default_unit_of_measurement = (
|
default_unit_of_measurement = (
|
||||||
self.suggested_unit_of_measurement or self.native_unit_of_measurement
|
self.suggested_unit_of_measurement
|
||||||
|
or self.__native_unit_of_measurement_compat
|
||||||
)
|
)
|
||||||
if default_unit_of_measurement is None:
|
if default_unit_of_measurement is None:
|
||||||
return display_precision
|
return display_precision
|
||||||
@ -841,7 +852,7 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
(sensor_options := self.registry_entry.options.get(primary_key))
|
(sensor_options := self.registry_entry.options.get(primary_key))
|
||||||
and secondary_key in sensor_options
|
and secondary_key in sensor_options
|
||||||
and (device_class := self.device_class) in UNIT_CONVERTERS
|
and (device_class := self.device_class) in UNIT_CONVERTERS
|
||||||
and self.native_unit_of_measurement
|
and self.__native_unit_of_measurement_compat
|
||||||
in UNIT_CONVERTERS[device_class].VALID_UNITS
|
in UNIT_CONVERTERS[device_class].VALID_UNITS
|
||||||
and (custom_unit := sensor_options[secondary_key])
|
and (custom_unit := sensor_options[secondary_key])
|
||||||
in UNIT_CONVERTERS[device_class].VALID_UNITS
|
in UNIT_CONVERTERS[device_class].VALID_UNITS
|
||||||
|
@ -8,6 +8,7 @@ from typing import Final
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT,
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||||
CONCENTRATION_PARTS_PER_BILLION,
|
CONCENTRATION_PARTS_PER_BILLION,
|
||||||
@ -770,3 +771,14 @@ DEVICE_CLASS_STATE_CLASSES: dict[SensorDeviceClass, set[SensorStateClass]] = {
|
|||||||
STATE_CLASS_UNITS: dict[SensorStateClass | str, set[type[StrEnum] | str | None]] = {
|
STATE_CLASS_UNITS: dict[SensorStateClass | str, set[type[StrEnum] | str | None]] = {
|
||||||
SensorStateClass.MEASUREMENT_ANGLE: {DEGREE},
|
SensorStateClass.MEASUREMENT_ANGLE: {DEGREE},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AMBIGUOUS_UNITS: dict[str | None, str | None] = {
|
||||||
|
"\u00b5Sv/h": "μSv/h", # aranet: radiation rate
|
||||||
|
"\u00b5S/cm": UnitOfConductivity.MICROSIEMENS_PER_CM,
|
||||||
|
"\u00b5V": UnitOfElectricPotential.MICROVOLT,
|
||||||
|
"\u00b5g/ft³": CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT,
|
||||||
|
"\u00b5g/m³": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
"\u00b5mol/s⋅m²": "μmol/s⋅m²", # fyta: light
|
||||||
|
"\u00b5g": UnitOfMass.MICROGRAMS,
|
||||||
|
"\u00b5s": UnitOfTime.MICROSECONDS,
|
||||||
|
}
|
||||||
|
@ -28,13 +28,9 @@ from homeassistant.components.recorder.models import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT,
|
|
||||||
REVOLUTIONS_PER_MINUTE,
|
REVOLUTIONS_PER_MINUTE,
|
||||||
UnitOfElectricPotential,
|
|
||||||
UnitOfIrradiance,
|
UnitOfIrradiance,
|
||||||
UnitOfMass,
|
|
||||||
UnitOfSoundPressure,
|
UnitOfSoundPressure,
|
||||||
UnitOfTime,
|
|
||||||
UnitOfVolume,
|
UnitOfVolume,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, State, callback, split_entity_id
|
from homeassistant.core import HomeAssistant, State, callback, split_entity_id
|
||||||
@ -49,12 +45,11 @@ from homeassistant.util.enum import try_parse_enum
|
|||||||
from homeassistant.util.hass_dict import HassKey
|
from homeassistant.util.hass_dict import HassKey
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
AMBIGUOUS_UNITS,
|
||||||
ATTR_LAST_RESET,
|
ATTR_LAST_RESET,
|
||||||
ATTR_STATE_CLASS,
|
ATTR_STATE_CLASS,
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
UnitOfConductivity,
|
|
||||||
UnitOfVolumeFlowRate,
|
UnitOfVolumeFlowRate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -85,15 +80,7 @@ EQUIVALENT_UNITS = {
|
|||||||
"ft3": UnitOfVolume.CUBIC_FEET,
|
"ft3": UnitOfVolume.CUBIC_FEET,
|
||||||
"m3": UnitOfVolume.CUBIC_METERS,
|
"m3": UnitOfVolume.CUBIC_METERS,
|
||||||
"ft³/m": UnitOfVolumeFlowRate.CUBIC_FEET_PER_MINUTE,
|
"ft³/m": UnitOfVolumeFlowRate.CUBIC_FEET_PER_MINUTE,
|
||||||
"\u00b5Sv/h": "μSv/h", # aranet: radiation rate
|
} | AMBIGUOUS_UNITS
|
||||||
"\u00b5S/cm": UnitOfConductivity.MICROSIEMENS_PER_CM,
|
|
||||||
"\u00b5V": UnitOfElectricPotential.MICROVOLT,
|
|
||||||
"\u00b5g/ft³": CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT,
|
|
||||||
"\u00b5g/m³": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
|
||||||
"\u00b5mol/s⋅m²": "μmol/s⋅m²", # fyta: light
|
|
||||||
"\u00b5g": UnitOfMass.MICROGRAMS,
|
|
||||||
"\u00b5s": UnitOfTime.MICROSECONDS,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Keep track of entities for which a warning about decreasing value has been logged
|
# Keep track of entities for which a warning about decreasing value has been logged
|
||||||
|
@ -165,6 +165,33 @@ async def test_temperature_conversion_wrong_device_class(
|
|||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfTemperature.FAHRENHEIT
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfTemperature.FAHRENHEIT
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("ambiguous_unit", "normalized_unit"),
|
||||||
|
[
|
||||||
|
(ambiguous_unit, normalized_unit)
|
||||||
|
for ambiguous_unit, normalized_unit in sensor.AMBIGUOUS_UNITS.items()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_ambiguous_unit_of_measurement_compat(
|
||||||
|
hass: HomeAssistant, ambiguous_unit: str, normalized_unit: str
|
||||||
|
) -> None:
|
||||||
|
"""Test ambiguous native_unit_of_measurement values are corrected."""
|
||||||
|
entity0 = MockSensor(
|
||||||
|
name="Test",
|
||||||
|
native_value="0.0",
|
||||||
|
native_unit_of_measurement=ambiguous_unit,
|
||||||
|
)
|
||||||
|
setup_test_component_platform(hass, sensor.DOMAIN, [entity0])
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check temperature is not converted
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert state.state == "0.0"
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == normalized_unit
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("state_class", ["measurement", "total_increasing"])
|
@pytest.mark.parametrize("state_class", ["measurement", "total_increasing"])
|
||||||
async def test_deprecated_last_reset(
|
async def test_deprecated_last_reset(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user