Skip to content

Commit

Permalink
Add support for OpenWeatherMap One Call API (#39839)
Browse files Browse the repository at this point in the history
  • Loading branch information
nzapponi authored Nov 3, 2020
1 parent 79b10a8 commit 5ddf99e
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 319 deletions.
1 change: 0 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,6 @@ omit =
homeassistant/components/openuv/sensor.py
homeassistant/components/openweathermap/sensor.py
homeassistant/components/openweathermap/weather.py
homeassistant/components/openweathermap/forecast_update_coordinator.py
homeassistant/components/openweathermap/weather_update_coordinator.py
homeassistant/components/openweathermap/abstract_owm_sensor.py
homeassistant/components/opnsense/*
Expand Down
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ homeassistant/components/openerz/* @misialq
homeassistant/components/opengarage/* @danielhiversen
homeassistant/components/opentherm_gw/* @mvn23
homeassistant/components/openuv/* @bachya
homeassistant/components/openweathermap/* @fabaff @freekode
homeassistant/components/openweathermap/* @fabaff @freekode @nzapponi
homeassistant/components/opnsense/* @mtreinish
homeassistant/components/orangepi_gpio/* @pascallj
homeassistant/components/oru/* @bvlaicu
Expand Down
68 changes: 39 additions & 29 deletions homeassistant/components/openweathermap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import logging

from pyowm import OWM
from pyowm.utils.config import get_default_config

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
Expand All @@ -18,13 +19,14 @@
from .const import (
COMPONENTS,
CONF_LANGUAGE,
CONFIG_FLOW_VERSION,
DOMAIN,
ENTRY_FORECAST_COORDINATOR,
ENTRY_NAME,
ENTRY_WEATHER_COORDINATOR,
FORECAST_MODE_FREE_DAILY,
FORECAST_MODE_ONECALL_DAILY,
UPDATE_LISTENER,
)
from .forecast_update_coordinator import ForecastUpdateCoordinator
from .weather_update_coordinator import WeatherUpdateCoordinator

_LOGGER = logging.getLogger(__name__)
Expand All @@ -33,11 +35,6 @@
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""Set up the OpenWeatherMap component."""
hass.data.setdefault(DOMAIN, {})

weather_configs = _filter_domain_configs(config.get("weather", []), DOMAIN)
sensor_configs = _filter_domain_configs(config.get("sensor", []), DOMAIN)

_import_configs(hass, weather_configs + sensor_configs)
return True


Expand All @@ -50,26 +47,22 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
forecast_mode = _get_config_value(config_entry, CONF_MODE)
language = _get_config_value(config_entry, CONF_LANGUAGE)

owm = OWM(API_key=api_key, language=language)
weather_coordinator = WeatherUpdateCoordinator(owm, latitude, longitude, hass)
forecast_coordinator = ForecastUpdateCoordinator(
config_dict = _get_owm_config(language)

owm = OWM(api_key, config_dict).weather_manager()
weather_coordinator = WeatherUpdateCoordinator(
owm, latitude, longitude, forecast_mode, hass
)

await weather_coordinator.async_refresh()
await forecast_coordinator.async_refresh()

if (
not weather_coordinator.last_update_success
and not forecast_coordinator.last_update_success
):
if not weather_coordinator.last_update_success:
raise ConfigEntryNotReady

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config_entry.entry_id] = {
ENTRY_NAME: name,
ENTRY_WEATHER_COORDINATOR: weather_coordinator,
ENTRY_FORECAST_COORDINATOR: forecast_coordinator,
}

for component in COMPONENTS:
Expand All @@ -83,6 +76,28 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
return True


async def async_migrate_entry(hass, entry):
"""Migrate old entry."""
config_entries = hass.config_entries
data = entry.data
version = entry.version

_LOGGER.debug("Migrating OpenWeatherMap entry from version %s", version)

if version == 1:
mode = data[CONF_MODE]
if mode == FORECAST_MODE_FREE_DAILY:
mode = FORECAST_MODE_ONECALL_DAILY

new_data = {**data, CONF_MODE: mode}
version = entry.version = CONFIG_FLOW_VERSION
config_entries.async_update_entry(entry, data=new_data)

_LOGGER.info("Migration to version %s successful", version)

return True


async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry):
"""Update options."""
await hass.config_entries.async_reload(config_entry.entry_id)
Expand All @@ -106,18 +121,6 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
return unload_ok


def _import_configs(hass, configs):
for config in configs:
_LOGGER.debug("Importing OpenWeatherMap %s", config)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
)


def _filter_domain_configs(elements, domain):
return list(filter(lambda elem: elem["platform"] == domain, elements))

Expand All @@ -126,3 +129,10 @@ def _get_config_value(config_entry, key):
if config_entry.options:
return config_entry.options[key]
return config_entry.data[key]


def _get_owm_config(language):
"""Get OpenWeatherMap configuration and add language to it."""
config_dict = get_default_config()
config_dict["language"] = language
return config_dict
64 changes: 29 additions & 35 deletions homeassistant/components/openweathermap/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Config flow for OpenWeatherMap."""
from pyowm import OWM
from pyowm.exceptions.api_call_error import APICallError
from pyowm.exceptions.api_response_error import UnauthorizedError
from pyowm.commons.exceptions import APIRequestError, UnauthorizedError
import voluptuous as vol

from homeassistant import config_entries
Expand All @@ -17,6 +16,7 @@

from .const import (
CONF_LANGUAGE,
CONFIG_FLOW_VERSION,
DEFAULT_FORECAST_MODE,
DEFAULT_LANGUAGE,
DEFAULT_NAME,
Expand All @@ -25,22 +25,11 @@
)
from .const import DOMAIN # pylint:disable=unused-import

SCHEMA = vol.Schema(
{
vol.Required(CONF_API_KEY): str,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
vol.Optional(CONF_LATITUDE): cv.latitude,
vol.Optional(CONF_LONGITUDE): cv.longitude,
vol.Optional(CONF_MODE, default=DEFAULT_FORECAST_MODE): vol.In(FORECAST_MODES),
vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): vol.In(LANGUAGES),
}
)


class OpenWeatherMapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for OpenWeatherMap."""

VERSION = 1
VERSION = CONFIG_FLOW_VERSION
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

@staticmethod
Expand All @@ -62,35 +51,40 @@ async def async_step_user(self, user_input=None):

try:
api_online = await _is_owm_api_online(
self.hass, user_input[CONF_API_KEY]
self.hass, user_input[CONF_API_KEY], latitude, longitude
)
if not api_online:
errors["base"] = "invalid_api_key"
except UnauthorizedError:
errors["base"] = "invalid_api_key"
except APICallError:
except APIRequestError:
errors["base"] = "cannot_connect"

if not errors:
return self.async_create_entry(
title=user_input[CONF_NAME], data=user_input
)
return self.async_show_form(step_id="user", data_schema=SCHEMA, errors=errors)

async def async_step_import(self, import_input=None):
"""Set the config entry up from yaml."""
config = import_input.copy()
if CONF_NAME not in config:
config[CONF_NAME] = DEFAULT_NAME
if CONF_LATITUDE not in config:
config[CONF_LATITUDE] = self.hass.config.latitude
if CONF_LONGITUDE not in config:
config[CONF_LONGITUDE] = self.hass.config.longitude
if CONF_MODE not in config:
config[CONF_MODE] = DEFAULT_FORECAST_MODE
if CONF_LANGUAGE not in config:
config[CONF_LANGUAGE] = DEFAULT_LANGUAGE
return await self.async_step_user(config)

schema = vol.Schema(
{
vol.Required(CONF_API_KEY): str,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
vol.Optional(
CONF_LATITUDE, default=self.hass.config.latitude
): cv.latitude,
vol.Optional(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
vol.Optional(CONF_MODE, default=DEFAULT_FORECAST_MODE): vol.In(
FORECAST_MODES
),
vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): vol.In(
LANGUAGES
),
}
)

return self.async_show_form(step_id="user", data_schema=schema, errors=errors)


class OpenWeatherMapOptionsFlow(config_entries.OptionsFlow):
Expand Down Expand Up @@ -129,6 +123,6 @@ def _get_options_schema(self):
)


async def _is_owm_api_online(hass, api_key):
owm = OWM(api_key)
return await hass.async_add_executor_job(owm.is_API_online)
async def _is_owm_api_online(hass, api_key, lat, lon):
owm = OWM(api_key).weather_manager()
return await hass.async_add_executor_job(owm.one_call, lat, lon)
19 changes: 15 additions & 4 deletions homeassistant/components/openweathermap/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@
DOMAIN = "openweathermap"
DEFAULT_NAME = "OpenWeatherMap"
DEFAULT_LANGUAGE = "en"
DEFAULT_FORECAST_MODE = "freedaily"
ATTRIBUTION = "Data provided by OpenWeatherMap"
CONF_LANGUAGE = "language"
CONFIG_FLOW_VERSION = 2
ENTRY_NAME = "name"
ENTRY_FORECAST_COORDINATOR = "forecast_coordinator"
ENTRY_WEATHER_COORDINATOR = "weather_coordinator"
ATTR_API_PRECIPITATION = "precipitation"
ATTR_API_DATETIME = "datetime"
Expand All @@ -44,13 +43,25 @@
ATTR_API_SNOW = "snow"
ATTR_API_WEATHER_CODE = "weather_code"
ATTR_API_FORECAST = "forecast"
ATTR_API_THIS_DAY_FORECAST = "this_day_forecast"
SENSOR_NAME = "sensor_name"
SENSOR_UNIT = "sensor_unit"
SENSOR_DEVICE_CLASS = "sensor_device_class"
UPDATE_LISTENER = "update_listener"
COMPONENTS = ["sensor", "weather"]
FORECAST_MODES = ["hourly", "daily", "freedaily"]

FORECAST_MODE_HOURLY = "hourly"
FORECAST_MODE_DAILY = "daily"
FORECAST_MODE_FREE_DAILY = "freedaily"
FORECAST_MODE_ONECALL_HOURLY = "onecall_hourly"
FORECAST_MODE_ONECALL_DAILY = "onecall_daily"
FORECAST_MODES = [
FORECAST_MODE_HOURLY,
FORECAST_MODE_DAILY,
FORECAST_MODE_ONECALL_HOURLY,
FORECAST_MODE_ONECALL_DAILY,
]
DEFAULT_FORECAST_MODE = FORECAST_MODE_ONECALL_DAILY

MONITORED_CONDITIONS = [
ATTR_API_WEATHER,
ATTR_API_TEMPERATURE,
Expand Down
Loading

0 comments on commit 5ddf99e

Please sign in to comment.