mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add Air Pollution support to OpenWeatherMap (#137949)
Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
parent
16394061cb
commit
b17d62177c
@ -12,7 +12,7 @@ from homeassistant.const import CONF_API_KEY, CONF_LANGUAGE, CONF_MODE, CONF_NAM
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import CONFIG_FLOW_VERSION, DEFAULT_OWM_MODE, OWM_MODES, PLATFORMS
|
||||
from .coordinator import WeatherUpdateCoordinator
|
||||
from .coordinator import OWMUpdateCoordinator, get_owm_update_coordinator
|
||||
from .repairs import async_create_issue, async_delete_issue
|
||||
from .utils import build_data_and_options
|
||||
|
||||
@ -27,7 +27,7 @@ class OpenweathermapData:
|
||||
|
||||
name: str
|
||||
mode: str
|
||||
coordinator: WeatherUpdateCoordinator
|
||||
coordinator: OWMUpdateCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@ -45,13 +45,13 @@ async def async_setup_entry(
|
||||
async_delete_issue(hass, entry.entry_id)
|
||||
|
||||
owm_client = create_owm_client(api_key, mode, lang=language)
|
||||
weather_coordinator = WeatherUpdateCoordinator(hass, entry, owm_client)
|
||||
owm_coordinator = get_owm_update_coordinator(mode)(hass, entry, owm_client)
|
||||
|
||||
await weather_coordinator.async_config_entry_first_refresh()
|
||||
await owm_coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(async_update_options))
|
||||
|
||||
entry.runtime_data = OpenweathermapData(name, mode, weather_coordinator)
|
||||
entry.runtime_data = OpenweathermapData(name, mode, owm_coordinator)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
|
@ -51,16 +51,28 @@ ATTR_API_CURRENT = "current"
|
||||
ATTR_API_MINUTE_FORECAST = "minute_forecast"
|
||||
ATTR_API_HOURLY_FORECAST = "hourly_forecast"
|
||||
ATTR_API_DAILY_FORECAST = "daily_forecast"
|
||||
ATTR_API_AIRPOLLUTION_AQI = "aqi"
|
||||
ATTR_API_AIRPOLLUTION_CO = "co"
|
||||
ATTR_API_AIRPOLLUTION_NO = "no"
|
||||
ATTR_API_AIRPOLLUTION_NO2 = "no2"
|
||||
ATTR_API_AIRPOLLUTION_O3 = "o3"
|
||||
ATTR_API_AIRPOLLUTION_SO2 = "so2"
|
||||
ATTR_API_AIRPOLLUTION_PM2_5 = "pm2_5"
|
||||
ATTR_API_AIRPOLLUTION_PM10 = "pm10"
|
||||
ATTR_API_AIRPOLLUTION_NH3 = "nh3"
|
||||
|
||||
UPDATE_LISTENER = "update_listener"
|
||||
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
||||
|
||||
OWM_MODE_FREE_CURRENT = "current"
|
||||
OWM_MODE_FREE_FORECAST = "forecast"
|
||||
OWM_MODE_V30 = "v3.0"
|
||||
OWM_MODE_AIRPOLLUTION = "air_pollution"
|
||||
OWM_MODES = [
|
||||
OWM_MODE_V30,
|
||||
OWM_MODE_FREE_CURRENT,
|
||||
OWM_MODE_FREE_FORECAST,
|
||||
OWM_MODE_AIRPOLLUTION,
|
||||
]
|
||||
DEFAULT_OWM_MODE = OWM_MODE_V30
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
"""Weather data coordinator for the OpenWeatherMap (OWM) service."""
|
||||
"""Data coordinator for the OpenWeatherMap (OWM) service."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from pyopenweathermap import (
|
||||
CurrentAirPollution,
|
||||
CurrentWeather,
|
||||
DailyWeatherForecast,
|
||||
HourlyWeatherForecast,
|
||||
@ -31,6 +32,15 @@ if TYPE_CHECKING:
|
||||
from . import OpenweathermapConfigEntry
|
||||
|
||||
from .const import (
|
||||
ATTR_API_AIRPOLLUTION_AQI,
|
||||
ATTR_API_AIRPOLLUTION_CO,
|
||||
ATTR_API_AIRPOLLUTION_NH3,
|
||||
ATTR_API_AIRPOLLUTION_NO,
|
||||
ATTR_API_AIRPOLLUTION_NO2,
|
||||
ATTR_API_AIRPOLLUTION_O3,
|
||||
ATTR_API_AIRPOLLUTION_PM2_5,
|
||||
ATTR_API_AIRPOLLUTION_PM10,
|
||||
ATTR_API_AIRPOLLUTION_SO2,
|
||||
ATTR_API_CLOUDS,
|
||||
ATTR_API_CONDITION,
|
||||
ATTR_API_CURRENT,
|
||||
@ -57,16 +67,20 @@ from .const import (
|
||||
ATTR_API_WIND_SPEED,
|
||||
CONDITION_MAP,
|
||||
DOMAIN,
|
||||
OWM_MODE_AIRPOLLUTION,
|
||||
OWM_MODE_FREE_CURRENT,
|
||||
OWM_MODE_FREE_FORECAST,
|
||||
OWM_MODE_V30,
|
||||
WEATHER_CODE_SUNNY_OR_CLEAR_NIGHT,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
WEATHER_UPDATE_INTERVAL = timedelta(minutes=10)
|
||||
OWM_UPDATE_INTERVAL = timedelta(minutes=10)
|
||||
|
||||
|
||||
class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Weather data update coordinator."""
|
||||
class OWMUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""OWM data update coordinator."""
|
||||
|
||||
config_entry: OpenweathermapConfigEntry
|
||||
|
||||
@ -86,9 +100,13 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
_LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=DOMAIN,
|
||||
update_interval=WEATHER_UPDATE_INTERVAL,
|
||||
update_interval=OWM_UPDATE_INTERVAL,
|
||||
)
|
||||
|
||||
|
||||
class WeatherUpdateCoordinator(OWMUpdateCoordinator):
|
||||
"""Weather data update coordinator."""
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Update the data."""
|
||||
try:
|
||||
@ -248,3 +266,52 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
return ATTR_CONDITION_CLEAR_NIGHT
|
||||
|
||||
return CONDITION_MAP.get(weather_code)
|
||||
|
||||
|
||||
class AirPollutionUpdateCoordinator(OWMUpdateCoordinator):
|
||||
"""Air Pollution data update coordinator."""
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Update the data."""
|
||||
try:
|
||||
air_pollution_report = await self._owm_client.get_air_pollution(
|
||||
self._latitude, self._longitude
|
||||
)
|
||||
except RequestError as error:
|
||||
raise UpdateFailed(error) from error
|
||||
current_air_pollution = (
|
||||
self._get_current_air_pollution_data(air_pollution_report.current)
|
||||
if air_pollution_report.current is not None
|
||||
else {}
|
||||
)
|
||||
|
||||
return {
|
||||
ATTR_API_CURRENT: current_air_pollution,
|
||||
}
|
||||
|
||||
def _get_current_air_pollution_data(
|
||||
self, current_air_pollution: CurrentAirPollution
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
ATTR_API_AIRPOLLUTION_AQI: current_air_pollution.aqi,
|
||||
ATTR_API_AIRPOLLUTION_CO: current_air_pollution.co,
|
||||
ATTR_API_AIRPOLLUTION_NO: current_air_pollution.no,
|
||||
ATTR_API_AIRPOLLUTION_NO2: current_air_pollution.no2,
|
||||
ATTR_API_AIRPOLLUTION_O3: current_air_pollution.o3,
|
||||
ATTR_API_AIRPOLLUTION_SO2: current_air_pollution.so2,
|
||||
ATTR_API_AIRPOLLUTION_PM2_5: current_air_pollution.pm2_5,
|
||||
ATTR_API_AIRPOLLUTION_PM10: current_air_pollution.pm10,
|
||||
ATTR_API_AIRPOLLUTION_NH3: current_air_pollution.nh3,
|
||||
}
|
||||
|
||||
|
||||
def get_owm_update_coordinator(mode: str) -> type[OWMUpdateCoordinator]:
|
||||
"""Create coordinator with a factory."""
|
||||
coordinators = {
|
||||
OWM_MODE_V30: WeatherUpdateCoordinator,
|
||||
OWM_MODE_FREE_CURRENT: WeatherUpdateCoordinator,
|
||||
OWM_MODE_FREE_FORECAST: WeatherUpdateCoordinator,
|
||||
OWM_MODE_AIRPOLLUTION: AirPollutionUpdateCoordinator,
|
||||
}
|
||||
|
||||
return coordinators[mode]
|
||||
|
@ -9,6 +9,8 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
DEGREE,
|
||||
PERCENTAGE,
|
||||
UV_INDEX,
|
||||
@ -23,10 +25,17 @@ from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from . import OpenweathermapConfigEntry
|
||||
from .const import (
|
||||
ATTR_API_AIRPOLLUTION_AQI,
|
||||
ATTR_API_AIRPOLLUTION_CO,
|
||||
ATTR_API_AIRPOLLUTION_NO,
|
||||
ATTR_API_AIRPOLLUTION_NO2,
|
||||
ATTR_API_AIRPOLLUTION_O3,
|
||||
ATTR_API_AIRPOLLUTION_PM2_5,
|
||||
ATTR_API_AIRPOLLUTION_PM10,
|
||||
ATTR_API_AIRPOLLUTION_SO2,
|
||||
ATTR_API_CLOUDS,
|
||||
ATTR_API_CONDITION,
|
||||
ATTR_API_CURRENT,
|
||||
@ -47,8 +56,10 @@ from .const import (
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
OWM_MODE_AIRPOLLUTION,
|
||||
OWM_MODE_FREE_FORECAST,
|
||||
)
|
||||
from .coordinator import OWMUpdateCoordinator
|
||||
|
||||
WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
@ -151,6 +162,56 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
),
|
||||
)
|
||||
|
||||
AIRPOLLUTION_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_AIRPOLLUTION_AQI,
|
||||
device_class=SensorDeviceClass.AQI,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_AIRPOLLUTION_CO,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_AIRPOLLUTION_NO,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.NITROGEN_MONOXIDE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_AIRPOLLUTION_NO2,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_AIRPOLLUTION_O3,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.OZONE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_AIRPOLLUTION_SO2,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_AIRPOLLUTION_PM2_5,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_API_AIRPOLLUTION_PM10,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -162,7 +223,7 @@ async def async_setup_entry(
|
||||
name = domain_data.name
|
||||
unique_id = config_entry.unique_id
|
||||
assert unique_id is not None
|
||||
weather_coordinator = domain_data.coordinator
|
||||
coordinator = domain_data.coordinator
|
||||
|
||||
if domain_data.mode == OWM_MODE_FREE_FORECAST:
|
||||
entity_registry = er.async_get(hass)
|
||||
@ -171,13 +232,23 @@ async def async_setup_entry(
|
||||
)
|
||||
for entry in entries:
|
||||
entity_registry.async_remove(entry.entity_id)
|
||||
elif domain_data.mode == OWM_MODE_AIRPOLLUTION:
|
||||
async_add_entities(
|
||||
OpenWeatherMapSensor(
|
||||
name,
|
||||
unique_id,
|
||||
description,
|
||||
coordinator,
|
||||
)
|
||||
for description in AIRPOLLUTION_SENSOR_TYPES
|
||||
)
|
||||
else:
|
||||
async_add_entities(
|
||||
OpenWeatherMapSensor(
|
||||
name,
|
||||
unique_id,
|
||||
description,
|
||||
weather_coordinator,
|
||||
coordinator,
|
||||
)
|
||||
for description in WEATHER_SENSOR_TYPES
|
||||
)
|
||||
@ -195,7 +266,7 @@ class AbstractOpenWeatherMapSensor(SensorEntity):
|
||||
name: str,
|
||||
unique_id: str,
|
||||
description: SensorEntityDescription,
|
||||
coordinator: DataUpdateCoordinator,
|
||||
coordinator: OWMUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self.entity_description = description
|
||||
|
@ -41,10 +41,11 @@ from .const import (
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
OWM_MODE_AIRPOLLUTION,
|
||||
OWM_MODE_FREE_FORECAST,
|
||||
OWM_MODE_V30,
|
||||
)
|
||||
from .coordinator import WeatherUpdateCoordinator
|
||||
from .coordinator import OWMUpdateCoordinator
|
||||
|
||||
SERVICE_GET_MINUTE_FORECAST = "get_minute_forecast"
|
||||
|
||||
@ -58,23 +59,25 @@ async def async_setup_entry(
|
||||
domain_data = config_entry.runtime_data
|
||||
name = domain_data.name
|
||||
mode = domain_data.mode
|
||||
weather_coordinator = domain_data.coordinator
|
||||
|
||||
unique_id = f"{config_entry.unique_id}"
|
||||
owm_weather = OpenWeatherMapWeather(name, unique_id, mode, weather_coordinator)
|
||||
if mode != OWM_MODE_AIRPOLLUTION:
|
||||
weather_coordinator = domain_data.coordinator
|
||||
|
||||
async_add_entities([owm_weather], False)
|
||||
unique_id = f"{config_entry.unique_id}"
|
||||
owm_weather = OpenWeatherMapWeather(name, unique_id, mode, weather_coordinator)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
name=SERVICE_GET_MINUTE_FORECAST,
|
||||
schema=None,
|
||||
func="async_get_minute_forecast",
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
async_add_entities([owm_weather], False)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
name=SERVICE_GET_MINUTE_FORECAST,
|
||||
schema=None,
|
||||
func="async_get_minute_forecast",
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
|
||||
|
||||
class OpenWeatherMapWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordinator]):
|
||||
class OpenWeatherMapWeather(SingleCoordinatorWeatherEntity[OWMUpdateCoordinator]):
|
||||
"""Implementation of an OpenWeatherMap sensor."""
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
@ -93,7 +96,7 @@ class OpenWeatherMapWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordina
|
||||
name: str,
|
||||
unique_id: str,
|
||||
mode: str,
|
||||
weather_coordinator: WeatherUpdateCoordinator,
|
||||
weather_coordinator: OWMUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(weather_coordinator)
|
||||
|
@ -5,6 +5,8 @@ from datetime import UTC, datetime
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from pyopenweathermap import (
|
||||
AirPollutionReport,
|
||||
CurrentAirPollution,
|
||||
CurrentWeather,
|
||||
DailyTemperature,
|
||||
DailyWeatherForecast,
|
||||
@ -132,6 +134,21 @@ def owm_client_mock() -> Generator[AsyncMock]:
|
||||
client.get_weather.return_value = WeatherReport(
|
||||
current_weather, minutely_weather_forecast, [], [daily_weather_forecast]
|
||||
)
|
||||
current_air_pollution = CurrentAirPollution(
|
||||
date_time=datetime.fromtimestamp(1714063537, tz=UTC),
|
||||
aqi=3,
|
||||
co=125.55,
|
||||
no=0.11,
|
||||
no2=0.78,
|
||||
o3=101.98,
|
||||
so2=0.59,
|
||||
pm2_5=4.48,
|
||||
pm10=4.77,
|
||||
nh3=4.62,
|
||||
)
|
||||
client.get_air_pollution.return_value = AirPollutionReport(
|
||||
current_air_pollution, []
|
||||
)
|
||||
client.validate_key.return_value = True
|
||||
with (
|
||||
patch(
|
||||
|
@ -1,4 +1,435 @@
|
||||
# serializer version: 1
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_air_quality_index-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.openweathermap_air_quality_index',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.AQI: 'aqi'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Air quality index',
|
||||
'platform': 'openweathermap',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12.34-56.78-aqi',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_air_quality_index-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by OpenWeatherMap',
|
||||
'device_class': 'aqi',
|
||||
'friendly_name': 'openweathermap Air quality index',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.openweathermap_air_quality_index',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '3',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_carbon_monoxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.openweathermap_carbon_monoxide',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.CO: 'carbon_monoxide'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Carbon monoxide',
|
||||
'platform': 'openweathermap',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12.34-56.78-co',
|
||||
'unit_of_measurement': 'ppm',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_carbon_monoxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by OpenWeatherMap',
|
||||
'device_class': 'carbon_monoxide',
|
||||
'friendly_name': 'openweathermap Carbon monoxide',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'ppm',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.openweathermap_carbon_monoxide',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '125.55',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_nitrogen_dioxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.openweathermap_nitrogen_dioxide',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.NITROGEN_DIOXIDE: 'nitrogen_dioxide'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Nitrogen dioxide',
|
||||
'platform': 'openweathermap',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12.34-56.78-no2',
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_nitrogen_dioxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by OpenWeatherMap',
|
||||
'device_class': 'nitrogen_dioxide',
|
||||
'friendly_name': 'openweathermap Nitrogen dioxide',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.openweathermap_nitrogen_dioxide',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.78',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_nitrogen_monoxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.openweathermap_nitrogen_monoxide',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.NITROGEN_MONOXIDE: 'nitrogen_monoxide'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Nitrogen monoxide',
|
||||
'platform': 'openweathermap',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12.34-56.78-no',
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_nitrogen_monoxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by OpenWeatherMap',
|
||||
'device_class': 'nitrogen_monoxide',
|
||||
'friendly_name': 'openweathermap Nitrogen monoxide',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.openweathermap_nitrogen_monoxide',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.11',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_ozone-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.openweathermap_ozone',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.OZONE: 'ozone'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Ozone',
|
||||
'platform': 'openweathermap',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12.34-56.78-o3',
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_ozone-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by OpenWeatherMap',
|
||||
'device_class': 'ozone',
|
||||
'friendly_name': 'openweathermap Ozone',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.openweathermap_ozone',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '101.98',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_pm10-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.openweathermap_pm10',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.PM10: 'pm10'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'PM10',
|
||||
'platform': 'openweathermap',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12.34-56.78-pm10',
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_pm10-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by OpenWeatherMap',
|
||||
'device_class': 'pm10',
|
||||
'friendly_name': 'openweathermap PM10',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.openweathermap_pm10',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '4.77',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_pm2_5-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.openweathermap_pm2_5',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.PM25: 'pm25'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'PM2.5',
|
||||
'platform': 'openweathermap',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12.34-56.78-pm2_5',
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_pm2_5-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by OpenWeatherMap',
|
||||
'device_class': 'pm25',
|
||||
'friendly_name': 'openweathermap PM2.5',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.openweathermap_pm2_5',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '4.48',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_sulphur_dioxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.openweathermap_sulphur_dioxide',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.SULPHUR_DIOXIDE: 'sulphur_dioxide'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sulphur dioxide',
|
||||
'platform': 'openweathermap',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12.34-56.78-so2',
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[air_pollution][sensor.openweathermap_sulphur_dioxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by OpenWeatherMap',
|
||||
'device_class': 'sulphur_dioxide',
|
||||
'friendly_name': 'openweathermap Sulphur dioxide',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'µg/m³',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.openweathermap_sulphur_dioxide',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.59',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[current][sensor.openweathermap_cloud_coverage-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -6,6 +6,7 @@ import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.openweathermap.const import (
|
||||
OWM_MODE_AIRPOLLUTION,
|
||||
OWM_MODE_FREE_CURRENT,
|
||||
OWM_MODE_FREE_FORECAST,
|
||||
OWM_MODE_V30,
|
||||
@ -19,7 +20,9 @@ from . import setup_platform
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", [OWM_MODE_V30, OWM_MODE_FREE_CURRENT], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"mode", [OWM_MODE_V30, OWM_MODE_FREE_CURRENT, OWM_MODE_AIRPOLLUTION], indirect=True
|
||||
)
|
||||
async def test_sensor_states(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
|
Loading…
x
Reference in New Issue
Block a user