Add Air Pollution support to OpenWeatherMap (#137949)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
wittypluck 2025-05-26 21:34:48 +02:00 committed by GitHub
parent 16394061cb
commit b17d62177c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 634 additions and 30 deletions

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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({

View File

@ -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,