From 0dec701acd0e707c6232e4517bcda0cc96e87fa2 Mon Sep 17 00:00:00 2001 From: bkobus-bbx Date: Thu, 11 Jun 2026 14:48:48 +0200 Subject: [PATCH] Add support for CO2Sensor to Blebox integration (#173507) --- homeassistant/components/blebox/const.py | 10 +++ homeassistant/components/blebox/icons.json | 3 + homeassistant/components/blebox/sensor.py | 16 +++- homeassistant/components/blebox/strings.json | 12 +++ tests/components/blebox/test_sensor.py | 77 +++++++++++++++++++- 5 files changed, 116 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/blebox/const.py b/homeassistant/components/blebox/const.py index ef264becbff..102c74e8158 100644 --- a/homeassistant/components/blebox/const.py +++ b/homeassistant/components/blebox/const.py @@ -24,3 +24,13 @@ OPEN_STATUS: dict[int, str] = { LIGHT_MAX_KELVINS = 6500 # 154 Mireds LIGHT_MIN_KELVINS = 2700 # 370 Mireds + +CO2_LEVEL: dict[int, str] = { + 0: "excellent", + 1: "good", + 2: "acceptable", + 3: "medium", + 4: "poor", + 5: "unhealthy", + 6: "hazardous", +} diff --git a/homeassistant/components/blebox/icons.json b/homeassistant/components/blebox/icons.json index 3c0f5123dbc..5e4dcb9081e 100644 --- a/homeassistant/components/blebox/icons.json +++ b/homeassistant/components/blebox/icons.json @@ -18,6 +18,9 @@ } }, "sensor": { + "co2_level": { + "default": "mdi:molecule-co2" + }, "open_status": { "default": "mdi:window-open" }, diff --git a/homeassistant/components/blebox/sensor.py b/homeassistant/components/blebox/sensor.py index d41fb6ff07e..8e1e561cfb0 100644 --- a/homeassistant/components/blebox/sensor.py +++ b/homeassistant/components/blebox/sensor.py @@ -14,6 +14,7 @@ from homeassistant.components.sensor import ( ) from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_MILLION, LIGHT_LUX, PERCENTAGE, UnitOfApparentPower, @@ -31,7 +32,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType from . import BleBoxConfigEntry -from .const import OPEN_STATUS +from .const import CO2_LEVEL, OPEN_STATUS from .coordinator import BleBoxCoordinator from .entity import BleBoxEntity @@ -149,6 +150,19 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = ( options=list(OPEN_STATUS.values()), value_fn=lambda v: OPEN_STATUS.get(int(v)) if v is not None else None, ), + BleBoxSensorEntityDescription( + key="co2", + device_class=SensorDeviceClass.CO2, + native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, + state_class=SensorStateClass.MEASUREMENT, + ), + BleBoxSensorEntityDescription( + key="co2Definition", + translation_key="co2_level", + device_class=SensorDeviceClass.ENUM, + options=list(CO2_LEVEL.values()), + value_fn=lambda v: CO2_LEVEL.get(int(v)) if v is not None else None, + ), ) diff --git a/homeassistant/components/blebox/strings.json b/homeassistant/components/blebox/strings.json index cbe6be653fe..42401674121 100644 --- a/homeassistant/components/blebox/strings.json +++ b/homeassistant/components/blebox/strings.json @@ -37,6 +37,18 @@ }, "entity": { "sensor": { + "co2_level": { + "name": "Carbon dioxide level", + "state": { + "acceptable": "Acceptable", + "excellent": "Excellent", + "good": "Good", + "hazardous": "Hazardous", + "medium": "Medium", + "poor": "Poor", + "unhealthy": "Unhealthy" + } + }, "open_status": { "state": { "ajar": "Ajar", diff --git a/tests/components/blebox/test_sensor.py b/tests/components/blebox/test_sensor.py index 0bce8658e6f..3ada992f6de 100644 --- a/tests/components/blebox/test_sensor.py +++ b/tests/components/blebox/test_sensor.py @@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, PropertyMock import blebox_uniapi import pytest -from homeassistant.components.blebox.const import OPEN_STATUS +from homeassistant.components.blebox.const import CO2_LEVEL, OPEN_STATUS from homeassistant.components.sensor import ATTR_OPTIONS, SensorDeviceClass from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( @@ -231,3 +231,78 @@ async def test_open_status_sensor_none_value( state = hass.states.get(entity_id) assert state.state == STATE_UNKNOWN + + +@pytest.fixture(name="co2_definition_sensor") +def co2_definition_sensor_fixture(): + """Return a default co2Definition sensor mock.""" + feature = mock_feature( + "sensors", + blebox_uniapi.sensor.GenericSensor, + unique_id="BleBox-co2Sensor-1afe34db9437-0.co2Definition", + full_name="co2Sensor-0.co2Definition", + device_class="co2Definition", + native_value=None, + ) + product = feature.product + type(product).name = PropertyMock(return_value="My CO2 sensor") + type(product).model = PropertyMock(return_value="co2Sensor") + return (feature, "sensor.my_co2_sensor_co2sensor_0_co2definition") + + +async def test_co2_definition_sensor_init( + co2_definition_sensor, hass: HomeAssistant +) -> None: + """Test co2Definition sensor initial state is unknown.""" + _, entity_id = co2_definition_sensor + await async_setup_entity(hass, entity_id) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.ENUM + assert state.attributes[ATTR_OPTIONS] == list(CO2_LEVEL.values()) + assert state.state == STATE_UNKNOWN + + +@pytest.mark.parametrize( + ("raw_value", "expected_state"), + [ + pytest.param(0, "excellent", id="0_excellent"), + pytest.param(1, "good", id="1_good"), + pytest.param(2, "acceptable", id="2_acceptable"), + pytest.param(3, "medium", id="3_medium"), + pytest.param(4, "poor", id="4_poor"), + pytest.param(5, "unhealthy", id="5_unhealthy"), + pytest.param(6, "hazardous", id="6_hazardous"), + ], +) +async def test_co2_definition_sensor_value_mapping( + co2_definition_sensor, + hass: HomeAssistant, + raw_value: int, + expected_state: str, +) -> None: + """Test that each raw co2Definition value maps to the correct string state.""" + feature_mock, entity_id = co2_definition_sensor + + feature_mock.native_value = raw_value + await async_setup_entity(hass, entity_id) + + state = hass.states.get(entity_id) + assert state.state == expected_state + assert state.state in CO2_LEVEL.values() + + +async def test_co2_definition_sensor_none_value( + co2_definition_sensor, hass: HomeAssistant +) -> None: + """Test that a None native_value yields an unknown state.""" + feature_mock, entity_id = co2_definition_sensor + + def set_none(): + feature_mock.native_value = None + + feature_mock.async_update = AsyncMock(side_effect=set_none) + await async_setup_entity(hass, entity_id) + + state = hass.states.get(entity_id) + assert state.state == STATE_UNKNOWN