Home Assistant Unofficial Reference 2024.12.1
geo_location.py
Go to the documentation of this file.
1 """Support for NSW Rural Fire Service Feeds."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from datetime import datetime, timedelta
7 import logging
8 from typing import Any
9 
10 from aio_geojson_nsw_rfs_incidents import NswRuralFireServiceIncidentsFeedManager
11 from aio_geojson_nsw_rfs_incidents.feed_entry import (
12  NswRuralFireServiceIncidentsFeedEntry,
13 )
14 import voluptuous as vol
15 
17  PLATFORM_SCHEMA as GEO_LOCATION_PLATFORM_SCHEMA,
18  GeolocationEvent,
19 )
20 from homeassistant.const import (
21  ATTR_LOCATION,
22  CONF_LATITUDE,
23  CONF_LONGITUDE,
24  CONF_RADIUS,
25  CONF_SCAN_INTERVAL,
26  EVENT_HOMEASSISTANT_START,
27  EVENT_HOMEASSISTANT_STOP,
28  UnitOfLength,
29 )
30 from homeassistant.core import Event, HomeAssistant, callback
31 from homeassistant.helpers import aiohttp_client, config_validation as cv
33  async_dispatcher_connect,
34  async_dispatcher_send,
35 )
36 from homeassistant.helpers.entity_platform import AddEntitiesCallback
37 from homeassistant.helpers.event import async_track_time_interval
38 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
39 
40 _LOGGER = logging.getLogger(__name__)
41 
42 ATTR_CATEGORY = "category"
43 ATTR_COUNCIL_AREA = "council_area"
44 ATTR_EXTERNAL_ID = "external_id"
45 ATTR_FIRE = "fire"
46 ATTR_PUBLICATION_DATE = "publication_date"
47 ATTR_RESPONSIBLE_AGENCY = "responsible_agency"
48 ATTR_SIZE = "size"
49 ATTR_STATUS = "status"
50 ATTR_TYPE = "type"
51 
52 CONF_CATEGORIES = "categories"
53 
54 DEFAULT_RADIUS_IN_KM = 20.0
55 
56 SCAN_INTERVAL = timedelta(minutes=5)
57 
58 SIGNAL_DELETE_ENTITY = "nsw_rural_fire_service_feed_delete_{}"
59 SIGNAL_UPDATE_ENTITY = "nsw_rural_fire_service_feed_update_{}"
60 
61 SOURCE = "nsw_rural_fire_service_feed"
62 
63 VALID_CATEGORIES = ["Advice", "Emergency Warning", "Not Applicable", "Watch and Act"]
64 
65 PLATFORM_SCHEMA = GEO_LOCATION_PLATFORM_SCHEMA.extend(
66  {
67  vol.Optional(CONF_CATEGORIES, default=[]): vol.All(
68  cv.ensure_list, [vol.In(VALID_CATEGORIES)]
69  ),
70  vol.Optional(CONF_LATITUDE): cv.latitude,
71  vol.Optional(CONF_LONGITUDE): cv.longitude,
72  vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS_IN_KM): vol.Coerce(float),
73  }
74 )
75 
76 
78  hass: HomeAssistant,
79  config: ConfigType,
80  async_add_entities: AddEntitiesCallback,
81  discovery_info: DiscoveryInfoType | None = None,
82 ) -> None:
83  """Set up the NSW Rural Fire Service Feed platform."""
84  scan_interval: timedelta = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
85  coordinates: tuple[float, float] = (
86  config.get(CONF_LATITUDE, hass.config.latitude),
87  config.get(CONF_LONGITUDE, hass.config.longitude),
88  )
89  radius_in_km: float = config[CONF_RADIUS]
90  categories: list[str] = config[CONF_CATEGORIES]
91  # Initialize the entity manager.
93  hass, async_add_entities, scan_interval, coordinates, radius_in_km, categories
94  )
95 
96  async def start_feed_manager(event: Event) -> None:
97  """Start feed manager."""
98  await manager.async_init()
99 
100  async def stop_feed_manager(event: Event) -> None:
101  """Stop feed manager."""
102  await manager.async_stop()
103 
104  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_feed_manager)
105  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_feed_manager)
106  hass.async_create_task(manager.async_update())
107 
108 
110  """Feed Entity Manager for NSW Rural Fire Service GeoJSON feed."""
111 
112  def __init__(
113  self,
114  hass: HomeAssistant,
115  async_add_entities: AddEntitiesCallback,
116  scan_interval: timedelta,
117  coordinates: tuple[float, float],
118  radius_in_km: float,
119  categories: list[str],
120  ) -> None:
121  """Initialize the Feed Entity Manager."""
122  self._hass_hass = hass
123  websession = aiohttp_client.async_get_clientsession(hass)
124  self._feed_manager_feed_manager = NswRuralFireServiceIncidentsFeedManager(
125  websession,
126  self._generate_entity_generate_entity,
127  self._update_entity_update_entity,
128  self._remove_entity_remove_entity,
129  coordinates,
130  filter_radius=radius_in_km,
131  filter_categories=categories,
132  )
133  self._async_add_entities_async_add_entities = async_add_entities
134  self._scan_interval_scan_interval = scan_interval
135  self._track_time_remove_callback_track_time_remove_callback: Callable[[], None] | None = None
136 
137  async def async_init(self) -> None:
138  """Schedule initial and regular updates based on configured time interval."""
139 
140  async def update(event_time: datetime) -> None:
141  """Update."""
142  await self.async_updateasync_update()
143 
144  # Trigger updates at regular intervals.
145  self._track_time_remove_callback_track_time_remove_callback = async_track_time_interval(
146  self._hass_hass, update, self._scan_interval_scan_interval
147  )
148 
149  _LOGGER.debug("Feed entity manager initialized")
150 
151  async def async_update(self) -> None:
152  """Refresh data."""
153  await self._feed_manager_feed_manager.update()
154  _LOGGER.debug("Feed entity manager updated")
155 
156  async def async_stop(self) -> None:
157  """Stop this feed entity manager from refreshing."""
158  if self._track_time_remove_callback_track_time_remove_callback:
159  self._track_time_remove_callback_track_time_remove_callback()
160  _LOGGER.debug("Feed entity manager stopped")
161 
163  self, external_id: str
164  ) -> NswRuralFireServiceIncidentsFeedEntry | None:
165  """Get feed entry by external id."""
166  return self._feed_manager_feed_manager.feed_entries.get(external_id)
167 
168  async def _generate_entity(self, external_id: str) -> None:
169  """Generate new entity."""
170  new_entity = NswRuralFireServiceLocationEvent(self, external_id)
171  # Add new entities to HA.
172  self._async_add_entities_async_add_entities([new_entity], True)
173 
174  async def _update_entity(self, external_id: str) -> None:
175  """Update entity."""
176  async_dispatcher_send(self._hass_hass, SIGNAL_UPDATE_ENTITY.format(external_id))
177 
178  async def _remove_entity(self, external_id: str) -> None:
179  """Remove entity."""
180  async_dispatcher_send(self._hass_hass, SIGNAL_DELETE_ENTITY.format(external_id))
181 
182 
184  """Represents an external event with NSW Rural Fire Service data."""
185 
186  _attr_should_poll = False
187  _attr_source = SOURCE
188  _attr_unit_of_measurement = UnitOfLength.KILOMETERS
189 
190  def __init__(
191  self, feed_manager: NswRuralFireServiceFeedEntityManager, external_id: str
192  ) -> None:
193  """Initialize entity with data from feed entry."""
194  self._feed_manager_feed_manager = feed_manager
195  self._external_id_external_id = external_id
196  self._category_category = None
197  self._publication_date_publication_date = None
198  self._location_location = None
199  self._council_area_council_area = None
200  self._status_status = None
201  self._type_type = None
202  self._fire_fire = None
203  self._size_size = None
204  self._responsible_agency_responsible_agency = None
205  self._remove_signal_delete_remove_signal_delete: Callable[[], None]
206  self._remove_signal_update_remove_signal_update: Callable[[], None]
207 
208  async def async_added_to_hass(self) -> None:
209  """Call when entity is added to hass."""
210  self._remove_signal_delete_remove_signal_delete = async_dispatcher_connect(
211  self.hasshass,
212  SIGNAL_DELETE_ENTITY.format(self._external_id_external_id),
213  self._delete_callback_delete_callback,
214  )
215  self._remove_signal_update_remove_signal_update = async_dispatcher_connect(
216  self.hasshass,
217  SIGNAL_UPDATE_ENTITY.format(self._external_id_external_id),
218  self._update_callback_update_callback,
219  )
220 
221  async def async_will_remove_from_hass(self) -> None:
222  """Call when entity will be removed from hass."""
223  self._remove_signal_delete_remove_signal_delete()
224  self._remove_signal_update_remove_signal_update()
225 
226  @callback
227  def _delete_callback(self) -> None:
228  """Remove this entity."""
229  self.hasshass.async_create_task(self.async_removeasync_remove(force_remove=True))
230 
231  @callback
232  def _update_callback(self) -> None:
233  """Call update method."""
234  self.async_schedule_update_ha_stateasync_schedule_update_ha_state(True)
235 
236  async def async_update(self) -> None:
237  """Update this entity from the data held in the feed manager."""
238  _LOGGER.debug("Updating %s", self._external_id_external_id)
239  feed_entry = self._feed_manager_feed_manager.get_entry(self._external_id_external_id)
240  if feed_entry:
241  self._update_from_feed_update_from_feed(feed_entry)
242 
244  self, feed_entry: NswRuralFireServiceIncidentsFeedEntry
245  ) -> None:
246  """Update the internal state from the provided feed entry."""
247  self._attr_name_attr_name = feed_entry.title
248  self._attr_distance_attr_distance = feed_entry.distance_to_home
249  self._attr_latitude_attr_latitude = feed_entry.coordinates[0]
250  self._attr_longitude_attr_longitude = feed_entry.coordinates[1]
251  self._attr_attribution_attr_attribution = feed_entry.attribution
252  self._category_category = feed_entry.category
253  self._publication_date_publication_date = feed_entry.publication_date
254  self._location_location = feed_entry.location
255  self._council_area_council_area = feed_entry.council_area
256  self._status_status = feed_entry.status
257  self._type_type = feed_entry.type
258  self._fire_fire = feed_entry.fire
259  self._size_size = feed_entry.size
260  self._responsible_agency_responsible_agency = feed_entry.responsible_agency
261 
262  @property
263  def icon(self) -> str:
264  """Return the icon to use in the frontend."""
265  if self._fire_fire:
266  return "mdi:fire"
267  return "mdi:alarm-light"
268 
269  @property
270  def extra_state_attributes(self) -> dict[str, Any]:
271  """Return the device state attributes."""
272  return {
273  key: value
274  for key, value in (
275  (ATTR_EXTERNAL_ID, self._external_id_external_id),
276  (ATTR_CATEGORY, self._category_category),
277  (ATTR_LOCATION, self._location_location),
278  (ATTR_PUBLICATION_DATE, self._publication_date_publication_date),
279  (ATTR_COUNCIL_AREA, self._council_area_council_area),
280  (ATTR_STATUS, self._status_status),
281  (ATTR_TYPE, self._type_type),
282  (ATTR_FIRE, self._fire_fire),
283  (ATTR_SIZE, self._size_size),
284  (ATTR_RESPONSIBLE_AGENCY, self._responsible_agency_responsible_agency),
285  )
286  if value or isinstance(value, bool)
287  }
None __init__(self, HomeAssistant hass, AddEntitiesCallback async_add_entities, timedelta scan_interval, tuple[float, float] coordinates, float radius_in_km, list[str] categories)
None __init__(self, NswRuralFireServiceFeedEntityManager feed_manager, str external_id)
None _update_from_feed(self, NswRuralFireServiceIncidentsFeedEntry feed_entry)
None async_schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1265
None async_remove(self, *bool force_remove=False)
Definition: entity.py:1387
config_entries.ConfigEntry|None get_entry(HomeAssistant hass, websocket_api.ActiveConnection connection, str entry_id, int msg_id)
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: geo_location.py:82
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)
Definition: event.py:1679