1
mirror of https://github.com/home-assistant/core synced 2024-08-15 18:25:44 +02:00

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:
epenet 2022-09-27 15:34:00 +01:00 committed by GitHub
parent a58f919972
commit bfcc18e5b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 126 additions and 8 deletions

View File

@ -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,

View File

@ -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 , 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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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",

View File

@ -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",

View File

@ -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³", "", "volume"),

View File

@ -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",
)

View File

@ -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", "", "", "", "volume", 1),
(IMPERIAL_SYSTEM, "gas", "ft³", "ft³", "", "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"),