From 6261994fcf4ac8f7b7dca464a0abf7de8127e864 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 29 Dec 2022 09:58:15 +0100 Subject: [PATCH] Enable unit conversion for DATA_SIZE (#84699) --- homeassistant/components/sensor/__init__.py | 2 + homeassistant/util/unit_conversion.py | 33 +++++++++++++++ tests/util/test_unit_conversion.py | 47 +++++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 08386ced6dee5..12f497668342f 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -88,6 +88,7 @@ BaseUnitConverter, DataRateConverter, DistanceConverter, + InformationConverter, MassConverter, PressureConverter, SpeedConverter, @@ -468,6 +469,7 @@ class SensorStateClass(StrEnum): # `entity-registry-settings.ts` UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] = { SensorDeviceClass.DATA_RATE: DataRateConverter, + SensorDeviceClass.DATA_SIZE: InformationConverter, SensorDeviceClass.DISTANCE: DistanceConverter, SensorDeviceClass.GAS: VolumeConverter, SensorDeviceClass.PRECIPITATION: DistanceConverter, diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index 3cfd0a764e014..623b70da1a843 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -5,6 +5,7 @@ UNIT_NOT_RECOGNIZED_TEMPLATE, UnitOfDataRate, UnitOfEnergy, + UnitOfInformation, UnitOfLength, UnitOfMass, UnitOfPower, @@ -155,6 +156,38 @@ class EnergyConverter(BaseUnitConverter): } +class InformationConverter(BaseUnitConverter): + """Utility to convert information values.""" + + UNIT_CLASS = "information" + NORMALIZED_UNIT = UnitOfInformation.BITS + # Units in terms of bits + _UNIT_CONVERSION: dict[str, float] = { + UnitOfInformation.BITS: 1, + UnitOfInformation.KILOBITS: 1 / 1e3, + UnitOfInformation.MEGABITS: 1 / 1e6, + UnitOfInformation.GIGABITS: 1 / 1e9, + UnitOfInformation.BYTES: 1 / 8, + UnitOfInformation.KILOBYTES: 1 / 8e3, + UnitOfInformation.MEGABYTES: 1 / 8e6, + UnitOfInformation.GIGABYTES: 1 / 8e9, + UnitOfInformation.TERABYTES: 1 / 8e12, + UnitOfInformation.PETABYTES: 1 / 8e15, + UnitOfInformation.EXABYTES: 1 / 8e18, + UnitOfInformation.ZETTABYTES: 1 / 8e21, + UnitOfInformation.YOTTABYTES: 1 / 8e24, + UnitOfInformation.KIBIBYTES: 1 / 2**13, + UnitOfInformation.MEBIBYTES: 1 / 2**23, + UnitOfInformation.GIBIBYTES: 1 / 2**33, + UnitOfInformation.TEBIBYTES: 1 / 2**43, + UnitOfInformation.PEBIBYTES: 1 / 2**53, + UnitOfInformation.EXBIBYTES: 1 / 2**63, + UnitOfInformation.ZEBIBYTES: 1 / 2**73, + UnitOfInformation.YOBIBYTES: 1 / 2**83, + } + VALID_UNITS = set(UnitOfInformation) + + class MassConverter(BaseUnitConverter): """Utility to convert mass values.""" diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index f5c9970ca59b2..a0e926ed2a692 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -4,6 +4,7 @@ from homeassistant.const import ( UnitOfDataRate, UnitOfEnergy, + UnitOfInformation, UnitOfLength, UnitOfMass, UnitOfPower, @@ -19,6 +20,7 @@ DataRateConverter, DistanceConverter, EnergyConverter, + InformationConverter, MassConverter, PowerConverter, PressureConverter, @@ -46,6 +48,7 @@ (EnergyConverter, UnitOfEnergy.KILO_WATT_HOUR), (EnergyConverter, UnitOfEnergy.MEGA_WATT_HOUR), (EnergyConverter, UnitOfEnergy.GIGA_JOULE), + (InformationConverter, UnitOfInformation.GIGABYTES), (MassConverter, UnitOfMass.GRAMS), (MassConverter, UnitOfMass.KILOGRAMS), (MassConverter, UnitOfMass.MICROGRAMS), @@ -91,6 +94,7 @@ def test_convert_same_unit(converter: type[BaseUnitConverter], valid_unit: str) (DataRateConverter, UnitOfDataRate.GIBIBYTES_PER_SECOND), (DistanceConverter, UnitOfLength.KILOMETERS), (EnergyConverter, UnitOfEnergy.KILO_WATT_HOUR), + (InformationConverter, UnitOfInformation.GIBIBYTES), (MassConverter, UnitOfMass.GRAMS), (PowerConverter, UnitOfPower.WATT), (PressureConverter, UnitOfPressure.PA), @@ -122,6 +126,11 @@ def test_convert_invalid_unit( ), (DistanceConverter, UnitOfLength.KILOMETERS, UnitOfLength.METERS), (EnergyConverter, UnitOfEnergy.WATT_HOUR, UnitOfEnergy.KILO_WATT_HOUR), + ( + InformationConverter, + UnitOfInformation.GIBIBYTES, + UnitOfInformation.GIGABYTES, + ), (MassConverter, UnitOfMass.GRAMS, UnitOfMass.KILOGRAMS), (PowerConverter, UnitOfPower.WATT, UnitOfPower.KILO_WATT), (PressureConverter, UnitOfPressure.HPA, UnitOfPressure.INHG), @@ -149,6 +158,7 @@ def test_convert_nonnumeric_value( ), (DistanceConverter, UnitOfLength.KILOMETERS, UnitOfLength.METERS, 1 / 1000), (EnergyConverter, UnitOfEnergy.WATT_HOUR, UnitOfEnergy.KILO_WATT_HOUR, 1000), + (InformationConverter, UnitOfInformation.BITS, UnitOfInformation.BYTES, 8), (PowerConverter, UnitOfPower.WATT, UnitOfPower.KILO_WATT, 1000), ( PressureConverter, @@ -364,6 +374,43 @@ def test_energy_convert( assert EnergyConverter.convert(value, from_unit, to_unit) == expected +@pytest.mark.parametrize( + "value,from_unit,expected,to_unit", + [ + (8e3, UnitOfInformation.BITS, 8, UnitOfInformation.KILOBITS), + (8e6, UnitOfInformation.BITS, 8, UnitOfInformation.MEGABITS), + (8e9, UnitOfInformation.BITS, 8, UnitOfInformation.GIGABITS), + (8, UnitOfInformation.BITS, 1, UnitOfInformation.BYTES), + (8e3, UnitOfInformation.BITS, 1, UnitOfInformation.KILOBYTES), + (8e6, UnitOfInformation.BITS, 1, UnitOfInformation.MEGABYTES), + (8e9, UnitOfInformation.BITS, 1, UnitOfInformation.GIGABYTES), + (8e12, UnitOfInformation.BITS, 1, UnitOfInformation.TERABYTES), + (8e15, UnitOfInformation.BITS, 1, UnitOfInformation.PETABYTES), + (8e18, UnitOfInformation.BITS, 1, UnitOfInformation.EXABYTES), + (8e21, UnitOfInformation.BITS, 1, UnitOfInformation.ZETTABYTES), + (8e24, UnitOfInformation.BITS, 1, UnitOfInformation.YOTTABYTES), + (8 * 2**10, UnitOfInformation.BITS, 1, UnitOfInformation.KIBIBYTES), + (8 * 2**20, UnitOfInformation.BITS, 1, UnitOfInformation.MEBIBYTES), + (8 * 2**30, UnitOfInformation.BITS, 1, UnitOfInformation.GIBIBYTES), + (8 * 2**40, UnitOfInformation.BITS, 1, UnitOfInformation.TEBIBYTES), + (8 * 2**50, UnitOfInformation.BITS, 1, UnitOfInformation.PEBIBYTES), + (8 * 2**60, UnitOfInformation.BITS, 1, UnitOfInformation.EXBIBYTES), + (8 * 2**70, UnitOfInformation.BITS, 1, UnitOfInformation.ZEBIBYTES), + (8 * 2**80, UnitOfInformation.BITS, 1, UnitOfInformation.YOBIBYTES), + ], +) +def test_information_convert( + value: float, + from_unit: str, + expected: float, + to_unit: str, +) -> None: + """Test conversion to other units.""" + assert InformationConverter.convert(value, from_unit, to_unit) == pytest.approx( + expected + ) + + @pytest.mark.parametrize( "value,from_unit,expected,to_unit", [