Skip to content

Commit

Permalink
Enable unit conversion for DATA_RATE (home-assistant#84698)
Browse files Browse the repository at this point in the history
  • Loading branch information
epenet authored and frenck committed Dec 30, 2022
1 parent 2cb7a80 commit b24c40f
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 0 deletions.
2 changes: 2 additions & 0 deletions homeassistant/components/sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
from homeassistant.util import dt as dt_util
from homeassistant.util.unit_conversion import (
BaseUnitConverter,
DataRateConverter,
DistanceConverter,
MassConverter,
PressureConverter,
Expand Down Expand Up @@ -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_RATE: DataRateConverter,
SensorDeviceClass.DISTANCE: DistanceConverter,
SensorDeviceClass.GAS: VolumeConverter,
SensorDeviceClass.PRECIPITATION: DistanceConverter,
Expand Down
23 changes: 23 additions & 0 deletions homeassistant/util/unit_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from homeassistant.const import (
UNIT_NOT_RECOGNIZED_TEMPLATE,
UnitOfDataRate,
UnitOfEnergy,
UnitOfLength,
UnitOfMass,
Expand Down Expand Up @@ -86,6 +87,28 @@ def get_unit_ratio(cls, from_unit: str, to_unit: str) -> float:
return cls._UNIT_CONVERSION[from_unit] / cls._UNIT_CONVERSION[to_unit]


class DataRateConverter(BaseUnitConverter):
"""Utility to convert data rate values."""

UNIT_CLASS = "data_rate"
NORMALIZED_UNIT = UnitOfDataRate.BITS_PER_SECOND
# Units in terms of bits
_UNIT_CONVERSION: dict[str, float] = {
UnitOfDataRate.BITS_PER_SECOND: 1,
UnitOfDataRate.KILOBITS_PER_SECOND: 1 / 1e3,
UnitOfDataRate.MEGABITS_PER_SECOND: 1 / 1e6,
UnitOfDataRate.GIGABITS_PER_SECOND: 1 / 1e9,
UnitOfDataRate.BYTES_PER_SECOND: 1 / 8,
UnitOfDataRate.KILOBYTES_PER_SECOND: 1 / 8e3,
UnitOfDataRate.MEGABYTES_PER_SECOND: 1 / 8e6,
UnitOfDataRate.GIGABYTES_PER_SECOND: 1 / 8e9,
UnitOfDataRate.KIBIBYTES_PER_SECOND: 1 / 2**13,
UnitOfDataRate.MEBIBYTES_PER_SECOND: 1 / 2**23,
UnitOfDataRate.GIBIBYTES_PER_SECOND: 1 / 2**33,
}
VALID_UNITS = set(UnitOfDataRate)


class DistanceConverter(BaseUnitConverter):
"""Utility to convert distance values."""

Expand Down
57 changes: 57 additions & 0 deletions tests/util/test_unit_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest

from homeassistant.const import (
UnitOfDataRate,
UnitOfEnergy,
UnitOfLength,
UnitOfMass,
Expand All @@ -15,6 +16,7 @@
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.unit_conversion import (
BaseUnitConverter,
DataRateConverter,
DistanceConverter,
EnergyConverter,
MassConverter,
Expand All @@ -31,6 +33,7 @@
@pytest.mark.parametrize(
"converter,valid_unit",
[
(DataRateConverter, UnitOfDataRate.GIBIBYTES_PER_SECOND),
(DistanceConverter, UnitOfLength.KILOMETERS),
(DistanceConverter, UnitOfLength.METERS),
(DistanceConverter, UnitOfLength.CENTIMETERS),
Expand Down Expand Up @@ -85,6 +88,7 @@ def test_convert_same_unit(converter: type[BaseUnitConverter], valid_unit: str)
@pytest.mark.parametrize(
"converter,valid_unit",
[
(DataRateConverter, UnitOfDataRate.GIBIBYTES_PER_SECOND),
(DistanceConverter, UnitOfLength.KILOMETERS),
(EnergyConverter, UnitOfEnergy.KILO_WATT_HOUR),
(MassConverter, UnitOfMass.GRAMS),
Expand All @@ -111,6 +115,11 @@ def test_convert_invalid_unit(
@pytest.mark.parametrize(
"converter,from_unit,to_unit",
[
(
DataRateConverter,
UnitOfDataRate.BYTES_PER_SECOND,
UnitOfDataRate.BITS_PER_SECOND,
),
(DistanceConverter, UnitOfLength.KILOMETERS, UnitOfLength.METERS),
(EnergyConverter, UnitOfEnergy.WATT_HOUR, UnitOfEnergy.KILO_WATT_HOUR),
(MassConverter, UnitOfMass.GRAMS, UnitOfMass.KILOGRAMS),
Expand All @@ -132,6 +141,12 @@ def test_convert_nonnumeric_value(
@pytest.mark.parametrize(
"converter,from_unit,to_unit,expected",
[
(
DataRateConverter,
UnitOfDataRate.BITS_PER_SECOND,
UnitOfDataRate.BYTES_PER_SECOND,
8,
),
(DistanceConverter, UnitOfLength.KILOMETERS, UnitOfLength.METERS, 1 / 1000),
(EnergyConverter, UnitOfEnergy.WATT_HOUR, UnitOfEnergy.KILO_WATT_HOUR, 1000),
(PowerConverter, UnitOfPower.WATT, UnitOfPower.KILO_WATT, 1000),
Expand Down Expand Up @@ -168,6 +183,48 @@ def test_get_unit_ratio(
assert converter.get_unit_ratio(from_unit, to_unit) == expected


@pytest.mark.parametrize(
"value,from_unit,expected,to_unit",
[
(8e3, UnitOfDataRate.BITS_PER_SECOND, 8, UnitOfDataRate.KILOBITS_PER_SECOND),
(8e6, UnitOfDataRate.BITS_PER_SECOND, 8, UnitOfDataRate.MEGABITS_PER_SECOND),
(8e9, UnitOfDataRate.BITS_PER_SECOND, 8, UnitOfDataRate.GIGABITS_PER_SECOND),
(8, UnitOfDataRate.BITS_PER_SECOND, 1, UnitOfDataRate.BYTES_PER_SECOND),
(8e3, UnitOfDataRate.BITS_PER_SECOND, 1, UnitOfDataRate.KILOBYTES_PER_SECOND),
(8e6, UnitOfDataRate.BITS_PER_SECOND, 1, UnitOfDataRate.MEGABYTES_PER_SECOND),
(8e9, UnitOfDataRate.BITS_PER_SECOND, 1, UnitOfDataRate.GIGABYTES_PER_SECOND),
(
8 * 2**10,
UnitOfDataRate.BITS_PER_SECOND,
1,
UnitOfDataRate.KIBIBYTES_PER_SECOND,
),
(
8 * 2**20,
UnitOfDataRate.BITS_PER_SECOND,
1,
UnitOfDataRate.MEBIBYTES_PER_SECOND,
),
(
8 * 2**30,
UnitOfDataRate.BITS_PER_SECOND,
1,
UnitOfDataRate.GIBIBYTES_PER_SECOND,
),
],
)
def test_data_rate_convert(
value: float,
from_unit: str,
expected: float,
to_unit: str,
) -> None:
"""Test conversion to other units."""
assert DataRateConverter.convert(value, from_unit, to_unit) == pytest.approx(
expected
)


@pytest.mark.parametrize(
"value,from_unit,expected,to_unit",
[
Expand Down

0 comments on commit b24c40f

Please sign in to comment.