diff --git a/homeassistant/components/harmony/__init__.py b/homeassistant/components/harmony/__init__.py index c0fddec09cc1e3ba81d6014e41f228c11e37242f..f7140bdb4009f76fa9ca6d9f7fe0d41411f83b25 100644 --- a/homeassistant/components/harmony/__init__.py +++ b/homeassistant/components/harmony/__init__.py @@ -37,11 +37,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): harmony_conf_file = hass.config.path(f"harmony_{entry.unique_id}.conf") try: - device = HarmonyRemote(name, address, activity, harmony_conf_file, delay_secs) - await device.connect() + device = HarmonyRemote( + name, entry.unique_id, address, activity, harmony_conf_file, delay_secs + ) + connected_ok = await device.connect() except (asyncio.TimeoutError, ValueError, AttributeError): raise ConfigEntryNotReady + if not connected_ok: + raise ConfigEntryNotReady + hass.data[DOMAIN][entry.entry_id] = device entry.add_update_listener(_update_listener) diff --git a/homeassistant/components/harmony/config_flow.py b/homeassistant/components/harmony/config_flow.py index 8f9e672c9d9e419d4e81143fdf57521fe385b754..ff7b47d60107e4280dcda27a6c51b641ea7abbc3 100644 --- a/homeassistant/components/harmony/config_flow.py +++ b/homeassistant/components/harmony/config_flow.py @@ -26,24 +26,32 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): - """Validate the user input allows us to connect. - - Data has the keys from DATA_SCHEMA with values provided by the user. - """ - harmony = HarmonyAPI(ip_address=data[CONF_HOST]) - - _LOGGER.debug("harmony:%s", harmony) +async def get_harmony_client_if_available(hass: core.HomeAssistant, ip_address): + """Connect to a harmony hub and fetch info.""" + harmony = HarmonyAPI(ip_address=ip_address) try: if not await harmony.connect(): await harmony.close() - raise CannotConnect + return None except harmony_exceptions.TimeOut: + return None + + await harmony.close() + + return harmony + + +async def validate_input(hass: core.HomeAssistant, data): + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + harmony = await get_harmony_client_if_available(hass, data[CONF_HOST]) + if not harmony: raise CannotConnect unique_id = find_unique_id_for_remote(harmony) - await harmony.close() # As a last resort we get the name from the harmony client # in the event a name was not provided. harmony.name is @@ -74,7 +82,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: try: - info = await validate_input(self.hass, user_input) + validated = await validate_input(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except @@ -82,7 +90,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "unknown" if "base" not in errors: - return await self._async_create_entry_from_valid_input(info, user_input) + await self.async_set_unique_id(validated[UNIQUE_ID]) + self._abort_if_unique_id_configured() + return await self._async_create_entry_from_valid_input( + validated, user_input + ) # Return form return self.async_show_form( @@ -104,8 +116,17 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONF_NAME: friendly_name, } - if self._host_already_configured(self.harmony_config): - return self.async_abort(reason="already_configured") + harmony = await get_harmony_client_if_available( + self.hass, self.harmony_config[CONF_HOST] + ) + + if harmony: + unique_id = find_unique_id_for_remote(harmony) + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured( + updates={CONF_HOST: self.harmony_config[CONF_HOST]} + ) + self.harmony_config[UNIQUE_ID] = unique_id return await self.async_step_link() @@ -114,16 +135,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors = {} if user_input is not None: - try: - info = await validate_input(self.hass, self.harmony_config) - except CannotConnect: - errors["base"] = "cannot_connect" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - - if "base" not in errors: - return await self._async_create_entry_from_valid_input(info, user_input) + # Everything was validated in async_step_ssdp + # all we do now is create. + return await self._async_create_entry_from_valid_input( + self.harmony_config, {} + ) return self.async_show_form( step_id="link", @@ -146,8 +162,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_create_entry_from_valid_input(self, validated, user_input): """Single path to create the config entry from validated input.""" - await self.async_set_unique_id(validated[UNIQUE_ID]) - self._abort_if_unique_id_configured() + data = {CONF_NAME: validated[CONF_NAME], CONF_HOST: validated[CONF_HOST]} # Options from yaml are preserved, we will pull them out when # we setup the config entry diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 47f7c7f974e8061fef98ab464a2dfa8948c8afff..7d23e15a4e70eebe2045b9e08d3633e71869fe4c 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -34,7 +34,6 @@ from .const import ( SERVICE_CHANGE_CHANNEL, SERVICE_SYNC, ) -from .util import find_unique_id_for_remote _LOGGER = logging.getLogger(__name__) @@ -130,7 +129,7 @@ def register_services(hass): class HarmonyRemote(remote.RemoteDevice): """Remote representation used to control a Harmony device.""" - def __init__(self, name, host, activity, out_path, delay_secs): + def __init__(self, name, unique_id, host, activity, out_path, delay_secs): """Initialize HarmonyRemote class.""" self._name = name self.host = host @@ -141,6 +140,7 @@ class HarmonyRemote(remote.RemoteDevice): self._config_path = out_path self.delay_secs = delay_secs self._available = False + self._unique_id = unique_id self._undo_dispatch_subscription = None @property @@ -217,7 +217,7 @@ class HarmonyRemote(remote.RemoteDevice): @property def unique_id(self): """Return the unique id.""" - return find_unique_id_for_remote(self._client) + return self._unique_id @property def name(self): diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index 4791b4e8d4ca813f7611aa3a96e36fb2af0e0342..18c0825b6a1dcb4436467118e3467f8d62d09ec7 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -91,19 +91,28 @@ async def test_form_ssdp(hass): """Test we get the form with ssdp source.""" await setup.async_setup_component(hass, "persistent_notification", {}) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_SSDP}, - data={ - "friendlyName": "Harmony Hub", - "ssdp_location": "http://192.168.209.238:8088/description", - }, - ) + harmonyapi = _get_mock_harmonyapi(connect=True) + + with patch( + "homeassistant.components.harmony.config_flow.HarmonyAPI", + return_value=harmonyapi, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data={ + "friendlyName": "Harmony Hub", + "ssdp_location": "http://192.168.1.12:8088/description", + }, + ) assert result["type"] == "form" assert result["step_id"] == "link" assert result["errors"] == {} + assert result["description_placeholders"] == { + "host": "Harmony Hub", + "name": "192.168.1.12", + } - harmonyapi = _get_mock_harmonyapi(connect=True) with patch( "homeassistant.components.harmony.config_flow.HarmonyAPI", return_value=harmonyapi, @@ -117,7 +126,7 @@ async def test_form_ssdp(hass): assert result2["type"] == "create_entry" assert result2["title"] == "Harmony Hub" assert result2["data"] == { - "host": "192.168.209.238", + "host": "192.168.1.12", "name": "Harmony Hub", } await hass.async_block_till_done()