Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Initial commit * Add gios to requirements * Add tests * Update .coveragerc file * Run gen_requirements_all.py * Change DEFAULT_SCAN_INTERVAL * Better strings * Bump library version * run script.hassfest * run isort * Add icons mapping * Remove unnecessary f-string * Remove unnecessary listener * Refactoring config_flow * Add unique_id to config entry * Change AQI states to consts in English * Remove unused init * Remove unused exception * Remove private instance attribute * Remove overwrite state property * Fix pylint error * Add SCAN_INTERVAL for air_quality entity * Add _abort_if_unique_id_configured()
- Loading branch information
1 parent
1ee299b
commit 2c1a7a5
Showing
13 changed files
with
471 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
"""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.helpers.aiohttp_client import async_get_clientsession | ||
from homeassistant.util import Throttle | ||
|
||
from .const import CONF_STATION_ID, DATA_CLIENT, DEFAULT_SCAN_INTERVAL, DOMAIN | ||
|
||
_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 | ||
|
||
|
||
async def async_setup_entry(hass, config_entry): | ||
"""Set up GIOS as config entry.""" | ||
station_id = config_entry.data[CONF_STATION_ID] | ||
_LOGGER.debug("Using station_id: %s", station_id) | ||
|
||
websession = async_get_clientsession(hass) | ||
|
||
gios = GiosData(websession, station_id) | ||
|
||
await gios.async_update() | ||
|
||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = gios | ||
|
||
hass.async_create_task( | ||
hass.config_entries.async_forward_entry_setup(config_entry, "air_quality") | ||
) | ||
return True | ||
|
||
|
||
async def async_unload_entry(hass, config_entry): | ||
"""Unload a config entry.""" | ||
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) | ||
await hass.config_entries.async_forward_entry_unload(config_entry, "air_quality") | ||
return True | ||
|
||
|
||
class GiosData: | ||
"""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.""" | ||
try: | ||
with timeout(30): | ||
await self._gios.update() | ||
except asyncio.TimeoutError: | ||
_LOGGER.error("Asyncio Timeout Error") | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
"""Support for the GIOS service.""" | ||
from homeassistant.components.air_quality import ( | ||
ATTR_CO, | ||
ATTR_NO2, | ||
ATTR_OZONE, | ||
ATTR_PM_2_5, | ||
ATTR_PM_10, | ||
ATTR_SO2, | ||
AirQualityEntity, | ||
) | ||
from homeassistant.const import CONF_NAME | ||
|
||
from .const import ATTR_STATION, DATA_CLIENT, DEFAULT_SCAN_INTERVAL, DOMAIN, ICONS_MAP | ||
|
||
ATTRIBUTION = "Data provided by GIOŚ" | ||
SCAN_INTERVAL = DEFAULT_SCAN_INTERVAL | ||
|
||
|
||
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] | ||
|
||
async_add_entities([GiosAirQuality(data, name)], True) | ||
|
||
|
||
def round_state(func): | ||
"""Round state.""" | ||
|
||
def _decorator(self): | ||
res = func(self) | ||
if isinstance(res, float): | ||
return round(res) | ||
return res | ||
|
||
return _decorator | ||
|
||
|
||
class GiosAirQuality(AirQualityEntity): | ||
"""Define an GIOS sensor.""" | ||
|
||
def __init__(self, gios, name): | ||
"""Initialize.""" | ||
self.gios = gios | ||
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 | ||
def name(self): | ||
"""Return the name.""" | ||
return self._name | ||
|
||
@property | ||
def icon(self): | ||
"""Return the icon.""" | ||
if self._aqi in ICONS_MAP: | ||
return ICONS_MAP[self._aqi] | ||
return "mdi:blur" | ||
|
||
@property | ||
def air_quality_index(self): | ||
"""Return the air quality index.""" | ||
return self._aqi | ||
|
||
@property | ||
@round_state | ||
def particulate_matter_2_5(self): | ||
"""Return the particulate matter 2.5 level.""" | ||
return self._pm_2_5 | ||
|
||
@property | ||
@round_state | ||
def particulate_matter_10(self): | ||
"""Return the particulate matter 10 level.""" | ||
return self._pm_10 | ||
|
||
@property | ||
@round_state | ||
def ozone(self): | ||
"""Return the O3 (ozone) level.""" | ||
return self._o3 | ||
|
||
@property | ||
@round_state | ||
def carbon_monoxide(self): | ||
"""Return the CO (carbon monoxide) level.""" | ||
return self._co | ||
|
||
@property | ||
@round_state | ||
def sulphur_dioxide(self): | ||
"""Return the SO2 (sulphur dioxide) level.""" | ||
return self._so2 | ||
|
||
@property | ||
@round_state | ||
def nitrogen_dioxide(self): | ||
"""Return the NO2 (nitrogen dioxide) level.""" | ||
return self._no2 | ||
|
||
@property | ||
def attribution(self): | ||
"""Return the attribution.""" | ||
return ATTRIBUTION | ||
|
||
@property | ||
def unique_id(self): | ||
"""Return a unique_id for this entity.""" | ||
return self.gios.station_id | ||
|
||
@property | ||
def available(self): | ||
"""Return True if entity is available.""" | ||
return self.gios.available | ||
|
||
@property | ||
def device_state_attributes(self): | ||
"""Return the state attributes.""" | ||
self._attrs[ATTR_STATION] = self.gios.station_name | ||
return self._attrs | ||
|
||
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
"""Adds config flow for GIOS.""" | ||
import asyncio | ||
|
||
from aiohttp.client_exceptions import ClientConnectorError | ||
from async_timeout import timeout | ||
from gios import ApiError, Gios, NoStationError | ||
import voluptuous as vol | ||
|
||
from homeassistant import config_entries, exceptions | ||
from homeassistant.const import CONF_NAME | ||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
||
from .const import CONF_STATION_ID, DEFAULT_NAME, DOMAIN # pylint:disable=unused-import | ||
|
||
DATA_SCHEMA = vol.Schema( | ||
{ | ||
vol.Required(CONF_STATION_ID): int, | ||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, | ||
} | ||
) | ||
|
||
|
||
class GiosFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): | ||
"""Config flow for GIOS.""" | ||
|
||
VERSION = 1 | ||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL | ||
|
||
async def async_step_user(self, user_input=None): | ||
"""Handle a flow initialized by the user.""" | ||
errors = {} | ||
|
||
if user_input is not None: | ||
try: | ||
await self.async_set_unique_id( | ||
user_input[CONF_STATION_ID], raise_on_progress=False | ||
) | ||
self._abort_if_unique_id_configured() | ||
|
||
websession = async_get_clientsession(self.hass) | ||
|
||
with timeout(30): | ||
gios = Gios(user_input[CONF_STATION_ID], websession) | ||
await gios.update() | ||
|
||
if not gios.available: | ||
raise InvalidSensorsData() | ||
|
||
return self.async_create_entry( | ||
title=user_input[CONF_STATION_ID], data=user_input, | ||
) | ||
except (ApiError, ClientConnectorError, asyncio.TimeoutError): | ||
errors["base"] = "cannot_connect" | ||
except NoStationError: | ||
errors[CONF_STATION_ID] = "wrong_station_id" | ||
except InvalidSensorsData: | ||
errors[CONF_STATION_ID] = "invalid_sensors_data" | ||
|
||
return self.async_show_form( | ||
step_id="user", data_schema=DATA_SCHEMA, errors=errors | ||
) | ||
|
||
|
||
class InvalidSensorsData(exceptions.HomeAssistantError): | ||
"""Error to indicate invalid sensors data.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
"""Constants for GIOS integration.""" | ||
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) | ||
DOMAIN = "gios" | ||
|
||
AQI_GOOD = "dobry" | ||
AQI_MODERATE = "umiarkowany" | ||
AQI_POOR = "dostateczny" | ||
AQI_VERY_GOOD = "bardzo dobry" | ||
AQI_VERY_POOR = "zły" | ||
|
||
ICONS_MAP = { | ||
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", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"domain": "gios", | ||
"name": "GIOŚ", | ||
"documentation": "https://www.home-assistant.io/integrations/gios", | ||
"dependencies": [], | ||
"codeowners": ["@bieniu"], | ||
"requirements": ["gios==0.0.3"], | ||
"config_flow": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"config": { | ||
"title": "GIOŚ", | ||
"step": { | ||
"user": { | ||
"title": "GIOŚ (Polish Chief Inspectorate Of Environmental Protection)", | ||
"description": "Set up GIOŚ (Polish Chief Inspectorate Of Environmental Protection) air quality integration. If you need help with the configuration have a look here: https://www.home-assistant.io/integrations/gios", | ||
"data": { | ||
"name": "Name of the integration", | ||
"station_id": "ID of the measuring station" | ||
} | ||
} | ||
}, | ||
"error": { | ||
"wrong_station_id": "ID of the measuring station is not correct.", | ||
"invalid_sensors_data": "Invalid sensors' data for this measuring station.", | ||
"cannot_connect": "Cannot connect to the GIOŚ server." | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ | |
"geofency", | ||
"geonetnz_quakes", | ||
"geonetnz_volcano", | ||
"gios", | ||
"glances", | ||
"gpslogger", | ||
"hangouts", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.