From 038034a736d646da5bc298fe8eb8768ddfb727c9 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 2 Nov 2018 20:55:41 -0600 Subject: [PATCH 1/4] Add support for sensors from Flu Near You --- .coveragerc | 1 + CODEOWNERS | 1 + homeassistant/components/sensor/flunearyou.py | 201 ++++++++++++++++++ requirements_all.txt | 3 + 4 files changed, 206 insertions(+) create mode 100644 homeassistant/components/sensor/flunearyou.py diff --git a/.coveragerc b/.coveragerc index 0346c28695d85b..189785e906ecaa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -707,6 +707,7 @@ omit = homeassistant/components/sensor/fints.py homeassistant/components/sensor/fitbit.py homeassistant/components/sensor/fixer.py + homeassistant/components/sensor/flunearyou.py homeassistant/components/sensor/folder.py homeassistant/components/sensor/foobot.py homeassistant/components/sensor/fritzbox_callmonitor.py diff --git a/CODEOWNERS b/CODEOWNERS index 9fc93b958e8e9d..dabc3bbd4db9df 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -102,6 +102,7 @@ homeassistant/components/sensor/darksky.py @fabaff homeassistant/components/sensor/file.py @fabaff homeassistant/components/sensor/filter.py @dgomes homeassistant/components/sensor/fixer.py @fabaff +homeassistant/components/sensor/flunearyou.py.py @bachya homeassistant/components/sensor/gearbest.py @HerrHofrat homeassistant/components/sensor/gitter.py @fabaff homeassistant/components/sensor/glances.py @fabaff diff --git a/homeassistant/components/sensor/flunearyou.py b/homeassistant/components/sensor/flunearyou.py new file mode 100644 index 00000000000000..bd4962602d2081 --- /dev/null +++ b/homeassistant/components/sensor/flunearyou.py @@ -0,0 +1,201 @@ +""" +Support for user- and CDC-based flu info sensors from Flu Near You. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.flunearyou/ +""" +import logging +from datetime import timedelta + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + ATTR_ATTRIBUTION, ATTR_STATE, CONF_LATITUDE, + CONF_MONITORED_CONDITIONS, CONF_LONGITUDE) +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +REQUIREMENTS = ['pyflunearyou==0.0.2'] +_LOGGER = logging.getLogger(__name__) + +ATTR_CITY = 'city' +ATTR_REPORTED_DATE = 'reported_date' +ATTR_REPORTED_LATITUDE = 'reported_latitude' +ATTR_REPORTED_LONGITUDE = 'reported_longitude' +ATTR_ZIP_CODE = 'zip_code' + +DEFAULT_ATTRIBUTION = 'Data provided by Flu Near You' +DEFAULT_SCAN_INTERVAL = timedelta(minutes=30) + +CATEGORY_CDC_REPORT = 'cdc_report' +CATEGORY_USER_REPORT = 'user_report' + +TYPE_CDC_LEVEL = 'level' +TYPE_CDC_LEVEL2 = 'level2' +TYPE_USER_CHICK = 'chick' +TYPE_USER_DENGUE = 'dengue' +TYPE_USER_FLU = 'flu' +TYPE_USER_LEPTO = 'lepto' +TYPE_USER_NO_NONE = 'none' +TYPE_USER_SYMPTOMS = 'symptoms' + +SENSORS = { + CATEGORY_CDC_REPORT: [ + (TYPE_CDC_LEVEL, 'CDC Level', 'mdi:biohazard', None), + (TYPE_CDC_LEVEL2, 'CDC Level 2', 'mdi:biohazard', None), + ], + CATEGORY_USER_REPORT: [ + (TYPE_USER_CHICK, 'Avian Flu Symptoms', 'mdi:alert', 'reports'), + (TYPE_USER_DENGUE, 'Dengue Fever Symptoms', 'mdi:alert', 'reports'), + (TYPE_USER_FLU, 'Flu Symptoms', 'mdi:alert', 'reports'), + (TYPE_USER_LEPTO, 'Leptospirosis Symptoms', 'mdi:alert', 'reports'), + (TYPE_USER_NO_NONE, 'No Symptoms', 'mdi:alert', 'reports'), + (TYPE_USER_SYMPTOMS, 'Flu-like Symptoms', 'mdi:alert', 'reports'), + ] +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): + vol.All(cv.ensure_list, [vol.In(SENSORS)]) +}) + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Configure the platform and add the sensors.""" + from pyflunearyou import create_client + from pyflunearyou.errors import FluNearYouError + + websession = aiohttp_client.async_get_clientsession(hass) + + latitude = config.get(CONF_LATITUDE, hass.config.latitude) + longitude = config.get(CONF_LONGITUDE, hass.config.longitude) + identifier = '{0},{1}'.format(latitude, longitude) + + try: + client = await create_client(latitude, longitude, websession) + except FluNearYouError as err: + _LOGGER.error('There was an error while setting up: %s', err) + return False + + fny = FluNearYouData(client, config[CONF_MONITORED_CONDITIONS]) + await fny.async_update() + + sensors = [ + FluNearYouSensor(fny, kind, name, identifier, category, icon, unit) + for category in config[CONF_MONITORED_CONDITIONS] + for kind, name, icon, unit in SENSORS[category] + ] + + async_add_entities(sensors, True) + + +class FluNearYouSensor(Entity): + """Define a base Flu Near You sensor.""" + + def __init__(self, fny, kind, name, identifier, category, icon, unit): + """Initialize the sensor.""" + self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} + self._category = category + self._icon = icon + self._identifier = identifier + self._kind = kind + self._name = name + self._state = None + self._unit = unit + self.fny = fny + + @property + def available(self): + """Return True if entity is available.""" + return bool(self.fny.data[self._category]) + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + return self._attrs + + @property + def icon(self): + """Return the icon.""" + return self._icon + + @property + def name(self): + """Return the name.""" + return self._name + + @property + def state(self): + """Return the state.""" + return self._state + + @property + def unique_id(self): + """Return a unique, HASS-friendly identifier for this entity.""" + return '{0}_{1}'.format(self._identifier, self._kind) + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit + + async def async_update(self): + """Update the sensor.""" + await self.fny.async_update() + + if self._category == CATEGORY_CDC_REPORT: + data = self.fny.data[CATEGORY_CDC_REPORT] + self._attrs.update({ + ATTR_REPORTED_DATE: data['week_date'], + ATTR_STATE: data['name'], + }) + self._state = data[self._kind] + elif self._category == CATEGORY_USER_REPORT: + data = self.fny.data[CATEGORY_USER_REPORT] + self._attrs.update({ + ATTR_CITY: data['city'].split('(')[0], + ATTR_REPORTED_LATITUDE: data['latitude'], + ATTR_REPORTED_LONGITUDE: data['longitude'], + ATTR_ZIP_CODE: data['zip'], + }) + self._state = data[self._kind] + + +class FluNearYouData: + """Define a data object to retrieve info from Flu Near You.""" + + def __init__(self, client, sensor_types): + """Initialize.""" + self._client = client + self._sensor_types = sensor_types + self.data = {} + + async def _get_data(self, category, method): + """Get data for a specific category.""" + from pyflunearyou.errors import FluNearYouError + + try: + self.data[category] = await method() + except FluNearYouError as err: + _LOGGER.error( + 'There was an error with "%s" data: %s', category, err) + self.data[category] = {} + + @Throttle(DEFAULT_SCAN_INTERVAL) + async def async_update(self): + """Update Flu Near You data.""" + if CATEGORY_CDC_REPORT in self._sensor_types: + await self._get_data( + CATEGORY_CDC_REPORT, self._client.cdc_reports.status) + + if CATEGORY_USER_REPORT in self._sensor_types: + await self._get_data( + CATEGORY_USER_REPORT, self._client.user_reports.status) + + _LOGGER.debug('New data stored: %s', self.data) diff --git a/requirements_all.txt b/requirements_all.txt index d8a41643e60076..701ad2f2b44b56 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -921,6 +921,9 @@ pyflexit==0.3 # homeassistant.components.binary_sensor.flic pyflic-homeassistant==0.4.dev0 +# homeassistant.components.sensor.flunearyou +pyflunearyou==0.0.2 + # homeassistant.components.light.futurenow pyfnip==0.2 From 8fc10eca4eeb9877a44a2fa39547b0ec1d2d8328 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 4 Nov 2018 13:37:13 -0700 Subject: [PATCH 2/4] Added sensor for total reports with symptoms --- homeassistant/components/sensor/flunearyou.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/flunearyou.py b/homeassistant/components/sensor/flunearyou.py index bd4962602d2081..79903686b5cb83 100644 --- a/homeassistant/components/sensor/flunearyou.py +++ b/homeassistant/components/sensor/flunearyou.py @@ -12,8 +12,8 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, ATTR_STATE, CONF_LATITUDE, - CONF_MONITORED_CONDITIONS, CONF_LONGITUDE) + ATTR_ATTRIBUTION, ATTR_STATE, CONF_LATITUDE, CONF_MONITORED_CONDITIONS, + CONF_LONGITUDE) from homeassistant.helpers import aiohttp_client from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -41,6 +41,7 @@ TYPE_USER_LEPTO = 'lepto' TYPE_USER_NO_NONE = 'none' TYPE_USER_SYMPTOMS = 'symptoms' +TYPE_USER_TOTAL = 'total' SENSORS = { CATEGORY_CDC_REPORT: [ @@ -54,12 +55,15 @@ (TYPE_USER_LEPTO, 'Leptospirosis Symptoms', 'mdi:alert', 'reports'), (TYPE_USER_NO_NONE, 'No Symptoms', 'mdi:alert', 'reports'), (TYPE_USER_SYMPTOMS, 'Flu-like Symptoms', 'mdi:alert', 'reports'), + (TYPE_USER_TOTAL, 'Total Symptoms', 'mdi:alert', 'reports'), ] } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_LATITUDE): + cv.latitude, + vol.Optional(CONF_LONGITUDE): + cv.longitude, vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All(cv.ensure_list, [vol.In(SENSORS)]) }) @@ -164,7 +168,14 @@ async def async_update(self): ATTR_REPORTED_LONGITUDE: data['longitude'], ATTR_ZIP_CODE: data['zip'], }) - self._state = data[self._kind] + + if self._kind == TYPE_USER_TOTAL: + self._state = sum( + v for k, v in data.items() if k in ( + TYPE_USER_CHICK, TYPE_USER_DENGUE, TYPE_USER_FLU, + TYPE_USER_LEPTO, TYPE_USER_SYMPTOMS)) + else: + self._state = data[self._kind] class FluNearYouData: From 1b049abb13866b26f4cce0717a294c2a259d7577 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 8 Nov 2018 18:04:56 -0700 Subject: [PATCH 3/4] Member comments --- homeassistant/components/sensor/flunearyou.py | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/sensor/flunearyou.py b/homeassistant/components/sensor/flunearyou.py index 79903686b5cb83..dd280dec6a9034 100644 --- a/homeassistant/components/sensor/flunearyou.py +++ b/homeassistant/components/sensor/flunearyou.py @@ -28,7 +28,9 @@ ATTR_ZIP_CODE = 'zip_code' DEFAULT_ATTRIBUTION = 'Data provided by Flu Near You' -DEFAULT_SCAN_INTERVAL = timedelta(minutes=30) + +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) +SCAN_INTERVAL = timedelta(minutes=30) CATEGORY_CDC_REPORT = 'cdc_report' CATEGORY_USER_REPORT = 'user_report' @@ -85,7 +87,7 @@ async def async_setup_platform( client = await create_client(latitude, longitude, websession) except FluNearYouError as err: _LOGGER.error('There was an error while setting up: %s', err) - return False + return fny = FluNearYouData(client, config[CONF_MONITORED_CONDITIONS]) await fny.async_update() @@ -153,29 +155,30 @@ async def async_update(self): """Update the sensor.""" await self.fny.async_update() - if self._category == CATEGORY_CDC_REPORT: - data = self.fny.data[CATEGORY_CDC_REPORT] + cdc_data = self.fny.data[CATEGORY_CDC_REPORT] + user_data = self.fny.data[CATEGORY_USER_REPORT] + + if self._category == CATEGORY_CDC_REPORT and cdc_data: self._attrs.update({ - ATTR_REPORTED_DATE: data['week_date'], - ATTR_STATE: data['name'], + ATTR_REPORTED_DATE: cdc_data['week_date'], + ATTR_STATE: cdc_data['name'], }) - self._state = data[self._kind] - elif self._category == CATEGORY_USER_REPORT: - data = self.fny.data[CATEGORY_USER_REPORT] + self._state = cdc_data[self._kind] + elif self._category == CATEGORY_USER_REPORT and user_data: self._attrs.update({ - ATTR_CITY: data['city'].split('(')[0], - ATTR_REPORTED_LATITUDE: data['latitude'], - ATTR_REPORTED_LONGITUDE: data['longitude'], - ATTR_ZIP_CODE: data['zip'], + ATTR_CITY: user_data['city'].split('(')[0], + ATTR_REPORTED_LATITUDE: user_data['latitude'], + ATTR_REPORTED_LONGITUDE: user_data['longitude'], + ATTR_ZIP_CODE: user_data['zip'], }) if self._kind == TYPE_USER_TOTAL: self._state = sum( - v for k, v in data.items() if k in ( + v for k, v in user_data.items() if k in ( TYPE_USER_CHICK, TYPE_USER_DENGUE, TYPE_USER_FLU, TYPE_USER_LEPTO, TYPE_USER_SYMPTOMS)) else: - self._state = data[self._kind] + self._state = user_data[self._kind] class FluNearYouData: @@ -198,7 +201,7 @@ async def _get_data(self, category, method): 'There was an error with "%s" data: %s', category, err) self.data[category] = {} - @Throttle(DEFAULT_SCAN_INTERVAL) + @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self): """Update Flu Near You data.""" if CATEGORY_CDC_REPORT in self._sensor_types: From d893ff798ec6da09314e629ae4e8b4d7cc14eba8 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 9 Nov 2018 07:32:00 -0700 Subject: [PATCH 4/4] Member comments --- homeassistant/components/sensor/flunearyou.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sensor/flunearyou.py b/homeassistant/components/sensor/flunearyou.py index dd280dec6a9034..2c3598044bd855 100644 --- a/homeassistant/components/sensor/flunearyou.py +++ b/homeassistant/components/sensor/flunearyou.py @@ -62,10 +62,8 @@ } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_LATITUDE): - cv.latitude, - vol.Optional(CONF_LONGITUDE): - cv.longitude, + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All(cv.ensure_list, [vol.In(SENSORS)]) }) @@ -155,8 +153,8 @@ async def async_update(self): """Update the sensor.""" await self.fny.async_update() - cdc_data = self.fny.data[CATEGORY_CDC_REPORT] - user_data = self.fny.data[CATEGORY_USER_REPORT] + cdc_data = self.fny.data.get(CATEGORY_CDC_REPORT) + user_data = self.fny.data.get(CATEGORY_USER_REPORT) if self._category == CATEGORY_CDC_REPORT and cdc_data: self._attrs.update({