diff --git a/homeassistant/components/climacell/__init__.py b/homeassistant/components/climacell/__init__.py index 9fd3e0b8340..97090cef31d 100644 --- a/homeassistant/components/climacell/__init__.py +++ b/homeassistant/components/climacell/__init__.py @@ -222,7 +222,7 @@ class ClimaCellDataUpdateCoordinator(DataUpdateCoordinator): CC_V3_ATTR_WIND_GUST, CC_V3_ATTR_CLOUD_COVER, CC_V3_ATTR_PRECIPITATION_TYPE, - *(sensor_type.field for sensor_type in CC_V3_SENSOR_TYPES), + *(sensor_type.key for sensor_type in CC_V3_SENSOR_TYPES), ] ) data[FORECASTS][HOURLY] = await self._api.forecast_hourly( @@ -279,7 +279,7 @@ class ClimaCellDataUpdateCoordinator(DataUpdateCoordinator): CC_ATTR_WIND_GUST, CC_ATTR_CLOUD_COVER, CC_ATTR_PRECIPITATION_TYPE, - *(sensor_type.field for sensor_type in CC_SENSOR_TYPES), + *(sensor_type.key for sensor_type in CC_SENSOR_TYPES), ], [ CC_ATTR_TEMPERATURE_LOW, diff --git a/homeassistant/components/climacell/const.py b/homeassistant/components/climacell/const.py index c11c0b1774b..bcbd139028a 100644 --- a/homeassistant/components/climacell/const.py +++ b/homeassistant/components/climacell/const.py @@ -17,6 +17,7 @@ from pyclimacell.const import ( WeatherCode, ) +from homeassistant.components.sensor import SensorEntityDescription from homeassistant.components.weather import ( ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLOUDY, @@ -151,11 +152,9 @@ CC_ATTR_CLOUD_CEILING = "cloudCeiling" @dataclass -class ClimaCellSensorMetadata: - """Metadata about an individual ClimaCell sensor.""" +class ClimaCellSensorEntityDescription(SensorEntityDescription): + """Describes a ClimaCell sensor entity.""" - field: str - name: str unit_imperial: str | None = None unit_metric: str | None = None metric_conversion: Callable[[float], float] | float = 1.0 @@ -171,30 +170,33 @@ class ClimaCellSensorMetadata: "`unit_imperial` and `unit_metric` both need to be None or both need " "to be defined." ) + if self.name is None: # pragma: no cover + raise TypeError + self.name_ = self.name CC_SENSOR_TYPES = ( - ClimaCellSensorMetadata( - CC_ATTR_FEELS_LIKE, - "Feels Like", + ClimaCellSensorEntityDescription( + key=CC_ATTR_FEELS_LIKE, + name="Feels Like", unit_imperial=TEMP_FAHRENHEIT, unit_metric=TEMP_CELSIUS, metric_conversion=lambda val: temp_convert(val, TEMP_FAHRENHEIT, TEMP_CELSIUS), is_metric_check=True, device_class=DEVICE_CLASS_TEMPERATURE, ), - ClimaCellSensorMetadata( - CC_ATTR_DEW_POINT, - "Dew Point", + ClimaCellSensorEntityDescription( + key=CC_ATTR_DEW_POINT, + name="Dew Point", unit_imperial=TEMP_FAHRENHEIT, unit_metric=TEMP_CELSIUS, metric_conversion=lambda val: temp_convert(val, TEMP_FAHRENHEIT, TEMP_CELSIUS), is_metric_check=True, device_class=DEVICE_CLASS_TEMPERATURE, ), - ClimaCellSensorMetadata( - CC_ATTR_PRESSURE_SURFACE_LEVEL, - "Pressure (Surface Level)", + ClimaCellSensorEntityDescription( + key=CC_ATTR_PRESSURE_SURFACE_LEVEL, + name="Pressure (Surface Level)", unit_imperial=PRESSURE_INHG, unit_metric=PRESSURE_HPA, metric_conversion=lambda val: pressure_convert( @@ -203,17 +205,17 @@ CC_SENSOR_TYPES = ( is_metric_check=True, device_class=DEVICE_CLASS_PRESSURE, ), - ClimaCellSensorMetadata( - CC_ATTR_SOLAR_GHI, - "Global Horizontal Irradiance", + ClimaCellSensorEntityDescription( + key=CC_ATTR_SOLAR_GHI, + name="Global Horizontal Irradiance", unit_imperial=IRRADIATION_BTUS_PER_HOUR_SQUARE_FOOT, unit_metric=IRRADIATION_WATTS_PER_SQUARE_METER, metric_conversion=3.15459, is_metric_check=True, ), - ClimaCellSensorMetadata( - CC_ATTR_CLOUD_BASE, - "Cloud Base", + ClimaCellSensorEntityDescription( + key=CC_ATTR_CLOUD_BASE, + name="Cloud Base", unit_imperial=LENGTH_MILES, unit_metric=LENGTH_KILOMETERS, metric_conversion=lambda val: distance_convert( @@ -221,9 +223,9 @@ CC_SENSOR_TYPES = ( ), is_metric_check=True, ), - ClimaCellSensorMetadata( - CC_ATTR_CLOUD_CEILING, - "Cloud Ceiling", + ClimaCellSensorEntityDescription( + key=CC_ATTR_CLOUD_CEILING, + name="Cloud Ceiling", unit_imperial=LENGTH_MILES, unit_metric=LENGTH_KILOMETERS, metric_conversion=lambda val: distance_convert( @@ -231,99 +233,114 @@ CC_SENSOR_TYPES = ( ), is_metric_check=True, ), - ClimaCellSensorMetadata( - CC_ATTR_CLOUD_COVER, - "Cloud Cover", + ClimaCellSensorEntityDescription( + key=CC_ATTR_CLOUD_COVER, + name="Cloud Cover", unit_imperial=PERCENTAGE, unit_metric=PERCENTAGE, ), - ClimaCellSensorMetadata( - CC_ATTR_WIND_GUST, - "Wind Gust", + ClimaCellSensorEntityDescription( + key=CC_ATTR_WIND_GUST, + name="Wind Gust", unit_imperial=SPEED_MILES_PER_HOUR, unit_metric=SPEED_METERS_PER_SECOND, metric_conversion=lambda val: distance_convert(val, LENGTH_MILES, LENGTH_METERS) / 3600, is_metric_check=True, ), - ClimaCellSensorMetadata( - CC_ATTR_PRECIPITATION_TYPE, - "Precipitation Type", + ClimaCellSensorEntityDescription( + key=CC_ATTR_PRECIPITATION_TYPE, + name="Precipitation Type", value_map=PrecipitationType, ), - ClimaCellSensorMetadata( - CC_ATTR_OZONE, - "Ozone", + ClimaCellSensorEntityDescription( + key=CC_ATTR_OZONE, + name="Ozone", unit_imperial=CONCENTRATION_PARTS_PER_BILLION, unit_metric=CONCENTRATION_PARTS_PER_BILLION, ), - ClimaCellSensorMetadata( - CC_ATTR_PARTICULATE_MATTER_25, - "Particulate Matter < 2.5 μm", + ClimaCellSensorEntityDescription( + key=CC_ATTR_PARTICULATE_MATTER_25, + name="Particulate Matter < 2.5 μm", unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, metric_conversion=3.2808399 ** 3, is_metric_check=True, ), - ClimaCellSensorMetadata( - CC_ATTR_PARTICULATE_MATTER_10, - "Particulate Matter < 10 μm", + ClimaCellSensorEntityDescription( + key=CC_ATTR_PARTICULATE_MATTER_10, + name="Particulate Matter < 10 μm", unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, metric_conversion=3.2808399 ** 3, is_metric_check=True, ), - ClimaCellSensorMetadata( - CC_ATTR_NITROGEN_DIOXIDE, - "Nitrogen Dioxide", + ClimaCellSensorEntityDescription( + key=CC_ATTR_NITROGEN_DIOXIDE, + name="Nitrogen Dioxide", unit_imperial=CONCENTRATION_PARTS_PER_BILLION, unit_metric=CONCENTRATION_PARTS_PER_BILLION, ), - ClimaCellSensorMetadata( - CC_ATTR_CARBON_MONOXIDE, - "Carbon Monoxide", + ClimaCellSensorEntityDescription( + key=CC_ATTR_CARBON_MONOXIDE, + name="Carbon Monoxide", unit_imperial=CONCENTRATION_PARTS_PER_MILLION, unit_metric=CONCENTRATION_PARTS_PER_MILLION, device_class=DEVICE_CLASS_CO, ), - ClimaCellSensorMetadata( - CC_ATTR_SULFUR_DIOXIDE, - "Sulfur Dioxide", + ClimaCellSensorEntityDescription( + key=CC_ATTR_SULFUR_DIOXIDE, + name="Sulfur Dioxide", unit_imperial=CONCENTRATION_PARTS_PER_BILLION, unit_metric=CONCENTRATION_PARTS_PER_BILLION, ), - ClimaCellSensorMetadata(CC_ATTR_EPA_AQI, "US EPA Air Quality Index"), - ClimaCellSensorMetadata( - CC_ATTR_EPA_PRIMARY_POLLUTANT, - "US EPA Primary Pollutant", + ClimaCellSensorEntityDescription( + key=CC_ATTR_EPA_AQI, + name="US EPA Air Quality Index", + ), + ClimaCellSensorEntityDescription( + key=CC_ATTR_EPA_PRIMARY_POLLUTANT, + name="US EPA Primary Pollutant", value_map=PrimaryPollutantType, ), - ClimaCellSensorMetadata( - CC_ATTR_EPA_HEALTH_CONCERN, - "US EPA Health Concern", + ClimaCellSensorEntityDescription( + key=CC_ATTR_EPA_HEALTH_CONCERN, + name="US EPA Health Concern", value_map=HealthConcernType, ), - ClimaCellSensorMetadata(CC_ATTR_CHINA_AQI, "China MEP Air Quality Index"), - ClimaCellSensorMetadata( - CC_ATTR_CHINA_PRIMARY_POLLUTANT, - "China MEP Primary Pollutant", + ClimaCellSensorEntityDescription( + key=CC_ATTR_CHINA_AQI, + name="China MEP Air Quality Index", + ), + ClimaCellSensorEntityDescription( + key=CC_ATTR_CHINA_PRIMARY_POLLUTANT, + name="China MEP Primary Pollutant", value_map=PrimaryPollutantType, ), - ClimaCellSensorMetadata( - CC_ATTR_CHINA_HEALTH_CONCERN, - "China MEP Health Concern", + ClimaCellSensorEntityDescription( + key=CC_ATTR_CHINA_HEALTH_CONCERN, + name="China MEP Health Concern", value_map=HealthConcernType, ), - ClimaCellSensorMetadata( - CC_ATTR_POLLEN_TREE, "Tree Pollen Index", value_map=PollenIndex + ClimaCellSensorEntityDescription( + key=CC_ATTR_POLLEN_TREE, + name="Tree Pollen Index", + value_map=PollenIndex, ), - ClimaCellSensorMetadata( - CC_ATTR_POLLEN_WEED, "Weed Pollen Index", value_map=PollenIndex + ClimaCellSensorEntityDescription( + key=CC_ATTR_POLLEN_WEED, + name="Weed Pollen Index", + value_map=PollenIndex, ), - ClimaCellSensorMetadata( - CC_ATTR_POLLEN_GRASS, "Grass Pollen Index", value_map=PollenIndex + ClimaCellSensorEntityDescription( + key=CC_ATTR_POLLEN_GRASS, + name="Grass Pollen Index", + value_map=PollenIndex, + ), + ClimaCellSensorEntityDescription( + CC_ATTR_FIRE_INDEX, + name="Fire Index", ), - ClimaCellSensorMetadata(CC_ATTR_FIRE_INDEX, "Fire Index"), ) # V3 constants @@ -389,67 +406,88 @@ CC_V3_ATTR_POLLEN_GRASS = "pollen_grass" CC_V3_ATTR_FIRE_INDEX = "fire_index" CC_V3_SENSOR_TYPES = ( - ClimaCellSensorMetadata( - CC_V3_ATTR_OZONE, - "Ozone", + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_OZONE, + name="Ozone", unit_imperial=CONCENTRATION_PARTS_PER_BILLION, unit_metric=CONCENTRATION_PARTS_PER_BILLION, ), - ClimaCellSensorMetadata( - CC_V3_ATTR_PARTICULATE_MATTER_25, - "Particulate Matter < 2.5 μm", + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_PARTICULATE_MATTER_25, + name="Particulate Matter < 2.5 μm", unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, metric_conversion=3.2808399 ** 3, is_metric_check=False, ), - ClimaCellSensorMetadata( - CC_V3_ATTR_PARTICULATE_MATTER_10, - "Particulate Matter < 10 μm", + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_PARTICULATE_MATTER_10, + name="Particulate Matter < 10 μm", unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, metric_conversion=3.2808399 ** 3, is_metric_check=False, ), - ClimaCellSensorMetadata( - CC_V3_ATTR_NITROGEN_DIOXIDE, - "Nitrogen Dioxide", + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_NITROGEN_DIOXIDE, + name="Nitrogen Dioxide", unit_imperial=CONCENTRATION_PARTS_PER_BILLION, unit_metric=CONCENTRATION_PARTS_PER_BILLION, ), - ClimaCellSensorMetadata( - CC_V3_ATTR_CARBON_MONOXIDE, - "Carbon Monoxide", + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_CARBON_MONOXIDE, + name="Carbon Monoxide", unit_imperial=CONCENTRATION_PARTS_PER_MILLION, unit_metric=CONCENTRATION_PARTS_PER_MILLION, device_class=DEVICE_CLASS_CO, ), - ClimaCellSensorMetadata( - CC_V3_ATTR_SULFUR_DIOXIDE, - "Sulfur Dioxide", + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_SULFUR_DIOXIDE, + name="Sulfur Dioxide", unit_imperial=CONCENTRATION_PARTS_PER_BILLION, unit_metric=CONCENTRATION_PARTS_PER_BILLION, ), - ClimaCellSensorMetadata(CC_V3_ATTR_EPA_AQI, "US EPA Air Quality Index"), - ClimaCellSensorMetadata( - CC_V3_ATTR_EPA_PRIMARY_POLLUTANT, "US EPA Primary Pollutant" + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_EPA_AQI, + name="US EPA Air Quality Index", ), - ClimaCellSensorMetadata(CC_V3_ATTR_EPA_HEALTH_CONCERN, "US EPA Health Concern"), - ClimaCellSensorMetadata(CC_V3_ATTR_CHINA_AQI, "China MEP Air Quality Index"), - ClimaCellSensorMetadata( - CC_V3_ATTR_CHINA_PRIMARY_POLLUTANT, "China MEP Primary Pollutant" + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_EPA_PRIMARY_POLLUTANT, + name="US EPA Primary Pollutant", ), - ClimaCellSensorMetadata( - CC_V3_ATTR_CHINA_HEALTH_CONCERN, "China MEP Health Concern" + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_EPA_HEALTH_CONCERN, + name="US EPA Health Concern", ), - ClimaCellSensorMetadata( - CC_V3_ATTR_POLLEN_TREE, "Tree Pollen Index", value_map=V3PollenIndex + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_CHINA_AQI, + name="China MEP Air Quality Index", ), - ClimaCellSensorMetadata( - CC_V3_ATTR_POLLEN_WEED, "Weed Pollen Index", value_map=V3PollenIndex + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_CHINA_PRIMARY_POLLUTANT, + name="China MEP Primary Pollutant", ), - ClimaCellSensorMetadata( - CC_V3_ATTR_POLLEN_GRASS, "Grass Pollen Index", value_map=V3PollenIndex + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_CHINA_HEALTH_CONCERN, + name="China MEP Health Concern", + ), + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_POLLEN_TREE, + name="Tree Pollen Index", + value_map=V3PollenIndex, + ), + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_POLLEN_WEED, + name="Weed Pollen Index", + value_map=V3PollenIndex, + ), + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_POLLEN_GRASS, + name="Grass Pollen Index", + value_map=V3PollenIndex, + ), + ClimaCellSensorEntityDescription( + key=CC_V3_ATTR_FIRE_INDEX, + name="Fire Index", ), - ClimaCellSensorMetadata(CC_V3_ATTR_FIRE_INDEX, "Fire Index"), ) diff --git a/homeassistant/components/climacell/sensor.py b/homeassistant/components/climacell/sensor.py index 77fa6486013..d67cde18e0b 100644 --- a/homeassistant/components/climacell/sensor.py +++ b/homeassistant/components/climacell/sensor.py @@ -14,7 +14,12 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import slugify from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity -from .const import CC_SENSOR_TYPES, CC_V3_SENSOR_TYPES, DOMAIN, ClimaCellSensorMetadata +from .const import ( + CC_SENSOR_TYPES, + CC_V3_SENSOR_TYPES, + DOMAIN, + ClimaCellSensorEntityDescription, +) _LOGGER = logging.getLogger(__name__) @@ -35,8 +40,8 @@ async def async_setup_entry( api_class = ClimaCellSensorEntity sensor_types = CC_SENSOR_TYPES entities = [ - api_class(hass, config_entry, coordinator, api_version, sensor_type) - for sensor_type in sensor_types + api_class(hass, config_entry, coordinator, api_version, description) + for description in sensor_types ] async_add_entities(entities) @@ -44,30 +49,29 @@ async def async_setup_entry( class BaseClimaCellSensorEntity(ClimaCellEntity, SensorEntity): """Base ClimaCell sensor entity.""" + entity_description: ClimaCellSensorEntityDescription + def __init__( self, hass: HomeAssistant, config_entry: ConfigEntry, coordinator: ClimaCellDataUpdateCoordinator, api_version: int, - sensor_type: ClimaCellSensorMetadata, + description: ClimaCellSensorEntityDescription, ) -> None: """Initialize ClimaCell Sensor Entity.""" super().__init__(config_entry, coordinator, api_version) - self.sensor_type = sensor_type - self._attr_device_class = self.sensor_type.device_class + self.entity_description = description self._attr_entity_registry_enabled_default = False - self._attr_name = ( - f"{self._config_entry.data[CONF_NAME]} - {self.sensor_type.name}" - ) + self._attr_name = f"{self._config_entry.data[CONF_NAME]} - {description.name}" self._attr_unique_id = ( - f"{self._config_entry.unique_id}_{slugify(self.sensor_type.name)}" + f"{self._config_entry.unique_id}_{slugify(description.name_)}" ) self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: self.attribution} self._attr_unit_of_measurement = ( - self.sensor_type.unit_metric + description.unit_metric if hass.config.units.is_metric - else self.sensor_type.unit_imperial + else description.unit_imperial ) @property @@ -81,20 +85,21 @@ class BaseClimaCellSensorEntity(ClimaCellEntity, SensorEntity): state = self._state if ( state is not None - and self.sensor_type.unit_imperial is not None - and self.sensor_type.metric_conversion != 1.0 - and self.sensor_type.is_metric_check is not None - and self.hass.config.units.is_metric == self.sensor_type.is_metric_check + and self.entity_description.unit_imperial is not None + and self.entity_description.metric_conversion != 1.0 + and self.entity_description.is_metric_check is not None + and self.hass.config.units.is_metric + == self.entity_description.is_metric_check ): - conversion = self.sensor_type.metric_conversion + conversion = self.entity_description.metric_conversion # When conversion is a callable, we assume it's a single input function if callable(conversion): return round(conversion(state), 4) return round(state * conversion, 4) - if self.sensor_type.value_map is not None and state is not None: - return self.sensor_type.value_map(state).name.lower() + if self.entity_description.value_map is not None and state is not None: + return self.entity_description.value_map(state).name.lower() return state @@ -105,7 +110,7 @@ class ClimaCellSensorEntity(BaseClimaCellSensorEntity): @property def _state(self) -> str | int | float | None: """Return the raw state.""" - return self._get_current_property(self.sensor_type.field) + return self._get_current_property(self.entity_description.key) class ClimaCellV3SensorEntity(BaseClimaCellSensorEntity): @@ -115,5 +120,5 @@ class ClimaCellV3SensorEntity(BaseClimaCellSensorEntity): def _state(self) -> str | int | float | None: """Return the raw state.""" return self._get_cc_value( - self.coordinator.data[CURRENT], self.sensor_type.field + self.coordinator.data[CURRENT], self.entity_description.key ) diff --git a/tests/components/climacell/test_const.py b/tests/components/climacell/test_const.py index ba8eb6d8f39..2719426a7a0 100644 --- a/tests/components/climacell/test_const.py +++ b/tests/components/climacell/test_const.py @@ -1,12 +1,14 @@ """Tests for ClimaCell const.""" import pytest -from homeassistant.components.climacell.const import ClimaCellSensorMetadata +from homeassistant.components.climacell.const import ClimaCellSensorEntityDescription from homeassistant.const import TEMP_FAHRENHEIT async def test_post_init(): - """Test post initiailization check for ClimaCellSensorMetadata.""" + """Test post initiailization check for ClimaCellSensorEntityDescription.""" with pytest.raises(RuntimeError): - ClimaCellSensorMetadata("a", "b", unit_imperial=TEMP_FAHRENHEIT) + ClimaCellSensorEntityDescription( + key="a", name="b", unit_imperial=TEMP_FAHRENHEIT + )