From bca4793c6983650aecafeb59d81fc639464857ca Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 23 May 2025 15:24:18 +0200 Subject: [PATCH] =?UTF-8?q?Add=20concentration=20conversion=20support=20fo?= =?UTF-8?q?r=20mg/m=C2=B3=20(#145325)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homeassistant/components/number/const.py | 6 +++-- .../components/recorder/statistics.py | 4 +++ .../components/recorder/websocket_api.py | 4 +++ homeassistant/components/sensor/const.py | 8 ++++-- homeassistant/util/unit_conversion.py | 16 ++++++++++++ tests/util/test_unit_conversion.py | 25 +++++++++++++++++++ 6 files changed, 59 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/number/const.py b/homeassistant/components/number/const.py index 2a9c4057168..1b41146cd2a 100644 --- a/homeassistant/components/number/const.py +++ b/homeassistant/components/number/const.py @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, DEGREE, @@ -370,7 +371,7 @@ class NumberDeviceClass(StrEnum): VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" """Amount of VOC. - Unit of measurement: `µg/m³` + Unit of measurement: `µg/m³`, `mg/m³` """ VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts" @@ -517,7 +518,8 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = { NumberDeviceClass.SULPHUR_DIOXIDE: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER}, NumberDeviceClass.TEMPERATURE: set(UnitOfTemperature), NumberDeviceClass.VOLATILE_ORGANIC_COMPOUNDS: { - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, }, NumberDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS: { CONCENTRATION_PARTS_PER_BILLION, diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index bdb5062e88e..7f41358dddf 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -55,6 +55,7 @@ from homeassistant.util.unit_conversion import ( EnergyDistanceConverter, InformationConverter, MassConverter, + MassVolumeConcentrationConverter, PowerConverter, PressureConverter, ReactiveEnergyConverter, @@ -197,6 +198,9 @@ STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = { BloodGlucoseConcentrationConverter.VALID_UNITS, BloodGlucoseConcentrationConverter, ), + **dict.fromkeys( + MassVolumeConcentrationConverter.VALID_UNITS, MassVolumeConcentrationConverter + ), **dict.fromkeys(ConductivityConverter.VALID_UNITS, ConductivityConverter), **dict.fromkeys(DataRateConverter.VALID_UNITS, DataRateConverter), **dict.fromkeys(DistanceConverter.VALID_UNITS, DistanceConverter), diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index 76a75a5849e..d052631c5f6 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 ( EnergyDistanceConverter, InformationConverter, MassConverter, + MassVolumeConcentrationConverter, PowerConverter, PressureConverter, ReactiveEnergyConverter, @@ -62,6 +63,9 @@ UNIT_SCHEMA = vol.Schema( vol.Optional("blood_glucose_concentration"): vol.In( BloodGlucoseConcentrationConverter.VALID_UNITS ), + vol.Optional("concentration"): vol.In( + MassVolumeConcentrationConverter.VALID_UNITS + ), vol.Optional("conductivity"): vol.In(ConductivityConverter.VALID_UNITS), vol.Optional("data_rate"): vol.In(DataRateConverter.VALID_UNITS), vol.Optional("distance"): vol.In(DistanceConverter.VALID_UNITS), diff --git a/homeassistant/components/sensor/const.py b/homeassistant/components/sensor/const.py index c466bc52703..f26edcd6c35 100644 --- a/homeassistant/components/sensor/const.py +++ b/homeassistant/components/sensor/const.py @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, DEGREE, @@ -57,6 +58,7 @@ from homeassistant.util.unit_conversion import ( EnergyDistanceConverter, InformationConverter, MassConverter, + MassVolumeConcentrationConverter, PowerConverter, PressureConverter, ReactiveEnergyConverter, @@ -400,7 +402,7 @@ class SensorDeviceClass(StrEnum): VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" """Amount of VOC. - Unit of measurement: `µg/m³` + Unit of measurement: `µg/m³`, `mg/m³` """ VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts" @@ -540,6 +542,7 @@ UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] = SensorDeviceClass.REACTIVE_ENERGY: ReactiveEnergyConverter, SensorDeviceClass.SPEED: SpeedConverter, SensorDeviceClass.TEMPERATURE: TemperatureConverter, + SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS: MassVolumeConcentrationConverter, SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS: UnitlessRatioConverter, SensorDeviceClass.VOLTAGE: ElectricPotentialConverter, SensorDeviceClass.VOLUME: VolumeConverter, @@ -617,7 +620,8 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = { SensorDeviceClass.SULPHUR_DIOXIDE: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER}, SensorDeviceClass.TEMPERATURE: set(UnitOfTemperature), SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS: { - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, }, SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS: { CONCENTRATION_PARTS_PER_BILLION, diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index 2ee7b5cd384..d0830d1f8bb 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -7,6 +7,8 @@ from functools import lru_cache from math import floor, log10 from homeassistant.const import ( + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, PERCENTAGE, @@ -686,6 +688,20 @@ class UnitlessRatioConverter(BaseUnitConverter): } +class MassVolumeConcentrationConverter(BaseUnitConverter): + """Utility to convert mass volume concentration values.""" + + UNIT_CLASS = "concentration" + _UNIT_CONVERSION: dict[str | None, float] = { + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER: 1000.0, # 1000 µg/m³ = 1 mg/m³ + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER: 1.0, + } + VALID_UNITS = { + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + } + + class VolumeConverter(BaseUnitConverter): """Utility to convert volume values.""" diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index 0e9da5dbf3d..7d0eb7226a0 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -8,6 +8,8 @@ from itertools import chain import pytest from homeassistant.const import ( + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, PERCENTAGE, @@ -48,6 +50,7 @@ from homeassistant.util.unit_conversion import ( EnergyDistanceConverter, InformationConverter, MassConverter, + MassVolumeConcentrationConverter, PowerConverter, PressureConverter, ReactiveEnergyConverter, @@ -69,6 +72,7 @@ _ALL_CONVERTERS: dict[type[BaseUnitConverter], list[str | None]] = { for converter in ( AreaConverter, BloodGlucoseConcentrationConverter, + MassVolumeConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -128,6 +132,11 @@ _GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, flo ), InformationConverter: (UnitOfInformation.BITS, UnitOfInformation.BYTES, 8), MassConverter: (UnitOfMass.STONES, UnitOfMass.KILOGRAMS, 0.157473), + MassVolumeConcentrationConverter: ( + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + 1000, + ), PowerConverter: (UnitOfPower.WATT, UnitOfPower.KILO_WATT, 1000), PressureConverter: (UnitOfPressure.HPA, UnitOfPressure.INHG, 33.86389), ReactiveEnergyConverter: ( @@ -738,6 +747,22 @@ _CONVERTED_VALUE: dict[ (5, None, 5000000, CONCENTRATION_PARTS_PER_MILLION), (5, PERCENTAGE, 0.05, None), ], + MassVolumeConcentrationConverter: [ + # 1000 µg/m³ = 1 mg/m³ + ( + 1000, + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + 1, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + ), + # 2 mg/m³ = 2000 µg/m³ + ( + 2, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + 2000, + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + ), + ], VolumeConverter: [ (5, UnitOfVolume.LITERS, 1.32086, UnitOfVolume.GALLONS), (5, UnitOfVolume.GALLONS, 18.92706, UnitOfVolume.LITERS),