Switch to dataclass from dictionary for climacell sensor definitions (#53168)

* Switch to dataclass from dictionary for climacell sensor definitions

* fix post_init

* fix dataclass and add test

* Update homeassistant/components/climacell/sensor.py

Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>

* Update homeassistant/components/climacell/const.py

Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>

* simplify logic

* use tuple

* simplify unit of measurement and use class attributes

* Switch from UnitT to str

Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
This commit is contained in:
Raman Gupta 2021-07-20 00:22:41 -04:00 committed by GitHub
parent 7711ac901c
commit 562aa74c77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 282 additions and 317 deletions

View File

@ -35,7 +35,6 @@ from homeassistant.helpers.update_coordinator import (
) )
from .const import ( from .const import (
ATTR_FIELD,
ATTRIBUTION, ATTRIBUTION,
CC_ATTR_CLOUD_COVER, CC_ATTR_CLOUD_COVER,
CC_ATTR_CONDITION, CC_ATTR_CONDITION,
@ -223,10 +222,7 @@ class ClimaCellDataUpdateCoordinator(DataUpdateCoordinator):
CC_V3_ATTR_WIND_GUST, CC_V3_ATTR_WIND_GUST,
CC_V3_ATTR_CLOUD_COVER, CC_V3_ATTR_CLOUD_COVER,
CC_V3_ATTR_PRECIPITATION_TYPE, CC_V3_ATTR_PRECIPITATION_TYPE,
*( *(sensor_type.field for sensor_type in CC_V3_SENSOR_TYPES),
sensor_type[ATTR_FIELD]
for sensor_type in CC_V3_SENSOR_TYPES
),
] ]
) )
data[FORECASTS][HOURLY] = await self._api.forecast_hourly( data[FORECASTS][HOURLY] = await self._api.forecast_hourly(
@ -283,7 +279,7 @@ class ClimaCellDataUpdateCoordinator(DataUpdateCoordinator):
CC_ATTR_WIND_GUST, CC_ATTR_WIND_GUST,
CC_ATTR_CLOUD_COVER, CC_ATTR_CLOUD_COVER,
CC_ATTR_PRECIPITATION_TYPE, CC_ATTR_PRECIPITATION_TYPE,
*(sensor_type[ATTR_FIELD] for sensor_type in CC_SENSOR_TYPES), *(sensor_type.field for sensor_type in CC_SENSOR_TYPES),
], ],
[ [
CC_ATTR_TEMPERATURE_LOW, CC_ATTR_TEMPERATURE_LOW,

View File

@ -1,4 +1,10 @@
"""Constants for the ClimaCell integration.""" """Constants for the ClimaCell integration."""
from __future__ import annotations
from dataclasses import dataclass
from enum import IntEnum
from typing import Callable
from pyclimacell.const import ( from pyclimacell.const import (
DAILY, DAILY,
HOURLY, HOURLY,
@ -26,15 +32,10 @@ from homeassistant.components.weather import (
ATTR_CONDITION_WINDY, ATTR_CONDITION_WINDY,
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_NAME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_PARTS_PER_MILLION,
CONF_UNIT_OF_MEASUREMENT,
CONF_UNIT_SYSTEM_IMPERIAL,
CONF_UNIT_SYSTEM_METRIC,
DEVICE_CLASS_CO, DEVICE_CLASS_CO,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
@ -74,13 +75,6 @@ MAX_FORECASTS = {
NOWCAST: 30, NOWCAST: 30,
} }
# Sensor type keys
ATTR_FIELD = "field"
ATTR_METRIC_CONVERSION = "metric_conversion"
ATTR_VALUE_MAP = "value_map"
ATTR_IS_METRIC_CHECK = "is_metric_check"
ATTR_SCALE = "scale"
# Additional attributes # Additional attributes
ATTR_WIND_GUST = "wind_gust" ATTR_WIND_GUST = "wind_gust"
ATTR_CLOUD_COVER = "cloud_cover" ATTR_CLOUD_COVER = "cloud_cover"
@ -155,165 +149,182 @@ CC_ATTR_SOLAR_GHI = "solarGHI"
CC_ATTR_CLOUD_BASE = "cloudBase" CC_ATTR_CLOUD_BASE = "cloudBase"
CC_ATTR_CLOUD_CEILING = "cloudCeiling" CC_ATTR_CLOUD_CEILING = "cloudCeiling"
CC_SENSOR_TYPES = [
{ @dataclass
ATTR_FIELD: CC_ATTR_FEELS_LIKE, class ClimaCellSensorMetadata:
ATTR_NAME: "Feels Like", """Metadata about an individual ClimaCell sensor."""
CONF_UNIT_SYSTEM_IMPERIAL: TEMP_FAHRENHEIT,
CONF_UNIT_SYSTEM_METRIC: TEMP_CELSIUS, field: str
ATTR_METRIC_CONVERSION: lambda val: temp_convert( name: str
val, TEMP_FAHRENHEIT, TEMP_CELSIUS unit_imperial: str | None = None
), unit_metric: str | None = None
ATTR_IS_METRIC_CHECK: True, metric_conversion: Callable | float = 1.0
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, is_metric_check: bool | None = None
}, device_class: str | None = None
{ value_map: IntEnum | None = None
ATTR_FIELD: CC_ATTR_DEW_POINT,
ATTR_NAME: "Dew Point", def __post_init__(self) -> None:
CONF_UNIT_SYSTEM_IMPERIAL: TEMP_FAHRENHEIT, """Post initialization."""
CONF_UNIT_SYSTEM_METRIC: TEMP_CELSIUS, units = (self.unit_imperial, self.unit_metric)
ATTR_METRIC_CONVERSION: lambda val: temp_convert( if any(u is not None for u in units) and any(u is None for u in units):
val, TEMP_FAHRENHEIT, TEMP_CELSIUS raise RuntimeError(
), "`unit_imperial` and `unit_metric` both need to be None or both need "
ATTR_IS_METRIC_CHECK: True, "to be defined."
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, )
},
{
ATTR_FIELD: CC_ATTR_PRESSURE_SURFACE_LEVEL, CC_SENSOR_TYPES = (
ATTR_NAME: "Pressure (Surface Level)", ClimaCellSensorMetadata(
CONF_UNIT_SYSTEM_IMPERIAL: PRESSURE_INHG, CC_ATTR_FEELS_LIKE,
CONF_UNIT_SYSTEM_METRIC: PRESSURE_HPA, "Feels Like",
ATTR_METRIC_CONVERSION: lambda val: pressure_convert( 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",
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)",
unit_imperial=PRESSURE_INHG,
unit_metric=PRESSURE_HPA,
metric_conversion=lambda val: pressure_convert(
val, PRESSURE_INHG, PRESSURE_HPA val, PRESSURE_INHG, PRESSURE_HPA
), ),
ATTR_IS_METRIC_CHECK: True, is_metric_check=True,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_PRESSURE,
}, ),
{ ClimaCellSensorMetadata(
ATTR_FIELD: CC_ATTR_SOLAR_GHI, CC_ATTR_SOLAR_GHI,
ATTR_NAME: "Global Horizontal Irradiance", "Global Horizontal Irradiance",
CONF_UNIT_SYSTEM_IMPERIAL: IRRADIATION_BTUS_PER_HOUR_SQUARE_FOOT, unit_imperial=IRRADIATION_BTUS_PER_HOUR_SQUARE_FOOT,
CONF_UNIT_SYSTEM_METRIC: IRRADIATION_WATTS_PER_SQUARE_METER, unit_metric=IRRADIATION_WATTS_PER_SQUARE_METER,
ATTR_METRIC_CONVERSION: 3.15459, metric_conversion=3.15459,
ATTR_IS_METRIC_CHECK: True, is_metric_check=True,
}, ),
{ ClimaCellSensorMetadata(
ATTR_FIELD: CC_ATTR_CLOUD_BASE, CC_ATTR_CLOUD_BASE,
ATTR_NAME: "Cloud Base", "Cloud Base",
CONF_UNIT_SYSTEM_IMPERIAL: LENGTH_MILES, unit_imperial=LENGTH_MILES,
CONF_UNIT_SYSTEM_METRIC: LENGTH_KILOMETERS, unit_metric=LENGTH_KILOMETERS,
ATTR_METRIC_CONVERSION: lambda val: distance_convert( metric_conversion=lambda val: distance_convert(
val, LENGTH_MILES, LENGTH_KILOMETERS val, LENGTH_MILES, LENGTH_KILOMETERS
), ),
ATTR_IS_METRIC_CHECK: True, is_metric_check=True,
}, ),
{ ClimaCellSensorMetadata(
ATTR_FIELD: CC_ATTR_CLOUD_CEILING, CC_ATTR_CLOUD_CEILING,
ATTR_NAME: "Cloud Ceiling", "Cloud Ceiling",
CONF_UNIT_SYSTEM_IMPERIAL: LENGTH_MILES, unit_imperial=LENGTH_MILES,
CONF_UNIT_SYSTEM_METRIC: LENGTH_KILOMETERS, unit_metric=LENGTH_KILOMETERS,
ATTR_METRIC_CONVERSION: lambda val: distance_convert( metric_conversion=lambda val: distance_convert(
val, LENGTH_MILES, LENGTH_KILOMETERS val, LENGTH_MILES, LENGTH_KILOMETERS
), ),
ATTR_IS_METRIC_CHECK: True, is_metric_check=True,
}, ),
{ ClimaCellSensorMetadata(
ATTR_FIELD: CC_ATTR_CLOUD_COVER, CC_ATTR_CLOUD_COVER,
ATTR_NAME: "Cloud Cover", "Cloud Cover",
CONF_UNIT_OF_MEASUREMENT: PERCENTAGE, unit_imperial=PERCENTAGE,
}, unit_metric=PERCENTAGE,
{ ),
ATTR_FIELD: CC_ATTR_WIND_GUST, ClimaCellSensorMetadata(
ATTR_NAME: "Wind Gust", CC_ATTR_WIND_GUST,
CONF_UNIT_SYSTEM_IMPERIAL: SPEED_MILES_PER_HOUR, "Wind Gust",
CONF_UNIT_SYSTEM_METRIC: SPEED_METERS_PER_SECOND, unit_imperial=SPEED_MILES_PER_HOUR,
ATTR_METRIC_CONVERSION: lambda val: distance_convert( unit_metric=SPEED_METERS_PER_SECOND,
val, LENGTH_MILES, LENGTH_METERS metric_conversion=lambda val: distance_convert(val, LENGTH_MILES, LENGTH_METERS)
)
/ 3600, / 3600,
ATTR_IS_METRIC_CHECK: True, is_metric_check=True,
}, ),
{ ClimaCellSensorMetadata(
ATTR_FIELD: CC_ATTR_PRECIPITATION_TYPE, CC_ATTR_PRECIPITATION_TYPE,
ATTR_NAME: "Precipitation Type", "Precipitation Type",
ATTR_VALUE_MAP: PrecipitationType, value_map=PrecipitationType,
}, ),
{ ClimaCellSensorMetadata(
ATTR_FIELD: CC_ATTR_OZONE, CC_ATTR_OZONE,
ATTR_NAME: "Ozone", "Ozone",
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, unit_imperial=CONCENTRATION_PARTS_PER_BILLION,
}, unit_metric=CONCENTRATION_PARTS_PER_BILLION,
{ ),
ATTR_FIELD: CC_ATTR_PARTICULATE_MATTER_25, ClimaCellSensorMetadata(
ATTR_NAME: "Particulate Matter < 2.5 μm", CC_ATTR_PARTICULATE_MATTER_25,
CONF_UNIT_SYSTEM_IMPERIAL: CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, "Particulate Matter < 2.5 μm",
CONF_UNIT_SYSTEM_METRIC: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT,
ATTR_METRIC_CONVERSION: 3.2808399 ** 3, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_IS_METRIC_CHECK: True, metric_conversion=3.2808399 ** 3,
}, is_metric_check=True,
{ ),
ATTR_FIELD: CC_ATTR_PARTICULATE_MATTER_10, ClimaCellSensorMetadata(
ATTR_NAME: "Particulate Matter < 10 μm", CC_ATTR_PARTICULATE_MATTER_10,
CONF_UNIT_SYSTEM_IMPERIAL: CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, "Particulate Matter < 10 μm",
CONF_UNIT_SYSTEM_METRIC: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT,
ATTR_METRIC_CONVERSION: 3.2808399 ** 3, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_IS_METRIC_CHECK: True, metric_conversion=3.2808399 ** 3,
}, is_metric_check=True,
{ ),
ATTR_FIELD: CC_ATTR_NITROGEN_DIOXIDE, ClimaCellSensorMetadata(
ATTR_NAME: "Nitrogen Dioxide", CC_ATTR_NITROGEN_DIOXIDE,
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, "Nitrogen Dioxide",
}, unit_imperial=CONCENTRATION_PARTS_PER_BILLION,
{ unit_metric=CONCENTRATION_PARTS_PER_BILLION,
ATTR_FIELD: CC_ATTR_CARBON_MONOXIDE, ),
ATTR_NAME: "Carbon Monoxide", ClimaCellSensorMetadata(
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, CC_ATTR_CARBON_MONOXIDE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_CO, "Carbon Monoxide",
}, unit_imperial=CONCENTRATION_PARTS_PER_MILLION,
{ unit_metric=CONCENTRATION_PARTS_PER_MILLION,
ATTR_FIELD: CC_ATTR_SULFUR_DIOXIDE, device_class=DEVICE_CLASS_CO,
ATTR_NAME: "Sulfur Dioxide", ),
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, ClimaCellSensorMetadata(
}, CC_ATTR_SULFUR_DIOXIDE,
{ATTR_FIELD: CC_ATTR_EPA_AQI, ATTR_NAME: "US EPA Air Quality Index"}, "Sulfur Dioxide",
{ unit_imperial=CONCENTRATION_PARTS_PER_BILLION,
ATTR_FIELD: CC_ATTR_EPA_PRIMARY_POLLUTANT, unit_metric=CONCENTRATION_PARTS_PER_BILLION,
ATTR_NAME: "US EPA Primary Pollutant", ),
ATTR_VALUE_MAP: PrimaryPollutantType, ClimaCellSensorMetadata(CC_ATTR_EPA_AQI, "US EPA Air Quality Index"),
}, ClimaCellSensorMetadata(
{ CC_ATTR_EPA_PRIMARY_POLLUTANT,
ATTR_FIELD: CC_ATTR_EPA_HEALTH_CONCERN, "US EPA Primary Pollutant",
ATTR_NAME: "US EPA Health Concern", value_map=PrimaryPollutantType,
ATTR_VALUE_MAP: HealthConcernType, ),
}, ClimaCellSensorMetadata(
{ATTR_FIELD: CC_ATTR_CHINA_AQI, ATTR_NAME: "China MEP Air Quality Index"}, CC_ATTR_EPA_HEALTH_CONCERN,
{ "US EPA Health Concern",
ATTR_FIELD: CC_ATTR_CHINA_PRIMARY_POLLUTANT, value_map=HealthConcernType,
ATTR_NAME: "China MEP Primary Pollutant", ),
ATTR_VALUE_MAP: PrimaryPollutantType, ClimaCellSensorMetadata(CC_ATTR_CHINA_AQI, "China MEP Air Quality Index"),
}, ClimaCellSensorMetadata(
{ CC_ATTR_CHINA_PRIMARY_POLLUTANT,
ATTR_FIELD: CC_ATTR_CHINA_HEALTH_CONCERN, "China MEP Primary Pollutant",
ATTR_NAME: "China MEP Health Concern", value_map=PrimaryPollutantType,
ATTR_VALUE_MAP: HealthConcernType, ),
}, ClimaCellSensorMetadata(
{ CC_ATTR_CHINA_HEALTH_CONCERN,
ATTR_FIELD: CC_ATTR_POLLEN_TREE, "China MEP Health Concern",
ATTR_NAME: "Tree Pollen Index", value_map=HealthConcernType,
ATTR_VALUE_MAP: PollenIndex, ),
}, ClimaCellSensorMetadata(
{ CC_ATTR_POLLEN_TREE, "Tree Pollen Index", value_map=PollenIndex
ATTR_FIELD: CC_ATTR_POLLEN_WEED, ),
ATTR_NAME: "Weed Pollen Index", ClimaCellSensorMetadata(
ATTR_VALUE_MAP: PollenIndex, CC_ATTR_POLLEN_WEED, "Weed Pollen Index", value_map=PollenIndex
}, ),
{ ClimaCellSensorMetadata(
ATTR_FIELD: CC_ATTR_POLLEN_GRASS, CC_ATTR_POLLEN_GRASS, "Grass Pollen Index", value_map=PollenIndex
ATTR_NAME: "Grass Pollen Index", ),
ATTR_VALUE_MAP: PollenIndex, ClimaCellSensorMetadata(CC_ATTR_FIRE_INDEX, "Fire Index"),
}, )
{ATTR_FIELD: CC_ATTR_FIRE_INDEX, ATTR_NAME: "Fire Index"},
]
# V3 constants # V3 constants
CONDITIONS_V3 = { CONDITIONS_V3 = {
@ -377,73 +388,68 @@ CC_V3_ATTR_POLLEN_WEED = "pollen_weed"
CC_V3_ATTR_POLLEN_GRASS = "pollen_grass" CC_V3_ATTR_POLLEN_GRASS = "pollen_grass"
CC_V3_ATTR_FIRE_INDEX = "fire_index" CC_V3_ATTR_FIRE_INDEX = "fire_index"
CC_V3_SENSOR_TYPES = [ CC_V3_SENSOR_TYPES = (
{ ClimaCellSensorMetadata(
ATTR_FIELD: CC_V3_ATTR_OZONE, CC_V3_ATTR_OZONE,
ATTR_NAME: "Ozone", "Ozone",
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, unit_imperial=CONCENTRATION_PARTS_PER_BILLION,
}, unit_metric=CONCENTRATION_PARTS_PER_BILLION,
{ ),
ATTR_FIELD: CC_V3_ATTR_PARTICULATE_MATTER_25, ClimaCellSensorMetadata(
ATTR_NAME: "Particulate Matter < 2.5 μm", CC_V3_ATTR_PARTICULATE_MATTER_25,
CONF_UNIT_SYSTEM_IMPERIAL: "μg/ft³", "Particulate Matter < 2.5 μm",
CONF_UNIT_SYSTEM_METRIC: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT,
ATTR_METRIC_CONVERSION: 3.2808399 ** 3, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_IS_METRIC_CHECK: False, metric_conversion=3.2808399 ** 3,
}, is_metric_check=False,
{ ),
ATTR_FIELD: CC_V3_ATTR_PARTICULATE_MATTER_10, ClimaCellSensorMetadata(
ATTR_NAME: "Particulate Matter < 10 μm", CC_V3_ATTR_PARTICULATE_MATTER_10,
CONF_UNIT_SYSTEM_IMPERIAL: "μg/ft³", "Particulate Matter < 10 μm",
CONF_UNIT_SYSTEM_METRIC: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT,
ATTR_METRIC_CONVERSION: 3.2808399 ** 3, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_IS_METRIC_CHECK: False, metric_conversion=3.2808399 ** 3,
}, is_metric_check=False,
{ ),
ATTR_FIELD: CC_V3_ATTR_NITROGEN_DIOXIDE, ClimaCellSensorMetadata(
ATTR_NAME: "Nitrogen Dioxide", CC_V3_ATTR_NITROGEN_DIOXIDE,
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, "Nitrogen Dioxide",
}, unit_imperial=CONCENTRATION_PARTS_PER_BILLION,
{ unit_metric=CONCENTRATION_PARTS_PER_BILLION,
ATTR_FIELD: CC_V3_ATTR_CARBON_MONOXIDE, ),
ATTR_NAME: "Carbon Monoxide", ClimaCellSensorMetadata(
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_MILLION, CC_V3_ATTR_CARBON_MONOXIDE,
ATTR_DEVICE_CLASS: DEVICE_CLASS_CO, "Carbon Monoxide",
}, unit_imperial=CONCENTRATION_PARTS_PER_MILLION,
{ unit_metric=CONCENTRATION_PARTS_PER_MILLION,
ATTR_FIELD: CC_V3_ATTR_SULFUR_DIOXIDE, device_class=DEVICE_CLASS_CO,
ATTR_NAME: "Sulfur Dioxide", ),
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, ClimaCellSensorMetadata(
}, CC_V3_ATTR_SULFUR_DIOXIDE,
{ATTR_FIELD: CC_V3_ATTR_EPA_AQI, ATTR_NAME: "US EPA Air Quality Index"}, "Sulfur Dioxide",
{ unit_imperial=CONCENTRATION_PARTS_PER_BILLION,
ATTR_FIELD: CC_V3_ATTR_EPA_PRIMARY_POLLUTANT, unit_metric=CONCENTRATION_PARTS_PER_BILLION,
ATTR_NAME: "US EPA Primary Pollutant", ),
}, ClimaCellSensorMetadata(CC_V3_ATTR_EPA_AQI, "US EPA Air Quality Index"),
{ATTR_FIELD: CC_V3_ATTR_EPA_HEALTH_CONCERN, ATTR_NAME: "US EPA Health Concern"}, ClimaCellSensorMetadata(
{ATTR_FIELD: CC_V3_ATTR_CHINA_AQI, ATTR_NAME: "China MEP Air Quality Index"}, CC_V3_ATTR_EPA_PRIMARY_POLLUTANT, "US EPA Primary Pollutant"
{ ),
ATTR_FIELD: CC_V3_ATTR_CHINA_PRIMARY_POLLUTANT, ClimaCellSensorMetadata(CC_V3_ATTR_EPA_HEALTH_CONCERN, "US EPA Health Concern"),
ATTR_NAME: "China MEP Primary Pollutant", ClimaCellSensorMetadata(CC_V3_ATTR_CHINA_AQI, "China MEP Air Quality Index"),
}, ClimaCellSensorMetadata(
{ CC_V3_ATTR_CHINA_PRIMARY_POLLUTANT, "China MEP Primary Pollutant"
ATTR_FIELD: CC_V3_ATTR_CHINA_HEALTH_CONCERN, ),
ATTR_NAME: "China MEP Health Concern", ClimaCellSensorMetadata(
}, CC_V3_ATTR_CHINA_HEALTH_CONCERN, "China MEP Health Concern"
{ ),
ATTR_FIELD: CC_V3_ATTR_POLLEN_TREE, ClimaCellSensorMetadata(
ATTR_NAME: "Tree Pollen Index", CC_V3_ATTR_POLLEN_TREE, "Tree Pollen Index", value_map=V3PollenIndex
ATTR_VALUE_MAP: V3PollenIndex, ),
}, ClimaCellSensorMetadata(
{ CC_V3_ATTR_POLLEN_WEED, "Weed Pollen Index", value_map=V3PollenIndex
ATTR_FIELD: CC_V3_ATTR_POLLEN_WEED, ),
ATTR_NAME: "Weed Pollen Index", ClimaCellSensorMetadata(
ATTR_VALUE_MAP: V3PollenIndex, CC_V3_ATTR_POLLEN_GRASS, "Grass Pollen Index", value_map=V3PollenIndex
}, ),
{ ClimaCellSensorMetadata(CC_V3_ATTR_FIRE_INDEX, "Fire Index"),
ATTR_FIELD: CC_V3_ATTR_POLLEN_GRASS, )
ATTR_NAME: "Grass Pollen Index",
ATTR_VALUE_MAP: V3PollenIndex,
},
{ATTR_FIELD: CC_V3_ATTR_FIRE_INDEX, ATTR_NAME: "Fire Index"},
]

View File

@ -2,39 +2,19 @@
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod from abc import abstractmethod
from collections.abc import Mapping
import logging import logging
from typing import Any
from pyclimacell.const import CURRENT from pyclimacell.const import CURRENT
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_VERSION, CONF_NAME
ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS,
ATTR_NAME,
CONF_API_VERSION,
CONF_NAME,
CONF_UNIT_OF_MEASUREMENT,
CONF_UNIT_SYSTEM_IMPERIAL,
CONF_UNIT_SYSTEM_METRIC,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import slugify from homeassistant.util import slugify
from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity
from .const import ( from .const import CC_SENSOR_TYPES, CC_V3_SENSOR_TYPES, DOMAIN, ClimaCellSensorMetadata
ATTR_FIELD,
ATTR_IS_METRIC_CHECK,
ATTR_METRIC_CONVERSION,
ATTR_SCALE,
ATTR_VALUE_MAP,
CC_SENSOR_TYPES,
CC_V3_SENSOR_TYPES,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -55,7 +35,7 @@ async def async_setup_entry(
api_class = ClimaCellSensorEntity api_class = ClimaCellSensorEntity
sensor_types = CC_SENSOR_TYPES sensor_types = CC_SENSOR_TYPES
entities = [ entities = [
api_class(config_entry, coordinator, api_version, sensor_type) api_class(hass, config_entry, coordinator, api_version, sensor_type)
for sensor_type in sensor_types for sensor_type in sensor_types
] ]
async_add_entities(entities) async_add_entities(entities)
@ -66,53 +46,29 @@ class BaseClimaCellSensorEntity(ClimaCellEntity, SensorEntity):
def __init__( def __init__(
self, self,
hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ConfigEntry,
coordinator: ClimaCellDataUpdateCoordinator, coordinator: ClimaCellDataUpdateCoordinator,
api_version: int, api_version: int,
sensor_type: dict[str, str | float], sensor_type: ClimaCellSensorMetadata,
) -> None: ) -> None:
"""Initialize ClimaCell Sensor Entity.""" """Initialize ClimaCell Sensor Entity."""
super().__init__(config_entry, coordinator, api_version) super().__init__(config_entry, coordinator, api_version)
self.sensor_type = sensor_type self.sensor_type = sensor_type
self._attr_device_class = self.sensor_type.get(ATTR_DEVICE_CLASS) self._attr_device_class = self.sensor_type.device_class
self._attr_entity_registry_enabled_default = False
@property self._attr_name = (
def entity_registry_enabled_default(self) -> bool: f"{self._config_entry.data[CONF_NAME]} - {self.sensor_type.name}"
"""Return if the entity should be enabled when first added to the entity registry.""" )
return False self._attr_unique_id = (
f"{self._config_entry.unique_id}_{slugify(self.sensor_type.name)}"
@property )
def name(self) -> str: self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: self.attribution}
"""Return the name of the entity.""" self._attr_unit_of_measurement = (
return f"{self._config_entry.data[CONF_NAME]} - {self.sensor_type[ATTR_NAME]}" self.sensor_type.unit_metric
if hass.config.units.is_metric
@property else self.sensor_type.unit_imperial
def unique_id(self) -> str: )
"""Return the unique id of the entity."""
return f"{self._config_entry.unique_id}_{slugify(self.sensor_type[ATTR_NAME])}"
@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return entity specific state attributes."""
return {ATTR_ATTRIBUTION: self.attribution}
@property
def unit_of_measurement(self) -> str | None:
"""Return the unit of measurement."""
if CONF_UNIT_OF_MEASUREMENT in self.sensor_type:
return self.sensor_type[CONF_UNIT_OF_MEASUREMENT]
if (
CONF_UNIT_SYSTEM_IMPERIAL in self.sensor_type
and CONF_UNIT_SYSTEM_METRIC in self.sensor_type
):
return (
self.sensor_type[CONF_UNIT_SYSTEM_METRIC]
if self.hass.config.units.is_metric
else self.sensor_type[CONF_UNIT_SYSTEM_IMPERIAL]
)
return None
@property @property
@abstractmethod @abstractmethod
@ -123,27 +79,22 @@ class BaseClimaCellSensorEntity(ClimaCellEntity, SensorEntity):
def state(self) -> str | int | float | None: def state(self) -> str | int | float | None:
"""Return the state.""" """Return the state."""
state = self._state state = self._state
if state and ATTR_SCALE in self.sensor_type:
state *= self.sensor_type[ATTR_SCALE]
if ( if (
state is not None state is not None
and CONF_UNIT_SYSTEM_IMPERIAL in self.sensor_type and self.sensor_type.unit_imperial is not None
and CONF_UNIT_SYSTEM_METRIC in self.sensor_type and self.sensor_type.metric_conversion != 1.0
and ATTR_METRIC_CONVERSION in self.sensor_type and self.sensor_type.is_metric_check is not None
and ATTR_IS_METRIC_CHECK in self.sensor_type and self.hass.config.units.is_metric == self.sensor_type.is_metric_check
and self.hass.config.units.is_metric
== self.sensor_type[ATTR_IS_METRIC_CHECK]
): ):
conversion = self.sensor_type[ATTR_METRIC_CONVERSION] conversion = self.sensor_type.metric_conversion
# When conversion is a callable, we assume it's a single input function # When conversion is a callable, we assume it's a single input function
if callable(conversion): if callable(conversion):
return round(conversion(state), 4) return round(conversion(state), 4)
return round(state * conversion, 4) return round(state * conversion, 4)
if ATTR_VALUE_MAP in self.sensor_type and state is not None: if self.sensor_type.value_map is not None and state is not None:
return self.sensor_type[ATTR_VALUE_MAP](state).name.lower() return self.sensor_type.value_map(state).name.lower()
return state return state
@ -154,7 +105,7 @@ class ClimaCellSensorEntity(BaseClimaCellSensorEntity):
@property @property
def _state(self) -> str | int | float | None: def _state(self) -> str | int | float | None:
"""Return the raw state.""" """Return the raw state."""
return self._get_current_property(self.sensor_type[ATTR_FIELD]) return self._get_current_property(self.sensor_type.field)
class ClimaCellV3SensorEntity(BaseClimaCellSensorEntity): class ClimaCellV3SensorEntity(BaseClimaCellSensorEntity):
@ -164,5 +115,5 @@ class ClimaCellV3SensorEntity(BaseClimaCellSensorEntity):
def _state(self) -> str | int | float | None: def _state(self) -> str | int | float | None:
"""Return the raw state.""" """Return the raw state."""
return self._get_cc_value( return self._get_cc_value(
self.coordinator.data[CURRENT], self.sensor_type[ATTR_FIELD] self.coordinator.data[CURRENT], self.sensor_type.field
) )

View File

@ -0,0 +1,12 @@
"""Tests for ClimaCell const."""
import pytest
from homeassistant.components.climacell.const import ClimaCellSensorMetadata
from homeassistant.const import TEMP_FAHRENHEIT
async def test_post_init():
"""Test post initiailization check for ClimaCellSensorMetadata."""
with pytest.raises(RuntimeError):
ClimaCellSensorMetadata("a", "b", unit_imperial=TEMP_FAHRENHEIT)