Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate GIOS to use DataUpdateCoordinator #33306

Merged
merged 2 commits into from Mar 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 24 additions & 32 deletions homeassistant/components/gios/__init__.py
@@ -1,24 +1,22 @@
"""The GIOS component."""
import asyncio
import logging

from aiohttp.client_exceptions import ClientConnectorError
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


Expand All @@ -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")
Expand All @@ -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

@Throttle(DEFAULT_SCAN_INTERVAL)
async def async_update(self):
"""Update GIOS data."""
def __init__(self, hass, session, station_id):
"""Class to manage fetching GIOS data API."""
self.gios = Gios(station_id, session)

super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)

async def _async_update_data(self):
"""Update data via library."""
try:
with timeout(30):
frenck marked this conversation as resolved.
Show resolved Hide resolved
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
107 changes: 54 additions & 53 deletions homeassistant/components/gios/air_quality.py
Expand Up @@ -10,19 +10,27 @@
)
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):
Expand All @@ -40,17 +48,10 @@ def _decorator(self):
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
Expand All @@ -61,50 +62,50 @@ def name(self):
@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):
Expand All @@ -114,45 +115,45 @@ def attribution(self):
@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_added_to_hass(self):
"""Connect to dispatcher listening for entity data notifications."""
self.coordinator.async_add_listener(self.async_write_ha_state)

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):
"""Get the data from GIOS."""
await self.gios.async_update()

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"]
"""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
3 changes: 1 addition & 2 deletions homeassistant/components/gios/const.py
Expand Up @@ -4,10 +4,9 @@
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"
Expand Down