Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7d04943
initial version of gdacs integration
exxamalte Jan 7, 2020
cffc7b1
updated translations
exxamalte Jan 7, 2020
e33b285
generated files
exxamalte Jan 7, 2020
44db180
added abbreviation
exxamalte Jan 12, 2020
edd304d
bumped library version
exxamalte Jan 20, 2020
3b04780
small feed entry attribute fixes
exxamalte Jan 20, 2020
07a8b21
add unit tests
exxamalte Jan 20, 2020
9b5f726
need to use original mdi name
exxamalte Jan 20, 2020
87eeedd
bumped library version
exxamalte Jan 23, 2020
5d93019
improved entity name for earthquakes
exxamalte Jan 23, 2020
7fdfa4b
round vulnerability number
exxamalte Jan 23, 2020
14febc6
typo
exxamalte Jan 23, 2020
0528428
support for categories
exxamalte Jan 24, 2020
78f51fb
testing support for categories
exxamalte Jan 24, 2020
1b4101e
tie longitude and latitude together
exxamalte Jan 30, 2020
16bc1b2
validating categories
exxamalte Jan 31, 2020
94d15e8
simplifying setup
exxamalte Jan 31, 2020
d9bf8bf
passing domain as parameter
exxamalte Jan 31, 2020
476c922
simplified test setup
exxamalte Jan 31, 2020
a035621
moved test code
exxamalte Jan 31, 2020
3bb4482
simplified test code
exxamalte Jan 31, 2020
03e1a30
removed superfluous code
exxamalte Jan 31, 2020
bb73e00
changed approach to unique identifier
exxamalte Jan 31, 2020
0c92204
changed code structure
exxamalte Jan 31, 2020
890c51f
simplified unit system handling
exxamalte Jan 31, 2020
71582e2
made schema a constant
exxamalte Jan 31, 2020
5693f14
comment added
exxamalte Jan 31, 2020
6f690ef
simplifying code
exxamalte Jan 31, 2020
f2996d6
added message if location already configured
exxamalte Jan 31, 2020
e3990bc
removed unnecessary code
exxamalte Jan 31, 2020
77af112
simplified test code
exxamalte Jan 31, 2020
3226a02
avoid mocking __init__
exxamalte Feb 2, 2020
ed32546
pylint
exxamalte Feb 2, 2020
01c61c2
simplified code
exxamalte Feb 2, 2020
f422260
fetch categories from integration library
exxamalte Feb 2, 2020
978ef22
setting PARALLEL_UPDATES
exxamalte Feb 3, 2020
8954c35
setting PARALLEL_UPDATES to zero/unlimited
exxamalte Feb 3, 2020
799a5dd
added quality scale
exxamalte Feb 4, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ homeassistant/components/freebox/* @snoof85
homeassistant/components/fronius/* @nielstron
homeassistant/components/frontend/* @home-assistant/frontend
homeassistant/components/garmin_connect/* @cyberjunky
homeassistant/components/gdacs/* @exxamalte
homeassistant/components/gearbest/* @HerrHofrat
homeassistant/components/geniushub/* @zxdavb
homeassistant/components/geo_rss_events/* @exxamalte
Expand Down
16 changes: 16 additions & 0 deletions homeassistant/components/gdacs/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config": {
"abort": {
"already_configured": "Location is already configured."
},
"step": {
"user": {
"data": {
"radius": "Radius"
},
"title": "Fill in your filter details."
}
},
"title": "Global Disaster Alert and Coordination System (GDACS)"
}
}
212 changes: 212 additions & 0 deletions homeassistant/components/gdacs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
"""The Global Disaster Alert and Coordination System (GDACS) integration."""
import asyncio
from datetime import timedelta
import logging

from aio_georss_gdacs import GdacsFeedManager
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_RADIUS,
CONF_SCAN_INTERVAL,
CONF_UNIT_SYSTEM_IMPERIAL,
LENGTH_MILES,
)
from homeassistant.core import callback
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 homeassistant.util.unit_system import METRIC_SYSTEM

from .const import (
CONF_CATEGORIES,
DEFAULT_RADIUS,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
FEED,
PLATFORMS,
SIGNAL_DELETE_ENTITY,
SIGNAL_NEW_GEOLOCATION,
SIGNAL_STATUS,
SIGNAL_UPDATE_ENTITY,
VALID_CATEGORIES,
)

_LOGGER = logging.getLogger(__name__)

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Inclusive(CONF_LATITUDE, "coordinates"): cv.latitude,
vol.Inclusive(CONF_LONGITUDE, "coordinates"): cv.longitude,
vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): vol.Coerce(float),
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.time_period,
vol.Optional(CONF_CATEGORIES, default=[]): vol.All(
cv.ensure_list, [vol.In(VALID_CATEGORIES)]
),
}
)
},
extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass, config):
"""Set up the GDACS component."""
if DOMAIN not in config:
return True

conf = config[DOMAIN]
latitude = conf.get(CONF_LATITUDE, hass.config.latitude)
longitude = conf.get(CONF_LONGITUDE, hass.config.longitude)
scan_interval = conf[CONF_SCAN_INTERVAL]
categories = conf[CONF_CATEGORIES]

hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_LATITUDE: latitude,
CONF_LONGITUDE: longitude,
CONF_RADIUS: conf[CONF_RADIUS],
CONF_SCAN_INTERVAL: scan_interval,
CONF_CATEGORIES: categories,
},
)
)

return True


async def async_setup_entry(hass, config_entry):
"""Set up the GDACS component as config entry."""
hass.data.setdefault(DOMAIN, {})
feeds = hass.data[DOMAIN].setdefault(FEED, {})

radius = config_entry.data[CONF_RADIUS]
if hass.config.units.name == CONF_UNIT_SYSTEM_IMPERIAL:
radius = METRIC_SYSTEM.length(radius, LENGTH_MILES)
# Create feed entity manager for all platforms.
manager = GdacsFeedEntityManager(hass, config_entry, radius)
feeds[config_entry.entry_id] = manager
_LOGGER.debug("Feed entity manager added for %s", config_entry.entry_id)
await manager.async_init()
return True


async def async_unload_entry(hass, config_entry):
"""Unload an GDACS component config entry."""
manager = hass.data[DOMAIN][FEED].pop(config_entry.entry_id)
await manager.async_stop()
await asyncio.wait(
[
hass.config_entries.async_forward_entry_unload(config_entry, domain)
for domain in PLATFORMS
]
)
return True


class GdacsFeedEntityManager:
"""Feed Entity Manager for GDACS feed."""

def __init__(self, hass, config_entry, radius_in_km):
"""Initialize the Feed Entity Manager."""
self._hass = hass
self._config_entry = config_entry
coordinates = (
config_entry.data[CONF_LATITUDE],
config_entry.data[CONF_LONGITUDE],
)
categories = config_entry.data[CONF_CATEGORIES]
websession = aiohttp_client.async_get_clientsession(hass)
self._feed_manager = GdacsFeedManager(
websession,
self._generate_entity,
self._update_entity,
self._remove_entity,
coordinates,
filter_radius=radius_in_km,
filter_categories=categories,
status_async_callback=self._status_update,
)
self._config_entry_id = config_entry.entry_id
self._scan_interval = timedelta(seconds=config_entry.data[CONF_SCAN_INTERVAL])
self._track_time_remove_callback = None
self._status_info = None
self.listeners = []

async def async_init(self):
"""Schedule initial and regular updates based on configured time interval."""

for domain in PLATFORMS:
self._hass.async_create_task(
self._hass.config_entries.async_forward_entry_setup(
self._config_entry, domain
)
)

async def update(event_time):
"""Update."""
await self.async_update()

# Trigger updates at regular intervals.
self._track_time_remove_callback = async_track_time_interval(
self._hass, update, self._scan_interval
)

_LOGGER.debug("Feed entity manager initialized")

async def async_update(self):
"""Refresh data."""
await self._feed_manager.update()
_LOGGER.debug("Feed entity manager updated")

async def async_stop(self):
"""Stop this feed entity manager from refreshing."""
for unsub_dispatcher in self.listeners:
unsub_dispatcher()
self.listeners = []
if self._track_time_remove_callback:
self._track_time_remove_callback()
_LOGGER.debug("Feed entity manager stopped")

@callback
def async_event_new_entity(self):
"""Return manager specific event to signal new entity."""
return SIGNAL_NEW_GEOLOCATION.format(self._config_entry_id)

def get_entry(self, external_id):
"""Get feed entry by external id."""
return self._feed_manager.feed_entries.get(external_id)

def status_info(self):
"""Return latest status update info received."""
return self._status_info

async def _generate_entity(self, external_id):
"""Generate new entity."""
async_dispatcher_send(
self._hass, self.async_event_new_entity(), self, external_id
)

async def _update_entity(self, external_id):
"""Update entity."""
async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY.format(external_id))

async def _remove_entity(self, external_id):
"""Remove entity."""
async_dispatcher_send(self._hass, SIGNAL_DELETE_ENTITY.format(external_id))

async def _status_update(self, status_info):
"""Propagate status update."""
_LOGGER.debug("Status update received: %s", status_info)
self._status_info = status_info
async_dispatcher_send(self._hass, SIGNAL_STATUS.format(self._config_entry_id))
66 changes: 66 additions & 0 deletions homeassistant/components/gdacs/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Config flow to configure the GDACS integration."""
import logging

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import (
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_RADIUS,
CONF_SCAN_INTERVAL,
)
from homeassistant.helpers import config_validation as cv

from .const import ( # pylint: disable=unused-import
CONF_CATEGORIES,
DEFAULT_RADIUS,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
)

DATA_SCHEMA = vol.Schema(
{vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): cv.positive_int}
)

_LOGGER = logging.getLogger(__name__)


class GdacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a GDACS config flow."""

CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

async def _show_form(self, errors=None):
"""Show the form to the user."""
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors or {}
)

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."""
_LOGGER.debug("User input: %s", user_input)
if not user_input:
return await self._show_form()

latitude = user_input.get(CONF_LATITUDE, self.hass.config.latitude)
user_input[CONF_LATITUDE] = latitude
longitude = user_input.get(CONF_LONGITUDE, self.hass.config.longitude)
user_input[CONF_LONGITUDE] = longitude

identifier = f"{user_input[CONF_LATITUDE]}, {user_input[CONF_LONGITUDE]}"

await self.async_set_unique_id(identifier)
self._abort_if_unique_id_configured()

scan_interval = user_input.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
user_input[CONF_SCAN_INTERVAL] = scan_interval.seconds

categories = user_input.get(CONF_CATEGORIES, [])
user_input[CONF_CATEGORIES] = categories

return self.async_create_entry(title=identifier, data=user_input)
25 changes: 25 additions & 0 deletions homeassistant/components/gdacs/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Define constants for the GDACS integration."""
from datetime import timedelta

from aio_georss_gdacs.consts import EVENT_TYPE_MAP

DOMAIN = "gdacs"

PLATFORMS = ("sensor", "geo_location")

FEED = "feed"

CONF_CATEGORIES = "categories"

DEFAULT_ICON = "mdi:alert"
DEFAULT_RADIUS = 500.0
DEFAULT_SCAN_INTERVAL = timedelta(minutes=5)

SIGNAL_DELETE_ENTITY = "gdacs_delete_{}"
SIGNAL_UPDATE_ENTITY = "gdacs_update_{}"
SIGNAL_STATUS = "gdacs_status_{}"

SIGNAL_NEW_GEOLOCATION = "gdacs_new_geolocation_{}"

# Fetch valid categories from integration library.
VALID_CATEGORIES = list(EVENT_TYPE_MAP.values())
Loading