diff --git a/homeassistant/components/met/.translations/en.json b/homeassistant/components/met/.translations/en.json new file mode 100644 index 00000000000..21ae7cb78fa --- /dev/null +++ b/homeassistant/components/met/.translations/en.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "name_exists": "Name already exists" + }, + "step": { + "user": { + "data": { + "elevation": "Elevation", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Name" + }, + "description": "Meteorologisk institutt", + "title": "Location" + } + }, + "title": "Met.no" + } +} \ No newline at end of file diff --git a/homeassistant/components/met/__init__.py b/homeassistant/components/met/__init__.py index 67bd64f3e16..aa284ad02e2 100644 --- a/homeassistant/components/met/__init__.py +++ b/homeassistant/components/met/__init__.py @@ -1 +1,23 @@ """The met component.""" +from homeassistant.core import Config, HomeAssistant +from .config_flow import MetFlowHandler # noqa +from .const import DOMAIN # noqa + + +async def async_setup(hass: HomeAssistant, config: Config) -> bool: + """Set up configured Met.""" + return True + + +async def async_setup_entry(hass, config_entry): + """Set up Met 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, config_entry): + """Unload a config entry.""" + await hass.config_entries.async_forward_entry_unload( + config_entry, 'weather') + return True diff --git a/homeassistant/components/met/config_flow.py b/homeassistant/components/met/config_flow.py new file mode 100644 index 00000000000..07123a91855 --- /dev/null +++ b/homeassistant/components/met/config_flow.py @@ -0,0 +1,63 @@ +"""Config flow to configure Met component.""" +import voluptuous as vol + +from homeassistant import config_entries, data_entry_flow +from homeassistant.const import ( + CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv + +from .const import DOMAIN, HOME_LOCATION_NAME + + +@callback +def configured_instances(hass): + """Return a set of configured SimpliSafe instances.""" + return set( + entry.data[CONF_NAME] + for entry in hass.config_entries.async_entries(DOMAIN)) + + +@config_entries.HANDLERS.register(DOMAIN) +class MetFlowHandler(data_entry_flow.FlowHandler): + """Config flow for Met component.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Init MetFlowHandler.""" + 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: + if user_input[CONF_NAME] not in configured_instances(self.hass): + return self.async_create_entry( + title=user_input[CONF_NAME], + data=user_input, + ) + + self._errors[CONF_NAME] = 'name_exists' + + return await self._show_config_form( + name=HOME_LOCATION_NAME, + latitude=self.hass.config.latitude, + longitude=self.hass.config.longitude, + elevation=self.hass.config.elevation) + + async def _show_config_form(self, name=None, latitude=None, + longitude=None, elevation=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, + vol.Required(CONF_ELEVATION, default=elevation): int + }), + errors=self._errors, + ) diff --git a/homeassistant/components/met/const.py b/homeassistant/components/met/const.py new file mode 100644 index 00000000000..cf1f3aac53d --- /dev/null +++ b/homeassistant/components/met/const.py @@ -0,0 +1,14 @@ +"""Constants for Met component.""" +import logging + +from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN + +DOMAIN = 'met' + +HOME_LOCATION_NAME = 'Home' + +ENTITY_ID_SENSOR_FORMAT = WEATHER_DOMAIN + ".met_{}" +ENTITY_ID_SENSOR_FORMAT_HOME = ENTITY_ID_SENSOR_FORMAT.format( + HOME_LOCATION_NAME) + +_LOGGER = logging.getLogger('.') diff --git a/homeassistant/components/met/manifest.json b/homeassistant/components/met/manifest.json index b2ef166be50..426d0faf860 100644 --- a/homeassistant/components/met/manifest.json +++ b/homeassistant/components/met/manifest.json @@ -1,6 +1,7 @@ { "domain": "met", "name": "Met", + "config_flow": true, "documentation": "https://www.home-assistant.io/components/met", "requirements": [ "pyMetno==0.4.6" diff --git a/homeassistant/components/met/strings.json b/homeassistant/components/met/strings.json new file mode 100644 index 00000000000..f5c49bac3c4 --- /dev/null +++ b/homeassistant/components/met/strings.json @@ -0,0 +1,20 @@ +{ + "config": { + "title": "Met.no", + "step": { + "user": { + "title": "Location", + "description": "Meteorologisk institutt", + "data": { + "name": "Name", + "latitude": "Latitude", + "longitude": "Longitude", + "elevation": "Elevation" + } + } + }, + "error": { + "name_exists": "Name already exists" + } + } +} diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py index d9824e203c5..20f408b91ba 100644 --- a/homeassistant/components/met/weather.py +++ b/homeassistant/components/met/weather.py @@ -33,23 +33,43 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Met.no weather platform.""" - elevation = config.get(CONF_ELEVATION, hass.config.elevation or 0) + _LOGGER.warning("Loading Met.no via platform config is deprecated") + + name = config.get(CONF_NAME) latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) - name = config.get(CONF_NAME) + elevation = config.get(CONF_ELEVATION, hass.config.elevation or 0) if None in (latitude, longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") return + station = await async_get_station( + hass, name, latitude, longitude, elevation) + async_add_entities([station]) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add a weather entity from a config_entry.""" + name = config_entry.data.get(CONF_NAME) + latitude = config_entry.data[CONF_LATITUDE] + longitude = config_entry.data[CONF_LONGITUDE] + elevation = config_entry.data[CONF_ELEVATION] + + station = await async_get_station( + hass, name, latitude, longitude, elevation) + async_add_entities([station]) + + +async def async_get_station(hass, name, latitude, longitude, elevation): + """Retrieve weather station, station name to be used as the entity name.""" coordinates = { 'lat': str(latitude), 'lon': str(longitude), 'msl': str(elevation), } - async_add_entities([MetWeather( - name, coordinates, async_get_clientsession(hass))]) + return MetWeather(name, coordinates, async_get_clientsession(hass)) class MetWeather(WeatherEntity): diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 4c7d77e0dab..926023f4a75 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -32,6 +32,7 @@ FLOWS = [ "logi_circle", "luftdaten", "mailgun", + "met", "mobile_app", "mqtt", "nest", diff --git a/tests/components/met/test_config_flow.py b/tests/components/met/test_config_flow.py new file mode 100644 index 00000000000..9e625eaa9f5 --- /dev/null +++ b/tests/components/met/test_config_flow.py @@ -0,0 +1,133 @@ +"""Tests for Met.no config flow.""" +from unittest.mock import Mock, patch + +from tests.common import MockConfigEntry, mock_coro + +from homeassistant.const import CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.components.met import config_flow + + +async def test_show_config_form(): + """Test show configuration form.""" + hass = Mock() + flow = config_flow.MetFlowHandler() + flow.hass = hass + + result = await flow._show_config_form() + + assert result['type'] == 'form' + assert result['step_id'] == 'user' + + +async def test_show_config_form_default_values(): + """Test show configuration form.""" + hass = Mock() + flow = config_flow.MetFlowHandler() + flow.hass = hass + + result = await flow._show_config_form( + name="test", latitude='0', longitude='0', elevation='0') + + assert result['type'] == 'form' + assert result['step_id'] == 'user' + + +async def test_flow_with_home_location(hass): + """Test config flow . + + Tests the flow when a default location is configured + then it should return a form with default values + """ + flow = config_flow.MetFlowHandler() + flow.hass = hass + + hass.config.location_name = 'Home' + hass.config.latitude = 1 + hass.config.longitude = 1 + hass.config.elevation = 1 + + result = await flow.async_step_user() + assert result['type'] == 'form' + assert result['step_id'] == 'user' + + +async def test_flow_show_form(): + """Test show form scenarios first time. + + Test when the form should show when no configurations exists + """ + hass = Mock() + flow = config_flow.MetFlowHandler() + flow.hass = hass + + with \ + patch.object(flow, '_show_config_form', + return_value=mock_coro()) as config_form: + await flow.async_step_user() + assert len(config_form.mock_calls) == 1 + + +async def test_flow_entry_created_from_user_input(): + """Test that create data from user input. + + Test when the form should show when no configurations exists + """ + hass = Mock() + flow = config_flow.MetFlowHandler() + flow.hass = hass + + test_data = { + 'name': 'home', + CONF_LONGITUDE: '0', + CONF_LATITUDE: '0', + CONF_ELEVATION: '0' + } + + # Test that entry created when user_input name not exists + with \ + patch.object(flow, '_show_config_form', + return_value=mock_coro()) as config_form,\ + patch.object(flow.hass.config_entries, 'async_entries', + return_value=mock_coro()) as config_entries: + + result = await flow.async_step_user(user_input=test_data) + + assert result['type'] == 'create_entry' + assert result['data'] == test_data + assert len(config_entries.mock_calls) == 1 + assert not config_form.mock_calls + + +async def test_flow_entry_config_entry_already_exists(): + """Test that create data from user input and config_entry already exists. + + Test when the form should show when user puts existing name + in the config gui. Then the form should show with error + """ + hass = Mock() + + flow = config_flow.MetFlowHandler() + flow.hass = hass + + first_entry = MockConfigEntry(domain='met') + first_entry.data['name'] = 'home' + first_entry.add_to_hass(hass) + + test_data = { + 'name': 'home', + CONF_LONGITUDE: '0', + CONF_LATITUDE: '0', + CONF_ELEVATION: '0' + } + + with \ + patch.object(flow, '_show_config_form', + return_value=mock_coro()) as config_form,\ + patch.object(flow.hass.config_entries, 'async_entries', + return_value=[first_entry]) as config_entries: + + await flow.async_step_user(user_input=test_data) + + assert len(config_form.mock_calls) == 1 + assert len(config_entries.mock_calls) == 1 + assert len(flow._errors) == 1