Add config entry for Flu Near You (#32858)
* Add config flow for Flu Near You * Cleanup * Cleanup * Add tests * Add test requirements * Code review * Reduce unnecessary async-ness * Handle API registration * Cleanup * Update homeassistant/components/flunearyou/.translations/en.json Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> * Code review * Ensure config schema allows additional keys Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>pull/33557/head
parent
55870aec31
commit
cb058ff6c0
|
@ -219,6 +219,7 @@ omit =
|
|||
homeassistant/components/flic/binary_sensor.py
|
||||
homeassistant/components/flock/notify.py
|
||||
homeassistant/components/flume/*
|
||||
homeassistant/components/flunearyou/__init__.py
|
||||
homeassistant/components/flunearyou/sensor.py
|
||||
homeassistant/components/flux_led/light.py
|
||||
homeassistant/components/folder/sensor.py
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
return self.async_show_form(
|
||||
step_id="user", errors={"base": "general_error"}
|
||||
)
|
||||
|
||||
return self.async_create_entry(title=unique_id, data=user_input)
|
|
@ -0,0 +1,38 @@
|
|||
"""Define flunearyou constants."""
|
||||
import logging
|
||||
|
||||
DOMAIN = "flunearyou"
|
||||
LOGGER = logging.getLogger("homeassistant.components.flunearyou")
|
||||
|
||||
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"),
|
||||
],
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
"""Support for user- and CDC-based flu info sensors from Flu Near You."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from pyflunearyou import Client
|
||||
from pyflunearyou.errors import FluNearYouError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_STATE,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
CONF_MONITORED_CONDITIONS,
|
||||
)
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_STATE
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .const import (
|
||||
CATEGORY_CDC_REPORT,
|
||||
CATEGORY_USER_REPORT,
|
||||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
SENSORS,
|
||||
TOPIC_UPDATE,
|
||||
TYPE_USER_CHICK,
|
||||
TYPE_USER_DENGUE,
|
||||
TYPE_USER_FLU,
|
||||
TYPE_USER_LEPTO,
|
||||
TYPE_USER_NO_SYMPTOMS,
|
||||
TYPE_USER_SYMPTOMS,
|
||||
TYPE_USER_TOTAL,
|
||||
)
|
||||
|
||||
ATTR_CITY = "city"
|
||||
ATTR_REPORTED_DATE = "reported_date"
|
||||
|
@ -31,94 +30,46 @@ 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_SYMPTOMS = "none"
|
||||
TYPE_USER_SYMPTOMS = "symptoms"
|
||||
TYPE_USER_TOTAL = "total"
|
||||
|
||||
EXTENDED_TYPE_MAPPING = {
|
||||
TYPE_USER_FLU: "ili",
|
||||
TYPE_USER_NO_SYMPTOMS: "no_symptoms",
|
||||
TYPE_USER_TOTAL: "total_surveys",
|
||||
}
|
||||
|
||||
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"),
|
||||
],
|
||||
}
|
||||
|
||||
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_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Flu Near You sensors based on a config entry."""
|
||||
fny = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Configure the platform and add the sensors."""
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||
|
||||
fny = FluNearYouData(
|
||||
Client(websession), latitude, longitude, config[CONF_MONITORED_CONDITIONS]
|
||||
async_add_entities(
|
||||
[
|
||||
FluNearYouSensor(fny, sensor_type, name, category, icon, unit)
|
||||
for category, sensors in SENSORS.items()
|
||||
for sensor_type, name, icon, unit in sensors
|
||||
],
|
||||
True,
|
||||
)
|
||||
await fny.async_update()
|
||||
|
||||
sensors = [
|
||||
FluNearYouSensor(fny, kind, name, 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, category, icon, unit):
|
||||
def __init__(self, fny, sensor_type, name, category, icon, unit):
|
||||
"""Initialize the sensor."""
|
||||
self._async_unsub_dispatcher_connect = None
|
||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
self._category = category
|
||||
self._fny = fny
|
||||
self._icon = icon
|
||||
self._kind = kind
|
||||
self._name = name
|
||||
self._sensor_type = sensor_type
|
||||
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])
|
||||
return bool(self._fny.data[self._category])
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
@ -143,19 +94,43 @@ class FluNearYouSensor(Entity):
|
|||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||
return f"{self.fny.latitude},{self.fny.longitude}_{self._kind}"
|
||||
return f"{self._fny.latitude},{self._fny.longitude}_{self._sensor_type}"
|
||||
|
||||
@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()
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
|
||||
cdc_data = self.fny.data.get(CATEGORY_CDC_REPORT)
|
||||
user_data = self.fny.data.get(CATEGORY_USER_REPORT)
|
||||
@callback
|
||||
def update():
|
||||
"""Update the state."""
|
||||
self.update_from_latest_data()
|
||||
self.async_write_ha_state()
|
||||
|
||||
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
|
||||
self.hass, TOPIC_UPDATE, update
|
||||
)
|
||||
|
||||
await self._fny.async_register_api_interest(self._sensor_type)
|
||||
|
||||
self.update_from_latest_data()
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect dispatcher listener when removed."""
|
||||
if self._async_unsub_dispatcher_connect:
|
||||
self._async_unsub_dispatcher_connect()
|
||||
self._async_unsub_dispatcher_connect = None
|
||||
|
||||
self._fny.async_deregister_api_interest(self._sensor_type)
|
||||
|
||||
@callback
|
||||
def update_from_latest_data(self):
|
||||
"""Update the sensor."""
|
||||
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(
|
||||
|
@ -164,7 +139,7 @@ class FluNearYouSensor(Entity):
|
|||
ATTR_STATE: cdc_data["name"],
|
||||
}
|
||||
)
|
||||
self._state = cdc_data[self._kind]
|
||||
self._state = cdc_data[self._sensor_type]
|
||||
elif self._category == CATEGORY_USER_REPORT and user_data:
|
||||
self._attrs.update(
|
||||
{
|
||||
|
@ -176,10 +151,10 @@ class FluNearYouSensor(Entity):
|
|||
}
|
||||
)
|
||||
|
||||
if self._kind in user_data["state"]["data"]:
|
||||
states_key = self._kind
|
||||
elif self._kind in EXTENDED_TYPE_MAPPING:
|
||||
states_key = EXTENDED_TYPE_MAPPING[self._kind]
|
||||
if self._sensor_type in user_data["state"]["data"]:
|
||||
states_key = self._sensor_type
|
||||
elif self._sensor_type in EXTENDED_TYPE_MAPPING:
|
||||
states_key = EXTENDED_TYPE_MAPPING[self._sensor_type]
|
||||
|
||||
self._attrs[ATTR_STATE_REPORTS_THIS_WEEK] = user_data["state"]["data"][
|
||||
states_key
|
||||
|
@ -188,7 +163,7 @@ class FluNearYouSensor(Entity):
|
|||
"last_week_data"
|
||||
][states_key]
|
||||
|
||||
if self._kind == TYPE_USER_TOTAL:
|
||||
if self._sensor_type == TYPE_USER_TOTAL:
|
||||
self._state = sum(
|
||||
v
|
||||
for k, v in user_data["local"].items()
|
||||
|
@ -202,32 +177,4 @@ class FluNearYouSensor(Entity):
|
|||
)
|
||||
)
|
||||
else:
|
||||
self._state = user_data["local"][self._kind]
|
||||
|
||||
|
||||
class FluNearYouData:
|
||||
"""Define a data object to retrieve info from Flu Near You."""
|
||||
|
||||
def __init__(self, client, latitude, longitude, sensor_types):
|
||||
"""Initialize."""
|
||||
self._client = client
|
||||
self._sensor_types = sensor_types
|
||||
self.data = {}
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
async def async_update(self):
|
||||
"""Update Flu Near You data."""
|
||||
for key, method in [
|
||||
(CATEGORY_CDC_REPORT, self._client.cdc_reports.status_by_coordinates),
|
||||
(CATEGORY_USER_REPORT, self._client.user_reports.status_by_coordinates),
|
||||
]:
|
||||
if key in self._sensor_types:
|
||||
try:
|
||||
self.data[key] = await method(self.latitude, self.longitude)
|
||||
except FluNearYouError as err:
|
||||
_LOGGER.error('There was an error with "%s" data: %s', key, err)
|
||||
self.data[key] = {}
|
||||
|
||||
_LOGGER.debug("New data stored: %s", self.data)
|
||||
self._state = user_data["local"][self._sensor_type]
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"config": {
|
||||
"title": "Flu Near You",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Configure Flu Near You",
|
||||
"description": "Monitor user-based and CDC repots for a pair of coordinates.",
|
||||
"data": {
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"general_error": "There was an unknown error."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "These coordinates are already registered."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ FLOWS = [
|
|||
"elkm1",
|
||||
"emulated_roku",
|
||||
"esphome",
|
||||
"flunearyou",
|
||||
"freebox",
|
||||
"garmin_connect",
|
||||
"gdacs",
|
||||
|
|
|
@ -1281,7 +1281,7 @@ pyflic-homeassistant==0.4.dev0
|
|||
pyflume==0.3.0
|
||||
|
||||
# homeassistant.components.flunearyou
|
||||
pyflunearyou==1.0.3
|
||||
pyflunearyou==1.0.7
|
||||
|
||||
# homeassistant.components.futurenow
|
||||
pyfnip==0.2
|
||||
|
|
|
@ -493,6 +493,9 @@ pyeverlights==0.1.0
|
|||
# homeassistant.components.fido
|
||||
pyfido==2.1.1
|
||||
|
||||
# homeassistant.components.flunearyou
|
||||
pyflunearyou==1.0.7
|
||||
|
||||
# homeassistant.components.fritzbox
|
||||
pyfritzhome==0.4.0
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"""Define tests for the flunearyou component."""
|
|
@ -0,0 +1,87 @@
|
|||
"""Define tests for the flunearyou config flow."""
|
||||
from asynctest import patch
|
||||
from pyflunearyou.errors import FluNearYouError
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.flunearyou import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_duplicate_error(hass):
|
||||
"""Test that an error is shown when duplicates are added."""
|
||||
conf = {CONF_LATITUDE: "51.528308", CONF_LONGITUDE: "-0.3817765"}
|
||||
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN, unique_id="51.528308, -0.3817765", data=conf
|
||||
).add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=conf
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_general_error(hass):
|
||||
"""Test that an error is shown on a library error."""
|
||||
conf = {CONF_LATITUDE: "51.528308", CONF_LONGITUDE: "-0.3817765"}
|
||||
|
||||
with patch(
|
||||
"pyflunearyou.cdc.CdcReport.status_by_coordinates", side_effect=FluNearYouError,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=conf
|
||||
)
|
||||
assert result["errors"] == {"base": "general_error"}
|
||||
|
||||
|
||||
async def test_show_form(hass):
|
||||
"""Test that the form is served with no input."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
|
||||
async def test_step_import(hass):
|
||||
"""Test that the import step works."""
|
||||
conf = {CONF_LATITUDE: "51.528308", CONF_LONGITUDE: "-0.3817765"}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.flunearyou.async_setup_entry", return_value=True
|
||||
), patch("pyflunearyou.cdc.CdcReport.status_by_coordinates"):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == "51.528308, -0.3817765"
|
||||
assert result["data"] == {
|
||||
CONF_LATITUDE: "51.528308",
|
||||
CONF_LONGITUDE: "-0.3817765",
|
||||
}
|
||||
|
||||
|
||||
async def test_step_user(hass):
|
||||
"""Test that the user step works."""
|
||||
conf = {CONF_LATITUDE: "51.528308", CONF_LONGITUDE: "-0.3817765"}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.flunearyou.async_setup_entry", return_value=True
|
||||
), patch("pyflunearyou.cdc.CdcReport.status_by_coordinates"):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=conf
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == "51.528308, -0.3817765"
|
||||
assert result["data"] == {
|
||||
CONF_LATITUDE: "51.528308",
|
||||
CONF_LONGITUDE: "-0.3817765",
|
||||
}
|
Loading…
Reference in New Issue