From f97130911362b07e17feaa920d9e5af719668f13 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 9 Nov 2018 08:23:07 -0700 Subject: [PATCH] Add support for sensors from Flu Near You (#18136) * Add support for sensors from Flu Near You * Added sensor for total reports with symptoms * Member comments * Member comments --- .coveragerc | 1 + CODEOWNERS | 1 + homeassistant/components/sensor/flunearyou.py | 213 ++++++++++++++++++ requirements_all.txt | 3 + 4 files changed, 218 insertions(+) create mode 100644 homeassistant/components/sensor/flunearyou.py diff --git a/.coveragerc b/.coveragerc index 0346c28695d..189785e906e 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 9fc93b958e8..dabc3bbd4db 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 00000000000..2c3598044bd --- /dev/null +++ b/homeassistant/components/sensor/flunearyou.py @@ -0,0 +1,213 @@ +""" +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' + +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) +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' +TYPE_USER_TOTAL = 'total' + +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'), + (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.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 + + 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() + + 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({ + ATTR_REPORTED_DATE: cdc_data['week_date'], + ATTR_STATE: cdc_data['name'], + }) + self._state = cdc_data[self._kind] + elif self._category == CATEGORY_USER_REPORT and user_data: + self._attrs.update({ + 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 user_data.items() if k in ( + TYPE_USER_CHICK, TYPE_USER_DENGUE, TYPE_USER_FLU, + TYPE_USER_LEPTO, TYPE_USER_SYMPTOMS)) + else: + self._state = user_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(MIN_TIME_BETWEEN_UPDATES) + 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 d8a41643e60..701ad2f2b44 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