diff --git a/homeassistant/components/heos/.translations/en.json b/homeassistant/components/heos/.translations/en.json index de440ec611a..a272c0a2a0f 100644 --- a/homeassistant/components/heos/.translations/en.json +++ b/homeassistant/components/heos/.translations/en.json @@ -1,5 +1,20 @@ { "config": { - "title": "Heos" + "title": "Heos", + "step": { + "user": { + "title": "Connect to Heos", + "description": "Please enter the host name or IP address of a Heos device (preferably one connected via wire to the network).", + "data": { + "access_token": "Host" + } + } + }, + "error": { + "connection_failure": "Unable to connect to the specified host." + }, + "abort": { + "already_setup": "You can only configure a single Heos connection as it will support all devices on the network." + } } } \ No newline at end of file diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index 536b4f8623b..2214a602ef3 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -27,6 +27,8 @@ _LOGGER = logging.getLogger(__name__) async def async_setup(hass: HomeAssistantType, config: ConfigType): """Set up the HEOS component.""" + if DOMAIN not in config: + return True host = config[DOMAIN][CONF_HOST] entries = hass.config_entries.async_entries(DOMAIN) if not entries: diff --git a/homeassistant/components/heos/config_flow.py b/homeassistant/components/heos/config_flow.py index 9c4cfc211ae..5fd7ea59912 100644 --- a/homeassistant/components/heos/config_flow.py +++ b/homeassistant/components/heos/config_flow.py @@ -1,4 +1,8 @@ """Config flow to configure Heos.""" +import asyncio + +import voluptuous as vol + from homeassistant import config_entries from homeassistant.const import CONF_HOST @@ -23,3 +27,34 @@ class HeosFlowHandler(config_entries.ConfigFlow): return self.async_create_entry( title=format_title(host), data={CONF_HOST: host}) + + async def async_step_user(self, user_input=None): + """Obtain host and validate connection.""" + from pyheos import Heos + + # Only a single entry is supported + entries = self.hass.config_entries.async_entries(DOMAIN) + if entries: + return self.async_abort(reason='already_setup') + + # Try connecting to host if provided + errors = {} + host = None + if user_input is not None: + host = user_input[CONF_HOST] + heos = Heos(host) + try: + await heos.connect() + return await self.async_step_import(user_input) + except (asyncio.TimeoutError, ConnectionError): + errors[CONF_HOST] = 'connection_failure' + finally: + await heos.disconnect() + + # Return form + return self.async_show_form( + step_id='user', + data_schema=vol.Schema({ + vol.Required(CONF_HOST, default=host): str + }), + errors=errors) diff --git a/homeassistant/components/heos/strings.json b/homeassistant/components/heos/strings.json index de440ec611a..a272c0a2a0f 100644 --- a/homeassistant/components/heos/strings.json +++ b/homeassistant/components/heos/strings.json @@ -1,5 +1,20 @@ { "config": { - "title": "Heos" + "title": "Heos", + "step": { + "user": { + "title": "Connect to Heos", + "description": "Please enter the host name or IP address of a Heos device (preferably one connected via wire to the network).", + "data": { + "access_token": "Host" + } + } + }, + "error": { + "connection_failure": "Unable to connect to the specified host." + }, + "abort": { + "already_setup": "You can only configure a single Heos connection as it will support all devices on the network." + } } } \ No newline at end of file diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index df635807abe..d0d48c0f764 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -153,6 +153,7 @@ FLOWS = [ 'geofency', 'gpslogger', 'hangouts', + 'heos', 'homematicip_cloud', 'hue', 'ifttt', diff --git a/tests/components/heos/test_config_flow.py b/tests/components/heos/test_config_flow.py new file mode 100644 index 00000000000..8314ad07bc2 --- /dev/null +++ b/tests/components/heos/test_config_flow.py @@ -0,0 +1,57 @@ +"""Tests for the Heos config flow module.""" +import asyncio + +from homeassistant import data_entry_flow +from homeassistant.components.heos.config_flow import HeosFlowHandler +from homeassistant.const import CONF_HOST + + +async def test_flow_aborts_already_setup(hass, config_entry): + """Test flow aborts when entry already setup.""" + config_entry.add_to_hass(hass) + flow = HeosFlowHandler() + flow.hass = hass + result = await flow.async_step_user() + assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT + assert result['reason'] == 'already_setup' + + +async def test_no_host_shows_form(hass): + """Test form is shown when host not provided.""" + flow = HeosFlowHandler() + flow.hass = hass + result = await flow.async_step_user() + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'user' + assert result['errors'] == {} + + +async def test_cannot_connect_shows_error_form(hass, controller): + """Test form is shown with error when cannot connect.""" + flow = HeosFlowHandler() + flow.hass = hass + + errors = [ConnectionError, asyncio.TimeoutError] + for error in errors: + controller.connect.side_effect = error + result = await flow.async_step_user({CONF_HOST: '127.0.0.1'}) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'user' + assert result['errors'][CONF_HOST] == 'connection_failure' + assert controller.connect.call_count == 1 + assert controller.disconnect.call_count == 1 + controller.connect.reset_mock() + controller.disconnect.reset_mock() + + +async def test_create_entry_when_host_valid(hass, controller): + """Test result type is create entry when host is valid.""" + flow = HeosFlowHandler() + flow.hass = hass + data = {CONF_HOST: '127.0.0.1'} + result = await flow.async_step_user(data) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result['title'] == 'Controller (127.0.0.1)' + assert result['data'] == data + assert controller.connect.call_count == 1 + assert controller.disconnect.call_count == 1 diff --git a/tests/components/heos/test_init.py b/tests/components/heos/test_init.py index d1932da5abb..b89c39113e4 100644 --- a/tests/components/heos/test_init.py +++ b/tests/components/heos/test_init.py @@ -45,6 +45,16 @@ async def test_async_setup_returns_true(hass, config_entry, config): assert entries[0] == config_entry +async def test_async_setup_no_config_returns_true(hass, config_entry): + """Test component setup updates entry from entry only.""" + config_entry.add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + assert entries[0] == config_entry + + async def test_async_setup_entry_loads_platforms( hass, config_entry, controller): """Test load connects to heos, retrieves players, and loads platforms."""