Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Generic GeoRSS events service.
2 
3 Retrieves current events (typically incidents or alerts) in GeoRSS format, and
4 shows information on events filtered by distance to the HA instance's location
5 and grouped by category.
6 """
7 
8 from __future__ import annotations
9 
10 from datetime import timedelta
11 import logging
12 
13 from georss_client import UPDATE_OK, UPDATE_OK_NO_DATA
14 from georss_generic_client import GenericFeed
15 import voluptuous as vol
16 
18  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
19  SensorEntity,
20 )
21 from homeassistant.const import (
22  CONF_LATITUDE,
23  CONF_LONGITUDE,
24  CONF_NAME,
25  CONF_RADIUS,
26  CONF_UNIT_OF_MEASUREMENT,
27  CONF_URL,
28  UnitOfLength,
29 )
30 from homeassistant.core import HomeAssistant
32 from homeassistant.helpers.entity_platform import AddEntitiesCallback
33 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
34 
35 _LOGGER = logging.getLogger(__name__)
36 
37 ATTR_CATEGORY = "category"
38 ATTR_DISTANCE = "distance"
39 ATTR_TITLE = "title"
40 
41 CONF_CATEGORIES = "categories"
42 
43 DEFAULT_ICON = "mdi:alert"
44 DEFAULT_NAME = "Event Service"
45 DEFAULT_RADIUS_IN_KM = 20.0
46 DEFAULT_UNIT_OF_MEASUREMENT = "Events"
47 
48 DOMAIN = "geo_rss_events"
49 
50 SCAN_INTERVAL = timedelta(minutes=5)
51 
52 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
53  {
54  vol.Required(CONF_URL): cv.string,
55  vol.Optional(CONF_LATITUDE): cv.latitude,
56  vol.Optional(CONF_LONGITUDE): cv.longitude,
57  vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS_IN_KM): vol.Coerce(float),
58  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
59  vol.Optional(CONF_CATEGORIES, default=[]): vol.All(cv.ensure_list, [cv.string]),
60  vol.Optional(
61  CONF_UNIT_OF_MEASUREMENT, default=DEFAULT_UNIT_OF_MEASUREMENT
62  ): cv.string,
63  }
64 )
65 
66 
68  hass: HomeAssistant,
69  config: ConfigType,
70  add_entities: AddEntitiesCallback,
71  discovery_info: DiscoveryInfoType | None = None,
72 ) -> None:
73  """Set up the GeoRSS component."""
74  latitude = config.get(CONF_LATITUDE, hass.config.latitude)
75  longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
76  url = config.get(CONF_URL)
77  radius_in_km = config.get(CONF_RADIUS)
78  name = config.get(CONF_NAME)
79  categories = config.get(CONF_CATEGORIES)
80  unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT)
81 
82  _LOGGER.debug(
83  "latitude=%s, longitude=%s, url=%s, radius=%s",
84  latitude,
85  longitude,
86  url,
87  radius_in_km,
88  )
89 
90  # Create all sensors based on categories.
91  devices = []
92  if not categories:
93  device = GeoRssServiceSensor(
94  (latitude, longitude), url, radius_in_km, None, name, unit_of_measurement
95  )
96  devices.append(device)
97  else:
98  for category in categories:
99  device = GeoRssServiceSensor(
100  (latitude, longitude),
101  url,
102  radius_in_km,
103  category,
104  name,
105  unit_of_measurement,
106  )
107  devices.append(device)
108  add_entities(devices, True)
109 
110 
112  """Representation of a Sensor."""
113 
114  def __init__(
115  self, coordinates, url, radius, category, service_name, unit_of_measurement
116  ):
117  """Initialize the sensor."""
118  self._category_category = category
119  self._service_name_service_name = service_name
120  self._state_state = None
121  self._state_attributes_state_attributes = None
122  self._unit_of_measurement_unit_of_measurement = unit_of_measurement
123 
124  self._feed_feed = GenericFeed(
125  coordinates,
126  url,
127  filter_radius=radius,
128  filter_categories=None if not category else [category],
129  )
130 
131  @property
132  def name(self):
133  """Return the name of the sensor."""
134  return f"{self._service_name} {'Any' if self._category is None else self._category}"
135 
136  @property
137  def native_value(self):
138  """Return the state of the sensor."""
139  return self._state_state
140 
141  @property
143  """Return the unit of measurement."""
144  return self._unit_of_measurement_unit_of_measurement
145 
146  @property
147  def icon(self):
148  """Return the default icon to use in the frontend."""
149  return DEFAULT_ICON
150 
151  @property
153  """Return the state attributes."""
154  return self._state_attributes_state_attributes
155 
156  def update(self) -> None:
157  """Update this sensor from the GeoRSS service."""
158 
159  status, feed_entries = self._feed_feed.update()
160  if status == UPDATE_OK:
161  _LOGGER.debug(
162  "Adding events to sensor %s: %s", self.entity_identity_id, feed_entries
163  )
164  self._state_state = len(feed_entries)
165  # And now compute the attributes from the filtered events.
166  matrix = {}
167  for entry in feed_entries:
168  matrix[entry.title] = (
169  f"{entry.distance_to_home:.0f}{UnitOfLength.KILOMETERS}"
170  )
171  self._state_attributes_state_attributes = matrix
172  elif status == UPDATE_OK_NO_DATA:
173  _LOGGER.debug("Update successful, but no data received from %s", self._feed_feed)
174  # Don't change the state or state attributes.
175  else:
176  _LOGGER.warning(
177  "Update not successful, no data received from %s", self._feed_feed
178  )
179  # If no events were found due to an error then just set state to
180  # zero.
181  self._state_state = 0
182  self._state_attributes_state_attributes = {}
def __init__(self, coordinates, url, radius, category, service_name, unit_of_measurement)
Definition: sensor.py:116
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:72
def add_entities(account, async_add_entities, tracked)
Definition: sensor.py:40