Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """DataUpdateCoordinator for the Airly integration."""
2 
3 from asyncio import timeout
4 from datetime import timedelta
5 import logging
6 from math import ceil
7 
8 from aiohttp import ClientSession
9 from aiohttp.client_exceptions import ClientConnectorError
10 from airly import Airly
11 from airly.exceptions import AirlyError
12 
13 from homeassistant.core import HomeAssistant
14 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
15 from homeassistant.util import dt as dt_util
16 
17 from .const import (
18  ATTR_API_ADVICE,
19  ATTR_API_CAQI,
20  ATTR_API_CAQI_DESCRIPTION,
21  ATTR_API_CAQI_LEVEL,
22  DOMAIN,
23  MAX_UPDATE_INTERVAL,
24  MIN_UPDATE_INTERVAL,
25  NO_AIRLY_SENSORS,
26 )
27 
28 _LOGGER = logging.getLogger(__name__)
29 
30 
31 def set_update_interval(instances_count: int, requests_remaining: int) -> timedelta:
32  """Return data update interval.
33 
34  The number of requests is reset at midnight UTC so we calculate the update
35  interval based on number of minutes until midnight, the number of Airly instances
36  and the number of remaining requests.
37  """
38  now = dt_util.utcnow()
39  midnight = dt_util.find_next_time_expression_time(
40  now, seconds=[0], minutes=[0], hours=[0]
41  )
42  minutes_to_midnight = (midnight - now).total_seconds() / 60
43  interval = timedelta(
44  minutes=min(
45  max(
46  ceil(minutes_to_midnight / requests_remaining * instances_count),
47  MIN_UPDATE_INTERVAL,
48  ),
49  MAX_UPDATE_INTERVAL,
50  )
51  )
52 
53  _LOGGER.debug("Data will be update every %s", interval)
54 
55  return interval
56 
57 
58 class AirlyDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str | float | int]]):
59  """Define an object to hold Airly data."""
60 
61  def __init__(
62  self,
63  hass: HomeAssistant,
64  session: ClientSession,
65  api_key: str,
66  latitude: float,
67  longitude: float,
68  update_interval: timedelta,
69  use_nearest: bool,
70  ) -> None:
71  """Initialize."""
72  self.latitudelatitude = latitude
73  self.longitudelongitude = longitude
74  # Currently, Airly only supports Polish and English
75  language = "pl" if hass.config.language == "pl" else "en"
76  self.airlyairly = Airly(api_key, session, language=language)
77  self.use_nearestuse_nearest = use_nearest
78 
79  super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
80 
81  async def _async_update_data(self) -> dict[str, str | float | int]:
82  """Update data via library."""
83  data: dict[str, str | float | int] = {}
84  if self.use_nearestuse_nearest:
85  measurements = self.airlyairly.create_measurements_session_nearest(
86  self.latitudelatitude, self.longitudelongitude, max_distance_km=5
87  )
88  else:
89  measurements = self.airlyairly.create_measurements_session_point(
90  self.latitudelatitude, self.longitudelongitude
91  )
92  async with timeout(20):
93  try:
94  await measurements.update()
95  except (AirlyError, ClientConnectorError) as error:
96  raise UpdateFailed(error) from error
97 
98  _LOGGER.debug(
99  "Requests remaining: %s/%s",
100  self.airlyairly.requests_remaining,
101  self.airlyairly.requests_per_day,
102  )
103 
104  # Airly API sometimes returns None for requests remaining so we update
105  # update_interval only if we have valid value.
106  if self.airlyairly.requests_remaining:
108  len(self.hasshass.config_entries.async_entries(DOMAIN)),
109  self.airlyairly.requests_remaining,
110  )
111 
112  values = measurements.current["values"]
113  index = measurements.current["indexes"][0]
114  standards = measurements.current["standards"]
115 
116  if index["description"] == NO_AIRLY_SENSORS:
117  raise UpdateFailed("Can't retrieve data: no Airly sensors in this area")
118  for value in values:
119  data[value["name"]] = value["value"]
120  for standard in standards:
121  data[f"{standard['pollutant']}_LIMIT"] = standard["limit"]
122  data[f"{standard['pollutant']}_PERCENT"] = standard["percent"]
123  data[ATTR_API_CAQI] = index["value"]
124  data[ATTR_API_CAQI_LEVEL] = index["level"].lower().replace("_", " ")
125  data[ATTR_API_CAQI_DESCRIPTION] = index["description"]
126  data[ATTR_API_ADVICE] = index["advice"]
127  return data
None __init__(self, HomeAssistant hass, ClientSession session, str api_key, float latitude, float longitude, timedelta update_interval, bool use_nearest)
Definition: coordinator.py:70
timedelta set_update_interval(int instances_count, int requests_remaining)
Definition: coordinator.py:31