Update Pollen.com sensor platform to include asthma info (#18024)
* Update Pollen.com sensor platform to include asthma info * Updated requirements * Bump to 2.2.2
This commit is contained in:
parent
4a3f754033
commit
4ee21e66dc
2 changed files with 188 additions and 112 deletions
|
@ -18,9 +18,10 @@ from homeassistant.helpers import aiohttp_client
|
|||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['pypollencom==2.1.0']
|
||||
REQUIREMENTS = ['pypollencom==2.2.2']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_ALLERGEN_AMOUNT = 'allergen_amount'
|
||||
ATTR_ALLERGEN_GENUS = 'allergen_genus'
|
||||
ATTR_ALLERGEN_NAME = 'allergen_name'
|
||||
ATTR_ALLERGEN_TYPE = 'allergen_type'
|
||||
|
@ -43,21 +44,35 @@ TYPE_ALLERGY_OUTLOOK = 'allergy_outlook'
|
|||
TYPE_ALLERGY_TODAY = 'allergy_index_today'
|
||||
TYPE_ALLERGY_TOMORROW = 'allergy_index_tomorrow'
|
||||
TYPE_ALLERGY_YESTERDAY = 'allergy_index_yesterday'
|
||||
TYPE_ASTHMA_FORECAST = 'asthma_average_forecasted'
|
||||
TYPE_ASTHMA_HISTORIC = 'asthma_average_historical'
|
||||
TYPE_ASTHMA_INDEX = 'asthma_index'
|
||||
TYPE_ASTHMA_TODAY = 'asthma_index_today'
|
||||
TYPE_ASTHMA_TOMORROW = 'asthma_index_tomorrow'
|
||||
TYPE_ASTHMA_YESTERDAY = 'asthma_index_yesterday'
|
||||
TYPE_DISEASE_FORECAST = 'disease_average_forecasted'
|
||||
|
||||
SENSORS = {
|
||||
TYPE_ALLERGY_FORECAST: (
|
||||
'Allergy Index: Forecasted Average', None, 'mdi:flower', 'index'),
|
||||
'ForecastSensor', 'Allergy Index: Forecasted Average', 'mdi:flower'),
|
||||
TYPE_ALLERGY_HISTORIC: (
|
||||
'Allergy Index: Historical Average', None, 'mdi:flower', 'index'),
|
||||
TYPE_ALLERGY_TODAY: (
|
||||
'Allergy Index: Today', TYPE_ALLERGY_INDEX, 'mdi:flower', 'index'),
|
||||
'HistoricalSensor', 'Allergy Index: Historical Average', 'mdi:flower'),
|
||||
TYPE_ALLERGY_TODAY: ('IndexSensor', 'Allergy Index: Today', 'mdi:flower'),
|
||||
TYPE_ALLERGY_TOMORROW: (
|
||||
'Allergy Index: Tomorrow', TYPE_ALLERGY_INDEX, 'mdi:flower', 'index'),
|
||||
'IndexSensor', 'Allergy Index: Tomorrow', 'mdi:flower'),
|
||||
TYPE_ALLERGY_YESTERDAY: (
|
||||
'Allergy Index: Yesterday', TYPE_ALLERGY_INDEX, 'mdi:flower', 'index'),
|
||||
'IndexSensor', 'Allergy Index: Yesterday', 'mdi:flower'),
|
||||
TYPE_ASTHMA_TODAY: ('IndexSensor', 'Ashma Index: Today', 'mdi:flower'),
|
||||
TYPE_ASTHMA_TOMORROW: (
|
||||
'IndexSensor', 'Ashma Index: Tomorrow', 'mdi:flower'),
|
||||
TYPE_ASTHMA_YESTERDAY: (
|
||||
'IndexSensor', 'Ashma Index: Yesterday', 'mdi:flower'),
|
||||
TYPE_ASTHMA_FORECAST: (
|
||||
'ForecastSensor', 'Asthma Index: Forecasted Average', 'mdi:flower'),
|
||||
TYPE_ASTHMA_HISTORIC: (
|
||||
'HistoricalSensor', 'Asthma Index: Historical Average', 'mdi:flower'),
|
||||
TYPE_DISEASE_FORECAST: (
|
||||
'Cold & Flu: Forecasted Average', None, 'mdi:snowflake', 'index')
|
||||
'ForecastSensor', 'Cold & Flu: Forecasted Average', 'mdi:snowflake')
|
||||
}
|
||||
|
||||
RATING_MAPPING = [{
|
||||
|
@ -87,7 +102,8 @@ TREND_INCREASING = 'Increasing'
|
|||
TREND_SUBSIDING = 'Subsiding'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ZIP_CODE): str,
|
||||
vol.Required(CONF_ZIP_CODE):
|
||||
str,
|
||||
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSORS)):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSORS)])
|
||||
})
|
||||
|
@ -100,18 +116,18 @@ async def async_setup_platform(
|
|||
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
data = PollenComData(
|
||||
pollen = PollenComData(
|
||||
Client(config[CONF_ZIP_CODE], websession),
|
||||
config[CONF_MONITORED_CONDITIONS])
|
||||
|
||||
await data.async_update()
|
||||
await pollen.async_update()
|
||||
|
||||
sensors = []
|
||||
for kind in config[CONF_MONITORED_CONDITIONS]:
|
||||
name, category, icon, unit = SENSORS[kind]
|
||||
sensor_class, name, icon = SENSORS[kind]
|
||||
sensors.append(
|
||||
PollencomSensor(
|
||||
data, config[CONF_ZIP_CODE], kind, category, name, icon, unit))
|
||||
globals()[sensor_class](
|
||||
pollen, kind, name, icon, config[CONF_ZIP_CODE]))
|
||||
|
||||
async_add_entities(sensors, True)
|
||||
|
||||
|
@ -124,27 +140,31 @@ def calculate_average_rating(indices):
|
|||
return max(set(ratings), key=ratings.count)
|
||||
|
||||
|
||||
class PollencomSensor(Entity):
|
||||
"""Define a Pollen.com sensor."""
|
||||
class BaseSensor(Entity):
|
||||
"""Define a base Pollen.com sensor."""
|
||||
|
||||
def __init__(self, pollencom, zip_code, kind, category, name, icon, unit):
|
||||
def __init__(self, pollen, kind, name, icon, zip_code):
|
||||
"""Initialize the sensor."""
|
||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
self._category = category
|
||||
self._icon = icon
|
||||
self._kind = kind
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._type = kind
|
||||
self._unit = unit
|
||||
self._zip_code = zip_code
|
||||
self.pollencom = pollencom
|
||||
self.pollen = pollen
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return bool(
|
||||
self.pollencom.data.get(self._type)
|
||||
or self.pollencom.data.get(self._category))
|
||||
if self._kind in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW,
|
||||
TYPE_ALLERGY_YESTERDAY):
|
||||
return bool(self.pollen.data[TYPE_ALLERGY_INDEX])
|
||||
|
||||
if self._kind in (TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW,
|
||||
TYPE_ASTHMA_YESTERDAY):
|
||||
return bool(self.pollen.data[TYPE_ASTHMA_INDEX])
|
||||
|
||||
return bool(self.pollen.data[self._kind])
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
@ -169,24 +189,24 @@ class PollencomSensor(Entity):
|
|||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||
return '{0}_{1}'.format(self._zip_code, self._type)
|
||||
return '{0}_{1}'.format(self._zip_code, self._kind)
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit the value is expressed in."""
|
||||
return self._unit
|
||||
return 'index'
|
||||
|
||||
|
||||
class ForecastSensor(BaseSensor):
|
||||
"""Define sensor related to forecast data."""
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the sensor."""
|
||||
await self.pollencom.async_update()
|
||||
if not self.pollencom.data:
|
||||
await self.pollen.async_update()
|
||||
if not self.pollen.data:
|
||||
return
|
||||
|
||||
if self._category:
|
||||
data = self.pollencom.data[self._category].get('Location')
|
||||
else:
|
||||
data = self.pollencom.data[self._type].get('Location')
|
||||
|
||||
data = self.pollen.data[self._kind].get('Location')
|
||||
if not data:
|
||||
return
|
||||
|
||||
|
@ -196,44 +216,101 @@ class PollencomSensor(Entity):
|
|||
i['label'] for i in RATING_MAPPING
|
||||
if i['minimum'] <= average <= i['maximum']
|
||||
]
|
||||
|
||||
slope = (data['periods'][-1]['Index'] - data['periods'][-2]['Index'])
|
||||
trend = TREND_FLAT
|
||||
if slope > 0:
|
||||
trend = TREND_INCREASING
|
||||
elif slope < 0:
|
||||
trend = TREND_SUBSIDING
|
||||
else:
|
||||
trend = TREND_FLAT
|
||||
|
||||
if self._type == TYPE_ALLERGY_FORECAST:
|
||||
outlook = self.pollencom.data[TYPE_ALLERGY_OUTLOOK]
|
||||
self._attrs.update({
|
||||
ATTR_CITY: data['City'].title(),
|
||||
ATTR_RATING: rating,
|
||||
ATTR_STATE: data['State'],
|
||||
ATTR_TREND: trend,
|
||||
ATTR_ZIP_CODE: data['ZIP']
|
||||
})
|
||||
|
||||
self._attrs.update({
|
||||
ATTR_CITY: data['City'].title(),
|
||||
ATTR_OUTLOOK: outlook['Outlook'],
|
||||
ATTR_RATING: rating,
|
||||
ATTR_SEASON: outlook['Season'].title(),
|
||||
ATTR_STATE: data['State'],
|
||||
ATTR_TREND: outlook['Trend'].title(),
|
||||
ATTR_ZIP_CODE: data['ZIP']
|
||||
})
|
||||
self._state = average
|
||||
elif self._type == TYPE_ALLERGY_HISTORIC:
|
||||
self._attrs.update({
|
||||
ATTR_CITY: data['City'].title(),
|
||||
ATTR_RATING: calculate_average_rating(indices),
|
||||
ATTR_STATE: data['State'],
|
||||
ATTR_TREND: trend,
|
||||
ATTR_ZIP_CODE: data['ZIP']
|
||||
})
|
||||
self._state = average
|
||||
elif self._type in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW,
|
||||
TYPE_ALLERGY_YESTERDAY):
|
||||
key = self._type.split('_')[-1].title()
|
||||
[period] = [p for p in data['periods'] if p['Type'] == key]
|
||||
[rating] = [
|
||||
i['label'] for i in RATING_MAPPING
|
||||
if i['minimum'] <= period['Index'] <= i['maximum']
|
||||
]
|
||||
if self._kind == TYPE_ALLERGY_FORECAST:
|
||||
outlook = self.pollen.data[TYPE_ALLERGY_OUTLOOK]
|
||||
self._attrs[ATTR_OUTLOOK] = outlook['Outlook']
|
||||
|
||||
self._state = average
|
||||
|
||||
|
||||
class HistoricalSensor(BaseSensor):
|
||||
"""Define sensor related to historical data."""
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the sensor."""
|
||||
await self.pollen.async_update()
|
||||
if not self.pollen.data:
|
||||
return
|
||||
|
||||
data = self.pollen.data[self._kind].get('Location')
|
||||
if not data:
|
||||
return
|
||||
|
||||
indices = [p['Index'] for p in data['periods']]
|
||||
average = round(mean(indices), 1)
|
||||
|
||||
slope = (data['periods'][-1]['Index'] - data['periods'][-2]['Index'])
|
||||
if slope > 0:
|
||||
trend = TREND_INCREASING
|
||||
elif slope < 0:
|
||||
trend = TREND_SUBSIDING
|
||||
else:
|
||||
trend = TREND_FLAT
|
||||
|
||||
self._attrs.update({
|
||||
ATTR_CITY: data['City'].title(),
|
||||
ATTR_RATING: calculate_average_rating(indices),
|
||||
ATTR_STATE: data['State'],
|
||||
ATTR_TREND: trend,
|
||||
ATTR_ZIP_CODE: data['ZIP']
|
||||
})
|
||||
|
||||
self._state = average
|
||||
|
||||
|
||||
class IndexSensor(BaseSensor):
|
||||
"""Define sensor related to indices."""
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the sensor."""
|
||||
await self.pollen.async_update()
|
||||
if not self.pollen.data:
|
||||
return
|
||||
|
||||
data = {}
|
||||
if self._kind in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW,
|
||||
TYPE_ALLERGY_YESTERDAY):
|
||||
data = self.pollen.data[TYPE_ALLERGY_INDEX].get('Location')
|
||||
elif self._kind in (TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW,
|
||||
TYPE_ASTHMA_YESTERDAY):
|
||||
data = self.pollen.data[TYPE_ASTHMA_INDEX].get('Location')
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
key = self._kind.split('_')[-1].title()
|
||||
[period] = [p for p in data['periods'] if p['Type'] == key]
|
||||
[rating] = [
|
||||
i['label'] for i in RATING_MAPPING
|
||||
if i['minimum'] <= period['Index'] <= i['maximum']
|
||||
]
|
||||
|
||||
self._attrs.update({
|
||||
ATTR_CITY: data['City'].title(),
|
||||
ATTR_RATING: rating,
|
||||
ATTR_STATE: data['State'],
|
||||
ATTR_ZIP_CODE: data['ZIP']
|
||||
})
|
||||
|
||||
if self._kind in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW,
|
||||
TYPE_ALLERGY_YESTERDAY):
|
||||
for idx, attrs in enumerate(period['Triggers']):
|
||||
index = idx + 1
|
||||
self._attrs.update({
|
||||
|
@ -244,23 +321,18 @@ class PollencomSensor(Entity):
|
|||
'{0}_{1}'.format(ATTR_ALLERGEN_TYPE, index):
|
||||
attrs['PlantType'],
|
||||
})
|
||||
elif self._kind in (TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW,
|
||||
TYPE_ASTHMA_YESTERDAY):
|
||||
for idx, attrs in enumerate(period['Triggers']):
|
||||
index = idx + 1
|
||||
self._attrs.update({
|
||||
'{0}_{1}'.format(ATTR_ALLERGEN_NAME, index):
|
||||
attrs['Name'],
|
||||
'{0}_{1}'.format(ATTR_ALLERGEN_AMOUNT, index):
|
||||
attrs['PPM'],
|
||||
})
|
||||
|
||||
self._attrs.update({
|
||||
ATTR_CITY: data['City'].title(),
|
||||
ATTR_RATING: rating,
|
||||
ATTR_STATE: data['State'],
|
||||
ATTR_ZIP_CODE: data['ZIP']
|
||||
})
|
||||
self._state = period['Index']
|
||||
elif self._type == TYPE_DISEASE_FORECAST:
|
||||
self._attrs.update({
|
||||
ATTR_CITY: data['City'].title(),
|
||||
ATTR_RATING: rating,
|
||||
ATTR_STATE: data['State'],
|
||||
ATTR_TREND: trend,
|
||||
ATTR_ZIP_CODE: data['ZIP']
|
||||
})
|
||||
self._state = average
|
||||
self._state = period['Index']
|
||||
|
||||
|
||||
class PollenComData:
|
||||
|
@ -272,10 +344,21 @@ class PollenComData:
|
|||
self._sensor_types = sensor_types
|
||||
self.data = {}
|
||||
|
||||
async def _get_data(self, method, key):
|
||||
"""Return API data from a specific call."""
|
||||
from pypollencom.errors import PollenComError
|
||||
|
||||
try:
|
||||
data = await method()
|
||||
self.data[key] = data
|
||||
except PollenComError as err:
|
||||
_LOGGER.error('Unable to get "%s" data: %s', key, err)
|
||||
self.data[key] = {}
|
||||
|
||||
@Throttle(DEFAULT_SCAN_INTERVAL)
|
||||
async def async_update(self):
|
||||
"""Update Pollen.com data."""
|
||||
from pypollencom.errors import InvalidZipError, PollenComError
|
||||
from pypollencom.errors import InvalidZipError
|
||||
|
||||
# Pollen.com requires a bit more complicated error handling, given that
|
||||
# it sometimes has parts (but not the whole thing) go down:
|
||||
|
@ -285,45 +368,38 @@ class PollenComData:
|
|||
|
||||
try:
|
||||
if TYPE_ALLERGY_FORECAST in self._sensor_types:
|
||||
try:
|
||||
data = await self._client.allergens.extended()
|
||||
self.data[TYPE_ALLERGY_FORECAST] = data
|
||||
except PollenComError as err:
|
||||
_LOGGER.error('Unable to get allergy forecast: %s', err)
|
||||
self.data[TYPE_ALLERGY_FORECAST] = {}
|
||||
|
||||
try:
|
||||
data = await self._client.allergens.outlook()
|
||||
self.data[TYPE_ALLERGY_OUTLOOK] = data
|
||||
except PollenComError as err:
|
||||
_LOGGER.error('Unable to get allergy outlook: %s', err)
|
||||
self.data[TYPE_ALLERGY_OUTLOOK] = {}
|
||||
await self._get_data(
|
||||
self._client.allergens.extended, TYPE_ALLERGY_FORECAST)
|
||||
await self._get_data(
|
||||
self._client.allergens.outlook, TYPE_ALLERGY_OUTLOOK)
|
||||
|
||||
if TYPE_ALLERGY_HISTORIC in self._sensor_types:
|
||||
try:
|
||||
data = await self._client.allergens.historic()
|
||||
self.data[TYPE_ALLERGY_HISTORIC] = data
|
||||
except PollenComError as err:
|
||||
_LOGGER.error('Unable to get allergy history: %s', err)
|
||||
self.data[TYPE_ALLERGY_HISTORIC] = {}
|
||||
await self._get_data(
|
||||
self._client.allergens.historic, TYPE_ALLERGY_HISTORIC)
|
||||
|
||||
if any(s in self._sensor_types
|
||||
for s in [TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW,
|
||||
TYPE_ALLERGY_YESTERDAY]):
|
||||
try:
|
||||
data = await self._client.allergens.current()
|
||||
self.data[TYPE_ALLERGY_INDEX] = data
|
||||
except PollenComError as err:
|
||||
_LOGGER.error('Unable to get current allergies: %s', err)
|
||||
self.data[TYPE_ALLERGY_TODAY] = {}
|
||||
await self._get_data(
|
||||
self._client.allergens.current, TYPE_ALLERGY_INDEX)
|
||||
|
||||
if TYPE_ASTHMA_FORECAST in self._sensor_types:
|
||||
await self._get_data(
|
||||
self._client.asthma.extended, TYPE_ASTHMA_FORECAST)
|
||||
|
||||
if TYPE_ASTHMA_HISTORIC in self._sensor_types:
|
||||
await self._get_data(
|
||||
self._client.asthma.historic, TYPE_ASTHMA_HISTORIC)
|
||||
|
||||
if any(s in self._sensor_types
|
||||
for s in [TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW,
|
||||
TYPE_ASTHMA_YESTERDAY]):
|
||||
await self._get_data(
|
||||
self._client.asthma.current, TYPE_ASTHMA_INDEX)
|
||||
|
||||
if TYPE_DISEASE_FORECAST in self._sensor_types:
|
||||
try:
|
||||
data = await self._client.disease.extended()
|
||||
self.data[TYPE_DISEASE_FORECAST] = data
|
||||
except PollenComError as err:
|
||||
_LOGGER.error('Unable to get disease forecast: %s', err)
|
||||
self.data[TYPE_DISEASE_FORECAST] = {}
|
||||
await self._get_data(
|
||||
self._client.disease.extended, TYPE_DISEASE_FORECAST)
|
||||
|
||||
_LOGGER.debug('New data retrieved: %s', self.data)
|
||||
except InvalidZipError:
|
||||
|
|
|
@ -1060,7 +1060,7 @@ pyowm==2.9.0
|
|||
pypjlink2==1.2.0
|
||||
|
||||
# homeassistant.components.sensor.pollen
|
||||
pypollencom==2.1.0
|
||||
pypollencom==2.2.2
|
||||
|
||||
# homeassistant.components.qwikswitch
|
||||
pyqwikswitch==0.8
|
||||
|
|
Loading…
Add table
Reference in a new issue