-
-
Notifications
You must be signed in to change notification settings - Fork 37.4k
Add config entry for Flu Near You #32858
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2b5047d
9aecb26
d7ebe10
aea185d
72c050c
57af7f2
37a549b
aa1e503
337b157
68f986a
60f6f50
c585b2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "config": { | ||
| "abort": { | ||
| "already_configured": "These coordinates are already registered." | ||
| }, | ||
| "error": { | ||
| "general_error": "There was an unknown error." | ||
| }, | ||
| "step": { | ||
| "user": { | ||
| "data": { | ||
| "latitude": "Latitude", | ||
| "longitude": "Longitude" | ||
| }, | ||
| "description": "Monitor user-based and CDC flu reports.", | ||
| "title": "Configure Flu Near You" | ||
| } | ||
| }, | ||
| "title": "Flu Near You" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,216 @@ | ||
| """The flunearyou component.""" | ||
| import asyncio | ||
| from datetime import timedelta | ||
|
|
||
| from pyflunearyou import Client | ||
| from pyflunearyou.errors import FluNearYouError | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.config_entries import SOURCE_IMPORT | ||
| from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE | ||
| from homeassistant.core import callback | ||
| from homeassistant.exceptions import ConfigEntryNotReady | ||
| from homeassistant.helpers import aiohttp_client, config_validation as cv | ||
| from homeassistant.helpers.dispatcher import async_dispatcher_send | ||
| from homeassistant.helpers.event import async_track_time_interval | ||
|
|
||
| from .const import ( | ||
| CATEGORY_CDC_REPORT, | ||
| CATEGORY_USER_REPORT, | ||
| DATA_CLIENT, | ||
| DOMAIN, | ||
| LOGGER, | ||
| SENSORS, | ||
| TOPIC_UPDATE, | ||
| ) | ||
|
|
||
| DATA_LISTENER = "listener" | ||
|
|
||
| DEFAULT_SCAN_INTERVAL = timedelta(minutes=30) | ||
|
|
||
| CONFIG_SCHEMA = vol.Schema( | ||
| { | ||
| vol.Optional(DOMAIN): vol.Schema( | ||
| { | ||
| vol.Optional(CONF_LATITUDE): cv.latitude, | ||
| vol.Optional(CONF_LONGITUDE): cv.longitude, | ||
| } | ||
| ) | ||
| }, | ||
| extra=vol.ALLOW_EXTRA, | ||
| ) | ||
|
|
||
|
|
||
| @callback | ||
| def async_get_api_category(sensor_type): | ||
| """Get the category that a particular sensor type belongs to.""" | ||
| try: | ||
| return next( | ||
| ( | ||
| category | ||
| for category, sensors in SENSORS.items() | ||
| for sensor in sensors | ||
| if sensor[0] == sensor_type | ||
| ) | ||
| ) | ||
| except StopIteration: | ||
| raise ValueError(f"Can't find category sensor type: {sensor_type}") | ||
|
|
||
|
|
||
| async def async_setup(hass, config): | ||
| """Set up the Flu Near You component.""" | ||
| hass.data[DOMAIN] = {DATA_CLIENT: {}, DATA_LISTENER: {}} | ||
|
|
||
| if DOMAIN not in config: | ||
| return True | ||
|
|
||
| hass.async_create_task( | ||
| hass.config_entries.flow.async_init( | ||
| DOMAIN, | ||
| context={"source": SOURCE_IMPORT}, | ||
| data={ | ||
| CONF_LATITUDE: config[DOMAIN].get(CONF_LATITUDE, hass.config.latitude), | ||
| CONF_LONGITUDE: config[DOMAIN].get( | ||
| CONF_LATITUDE, hass.config.longitude | ||
| ), | ||
| }, | ||
| ) | ||
| ) | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| async def async_setup_entry(hass, config_entry): | ||
| """Set up Flu Near You as config entry.""" | ||
| websession = aiohttp_client.async_get_clientsession(hass) | ||
|
|
||
| fny = FluNearYouData( | ||
| hass, | ||
| Client(websession), | ||
| config_entry.data.get(CONF_LATITUDE, hass.config.latitude), | ||
| config_entry.data.get(CONF_LONGITUDE, hass.config.longitude), | ||
| ) | ||
|
|
||
| try: | ||
| await fny.async_update() | ||
| except FluNearYouError as err: | ||
| LOGGER.error("Error while setting up integration: %s", err) | ||
| raise ConfigEntryNotReady | ||
|
|
||
| hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = fny | ||
|
|
||
| hass.async_create_task( | ||
| hass.config_entries.async_forward_entry_setup(config_entry, "sensor") | ||
| ) | ||
|
|
||
| async def refresh(event_time): | ||
| """Refresh data from Flu Near You.""" | ||
| await fny.async_update() | ||
|
|
||
| hass.data[DOMAIN][DATA_LISTENER][config_entry.entry_id] = async_track_time_interval( | ||
| hass, refresh, DEFAULT_SCAN_INTERVAL | ||
| ) | ||
|
Comment on lines
+106
to
+112
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you not using the Data Update Coordinator? https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because I had no idea this was a thing? 😆
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A few things:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Data Update Coordinators will only refresh if they have at least 1 listener. So you can create two coordinators and let entities subscribe. |
||
|
|
||
| return True | ||
|
|
||
|
|
||
| async def async_unload_entry(hass, config_entry): | ||
| """Unload an Flu Near You config entry.""" | ||
| hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) | ||
|
|
||
| remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id) | ||
| remove_listener() | ||
|
|
||
| await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| class FluNearYouData: | ||
| """Define a data object to retrieve info from Flu Near You.""" | ||
|
|
||
| def __init__(self, hass, client, latitude, longitude): | ||
| """Initialize.""" | ||
| self._async_cancel_time_interval_listener = None | ||
| self._client = client | ||
| self._hass = hass | ||
| self.data = {} | ||
| self.latitude = latitude | ||
| self.longitude = longitude | ||
|
|
||
| self._api_coros = { | ||
| CATEGORY_CDC_REPORT: self._client.cdc_reports.status_by_coordinates( | ||
| latitude, longitude | ||
| ), | ||
| CATEGORY_USER_REPORT: self._client.user_reports.status_by_coordinates( | ||
| latitude, longitude | ||
| ), | ||
| } | ||
|
|
||
| self._api_category_count = { | ||
| CATEGORY_CDC_REPORT: 0, | ||
| CATEGORY_USER_REPORT: 0, | ||
| } | ||
|
|
||
| self._api_category_locks = { | ||
| CATEGORY_CDC_REPORT: asyncio.Lock(), | ||
| CATEGORY_USER_REPORT: asyncio.Lock(), | ||
| } | ||
|
|
||
| async def _async_get_data_from_api(self, api_category): | ||
| """Update and save data for a particular API category.""" | ||
| if self._api_category_count[api_category] == 0: | ||
| return | ||
|
|
||
| try: | ||
| self.data[api_category] = await self._api_coros[api_category] | ||
| except FluNearYouError as err: | ||
| LOGGER.error("Unable to get %s data: %s", api_category, err) | ||
| self.data[api_category] = None | ||
|
|
||
| async def _async_update_listener_action(self, now): | ||
| """Define an async_track_time_interval action to update data.""" | ||
| await self.async_update() | ||
|
|
||
| @callback | ||
| def async_deregister_api_interest(self, sensor_type): | ||
| """Decrement the number of entities with data needs from an API category.""" | ||
| # If this deregistration should leave us with no registration at all, remove the | ||
| # time interval: | ||
| if sum(self._api_category_count.values()) == 0: | ||
| if self._async_cancel_time_interval_listener: | ||
| self._async_cancel_time_interval_listener() | ||
| self._async_cancel_time_interval_listener = None | ||
| return | ||
|
|
||
| api_category = async_get_api_category(sensor_type) | ||
| self._api_category_count[api_category] -= 1 | ||
|
|
||
| async def async_register_api_interest(self, sensor_type): | ||
| """Increment the number of entities with data needs from an API category.""" | ||
| # If this is the first registration we have, start a time interval: | ||
| if not self._async_cancel_time_interval_listener: | ||
| self._async_cancel_time_interval_listener = async_track_time_interval( | ||
| self._hass, self._async_update_listener_action, DEFAULT_SCAN_INTERVAL, | ||
| ) | ||
|
|
||
| api_category = async_get_api_category(sensor_type) | ||
| self._api_category_count[api_category] += 1 | ||
|
|
||
| # If a sensor registers interest in a particular API call and the data doesn't | ||
| # exist for it yet, make the API call and grab the data: | ||
| async with self._api_category_locks[api_category]: | ||
| if api_category not in self.data: | ||
| await self._async_get_data_from_api(api_category) | ||
|
|
||
| async def async_update(self): | ||
| """Update Flu Near You data.""" | ||
| tasks = [ | ||
| self._async_get_data_from_api(api_category) | ||
| for api_category in self._api_coros | ||
| ] | ||
|
|
||
| await asyncio.gather(*tasks) | ||
|
|
||
| LOGGER.debug("Received new data") | ||
| async_dispatcher_send(self._hass, TOPIC_UPDATE) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| """Define a config flow manager for flunearyou.""" | ||
| from pyflunearyou import Client | ||
| from pyflunearyou.errors import FluNearYouError | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant import config_entries | ||
| from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE | ||
| from homeassistant.helpers import aiohttp_client, config_validation as cv | ||
|
|
||
| from .const import DOMAIN, LOGGER # pylint: disable=unused-import | ||
|
|
||
|
|
||
| class FluNearYouFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): | ||
| """Handle an FluNearYou config flow.""" | ||
|
|
||
| VERSION = 1 | ||
| CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL | ||
|
|
||
| @property | ||
| def data_schema(self): | ||
| """Return the data schema for integration.""" | ||
| return vol.Schema( | ||
| { | ||
| vol.Required( | ||
| CONF_LATITUDE, default=self.hass.config.latitude | ||
| ): cv.latitude, | ||
| vol.Required( | ||
| CONF_LONGITUDE, default=self.hass.config.longitude | ||
| ): cv.longitude, | ||
| } | ||
| ) | ||
|
|
||
| async def async_step_import(self, import_config): | ||
| """Import a config entry from configuration.yaml.""" | ||
| return await self.async_step_user(import_config) | ||
|
|
||
| async def async_step_user(self, user_input=None): | ||
| """Handle the start of the config flow.""" | ||
| if not user_input: | ||
| return self.async_show_form(step_id="user", data_schema=self.data_schema) | ||
|
|
||
| unique_id = f"{user_input[CONF_LATITUDE]}, {user_input[CONF_LONGITUDE]}" | ||
|
|
||
| await self.async_set_unique_id(unique_id) | ||
| self._abort_if_unique_id_configured() | ||
|
|
||
| websession = aiohttp_client.async_get_clientsession(self.hass) | ||
| client = Client(websession) | ||
|
|
||
| try: | ||
| await client.cdc_reports.status_by_coordinates( | ||
| user_input[CONF_LATITUDE], user_input[CONF_LONGITUDE] | ||
| ) | ||
| except FluNearYouError as err: | ||
| LOGGER.error("Error while setting up integration: %s", err) | ||
|
bachya marked this conversation as resolved.
|
||
| return self.async_show_form( | ||
| step_id="user", errors={"base": "general_error"} | ||
| ) | ||
|
|
||
| return self.async_create_entry(title=unique_id, data=user_input) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| """Define flunearyou constants.""" | ||
| import logging | ||
|
|
||
| DOMAIN = "flunearyou" | ||
| LOGGER = logging.getLogger("homeassistant.components.flunearyou") | ||
|
bachya marked this conversation as resolved.
|
||
|
|
||
| DATA_CLIENT = "client" | ||
|
|
||
| CATEGORY_CDC_REPORT = "cdc_report" | ||
| CATEGORY_USER_REPORT = "user_report" | ||
|
|
||
| TOPIC_UPDATE = "flunearyou_update" | ||
|
|
||
| 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_SYMPTOMS = "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_SYMPTOMS, "No Symptoms", "mdi:alert", "reports"), | ||
| (TYPE_USER_SYMPTOMS, "Flu-like Symptoms", "mdi:alert", "reports"), | ||
| (TYPE_USER_TOTAL, "Total Symptoms", "mdi:alert", "reports"), | ||
| ], | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| { | ||
| "domain": "flunearyou", | ||
| "name": "Flu Near You", | ||
| "config_flow": true, | ||
| "documentation": "https://www.home-assistant.io/integrations/flunearyou", | ||
| "requirements": ["pyflunearyou==1.0.3"], | ||
| "requirements": ["pyflunearyou==1.0.7"], | ||
| "dependencies": [], | ||
| "codeowners": ["@bachya"] | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.