mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 03:37:07 +00:00
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:
parent
087b672449
commit
0b2a8bf79a
@ -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")
|
harmony_conf_file = hass.config.path(f"harmony_{entry.unique_id}.conf")
|
||||||
try:
|
try:
|
||||||
device = HarmonyRemote(name, address, activity, harmony_conf_file, delay_secs)
|
device = HarmonyRemote(
|
||||||
await device.connect()
|
name, entry.unique_id, address, activity, harmony_conf_file, delay_secs
|
||||||
|
)
|
||||||
|
connected_ok = await device.connect()
|
||||||
except (asyncio.TimeoutError, ValueError, AttributeError):
|
except (asyncio.TimeoutError, ValueError, AttributeError):
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
if not connected_ok:
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
hass.data[DOMAIN][entry.entry_id] = device
|
hass.data[DOMAIN][entry.entry_id] = device
|
||||||
|
|
||||||
entry.add_update_listener(_update_listener)
|
entry.add_update_listener(_update_listener)
|
||||||
|
@ -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):
|
async def validate_input(hass: core.HomeAssistant, data):
|
||||||
"""Validate the user input allows us to connect.
|
"""Validate the user input allows us to connect.
|
||||||
|
|
||||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||||
"""
|
"""
|
||||||
harmony = HarmonyAPI(ip_address=data[CONF_HOST])
|
harmony = await get_harmony_client_if_available(hass, data[CONF_HOST])
|
||||||
|
if not harmony:
|
||||||
_LOGGER.debug("harmony:%s", harmony)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not await harmony.connect():
|
|
||||||
await harmony.close()
|
|
||||||
raise CannotConnect
|
|
||||||
except harmony_exceptions.TimeOut:
|
|
||||||
raise CannotConnect
|
raise CannotConnect
|
||||||
|
|
||||||
unique_id = find_unique_id_for_remote(harmony)
|
unique_id = find_unique_id_for_remote(harmony)
|
||||||
await harmony.close()
|
|
||||||
|
|
||||||
# As a last resort we get the name from the harmony client
|
# As a last resort we get the name from the harmony client
|
||||||
# in the event a name was not provided. harmony.name is
|
# 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:
|
if user_input is not None:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
info = await validate_input(self.hass, user_input)
|
validated = await validate_input(self.hass, user_input)
|
||||||
except CannotConnect:
|
except CannotConnect:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
@ -82,7 +90,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
|
|
||||||
if "base" not in errors:
|
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 form
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
@ -104,8 +116,17 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
CONF_NAME: friendly_name,
|
CONF_NAME: friendly_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self._host_already_configured(self.harmony_config):
|
harmony = await get_harmony_client_if_available(
|
||||||
return self.async_abort(reason="already_configured")
|
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()
|
return await self.async_step_link()
|
||||||
|
|
||||||
@ -114,16 +135,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
try:
|
# Everything was validated in async_step_ssdp
|
||||||
info = await validate_input(self.hass, self.harmony_config)
|
# all we do now is create.
|
||||||
except CannotConnect:
|
return await self._async_create_entry_from_valid_input(
|
||||||
errors["base"] = "cannot_connect"
|
self.harmony_config, {}
|
||||||
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)
|
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="link",
|
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):
|
async def _async_create_entry_from_valid_input(self, validated, user_input):
|
||||||
"""Single path to create the config entry from validated 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]}
|
data = {CONF_NAME: validated[CONF_NAME], CONF_HOST: validated[CONF_HOST]}
|
||||||
# Options from yaml are preserved, we will pull them out when
|
# Options from yaml are preserved, we will pull them out when
|
||||||
# we setup the config entry
|
# we setup the config entry
|
||||||
|
@ -34,7 +34,6 @@ from .const import (
|
|||||||
SERVICE_CHANGE_CHANNEL,
|
SERVICE_CHANGE_CHANNEL,
|
||||||
SERVICE_SYNC,
|
SERVICE_SYNC,
|
||||||
)
|
)
|
||||||
from .util import find_unique_id_for_remote
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -130,7 +129,7 @@ def register_services(hass):
|
|||||||
class HarmonyRemote(remote.RemoteDevice):
|
class HarmonyRemote(remote.RemoteDevice):
|
||||||
"""Remote representation used to control a Harmony device."""
|
"""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."""
|
"""Initialize HarmonyRemote class."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self.host = host
|
self.host = host
|
||||||
@ -141,6 +140,7 @@ class HarmonyRemote(remote.RemoteDevice):
|
|||||||
self._config_path = out_path
|
self._config_path = out_path
|
||||||
self.delay_secs = delay_secs
|
self.delay_secs = delay_secs
|
||||||
self._available = False
|
self._available = False
|
||||||
|
self._unique_id = unique_id
|
||||||
self._undo_dispatch_subscription = None
|
self._undo_dispatch_subscription = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -217,7 +217,7 @@ class HarmonyRemote(remote.RemoteDevice):
|
|||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return the unique id."""
|
"""Return the unique id."""
|
||||||
return find_unique_id_for_remote(self._client)
|
return self._unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
@ -91,19 +91,28 @@ async def test_form_ssdp(hass):
|
|||||||
"""Test we get the form with ssdp source."""
|
"""Test we get the form with ssdp source."""
|
||||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
||||||
|
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(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_SSDP},
|
context={"source": config_entries.SOURCE_SSDP},
|
||||||
data={
|
data={
|
||||||
"friendlyName": "Harmony Hub",
|
"friendlyName": "Harmony Hub",
|
||||||
"ssdp_location": "http://192.168.209.238:8088/description",
|
"ssdp_location": "http://192.168.1.12:8088/description",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "link"
|
assert result["step_id"] == "link"
|
||||||
assert result["errors"] == {}
|
assert result["errors"] == {}
|
||||||
|
assert result["description_placeholders"] == {
|
||||||
|
"host": "Harmony Hub",
|
||||||
|
"name": "192.168.1.12",
|
||||||
|
}
|
||||||
|
|
||||||
harmonyapi = _get_mock_harmonyapi(connect=True)
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.harmony.config_flow.HarmonyAPI",
|
"homeassistant.components.harmony.config_flow.HarmonyAPI",
|
||||||
return_value=harmonyapi,
|
return_value=harmonyapi,
|
||||||
@ -117,7 +126,7 @@ async def test_form_ssdp(hass):
|
|||||||
assert result2["type"] == "create_entry"
|
assert result2["type"] == "create_entry"
|
||||||
assert result2["title"] == "Harmony Hub"
|
assert result2["title"] == "Harmony Hub"
|
||||||
assert result2["data"] == {
|
assert result2["data"] == {
|
||||||
"host": "192.168.209.238",
|
"host": "192.168.1.12",
|
||||||
"name": "Harmony Hub",
|
"name": "Harmony Hub",
|
||||||
}
|
}
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user