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

Add new weight device class (#79185)

This commit is contained in:
epenet 2022-09-28 12:13:49 +02:00 committed by GitHub
parent c96c5bed7d
commit 5389ff3253
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 165 additions and 4 deletions

View File

@ -35,6 +35,7 @@ from homeassistant.util.unit_conversion import (
BaseUnitConverter,
DistanceConverter,
EnergyConverter,
MassConverter,
PowerConverter,
PressureConverter,
SpeedConverter,
@ -126,6 +127,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,
MassConverter.NORMALIZED_UNIT: MassConverter.UNIT_CLASS,
PowerConverter.NORMALIZED_UNIT: PowerConverter.UNIT_CLASS,
PressureConverter.NORMALIZED_UNIT: PressureConverter.UNIT_CLASS,
SpeedConverter.NORMALIZED_UNIT: SpeedConverter.UNIT_CLASS,
@ -136,6 +138,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,
MassConverter.NORMALIZED_UNIT: MassConverter,
PowerConverter.NORMALIZED_UNIT: PowerConverter,
PressureConverter.NORMALIZED_UNIT: PressureConverter,
SpeedConverter.NORMALIZED_UNIT: SpeedConverter,

View File

@ -21,6 +21,7 @@ from homeassistant.util import dt as dt_util
from homeassistant.util.unit_conversion import (
DistanceConverter,
EnergyConverter,
MassConverter,
PowerConverter,
PressureConverter,
SpeedConverter,
@ -126,6 +127,7 @@ async def ws_handle_get_statistics_during_period(
{
vol.Optional("distance"): vol.In(DistanceConverter.VALID_UNITS),
vol.Optional("energy"): vol.In(EnergyConverter.VALID_UNITS),
vol.Optional("mass"): vol.In(MassConverter.VALID_UNITS),
vol.Optional("power"): vol.In(PowerConverter.VALID_UNITS),
vol.Optional("pressure"): vol.In(PressureConverter.VALID_UNITS),
vol.Optional("speed"): vol.In(SpeedConverter.VALID_UNITS),
@ -335,6 +337,11 @@ async def ws_adjust_sum_statistics(
ENERGY_WATT_HOUR,
):
return True
if (
statistics_unit == MassConverter.NORMALIZED_UNIT
and display_unit in MassConverter.VALID_UNITS
):
return True
if (
statistics_unit == VolumeConverter.NORMALIZED_UNIT
and display_unit in VolumeConverter.VALID_UNITS

View File

@ -61,6 +61,7 @@ from homeassistant.util import dt as dt_util
from homeassistant.util.unit_conversion import (
BaseUnitConverter,
DistanceConverter,
MassConverter,
PressureConverter,
SpeedConverter,
TemperatureConverter,
@ -189,6 +190,10 @@ class SensorDeviceClass(StrEnum):
# volume (VOLUME_*)
VOLUME = "volume"
# weight/mass (g, kg, mg, µg, oz, lb)
WEIGHT = "weight"
"""Using weight instead of mass because is fits better with every day language"""
DEVICE_CLASSES_SCHEMA: Final = vol.All(vol.Lower, vol.Coerce(SensorDeviceClass))
@ -226,6 +231,7 @@ UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = {
SensorDeviceClass.SPEED: SpeedConverter,
SensorDeviceClass.TEMPERATURE: TemperatureConverter,
SensorDeviceClass.VOLUME: VolumeConverter,
SensorDeviceClass.WEIGHT: MassConverter,
}
UNIT_RATIOS: dict[str, dict[str, float]] = {
@ -238,6 +244,7 @@ UNIT_RATIOS: dict[str, dict[str, float]] = {
TEMP_KELVIN: 1.0,
},
SensorDeviceClass.VOLUME: VolumeConverter.UNIT_CONVERSION,
SensorDeviceClass.WEIGHT: MassConverter.UNIT_CONVERSION,
}
# mypy: disallow-any-generics

View File

@ -62,6 +62,7 @@ CONF_IS_VALUE = "is_value"
CONF_IS_VOLATILE_ORGANIC_COMPOUNDS = "is_volatile_organic_compounds"
CONF_IS_VOLTAGE = "is_voltage"
CONF_IS_VOLUME = "is_volume"
CONF_IS_WEIGHT = "is_weight"
ENTITY_CONDITIONS = {
SensorDeviceClass.APPARENT_POWER: [{CONF_TYPE: CONF_IS_APPARENT_POWER}],
@ -96,6 +97,7 @@ ENTITY_CONDITIONS = {
],
SensorDeviceClass.VOLTAGE: [{CONF_TYPE: CONF_IS_VOLTAGE}],
SensorDeviceClass.VOLUME: [{CONF_TYPE: CONF_IS_VOLUME}],
SensorDeviceClass.WEIGHT: [{CONF_TYPE: CONF_IS_WEIGHT}],
DEVICE_CLASS_NONE: [{CONF_TYPE: CONF_IS_VALUE}],
}

View File

@ -61,6 +61,7 @@ CONF_VALUE = "value"
CONF_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
CONF_VOLTAGE = "voltage"
CONF_VOLUME = "volume"
CONF_WEIGHT = "weight"
ENTITY_TRIGGERS = {
SensorDeviceClass.APPARENT_POWER: [{CONF_TYPE: CONF_APPARENT_POWER}],
@ -95,6 +96,7 @@ ENTITY_TRIGGERS = {
],
SensorDeviceClass.VOLTAGE: [{CONF_TYPE: CONF_VOLTAGE}],
SensorDeviceClass.VOLUME: [{CONF_TYPE: CONF_VOLUME}],
SensorDeviceClass.WEIGHT: [{CONF_TYPE: CONF_WEIGHT}],
DEVICE_CLASS_NONE: [{CONF_TYPE: CONF_VALUE}],
}

View File

@ -32,6 +32,7 @@ from homeassistant.util.unit_conversion import (
BaseUnitConverter,
DistanceConverter,
EnergyConverter,
MassConverter,
PowerConverter,
PressureConverter,
SpeedConverter,
@ -67,6 +68,7 @@ UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = {
SensorDeviceClass.SPEED: SpeedConverter,
SensorDeviceClass.TEMPERATURE: TemperatureConverter,
SensorDeviceClass.VOLUME: VolumeConverter,
SensorDeviceClass.WEIGHT: MassConverter,
}
# Keep track of entities for which a warning about decreasing value has been logged

View File

@ -32,7 +32,8 @@
"is_value": "Current {entity_name} value",
"is_volatile_organic_compounds": "Current {entity_name} volatile organic compounds concentration level",
"is_voltage": "Current {entity_name} voltage",
"is_volume": "Current {entity_name} volume"
"is_volume": "Current {entity_name} volume",
"is_weight": "Current {entity_name} weight"
},
"trigger_type": {
"apparent_power": "{entity_name} apparent power changes",
@ -65,7 +66,8 @@
"value": "{entity_name} value changes",
"volatile_organic_compounds": "{entity_name} volatile organic compounds concentration changes",
"voltage": "{entity_name} voltage changes",
"volume": "{entity_name} volume changes"
"volume": "{entity_name} volume changes",
"weight": "{entity_name} weight changes"
}
},
"state": {

View File

@ -31,7 +31,8 @@
"is_value": "Current {entity_name} value",
"is_volatile_organic_compounds": "Current {entity_name} volatile organic compounds concentration level",
"is_voltage": "Current {entity_name} voltage",
"is_volume": "Current {entity_name} volume"
"is_volume": "Current {entity_name} volume",
"is_weight": "Current {entity_name} weight"
},
"trigger_type": {
"apparent_power": "{entity_name} apparent power changes",
@ -64,7 +65,8 @@
"value": "{entity_name} value changes",
"volatile_organic_compounds": "{entity_name} volatile organic compounds concentration changes",
"voltage": "{entity_name} voltage changes",
"volume": "{entity_name} volume changes"
"volume": "{entity_name} volume changes",
"weight": "{entity_name} weight changes"
}
},
"state": {

View File

@ -15,6 +15,12 @@ from homeassistant.const import (
LENGTH_MILES,
LENGTH_MILLIMETERS,
LENGTH_YARD,
MASS_GRAMS,
MASS_KILOGRAMS,
MASS_MICROGRAMS,
MASS_MILLIGRAMS,
MASS_OUNCES,
MASS_POUNDS,
POWER_KILO_WATT,
POWER_WATT,
PRESSURE_BAR,
@ -63,6 +69,10 @@ _NAUTICAL_MILE_TO_M = 1852 # 1 nautical mile = 1852 m
_HRS_TO_SECS = 60 * 60 # 1 hr = 3600 seconds
_DAYS_TO_SECS = 24 * _HRS_TO_SECS # 1 day = 24 hours = 86400 seconds
# Mass conversion constants
_POUND_TO_G = 453.59237
_OUNCE_TO_G = _POUND_TO_G / 16
# Volume conversion constants
_L_TO_CUBIC_METER = 0.001 # 1 L = 0.001 m³
_ML_TO_CUBIC_METER = 0.001 * _L_TO_CUBIC_METER # 1 mL = 0.001 L
@ -157,6 +167,29 @@ class EnergyConverter(BaseUnitConverterWithUnitConversion):
}
class MassConverter(BaseUnitConverterWithUnitConversion):
"""Utility to convert mass values."""
UNIT_CLASS = "mass"
NORMALIZED_UNIT = MASS_GRAMS
UNIT_CONVERSION: dict[str, float] = {
MASS_MICROGRAMS: 1 * 1000 * 1000,
MASS_MILLIGRAMS: 1 * 1000,
MASS_GRAMS: 1,
MASS_KILOGRAMS: 1 / 1000,
MASS_OUNCES: 1 / _OUNCE_TO_G,
MASS_POUNDS: 1 / _POUND_TO_G,
}
VALID_UNITS = {
MASS_GRAMS,
MASS_KILOGRAMS,
MASS_MILLIGRAMS,
MASS_MICROGRAMS,
MASS_OUNCES,
MASS_POUNDS,
}
class PowerConverter(BaseUnitConverterWithUnitConversion):
"""Utility to convert power values."""

View File

@ -12,6 +12,8 @@ from homeassistant.const import (
LENGTH_INCHES,
LENGTH_KILOMETERS,
LENGTH_MILES,
MASS_GRAMS,
MASS_OUNCES,
PRESSURE_HPA,
PRESSURE_INHG,
PRESSURE_KPA,
@ -578,6 +580,31 @@ async def test_custom_unit(
100,
SensorDeviceClass.VOLUME,
),
# Weight
(
MASS_GRAMS,
MASS_OUNCES,
MASS_OUNCES,
100,
3.5,
SensorDeviceClass.WEIGHT,
),
(
MASS_OUNCES,
MASS_GRAMS,
MASS_GRAMS,
78,
2211,
SensorDeviceClass.WEIGHT,
),
(
MASS_GRAMS,
"peer_distance",
MASS_GRAMS,
100,
100,
SensorDeviceClass.WEIGHT,
),
],
)
async def test_custom_unit_change(

View File

@ -100,6 +100,8 @@ def set_time_zone():
("temperature", "°F", "°F", "°C", "temperature", 13.050847, -10, 30),
("volume", "", "", "", "volume", 13.050847, -10, 30),
("volume", "ft³", "ft³", "", "volume", 13.050847, -10, 30),
("weight", "g", "g", "g", "mass", 13.050847, -10, 30),
("weight", "oz", "oz", "g", "mass", 13.050847, -10, 30),
],
)
def test_compile_hourly_statistics(
@ -367,6 +369,8 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes
(IMPERIAL_SYSTEM, "monetary", "SEK", "SEK", "SEK", None, 1),
(IMPERIAL_SYSTEM, "volume", "", "", "", "volume", 1),
(IMPERIAL_SYSTEM, "volume", "ft³", "ft³", "", "volume", 1),
(IMPERIAL_SYSTEM, "weight", "g", "g", "g", "mass", 1),
(IMPERIAL_SYSTEM, "weight", "oz", "oz", "g", "mass", 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),
@ -377,6 +381,8 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes
(METRIC_SYSTEM, "monetary", "SEK", "SEK", "SEK", None, 1),
(METRIC_SYSTEM, "volume", "", "", "", "volume", 1),
(METRIC_SYSTEM, "volume", "ft³", "ft³", "", "volume", 1),
(METRIC_SYSTEM, "weight", "g", "g", "g", "mass", 1),
(METRIC_SYSTEM, "weight", "oz", "oz", "g", "mass", 1),
],
)
async def test_compile_hourly_sum_statistics_amount(
@ -1577,6 +1583,8 @@ def test_compile_hourly_energy_statistics_multiple(hass_recorder, caplog):
("temperature", "°F", 30),
("volume", "", 30),
("volume", "ft³", 30),
("weight", "g", 30),
("weight", "oz", 30),
],
)
def test_compile_hourly_statistics_unchanged(
@ -1670,6 +1678,8 @@ def test_compile_hourly_statistics_partially_unavailable(hass_recorder, caplog):
("temperature", "°F", 30),
("volume", "", 30),
("volume", "ft³", 30),
("weight", "g", 30),
("weight", "oz", 30),
],
)
def test_compile_hourly_statistics_unavailable(
@ -1765,6 +1775,10 @@ def test_compile_hourly_statistics_fails(hass_recorder, caplog):
("measurement", "volume", "ft³", "ft³", "", "volume", "mean"),
("total", "volume", "", "", "", "volume", "sum"),
("total", "volume", "ft³", "ft³", "", "volume", "sum"),
("measurement", "weight", "g", "g", "g", "mass", "mean"),
("measurement", "weight", "oz", "oz", "g", "mass", "mean"),
("total", "weight", "g", "g", "g", "mass", "sum"),
("total", "weight", "oz", "oz", "g", "mass", "sum"),
],
)
def test_list_statistic_ids(

View File

@ -13,6 +13,12 @@ from homeassistant.const import (
LENGTH_MILES,
LENGTH_MILLIMETERS,
LENGTH_YARD,
MASS_GRAMS,
MASS_KILOGRAMS,
MASS_MICROGRAMS,
MASS_MILLIGRAMS,
MASS_OUNCES,
MASS_POUNDS,
POWER_KILO_WATT,
POWER_WATT,
PRESSURE_CBAR,
@ -46,6 +52,7 @@ from homeassistant.util.unit_conversion import (
BaseUnitConverter,
DistanceConverter,
EnergyConverter,
MassConverter,
PowerConverter,
PressureConverter,
SpeedConverter,
@ -70,6 +77,12 @@ INVALID_SYMBOL = "bob"
(EnergyConverter, ENERGY_WATT_HOUR),
(EnergyConverter, ENERGY_KILO_WATT_HOUR),
(EnergyConverter, ENERGY_MEGA_WATT_HOUR),
(MassConverter, MASS_GRAMS),
(MassConverter, MASS_KILOGRAMS),
(MassConverter, MASS_MICROGRAMS),
(MassConverter, MASS_MILLIGRAMS),
(MassConverter, MASS_OUNCES),
(MassConverter, MASS_POUNDS),
(PowerConverter, POWER_WATT),
(PowerConverter, POWER_KILO_WATT),
(PressureConverter, PRESSURE_PA),
@ -107,6 +120,7 @@ def test_convert_same_unit(converter: type[BaseUnitConverter], valid_unit: str)
[
(DistanceConverter, LENGTH_KILOMETERS),
(EnergyConverter, ENERGY_KILO_WATT_HOUR),
(MassConverter, MASS_GRAMS),
(PowerConverter, POWER_WATT),
(PressureConverter, PRESSURE_PA),
(SpeedConverter, SPEED_KILOMETERS_PER_HOUR),
@ -132,6 +146,7 @@ def test_convert_invalid_unit(
[
(DistanceConverter, LENGTH_KILOMETERS, LENGTH_METERS),
(EnergyConverter, ENERGY_WATT_HOUR, ENERGY_KILO_WATT_HOUR),
(MassConverter, MASS_GRAMS, MASS_KILOGRAMS),
(PowerConverter, POWER_WATT, POWER_KILO_WATT),
(PressureConverter, PRESSURE_HPA, PRESSURE_INHG),
(SpeedConverter, SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR),
@ -239,6 +254,51 @@ def test_energy_convert(
assert EnergyConverter.convert(value, from_unit, to_unit) == expected
@pytest.mark.parametrize(
"value,from_unit,expected,to_unit",
[
(10, MASS_KILOGRAMS, 10000, MASS_GRAMS),
(10, MASS_KILOGRAMS, 10000000, MASS_MILLIGRAMS),
(10, MASS_KILOGRAMS, 10000000000, MASS_MICROGRAMS),
(10, MASS_KILOGRAMS, pytest.approx(352.73961), MASS_OUNCES),
(10, MASS_KILOGRAMS, pytest.approx(22.046226), MASS_POUNDS),
(10, MASS_GRAMS, 0.01, MASS_KILOGRAMS),
(10, MASS_GRAMS, 10000, MASS_MILLIGRAMS),
(10, MASS_GRAMS, 10000000, MASS_MICROGRAMS),
(10, MASS_GRAMS, pytest.approx(0.35273961), MASS_OUNCES),
(10, MASS_GRAMS, pytest.approx(0.022046226), MASS_POUNDS),
(10, MASS_MILLIGRAMS, 0.00001, MASS_KILOGRAMS),
(10, MASS_MILLIGRAMS, 0.01, MASS_GRAMS),
(10, MASS_MILLIGRAMS, 10000, MASS_MICROGRAMS),
(10, MASS_MILLIGRAMS, pytest.approx(0.00035273961), MASS_OUNCES),
(10, MASS_MILLIGRAMS, pytest.approx(0.000022046226), MASS_POUNDS),
(10000, MASS_MICROGRAMS, 0.00001, MASS_KILOGRAMS),
(10000, MASS_MICROGRAMS, 0.01, MASS_GRAMS),
(10000, MASS_MICROGRAMS, 10, MASS_MILLIGRAMS),
(10000, MASS_MICROGRAMS, pytest.approx(0.00035273961), MASS_OUNCES),
(10000, MASS_MICROGRAMS, pytest.approx(0.000022046226), MASS_POUNDS),
(1, MASS_POUNDS, 0.45359237, MASS_KILOGRAMS),
(1, MASS_POUNDS, 453.59237, MASS_GRAMS),
(1, MASS_POUNDS, 453592.37, MASS_MILLIGRAMS),
(1, MASS_POUNDS, 453592370, MASS_MICROGRAMS),
(1, MASS_POUNDS, 16, MASS_OUNCES),
(16, MASS_OUNCES, 0.45359237, MASS_KILOGRAMS),
(16, MASS_OUNCES, 453.59237, MASS_GRAMS),
(16, MASS_OUNCES, 453592.37, MASS_MILLIGRAMS),
(16, MASS_OUNCES, 453592370, MASS_MICROGRAMS),
(16, MASS_OUNCES, 1, MASS_POUNDS),
],
)
def test_mass_convert(
value: float,
from_unit: str,
expected: float,
to_unit: str,
) -> None:
"""Test conversion to other units."""
assert MassConverter.convert(value, from_unit, to_unit) == expected
@pytest.mark.parametrize(
"value,from_unit,expected,to_unit",
[