From 5eef6edded1d986387e73bba1d3d69a8bbc1af7a Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 17 Sep 2025 23:04:23 +0200 Subject: [PATCH] =?UTF-8?q?Add=20mg/m=C2=B3=20as=20a=20valid=20UOM=20for?= =?UTF-8?q?=20sensor/number=20Carbon=20Monoxide=20device=20class=20(#15245?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homeassistant/components/number/const.py | 7 +++++-- .../components/recorder/statistics.py | 5 +++++ .../components/recorder/websocket_api.py | 4 ++++ homeassistant/components/sensor/const.py | 9 ++++++-- homeassistant/util/unit_conversion.py | 14 +++++++++++++ tests/components/sensor/test_init.py | 1 - tests/util/test_unit_conversion.py | 21 +++++++++++++++++++ 7 files changed, 56 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/number/const.py b/homeassistant/components/number/const.py index ec604623517..402592888a2 100644 --- a/homeassistant/components/number/const.py +++ b/homeassistant/components/number/const.py @@ -124,7 +124,7 @@ class NumberDeviceClass(StrEnum): CO = "carbon_monoxide" """Carbon Monoxide gas concentration. - Unit of measurement: `ppm` (parts per million) + Unit of measurement: `ppm` (parts per million), mg/m³ """ CO2 = "carbon_dioxide" @@ -469,7 +469,10 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = { NumberDeviceClass.ATMOSPHERIC_PRESSURE: set(UnitOfPressure), NumberDeviceClass.BATTERY: {PERCENTAGE}, NumberDeviceClass.BLOOD_GLUCOSE_CONCENTRATION: set(UnitOfBloodGlucoseConcentration), - NumberDeviceClass.CO: {CONCENTRATION_PARTS_PER_MILLION}, + NumberDeviceClass.CO: { + CONCENTRATION_PARTS_PER_MILLION, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + }, NumberDeviceClass.CO2: {CONCENTRATION_PARTS_PER_MILLION}, NumberDeviceClass.CONDUCTIVITY: set(UnitOfConductivity), NumberDeviceClass.CURRENT: set(UnitOfElectricCurrent), diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 2321da45bb9..c2a8a6c7607 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -46,6 +46,7 @@ from homeassistant.util.unit_conversion import ( AreaConverter, BaseUnitConverter, BloodGlucoseConcentrationConverter, + CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -204,6 +205,10 @@ STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = { **dict.fromkeys( MassVolumeConcentrationConverter.VALID_UNITS, MassVolumeConcentrationConverter ), + **dict.fromkeys( + CarbonMonoxideConcentrationConverter.VALID_UNITS, + CarbonMonoxideConcentrationConverter, + ), **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 4f798fb86d0..c65a11cee2a 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -19,6 +19,7 @@ from homeassistant.util.unit_conversion import ( ApparentPowerConverter, AreaConverter, BloodGlucoseConcentrationConverter, + CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -66,6 +67,9 @@ UNIT_SCHEMA = vol.Schema( vol.Optional("blood_glucose_concentration"): vol.In( BloodGlucoseConcentrationConverter.VALID_UNITS ), + vol.Optional("carbon_monoxide"): vol.In( + CarbonMonoxideConcentrationConverter.VALID_UNITS + ), vol.Optional("concentration"): vol.In( MassVolumeConcentrationConverter.VALID_UNITS ), diff --git a/homeassistant/components/sensor/const.py b/homeassistant/components/sensor/const.py index 7d21b68019d..098ac960fe8 100644 --- a/homeassistant/components/sensor/const.py +++ b/homeassistant/components/sensor/const.py @@ -51,6 +51,7 @@ from homeassistant.util.unit_conversion import ( AreaConverter, BaseUnitConverter, BloodGlucoseConcentrationConverter, + CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -156,7 +157,7 @@ class SensorDeviceClass(StrEnum): CO = "carbon_monoxide" """Carbon Monoxide gas concentration. - Unit of measurement: `ppm` (parts per million) + Unit of measurement: `ppm` (parts per million), `mg/m³` """ CO2 = "carbon_dioxide" @@ -537,6 +538,7 @@ UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] = SensorDeviceClass.AREA: AreaConverter, SensorDeviceClass.ATMOSPHERIC_PRESSURE: PressureConverter, SensorDeviceClass.BLOOD_GLUCOSE_CONCENTRATION: BloodGlucoseConcentrationConverter, + SensorDeviceClass.CO: CarbonMonoxideConcentrationConverter, SensorDeviceClass.CONDUCTIVITY: ConductivityConverter, SensorDeviceClass.CURRENT: ElectricCurrentConverter, SensorDeviceClass.DATA_RATE: DataRateConverter, @@ -578,7 +580,10 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = { SensorDeviceClass.ATMOSPHERIC_PRESSURE: set(UnitOfPressure), SensorDeviceClass.BATTERY: {PERCENTAGE}, SensorDeviceClass.BLOOD_GLUCOSE_CONCENTRATION: set(UnitOfBloodGlucoseConcentration), - SensorDeviceClass.CO: {CONCENTRATION_PARTS_PER_MILLION}, + SensorDeviceClass.CO: { + CONCENTRATION_PARTS_PER_MILLION, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + }, SensorDeviceClass.CO2: {CONCENTRATION_PARTS_PER_MILLION}, SensorDeviceClass.CONDUCTIVITY: set(UnitOfConductivity), SensorDeviceClass.CURRENT: set(UnitOfElectricCurrent), diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index f969a613a47..75e515cd95c 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -168,6 +168,20 @@ class BaseUnitConverter: return (from_unit in cls._UNIT_INVERSES) != (to_unit in cls._UNIT_INVERSES) +class CarbonMonoxideConcentrationConverter(BaseUnitConverter): + """Convert carbon monoxide ratio to mass per volume.""" + + UNIT_CLASS = "carbon_monoxide" + _UNIT_CONVERSION: dict[str | None, float] = { + CONCENTRATION_PARTS_PER_MILLION: 1, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER: 1.145609, + } + VALID_UNITS = { + CONCENTRATION_PARTS_PER_MILLION, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + } + + class DataRateConverter(BaseUnitConverter): """Utility to convert data rate values.""" diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index c31abe62826..84dcf7742d8 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -3007,7 +3007,6 @@ def test_device_class_converters_are_complete() -> None: no_converter_device_classes = { SensorDeviceClass.AQI, SensorDeviceClass.BATTERY, - SensorDeviceClass.CO, SensorDeviceClass.CO2, SensorDeviceClass.DATE, SensorDeviceClass.ENUM, diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index d9377779b68..0d14a30a1b8 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -44,6 +44,7 @@ from homeassistant.util.unit_conversion import ( AreaConverter, BaseUnitConverter, BloodGlucoseConcentrationConverter, + CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -78,6 +79,7 @@ _ALL_CONVERTERS: dict[type[BaseUnitConverter], list[str | None]] = { AreaConverter, BloodGlucoseConcentrationConverter, MassVolumeConcentrationConverter, + CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -114,6 +116,11 @@ _GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, flo UnitOfBloodGlucoseConcentration.MILLIMOLE_PER_LITER, 18, ), + CarbonMonoxideConcentrationConverter: ( + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_MILLION, + 1.145609, + ), ConductivityConverter: ( UnitOfConductivity.MICROSIEMENS_PER_CM, UnitOfConductivity.MILLISIEMENS_PER_CM, @@ -280,6 +287,20 @@ _CONVERTED_VALUE: dict[ UnitOfBloodGlucoseConcentration.MILLIGRAMS_PER_DECILITER, ), ], + CarbonMonoxideConcentrationConverter: [ + ( + 1, + CONCENTRATION_PARTS_PER_MILLION, + 1.145609, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + ), + ( + 120, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + 104.74778, + CONCENTRATION_PARTS_PER_MILLION, + ), + ], ConductivityConverter: [ # Deprecated to deprecated (5, UnitOfConductivity.SIEMENS, 5e3, UnitOfConductivity.MILLISIEMENS),