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 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.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import async_get_registry
|
||||
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__)
|
||||
|
||||
PLATFORMS = ["air_quality"]
|
||||
PLATFORMS = ["sensor"]
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
|
|
|
@ -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 typing import Final
|
||||
|
||||
from homeassistant.components.air_quality import (
|
||||
ATTR_CO,
|
||||
ATTR_NO2,
|
||||
ATTR_OZONE,
|
||||
ATTR_PM_2_5,
|
||||
ATTR_PM_10,
|
||||
ATTR_SO2,
|
||||
)
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
||||
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
|
||||
from .model import SensorDescription
|
||||
|
||||
ATTRIBUTION: Final = "Data provided by GIOŚ"
|
||||
|
||||
ATTR_STATION: Final = "station"
|
||||
CONF_STATION_ID: Final = "station_id"
|
||||
DEFAULT_NAME: Final = "GIOŚ"
|
||||
# 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"
|
||||
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
|
||||
|
||||
AQI_GOOD: Final = "dobry"
|
||||
AQI_MODERATE: Final = "umiarkowany"
|
||||
AQI_POOR: Final = "dostateczny"
|
||||
AQI_VERY_GOOD: Final = "bardzo dobry"
|
||||
AQI_VERY_POOR: Final = "zły"
|
||||
ATTR_INDEX: Final = "index"
|
||||
ATTR_STATION: Final = "station"
|
||||
ATTR_UNIT: Final = "unit"
|
||||
ATTR_VALUE: Final = "value"
|
||||
ATTR_STATION_NAME: Final = "station_name"
|
||||
|
||||
ICONS_MAP: Final[dict[str, str]] = {
|
||||
AQI_VERY_GOOD: "mdi:emoticon-excited",
|
||||
AQI_GOOD: "mdi:emoticon-happy",
|
||||
AQI_MODERATE: "mdi:emoticon-neutral",
|
||||
AQI_POOR: "mdi:emoticon-sad",
|
||||
AQI_VERY_POOR: "mdi:emoticon-dead",
|
||||
}
|
||||
ATTR_C6H6: Final = "c6h6"
|
||||
ATTR_CO: Final = "co"
|
||||
ATTR_NO2: Final = "no2"
|
||||
ATTR_O3: Final = "o3"
|
||||
ATTR_PM10: Final = "pm10"
|
||||
ATTR_PM25: Final = "pm2.5"
|
||||
ATTR_SO2: Final = "so2"
|
||||
ATTR_AQI: Final = "aqi"
|
||||
|
||||
SENSOR_MAP: Final[dict[str, str]] = {
|
||||
API_CO: ATTR_CO,
|
||||
API_NO2: ATTR_NO2,
|
||||
API_O3: ATTR_OZONE,
|
||||
API_PM10: ATTR_PM_10,
|
||||
API_PM25: ATTR_PM_2_5,
|
||||
API_SO2: ATTR_SO2,
|
||||
SENSOR_TYPES: Final[dict[str, SensorDescription]] = {
|
||||
ATTR_AQI: {},
|
||||
ATTR_C6H6: {
|
||||
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
ATTR_VALUE: round,
|
||||
},
|
||||
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
|
||||
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.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import STATIONS
|
||||
|
||||
|
@ -16,7 +18,7 @@ async def test_async_setup_entry(hass):
|
|||
"""Test a successful setup entry."""
|
||||
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.state != STATE_UNAVAILABLE
|
||||
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")}
|
||||
)
|
||||
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