Migrate GIOS to use DataUpdateCoordinator (#33306)

* Migrate to DataUpdateCoordinator

* Simplify code
This commit is contained in:
Maciej Bieniek 2020-03-29 05:57:06 +02:00 committed by GitHub
parent f7ae78f78e
commit de2f506585
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 86 deletions

View file

@ -1,5 +1,4 @@
"""The GIOS component.""" """The GIOS component."""
import asyncio
import logging import logging
from aiohttp.client_exceptions import ClientConnectorError from aiohttp.client_exceptions import ClientConnectorError
@ -7,18 +6,17 @@ from async_timeout import timeout
from gios import ApiError, Gios, NoStationError from gios import ApiError, Gios, NoStationError
from homeassistant.core import Config, HomeAssistant from homeassistant.core import Config, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession 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__) _LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: Config) -> bool: async def async_setup(hass: HomeAssistant, config: Config) -> bool:
"""Set up configured GIOS.""" """Set up configured GIOS."""
hass.data[DOMAIN] = {}
hass.data[DOMAIN][DATA_CLIENT] = {}
return True return True
@ -29,11 +27,14 @@ async def async_setup_entry(hass, config_entry):
websession = async_get_clientsession(hass) 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.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, "air_quality") 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): async def async_unload_entry(hass, config_entry):
"""Unload a 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") await hass.config_entries.async_forward_entry_unload(config_entry, "air_quality")
return True return True
class GiosData: class GiosDataUpdateCoordinator(DataUpdateCoordinator):
"""Define an object to hold GIOS data.""" """Define an object to hold GIOS data."""
def __init__(self, session, station_id): def __init__(self, hass, session, station_id):
"""Initialize.""" """Class to manage fetching GIOS data API."""
self._gios = Gios(station_id, session) 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
@Throttle(DEFAULT_SCAN_INTERVAL) super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
async def async_update(self):
"""Update GIOS data.""" async def _async_update_data(self):
"""Update data via library."""
try: try:
with timeout(30): with timeout(30):
await self._gios.update() await self.gios.update()
except asyncio.TimeoutError:
_LOGGER.error("Asyncio Timeout Error")
except (ApiError, NoStationError, ClientConnectorError) as error: except (ApiError, NoStationError, ClientConnectorError) as error:
_LOGGER.error("GIOS data update failed: %s", error) raise UpdateFailed(error)
self.available = self._gios.available if not self.gios.data:
self.latitude = self._gios.latitude raise UpdateFailed("Invalid sensors data")
self.longitude = self._gios.longitude return self.gios.data
self.station_name = self._gios.station_name
self.sensors = self._gios.data

View file

@ -10,19 +10,27 @@ from homeassistant.components.air_quality import (
) )
from homeassistant.const import CONF_NAME 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Ś" 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): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Add a GIOS entities from a config_entry.""" """Add a GIOS entities from a config_entry."""
name = config_entry.data[CONF_NAME] 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): def round_state(func):
@ -40,17 +48,10 @@ def round_state(func):
class GiosAirQuality(AirQualityEntity): class GiosAirQuality(AirQualityEntity):
"""Define an GIOS sensor.""" """Define an GIOS sensor."""
def __init__(self, gios, name): def __init__(self, coordinator, name):
"""Initialize.""" """Initialize."""
self.gios = gios self.coordinator = coordinator
self._name = name 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 = {} self._attrs = {}
@property @property
@ -61,50 +62,50 @@ class GiosAirQuality(AirQualityEntity):
@property @property
def icon(self): def icon(self):
"""Return the icon.""" """Return the icon."""
if self._aqi in ICONS_MAP: if self.air_quality_index in ICONS_MAP:
return ICONS_MAP[self._aqi] return ICONS_MAP[self.air_quality_index]
return "mdi:blur" return "mdi:blur"
@property @property
def air_quality_index(self): def air_quality_index(self):
"""Return the air quality index.""" """Return the air quality index."""
return self._aqi return self._get_sensor_value("AQI")
@property @property
@round_state @round_state
def particulate_matter_2_5(self): def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level.""" """Return the particulate matter 2.5 level."""
return self._pm_2_5 return self._get_sensor_value("PM2.5")
@property @property
@round_state @round_state
def particulate_matter_10(self): def particulate_matter_10(self):
"""Return the particulate matter 10 level.""" """Return the particulate matter 10 level."""
return self._pm_10 return self._get_sensor_value("PM10")
@property @property
@round_state @round_state
def ozone(self): def ozone(self):
"""Return the O3 (ozone) level.""" """Return the O3 (ozone) level."""
return self._o3 return self._get_sensor_value("O3")
@property @property
@round_state @round_state
def carbon_monoxide(self): def carbon_monoxide(self):
"""Return the CO (carbon monoxide) level.""" """Return the CO (carbon monoxide) level."""
return self._co return self._get_sensor_value("CO")
@property @property
@round_state @round_state
def sulphur_dioxide(self): def sulphur_dioxide(self):
"""Return the SO2 (sulphur dioxide) level.""" """Return the SO2 (sulphur dioxide) level."""
return self._so2 return self._get_sensor_value("SO2")
@property @property
@round_state @round_state
def nitrogen_dioxide(self): def nitrogen_dioxide(self):
"""Return the NO2 (nitrogen dioxide) level.""" """Return the NO2 (nitrogen dioxide) level."""
return self._no2 return self._get_sensor_value("NO2")
@property @property
def attribution(self): def attribution(self):
@ -114,45 +115,45 @@ class GiosAirQuality(AirQualityEntity):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique_id for this entity.""" """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 @property
def available(self): def available(self):
"""Return True if entity is available.""" """Return True if entity is available."""
return self.gios.available return self.coordinator.last_update_success
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """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 return self._attrs
async def async_update(self): async def async_added_to_hass(self):
"""Get the data from GIOS.""" """Connect to dispatcher listening for entity data notifications."""
await self.gios.async_update() self.coordinator.async_add_listener(self.async_write_ha_state)
if self.gios.available: async def async_will_remove_from_hass(self):
# Different measuring stations have different sets of sensors. We don't know """Disconnect from update signal."""
# what data we will get. self.coordinator.async_remove_listener(self.async_write_ha_state)
if "AQI" in self.gios.sensors:
self._aqi = self.gios.sensors["AQI"]["value"] async def async_update(self):
if "CO" in self.gios.sensors: """Update GIOS entity."""
self._co = self.gios.sensors["CO"]["value"] await self.coordinator.async_request_refresh()
self._attrs[f"{ATTR_CO}_index"] = self.gios.sensors["CO"]["index"]
if "NO2" in self.gios.sensors: def _get_sensor_value(self, sensor):
self._no2 = self.gios.sensors["NO2"]["value"] """Return value of specified sensor."""
self._attrs[f"{ATTR_NO2}_index"] = self.gios.sensors["NO2"]["index"] if sensor in self.coordinator.data:
if "O3" in self.gios.sensors: return self.coordinator.data[sensor]["value"]
self._o3 = self.gios.sensors["O3"]["value"] return None
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"]

View file

@ -4,10 +4,9 @@ from datetime import timedelta
ATTR_NAME = "name" ATTR_NAME = "name"
ATTR_STATION = "station" ATTR_STATION = "station"
CONF_STATION_ID = "station_id" CONF_STATION_ID = "station_id"
DATA_CLIENT = "client"
DEFAULT_NAME = "GIOŚ" DEFAULT_NAME = "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.
DEFAULT_SCAN_INTERVAL = timedelta(minutes=30) SCAN_INTERVAL = timedelta(minutes=30)
DOMAIN = "gios" DOMAIN = "gios"
AQI_GOOD = "dobry" AQI_GOOD = "dobry"