From 24423b947c7cc06d9f45c5d05013c6e7b5a18678 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 28 Dec 2022 14:20:07 +0000 Subject: [PATCH] Enable unit conversion for DATA_SIZE --- 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 fc6f0b6081544d..1ebb0cb3e6371d 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -87,6 +87,7 @@ from homeassistant.util.unit_conversion import ( BaseUnitConverter, DistanceConverter, + InformationConverter, MassConverter, PressureConverter, SpeedConverter, @@ -466,6 +467,7 @@ class SensorStateClass(StrEnum): # Note: this needs to be aligned with frontend: OVERRIDE_SENSOR_UNITS in # `entity-registry-settings.ts` UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] = { + 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 1aeda96b6b3be0..9254e76ddad6ad 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -4,6 +4,7 @@ from homeassistant.const import ( UNIT_NOT_RECOGNIZED_TEMPLATE, UnitOfEnergy, + UnitOfInformation, UnitOfLength, UnitOfMass, UnitOfPower, @@ -132,6 +133,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 0d5cb7143ec27e..a6236d88a70211 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -3,6 +3,7 @@ from homeassistant.const import ( UnitOfEnergy, + UnitOfInformation, UnitOfLength, UnitOfMass, UnitOfPower, @@ -17,6 +18,7 @@ BaseUnitConverter, DistanceConverter, EnergyConverter, + InformationConverter, MassConverter, PowerConverter, PressureConverter, @@ -43,6 +45,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), @@ -87,6 +90,7 @@ def test_convert_same_unit(converter: type[BaseUnitConverter], valid_unit: str) [ (DistanceConverter, UnitOfLength.KILOMETERS), (EnergyConverter, UnitOfEnergy.KILO_WATT_HOUR), + (InformationConverter, UnitOfInformation.GIBIBYTES), (MassConverter, UnitOfMass.GRAMS), (PowerConverter, UnitOfPower.WATT), (PressureConverter, UnitOfPressure.PA), @@ -113,6 +117,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), @@ -134,6 +143,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, @@ -307,6 +317,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", [