Migrate GIOS air_quality platform to sensor (#52295)
This commit is contained in:
parent
27295d8f58
commit
bdf247faaa
8 changed files with 378 additions and 340 deletions
|
@ -9,8 +9,10 @@ from aiohttp.client_exceptions import ClientConnectorError
|
||||||
from async_timeout import timeout
|
from async_timeout import timeout
|
||||||
from gios import ApiError, Gios, InvalidSensorsData, NoStationError
|
from gios import ApiError, Gios, InvalidSensorsData, NoStationError
|
||||||
|
|
||||||
|
from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.device_registry import async_get_registry
|
from homeassistant.helpers.device_registry import async_get_registry
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
@ -19,7 +21,7 @@ from .const import API_TIMEOUT, CONF_STATION_ID, DOMAIN, SCAN_INTERVAL
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORMS = ["air_quality"]
|
PLATFORMS = ["sensor"]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
@ -49,6 +51,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
# Remove air_quality entities from registry if they exist
|
||||||
|
ent_reg = entity_registry.async_get(hass)
|
||||||
|
unique_id = str(coordinator.gios.station_id)
|
||||||
|
if entity_id := ent_reg.async_get_entity_id(
|
||||||
|
AIR_QUALITY_PLATFORM, DOMAIN, unique_id
|
||||||
|
):
|
||||||
|
_LOGGER.debug("Removing deprecated air_quality entity %s", entity_id)
|
||||||
|
ent_reg.async_remove(entity_id)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,157 +0,0 @@
|
||||||
"""Support for the GIOS service."""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any, Optional, cast
|
|
||||||
|
|
||||||
from homeassistant.components.air_quality import AirQualityEntity
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import CONF_NAME
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
||||||
from homeassistant.helpers.entity_registry import async_get_registry
|
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
||||||
|
|
||||||
from . import GiosDataUpdateCoordinator
|
|
||||||
from .const import (
|
|
||||||
API_AQI,
|
|
||||||
API_CO,
|
|
||||||
API_NO2,
|
|
||||||
API_O3,
|
|
||||||
API_PM10,
|
|
||||||
API_PM25,
|
|
||||||
API_SO2,
|
|
||||||
ATTR_STATION,
|
|
||||||
ATTRIBUTION,
|
|
||||||
DEFAULT_NAME,
|
|
||||||
DOMAIN,
|
|
||||||
ICONS_MAP,
|
|
||||||
MANUFACTURER,
|
|
||||||
SENSOR_MAP,
|
|
||||||
)
|
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
|
||||||
) -> None:
|
|
||||||
"""Add a GIOS entities from a config_entry."""
|
|
||||||
name = entry.data[CONF_NAME]
|
|
||||||
|
|
||||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
|
||||||
|
|
||||||
# We used to use int as entity unique_id, convert this to str.
|
|
||||||
entity_registry = await async_get_registry(hass)
|
|
||||||
old_entity_id = entity_registry.async_get_entity_id(
|
|
||||||
"air_quality", DOMAIN, coordinator.gios.station_id
|
|
||||||
)
|
|
||||||
if old_entity_id is not None:
|
|
||||||
entity_registry.async_update_entity(
|
|
||||||
old_entity_id, new_unique_id=str(coordinator.gios.station_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
async_add_entities([GiosAirQuality(coordinator, name)])
|
|
||||||
|
|
||||||
|
|
||||||
class GiosAirQuality(CoordinatorEntity, AirQualityEntity):
|
|
||||||
"""Define an GIOS sensor."""
|
|
||||||
|
|
||||||
coordinator: GiosDataUpdateCoordinator
|
|
||||||
|
|
||||||
def __init__(self, coordinator: GiosDataUpdateCoordinator, name: str) -> None:
|
|
||||||
"""Initialize."""
|
|
||||||
super().__init__(coordinator)
|
|
||||||
self._name = name
|
|
||||||
self._attrs: dict[str, Any] = {}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return the name."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self) -> str:
|
|
||||||
"""Return the icon."""
|
|
||||||
if self.air_quality_index is not None and self.air_quality_index in ICONS_MAP:
|
|
||||||
return ICONS_MAP[self.air_quality_index]
|
|
||||||
return "mdi:blur"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def air_quality_index(self) -> str | None:
|
|
||||||
"""Return the air quality index."""
|
|
||||||
return cast(Optional[str], self.coordinator.data.get(API_AQI, {}).get("value"))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def particulate_matter_2_5(self) -> float | None:
|
|
||||||
"""Return the particulate matter 2.5 level."""
|
|
||||||
return round_state(self._get_sensor_value(API_PM25))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def particulate_matter_10(self) -> float | None:
|
|
||||||
"""Return the particulate matter 10 level."""
|
|
||||||
return round_state(self._get_sensor_value(API_PM10))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ozone(self) -> float | None:
|
|
||||||
"""Return the O3 (ozone) level."""
|
|
||||||
return round_state(self._get_sensor_value(API_O3))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def carbon_monoxide(self) -> float | None:
|
|
||||||
"""Return the CO (carbon monoxide) level."""
|
|
||||||
return round_state(self._get_sensor_value(API_CO))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sulphur_dioxide(self) -> float | None:
|
|
||||||
"""Return the SO2 (sulphur dioxide) level."""
|
|
||||||
return round_state(self._get_sensor_value(API_SO2))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def nitrogen_dioxide(self) -> float | None:
|
|
||||||
"""Return the NO2 (nitrogen dioxide) level."""
|
|
||||||
return round_state(self._get_sensor_value(API_NO2))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def attribution(self) -> str:
|
|
||||||
"""Return the attribution."""
|
|
||||||
return ATTRIBUTION
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self) -> str:
|
|
||||||
"""Return a unique_id for this entity."""
|
|
||||||
return str(self.coordinator.gios.station_id)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Return the device info."""
|
|
||||||
return {
|
|
||||||
"identifiers": {(DOMAIN, str(self.coordinator.gios.station_id))},
|
|
||||||
"name": DEFAULT_NAME,
|
|
||||||
"manufacturer": MANUFACTURER,
|
|
||||||
"entry_type": "service",
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_state_attributes(self) -> dict[str, Any] | None:
|
|
||||||
"""Return the state attributes."""
|
|
||||||
# Different measuring stations have different sets of sensors. We don't know
|
|
||||||
# what data we will get.
|
|
||||||
for sensor in SENSOR_MAP:
|
|
||||||
if sensor in self.coordinator.data:
|
|
||||||
self._attrs[f"{SENSOR_MAP[sensor]}_index"] = self.coordinator.data[
|
|
||||||
sensor
|
|
||||||
]["index"]
|
|
||||||
self._attrs[ATTR_STATION] = self.coordinator.gios.station_name
|
|
||||||
return self._attrs
|
|
||||||
|
|
||||||
def _get_sensor_value(self, sensor: str) -> float | None:
|
|
||||||
"""Return value of specified sensor."""
|
|
||||||
if sensor in self.coordinator.data:
|
|
||||||
return cast(float, self.coordinator.data[sensor]["value"])
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def round_state(state: float | None) -> float | None:
|
|
||||||
"""Round state."""
|
|
||||||
return round(state) if state is not None else None
|
|
|
@ -4,18 +4,13 @@ from __future__ import annotations
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from homeassistant.components.air_quality import (
|
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
||||||
ATTR_CO,
|
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
ATTR_NO2,
|
|
||||||
ATTR_OZONE,
|
from .model import SensorDescription
|
||||||
ATTR_PM_2_5,
|
|
||||||
ATTR_PM_10,
|
|
||||||
ATTR_SO2,
|
|
||||||
)
|
|
||||||
|
|
||||||
ATTRIBUTION: Final = "Data provided by GIOŚ"
|
ATTRIBUTION: Final = "Data provided by GIOŚ"
|
||||||
|
|
||||||
ATTR_STATION: Final = "station"
|
|
||||||
CONF_STATION_ID: Final = "station_id"
|
CONF_STATION_ID: Final = "station_id"
|
||||||
DEFAULT_NAME: Final = "GIOŚ"
|
DEFAULT_NAME: Final = "GIOŚ"
|
||||||
# Term of service GIOŚ allow downloading data no more than twice an hour.
|
# Term of service GIOŚ allow downloading data no more than twice an hour.
|
||||||
|
@ -23,35 +18,58 @@ SCAN_INTERVAL: Final = timedelta(minutes=30)
|
||||||
DOMAIN: Final = "gios"
|
DOMAIN: Final = "gios"
|
||||||
MANUFACTURER: Final = "Główny Inspektorat Ochrony Środowiska"
|
MANUFACTURER: Final = "Główny Inspektorat Ochrony Środowiska"
|
||||||
|
|
||||||
API_AQI: Final = "aqi"
|
|
||||||
API_CO: Final = "co"
|
|
||||||
API_NO2: Final = "no2"
|
|
||||||
API_O3: Final = "o3"
|
|
||||||
API_PM10: Final = "pm10"
|
|
||||||
API_PM25: Final = "pm2.5"
|
|
||||||
API_SO2: Final = "so2"
|
|
||||||
|
|
||||||
API_TIMEOUT: Final = 30
|
API_TIMEOUT: Final = 30
|
||||||
|
|
||||||
AQI_GOOD: Final = "dobry"
|
ATTR_INDEX: Final = "index"
|
||||||
AQI_MODERATE: Final = "umiarkowany"
|
ATTR_STATION: Final = "station"
|
||||||
AQI_POOR: Final = "dostateczny"
|
ATTR_UNIT: Final = "unit"
|
||||||
AQI_VERY_GOOD: Final = "bardzo dobry"
|
ATTR_VALUE: Final = "value"
|
||||||
AQI_VERY_POOR: Final = "zły"
|
ATTR_STATION_NAME: Final = "station_name"
|
||||||
|
|
||||||
ICONS_MAP: Final[dict[str, str]] = {
|
ATTR_C6H6: Final = "c6h6"
|
||||||
AQI_VERY_GOOD: "mdi:emoticon-excited",
|
ATTR_CO: Final = "co"
|
||||||
AQI_GOOD: "mdi:emoticon-happy",
|
ATTR_NO2: Final = "no2"
|
||||||
AQI_MODERATE: "mdi:emoticon-neutral",
|
ATTR_O3: Final = "o3"
|
||||||
AQI_POOR: "mdi:emoticon-sad",
|
ATTR_PM10: Final = "pm10"
|
||||||
AQI_VERY_POOR: "mdi:emoticon-dead",
|
ATTR_PM25: Final = "pm2.5"
|
||||||
}
|
ATTR_SO2: Final = "so2"
|
||||||
|
ATTR_AQI: Final = "aqi"
|
||||||
|
|
||||||
SENSOR_MAP: Final[dict[str, str]] = {
|
SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||||
API_CO: ATTR_CO,
|
ATTR_AQI: {},
|
||||||
API_NO2: ATTR_NO2,
|
ATTR_C6H6: {
|
||||||
API_O3: ATTR_OZONE,
|
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
API_PM10: ATTR_PM_10,
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
API_PM25: ATTR_PM_2_5,
|
ATTR_VALUE: round,
|
||||||
API_SO2: ATTR_SO2,
|
},
|
||||||
|
ATTR_CO: {
|
||||||
|
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_VALUE: round,
|
||||||
|
},
|
||||||
|
ATTR_NO2: {
|
||||||
|
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_VALUE: round,
|
||||||
|
},
|
||||||
|
ATTR_O3: {
|
||||||
|
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_VALUE: round,
|
||||||
|
},
|
||||||
|
ATTR_PM10: {
|
||||||
|
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_VALUE: round,
|
||||||
|
},
|
||||||
|
ATTR_PM25: {
|
||||||
|
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_VALUE: round,
|
||||||
|
},
|
||||||
|
ATTR_SO2: {
|
||||||
|
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_VALUE: round,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
12
homeassistant/components/gios/model.py
Normal file
12
homeassistant/components/gios/model.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
"""Type definitions for GIOS integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Callable, TypedDict
|
||||||
|
|
||||||
|
|
||||||
|
class SensorDescription(TypedDict, total=False):
|
||||||
|
"""Sensor description class."""
|
||||||
|
|
||||||
|
unit: str
|
||||||
|
state_class: str
|
||||||
|
value: Callable
|
89
homeassistant/components/gios/sensor.py
Normal file
89
homeassistant/components/gios/sensor.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
"""Support for the GIOS service."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_NAME, CONF_NAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from . import GiosDataUpdateCoordinator
|
||||||
|
from .const import (
|
||||||
|
ATTR_INDEX,
|
||||||
|
ATTR_STATION,
|
||||||
|
ATTR_UNIT,
|
||||||
|
ATTR_VALUE,
|
||||||
|
ATTRIBUTION,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
DOMAIN,
|
||||||
|
MANUFACTURER,
|
||||||
|
SENSOR_TYPES,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
|
"""Add a GIOS entities from a config_entry."""
|
||||||
|
name = entry.data[CONF_NAME]
|
||||||
|
|
||||||
|
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
|
||||||
|
for sensor in coordinator.data:
|
||||||
|
if sensor in SENSOR_TYPES:
|
||||||
|
sensors.append(GiosSensor(name, sensor, coordinator))
|
||||||
|
async_add_entities(sensors)
|
||||||
|
|
||||||
|
|
||||||
|
class GiosSensor(CoordinatorEntity, SensorEntity):
|
||||||
|
"""Define an GIOS sensor."""
|
||||||
|
|
||||||
|
coordinator: GiosDataUpdateCoordinator
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, name: str, sensor_type: str, coordinator: GiosDataUpdateCoordinator
|
||||||
|
) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._description = SENSOR_TYPES[sensor_type]
|
||||||
|
self._attr_device_info = {
|
||||||
|
"identifiers": {(DOMAIN, str(coordinator.gios.station_id))},
|
||||||
|
"name": DEFAULT_NAME,
|
||||||
|
"manufacturer": MANUFACTURER,
|
||||||
|
"entry_type": "service",
|
||||||
|
}
|
||||||
|
self._attr_icon = "mdi:blur"
|
||||||
|
self._attr_name = f"{name} {sensor_type.upper()}"
|
||||||
|
self._attr_state_class = self._description.get(ATTR_STATE_CLASS)
|
||||||
|
self._attr_unique_id = f"{coordinator.gios.station_id}-{sensor_type}"
|
||||||
|
self._attr_unit_of_measurement = self._description.get(ATTR_UNIT)
|
||||||
|
self._sensor_type = sensor_type
|
||||||
|
self._state = None
|
||||||
|
self._attrs: dict[str, Any] = {
|
||||||
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||||
|
ATTR_STATION: self.coordinator.gios.station_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return the state attributes."""
|
||||||
|
if self.coordinator.data[self._sensor_type].get(ATTR_INDEX):
|
||||||
|
self._attrs[ATTR_NAME] = self.coordinator.data[self._sensor_type][ATTR_NAME]
|
||||||
|
self._attrs[ATTR_INDEX] = self.coordinator.data[self._sensor_type][
|
||||||
|
ATTR_INDEX
|
||||||
|
]
|
||||||
|
return self._attrs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> StateType:
|
||||||
|
"""Return the state."""
|
||||||
|
self._state = self.coordinator.data[self._sensor_type][ATTR_VALUE]
|
||||||
|
if self._description.get(ATTR_VALUE):
|
||||||
|
return cast(StateType, self._description[ATTR_VALUE](self._state))
|
||||||
|
return cast(StateType, self._state)
|
|
@ -1,145 +0,0 @@
|
||||||
"""Test air_quality of GIOS integration."""
|
|
||||||
from datetime import timedelta
|
|
||||||
import json
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from gios import ApiError
|
|
||||||
|
|
||||||
from homeassistant.components.air_quality import (
|
|
||||||
ATTR_AQI,
|
|
||||||
ATTR_CO,
|
|
||||||
ATTR_NO2,
|
|
||||||
ATTR_OZONE,
|
|
||||||
ATTR_PM_2_5,
|
|
||||||
ATTR_PM_10,
|
|
||||||
ATTR_SO2,
|
|
||||||
DOMAIN as AIR_QUALITY_DOMAIN,
|
|
||||||
)
|
|
||||||
from homeassistant.components.gios.air_quality import ATTRIBUTION
|
|
||||||
from homeassistant.components.gios.const import AQI_GOOD, DOMAIN
|
|
||||||
from homeassistant.const import (
|
|
||||||
ATTR_ATTRIBUTION,
|
|
||||||
ATTR_ICON,
|
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers import entity_registry as er
|
|
||||||
from homeassistant.util.dt import utcnow
|
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed, load_fixture
|
|
||||||
from tests.components.gios import init_integration
|
|
||||||
|
|
||||||
|
|
||||||
async def test_air_quality(hass):
|
|
||||||
"""Test states of the air_quality."""
|
|
||||||
await init_integration(hass)
|
|
||||||
registry = er.async_get(hass)
|
|
||||||
|
|
||||||
state = hass.states.get("air_quality.home")
|
|
||||||
assert state
|
|
||||||
assert state.state == "4"
|
|
||||||
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
|
||||||
assert state.attributes.get(ATTR_AQI) == AQI_GOOD
|
|
||||||
assert state.attributes.get(ATTR_PM_10) == 17
|
|
||||||
assert state.attributes.get(ATTR_PM_2_5) == 4
|
|
||||||
assert state.attributes.get(ATTR_CO) == 252
|
|
||||||
assert state.attributes.get(ATTR_SO2) == 4
|
|
||||||
assert state.attributes.get(ATTR_NO2) == 7
|
|
||||||
assert state.attributes.get(ATTR_OZONE) == 96
|
|
||||||
assert (
|
|
||||||
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
|
||||||
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
|
||||||
)
|
|
||||||
assert state.attributes.get(ATTR_ICON) == "mdi:emoticon-happy"
|
|
||||||
assert state.attributes.get("station") == "Test Name 1"
|
|
||||||
|
|
||||||
entry = registry.async_get("air_quality.home")
|
|
||||||
assert entry
|
|
||||||
assert entry.unique_id == "123"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_air_quality_with_incomplete_data(hass):
|
|
||||||
"""Test states of the air_quality with incomplete data from measuring station."""
|
|
||||||
await init_integration(hass, incomplete_data=True)
|
|
||||||
registry = er.async_get(hass)
|
|
||||||
|
|
||||||
state = hass.states.get("air_quality.home")
|
|
||||||
assert state
|
|
||||||
assert state.state == "4"
|
|
||||||
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
|
||||||
assert state.attributes.get(ATTR_AQI) == "foo"
|
|
||||||
assert state.attributes.get(ATTR_PM_10) is None
|
|
||||||
assert state.attributes.get(ATTR_PM_2_5) == 4
|
|
||||||
assert state.attributes.get(ATTR_CO) == 252
|
|
||||||
assert state.attributes.get(ATTR_SO2) == 4
|
|
||||||
assert state.attributes.get(ATTR_NO2) == 7
|
|
||||||
assert state.attributes.get(ATTR_OZONE) == 96
|
|
||||||
assert (
|
|
||||||
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
|
||||||
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
|
||||||
)
|
|
||||||
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
|
|
||||||
assert state.attributes.get("station") == "Test Name 1"
|
|
||||||
|
|
||||||
entry = registry.async_get("air_quality.home")
|
|
||||||
assert entry
|
|
||||||
assert entry.unique_id == "123"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_availability(hass):
|
|
||||||
"""Ensure that we mark the entities unavailable correctly when service causes an error."""
|
|
||||||
await init_integration(hass)
|
|
||||||
|
|
||||||
state = hass.states.get("air_quality.home")
|
|
||||||
assert state
|
|
||||||
assert state.state != STATE_UNAVAILABLE
|
|
||||||
assert state.state == "4"
|
|
||||||
|
|
||||||
future = utcnow() + timedelta(minutes=60)
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.gios.Gios._get_all_sensors",
|
|
||||||
side_effect=ApiError("Unexpected error"),
|
|
||||||
):
|
|
||||||
async_fire_time_changed(hass, future)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
state = hass.states.get("air_quality.home")
|
|
||||||
assert state
|
|
||||||
assert state.state == STATE_UNAVAILABLE
|
|
||||||
|
|
||||||
future = utcnow() + timedelta(minutes=120)
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.gios.Gios._get_all_sensors",
|
|
||||||
return_value=json.loads(load_fixture("gios/sensors.json")),
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.gios.Gios._get_indexes",
|
|
||||||
return_value=json.loads(load_fixture("gios/indexes.json")),
|
|
||||||
):
|
|
||||||
async_fire_time_changed(hass, future)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
state = hass.states.get("air_quality.home")
|
|
||||||
assert state
|
|
||||||
assert state.state != STATE_UNAVAILABLE
|
|
||||||
assert state.state == "4"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_migrate_unique_id(hass):
|
|
||||||
"""Test migrate unique_id of the air_quality entity."""
|
|
||||||
registry = er.async_get(hass)
|
|
||||||
|
|
||||||
# Pre-create registry entries for disabled by default sensors
|
|
||||||
registry.async_get_or_create(
|
|
||||||
AIR_QUALITY_DOMAIN,
|
|
||||||
DOMAIN,
|
|
||||||
123,
|
|
||||||
suggested_object_id="home",
|
|
||||||
disabled_by=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
await init_integration(hass)
|
|
||||||
|
|
||||||
entry = registry.async_get("air_quality.home")
|
|
||||||
assert entry
|
|
||||||
assert entry.unique_id == "123"
|
|
|
@ -2,9 +2,11 @@
|
||||||
import json
|
import json
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM
|
||||||
from homeassistant.components.gios.const import DOMAIN
|
from homeassistant.components.gios.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import STATE_UNAVAILABLE
|
from homeassistant.const import STATE_UNAVAILABLE
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from . import STATIONS
|
from . import STATIONS
|
||||||
|
|
||||||
|
@ -16,7 +18,7 @@ async def test_async_setup_entry(hass):
|
||||||
"""Test a successful setup entry."""
|
"""Test a successful setup entry."""
|
||||||
await init_integration(hass)
|
await init_integration(hass)
|
||||||
|
|
||||||
state = hass.states.get("air_quality.home")
|
state = hass.states.get("sensor.home_pm2_5")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state != STATE_UNAVAILABLE
|
assert state.state != STATE_UNAVAILABLE
|
||||||
assert state.state == "4"
|
assert state.state == "4"
|
||||||
|
@ -95,3 +97,21 @@ async def test_migrate_device_and_config_entry(hass):
|
||||||
config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, "123")}
|
config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, "123")}
|
||||||
)
|
)
|
||||||
assert device_entry.id == migrated_device_entry.id
|
assert device_entry.id == migrated_device_entry.id
|
||||||
|
|
||||||
|
|
||||||
|
async def test_remove_air_quality_entities(hass):
|
||||||
|
"""Test remove air_quality entities from registry."""
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
|
||||||
|
registry.async_get_or_create(
|
||||||
|
AIR_QUALITY_PLATFORM,
|
||||||
|
DOMAIN,
|
||||||
|
"123",
|
||||||
|
suggested_object_id="home",
|
||||||
|
disabled_by=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
await init_integration(hass)
|
||||||
|
|
||||||
|
entry = registry.async_get("air_quality.home")
|
||||||
|
assert entry is None
|
||||||
|
|
190
tests/components/gios/test_sensor.py
Normal file
190
tests/components/gios/test_sensor.py
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
"""Test sensor of GIOS integration."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import json
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from gios import ApiError
|
||||||
|
|
||||||
|
from homeassistant.components.gios.const import ATTR_STATION, ATTRIBUTION
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ATTRIBUTION,
|
||||||
|
ATTR_ICON,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed, load_fixture
|
||||||
|
from tests.components.gios import init_integration
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor(hass):
|
||||||
|
"""Test states of the sensor."""
|
||||||
|
await init_integration(hass)
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_c6h6")
|
||||||
|
assert state
|
||||||
|
assert state.state == "0"
|
||||||
|
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||||
|
assert state.attributes.get(ATTR_STATION) == "Test Name 1"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
|
||||||
|
|
||||||
|
entry = registry.async_get("sensor.home_c6h6")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "123-c6h6"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_co")
|
||||||
|
assert state
|
||||||
|
assert state.state == "252"
|
||||||
|
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||||
|
assert state.attributes.get(ATTR_STATION) == "Test Name 1"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
|
||||||
|
|
||||||
|
entry = registry.async_get("sensor.home_co")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "123-co"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_no2")
|
||||||
|
assert state
|
||||||
|
assert state.state == "7"
|
||||||
|
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||||
|
assert state.attributes.get(ATTR_STATION) == "Test Name 1"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
|
||||||
|
|
||||||
|
entry = registry.async_get("sensor.home_no2")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "123-no2"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_o3")
|
||||||
|
assert state
|
||||||
|
assert state.state == "96"
|
||||||
|
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||||
|
assert state.attributes.get(ATTR_STATION) == "Test Name 1"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
|
||||||
|
|
||||||
|
entry = registry.async_get("sensor.home_o3")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "123-o3"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_pm10")
|
||||||
|
assert state
|
||||||
|
assert state.state == "17"
|
||||||
|
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||||
|
assert state.attributes.get(ATTR_STATION) == "Test Name 1"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
|
||||||
|
|
||||||
|
entry = registry.async_get("sensor.home_pm10")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "123-pm10"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_pm2_5")
|
||||||
|
assert state
|
||||||
|
assert state.state == "4"
|
||||||
|
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||||
|
assert state.attributes.get(ATTR_STATION) == "Test Name 1"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
|
||||||
|
|
||||||
|
entry = registry.async_get("sensor.home_pm2_5")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "123-pm2.5"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_so2")
|
||||||
|
assert state
|
||||||
|
assert state.state == "4"
|
||||||
|
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||||
|
assert state.attributes.get(ATTR_STATION) == "Test Name 1"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
|
||||||
|
|
||||||
|
entry = registry.async_get("sensor.home_so2")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "123-so2"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_aqi")
|
||||||
|
assert state
|
||||||
|
assert state.state == "dobry"
|
||||||
|
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||||
|
assert state.attributes.get(ATTR_STATION) == "Test Name 1"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) is None
|
||||||
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
|
||||||
|
|
||||||
|
entry = registry.async_get("sensor.home_aqi")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "123-aqi"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_availability(hass):
|
||||||
|
"""Ensure that we mark the entities unavailable correctly when service causes an error."""
|
||||||
|
await init_integration(hass)
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_pm2_5")
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
assert state.state == "4"
|
||||||
|
|
||||||
|
future = utcnow() + timedelta(minutes=60)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.gios.Gios._get_all_sensors",
|
||||||
|
side_effect=ApiError("Unexpected error"),
|
||||||
|
):
|
||||||
|
async_fire_time_changed(hass, future)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_pm2_5")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
future = utcnow() + timedelta(minutes=120)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.gios.Gios._get_all_sensors",
|
||||||
|
return_value=json.loads(load_fixture("gios/sensors.json")),
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.gios.Gios._get_indexes",
|
||||||
|
return_value=json.loads(load_fixture("gios/indexes.json")),
|
||||||
|
):
|
||||||
|
async_fire_time_changed(hass, future)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.home_pm2_5")
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
assert state.state == "4"
|
Loading…
Add table
Reference in a new issue