Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The National Weather Service integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Awaitable, Callable
6 from dataclasses import dataclass
7 import datetime
8 import logging
9 
10 from pynws import NwsNoDataError, SimpleNWS, call_with_retry
11 
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform
14 from homeassistant.core import HomeAssistant
15 from homeassistant.helpers import debounce
16 from homeassistant.helpers.aiohttp_client import async_get_clientsession
17 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
19  TimestampDataUpdateCoordinator,
20  UpdateFailed,
21 )
22 
23 from .const import (
24  CONF_STATION,
25  DEBOUNCE_TIME,
26  DEFAULT_SCAN_INTERVAL,
27  DOMAIN,
28  RETRY_INTERVAL,
29  RETRY_STOP,
30 )
31 from .coordinator import NWSObservationDataUpdateCoordinator
32 
33 _LOGGER = logging.getLogger(__name__)
34 
35 PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
36 
37 type NWSConfigEntry = ConfigEntry[NWSData]
38 
39 
40 def base_unique_id(latitude: float, longitude: float) -> str:
41  """Return unique id for entries in configuration."""
42  return f"{latitude}_{longitude}"
43 
44 
45 @dataclass
46 class NWSData:
47  """Data for the National Weather Service integration."""
48 
49  api: SimpleNWS
50  coordinator_observation: NWSObservationDataUpdateCoordinator
51  coordinator_forecast: TimestampDataUpdateCoordinator[None]
52  coordinator_forecast_hourly: TimestampDataUpdateCoordinator[None]
53 
54 
55 async def async_setup_entry(hass: HomeAssistant, entry: NWSConfigEntry) -> bool:
56  """Set up a National Weather Service entry."""
57  latitude = entry.data[CONF_LATITUDE]
58  longitude = entry.data[CONF_LONGITUDE]
59  api_key = entry.data[CONF_API_KEY]
60  station = entry.data[CONF_STATION]
61 
62  client_session = async_get_clientsession(hass)
63 
64  # set_station only does IO when station is None
65  nws_data = SimpleNWS(latitude, longitude, api_key, client_session)
66  await nws_data.set_station(station)
67 
68  def async_setup_update_forecast(
69  retry_interval: datetime.timedelta | float,
70  retry_stop: datetime.timedelta | float,
71  ) -> Callable[[], Awaitable[None]]:
72  async def update_forecast() -> None:
73  """Retrieve forecast."""
74  try:
75  await call_with_retry(
76  nws_data.update_forecast,
77  retry_interval,
78  retry_stop,
79  retry_no_data=True,
80  )
81  except NwsNoDataError as err:
82  raise UpdateFailed("No data returned.") from err
83 
84  return update_forecast
85 
86  def async_setup_update_forecast_hourly(
87  retry_interval: datetime.timedelta | float,
88  retry_stop: datetime.timedelta | float,
89  ) -> Callable[[], Awaitable[None]]:
90  async def update_forecast_hourly() -> None:
91  """Retrieve forecast hourly."""
92  try:
93  await call_with_retry(
94  nws_data.update_forecast_hourly,
95  retry_interval,
96  retry_stop,
97  retry_no_data=True,
98  )
99  except NwsNoDataError as err:
100  raise UpdateFailed("No data returned.") from err
101 
102  return update_forecast_hourly
103 
104  coordinator_observation = NWSObservationDataUpdateCoordinator(
105  hass,
106  nws_data,
107  )
108 
109  # Don't use retries in setup
110  coordinator_forecast = TimestampDataUpdateCoordinator(
111  hass,
112  _LOGGER,
113  config_entry=entry,
114  name=f"NWS forecast station {station}",
115  update_method=async_setup_update_forecast(0, 0),
116  update_interval=DEFAULT_SCAN_INTERVAL,
117  request_refresh_debouncer=debounce.Debouncer(
118  hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True
119  ),
120  )
121 
122  coordinator_forecast_hourly = TimestampDataUpdateCoordinator(
123  hass,
124  _LOGGER,
125  config_entry=entry,
126  name=f"NWS forecast hourly station {station}",
127  update_method=async_setup_update_forecast_hourly(0, 0),
128  update_interval=DEFAULT_SCAN_INTERVAL,
129  request_refresh_debouncer=debounce.Debouncer(
130  hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True
131  ),
132  )
133  entry.runtime_data = NWSData(
134  nws_data,
135  coordinator_observation,
136  coordinator_forecast,
137  coordinator_forecast_hourly,
138  )
139 
140  # Fetch initial data so we have data when entities subscribe
141  await coordinator_observation.async_refresh()
142  await coordinator_forecast.async_refresh()
143  await coordinator_forecast_hourly.async_refresh()
144 
145  # Use retries
146  coordinator_forecast.update_method = async_setup_update_forecast(
147  RETRY_INTERVAL, RETRY_STOP
148  )
149  coordinator_forecast_hourly.update_method = async_setup_update_forecast_hourly(
150  RETRY_INTERVAL, RETRY_STOP
151  )
152 
153  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
154 
155  return True
156 
157 
158 async def async_unload_entry(hass: HomeAssistant, entry: NWSConfigEntry) -> bool:
159  """Unload a config entry."""
160  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
161 
162 
163 def device_info(latitude: float, longitude: float) -> DeviceInfo:
164  """Return device registry information."""
165  return DeviceInfo(
166  entry_type=DeviceEntryType.SERVICE,
167  identifiers={(DOMAIN, base_unique_id(latitude, longitude))},
168  manufacturer="National Weather Service",
169  name=f"NWS: {latitude}, {longitude}",
170  )
bool async_unload_entry(HomeAssistant hass, NWSConfigEntry entry)
Definition: __init__.py:158
bool async_setup_entry(HomeAssistant hass, NWSConfigEntry entry)
Definition: __init__.py:55
DeviceInfo device_info(float latitude, float longitude)
Definition: __init__.py:163
str base_unique_id(float latitude, float longitude)
Definition: __init__.py:40
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)