Skip to content

Commit

Permalink
Swedish weather institute weather component (#16717)
Browse files Browse the repository at this point in the history
* SMHI Component

* Clean up typos

* Fixed default values first config to home location (tests will follow)

* Fixed tests and removed unused function

* Minor fixup after comments from @Kane610

* add support for precipitation in forecast

* Removed old async_step_init not needed.
  • Loading branch information
helto4real authored and MartinHjelmare committed Oct 8, 2018
1 parent 56a4343 commit 540d22d
Show file tree
Hide file tree
Showing 18 changed files with 2,716 additions and 2 deletions.
19 changes: 19 additions & 0 deletions homeassistant/components/smhi/.translations/en.json
@@ -0,0 +1,19 @@
{
"config": {
"error": {
"name_exists": "Name already exists",
"wrong_location": "Location in Sweden only"
},
"step": {
"user": {
"data": {
"latitude": "Latitude",
"longitude": "Longitude",
"name": "Name"
},
"title": "Location in Sweden"
}
},
"title": "Swedish weather service (SMHI)"
}
}
19 changes: 19 additions & 0 deletions homeassistant/components/smhi/.translations/sv.json
@@ -0,0 +1,19 @@
{
"config": {
"error": {
"name_exists": "Namnet finns redan",
"wrong_location": "Endast plats i Sverige"
},
"step": {
"user": {
"data": {
"latitude": "Latitud",
"longitude": "Longitud",
"name": "Namn"
},
"title": "Plats i Sverige"
}
},
"title": "SMHI svenskt väder"
}
}
39 changes: 39 additions & 0 deletions homeassistant/components/smhi/__init__.py
@@ -0,0 +1,39 @@
"""
Component for the swedish weather institute weather service.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/smhi/
"""
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import Config, HomeAssistant

# Have to import for config_flow to work
# even if they are not used here
from .config_flow import smhi_locations # noqa: F401
from .const import DOMAIN # noqa: F401

REQUIREMENTS = ['smhi-pkg==1.0.4']

DEFAULT_NAME = 'smhi'


async def async_setup(hass: HomeAssistant, config: Config) -> bool:
"""Set up configured smhi."""
# We allow setup only through config flow type of config
return True


async def async_setup_entry(hass: HomeAssistant,
config_entry: ConfigEntry) -> bool:
"""Set up smhi forecast as config entry."""
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
config_entry, 'weather'))
return True


async def async_unload_entry(hass: HomeAssistant,
config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
await hass.config_entries.async_forward_entry_unload(
config_entry, 'weather')
return True
124 changes: 124 additions & 0 deletions homeassistant/components/smhi/config_flow.py
@@ -0,0 +1,124 @@
"""Config flow to configure smhi component.
First time the user creates the configuration and
a valid location is set in the hass configuration yaml
it will use that location and use it as default values.
Additional locations can be added in config form.
The input location will be checked by invoking
the API. Exception will be thrown if the location
is not supported by the API (Swedish locations only)
"""
import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries, data_entry_flow
from homeassistant.const import (CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import aiohttp_client
from homeassistant.util import slugify

from .const import DOMAIN, HOME_LOCATION_NAME

REQUIREMENTS = ['smhi-pkg==1.0.4']


@callback
def smhi_locations(hass: HomeAssistant):
"""Return configurations of SMHI component."""
return set((slugify(entry.data[CONF_NAME])) for
entry in hass.config_entries.async_entries(DOMAIN))


@config_entries.HANDLERS.register(DOMAIN)
class SmhiFlowHandler(data_entry_flow.FlowHandler):
"""Config flow for SMHI component."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

def __init__(self) -> None:
"""Initialize SMHI forecast configuration flow."""
self._errors = {}

async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
self._errors = {}

if user_input is not None:
is_ok = await self._check_location(
user_input[CONF_LONGITUDE],
user_input[CONF_LATITUDE]
)
if is_ok:
name = slugify(user_input[CONF_NAME])
if not self._name_in_configuration_exists(name):
return self.async_create_entry(
title=user_input[CONF_NAME],
data=user_input,
)

self._errors[CONF_NAME] = 'name_exists'
else:
self._errors['base'] = 'wrong_location'

# If hass config has the location set and
# is a valid coordinate the default location
# is set as default values in the form
if not smhi_locations(self.hass):
if await self._homeassistant_location_exists():
return await self._show_config_form(
name=HOME_LOCATION_NAME,
latitude=self.hass.config.latitude,
longitude=self.hass.config.longitude
)

return await self._show_config_form()

async def _homeassistant_location_exists(self) -> bool:
"""Return true if default location is set and is valid."""
if self.hass.config.latitude != 0.0 and \
self.hass.config.longitude != 0.0:
# Return true if valid location
if await self._check_location(
self.hass.config.longitude,
self.hass.config.latitude):
return True
return False

def _name_in_configuration_exists(self, name: str) -> bool:
"""Return True if name exists in configuration."""
if name in smhi_locations(self.hass):
return True
return False

async def _show_config_form(self,
name: str = None,
latitude: str = None,
longitude: str = None):
"""Show the configuration form to edit location data."""
return self.async_show_form(
step_id='user',
data_schema=vol.Schema({
vol.Required(CONF_NAME, default=name): str,
vol.Required(CONF_LATITUDE, default=latitude): cv.latitude,
vol.Required(CONF_LONGITUDE, default=longitude): cv.longitude
}),
errors=self._errors,
)

async def _check_location(self, longitude: str, latitude: str) -> bool:
"""Return true if location is ok."""
from smhi.smhi_lib import Smhi, SmhiForecastException
try:
session = aiohttp_client.async_get_clientsession(self.hass)
smhi_api = Smhi(longitude, latitude, session=session)

await smhi_api.async_get_forecast()

return True
except SmhiForecastException:
# The API will throw an exception if faulty location
pass

return False
12 changes: 12 additions & 0 deletions homeassistant/components/smhi/const.py
@@ -0,0 +1,12 @@
"""Constants in smhi component."""
import logging
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN

HOME_LOCATION_NAME = 'Home'

ATTR_SMHI_CLOUDINESS = 'cloudiness'
DOMAIN = 'smhi'
LOGGER = logging.getLogger('homeassistant.components.smhi')
ENTITY_ID_SENSOR_FORMAT = WEATHER_DOMAIN + ".smhi_{}"
ENTITY_ID_SENSOR_FORMAT_HOME = ENTITY_ID_SENSOR_FORMAT.format(
HOME_LOCATION_NAME)
19 changes: 19 additions & 0 deletions homeassistant/components/smhi/strings.json
@@ -0,0 +1,19 @@
{
"config": {
"title": "Swedish weather service (SMHI)",
"step": {
"user": {
"title": "Location in Sweden",
"data": {
"name": "Name",
"latitude": "Latitude",
"longitude": "Longitude"
}
}
},
"error": {
"name_exists": "Name already exists",
"wrong_location": "Location Sweden only"
}
}
}
13 changes: 11 additions & 2 deletions homeassistant/components/weather/__init__.py
Expand Up @@ -40,12 +40,21 @@

async def async_setup(hass, config):
"""Set up the weather component."""
component = EntityComponent(_LOGGER, DOMAIN, hass)

component = hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass)
await component.async_setup(config)
return True


async def async_setup_entry(hass, entry):
"""Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)


async def async_unload_entry(hass, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)


class WeatherEntity(Entity):
"""ABC for weather data."""

Expand Down

0 comments on commit 540d22d

Please sign in to comment.