Make harmony handle IP address changes (#33173)

If the IP address of the harmony hub changed it would
not be rediscovered.  We now connect to get the
unique id and then update config entries with
the correct ip if it is already setup.
This commit is contained in:
J. Nick Koston 2020-03-22 23:24:49 -05:00 committed by GitHub
parent 087b672449
commit 0b2a8bf79a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 41 deletions

View File

@ -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)

View File

@ -26,24 +26,32 @@ DATA_SCHEMA = vol.Schema(
)
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()
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 = HarmonyAPI(ip_address=data[CONF_HOST])
_LOGGER.debug("harmony:%s", harmony)
try:
if not await harmony.connect():
await harmony.close()
raise CannotConnect
except harmony_exceptions.TimeOut:
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

View File

@ -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):

View File

@ -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()