Add distance to SensorDeviceClass (#77951)
* Add distance to SensorDeviceClass * Adjust recorder * Adjust tests * Adjust recorder * Update __init__.py * Update test_websocket_api.py * Update test_websocket_api.py * Update test_websocket_api.py * Update strings.json * Fix tests * Adjust docstring
This commit is contained in:
parent
a58f919972
commit
bfcc18e5b8
11 changed files with 126 additions and 8 deletions
|
@ -33,6 +33,7 @@ from homeassistant.helpers.typing import UNDEFINED, UndefinedType
|
|||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.unit_conversion import (
|
||||
BaseUnitConverter,
|
||||
DistanceConverter,
|
||||
EnergyConverter,
|
||||
PowerConverter,
|
||||
PressureConverter,
|
||||
|
@ -122,6 +123,7 @@ QUERY_STATISTIC_META = [
|
|||
|
||||
|
||||
STATISTIC_UNIT_TO_UNIT_CLASS: dict[str | None, str] = {
|
||||
DistanceConverter.NORMALIZED_UNIT: DistanceConverter.UNIT_CLASS,
|
||||
EnergyConverter.NORMALIZED_UNIT: EnergyConverter.UNIT_CLASS,
|
||||
PowerConverter.NORMALIZED_UNIT: PowerConverter.UNIT_CLASS,
|
||||
PressureConverter.NORMALIZED_UNIT: PressureConverter.UNIT_CLASS,
|
||||
|
@ -130,6 +132,7 @@ STATISTIC_UNIT_TO_UNIT_CLASS: dict[str | None, str] = {
|
|||
}
|
||||
|
||||
STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = {
|
||||
DistanceConverter.NORMALIZED_UNIT: DistanceConverter,
|
||||
EnergyConverter.NORMALIZED_UNIT: EnergyConverter,
|
||||
PowerConverter.NORMALIZED_UNIT: PowerConverter,
|
||||
PressureConverter.NORMALIZED_UNIT: PressureConverter,
|
||||
|
|
|
@ -21,6 +21,7 @@ from homeassistant.helpers import config_validation as cv
|
|||
from homeassistant.helpers.json import JSON_DUMP
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.unit_conversion import (
|
||||
DistanceConverter,
|
||||
EnergyConverter,
|
||||
PowerConverter,
|
||||
PressureConverter,
|
||||
|
@ -123,6 +124,7 @@ async def ws_handle_get_statistics_during_period(
|
|||
vol.Required("period"): vol.Any("5minute", "hour", "day", "month"),
|
||||
vol.Optional("units"): vol.Schema(
|
||||
{
|
||||
vol.Optional("distance"): vol.In(DistanceConverter.VALID_UNITS),
|
||||
vol.Optional("energy"): vol.In(EnergyConverter.VALID_UNITS),
|
||||
vol.Optional("power"): vol.In(PowerConverter.VALID_UNITS),
|
||||
vol.Optional("pressure"): vol.In(PressureConverter.VALID_UNITS),
|
||||
|
@ -299,8 +301,8 @@ async def ws_adjust_sum_statistics(
|
|||
) -> None:
|
||||
"""Adjust sum statistics.
|
||||
|
||||
If the statistics is stored as kWh, it's allowed to make an adjustment in Wh or MWh
|
||||
If the statistics is stored as m³, it's allowed to make an adjustment in ft³
|
||||
If the statistics is stored as NORMALIZED_UNIT,
|
||||
it's allowed to make an adjustment in VALID_UNIT
|
||||
"""
|
||||
start_time_str = msg["start_time"]
|
||||
|
||||
|
@ -322,6 +324,11 @@ async def ws_adjust_sum_statistics(
|
|||
def valid_units(statistics_unit: str | None, display_unit: str | None) -> bool:
|
||||
if statistics_unit == display_unit:
|
||||
return True
|
||||
if (
|
||||
statistics_unit == DistanceConverter.NORMALIZED_UNIT
|
||||
and display_unit in DistanceConverter.VALID_UNITS
|
||||
):
|
||||
return True
|
||||
if statistics_unit == ENERGY_KILO_WATT_HOUR and display_unit in (
|
||||
ENERGY_MEGA_WATT_HOUR,
|
||||
ENERGY_WATT_HOUR,
|
||||
|
|
|
@ -60,6 +60,7 @@ from homeassistant.helpers.typing import ConfigType, StateType
|
|||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.unit_conversion import (
|
||||
BaseUnitConverter,
|
||||
DistanceConverter,
|
||||
PressureConverter,
|
||||
TemperatureConverter,
|
||||
)
|
||||
|
@ -102,6 +103,9 @@ class SensorDeviceClass(StrEnum):
|
|||
# date (ISO8601)
|
||||
DATE = "date"
|
||||
|
||||
# distance (LENGTH_*)
|
||||
DISTANCE = "distance"
|
||||
|
||||
# fixed duration (TIME_DAYS, TIME_HOURS, TIME_MINUTES, TIME_SECONDS)
|
||||
DURATION = "duration"
|
||||
|
||||
|
@ -209,11 +213,13 @@ STATE_CLASS_TOTAL_INCREASING: Final = "total_increasing"
|
|||
STATE_CLASSES: Final[list[str]] = [cls.value for cls in SensorStateClass]
|
||||
|
||||
UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = {
|
||||
SensorDeviceClass.DISTANCE: DistanceConverter,
|
||||
SensorDeviceClass.PRESSURE: PressureConverter,
|
||||
SensorDeviceClass.TEMPERATURE: TemperatureConverter,
|
||||
}
|
||||
|
||||
UNIT_RATIOS: dict[str, dict[str, float]] = {
|
||||
SensorDeviceClass.DISTANCE: DistanceConverter.UNIT_CONVERSION,
|
||||
SensorDeviceClass.PRESSURE: PressureConverter.UNIT_CONVERSION,
|
||||
SensorDeviceClass.TEMPERATURE: {
|
||||
TEMP_CELSIUS: 1.0,
|
||||
|
|
|
@ -36,6 +36,7 @@ CONF_IS_BATTERY_LEVEL = "is_battery_level"
|
|||
CONF_IS_CO = "is_carbon_monoxide"
|
||||
CONF_IS_CO2 = "is_carbon_dioxide"
|
||||
CONF_IS_CURRENT = "is_current"
|
||||
CONF_IS_DISTANCE = "is_distance"
|
||||
CONF_IS_ENERGY = "is_energy"
|
||||
CONF_IS_FREQUENCY = "is_frequency"
|
||||
CONF_IS_HUMIDITY = "is_humidity"
|
||||
|
@ -66,6 +67,7 @@ ENTITY_CONDITIONS = {
|
|||
SensorDeviceClass.CO: [{CONF_TYPE: CONF_IS_CO}],
|
||||
SensorDeviceClass.CO2: [{CONF_TYPE: CONF_IS_CO2}],
|
||||
SensorDeviceClass.CURRENT: [{CONF_TYPE: CONF_IS_CURRENT}],
|
||||
SensorDeviceClass.DISTANCE: [{CONF_TYPE: CONF_IS_DISTANCE}],
|
||||
SensorDeviceClass.ENERGY: [{CONF_TYPE: CONF_IS_ENERGY}],
|
||||
SensorDeviceClass.FREQUENCY: [{CONF_TYPE: CONF_IS_FREQUENCY}],
|
||||
SensorDeviceClass.GAS: [{CONF_TYPE: CONF_IS_GAS}],
|
||||
|
@ -104,6 +106,7 @@ CONDITION_SCHEMA = vol.All(
|
|||
CONF_IS_CO,
|
||||
CONF_IS_CO2,
|
||||
CONF_IS_CURRENT,
|
||||
CONF_IS_DISTANCE,
|
||||
CONF_IS_ENERGY,
|
||||
CONF_IS_FREQUENCY,
|
||||
CONF_IS_GAS,
|
||||
|
|
|
@ -35,6 +35,7 @@ CONF_BATTERY_LEVEL = "battery_level"
|
|||
CONF_CO = "carbon_monoxide"
|
||||
CONF_CO2 = "carbon_dioxide"
|
||||
CONF_CURRENT = "current"
|
||||
CONF_DISTANCE = "distance"
|
||||
CONF_ENERGY = "energy"
|
||||
CONF_FREQUENCY = "frequency"
|
||||
CONF_GAS = "gas"
|
||||
|
@ -65,6 +66,7 @@ ENTITY_TRIGGERS = {
|
|||
SensorDeviceClass.CO: [{CONF_TYPE: CONF_CO}],
|
||||
SensorDeviceClass.CO2: [{CONF_TYPE: CONF_CO2}],
|
||||
SensorDeviceClass.CURRENT: [{CONF_TYPE: CONF_CURRENT}],
|
||||
SensorDeviceClass.DISTANCE: [{CONF_TYPE: CONF_DISTANCE}],
|
||||
SensorDeviceClass.ENERGY: [{CONF_TYPE: CONF_ENERGY}],
|
||||
SensorDeviceClass.FREQUENCY: [{CONF_TYPE: CONF_FREQUENCY}],
|
||||
SensorDeviceClass.GAS: [{CONF_TYPE: CONF_GAS}],
|
||||
|
@ -104,6 +106,7 @@ TRIGGER_SCHEMA = vol.All(
|
|||
CONF_CO,
|
||||
CONF_CO2,
|
||||
CONF_CURRENT,
|
||||
CONF_DISTANCE,
|
||||
CONF_ENERGY,
|
||||
CONF_FREQUENCY,
|
||||
CONF_GAS,
|
||||
|
|
|
@ -30,6 +30,7 @@ from homeassistant.helpers.entity import entity_sources
|
|||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.unit_conversion import (
|
||||
BaseUnitConverter,
|
||||
DistanceConverter,
|
||||
EnergyConverter,
|
||||
PowerConverter,
|
||||
PressureConverter,
|
||||
|
@ -57,6 +58,7 @@ DEFAULT_STATISTICS = {
|
|||
}
|
||||
|
||||
UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = {
|
||||
SensorDeviceClass.DISTANCE: DistanceConverter,
|
||||
SensorDeviceClass.ENERGY: EnergyConverter,
|
||||
SensorDeviceClass.POWER: PowerConverter,
|
||||
SensorDeviceClass.PRESSURE: PressureConverter,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"is_battery_level": "Current {entity_name} battery level",
|
||||
"is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level",
|
||||
"is_carbon_dioxide": "Current {entity_name} carbon dioxide concentration level",
|
||||
"is_distance": "Current {entity_name} distance",
|
||||
"is_gas": "Current {entity_name} gas",
|
||||
"is_humidity": "Current {entity_name} humidity",
|
||||
"is_illuminance": "Current {entity_name} illuminance",
|
||||
|
@ -36,6 +37,7 @@
|
|||
"battery_level": "{entity_name} battery level changes",
|
||||
"carbon_monoxide": "{entity_name} carbon monoxide concentration changes",
|
||||
"carbon_dioxide": "{entity_name} carbon dioxide concentration changes",
|
||||
"distance": "{entity_name} distance changes",
|
||||
"gas": "{entity_name} gas changes",
|
||||
"humidity": "{entity_name} humidity changes",
|
||||
"illuminance": "{entity_name} illuminance changes",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"is_carbon_dioxide": "Current {entity_name} carbon dioxide concentration level",
|
||||
"is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level",
|
||||
"is_current": "Current {entity_name} current",
|
||||
"is_distance": "Current {entity_name} distance",
|
||||
"is_energy": "Current {entity_name} energy",
|
||||
"is_frequency": "Current {entity_name} frequency",
|
||||
"is_gas": "Current {entity_name} gas",
|
||||
|
@ -36,6 +37,7 @@
|
|||
"carbon_dioxide": "{entity_name} carbon dioxide concentration changes",
|
||||
"carbon_monoxide": "{entity_name} carbon monoxide concentration changes",
|
||||
"current": "{entity_name} current changes",
|
||||
"distance": "{entity_name} distance changes",
|
||||
"energy": "{entity_name} energy changes",
|
||||
"frequency": "{entity_name} frequency changes",
|
||||
"gas": "{entity_name} gas changes",
|
||||
|
|
|
@ -30,6 +30,16 @@ from .common import (
|
|||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
DISTANCE_SENSOR_FT_ATTRIBUTES = {
|
||||
"device_class": "distance",
|
||||
"state_class": "measurement",
|
||||
"unit_of_measurement": "ft",
|
||||
}
|
||||
DISTANCE_SENSOR_M_ATTRIBUTES = {
|
||||
"device_class": "distance",
|
||||
"state_class": "measurement",
|
||||
"unit_of_measurement": "m",
|
||||
}
|
||||
ENERGY_SENSOR_KWH_ATTRIBUTES = {
|
||||
"device_class": "energy",
|
||||
"state_class": "total",
|
||||
|
@ -141,6 +151,9 @@ async def test_statistics_during_period(hass, hass_ws_client, recorder_mock):
|
|||
@pytest.mark.parametrize(
|
||||
"attributes, state, value, custom_units, converted_value",
|
||||
[
|
||||
(DISTANCE_SENSOR_M_ATTRIBUTES, 10, 10, {"distance": "cm"}, 1000),
|
||||
(DISTANCE_SENSOR_M_ATTRIBUTES, 10, 10, {"distance": "m"}, 10),
|
||||
(DISTANCE_SENSOR_M_ATTRIBUTES, 10, 10, {"distance": "in"}, 10 / 0.0254),
|
||||
(POWER_SENSOR_KW_ATTRIBUTES, 10, 10, {"power": "W"}, 10000),
|
||||
(POWER_SENSOR_KW_ATTRIBUTES, 10, 10, {"power": "kW"}, 10),
|
||||
(PRESSURE_SENSOR_HPA_ATTRIBUTES, 10, 10, {"pressure": "Pa"}, 1000),
|
||||
|
@ -327,6 +340,7 @@ async def test_sum_statistics_during_period_unit_conversion(
|
|||
@pytest.mark.parametrize(
|
||||
"custom_units",
|
||||
[
|
||||
{"distance": "L"},
|
||||
{"energy": "W"},
|
||||
{"power": "Pa"},
|
||||
{"pressure": "K"},
|
||||
|
@ -538,6 +552,10 @@ async def test_statistics_during_period_bad_end_time(
|
|||
@pytest.mark.parametrize(
|
||||
"units, attributes, display_unit, statistics_unit, unit_class",
|
||||
[
|
||||
(IMPERIAL_SYSTEM, DISTANCE_SENSOR_M_ATTRIBUTES, "m", "m", "distance"),
|
||||
(METRIC_SYSTEM, DISTANCE_SENSOR_M_ATTRIBUTES, "m", "m", "distance"),
|
||||
(IMPERIAL_SYSTEM, DISTANCE_SENSOR_FT_ATTRIBUTES, "ft", "m", "distance"),
|
||||
(METRIC_SYSTEM, DISTANCE_SENSOR_FT_ATTRIBUTES, "ft", "m", "distance"),
|
||||
(IMPERIAL_SYSTEM, ENERGY_SENSOR_WH_ATTRIBUTES, "Wh", "kWh", "energy"),
|
||||
(METRIC_SYSTEM, ENERGY_SENSOR_WH_ATTRIBUTES, "Wh", "kWh", "energy"),
|
||||
(IMPERIAL_SYSTEM, GAS_SENSOR_FT3_ATTRIBUTES, "ft³", "m³", "volume"),
|
||||
|
|
|
@ -8,6 +8,10 @@ from pytest import approx
|
|||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
LENGTH_CENTIMETERS,
|
||||
LENGTH_INCHES,
|
||||
LENGTH_KILOMETERS,
|
||||
LENGTH_MILES,
|
||||
PRESSURE_HPA,
|
||||
PRESSURE_INHG,
|
||||
PRESSURE_KPA,
|
||||
|
@ -455,14 +459,67 @@ async def test_custom_unit(
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"native_unit,custom_unit,state_unit,native_value,custom_value",
|
||||
"native_unit,custom_unit,state_unit,native_value,custom_value,device_class",
|
||||
[
|
||||
# Distance
|
||||
(
|
||||
LENGTH_KILOMETERS,
|
||||
LENGTH_MILES,
|
||||
LENGTH_MILES,
|
||||
1000,
|
||||
621,
|
||||
SensorDeviceClass.DISTANCE,
|
||||
),
|
||||
(
|
||||
LENGTH_CENTIMETERS,
|
||||
LENGTH_INCHES,
|
||||
LENGTH_INCHES,
|
||||
7.24,
|
||||
2.85,
|
||||
SensorDeviceClass.DISTANCE,
|
||||
),
|
||||
(
|
||||
LENGTH_KILOMETERS,
|
||||
"peer_distance",
|
||||
LENGTH_KILOMETERS,
|
||||
1000,
|
||||
1000,
|
||||
SensorDeviceClass.DISTANCE,
|
||||
),
|
||||
# Smaller to larger unit, InHg is ~33x larger than hPa -> 1 more decimal
|
||||
(PRESSURE_HPA, PRESSURE_INHG, PRESSURE_INHG, 1000.0, 29.53),
|
||||
(PRESSURE_KPA, PRESSURE_HPA, PRESSURE_HPA, 1.234, 12.34),
|
||||
(PRESSURE_HPA, PRESSURE_MMHG, PRESSURE_MMHG, 1000, 750),
|
||||
(
|
||||
PRESSURE_HPA,
|
||||
PRESSURE_INHG,
|
||||
PRESSURE_INHG,
|
||||
1000.0,
|
||||
29.53,
|
||||
SensorDeviceClass.PRESSURE,
|
||||
),
|
||||
(
|
||||
PRESSURE_KPA,
|
||||
PRESSURE_HPA,
|
||||
PRESSURE_HPA,
|
||||
1.234,
|
||||
12.34,
|
||||
SensorDeviceClass.PRESSURE,
|
||||
),
|
||||
(
|
||||
PRESSURE_HPA,
|
||||
PRESSURE_MMHG,
|
||||
PRESSURE_MMHG,
|
||||
1000,
|
||||
750,
|
||||
SensorDeviceClass.PRESSURE,
|
||||
),
|
||||
# Not a supported pressure unit
|
||||
(PRESSURE_HPA, "peer_pressure", PRESSURE_HPA, 1000, 1000),
|
||||
(
|
||||
PRESSURE_HPA,
|
||||
"peer_pressure",
|
||||
PRESSURE_HPA,
|
||||
1000,
|
||||
1000,
|
||||
SensorDeviceClass.PRESSURE,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_custom_unit_change(
|
||||
|
@ -473,6 +530,7 @@ async def test_custom_unit_change(
|
|||
state_unit,
|
||||
native_value,
|
||||
custom_value,
|
||||
device_class,
|
||||
):
|
||||
"""Test custom unit changes are picked up."""
|
||||
entity_registry = er.async_get(hass)
|
||||
|
@ -482,7 +540,7 @@ async def test_custom_unit_change(
|
|||
name="Test",
|
||||
native_value=str(native_value),
|
||||
native_unit_of_measurement=native_unit,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
device_class=device_class,
|
||||
unique_id="very_unique",
|
||||
)
|
||||
|
||||
|
|
|
@ -85,6 +85,8 @@ def set_time_zone():
|
|||
(None, "%", "%", "%", None, 13.050847, -10, 30),
|
||||
("battery", "%", "%", "%", None, 13.050847, -10, 30),
|
||||
("battery", None, None, None, None, 13.050847, -10, 30),
|
||||
("distance", "m", "m", "m", "distance", 13.050847, -10, 30),
|
||||
("distance", "mi", "mi", "m", "distance", 13.050847, -10, 30),
|
||||
("humidity", "%", "%", "%", None, 13.050847, -10, 30),
|
||||
("humidity", None, None, None, None, 13.050847, -10, 30),
|
||||
("pressure", "Pa", "Pa", "Pa", "pressure", 13.050847, -10, 30),
|
||||
|
@ -351,12 +353,16 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes
|
|||
@pytest.mark.parametrize(
|
||||
"units, device_class, state_unit, display_unit, statistics_unit, unit_class, factor",
|
||||
[
|
||||
(IMPERIAL_SYSTEM, "distance", "m", "m", "m", "distance", 1),
|
||||
(IMPERIAL_SYSTEM, "distance", "mi", "mi", "m", "distance", 1),
|
||||
(IMPERIAL_SYSTEM, "energy", "kWh", "kWh", "kWh", "energy", 1),
|
||||
(IMPERIAL_SYSTEM, "energy", "Wh", "Wh", "kWh", "energy", 1),
|
||||
(IMPERIAL_SYSTEM, "monetary", "EUR", "EUR", "EUR", None, 1),
|
||||
(IMPERIAL_SYSTEM, "monetary", "SEK", "SEK", "SEK", None, 1),
|
||||
(IMPERIAL_SYSTEM, "gas", "m³", "m³", "m³", "volume", 1),
|
||||
(IMPERIAL_SYSTEM, "gas", "ft³", "ft³", "m³", "volume", 1),
|
||||
(METRIC_SYSTEM, "distance", "m", "m", "m", "distance", 1),
|
||||
(METRIC_SYSTEM, "distance", "mi", "mi", "m", "distance", 1),
|
||||
(METRIC_SYSTEM, "energy", "kWh", "kWh", "kWh", "energy", 1),
|
||||
(METRIC_SYSTEM, "energy", "Wh", "Wh", "kWh", "energy", 1),
|
||||
(METRIC_SYSTEM, "monetary", "EUR", "EUR", "EUR", None, 1),
|
||||
|
@ -1548,6 +1554,8 @@ def test_compile_hourly_energy_statistics_multiple(hass_recorder, caplog):
|
|||
[
|
||||
("battery", "%", 30),
|
||||
("battery", None, 30),
|
||||
("distance", "m", 30),
|
||||
("distance", "mi", 30),
|
||||
("humidity", "%", 30),
|
||||
("humidity", None, 30),
|
||||
("pressure", "Pa", 30),
|
||||
|
@ -1635,6 +1643,8 @@ def test_compile_hourly_statistics_partially_unavailable(hass_recorder, caplog):
|
|||
[
|
||||
("battery", "%", 30),
|
||||
("battery", None, 30),
|
||||
("distance", "m", 30),
|
||||
("distance", "mi", 30),
|
||||
("humidity", "%", 30),
|
||||
("humidity", None, 30),
|
||||
("pressure", "Pa", 30),
|
||||
|
@ -1708,6 +1718,10 @@ def test_compile_hourly_statistics_fails(hass_recorder, caplog):
|
|||
[
|
||||
("measurement", "battery", "%", "%", "%", None, "mean"),
|
||||
("measurement", "battery", None, None, None, None, "mean"),
|
||||
("measurement", "distance", "m", "m", "m", "distance", "mean"),
|
||||
("measurement", "distance", "mi", "mi", "m", "distance", "mean"),
|
||||
("total", "distance", "m", "m", "m", "distance", "sum"),
|
||||
("total", "distance", "mi", "mi", "m", "distance", "sum"),
|
||||
("total", "energy", "Wh", "Wh", "kWh", "energy", "sum"),
|
||||
("total", "energy", "kWh", "kWh", "kWh", "energy", "sum"),
|
||||
("measurement", "energy", "Wh", "Wh", "kWh", "energy", "mean"),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue