core/homeassistant/components/nws/coordinator.py

94 lines
2.9 KiB
Python

"""The NWS coordinator."""
from datetime import datetime
import logging
from aiohttp import ClientResponseError
from pynws import NwsNoDataError, SimpleNWS, call_with_retry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import debounce
from homeassistant.helpers.update_coordinator import (
TimestampDataUpdateCoordinator,
UpdateFailed,
)
from homeassistant.util.dt import utcnow
from .const import (
DEBOUNCE_TIME,
DEFAULT_SCAN_INTERVAL,
OBSERVATION_VALID_TIME,
RETRY_INTERVAL,
RETRY_STOP,
UPDATE_TIME_PERIOD,
)
_LOGGER = logging.getLogger(__name__)
class NWSObservationDataUpdateCoordinator(TimestampDataUpdateCoordinator[None]):
"""Class to manage fetching NWS observation data."""
def __init__(
self,
hass: HomeAssistant,
nws: SimpleNWS,
) -> None:
"""Initialize."""
self.nws = nws
self.last_api_success_time: datetime | None = None
self.initialized: bool = False
super().__init__(
hass,
_LOGGER,
name=f"NWS observation station {nws.station}",
update_interval=DEFAULT_SCAN_INTERVAL,
request_refresh_debouncer=debounce.Debouncer(
hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True
),
)
async def _async_update_data(self) -> None:
"""Update data via library."""
if not self.initialized:
await self._async_first_update_data()
else:
await self._async_subsequent_update_data()
async def _async_first_update_data(self):
"""Update data without retries first."""
try:
await self.nws.update_observation(
raise_no_data=True,
start_time=utcnow() - UPDATE_TIME_PERIOD,
)
except (NwsNoDataError, ClientResponseError) as err:
raise UpdateFailed(err) from err
else:
self.last_api_success_time = utcnow()
finally:
self.initialized = True
async def _async_subsequent_update_data(self) -> None:
"""Update data with retries and caching data over multiple failed rounds."""
try:
await call_with_retry(
self.nws.update_observation,
RETRY_INTERVAL,
RETRY_STOP,
retry_no_data=True,
start_time=utcnow() - UPDATE_TIME_PERIOD,
)
except (NwsNoDataError, ClientResponseError) as err:
if not self.last_api_success_time or (
utcnow() - self.last_api_success_time > OBSERVATION_VALID_TIME
):
raise UpdateFailed(err) from err
_LOGGER.debug(
"NWS observation update failed, but data still valid. Last success: %s",
self.last_api_success_time,
)
else:
self.last_api_success_time = utcnow()