1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00
ha-core/homeassistant/components/met/__init__.py

182 lines
6.0 KiB
Python

"""The met component."""
from __future__ import annotations
from collections.abc import Callable
from datetime import timedelta
import logging
from random import randrange
from types import MappingProxyType
from typing import Any
import metno
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_ELEVATION,
CONF_LATITUDE,
CONF_LONGITUDE,
EVENT_CORE_CONFIG_UPDATE,
LENGTH_FEET,
LENGTH_METERS,
Platform,
)
from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.distance import convert as convert_distance
import homeassistant.util.dt as dt_util
from .const import (
CONF_TRACK_HOME,
DEFAULT_HOME_LATITUDE,
DEFAULT_HOME_LONGITUDE,
DOMAIN,
)
URL = "https://aa015h6buqvih86i1.api.met.no/weatherapi/locationforecast/2.0/complete"
PLATFORMS = [Platform.WEATHER]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up Met as config entry."""
# Don't setup if tracking home location and latitude or longitude isn't set.
# Also, filters out our onboarding default location.
if config_entry.data.get(CONF_TRACK_HOME, False) and (
(not hass.config.latitude and not hass.config.longitude)
or (
hass.config.latitude == DEFAULT_HOME_LATITUDE
and hass.config.longitude == DEFAULT_HOME_LONGITUDE
)
):
_LOGGER.warning(
"Skip setting up met.no integration; No Home location has been set"
)
return False
coordinator = MetDataUpdateCoordinator(hass, config_entry)
await coordinator.async_config_entry_first_refresh()
if config_entry.data.get(CONF_TRACK_HOME, False):
coordinator.track_home()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config_entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
hass.data[DOMAIN][config_entry.entry_id].untrack_home()
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok
class MetDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Met data."""
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize global Met data updater."""
self._unsub_track_home: Callable | None = None
self.weather = MetWeatherData(
hass, config_entry.data, hass.config.units.is_metric
)
self.weather.set_coordinates()
update_interval = timedelta(minutes=randrange(55, 65))
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
async def _async_update_data(self) -> MetWeatherData:
"""Fetch data from Met."""
try:
return await self.weather.fetch_data()
except Exception as err:
raise UpdateFailed(f"Update failed: {err}") from err
def track_home(self) -> None:
"""Start tracking changes to HA home setting."""
if self._unsub_track_home:
return
async def _async_update_weather_data(_event: Event | None = None) -> None:
"""Update weather data."""
if self.weather.set_coordinates():
await self.async_refresh()
self._unsub_track_home = self.hass.bus.async_listen(
EVENT_CORE_CONFIG_UPDATE, _async_update_weather_data
)
def untrack_home(self) -> None:
"""Stop tracking changes to HA home setting."""
if self._unsub_track_home:
self._unsub_track_home()
self._unsub_track_home = None
class MetWeatherData:
"""Keep data for Met.no weather entities."""
def __init__(
self, hass: HomeAssistant, config: MappingProxyType[str, Any], is_metric: bool
) -> None:
"""Initialise the weather entity data."""
self.hass = hass
self._config = config
self._is_metric = is_metric
self._weather_data: metno.MetWeatherData
self.current_weather_data: dict = {}
self.daily_forecast = None
self.hourly_forecast = None
self._coordinates: dict[str, str] | None = None
def set_coordinates(self) -> bool:
"""Weather data inialization - set the coordinates."""
if self._config.get(CONF_TRACK_HOME, False):
latitude = self.hass.config.latitude
longitude = self.hass.config.longitude
elevation = self.hass.config.elevation
else:
latitude = self._config[CONF_LATITUDE]
longitude = self._config[CONF_LONGITUDE]
elevation = self._config[CONF_ELEVATION]
if not self._is_metric:
elevation = int(
round(convert_distance(elevation, LENGTH_FEET, LENGTH_METERS))
)
coordinates = {
"lat": str(latitude),
"lon": str(longitude),
"msl": str(elevation),
}
if coordinates == self._coordinates:
return False
self._coordinates = coordinates
self._weather_data = metno.MetWeatherData(
coordinates, async_get_clientsession(self.hass), api_url=URL
)
return True
async def fetch_data(self) -> MetWeatherData:
"""Fetch data from API - (current weather and forecast)."""
await self._weather_data.fetching_data()
self.current_weather_data = self._weather_data.get_current_weather()
time_zone = dt_util.DEFAULT_TIME_ZONE
self.daily_forecast = self._weather_data.get_forecast(time_zone, False)
self.hourly_forecast = self._weather_data.get_forecast(time_zone, True)
return self