Skip to content

Commit

Permalink
Implement reconnect logic in reconfigure flow
Browse files Browse the repository at this point in the history
  • Loading branch information
JackJPowell committed Jan 6, 2025
1 parent a68bff2 commit faef8ff
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 22 deletions.
29 changes: 16 additions & 13 deletions custom_components/unfoldedcircle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@
UnfoldedCircleDockCoordinator,
)

from .helpers import (
get_ha_websocket_url,
get_registered_websocket_url,
validate_and_register_system_and_driver,
)

from .helpers import get_registered_websocket_url

PLATFORMS: list[Platform] = [
Platform.SWITCH,
Expand Down Expand Up @@ -202,13 +197,21 @@ def async_migrate_entity_entry(
await coordinator.async_config_entry_first_refresh()

if coordinator.api.external_entity_configuration_available:
websocket_url = await get_registered_websocket_url(coordinator.api)
if not websocket_url:
websocket_url = get_ha_websocket_url(hass)

await validate_and_register_system_and_driver(
coordinator.api, hass, websocket_url
)
if not await get_registered_websocket_url(coordinator.api):
# We haven't registered a new external system yet, raise issue
issue_registry.async_create_issue(
hass,
DOMAIN,
"websocket_connection",
breaks_in_ha_version=None,
data={"config_entry": entry, "name": coordinator.api.name},
is_fixable=True,
is_persistent=False,
learn_more_url="https://github.com/jackjpowell/hass-unfoldedcircle",
severity=issue_registry.IssueSeverity.WARNING,
translation_key="websocket_connection",
translation_placeholders={"name": coordinator.api.name},
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))
Expand Down
53 changes: 48 additions & 5 deletions custom_components/unfoldedcircle/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import asyncio
import logging
from typing import Any, Awaitable, Callable, Type

from aiohttp import ClientConnectionError
from pyUnfoldedCircleRemote.const import AUTH_APIKEY_NAME, SIMULATOR_MAC_ADDRESS
from pyUnfoldedCircleRemote.remote import (
ApiKeyCreateError,
Expand Down Expand Up @@ -585,9 +585,7 @@ async def async_step_media_player(self, user_input=None) -> FlowResult:
"""Handle a flow initialized by the user."""
if user_input is not None:
self.options.update(user_input)
if self._remote.external_entity_configuration_available:
return await self.async_step_websocket()
return await self._update_options()
return await self.async_step_remote_host()

return self.async_show_form(
step_id="media_player",
Expand Down Expand Up @@ -616,6 +614,49 @@ async def async_step_media_player(self, user_input=None) -> FlowResult:
last_step=False,
)

async def async_step_remote_host(self, user_input=None) -> FlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input is not None:
existing_entry = self._config_entry

remote_api = Remote(
api_url=user_input.get("host"),
apikey=existing_entry.data["apiKey"],
)
try:
if await remote_api.validate_connection():
data = existing_entry.data.copy()
_LOGGER.debug("Updating host for remote")
data["host"] = remote_api.endpoint
except ClientConnectionError:
errors["base"] = "invalid_host"
else:
self.hass.config_entries.async_update_entry(existing_entry, data=data)

if self._remote.external_entity_configuration_available:
return await self.async_step_websocket()
return await self._update_options()

last_step = True
if self._remote.external_entity_configuration_available:
last_step = False

return self.async_show_form(
step_id="remote_host",
data_schema=vol.Schema(
{
vol.Required(
"host",
default=self._config_entry.data["host"],
): str,
}
),
description_placeholders={"name": self._remote.name},
last_step=last_step,
errors=errors,
)

async def async_step_websocket(self, user_input=None):
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
Expand Down Expand Up @@ -648,7 +689,9 @@ async def async_step_websocket(self, user_input=None):
_LOGGER.error("Invalid Websocket Address: %s", ex)
errors["base"] = "invalid_websocket_address"

url = get_ha_websocket_url(self.hass)
url = await get_registered_websocket_url(self._remote)
if url is None:
url = get_ha_websocket_url(self.hass)
if user_input is not None:
url = user_input.get("websocket_url")

Expand Down
88 changes: 87 additions & 1 deletion custom_components/unfoldedcircle/repairs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@
from homeassistant.components.repairs import RepairsFlow
from homeassistant.helpers import issue_registry
from homeassistant.core import HomeAssistant
from .helpers import validate_dock_password, synchronize_dock_password
from .helpers import (
validate_dock_password,
synchronize_dock_password,
register_system_and_driver,
get_ha_websocket_url,
validate_websocket_address,
)
from .config_flow import CannotConnect, InvalidDockPassword
from .const import DOMAIN
from . import UnfoldedCircleConfigEntry
from .websocket import UCWebsocketClient


_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -93,6 +101,78 @@ async def async_step_confirm(
)


class WebSocketRepairFlow(RepairsFlow):
"""Handler for an issue fixing flow."""

def __init__(self, hass, issue_id, data) -> None:
super().__init__()
self.data = data
self.issue_id = issue_id
self.hass = hass
self.config_entry: UnfoldedCircleConfigEntry = self.data.get("config_entry")
self.coordinator = self.config_entry.runtime_data.coordinator

async def async_step_init(
self,
user_input: dict[str, str] | None = None,
) -> data_entry_flow.FlowResult:
"""Handle the first step of a fix flow."""

return await self.async_step_confirm()

async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the confirm step of a fix flow."""
errors: dict[str, str] = {}
if user_input is not None:
try:
if validate_websocket_address(user_input.get("websocket_url")):
await register_system_and_driver(
self.coordinator.api, self.hass, user_input.get("websocket_url")
)
websocket_client = UCWebsocketClient(self.hass)
configure_entities_subscription = (
websocket_client.get_driver_subscription(
self.coordinator.api.hostname
)
)
if not configure_entities_subscription:
raise WebsocketFailure
try:
await self.hass.config_entries.async_reload(
self.coordinator.config_entry.entry_id
)

issue_registry.async_delete_issue(
self.hass, DOMAIN, self.issue_id
)

return self.async_abort(reason="ws_connection_successful")
except Exception:
errors["base"] = "cannot_connect"
except CannotConnect:
errors["base"] = "cannot_connect"
except WebsocketFailure:
errors["base"] = "websocket_failure"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"

return self.async_show_form(
step_id="confirm",
errors=errors,
data_schema=vol.Schema(
{
vol.Required(
"websocket_url", default=get_ha_websocket_url(self.hass)
): str
}
),
description_placeholders={"name": self.data["name"]},
)


async def async_create_fix_flow(
hass: HomeAssistant,
issue_id: str,
Expand All @@ -101,3 +181,9 @@ async def async_create_fix_flow(
"""Create flow."""
if issue_id.startswith("dock_password"):
return DockPasswordRepairFlow(hass, issue_id, data)
if issue_id == "websocket_connection":
return WebSocketRepairFlow(hass, issue_id, data)


class WebsocketFailure(Exception):
"""Error to indicate there the creation of HA token failed."""
36 changes: 33 additions & 3 deletions custom_components/unfoldedcircle/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"error": {
"ha_driver_failure": "Unexpected error when configuring remote entities",
"cannot_create_ha_token": "Unable to create Home Assistant Token",
"invalid_websocket_address": "An invalid home assistant websocket address was supplied"
"invalid_websocket_address": "An invalid home assistant websocket address was supplied",
"invalid_host": "An invalid host was supplied for the remote"
},
"step": {
"init": {
Expand All @@ -90,11 +91,18 @@
"suppress_activity_groups": "Suppress creation of activity group entities"
}
},
"remote_host": {
"title": "Unfolded Circle Options",
"description": "Configure Host / IP Address of {name}",
"data": {
"host": "Host / IP Address"
}
},
"websocket": {
"title": "Unfolded Circle Options",
"description": "Configure Websocket Address",
"description": "Configure Home Assistant Websocket Address",
"data": {
"websocket_url": "Websocket Address for Home Assistant"
"websocket_url": "Home Assistant Websocket Address"
}
},
"select_entities": {
Expand All @@ -114,6 +122,28 @@
}
},
"issues": {
"websocket_connection": {
"title": "Enable improved communications between {name} and Home Assistant",
"fix_flow": {
"step": {
"confirm": {
"title": "Home Assistant Websocket URL",
"description": "To improve communications {name} requires the websocket address of this home assistant server.",
"data": {
"websocket_url": "Home Assistant Websocket URL"
}
}
},
"error": {
"cannot_connect": "Failed to connect",
"websocket_failure": "Invalid Websocket URL",
"unknown": "Unexpected error"
},
"abort": {
"ws_connection_successful": "Improved communications enabled"
}
}
},
"dock_password": {
"title": "Supply dock password for {name}",
"fix_flow": {
Expand Down

0 comments on commit faef8ff

Please sign in to comment.