diff --git a/homeassistant/components/rabbitair/__init__.py b/homeassistant/components/rabbitair/__init__.py index b50a7bf213fc73..568a557f51b2db 100644 --- a/homeassistant/components/rabbitair/__init__.py +++ b/homeassistant/components/rabbitair/__init__.py @@ -8,7 +8,7 @@ from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator -PLATFORMS: list[Platform] = [Platform.FAN] +PLATFORMS: list[Platform] = [Platform.FAN, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> bool: diff --git a/homeassistant/components/rabbitair/entity.py b/homeassistant/components/rabbitair/entity.py index c95bbfa40d3919..2550d6eb220b8a 100644 --- a/homeassistant/components/rabbitair/entity.py +++ b/homeassistant/components/rabbitair/entity.py @@ -25,6 +25,8 @@ class RabbitAirBaseEntity(CoordinatorEntity[RabbitAirDataUpdateCoordinator]): """Base class for Rabbit Air entity.""" + _attr_has_entity_name = True + def __init__( self, coordinator: RabbitAirDataUpdateCoordinator, @@ -32,7 +34,6 @@ def __init__( ) -> None: """Initialize the entity.""" super().__init__(coordinator) - self._attr_name = entry.title self._attr_unique_id = entry.unique_id self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, entry.data[CONF_MAC])}, diff --git a/homeassistant/components/rabbitair/fan.py b/homeassistant/components/rabbitair/fan.py index 99aa80dae00792..4f40aab49d1b7d 100644 --- a/homeassistant/components/rabbitair/fan.py +++ b/homeassistant/components/rabbitair/fan.py @@ -46,6 +46,7 @@ async def async_setup_entry( class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity): """Fan control functions of the Rabbit Air air purifier.""" + _attr_name = None _attr_translation_key = "rabbitair" _attr_supported_features = ( FanEntityFeature.PRESET_MODE diff --git a/homeassistant/components/rabbitair/icons.json b/homeassistant/components/rabbitair/icons.json index 5ee0b6eccf138f..ea9db3760acecc 100644 --- a/homeassistant/components/rabbitair/icons.json +++ b/homeassistant/components/rabbitair/icons.json @@ -12,6 +12,11 @@ } } } + }, + "sensor": { + "air_quality": { + "default": "mdi:air-filter" + } } } } diff --git a/homeassistant/components/rabbitair/sensor.py b/homeassistant/components/rabbitair/sensor.py new file mode 100644 index 00000000000000..7b0885156aab06 --- /dev/null +++ b/homeassistant/components/rabbitair/sensor.py @@ -0,0 +1,60 @@ +"""Support for Rabbit Air sensors.""" + +from rabbitair import Quality + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.typing import StateType + +from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator +from .entity import RabbitAirBaseEntity + + +def _quality_value(quality: Quality | None) -> StateType: + """Return the air quality state.""" + return None if quality is None else quality.name.lower() + + +AIR_QUALITY_OPTIONS = [quality.name.lower() for quality in Quality] + +AIR_QUALITY_DESCRIPTION = SensorEntityDescription( + key="air_quality", + translation_key="air_quality", + device_class=SensorDeviceClass.ENUM, + options=AIR_QUALITY_OPTIONS, +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: RabbitAirConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up Rabbit Air sensors.""" + if entry.runtime_data.data.quality is not None: + async_add_entities([RabbitAirAirQualitySensor(entry.runtime_data, entry)]) + + +class RabbitAirAirQualitySensor(RabbitAirBaseEntity, SensorEntity): + """Rabbit Air air quality sensor.""" + + entity_description = AIR_QUALITY_DESCRIPTION + + def __init__( + self, + coordinator: RabbitAirDataUpdateCoordinator, + entry: RabbitAirConfigEntry, + ) -> None: + """Initialize the entity.""" + super().__init__(coordinator, entry) + self._attr_unique_id = f"{entry.unique_id}_{self.entity_description.key}" + + @property + def native_value(self) -> StateType: + """Return the air quality state.""" + return _quality_value(self.coordinator.data.quality) diff --git a/homeassistant/components/rabbitair/strings.json b/homeassistant/components/rabbitair/strings.json index d24af81560b56f..9003c021ffa336 100644 --- a/homeassistant/components/rabbitair/strings.json +++ b/homeassistant/components/rabbitair/strings.json @@ -32,6 +32,18 @@ } } } + }, + "sensor": { + "air_quality": { + "name": "Air quality", + "state": { + "high": "[%key:common::state::high%]", + "highest": "Highest", + "low": "[%key:common::state::low%]", + "lowest": "Lowest", + "medium": "[%key:common::state::medium%]" + } + } } } } diff --git a/tests/components/rabbitair/test_config_flow.py b/tests/components/rabbitair/test_config_flow.py index 293517e0828c75..fb8affa8684c94 100644 --- a/tests/components/rabbitair/test_config_flow.py +++ b/tests/components/rabbitair/test_config_flow.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock, Mock, patch import pytest -from rabbitair import Mode, Model, Speed +from rabbitair import Mode, Model, Quality, Speed from homeassistant import config_entries from homeassistant.components.rabbitair.const import DOMAIN @@ -62,6 +62,7 @@ def get_mock_state( main_firmware: str | None = TEST_HARDWARE, power: bool | None = True, mode: Mode | None = Mode.Auto, + quality: Quality | None = Quality.High, speed: Speed | None = Speed.Low, wifi_firmware: str | None = TEST_FIRMWARE, ) -> Mock: @@ -71,6 +72,7 @@ def get_mock_state( mock_state.main_firmware = main_firmware mock_state.power = power mock_state.mode = mode + mock_state.quality = quality mock_state.speed = speed mock_state.wifi_firmware = wifi_firmware return mock_state diff --git a/tests/components/rabbitair/test_sensor.py b/tests/components/rabbitair/test_sensor.py new file mode 100644 index 00000000000000..7c6d99e3f8412a --- /dev/null +++ b/tests/components/rabbitair/test_sensor.py @@ -0,0 +1,77 @@ +"""Test the Rabbit Air sensor platform.""" + +from unittest.mock import patch + +import pytest +from rabbitair import Quality + +from homeassistant.components.rabbitair.const import DOMAIN +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_MAC +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .test_config_flow import ( + TEST_HOST, + TEST_MAC, + TEST_TITLE, + TEST_TOKEN, + TEST_UNIQUE_ID, + get_mock_state, +) + +from tests.common import MockConfigEntry + +pytestmark = pytest.mark.usefixtures("mock_async_zeroconf") + + +@pytest.fixture +def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry: + """Return a mock config entry.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: TEST_HOST, + CONF_ACCESS_TOKEN: TEST_TOKEN, + CONF_MAC: TEST_MAC, + }, + title=TEST_TITLE, + unique_id=TEST_UNIQUE_ID, + ) + entry.add_to_hass(hass) + return entry + + +async def test_air_quality_sensor( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the air quality sensor.""" + with patch( + "homeassistant.components.rabbitair.coordinator.Client.get_state", + return_value=get_mock_state(quality=Quality.High), + ): + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("sensor.rabbit_air_air_quality") + assert state + assert state.state == "high" + + registry_entry = entity_registry.async_get("sensor.rabbit_air_air_quality") + assert registry_entry + assert registry_entry.unique_id == f"{TEST_UNIQUE_ID}_air_quality" + + +async def test_no_air_quality_sensor_when_quality_is_none( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: + """Test the air quality sensor is not created when quality is unavailable.""" + with patch( + "homeassistant.components.rabbitair.coordinator.Client.get_state", + return_value=get_mock_state(quality=None), + ): + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get("sensor.rabbit_air_air_quality") is None