2019-07-10 22:40:11 +00:00
|
|
|
"""Support for WWLLN geo location events."""
|
|
|
|
from datetime import timedelta
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from aiowwlln.errors import WWLLNError
|
|
|
|
|
|
|
|
from homeassistant.components.geo_location import GeolocationEvent
|
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_ATTRIBUTION,
|
|
|
|
CONF_LATITUDE,
|
|
|
|
CONF_LONGITUDE,
|
|
|
|
CONF_RADIUS,
|
|
|
|
CONF_UNIT_SYSTEM,
|
|
|
|
CONF_UNIT_SYSTEM_IMPERIAL,
|
|
|
|
LENGTH_KILOMETERS,
|
|
|
|
LENGTH_MILES,
|
|
|
|
)
|
2019-07-10 22:40:11 +00:00
|
|
|
from homeassistant.core import callback
|
|
|
|
from homeassistant.helpers.dispatcher import (
|
2019-07-31 19:25:30 +00:00
|
|
|
async_dispatcher_connect,
|
|
|
|
async_dispatcher_send,
|
|
|
|
)
|
2019-07-10 22:40:11 +00:00
|
|
|
from homeassistant.helpers.event import async_track_time_interval
|
|
|
|
from homeassistant.util.dt import utc_from_timestamp
|
|
|
|
|
2019-07-28 20:09:14 +00:00
|
|
|
from .const import CONF_WINDOW, DATA_CLIENT, DOMAIN
|
2019-07-10 22:40:11 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_EXTERNAL_ID = "external_id"
|
|
|
|
ATTR_PUBLICATION_DATE = "publication_date"
|
2019-07-10 22:40:11 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DEFAULT_ATTRIBUTION = "Data provided by the WWLLN"
|
|
|
|
DEFAULT_EVENT_NAME = "Lightning Strike: {0}"
|
|
|
|
DEFAULT_ICON = "mdi:flash"
|
2019-07-28 20:09:14 +00:00
|
|
|
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=10)
|
2019-07-10 22:40:11 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SIGNAL_DELETE_ENTITY = "delete_entity_{0}"
|
2019-07-10 22:40:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_entry(hass, entry, async_add_entities):
|
|
|
|
"""Set up WWLLN based on a config entry."""
|
|
|
|
client = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
|
|
|
|
manager = WWLLNEventManager(
|
|
|
|
hass,
|
|
|
|
async_add_entities,
|
|
|
|
client,
|
|
|
|
entry.data[CONF_LATITUDE],
|
|
|
|
entry.data[CONF_LONGITUDE],
|
|
|
|
entry.data[CONF_RADIUS],
|
|
|
|
entry.data[CONF_WINDOW],
|
2019-07-31 19:25:30 +00:00
|
|
|
entry.data[CONF_UNIT_SYSTEM],
|
|
|
|
)
|
2019-07-10 22:40:11 +00:00
|
|
|
await manager.async_init()
|
|
|
|
|
|
|
|
|
|
|
|
class WWLLNEventManager:
|
|
|
|
"""Define a class to handle WWLLN events."""
|
|
|
|
|
|
|
|
def __init__(
|
2019-07-31 19:25:30 +00:00
|
|
|
self,
|
|
|
|
hass,
|
|
|
|
async_add_entities,
|
|
|
|
client,
|
|
|
|
latitude,
|
|
|
|
longitude,
|
|
|
|
radius,
|
|
|
|
window_seconds,
|
|
|
|
unit_system,
|
|
|
|
):
|
2019-07-10 22:40:11 +00:00
|
|
|
"""Initialize."""
|
|
|
|
self._async_add_entities = async_add_entities
|
|
|
|
self._client = client
|
|
|
|
self._hass = hass
|
|
|
|
self._latitude = latitude
|
|
|
|
self._longitude = longitude
|
|
|
|
self._managed_strike_ids = set()
|
|
|
|
self._radius = radius
|
|
|
|
self._strikes = {}
|
|
|
|
self._window = timedelta(seconds=window_seconds)
|
|
|
|
|
|
|
|
self._unit_system = unit_system
|
|
|
|
if unit_system == CONF_UNIT_SYSTEM_IMPERIAL:
|
|
|
|
self._unit = LENGTH_MILES
|
|
|
|
else:
|
|
|
|
self._unit = LENGTH_KILOMETERS
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def _create_events(self, ids_to_create):
|
|
|
|
"""Create new geo location events."""
|
|
|
|
events = []
|
|
|
|
for strike_id in ids_to_create:
|
|
|
|
strike = self._strikes[strike_id]
|
|
|
|
event = WWLLNEvent(
|
2019-07-31 19:25:30 +00:00
|
|
|
strike["distance"],
|
|
|
|
strike["lat"],
|
|
|
|
strike["long"],
|
2019-07-10 22:40:11 +00:00
|
|
|
self._unit,
|
|
|
|
strike_id,
|
2019-07-31 19:25:30 +00:00
|
|
|
strike["unixTime"],
|
|
|
|
)
|
2019-07-10 22:40:11 +00:00
|
|
|
events.append(event)
|
|
|
|
|
|
|
|
self._async_add_entities(events)
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def _remove_events(self, ids_to_remove):
|
|
|
|
"""Remove old geo location events."""
|
|
|
|
for strike_id in ids_to_remove:
|
2019-07-31 19:25:30 +00:00
|
|
|
async_dispatcher_send(self._hass, SIGNAL_DELETE_ENTITY.format(strike_id))
|
2019-07-10 22:40:11 +00:00
|
|
|
|
|
|
|
async def async_init(self):
|
|
|
|
"""Schedule regular updates based on configured time interval."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2019-07-10 22:40:11 +00:00
|
|
|
async def update(event_time):
|
|
|
|
"""Update."""
|
|
|
|
await self.async_update()
|
|
|
|
|
|
|
|
await self.async_update()
|
2019-07-28 20:09:14 +00:00
|
|
|
async_track_time_interval(self._hass, update, DEFAULT_UPDATE_INTERVAL)
|
2019-07-10 22:40:11 +00:00
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Refresh data."""
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.debug("Refreshing WWLLN data")
|
2019-07-10 22:40:11 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
self._strikes = await self._client.within_radius(
|
|
|
|
self._latitude,
|
|
|
|
self._longitude,
|
|
|
|
self._radius,
|
|
|
|
unit=self._unit_system,
|
2019-07-31 19:25:30 +00:00
|
|
|
window=self._window,
|
|
|
|
)
|
2019-07-10 22:40:11 +00:00
|
|
|
except WWLLNError as err:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error("Error while updating WWLLN data: %s", err)
|
2019-07-10 22:40:11 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
new_strike_ids = set(self._strikes)
|
|
|
|
ids_to_remove = self._managed_strike_ids.difference(new_strike_ids)
|
|
|
|
self._remove_events(ids_to_remove)
|
|
|
|
|
|
|
|
ids_to_create = new_strike_ids.difference(self._managed_strike_ids)
|
|
|
|
self._create_events(ids_to_create)
|
|
|
|
|
|
|
|
|
|
|
|
class WWLLNEvent(GeolocationEvent):
|
|
|
|
"""Define a lightning strike event."""
|
|
|
|
|
|
|
|
def __init__(
|
2019-07-31 19:25:30 +00:00
|
|
|
self, distance, latitude, longitude, unit, strike_id, publication_date
|
|
|
|
):
|
2019-07-10 22:40:11 +00:00
|
|
|
"""Initialize entity with data provided."""
|
|
|
|
self._distance = distance
|
|
|
|
self._latitude = latitude
|
|
|
|
self._longitude = longitude
|
|
|
|
self._publication_date = publication_date
|
|
|
|
self._remove_signal_delete = None
|
|
|
|
self._strike_id = strike_id
|
|
|
|
self._unit_of_measurement = unit
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_state_attributes(self):
|
|
|
|
"""Return the device state attributes."""
|
|
|
|
attributes = {}
|
|
|
|
for key, value in (
|
2019-07-31 19:25:30 +00:00
|
|
|
(ATTR_EXTERNAL_ID, self._strike_id),
|
|
|
|
(ATTR_ATTRIBUTION, DEFAULT_ATTRIBUTION),
|
|
|
|
(ATTR_PUBLICATION_DATE, utc_from_timestamp(self._publication_date)),
|
2019-07-10 22:40:11 +00:00
|
|
|
):
|
|
|
|
attributes[key] = value
|
|
|
|
return attributes
|
|
|
|
|
|
|
|
@property
|
|
|
|
def distance(self):
|
|
|
|
"""Return distance value of this external event."""
|
|
|
|
return self._distance
|
|
|
|
|
|
|
|
@property
|
|
|
|
def icon(self):
|
|
|
|
"""Return the icon to use in the front-end."""
|
|
|
|
return DEFAULT_ICON
|
|
|
|
|
|
|
|
@property
|
|
|
|
def latitude(self):
|
|
|
|
"""Return latitude value of this external event."""
|
|
|
|
return self._latitude
|
|
|
|
|
|
|
|
@property
|
|
|
|
def longitude(self):
|
|
|
|
"""Return longitude value of this external event."""
|
|
|
|
return self._longitude
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the event."""
|
|
|
|
return DEFAULT_EVENT_NAME.format(self._strike_id)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def source(self) -> str:
|
|
|
|
"""Return source value of this external event."""
|
|
|
|
return DOMAIN
|
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self):
|
|
|
|
"""Disable polling."""
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unit_of_measurement(self):
|
|
|
|
"""Return the unit of measurement."""
|
|
|
|
return self._unit_of_measurement
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def _delete_callback(self):
|
|
|
|
"""Remove this entity."""
|
|
|
|
self._remove_signal_delete()
|
|
|
|
self.hass.async_create_task(self.async_remove())
|
|
|
|
|
|
|
|
async def async_added_to_hass(self):
|
|
|
|
"""Call when entity is added to hass."""
|
|
|
|
self._remove_signal_delete = async_dispatcher_connect(
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass,
|
|
|
|
SIGNAL_DELETE_ENTITY.format(self._strike_id),
|
|
|
|
self._delete_callback,
|
|
|
|
)
|