diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index f250c3f565e..634f8384c8c 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -17,7 +17,7 @@ from .const import DEFAULT_SETUP_TIMEOUT, DOMAIN, PRODUCT _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["cover", "sensor", "switch"] +PLATFORMS = ["cover", "sensor", "switch", "air_quality"] PARALLEL_UPDATES = 0 @@ -74,9 +74,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): @callback -def create_blebox_entities(product, async_add, entity_klass, entity_type): +def create_blebox_entities(hass, config_entry, async_add, entity_klass, entity_type): """Create entities from a BleBox product's features.""" + product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] + entities = [] if entity_type in product.features: for feature in product.features[entity_type]: diff --git a/homeassistant/components/blebox/air_quality.py b/homeassistant/components/blebox/air_quality.py new file mode 100644 index 00000000000..656f19109ea --- /dev/null +++ b/homeassistant/components/blebox/air_quality.py @@ -0,0 +1,36 @@ +"""BleBox air quality entity.""" + +from homeassistant.components.air_quality import AirQualityEntity + +from . import BleBoxEntity, create_blebox_entities + + +async def async_setup_entry(hass, config_entry, async_add): + """Set up a BleBox air quality entity.""" + create_blebox_entities( + hass, config_entry, async_add, BleBoxAirQualityEntity, "air_qualities" + ) + + +class BleBoxAirQualityEntity(BleBoxEntity, AirQualityEntity): + """Representation of a BleBox air quality feature.""" + + @property + def icon(self): + """Return the icon.""" + return "mdi:blur" + + @property + def particulate_matter_0_1(self): + """Return the particulate matter 0.1 level.""" + return self._feature.pm1 + + @property + def particulate_matter_2_5(self): + """Return the particulate matter 2.5 level.""" + return self._feature.pm2_5 + + @property + def particulate_matter_10(self): + """Return the particulate matter 10 level.""" + return self._feature.pm10 diff --git a/homeassistant/components/blebox/cover.py b/homeassistant/components/blebox/cover.py index 2a8f0219267..714b642e288 100644 --- a/homeassistant/components/blebox/cover.py +++ b/homeassistant/components/blebox/cover.py @@ -13,20 +13,13 @@ from homeassistant.components.cover import ( ) from . import BleBoxEntity, create_blebox_entities -from .const import ( - BLEBOX_TO_HASS_COVER_STATES, - BLEBOX_TO_HASS_DEVICE_CLASSES, - DOMAIN, - PRODUCT, -) +from .const import BLEBOX_TO_HASS_COVER_STATES, BLEBOX_TO_HASS_DEVICE_CLASSES async def async_setup_entry(hass, config_entry, async_add): """Set up a BleBox entry.""" - product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] - create_blebox_entities(product, async_add, BleBoxCoverEntity, "covers") - return True + create_blebox_entities(hass, config_entry, async_add, BleBoxCoverEntity, "covers") class BleBoxCoverEntity(BleBoxEntity, CoverEntity): diff --git a/homeassistant/components/blebox/sensor.py b/homeassistant/components/blebox/sensor.py index 7a7aa0bac8d..00ea4e82b9d 100644 --- a/homeassistant/components/blebox/sensor.py +++ b/homeassistant/components/blebox/sensor.py @@ -3,15 +3,13 @@ from homeassistant.helpers.entity import Entity from . import BleBoxEntity, create_blebox_entities -from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, BLEBOX_TO_UNIT_MAP, DOMAIN, PRODUCT +from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, BLEBOX_TO_UNIT_MAP async def async_setup_entry(hass, config_entry, async_add): """Set up a BleBox entry.""" - product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] - create_blebox_entities(product, async_add, BleBoxSensorEntity, "sensors") - return True + create_blebox_entities(hass, config_entry, async_add, BleBoxSensorEntity, "sensors") class BleBoxSensorEntity(BleBoxEntity, Entity): diff --git a/homeassistant/components/blebox/switch.py b/homeassistant/components/blebox/switch.py index 116ae1645f4..1e6f09a72a4 100644 --- a/homeassistant/components/blebox/switch.py +++ b/homeassistant/components/blebox/switch.py @@ -2,15 +2,14 @@ from homeassistant.components.switch import SwitchDevice from . import BleBoxEntity, create_blebox_entities -from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, DOMAIN, PRODUCT +from .const import BLEBOX_TO_HASS_DEVICE_CLASSES async def async_setup_entry(hass, config_entry, async_add): """Set up a BleBox switch entity.""" - - product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] - create_blebox_entities(product, async_add, BleBoxSwitchEntity, "switches") - return True + create_blebox_entities( + hass, config_entry, async_add, BleBoxSwitchEntity, "switches" + ) class BleBoxSwitchEntity(BleBoxEntity, SwitchDevice): diff --git a/tests/components/blebox/test_air_quality.py b/tests/components/blebox/test_air_quality.py new file mode 100644 index 00000000000..3467c94411c --- /dev/null +++ b/tests/components/blebox/test_air_quality.py @@ -0,0 +1,94 @@ +"""Blebox air_quality tests.""" + +import logging + +import blebox_uniapi +import pytest + +from homeassistant.components.air_quality import ATTR_PM_0_1, ATTR_PM_2_5, ATTR_PM_10 +from homeassistant.const import ATTR_ICON, STATE_UNKNOWN + +from .conftest import async_setup_entity, mock_feature + +from tests.async_mock import AsyncMock, PropertyMock + + +@pytest.fixture(name="airsensor") +def airsensor_fixture(): + """Return a default air quality fixture.""" + feature = mock_feature( + "air_qualities", + blebox_uniapi.air_quality.AirQuality, + unique_id="BleBox-airSensor-1afe34db9437-0.air", + full_name="airSensor-0.air", + device_class=None, + pm1=None, + pm2_5=None, + pm10=None, + ) + product = feature.product + type(product).name = PropertyMock(return_value="My air sensor") + type(product).model = PropertyMock(return_value="airSensor") + return (feature, "air_quality.airsensor_0_air") + + +async def test_init(airsensor, hass, config): + """Test airSensor default state.""" + + _, entity_id = airsensor + entry = await async_setup_entity(hass, config, entity_id) + assert entry.unique_id == "BleBox-airSensor-1afe34db9437-0.air" + + state = hass.states.get(entity_id) + assert state.name == "airSensor-0.air" + + assert ATTR_PM_0_1 not in state.attributes + assert ATTR_PM_2_5 not in state.attributes + assert ATTR_PM_10 not in state.attributes + + assert state.attributes[ATTR_ICON] == "mdi:blur" + + assert state.state == STATE_UNKNOWN + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + + assert device.name == "My air sensor" + assert device.identifiers == {("blebox", "abcd0123ef5678")} + assert device.manufacturer == "BleBox" + assert device.model == "airSensor" + assert device.sw_version == "1.23" + + +async def test_update(airsensor, hass, config): + """Test air quality sensor state after update.""" + + feature_mock, entity_id = airsensor + + def initial_update(): + feature_mock.pm1 = 49 + feature_mock.pm2_5 = 222 + feature_mock.pm10 = 333 + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + + state = hass.states.get(entity_id) + + assert state.attributes[ATTR_PM_0_1] == 49 + assert state.attributes[ATTR_PM_2_5] == 222 + assert state.attributes[ATTR_PM_10] == 333 + + assert state.state == "222" + + +async def test_update_failure(airsensor, hass, config, caplog): + """Test that update failures are logged.""" + + caplog.set_level(logging.ERROR) + + feature_mock, entity_id = airsensor + feature_mock.async_update = AsyncMock(side_effect=blebox_uniapi.error.ClientError) + await async_setup_entity(hass, config, entity_id) + + assert f"Updating '{feature_mock.full_name}' failed: " in caplog.text