diff --git a/homeassistant/components/number/const.py b/homeassistant/components/number/const.py index ec60462351758..402592888a2f6 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 @@ class NumberDeviceClass(StrEnum): 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 2321da45bb9a7..c2a8a6c7607c5 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -46,6 +46,7 @@ AreaConverter, BaseUnitConverter, BloodGlucoseConcentrationConverter, + CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -204,6 +205,10 @@ def query_circular_mean(table: type[StatisticsBase]) -> tuple[Label, Label]: **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 4f798fb86d01e..c65a11cee2ae5 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -19,6 +19,7 @@ ApparentPowerConverter, AreaConverter, BloodGlucoseConcentrationConverter, + CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -66,6 +67,9 @@ 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 7d21b68019d03..098ac960fe89e 100644 --- a/homeassistant/components/sensor/const.py +++ b/homeassistant/components/sensor/const.py @@ -51,6 +51,7 @@ 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 @@ class SensorStateClass(StrEnum): 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 @@ class SensorStateClass(StrEnum): 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 f969a613a4786..75e515cd95c5d 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -168,6 +168,20 @@ def _are_unit_inverses(cls, from_unit: str | None, to_unit: str | None) -> bool: 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 c31abe62826a9..84dcf7742d873 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 d9377779b68e8..0d14a30a1b87b 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -44,6 +44,7 @@ AreaConverter, BaseUnitConverter, BloodGlucoseConcentrationConverter, + CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -78,6 +79,7 @@ AreaConverter, BloodGlucoseConcentrationConverter, MassVolumeConcentrationConverter, + CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -114,6 +116,11 @@ 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 @@ 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),