From 79b5147b46a16b65404c74df5dd9a10ce16ea216 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Aug 2022 20:46:03 -0400 Subject: [PATCH] Awair local use config entry name + add measurement state class (#77383) --- homeassistant/components/awair/__init__.py | 11 ++++++++ homeassistant/components/awair/const.py | 15 ++++++++++- homeassistant/components/awair/sensor.py | 29 ++++++++-------------- tests/components/awair/__init__.py | 21 ++++++++++++++++ tests/components/awair/test_init.py | 26 +++++++++++++++++++ tests/components/awair/test_sensor.py | 16 ++---------- 6 files changed, 85 insertions(+), 33 deletions(-) create mode 100644 tests/components/awair/test_init.py diff --git a/homeassistant/components/awair/__init__.py b/homeassistant/components/awair/__init__.py index 359d0d6d853..fd964328c4d 100644 --- a/homeassistant/components/awair/__init__.py +++ b/homeassistant/components/awair/__init__.py @@ -37,6 +37,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b if CONF_HOST in config_entry.data: coordinator = AwairLocalDataUpdateCoordinator(hass, config_entry, session) + config_entry.async_on_unload( + config_entry.add_update_listener(_async_update_listener) + ) else: coordinator = AwairCloudDataUpdateCoordinator(hass, config_entry, session) @@ -50,6 +53,13 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b return True +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Handle options update.""" + coordinator: AwairLocalDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + if entry.title != coordinator.title: + await hass.config_entries.async_reload(entry.entry_id) + + async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload Awair configuration.""" unload_ok = await hass.config_entries.async_unload_platforms( @@ -73,6 +83,7 @@ class AwairDataUpdateCoordinator(DataUpdateCoordinator): ) -> None: """Set up the AwairDataUpdateCoordinator class.""" self._config_entry = config_entry + self.title = config_entry.title super().__init__(hass, LOGGER, name=DOMAIN, update_interval=update_interval) diff --git a/homeassistant/components/awair/const.py b/homeassistant/components/awair/const.py index 133cf03fdbe..4de912c9fd9 100644 --- a/homeassistant/components/awair/const.py +++ b/homeassistant/components/awair/const.py @@ -8,7 +8,11 @@ import logging from python_awair.air_data import AirData from python_awair.devices import AwairBaseDevice -from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntityDescription, + SensorStateClass, +) from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, @@ -61,6 +65,7 @@ SENSOR_TYPE_SCORE = AwairSensorEntityDescription( native_unit_of_measurement=PERCENTAGE, name="Awair score", unique_id_tag="score", # matches legacy format + state_class=SensorStateClass.MEASUREMENT, ) SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = ( @@ -70,6 +75,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = ( native_unit_of_measurement=PERCENTAGE, name="Humidity", unique_id_tag="HUMID", # matches legacy format + state_class=SensorStateClass.MEASUREMENT, ), AwairSensorEntityDescription( key=API_LUX, @@ -77,6 +83,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = ( native_unit_of_measurement=LIGHT_LUX, name="Illuminance", unique_id_tag="illuminance", + state_class=SensorStateClass.MEASUREMENT, ), AwairSensorEntityDescription( key=API_SPL_A, @@ -84,6 +91,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = ( native_unit_of_measurement=SOUND_PRESSURE_WEIGHTED_DBA, name="Sound level", unique_id_tag="sound_level", + state_class=SensorStateClass.MEASUREMENT, ), AwairSensorEntityDescription( key=API_VOC, @@ -91,6 +99,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = ( native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, name="Volatile organic compounds", unique_id_tag="VOC", # matches legacy format + state_class=SensorStateClass.MEASUREMENT, ), AwairSensorEntityDescription( key=API_TEMP, @@ -98,6 +107,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = ( native_unit_of_measurement=TEMP_CELSIUS, name="Temperature", unique_id_tag="TEMP", # matches legacy format + state_class=SensorStateClass.MEASUREMENT, ), AwairSensorEntityDescription( key=API_CO2, @@ -105,6 +115,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = ( native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, name="Carbon dioxide", unique_id_tag="CO2", # matches legacy format + state_class=SensorStateClass.MEASUREMENT, ), ) @@ -115,6 +126,7 @@ SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = ( native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, name="PM2.5", unique_id_tag="PM25", # matches legacy format + state_class=SensorStateClass.MEASUREMENT, ), AwairSensorEntityDescription( key=API_PM10, @@ -122,6 +134,7 @@ SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = ( native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, name="PM10", unique_id_tag="PM10", # matches legacy format + state_class=SensorStateClass.MEASUREMENT, ), ) diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 00d5c929409..18805154283 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -1,17 +1,14 @@ """Support for Awair sensors.""" from __future__ import annotations +from typing import cast + from python_awair.air_data import AirData from python_awair.devices import AwairBaseDevice, AwairLocalDevice from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_ATTRIBUTION, - ATTR_CONNECTIONS, - ATTR_NAME, - ATTR_SW_VERSION, -) +from homeassistant.const import ATTR_CONNECTIONS, ATTR_SW_VERSION from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo @@ -78,6 +75,8 @@ class AwairSensor(CoordinatorEntity[AwairDataUpdateCoordinator], SensorEntity): """Defines an Awair sensor entity.""" entity_description: AwairSensorEntityDescription + _attr_has_entity_name = True + _attr_attribution = ATTRIBUTION def __init__( self, @@ -90,14 +89,6 @@ class AwairSensor(CoordinatorEntity[AwairDataUpdateCoordinator], SensorEntity): self.entity_description = description self._device = device - @property - def name(self) -> str | None: - """Return the name of the sensor.""" - if self._device.name: - return f"{self._device.name} {self.entity_description.name}" - - return self.entity_description.name - @property def unique_id(self) -> str: """Return the uuid as the unique_id.""" @@ -187,7 +178,7 @@ class AwairSensor(CoordinatorEntity[AwairDataUpdateCoordinator], SensorEntity): https://docs.developer.getawair.com/?version=latest#awair-score-and-index """ sensor_type = self.entity_description.key - attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} + attrs: dict = {} if not self._air_data: return attrs if sensor_type in self._air_data.indices: @@ -204,11 +195,13 @@ class AwairSensor(CoordinatorEntity[AwairDataUpdateCoordinator], SensorEntity): identifiers={(DOMAIN, self._device.uuid)}, manufacturer="Awair", model=self._device.model, + name=( + self._device.name + or cast(ConfigEntry, self.coordinator.config_entry).title + or f"{self._device.model} ({self._device.device_id})" + ), ) - if self._device.name: - info[ATTR_NAME] = self._device.name - if self._device.mac_address: info[ATTR_CONNECTIONS] = { (dr.CONNECTION_NETWORK_MAC, self._device.mac_address) diff --git a/tests/components/awair/__init__.py b/tests/components/awair/__init__.py index 5331ae5492a..e8b93e47fd7 100644 --- a/tests/components/awair/__init__.py +++ b/tests/components/awair/__init__.py @@ -1 +1,22 @@ """Tests for the awair component.""" + + +from unittest.mock import patch + +from homeassistant.components.awair import DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def setup_awair(hass: HomeAssistant, fixtures, unique_id, data) -> ConfigEntry: + """Add Awair devices to hass, using specified fixtures for data.""" + + entry = MockConfigEntry(domain=DOMAIN, unique_id=unique_id, data=data) + with patch("python_awair.AwairClient.query", side_effect=fixtures): + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry diff --git a/tests/components/awair/test_init.py b/tests/components/awair/test_init.py new file mode 100644 index 00000000000..82a9f18597b --- /dev/null +++ b/tests/components/awair/test_init.py @@ -0,0 +1,26 @@ +"""Test Awair init.""" +from unittest.mock import patch + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr + +from . import setup_awair +from .const import LOCAL_CONFIG, LOCAL_UNIQUE_ID + + +async def test_local_awair_sensors(hass: HomeAssistant, local_devices, local_data): + """Test expected sensors on a local Awair.""" + fixtures = [local_devices, local_data] + entry = await setup_awair(hass, fixtures, LOCAL_UNIQUE_ID, LOCAL_CONFIG) + + dev_reg = dr.async_get(hass) + device_entry = dr.async_entries_for_config_entry(dev_reg, entry.entry_id)[0] + + assert device_entry.name == "Mock Title" + + with patch("python_awair.AwairClient.query", side_effect=fixtures): + hass.config_entries.async_update_entry(entry, title="Hello World") + await hass.async_block_till_done() + + device_entry = dev_reg.async_get(device_entry.id) + assert device_entry.name == "Hello World" diff --git a/tests/components/awair/test_sensor.py b/tests/components/awair/test_sensor.py index 87b931a3f7f..1a17f812d4d 100644 --- a/tests/components/awair/test_sensor.py +++ b/tests/components/awair/test_sensor.py @@ -11,7 +11,6 @@ from homeassistant.components.awair.const import ( API_SPL_A, API_TEMP, API_VOC, - DOMAIN, SENSOR_TYPE_SCORE, SENSOR_TYPES, SENSOR_TYPES_DUST, @@ -30,6 +29,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_component import async_update_entity +from . import setup_awair from .const import ( AWAIR_UUID, CLOUD_CONFIG, @@ -38,23 +38,11 @@ from .const import ( LOCAL_UNIQUE_ID, ) -from tests.common import MockConfigEntry - SENSOR_TYPES_MAP = { desc.key: desc for desc in (SENSOR_TYPE_SCORE, *SENSOR_TYPES, *SENSOR_TYPES_DUST) } -async def setup_awair(hass: HomeAssistant, fixtures, unique_id, data): - """Add Awair devices to hass, using specified fixtures for data.""" - - entry = MockConfigEntry(domain=DOMAIN, unique_id=unique_id, data=data) - with patch("python_awair.AwairClient.query", side_effect=fixtures): - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - def assert_expected_properties( hass: HomeAssistant, registry: er.RegistryEntry, @@ -209,7 +197,7 @@ async def test_local_awair_sensors(hass: HomeAssistant, local_devices, local_dat assert_expected_properties( hass, registry, - "sensor.awair_score", + "sensor.mock_title_awair_score", f"{local_devices['device_uuid']}_{SENSOR_TYPES_MAP[API_SCORE].unique_id_tag}", "94", {},