diff --git a/homeassistant/components/gios/__init__.py b/homeassistant/components/gios/__init__.py index ab956fe9da7..c3227254075 100644 --- a/homeassistant/components/gios/__init__.py +++ b/homeassistant/components/gios/__init__.py @@ -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 diff --git a/homeassistant/components/gios/air_quality.py b/homeassistant/components/gios/air_quality.py deleted file mode 100644 index e74cec8e151..00000000000 --- a/homeassistant/components/gios/air_quality.py +++ /dev/null @@ -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 diff --git a/homeassistant/components/gios/const.py b/homeassistant/components/gios/const.py index d16225d90a7..f78e876a0e7 100644 --- a/homeassistant/components/gios/const.py +++ b/homeassistant/components/gios/const.py @@ -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, + }, } diff --git a/homeassistant/components/gios/model.py b/homeassistant/components/gios/model.py new file mode 100644 index 00000000000..867c950e183 --- /dev/null +++ b/homeassistant/components/gios/model.py @@ -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 diff --git a/homeassistant/components/gios/sensor.py b/homeassistant/components/gios/sensor.py new file mode 100644 index 00000000000..c0bda830f25 --- /dev/null +++ b/homeassistant/components/gios/sensor.py @@ -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) diff --git a/tests/components/gios/test_air_quality.py b/tests/components/gios/test_air_quality.py deleted file mode 100644 index b7ce8d1f97a..00000000000 --- a/tests/components/gios/test_air_quality.py +++ /dev/null @@ -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" diff --git a/tests/components/gios/test_init.py b/tests/components/gios/test_init.py index 08629608cd4..f0b3f660e8d 100644 --- a/tests/components/gios/test_init.py +++ b/tests/components/gios/test_init.py @@ -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 diff --git a/tests/components/gios/test_sensor.py b/tests/components/gios/test_sensor.py new file mode 100644 index 00000000000..4d43d90f9a2 --- /dev/null +++ b/tests/components/gios/test_sensor.py @@ -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"