diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 023f94ec88e..5786c9ee542 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -41,6 +41,7 @@ from homeassistant.util.unit_conversion import ( TemperatureConverter, UnitlessRatioConverter, VolumeConverter, + VolumeFlowRateConverter, ) from .const import ( @@ -139,6 +140,7 @@ STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = { **{unit: TemperatureConverter for unit in TemperatureConverter.VALID_UNITS}, **{unit: UnitlessRatioConverter for unit in UnitlessRatioConverter.VALID_UNITS}, **{unit: VolumeConverter for unit in VolumeConverter.VALID_UNITS}, + **{unit: VolumeFlowRateConverter for unit in VolumeFlowRateConverter.VALID_UNITS}, } DATA_SHORT_TERM_STATISTICS_RUN_CACHE = "recorder_short_term_statistics_run_cache" diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index 733dafeba27..4cbc0bf7868 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -28,6 +28,7 @@ from homeassistant.util.unit_conversion import ( TemperatureConverter, UnitlessRatioConverter, VolumeConverter, + VolumeFlowRateConverter, ) from .models import StatisticPeriod @@ -67,6 +68,7 @@ UNIT_SCHEMA = vol.Schema( vol.Optional("temperature"): vol.In(TemperatureConverter.VALID_UNITS), vol.Optional("unitless"): vol.In(UnitlessRatioConverter.VALID_UNITS), vol.Optional("volume"): vol.In(VolumeConverter.VALID_UNITS), + vol.Optional("volume_flow_rate"): vol.In(VolumeFlowRateConverter.VALID_UNITS), } ) diff --git a/homeassistant/components/sensor/const.py b/homeassistant/components/sensor/const.py index b1cb120e3fe..8a290a67b53 100644 --- a/homeassistant/components/sensor/const.py +++ b/homeassistant/components/sensor/const.py @@ -34,6 +34,7 @@ from homeassistant.const import ( UnitOfTemperature, UnitOfTime, UnitOfVolume, + UnitOfVolumeFlowRate, UnitOfVolumetricFlux, ) from homeassistant.helpers.deprecation import ( @@ -57,6 +58,7 @@ from homeassistant.util.unit_conversion import ( TemperatureConverter, UnitlessRatioConverter, VolumeConverter, + VolumeFlowRateConverter, ) DOMAIN: Final = "sensor" @@ -394,6 +396,14 @@ class SensorDeviceClass(StrEnum): USCS/imperial units are currently assumed to be US volumes) """ + VOLUME_FLOW_RATE = "volume_flow_rate" + """Generic flow rate + + Unit of measurement: UnitOfVolumeFlowRate + - SI /metric: `m³/h`, `l/m` + - USCS / imperial: `ft³/m` + """ + WATER = "water" """Water. @@ -489,6 +499,7 @@ UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] = SensorDeviceClass.VOLTAGE: ElectricPotentialConverter, SensorDeviceClass.VOLUME: VolumeConverter, SensorDeviceClass.VOLUME_STORAGE: VolumeConverter, + SensorDeviceClass.VOLUME_FLOW_RATE: VolumeFlowRateConverter, SensorDeviceClass.WATER: VolumeConverter, SensorDeviceClass.WEIGHT: MassConverter, SensorDeviceClass.WIND_SPEED: SpeedConverter, @@ -555,6 +566,7 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = { }, SensorDeviceClass.VOLTAGE: set(UnitOfElectricPotential), SensorDeviceClass.VOLUME: set(UnitOfVolume), + SensorDeviceClass.VOLUME_FLOW_RATE: set(UnitOfVolumeFlowRate), SensorDeviceClass.VOLUME_STORAGE: set(UnitOfVolume), SensorDeviceClass.WATER: { UnitOfVolume.CENTUM_CUBIC_FEET, diff --git a/homeassistant/const.py b/homeassistant/const.py index e0d5a859913..71fad5c82f8 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1041,6 +1041,7 @@ class UnitOfVolumeFlowRate(StrEnum): CUBIC_METERS_PER_HOUR = "m³/h" CUBIC_FEET_PER_MINUTE = "ft³/m" + LITERS_PER_MINUTE = "l/m" _DEPRECATED_VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR: Final = DeprecatedConstantEnum( diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index 5ce31b072cf..8fb87d04822 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -21,6 +21,7 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, UnitOfVolume, + UnitOfVolumeFlowRate, UnitOfVolumetricFlux, ) from homeassistant.exceptions import HomeAssistantError @@ -39,6 +40,7 @@ _NAUTICAL_MILE_TO_M = 1852 # 1 nautical mile = 1852 m # Duration conversion constants _HRS_TO_SECS = 60 * 60 # 1 hr = 3600 seconds +_HRS_TO_MINUTES = 60 # 1 hr = 60 minutes _DAYS_TO_SECS = 24 * _HRS_TO_SECS # 1 day = 24 hours = 86400 seconds # Mass conversion constants @@ -516,3 +518,23 @@ class VolumeConverter(BaseUnitConverter): UnitOfVolume.CUBIC_FEET, UnitOfVolume.CENTUM_CUBIC_FEET, } + + +class VolumeFlowRateConverter(BaseUnitConverter): + """Utility to convert volume values.""" + + UNIT_CLASS = "volume_flow_rate" + NORMALIZED_UNIT = UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR + # Units in terms of m³/h + _UNIT_CONVERSION: dict[str | None, float] = { + UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR: 1, + UnitOfVolumeFlowRate.CUBIC_FEET_PER_MINUTE: 1 + / (_HRS_TO_MINUTES * _CUBIC_FOOT_TO_CUBIC_METER), + UnitOfVolumeFlowRate.LITERS_PER_MINUTE: 1 + / (_HRS_TO_MINUTES * _L_TO_CUBIC_METER), + } + VALID_UNITS = { + UnitOfVolumeFlowRate.CUBIC_FEET_PER_MINUTE, + UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, + UnitOfVolumeFlowRate.LITERS_PER_MINUTE, + } diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index e7affecfaf4..8b662c398d9 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -22,6 +22,7 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, UnitOfVolume, + UnitOfVolumeFlowRate, UnitOfVolumetricFlux, ) from homeassistant.exceptions import HomeAssistantError @@ -41,6 +42,7 @@ from homeassistant.util.unit_conversion import ( TemperatureConverter, UnitlessRatioConverter, VolumeConverter, + VolumeFlowRateConverter, ) INVALID_SYMBOL = "bob" @@ -65,6 +67,7 @@ _ALL_CONVERTERS: dict[type[BaseUnitConverter], list[str | None]] = { TemperatureConverter, UnitlessRatioConverter, VolumeConverter, + VolumeFlowRateConverter, ) } @@ -103,6 +106,11 @@ _GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, flo ), UnitlessRatioConverter: (PERCENTAGE, None, 100), VolumeConverter: (UnitOfVolume.GALLONS, UnitOfVolume.LITERS, 0.264172), + VolumeFlowRateConverter: ( + UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, + UnitOfVolumeFlowRate.LITERS_PER_MINUTE, + 0.06, + ), } # Dict containing a conversion test for every known unit. @@ -413,6 +421,44 @@ _CONVERTED_VALUE: dict[ (5, UnitOfVolume.CENTUM_CUBIC_FEET, 3740.26, UnitOfVolume.GALLONS), (5, UnitOfVolume.CENTUM_CUBIC_FEET, 14158.42, UnitOfVolume.LITERS), ], + VolumeFlowRateConverter: [ + ( + 1, + UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, + 16.6666667, + UnitOfVolumeFlowRate.LITERS_PER_MINUTE, + ), + ( + 1, + UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, + 0.58857777, + UnitOfVolumeFlowRate.CUBIC_FEET_PER_MINUTE, + ), + ( + 1, + UnitOfVolumeFlowRate.LITERS_PER_MINUTE, + 0.06, + UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, + ), + ( + 1, + UnitOfVolumeFlowRate.LITERS_PER_MINUTE, + 0.03531466, + UnitOfVolumeFlowRate.CUBIC_FEET_PER_MINUTE, + ), + ( + 1, + UnitOfVolumeFlowRate.CUBIC_FEET_PER_MINUTE, + 1.69901079, + UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, + ), + ( + 1, + UnitOfVolumeFlowRate.CUBIC_FEET_PER_MINUTE, + 28.3168465, + UnitOfVolumeFlowRate.LITERS_PER_MINUTE, + ), + ], }