mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add sensors for other ClimaCell data (#49259)
* Add sensors for other ClimaCell data * add tests and add rounding * docstrings * fix pressure * Update homeassistant/components/climacell/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/climacell/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * review comments * add another abstractmethod * use superscript * remove mypy ignore Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
5fb36ad9e1
commit
898a1a17be
@ -16,6 +16,7 @@ from pyclimacell.exceptions import (
|
|||||||
UnknownException,
|
UnknownException,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
|
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -34,6 +35,7 @@ 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,
|
||||||
@ -50,6 +52,7 @@ from .const import (
|
|||||||
CC_ATTR_WIND_DIRECTION,
|
CC_ATTR_WIND_DIRECTION,
|
||||||
CC_ATTR_WIND_GUST,
|
CC_ATTR_WIND_GUST,
|
||||||
CC_ATTR_WIND_SPEED,
|
CC_ATTR_WIND_SPEED,
|
||||||
|
CC_SENSOR_TYPES,
|
||||||
CC_V3_ATTR_CLOUD_COVER,
|
CC_V3_ATTR_CLOUD_COVER,
|
||||||
CC_V3_ATTR_CONDITION,
|
CC_V3_ATTR_CONDITION,
|
||||||
CC_V3_ATTR_HUMIDITY,
|
CC_V3_ATTR_HUMIDITY,
|
||||||
@ -64,8 +67,8 @@ from .const import (
|
|||||||
CC_V3_ATTR_WIND_DIRECTION,
|
CC_V3_ATTR_WIND_DIRECTION,
|
||||||
CC_V3_ATTR_WIND_GUST,
|
CC_V3_ATTR_WIND_GUST,
|
||||||
CC_V3_ATTR_WIND_SPEED,
|
CC_V3_ATTR_WIND_SPEED,
|
||||||
|
CC_V3_SENSOR_TYPES,
|
||||||
CONF_TIMESTEP,
|
CONF_TIMESTEP,
|
||||||
DEFAULT_FORECAST_TYPE,
|
|
||||||
DEFAULT_TIMESTEP,
|
DEFAULT_TIMESTEP,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
MAX_REQUESTS_PER_DAY,
|
MAX_REQUESTS_PER_DAY,
|
||||||
@ -73,7 +76,7 @@ from .const import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORMS = [WEATHER_DOMAIN]
|
PLATFORMS = [SENSOR_DOMAIN, WEATHER_DOMAIN]
|
||||||
|
|
||||||
|
|
||||||
def _set_update_interval(
|
def _set_update_interval(
|
||||||
@ -232,6 +235,10 @@ 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[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(
|
||||||
@ -288,6 +295,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],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
CC_ATTR_TEMPERATURE_LOW,
|
CC_ATTR_TEMPERATURE_LOW,
|
||||||
@ -317,20 +325,22 @@ class ClimaCellEntity(CoordinatorEntity):
|
|||||||
self,
|
self,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
coordinator: ClimaCellDataUpdateCoordinator,
|
coordinator: ClimaCellDataUpdateCoordinator,
|
||||||
forecast_type: str,
|
|
||||||
api_version: int,
|
api_version: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize ClimaCell Entity."""
|
"""Initialize ClimaCell Entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.api_version = api_version
|
self.api_version = api_version
|
||||||
self.forecast_type = forecast_type
|
|
||||||
self._config_entry = config_entry
|
self._config_entry = config_entry
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_cc_value(
|
def _get_cc_value(
|
||||||
weather_dict: dict[str, Any], key: str
|
weather_dict: dict[str, Any], key: str
|
||||||
) -> int | float | str | None:
|
) -> int | float | str | None:
|
||||||
"""Return property from weather_dict."""
|
"""
|
||||||
|
Return property from weather_dict.
|
||||||
|
|
||||||
|
Used for V3 API.
|
||||||
|
"""
|
||||||
items = weather_dict.get(key, {})
|
items = weather_dict.get(key, {})
|
||||||
# Handle cases where value returned is a list.
|
# Handle cases where value returned is a list.
|
||||||
# Optimistically find the best value to return.
|
# Optimistically find the best value to return.
|
||||||
@ -347,23 +357,13 @@ class ClimaCellEntity(CoordinatorEntity):
|
|||||||
|
|
||||||
return items.get("value")
|
return items.get("value")
|
||||||
|
|
||||||
@property
|
def _get_current_property(self, property_name: str) -> int | str | float | None:
|
||||||
def entity_registry_enabled_default(self) -> bool:
|
"""
|
||||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
Get property from current conditions.
|
||||||
if self.forecast_type == DEFAULT_FORECAST_TYPE:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
Used for V4 API.
|
||||||
|
"""
|
||||||
@property
|
return self.coordinator.data.get(CURRENT, {}).get(property_name)
|
||||||
def name(self) -> str:
|
|
||||||
"""Return the name of the entity."""
|
|
||||||
return f"{self._config_entry.data[CONF_NAME]} - {self.forecast_type.title()}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self) -> str:
|
|
||||||
"""Return the unique id of the entity."""
|
|
||||||
return f"{self._config_entry.unique_id}_{self.forecast_type}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attribution(self):
|
def attribution(self):
|
||||||
@ -377,6 +377,6 @@ class ClimaCellEntity(CoordinatorEntity):
|
|||||||
"identifiers": {(DOMAIN, self._config_entry.data[CONF_API_KEY])},
|
"identifiers": {(DOMAIN, self._config_entry.data[CONF_API_KEY])},
|
||||||
"name": "ClimaCell",
|
"name": "ClimaCell",
|
||||||
"manufacturer": "ClimaCell",
|
"manufacturer": "ClimaCell",
|
||||||
"sw_version": "v3",
|
"sw_version": f"v{self.api_version}",
|
||||||
"entry_type": "service",
|
"entry_type": "service",
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
"""Constants for the ClimaCell integration."""
|
"""Constants for the ClimaCell integration."""
|
||||||
from pyclimacell.const import DAILY, HOURLY, NOWCAST, WeatherCode
|
from pyclimacell.const import (
|
||||||
|
DAILY,
|
||||||
|
HOURLY,
|
||||||
|
NOWCAST,
|
||||||
|
HealthConcernType,
|
||||||
|
PollenIndex,
|
||||||
|
PrimaryPollutantType,
|
||||||
|
WeatherCode,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.components.weather import (
|
from homeassistant.components.weather import (
|
||||||
ATTR_CONDITION_CLEAR_NIGHT,
|
ATTR_CONDITION_CLEAR_NIGHT,
|
||||||
@ -15,6 +23,15 @@ from homeassistant.components.weather import (
|
|||||||
ATTR_CONDITION_SUNNY,
|
ATTR_CONDITION_SUNNY,
|
||||||
ATTR_CONDITION_WINDY,
|
ATTR_CONDITION_WINDY,
|
||||||
)
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_NAME,
|
||||||
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_UNIT_SYSTEM_IMPERIAL,
|
||||||
|
CONF_UNIT_SYSTEM_METRIC,
|
||||||
|
)
|
||||||
|
|
||||||
CONF_TIMESTEP = "timestep"
|
CONF_TIMESTEP = "timestep"
|
||||||
FORECAST_TYPES = [DAILY, HOURLY, NOWCAST]
|
FORECAST_TYPES = [DAILY, HOURLY, NOWCAST]
|
||||||
@ -35,6 +52,12 @@ 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"
|
||||||
|
|
||||||
# Additional attributes
|
# Additional attributes
|
||||||
ATTR_WIND_GUST = "wind_gust"
|
ATTR_WIND_GUST = "wind_gust"
|
||||||
ATTR_CLOUD_COVER = "cloud_cover"
|
ATTR_CLOUD_COVER = "cloud_cover"
|
||||||
@ -68,6 +91,7 @@ CONDITIONS = {
|
|||||||
WeatherCode.PARTLY_CLOUDY: ATTR_CONDITION_PARTLYCLOUDY,
|
WeatherCode.PARTLY_CLOUDY: ATTR_CONDITION_PARTLYCLOUDY,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Weather constants
|
||||||
CC_ATTR_TIMESTAMP = "startTime"
|
CC_ATTR_TIMESTAMP = "startTime"
|
||||||
CC_ATTR_TEMPERATURE = "temperature"
|
CC_ATTR_TEMPERATURE = "temperature"
|
||||||
CC_ATTR_TEMPERATURE_HIGH = "temperatureMax"
|
CC_ATTR_TEMPERATURE_HIGH = "temperatureMax"
|
||||||
@ -85,6 +109,95 @@ CC_ATTR_WIND_GUST = "windGust"
|
|||||||
CC_ATTR_CLOUD_COVER = "cloudCover"
|
CC_ATTR_CLOUD_COVER = "cloudCover"
|
||||||
CC_ATTR_PRECIPITATION_TYPE = "precipitationType"
|
CC_ATTR_PRECIPITATION_TYPE = "precipitationType"
|
||||||
|
|
||||||
|
# Sensor attributes
|
||||||
|
CC_ATTR_PARTICULATE_MATTER_25 = "particulateMatter25"
|
||||||
|
CC_ATTR_PARTICULATE_MATTER_10 = "particulateMatter10"
|
||||||
|
CC_ATTR_NITROGEN_DIOXIDE = "pollutantNO2"
|
||||||
|
CC_ATTR_CARBON_MONOXIDE = "pollutantCO"
|
||||||
|
CC_ATTR_SULFUR_DIOXIDE = "pollutantSO2"
|
||||||
|
CC_ATTR_EPA_AQI = "epaIndex"
|
||||||
|
CC_ATTR_EPA_PRIMARY_POLLUTANT = "epaPrimaryPollutant"
|
||||||
|
CC_ATTR_EPA_HEALTH_CONCERN = "epaHealthConcern"
|
||||||
|
CC_ATTR_CHINA_AQI = "mepIndex"
|
||||||
|
CC_ATTR_CHINA_PRIMARY_POLLUTANT = "mepPrimaryPollutant"
|
||||||
|
CC_ATTR_CHINA_HEALTH_CONCERN = "mepHealthConcern"
|
||||||
|
CC_ATTR_POLLEN_TREE = "treeIndex"
|
||||||
|
CC_ATTR_POLLEN_WEED = "weedIndex"
|
||||||
|
CC_ATTR_POLLEN_GRASS = "grassIndex"
|
||||||
|
CC_ATTR_FIRE_INDEX = "fireIndex"
|
||||||
|
|
||||||
|
CC_SENSOR_TYPES = [
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_PARTICULATE_MATTER_25,
|
||||||
|
ATTR_NAME: "Particulate Matter < 2.5 μm",
|
||||||
|
CONF_UNIT_SYSTEM_IMPERIAL: "μg/ft³",
|
||||||
|
CONF_UNIT_SYSTEM_METRIC: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_METRIC_CONVERSION: 3.2808399 ** 3,
|
||||||
|
ATTR_IS_METRIC_CHECK: True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_PARTICULATE_MATTER_10,
|
||||||
|
ATTR_NAME: "Particulate Matter < 10 μm",
|
||||||
|
CONF_UNIT_SYSTEM_IMPERIAL: "μg/ft³",
|
||||||
|
CONF_UNIT_SYSTEM_METRIC: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_METRIC_CONVERSION: 3.2808399 ** 3,
|
||||||
|
ATTR_IS_METRIC_CHECK: True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_NITROGEN_DIOXIDE,
|
||||||
|
ATTR_NAME: "Nitrogen Dioxide",
|
||||||
|
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_CARBON_MONOXIDE,
|
||||||
|
ATTR_NAME: "Carbon Monoxide",
|
||||||
|
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_SULFUR_DIOXIDE,
|
||||||
|
ATTR_NAME: "Sulfur Dioxide",
|
||||||
|
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
},
|
||||||
|
{ATTR_FIELD: CC_ATTR_EPA_AQI, ATTR_NAME: "US EPA Air Quality Index"},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_EPA_PRIMARY_POLLUTANT,
|
||||||
|
ATTR_NAME: "US EPA Primary Pollutant",
|
||||||
|
ATTR_VALUE_MAP: PrimaryPollutantType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_EPA_HEALTH_CONCERN,
|
||||||
|
ATTR_NAME: "US EPA Health Concern",
|
||||||
|
ATTR_VALUE_MAP: HealthConcernType,
|
||||||
|
},
|
||||||
|
{ATTR_FIELD: CC_ATTR_CHINA_AQI, ATTR_NAME: "China MEP Air Quality Index"},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_CHINA_PRIMARY_POLLUTANT,
|
||||||
|
ATTR_NAME: "China MEP Primary Pollutant",
|
||||||
|
ATTR_VALUE_MAP: PrimaryPollutantType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_CHINA_HEALTH_CONCERN,
|
||||||
|
ATTR_NAME: "China MEP Health Concern",
|
||||||
|
ATTR_VALUE_MAP: HealthConcernType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_POLLEN_TREE,
|
||||||
|
ATTR_NAME: "Tree Pollen Index",
|
||||||
|
ATTR_VALUE_MAP: PollenIndex,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_POLLEN_WEED,
|
||||||
|
ATTR_NAME: "Weed Pollen Index",
|
||||||
|
ATTR_VALUE_MAP: PollenIndex,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_ATTR_POLLEN_GRASS,
|
||||||
|
ATTR_NAME: "Grass Pollen Index",
|
||||||
|
ATTR_VALUE_MAP: PollenIndex,
|
||||||
|
},
|
||||||
|
{ATTR_FIELD: CC_ATTR_FIRE_INDEX, ATTR_NAME: "Fire Index"},
|
||||||
|
]
|
||||||
|
|
||||||
# V3 constants
|
# V3 constants
|
||||||
CONDITIONS_V3 = {
|
CONDITIONS_V3 = {
|
||||||
"breezy": ATTR_CONDITION_WINDY,
|
"breezy": ATTR_CONDITION_WINDY,
|
||||||
@ -111,6 +224,7 @@ CONDITIONS_V3 = {
|
|||||||
"partly_cloudy": ATTR_CONDITION_PARTLYCLOUDY,
|
"partly_cloudy": ATTR_CONDITION_PARTLYCLOUDY,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Weather attributes
|
||||||
CC_V3_ATTR_TIMESTAMP = "observation_time"
|
CC_V3_ATTR_TIMESTAMP = "observation_time"
|
||||||
CC_V3_ATTR_TEMPERATURE = "temp"
|
CC_V3_ATTR_TEMPERATURE = "temp"
|
||||||
CC_V3_ATTR_TEMPERATURE_HIGH = "max"
|
CC_V3_ATTR_TEMPERATURE_HIGH = "max"
|
||||||
@ -128,3 +242,73 @@ CC_V3_ATTR_PRECIPITATION_PROBABILITY = "precipitation_probability"
|
|||||||
CC_V3_ATTR_WIND_GUST = "wind_gust"
|
CC_V3_ATTR_WIND_GUST = "wind_gust"
|
||||||
CC_V3_ATTR_CLOUD_COVER = "cloud_cover"
|
CC_V3_ATTR_CLOUD_COVER = "cloud_cover"
|
||||||
CC_V3_ATTR_PRECIPITATION_TYPE = "precipitation_type"
|
CC_V3_ATTR_PRECIPITATION_TYPE = "precipitation_type"
|
||||||
|
|
||||||
|
# Sensor attributes
|
||||||
|
CC_V3_ATTR_PARTICULATE_MATTER_25 = "pm25"
|
||||||
|
CC_V3_ATTR_PARTICULATE_MATTER_10 = "pm10"
|
||||||
|
CC_V3_ATTR_NITROGEN_DIOXIDE = "no2"
|
||||||
|
CC_V3_ATTR_CARBON_MONOXIDE = "co"
|
||||||
|
CC_V3_ATTR_SULFUR_DIOXIDE = "so2"
|
||||||
|
CC_V3_ATTR_EPA_AQI = "epa_aqi"
|
||||||
|
CC_V3_ATTR_EPA_PRIMARY_POLLUTANT = "epa_primary_pollutant"
|
||||||
|
CC_V3_ATTR_EPA_HEALTH_CONCERN = "epa_health_concern"
|
||||||
|
CC_V3_ATTR_CHINA_AQI = "china_aqi"
|
||||||
|
CC_V3_ATTR_CHINA_PRIMARY_POLLUTANT = "china_primary_pollutant"
|
||||||
|
CC_V3_ATTR_CHINA_HEALTH_CONCERN = "china_health_concern"
|
||||||
|
CC_V3_ATTR_POLLEN_TREE = "pollen_tree"
|
||||||
|
CC_V3_ATTR_POLLEN_WEED = "pollen_weed"
|
||||||
|
CC_V3_ATTR_POLLEN_GRASS = "pollen_grass"
|
||||||
|
CC_V3_ATTR_FIRE_INDEX = "fire_index"
|
||||||
|
|
||||||
|
CC_V3_SENSOR_TYPES = [
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_V3_ATTR_PARTICULATE_MATTER_25,
|
||||||
|
ATTR_NAME: "Particulate Matter < 2.5 μm",
|
||||||
|
CONF_UNIT_SYSTEM_IMPERIAL: "μg/ft³",
|
||||||
|
CONF_UNIT_SYSTEM_METRIC: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_METRIC_CONVERSION: 1 / (3.2808399 ** 3),
|
||||||
|
ATTR_IS_METRIC_CHECK: False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_V3_ATTR_PARTICULATE_MATTER_10,
|
||||||
|
ATTR_NAME: "Particulate Matter < 10 μm",
|
||||||
|
CONF_UNIT_SYSTEM_IMPERIAL: "μg/ft³",
|
||||||
|
CONF_UNIT_SYSTEM_METRIC: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_METRIC_CONVERSION: 1 / (3.2808399 ** 3),
|
||||||
|
ATTR_IS_METRIC_CHECK: False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_V3_ATTR_NITROGEN_DIOXIDE,
|
||||||
|
ATTR_NAME: "Nitrogen Dioxide",
|
||||||
|
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_V3_ATTR_CARBON_MONOXIDE,
|
||||||
|
ATTR_NAME: "Carbon Monoxide",
|
||||||
|
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_MILLION,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_V3_ATTR_SULFUR_DIOXIDE,
|
||||||
|
ATTR_NAME: "Sulfur Dioxide",
|
||||||
|
CONF_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
},
|
||||||
|
{ATTR_FIELD: CC_V3_ATTR_EPA_AQI, ATTR_NAME: "US EPA Air Quality Index"},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_V3_ATTR_EPA_PRIMARY_POLLUTANT,
|
||||||
|
ATTR_NAME: "US EPA Primary Pollutant",
|
||||||
|
},
|
||||||
|
{ATTR_FIELD: CC_V3_ATTR_EPA_HEALTH_CONCERN, ATTR_NAME: "US EPA Health Concern"},
|
||||||
|
{ATTR_FIELD: CC_V3_ATTR_CHINA_AQI, ATTR_NAME: "China MEP Air Quality Index"},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_V3_ATTR_CHINA_PRIMARY_POLLUTANT,
|
||||||
|
ATTR_NAME: "China MEP Primary Pollutant",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_FIELD: CC_V3_ATTR_CHINA_HEALTH_CONCERN,
|
||||||
|
ATTR_NAME: "China MEP Health Concern",
|
||||||
|
},
|
||||||
|
{ATTR_FIELD: CC_V3_ATTR_POLLEN_TREE, ATTR_NAME: "Tree Pollen Index"},
|
||||||
|
{ATTR_FIELD: CC_V3_ATTR_POLLEN_WEED, ATTR_NAME: "Weed Pollen Index"},
|
||||||
|
{ATTR_FIELD: CC_V3_ATTR_POLLEN_GRASS, ATTR_NAME: "Grass Pollen Index"},
|
||||||
|
{ATTR_FIELD: CC_V3_ATTR_FIRE_INDEX, ATTR_NAME: "Fire Index"},
|
||||||
|
]
|
||||||
|
152
homeassistant/components/climacell/sensor.py
Normal file
152
homeassistant/components/climacell/sensor.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
"""Sensor component that handles additional ClimaCell data for your location."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import abstractmethod
|
||||||
|
import logging
|
||||||
|
from typing import Any, Callable, Mapping
|
||||||
|
|
||||||
|
from pyclimacell.const import CURRENT
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ATTRIBUTION,
|
||||||
|
ATTR_NAME,
|
||||||
|
CONF_API_VERSION,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_UNIT_SYSTEM_IMPERIAL,
|
||||||
|
CONF_UNIT_SYSTEM_METRIC,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
|
from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity
|
||||||
|
from .const import (
|
||||||
|
ATTR_FIELD,
|
||||||
|
ATTR_IS_METRIC_CHECK,
|
||||||
|
ATTR_METRIC_CONVERSION,
|
||||||
|
ATTR_VALUE_MAP,
|
||||||
|
CC_SENSOR_TYPES,
|
||||||
|
CC_V3_SENSOR_TYPES,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistantType,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: Callable[[list[Entity], bool], None],
|
||||||
|
) -> None:
|
||||||
|
"""Set up a config entry."""
|
||||||
|
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
api_version = config_entry.data[CONF_API_VERSION]
|
||||||
|
|
||||||
|
if api_version == 3:
|
||||||
|
api_class = ClimaCellV3SensorEntity
|
||||||
|
sensor_types = CC_V3_SENSOR_TYPES
|
||||||
|
else:
|
||||||
|
api_class = ClimaCellSensorEntity
|
||||||
|
sensor_types = CC_SENSOR_TYPES
|
||||||
|
entities = [
|
||||||
|
api_class(config_entry, coordinator, api_version, sensor_type)
|
||||||
|
for sensor_type in sensor_types
|
||||||
|
]
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseClimaCellSensorEntity(ClimaCellEntity, SensorEntity):
|
||||||
|
"""Base ClimaCell sensor entity."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
coordinator: ClimaCellDataUpdateCoordinator,
|
||||||
|
api_version: int,
|
||||||
|
sensor_type: dict[str, str | float],
|
||||||
|
) -> None:
|
||||||
|
"""Initialize ClimaCell Sensor Entity."""
|
||||||
|
super().__init__(config_entry, coordinator, api_version)
|
||||||
|
self.sensor_type = sensor_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entity_registry_enabled_default(self) -> bool:
|
||||||
|
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name of the entity."""
|
||||||
|
return f"{self._config_entry.data[CONF_NAME]} - {self.sensor_type[ATTR_NAME]}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
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
|
||||||
|
):
|
||||||
|
if self.hass.config.units.is_metric:
|
||||||
|
return self.sensor_type[CONF_UNIT_SYSTEM_METRIC]
|
||||||
|
return self.sensor_type[CONF_UNIT_SYSTEM_IMPERIAL]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def _state(self) -> str | int | float | None:
|
||||||
|
"""Return the raw state."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> str | int | float | None:
|
||||||
|
"""Return the state."""
|
||||||
|
if (
|
||||||
|
self._state is not None
|
||||||
|
and CONF_UNIT_SYSTEM_IMPERIAL in self.sensor_type
|
||||||
|
and CONF_UNIT_SYSTEM_METRIC in self.sensor_type
|
||||||
|
and ATTR_METRIC_CONVERSION in self.sensor_type
|
||||||
|
and ATTR_IS_METRIC_CHECK in self.sensor_type
|
||||||
|
and self.hass.config.units.is_metric
|
||||||
|
== self.sensor_type[ATTR_IS_METRIC_CHECK]
|
||||||
|
):
|
||||||
|
return round(self._state * self.sensor_type[ATTR_METRIC_CONVERSION], 4)
|
||||||
|
|
||||||
|
if ATTR_VALUE_MAP in self.sensor_type:
|
||||||
|
return self.sensor_type[ATTR_VALUE_MAP](self._state).name.lower()
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
|
||||||
|
class ClimaCellSensorEntity(BaseClimaCellSensorEntity):
|
||||||
|
"""Sensor entity that talks to ClimaCell v4 API to retrieve non-weather data."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _state(self) -> str | int | float | None:
|
||||||
|
"""Return the raw state."""
|
||||||
|
return self._get_current_property(self.sensor_type[ATTR_FIELD])
|
||||||
|
|
||||||
|
|
||||||
|
class ClimaCellV3SensorEntity(BaseClimaCellSensorEntity):
|
||||||
|
"""Sensor entity that talks to ClimaCell v3 API to retrieve non-weather data."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _state(self) -> str | int | float | None:
|
||||||
|
"""Return the raw state."""
|
||||||
|
return self._get_cc_value(
|
||||||
|
self.coordinator.data[CURRENT], self.sensor_type[ATTR_FIELD]
|
||||||
|
)
|
@ -1,6 +1,7 @@
|
|||||||
"""Weather component that handles meteorological data for your location."""
|
"""Weather component that handles meteorological data for your location."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import abstractmethod
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Callable, Mapping
|
from typing import Any, Callable, Mapping
|
||||||
@ -29,6 +30,7 @@ from homeassistant.components.weather import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_VERSION,
|
CONF_API_VERSION,
|
||||||
|
CONF_NAME,
|
||||||
LENGTH_FEET,
|
LENGTH_FEET,
|
||||||
LENGTH_KILOMETERS,
|
LENGTH_KILOMETERS,
|
||||||
LENGTH_METERS,
|
LENGTH_METERS,
|
||||||
@ -44,7 +46,7 @@ from homeassistant.util import dt as dt_util
|
|||||||
from homeassistant.util.distance import convert as distance_convert
|
from homeassistant.util.distance import convert as distance_convert
|
||||||
from homeassistant.util.pressure import convert as pressure_convert
|
from homeassistant.util.pressure import convert as pressure_convert
|
||||||
|
|
||||||
from . import ClimaCellEntity
|
from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_CLOUD_COVER,
|
ATTR_CLOUD_COVER,
|
||||||
ATTR_PRECIPITATION_TYPE,
|
ATTR_PRECIPITATION_TYPE,
|
||||||
@ -86,12 +88,11 @@ from .const import (
|
|||||||
CONDITIONS,
|
CONDITIONS,
|
||||||
CONDITIONS_V3,
|
CONDITIONS_V3,
|
||||||
CONF_TIMESTEP,
|
CONF_TIMESTEP,
|
||||||
|
DEFAULT_FORECAST_TYPE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
MAX_FORECASTS,
|
MAX_FORECASTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -106,7 +107,7 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
api_class = ClimaCellV3WeatherEntity if api_version == 3 else ClimaCellWeatherEntity
|
api_class = ClimaCellV3WeatherEntity if api_version == 3 else ClimaCellWeatherEntity
|
||||||
entities = [
|
entities = [
|
||||||
api_class(config_entry, coordinator, forecast_type, api_version)
|
api_class(config_entry, coordinator, api_version, forecast_type)
|
||||||
for forecast_type in [DAILY, HOURLY, NOWCAST]
|
for forecast_type in [DAILY, HOURLY, NOWCAST]
|
||||||
]
|
]
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
@ -115,12 +116,41 @@ async def async_setup_entry(
|
|||||||
class BaseClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity):
|
class BaseClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity):
|
||||||
"""Base ClimaCell weather entity."""
|
"""Base ClimaCell weather entity."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
coordinator: ClimaCellDataUpdateCoordinator,
|
||||||
|
api_version: int,
|
||||||
|
forecast_type: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize ClimaCell Weather Entity."""
|
||||||
|
super().__init__(config_entry, coordinator, api_version)
|
||||||
|
self.forecast_type = forecast_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entity_registry_enabled_default(self) -> bool:
|
||||||
|
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||||
|
if self.forecast_type == DEFAULT_FORECAST_TYPE:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name of the entity."""
|
||||||
|
return f"{self._config_entry.data[CONF_NAME]} - {self.forecast_type.title()}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return the unique id of the entity."""
|
||||||
|
return f"{self._config_entry.unique_id}_{self.forecast_type}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
def _translate_condition(
|
def _translate_condition(
|
||||||
condition: int | None, sun_is_up: bool = True
|
condition: int | None, sun_is_up: bool = True
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""Translate ClimaCell condition into an HA condition."""
|
"""Translate ClimaCell condition into an HA condition."""
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _forecast_dict(
|
def _forecast_dict(
|
||||||
self,
|
self,
|
||||||
@ -144,13 +174,14 @@ class BaseClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity):
|
|||||||
|
|
||||||
if self.hass.config.units.is_metric:
|
if self.hass.config.units.is_metric:
|
||||||
if precipitation:
|
if precipitation:
|
||||||
precipitation = (
|
precipitation = round(
|
||||||
distance_convert(precipitation / 12, LENGTH_FEET, LENGTH_METERS)
|
distance_convert(precipitation / 12, LENGTH_FEET, LENGTH_METERS)
|
||||||
* 1000
|
* 1000,
|
||||||
|
4,
|
||||||
)
|
)
|
||||||
if wind_speed:
|
if wind_speed:
|
||||||
wind_speed = distance_convert(
|
wind_speed = round(
|
||||||
wind_speed, LENGTH_MILES, LENGTH_KILOMETERS
|
distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS), 4
|
||||||
)
|
)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -171,8 +202,8 @@ class BaseClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity):
|
|||||||
"""Return additional state attributes."""
|
"""Return additional state attributes."""
|
||||||
wind_gust = self.wind_gust
|
wind_gust = self.wind_gust
|
||||||
if wind_gust and self.hass.config.units.is_metric:
|
if wind_gust and self.hass.config.units.is_metric:
|
||||||
wind_gust = distance_convert(
|
wind_gust = round(
|
||||||
self.wind_gust, LENGTH_MILES, LENGTH_KILOMETERS
|
distance_convert(self.wind_gust, LENGTH_MILES, LENGTH_KILOMETERS), 4
|
||||||
)
|
)
|
||||||
cloud_cover = self.cloud_cover
|
cloud_cover = self.cloud_cover
|
||||||
if cloud_cover is not None:
|
if cloud_cover is not None:
|
||||||
@ -184,19 +215,61 @@ class BaseClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@abstractmethod
|
||||||
def cloud_cover(self):
|
def cloud_cover(self):
|
||||||
"""Return cloud cover."""
|
"""Return cloud cover."""
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@abstractmethod
|
||||||
def wind_gust(self):
|
def wind_gust(self):
|
||||||
"""Return wind gust speed."""
|
"""Return wind gust speed."""
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@abstractmethod
|
||||||
def precipitation_type(self):
|
def precipitation_type(self):
|
||||||
"""Return precipitation type."""
|
"""Return precipitation type."""
|
||||||
raise NotImplementedError
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def _pressure(self):
|
||||||
|
"""Return the raw pressure."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pressure(self):
|
||||||
|
"""Return the pressure."""
|
||||||
|
if self.hass.config.units.is_metric and self._pressure:
|
||||||
|
return round(
|
||||||
|
pressure_convert(self._pressure, PRESSURE_INHG, PRESSURE_HPA), 4
|
||||||
|
)
|
||||||
|
return self._pressure
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def _wind_speed(self):
|
||||||
|
"""Return the raw wind speed."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wind_speed(self):
|
||||||
|
"""Return the wind speed."""
|
||||||
|
if self.hass.config.units.is_metric and self._wind_speed:
|
||||||
|
return round(
|
||||||
|
distance_convert(self._wind_speed, LENGTH_MILES, LENGTH_KILOMETERS), 4
|
||||||
|
)
|
||||||
|
return self._wind_speed
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def _visibility(self):
|
||||||
|
"""Return the raw visibility."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def visibility(self):
|
||||||
|
"""Return the visibility."""
|
||||||
|
if self.hass.config.units.is_metric and self._visibility:
|
||||||
|
return round(
|
||||||
|
distance_convert(self._visibility, LENGTH_MILES, LENGTH_KILOMETERS), 4
|
||||||
|
)
|
||||||
|
return self._visibility
|
||||||
|
|
||||||
|
|
||||||
class ClimaCellWeatherEntity(BaseClimaCellWeatherEntity):
|
class ClimaCellWeatherEntity(BaseClimaCellWeatherEntity):
|
||||||
@ -217,10 +290,6 @@ class ClimaCellWeatherEntity(BaseClimaCellWeatherEntity):
|
|||||||
return CLEAR_CONDITIONS["night"]
|
return CLEAR_CONDITIONS["night"]
|
||||||
return CONDITIONS[condition]
|
return CONDITIONS[condition]
|
||||||
|
|
||||||
def _get_current_property(self, property_name: str) -> int | str | float | None:
|
|
||||||
"""Get property from current conditions."""
|
|
||||||
return self.coordinator.data.get(CURRENT, {}).get(property_name)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature(self):
|
def temperature(self):
|
||||||
"""Return the platform temperature."""
|
"""Return the platform temperature."""
|
||||||
@ -232,12 +301,9 @@ class ClimaCellWeatherEntity(BaseClimaCellWeatherEntity):
|
|||||||
return TEMP_FAHRENHEIT
|
return TEMP_FAHRENHEIT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pressure(self):
|
def _pressure(self):
|
||||||
"""Return the pressure."""
|
"""Return the raw pressure."""
|
||||||
pressure = self._get_current_property(CC_ATTR_PRESSURE)
|
return self._get_current_property(CC_ATTR_PRESSURE)
|
||||||
if self.hass.config.units.is_metric and pressure:
|
|
||||||
return pressure_convert(pressure, PRESSURE_INHG, PRESSURE_HPA)
|
|
||||||
return pressure
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def humidity(self):
|
def humidity(self):
|
||||||
@ -263,12 +329,9 @@ class ClimaCellWeatherEntity(BaseClimaCellWeatherEntity):
|
|||||||
return PrecipitationType(precipitation_type).name.lower()
|
return PrecipitationType(precipitation_type).name.lower()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wind_speed(self):
|
def _wind_speed(self):
|
||||||
"""Return the wind speed."""
|
"""Return the raw wind speed."""
|
||||||
wind_speed = self._get_current_property(CC_ATTR_WIND_SPEED)
|
return self._get_current_property(CC_ATTR_WIND_SPEED)
|
||||||
if self.hass.config.units.is_metric and wind_speed:
|
|
||||||
return distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS)
|
|
||||||
return wind_speed
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wind_bearing(self):
|
def wind_bearing(self):
|
||||||
@ -289,12 +352,9 @@ class ClimaCellWeatherEntity(BaseClimaCellWeatherEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def visibility(self):
|
def _visibility(self):
|
||||||
"""Return the visibility."""
|
"""Return the raw visibility."""
|
||||||
visibility = self._get_current_property(CC_ATTR_VISIBILITY)
|
return self._get_current_property(CC_ATTR_VISIBILITY)
|
||||||
if self.hass.config.units.is_metric and visibility:
|
|
||||||
return distance_convert(visibility, LENGTH_MILES, LENGTH_KILOMETERS)
|
|
||||||
return visibility
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def forecast(self):
|
def forecast(self):
|
||||||
@ -391,14 +451,9 @@ class ClimaCellV3WeatherEntity(BaseClimaCellWeatherEntity):
|
|||||||
return TEMP_FAHRENHEIT
|
return TEMP_FAHRENHEIT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pressure(self):
|
def _pressure(self):
|
||||||
"""Return the pressure."""
|
"""Return the raw pressure."""
|
||||||
pressure = self._get_cc_value(
|
return self._get_cc_value(self.coordinator.data[CURRENT], CC_V3_ATTR_PRESSURE)
|
||||||
self.coordinator.data[CURRENT], CC_V3_ATTR_PRESSURE
|
|
||||||
)
|
|
||||||
if self.hass.config.units.is_metric and pressure:
|
|
||||||
return pressure_convert(pressure, PRESSURE_INHG, PRESSURE_HPA)
|
|
||||||
return pressure
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def humidity(self):
|
def humidity(self):
|
||||||
@ -425,14 +480,9 @@ class ClimaCellV3WeatherEntity(BaseClimaCellWeatherEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wind_speed(self):
|
def _wind_speed(self):
|
||||||
"""Return the wind speed."""
|
"""Return the raw wind speed."""
|
||||||
wind_speed = self._get_cc_value(
|
return self._get_cc_value(self.coordinator.data[CURRENT], CC_V3_ATTR_WIND_SPEED)
|
||||||
self.coordinator.data[CURRENT], CC_V3_ATTR_WIND_SPEED
|
|
||||||
)
|
|
||||||
if self.hass.config.units.is_metric and wind_speed:
|
|
||||||
return distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS)
|
|
||||||
return wind_speed
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wind_bearing(self):
|
def wind_bearing(self):
|
||||||
@ -455,14 +505,9 @@ class ClimaCellV3WeatherEntity(BaseClimaCellWeatherEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def visibility(self):
|
def _visibility(self):
|
||||||
"""Return the visibility."""
|
"""Return the raw visibility."""
|
||||||
visibility = self._get_cc_value(
|
return self._get_cc_value(self.coordinator.data[CURRENT], CC_V3_ATTR_VISIBILITY)
|
||||||
self.coordinator.data[CURRENT], CC_V3_ATTR_VISIBILITY
|
|
||||||
)
|
|
||||||
if self.hass.config.units.is_metric and visibility:
|
|
||||||
return distance_convert(visibility, LENGTH_MILES, LENGTH_KILOMETERS)
|
|
||||||
return visibility
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def forecast(self):
|
def forecast(self):
|
||||||
|
148
tests/components/climacell/test_sensor.py
Normal file
148
tests/components/climacell/test_sensor.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
"""Tests for Climacell sensor entities."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
from homeassistant.components.climacell.config_flow import (
|
||||||
|
_get_config_schema,
|
||||||
|
_get_unique_id,
|
||||||
|
)
|
||||||
|
from homeassistant.components.climacell.const import ATTRIBUTION, DOMAIN
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
|
from homeassistant.const import ATTR_ATTRIBUTION
|
||||||
|
from homeassistant.core import State, callback
|
||||||
|
from homeassistant.helpers.entity_registry import async_get
|
||||||
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
|
from .const import API_V3_ENTRY_DATA, API_V4_ENTRY_DATA
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
CC_SENSOR_ENTITY_ID = "sensor.climacell_{}"
|
||||||
|
|
||||||
|
CO = "carbon_monoxide"
|
||||||
|
NO2 = "nitrogen_dioxide"
|
||||||
|
SO2 = "sulfur_dioxide"
|
||||||
|
PM25 = "particulate_matter_2_5_mm"
|
||||||
|
PM10 = "particulate_matter_10_mm"
|
||||||
|
MEP_AQI = "china_mep_air_quality_index"
|
||||||
|
MEP_HEALTH_CONCERN = "china_mep_health_concern"
|
||||||
|
MEP_PRIMARY_POLLUTANT = "china_mep_primary_pollutant"
|
||||||
|
EPA_AQI = "us_epa_air_quality_index"
|
||||||
|
EPA_HEALTH_CONCERN = "us_epa_health_concern"
|
||||||
|
EPA_PRIMARY_POLLUTANT = "us_epa_primary_pollutant"
|
||||||
|
FIRE_INDEX = "fire_index"
|
||||||
|
GRASS_POLLEN = "grass_pollen_index"
|
||||||
|
WEED_POLLEN = "weed_pollen_index"
|
||||||
|
TREE_POLLEN = "tree_pollen_index"
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _enable_entity(hass: HomeAssistantType, entity_name: str) -> None:
|
||||||
|
"""Enable disabled entity."""
|
||||||
|
ent_reg = async_get(hass)
|
||||||
|
entry = ent_reg.async_get(entity_name)
|
||||||
|
updated_entry = ent_reg.async_update_entity(
|
||||||
|
entry.entity_id, **{"disabled_by": None}
|
||||||
|
)
|
||||||
|
assert updated_entry != entry
|
||||||
|
assert updated_entry.disabled is False
|
||||||
|
|
||||||
|
|
||||||
|
async def _setup(hass: HomeAssistantType, config: dict[str, Any]) -> State:
|
||||||
|
"""Set up entry and return entity state."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.util.dt.utcnow",
|
||||||
|
return_value=datetime(2021, 3, 6, 23, 59, 59, tzinfo=pytz.UTC),
|
||||||
|
):
|
||||||
|
data = _get_config_schema(hass)(config)
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=data,
|
||||||
|
unique_id=_get_unique_id(hass, data),
|
||||||
|
version=1,
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
for entity_name in (
|
||||||
|
CO,
|
||||||
|
NO2,
|
||||||
|
SO2,
|
||||||
|
PM25,
|
||||||
|
PM10,
|
||||||
|
MEP_AQI,
|
||||||
|
MEP_HEALTH_CONCERN,
|
||||||
|
MEP_PRIMARY_POLLUTANT,
|
||||||
|
EPA_AQI,
|
||||||
|
EPA_HEALTH_CONCERN,
|
||||||
|
EPA_PRIMARY_POLLUTANT,
|
||||||
|
FIRE_INDEX,
|
||||||
|
GRASS_POLLEN,
|
||||||
|
WEED_POLLEN,
|
||||||
|
TREE_POLLEN,
|
||||||
|
):
|
||||||
|
_enable_entity(hass, CC_SENSOR_ENTITY_ID.format(entity_name))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 15
|
||||||
|
|
||||||
|
|
||||||
|
def check_sensor_state(hass: HomeAssistantType, entity_name: str, value: str):
|
||||||
|
"""Check the state of a ClimaCell sensor."""
|
||||||
|
state = hass.states.get(CC_SENSOR_ENTITY_ID.format(entity_name))
|
||||||
|
assert state
|
||||||
|
assert state.state == value
|
||||||
|
assert state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION
|
||||||
|
|
||||||
|
|
||||||
|
async def test_v3_sensor(
|
||||||
|
hass: HomeAssistantType,
|
||||||
|
climacell_config_entry_update: pytest.fixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test v3 sensor data."""
|
||||||
|
await _setup(hass, API_V3_ENTRY_DATA)
|
||||||
|
check_sensor_state(hass, CO, "0.875")
|
||||||
|
check_sensor_state(hass, NO2, "14.1875")
|
||||||
|
check_sensor_state(hass, SO2, "2")
|
||||||
|
check_sensor_state(hass, PM25, "5.3125")
|
||||||
|
check_sensor_state(hass, PM10, "27")
|
||||||
|
check_sensor_state(hass, MEP_AQI, "27")
|
||||||
|
check_sensor_state(hass, MEP_HEALTH_CONCERN, "Good")
|
||||||
|
check_sensor_state(hass, MEP_PRIMARY_POLLUTANT, "pm10")
|
||||||
|
check_sensor_state(hass, EPA_AQI, "22.3125")
|
||||||
|
check_sensor_state(hass, EPA_HEALTH_CONCERN, "Good")
|
||||||
|
check_sensor_state(hass, EPA_PRIMARY_POLLUTANT, "pm25")
|
||||||
|
check_sensor_state(hass, FIRE_INDEX, "9")
|
||||||
|
check_sensor_state(hass, GRASS_POLLEN, "0")
|
||||||
|
check_sensor_state(hass, WEED_POLLEN, "0")
|
||||||
|
check_sensor_state(hass, TREE_POLLEN, "0")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_v4_sensor(
|
||||||
|
hass: HomeAssistantType,
|
||||||
|
climacell_config_entry_update: pytest.fixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test v4 sensor data."""
|
||||||
|
await _setup(hass, API_V4_ENTRY_DATA)
|
||||||
|
check_sensor_state(hass, CO, "0.63")
|
||||||
|
check_sensor_state(hass, NO2, "10.67")
|
||||||
|
check_sensor_state(hass, SO2, "1.65")
|
||||||
|
check_sensor_state(hass, PM25, "5.2972")
|
||||||
|
check_sensor_state(hass, PM10, "20.1294")
|
||||||
|
check_sensor_state(hass, MEP_AQI, "23")
|
||||||
|
check_sensor_state(hass, MEP_HEALTH_CONCERN, "good")
|
||||||
|
check_sensor_state(hass, MEP_PRIMARY_POLLUTANT, "pm10")
|
||||||
|
check_sensor_state(hass, EPA_AQI, "24")
|
||||||
|
check_sensor_state(hass, EPA_HEALTH_CONCERN, "good")
|
||||||
|
check_sensor_state(hass, EPA_PRIMARY_POLLUTANT, "pm25")
|
||||||
|
check_sensor_state(hass, FIRE_INDEX, "10")
|
||||||
|
check_sensor_state(hass, GRASS_POLLEN, "none")
|
||||||
|
check_sensor_state(hass, WEED_POLLEN, "none")
|
||||||
|
check_sensor_state(hass, TREE_POLLEN, "none")
|
@ -44,7 +44,7 @@ from homeassistant.components.weather import (
|
|||||||
DOMAIN as WEATHER_DOMAIN,
|
DOMAIN as WEATHER_DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME
|
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME
|
||||||
from homeassistant.core import State
|
from homeassistant.core import State, callback
|
||||||
from homeassistant.helpers.entity_registry import async_get
|
from homeassistant.helpers.entity_registry import async_get
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
@ -55,7 +55,8 @@ from tests.common import MockConfigEntry
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def _enable_entity(hass: HomeAssistantType, entity_name: str) -> None:
|
@callback
|
||||||
|
def _enable_entity(hass: HomeAssistantType, entity_name: str) -> None:
|
||||||
"""Enable disabled entity."""
|
"""Enable disabled entity."""
|
||||||
ent_reg = async_get(hass)
|
ent_reg = async_get(hass)
|
||||||
entry = ent_reg.async_get(entity_name)
|
entry = ent_reg.async_get(entity_name)
|
||||||
@ -82,8 +83,8 @@ async def _setup(hass: HomeAssistantType, config: dict[str, Any]) -> State:
|
|||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await _enable_entity(hass, "weather.climacell_hourly")
|
for entity_name in ("hourly", "nowcast"):
|
||||||
await _enable_entity(hass, "weather.climacell_nowcast")
|
_enable_entity(hass, f"weather.climacell_{entity_name}")
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 3
|
assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 3
|
||||||
|
|
||||||
@ -142,7 +143,7 @@ async def test_v3_weather(
|
|||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
||||||
ATTR_FORECAST_TIME: "2021-03-12T00:00:00+00:00",
|
ATTR_FORECAST_TIME: "2021-03-12T00:00:00+00:00",
|
||||||
ATTR_FORECAST_PRECIPITATION: 0.04572,
|
ATTR_FORECAST_PRECIPITATION: 0.0457,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25,
|
||||||
ATTR_FORECAST_TEMP: 20,
|
ATTR_FORECAST_TEMP: 20,
|
||||||
ATTR_FORECAST_TEMP_LOW: 12,
|
ATTR_FORECAST_TEMP_LOW: 12,
|
||||||
@ -158,7 +159,7 @@ async def test_v3_weather(
|
|||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_RAINY,
|
ATTR_FORECAST_CONDITION: ATTR_CONDITION_RAINY,
|
||||||
ATTR_FORECAST_TIME: "2021-03-14T00:00:00+00:00",
|
ATTR_FORECAST_TIME: "2021-03-14T00:00:00+00:00",
|
||||||
ATTR_FORECAST_PRECIPITATION: 1.07442,
|
ATTR_FORECAST_PRECIPITATION: 1.0744,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 75,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 75,
|
||||||
ATTR_FORECAST_TEMP: 6,
|
ATTR_FORECAST_TEMP: 6,
|
||||||
ATTR_FORECAST_TEMP_LOW: 3,
|
ATTR_FORECAST_TEMP_LOW: 3,
|
||||||
@ -166,7 +167,7 @@ async def test_v3_weather(
|
|||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_SNOWY,
|
ATTR_FORECAST_CONDITION: ATTR_CONDITION_SNOWY,
|
||||||
ATTR_FORECAST_TIME: "2021-03-15T00:00:00+00:00",
|
ATTR_FORECAST_TIME: "2021-03-15T00:00:00+00:00",
|
||||||
ATTR_FORECAST_PRECIPITATION: 7.305040000000001,
|
ATTR_FORECAST_PRECIPITATION: 7.3050,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 95,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 95,
|
||||||
ATTR_FORECAST_TEMP: 1,
|
ATTR_FORECAST_TEMP: 1,
|
||||||
ATTR_FORECAST_TEMP_LOW: 0,
|
ATTR_FORECAST_TEMP_LOW: 0,
|
||||||
@ -174,7 +175,7 @@ async def test_v3_weather(
|
|||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
||||||
ATTR_FORECAST_TIME: "2021-03-16T00:00:00+00:00",
|
ATTR_FORECAST_TIME: "2021-03-16T00:00:00+00:00",
|
||||||
ATTR_FORECAST_PRECIPITATION: 0.00508,
|
ATTR_FORECAST_PRECIPITATION: 0.0051,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 5,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 5,
|
||||||
ATTR_FORECAST_TEMP: 6,
|
ATTR_FORECAST_TEMP: 6,
|
||||||
ATTR_FORECAST_TEMP_LOW: -2,
|
ATTR_FORECAST_TEMP_LOW: -2,
|
||||||
@ -214,7 +215,7 @@ async def test_v3_weather(
|
|||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
||||||
ATTR_FORECAST_TIME: "2021-03-21T00:00:00+00:00",
|
ATTR_FORECAST_TIME: "2021-03-21T00:00:00+00:00",
|
||||||
ATTR_FORECAST_PRECIPITATION: 0.043179999999999996,
|
ATTR_FORECAST_PRECIPITATION: 0.0432,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 20,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 20,
|
||||||
ATTR_FORECAST_TEMP: 7,
|
ATTR_FORECAST_TEMP: 7,
|
||||||
ATTR_FORECAST_TEMP_LOW: 1,
|
ATTR_FORECAST_TEMP_LOW: 1,
|
||||||
@ -223,13 +224,13 @@ async def test_v3_weather(
|
|||||||
assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "ClimaCell - Daily"
|
assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "ClimaCell - Daily"
|
||||||
assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 24
|
assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 24
|
||||||
assert weather_state.attributes[ATTR_WEATHER_OZONE] == 52.625
|
assert weather_state.attributes[ATTR_WEATHER_OZONE] == 52.625
|
||||||
assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1028.124632345
|
assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1028.1246
|
||||||
assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 7
|
assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 7
|
||||||
assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 9.994026240000002
|
assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 9.9940
|
||||||
assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 320.31
|
assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 320.31
|
||||||
assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 14.62893696
|
assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 14.6289
|
||||||
assert weather_state.attributes[ATTR_CLOUD_COVER] == 1
|
assert weather_state.attributes[ATTR_CLOUD_COVER] == 1
|
||||||
assert weather_state.attributes[ATTR_WIND_GUST] == 24.075786240000003
|
assert weather_state.attributes[ATTR_WIND_GUST] == 24.0758
|
||||||
assert weather_state.attributes[ATTR_PRECIPITATION_TYPE] == "rain"
|
assert weather_state.attributes[ATTR_PRECIPITATION_TYPE] == "rain"
|
||||||
|
|
||||||
|
|
||||||
@ -250,7 +251,7 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 8,
|
ATTR_FORECAST_TEMP: 8,
|
||||||
ATTR_FORECAST_TEMP_LOW: -3,
|
ATTR_FORECAST_TEMP_LOW: -3,
|
||||||
ATTR_FORECAST_WIND_BEARING: 239.6,
|
ATTR_FORECAST_WIND_BEARING: 239.6,
|
||||||
ATTR_FORECAST_WIND_SPEED: 15.272674560000002,
|
ATTR_FORECAST_WIND_SPEED: 15.2727,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "cloudy",
|
ATTR_FORECAST_CONDITION: "cloudy",
|
||||||
@ -260,7 +261,7 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 10,
|
ATTR_FORECAST_TEMP: 10,
|
||||||
ATTR_FORECAST_TEMP_LOW: -3,
|
ATTR_FORECAST_TEMP_LOW: -3,
|
||||||
ATTR_FORECAST_WIND_BEARING: 262.82,
|
ATTR_FORECAST_WIND_BEARING: 262.82,
|
||||||
ATTR_FORECAST_WIND_SPEED: 11.65165056,
|
ATTR_FORECAST_WIND_SPEED: 11.6517,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "cloudy",
|
ATTR_FORECAST_CONDITION: "cloudy",
|
||||||
@ -270,7 +271,7 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 19,
|
ATTR_FORECAST_TEMP: 19,
|
||||||
ATTR_FORECAST_TEMP_LOW: 0,
|
ATTR_FORECAST_TEMP_LOW: 0,
|
||||||
ATTR_FORECAST_WIND_BEARING: 229.3,
|
ATTR_FORECAST_WIND_BEARING: 229.3,
|
||||||
ATTR_FORECAST_WIND_SPEED: 11.3458752,
|
ATTR_FORECAST_WIND_SPEED: 11.3459,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "cloudy",
|
ATTR_FORECAST_CONDITION: "cloudy",
|
||||||
@ -280,7 +281,7 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 18,
|
ATTR_FORECAST_TEMP: 18,
|
||||||
ATTR_FORECAST_TEMP_LOW: 3,
|
ATTR_FORECAST_TEMP_LOW: 3,
|
||||||
ATTR_FORECAST_WIND_BEARING: 149.91,
|
ATTR_FORECAST_WIND_BEARING: 149.91,
|
||||||
ATTR_FORECAST_WIND_SPEED: 17.123420160000002,
|
ATTR_FORECAST_WIND_SPEED: 17.1234,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "cloudy",
|
ATTR_FORECAST_CONDITION: "cloudy",
|
||||||
@ -290,17 +291,17 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 19,
|
ATTR_FORECAST_TEMP: 19,
|
||||||
ATTR_FORECAST_TEMP_LOW: 9,
|
ATTR_FORECAST_TEMP_LOW: 9,
|
||||||
ATTR_FORECAST_WIND_BEARING: 210.45,
|
ATTR_FORECAST_WIND_BEARING: 210.45,
|
||||||
ATTR_FORECAST_WIND_SPEED: 25.250607360000004,
|
ATTR_FORECAST_WIND_SPEED: 25.2506,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "rainy",
|
ATTR_FORECAST_CONDITION: "rainy",
|
||||||
ATTR_FORECAST_TIME: "2021-03-12T11:00:00+00:00",
|
ATTR_FORECAST_TIME: "2021-03-12T11:00:00+00:00",
|
||||||
ATTR_FORECAST_PRECIPITATION: 0.12192000000000001,
|
ATTR_FORECAST_PRECIPITATION: 0.1219,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25,
|
||||||
ATTR_FORECAST_TEMP: 20,
|
ATTR_FORECAST_TEMP: 20,
|
||||||
ATTR_FORECAST_TEMP_LOW: 12,
|
ATTR_FORECAST_TEMP_LOW: 12,
|
||||||
ATTR_FORECAST_WIND_BEARING: 217.98,
|
ATTR_FORECAST_WIND_BEARING: 217.98,
|
||||||
ATTR_FORECAST_WIND_SPEED: 19.794931200000004,
|
ATTR_FORECAST_WIND_SPEED: 19.7949,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "cloudy",
|
ATTR_FORECAST_CONDITION: "cloudy",
|
||||||
@ -310,27 +311,27 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 12,
|
ATTR_FORECAST_TEMP: 12,
|
||||||
ATTR_FORECAST_TEMP_LOW: 6,
|
ATTR_FORECAST_TEMP_LOW: 6,
|
||||||
ATTR_FORECAST_WIND_BEARING: 58.79,
|
ATTR_FORECAST_WIND_BEARING: 58.79,
|
||||||
ATTR_FORECAST_WIND_SPEED: 15.642823680000001,
|
ATTR_FORECAST_WIND_SPEED: 15.6428,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "snowy",
|
ATTR_FORECAST_CONDITION: "snowy",
|
||||||
ATTR_FORECAST_TIME: "2021-03-14T10:00:00+00:00",
|
ATTR_FORECAST_TIME: "2021-03-14T10:00:00+00:00",
|
||||||
ATTR_FORECAST_PRECIPITATION: 23.95728,
|
ATTR_FORECAST_PRECIPITATION: 23.9573,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 95,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 95,
|
||||||
ATTR_FORECAST_TEMP: 6,
|
ATTR_FORECAST_TEMP: 6,
|
||||||
ATTR_FORECAST_TEMP_LOW: 1,
|
ATTR_FORECAST_TEMP_LOW: 1,
|
||||||
ATTR_FORECAST_WIND_BEARING: 70.25,
|
ATTR_FORECAST_WIND_BEARING: 70.25,
|
||||||
ATTR_FORECAST_WIND_SPEED: 26.15184,
|
ATTR_FORECAST_WIND_SPEED: 26.1518,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "snowy",
|
ATTR_FORECAST_CONDITION: "snowy",
|
||||||
ATTR_FORECAST_TIME: "2021-03-15T10:00:00+00:00",
|
ATTR_FORECAST_TIME: "2021-03-15T10:00:00+00:00",
|
||||||
ATTR_FORECAST_PRECIPITATION: 1.46304,
|
ATTR_FORECAST_PRECIPITATION: 1.4630,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55,
|
||||||
ATTR_FORECAST_TEMP: 6,
|
ATTR_FORECAST_TEMP: 6,
|
||||||
ATTR_FORECAST_TEMP_LOW: -1,
|
ATTR_FORECAST_TEMP_LOW: -1,
|
||||||
ATTR_FORECAST_WIND_BEARING: 84.47,
|
ATTR_FORECAST_WIND_BEARING: 84.47,
|
||||||
ATTR_FORECAST_WIND_SPEED: 25.57247616,
|
ATTR_FORECAST_WIND_SPEED: 25.5725,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "cloudy",
|
ATTR_FORECAST_CONDITION: "cloudy",
|
||||||
@ -340,7 +341,7 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 6,
|
ATTR_FORECAST_TEMP: 6,
|
||||||
ATTR_FORECAST_TEMP_LOW: -2,
|
ATTR_FORECAST_TEMP_LOW: -2,
|
||||||
ATTR_FORECAST_WIND_BEARING: 103.85,
|
ATTR_FORECAST_WIND_BEARING: 103.85,
|
||||||
ATTR_FORECAST_WIND_SPEED: 10.79869824,
|
ATTR_FORECAST_WIND_SPEED: 10.7987,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "cloudy",
|
ATTR_FORECAST_CONDITION: "cloudy",
|
||||||
@ -350,7 +351,7 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 11,
|
ATTR_FORECAST_TEMP: 11,
|
||||||
ATTR_FORECAST_TEMP_LOW: 1,
|
ATTR_FORECAST_TEMP_LOW: 1,
|
||||||
ATTR_FORECAST_WIND_BEARING: 145.41,
|
ATTR_FORECAST_WIND_BEARING: 145.41,
|
||||||
ATTR_FORECAST_WIND_SPEED: 11.69993088,
|
ATTR_FORECAST_WIND_SPEED: 11.6999,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "cloudy",
|
ATTR_FORECAST_CONDITION: "cloudy",
|
||||||
@ -360,17 +361,17 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 12,
|
ATTR_FORECAST_TEMP: 12,
|
||||||
ATTR_FORECAST_TEMP_LOW: 5,
|
ATTR_FORECAST_TEMP_LOW: 5,
|
||||||
ATTR_FORECAST_WIND_BEARING: 62.99,
|
ATTR_FORECAST_WIND_BEARING: 62.99,
|
||||||
ATTR_FORECAST_WIND_SPEED: 10.58948352,
|
ATTR_FORECAST_WIND_SPEED: 10.5895,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "rainy",
|
ATTR_FORECAST_CONDITION: "rainy",
|
||||||
ATTR_FORECAST_TIME: "2021-03-19T10:00:00+00:00",
|
ATTR_FORECAST_TIME: "2021-03-19T10:00:00+00:00",
|
||||||
ATTR_FORECAST_PRECIPITATION: 2.92608,
|
ATTR_FORECAST_PRECIPITATION: 2.9261,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55,
|
||||||
ATTR_FORECAST_TEMP: 9,
|
ATTR_FORECAST_TEMP: 9,
|
||||||
ATTR_FORECAST_TEMP_LOW: 4,
|
ATTR_FORECAST_TEMP_LOW: 4,
|
||||||
ATTR_FORECAST_WIND_BEARING: 68.54,
|
ATTR_FORECAST_WIND_BEARING: 68.54,
|
||||||
ATTR_FORECAST_WIND_SPEED: 22.38597504,
|
ATTR_FORECAST_WIND_SPEED: 22.3860,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_CONDITION: "snowy",
|
ATTR_FORECAST_CONDITION: "snowy",
|
||||||
@ -380,17 +381,17 @@ async def test_v4_weather(
|
|||||||
ATTR_FORECAST_TEMP: 5,
|
ATTR_FORECAST_TEMP: 5,
|
||||||
ATTR_FORECAST_TEMP_LOW: 2,
|
ATTR_FORECAST_TEMP_LOW: 2,
|
||||||
ATTR_FORECAST_WIND_BEARING: 56.98,
|
ATTR_FORECAST_WIND_BEARING: 56.98,
|
||||||
ATTR_FORECAST_WIND_SPEED: 27.922118400000002,
|
ATTR_FORECAST_WIND_SPEED: 27.9221,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "ClimaCell - Daily"
|
assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "ClimaCell - Daily"
|
||||||
assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23
|
assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23
|
||||||
assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53
|
assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53
|
||||||
assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1027.7690615000001
|
assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1027.7691
|
||||||
assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 7
|
assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 7
|
||||||
assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 13.116153600000002
|
assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 13.1162
|
||||||
assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 315.14
|
assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 315.14
|
||||||
assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 15.01517952
|
assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 15.0152
|
||||||
assert weather_state.attributes[ATTR_CLOUD_COVER] == 1
|
assert weather_state.attributes[ATTR_CLOUD_COVER] == 1
|
||||||
assert weather_state.attributes[ATTR_WIND_GUST] == 20.34210816
|
assert weather_state.attributes[ATTR_WIND_GUST] == 20.3421
|
||||||
assert weather_state.attributes[ATTR_PRECIPITATION_TYPE] == "rain"
|
assert weather_state.attributes[ATTR_PRECIPITATION_TYPE] == "rain"
|
||||||
|
53
tests/fixtures/climacell/v3_realtime.json
vendored
53
tests/fixtures/climacell/v3_realtime.json
vendored
@ -43,6 +43,59 @@
|
|||||||
"value": 100,
|
"value": 100,
|
||||||
"units": "%"
|
"units": "%"
|
||||||
},
|
},
|
||||||
|
"fire_index": {
|
||||||
|
"value": 9
|
||||||
|
},
|
||||||
|
"epa_aqi": {
|
||||||
|
"value": 22.3125
|
||||||
|
},
|
||||||
|
"epa_primary_pollutant": {
|
||||||
|
"value": "pm25"
|
||||||
|
},
|
||||||
|
"china_aqi": {
|
||||||
|
"value": 27
|
||||||
|
},
|
||||||
|
"china_primary_pollutant": {
|
||||||
|
"value": "pm10"
|
||||||
|
},
|
||||||
|
"pm25": {
|
||||||
|
"value": 5.3125,
|
||||||
|
"units": "\u00b5g/m3"
|
||||||
|
},
|
||||||
|
"pm10": {
|
||||||
|
"value": 27,
|
||||||
|
"units": "\u00b5g/m3"
|
||||||
|
},
|
||||||
|
"no2": {
|
||||||
|
"value": 14.1875,
|
||||||
|
"units": "ppb"
|
||||||
|
},
|
||||||
|
"co": {
|
||||||
|
"value": 0.875,
|
||||||
|
"units": "ppm"
|
||||||
|
},
|
||||||
|
"so2": {
|
||||||
|
"value": 2,
|
||||||
|
"units": "ppb"
|
||||||
|
},
|
||||||
|
"epa_health_concern": {
|
||||||
|
"value": "Good"
|
||||||
|
},
|
||||||
|
"china_health_concern": {
|
||||||
|
"value": "Good"
|
||||||
|
},
|
||||||
|
"pollen_tree": {
|
||||||
|
"value": 0,
|
||||||
|
"units": "Climacell Pollen Index"
|
||||||
|
},
|
||||||
|
"pollen_weed": {
|
||||||
|
"value": 0,
|
||||||
|
"units": "Climacell Pollen Index"
|
||||||
|
},
|
||||||
|
"pollen_grass": {
|
||||||
|
"value": 0,
|
||||||
|
"units": "Climacell Pollen Index"
|
||||||
|
},
|
||||||
"observation_time": {
|
"observation_time": {
|
||||||
"value": "2021-03-07T18:54:06.055Z"
|
"value": "2021-03-07T18:54:06.055Z"
|
||||||
}
|
}
|
||||||
|
17
tests/fixtures/climacell/v4.json
vendored
17
tests/fixtures/climacell/v4.json
vendored
@ -10,7 +10,22 @@
|
|||||||
"pollutantO3": 46.53,
|
"pollutantO3": 46.53,
|
||||||
"windGust": 12.64,
|
"windGust": 12.64,
|
||||||
"cloudCover": 100,
|
"cloudCover": 100,
|
||||||
"precipitationType": 1
|
"precipitationType": 1,
|
||||||
|
"particulateMatter25": 0.15,
|
||||||
|
"particulateMatter10": 0.57,
|
||||||
|
"pollutantNO2": 10.67,
|
||||||
|
"pollutantCO": 0.63,
|
||||||
|
"pollutantSO2": 1.65,
|
||||||
|
"epaIndex": 24,
|
||||||
|
"epaPrimaryPollutant": 0,
|
||||||
|
"epaHealthConcern": 0,
|
||||||
|
"mepIndex": 23,
|
||||||
|
"mepPrimaryPollutant": 1,
|
||||||
|
"mepHealthConcern": 0,
|
||||||
|
"treeIndex": 0,
|
||||||
|
"weedIndex": 0,
|
||||||
|
"grassIndex": 0,
|
||||||
|
"fireIndex": 10
|
||||||
},
|
},
|
||||||
"forecasts": {
|
"forecasts": {
|
||||||
"nowcast": [
|
"nowcast": [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user