diff --git a/homeassistant/components/gios/__init__.py b/homeassistant/components/gios/__init__.py index 981de6395de..0a7973709c1 100644 --- a/homeassistant/components/gios/__init__.py +++ b/homeassistant/components/gios/__init__.py @@ -1,5 +1,4 @@ """The GIOS component.""" -import asyncio import logging from aiohttp.client_exceptions import ClientConnectorError @@ -7,18 +6,17 @@ from async_timeout import timeout from gios import ApiError, Gios, NoStationError from homeassistant.core import Config, HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.util import Throttle +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import CONF_STATION_ID, DATA_CLIENT, DEFAULT_SCAN_INTERVAL, DOMAIN +from .const import CONF_STATION_ID, DOMAIN, SCAN_INTERVAL _LOGGER = logging.getLogger(__name__) async def async_setup(hass: HomeAssistant, config: Config) -> bool: """Set up configured GIOS.""" - hass.data[DOMAIN] = {} - hass.data[DOMAIN][DATA_CLIENT] = {} return True @@ -29,11 +27,14 @@ async def async_setup_entry(hass, config_entry): websession = async_get_clientsession(hass) - gios = GiosData(websession, station_id) + coordinator = GiosDataUpdateCoordinator(hass, websession, station_id) + await coordinator.async_refresh() - await gios.async_update() + if not coordinator.last_update_success: + raise ConfigEntryNotReady - hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = gios + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][config_entry.entry_id] = coordinator hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, "air_quality") @@ -43,36 +44,27 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) + hass.data[DOMAIN].pop(config_entry.entry_id) await hass.config_entries.async_forward_entry_unload(config_entry, "air_quality") return True -class GiosData: +class GiosDataUpdateCoordinator(DataUpdateCoordinator): """Define an object to hold GIOS data.""" - def __init__(self, session, station_id): - """Initialize.""" - self._gios = Gios(station_id, session) - self.station_id = station_id - self.sensors = {} - self.latitude = None - self.longitude = None - self.station_name = None - self.available = True + def __init__(self, hass, session, station_id): + """Class to manage fetching GIOS data API.""" + self.gios = Gios(station_id, session) - @Throttle(DEFAULT_SCAN_INTERVAL) - async def async_update(self): - """Update GIOS data.""" + super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) + + async def _async_update_data(self): + """Update data via library.""" try: with timeout(30): - await self._gios.update() - except asyncio.TimeoutError: - _LOGGER.error("Asyncio Timeout Error") + await self.gios.update() except (ApiError, NoStationError, ClientConnectorError) as error: - _LOGGER.error("GIOS data update failed: %s", error) - self.available = self._gios.available - self.latitude = self._gios.latitude - self.longitude = self._gios.longitude - self.station_name = self._gios.station_name - self.sensors = self._gios.data + raise UpdateFailed(error) + if not self.gios.data: + raise UpdateFailed("Invalid sensors data") + return self.gios.data diff --git a/homeassistant/components/gios/air_quality.py b/homeassistant/components/gios/air_quality.py index f7285c8cc5a..c8cd8be11c7 100644 --- a/homeassistant/components/gios/air_quality.py +++ b/homeassistant/components/gios/air_quality.py @@ -10,19 +10,27 @@ from homeassistant.components.air_quality import ( ) from homeassistant.const import CONF_NAME -from .const import ATTR_STATION, DATA_CLIENT, DEFAULT_SCAN_INTERVAL, DOMAIN, ICONS_MAP +from .const import ATTR_STATION, DOMAIN, ICONS_MAP ATTRIBUTION = "Data provided by GIOŚ" -SCAN_INTERVAL = DEFAULT_SCAN_INTERVAL + +SENSOR_MAP = { + "CO": ATTR_CO, + "NO2": ATTR_NO2, + "O3": ATTR_OZONE, + "PM10": ATTR_PM_10, + "PM2.5": ATTR_PM_2_5, + "SO2": ATTR_SO2, +} async def async_setup_entry(hass, config_entry, async_add_entities): """Add a GIOS entities from a config_entry.""" name = config_entry.data[CONF_NAME] - data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] + coordinator = hass.data[DOMAIN][config_entry.entry_id] - async_add_entities([GiosAirQuality(data, name)], True) + async_add_entities([GiosAirQuality(coordinator, name)], False) def round_state(func): @@ -40,17 +48,10 @@ def round_state(func): class GiosAirQuality(AirQualityEntity): """Define an GIOS sensor.""" - def __init__(self, gios, name): + def __init__(self, coordinator, name): """Initialize.""" - self.gios = gios + self.coordinator = coordinator self._name = name - self._aqi = None - self._co = None - self._no2 = None - self._o3 = None - self._pm_2_5 = None - self._pm_10 = None - self._so2 = None self._attrs = {} @property @@ -61,50 +62,50 @@ class GiosAirQuality(AirQualityEntity): @property def icon(self): """Return the icon.""" - if self._aqi in ICONS_MAP: - return ICONS_MAP[self._aqi] + if self.air_quality_index in ICONS_MAP: + return ICONS_MAP[self.air_quality_index] return "mdi:blur" @property def air_quality_index(self): """Return the air quality index.""" - return self._aqi + return self._get_sensor_value("AQI") @property @round_state def particulate_matter_2_5(self): """Return the particulate matter 2.5 level.""" - return self._pm_2_5 + return self._get_sensor_value("PM2.5") @property @round_state def particulate_matter_10(self): """Return the particulate matter 10 level.""" - return self._pm_10 + return self._get_sensor_value("PM10") @property @round_state def ozone(self): """Return the O3 (ozone) level.""" - return self._o3 + return self._get_sensor_value("O3") @property @round_state def carbon_monoxide(self): """Return the CO (carbon monoxide) level.""" - return self._co + return self._get_sensor_value("CO") @property @round_state def sulphur_dioxide(self): """Return the SO2 (sulphur dioxide) level.""" - return self._so2 + return self._get_sensor_value("SO2") @property @round_state def nitrogen_dioxide(self): """Return the NO2 (nitrogen dioxide) level.""" - return self._no2 + return self._get_sensor_value("NO2") @property def attribution(self): @@ -114,45 +115,45 @@ class GiosAirQuality(AirQualityEntity): @property def unique_id(self): """Return a unique_id for this entity.""" - return self.gios.station_id + return self.coordinator.gios.station_id + + @property + def should_poll(self): + """Return the polling requirement of the entity.""" + return False @property def available(self): """Return True if entity is available.""" - return self.gios.available + return self.coordinator.last_update_success @property def device_state_attributes(self): """Return the state attributes.""" - self._attrs[ATTR_STATION] = self.gios.station_name + # 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 - async def async_update(self): - """Get the data from GIOS.""" - await self.gios.async_update() + async def async_added_to_hass(self): + """Connect to dispatcher listening for entity data notifications.""" + self.coordinator.async_add_listener(self.async_write_ha_state) - if self.gios.available: - # Different measuring stations have different sets of sensors. We don't know - # what data we will get. - if "AQI" in self.gios.sensors: - self._aqi = self.gios.sensors["AQI"]["value"] - if "CO" in self.gios.sensors: - self._co = self.gios.sensors["CO"]["value"] - self._attrs[f"{ATTR_CO}_index"] = self.gios.sensors["CO"]["index"] - if "NO2" in self.gios.sensors: - self._no2 = self.gios.sensors["NO2"]["value"] - self._attrs[f"{ATTR_NO2}_index"] = self.gios.sensors["NO2"]["index"] - if "O3" in self.gios.sensors: - self._o3 = self.gios.sensors["O3"]["value"] - self._attrs[f"{ATTR_OZONE}_index"] = self.gios.sensors["O3"]["index"] - if "PM2.5" in self.gios.sensors: - self._pm_2_5 = self.gios.sensors["PM2.5"]["value"] - self._attrs[f"{ATTR_PM_2_5}_index"] = self.gios.sensors["PM2.5"][ - "index" - ] - if "PM10" in self.gios.sensors: - self._pm_10 = self.gios.sensors["PM10"]["value"] - self._attrs[f"{ATTR_PM_10}_index"] = self.gios.sensors["PM10"]["index"] - if "SO2" in self.gios.sensors: - self._so2 = self.gios.sensors["SO2"]["value"] - self._attrs[f"{ATTR_SO2}_index"] = self.gios.sensors["SO2"]["index"] + async def async_will_remove_from_hass(self): + """Disconnect from update signal.""" + self.coordinator.async_remove_listener(self.async_write_ha_state) + + async def async_update(self): + """Update GIOS entity.""" + await self.coordinator.async_request_refresh() + + def _get_sensor_value(self, sensor): + """Return value of specified sensor.""" + if sensor in self.coordinator.data: + return self.coordinator.data[sensor]["value"] + return None diff --git a/homeassistant/components/gios/const.py b/homeassistant/components/gios/const.py index 3588b5e8dfc..918b4fba2e4 100644 --- a/homeassistant/components/gios/const.py +++ b/homeassistant/components/gios/const.py @@ -4,10 +4,9 @@ from datetime import timedelta ATTR_NAME = "name" ATTR_STATION = "station" CONF_STATION_ID = "station_id" -DATA_CLIENT = "client" DEFAULT_NAME = "GIOŚ" # Term of service GIOŚ allow downloading data no more than twice an hour. -DEFAULT_SCAN_INTERVAL = timedelta(minutes=30) +SCAN_INTERVAL = timedelta(minutes=30) DOMAIN = "gios" AQI_GOOD = "dobry"