Skip to content

Commit

Permalink
Merge pull request #66 from JackJPowell/unified-integration-improvements
Browse files Browse the repository at this point in the history
Improve Unified Integration
  • Loading branch information
JackJPowell authored Dec 6, 2024
2 parents 5f64489 + 965aedc commit 8f0651f
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 91 deletions.
19 changes: 11 additions & 8 deletions custom_components/unfoldedcircle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
UnfoldedCircleDockCoordinator,
)

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


PLATFORMS: list[Platform] = [
Expand Down Expand Up @@ -197,14 +201,13 @@ def async_migrate_entity_entry(

await coordinator.async_config_entry_first_refresh()

# If websocket_url is present, we've setup the new flow for the remote
# This means it's safe to validate and register the connection if needed
if (
entry.data.get("websocket_url", "") != ""
and coordinator.api.external_entity_configuration_available
):
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, entry.data.get("websocket_url", "")
coordinator.api, hass, websocket_url
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
Expand Down
150 changes: 107 additions & 43 deletions custom_components/unfoldedcircle/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
import logging
from typing import Any, Awaitable, Callable, Type

from pyUnfoldedCircleRemote.const import AUTH_APIKEY_NAME, SIMULATOR_MAC_ADDRESS
from pyUnfoldedCircleRemote.remote import (
ApiKeyCreateError,
ApiKeyRevokeError,
AuthenticationError,
ExternalSystemAlreadyRegistered,
ExternalSystemNotRegistered,
Remote,
RemoteConnectionError,
TokenRegistrationError,
)
import voluptuous as vol
from voluptuous import Optional, Required

Expand All @@ -30,23 +41,17 @@
from .helpers import (
IntegrationNotFound,
UnableToExtractMacAddress,
InvalidWebsocketAddress,
connect_integration,
device_info_from_discovery_info,
get_ha_websocket_url,
get_registered_websocket_url,
mac_address_from_discovery_info,
synchronize_dock_password,
validate_and_register_system_and_driver,
register_system_and_driver,
validate_dock_password,
synchronize_dock_password,
)
from pyUnfoldedCircleRemote.const import AUTH_APIKEY_NAME, SIMULATOR_MAC_ADDRESS
from pyUnfoldedCircleRemote.remote import (
ApiKeyCreateError,
ApiKeyRevokeError,
AuthenticationError,
ExternalSystemAlreadyRegistered,
Remote,
RemoteConnectionError,
TokenRegistrationError,
validate_websocket_address,
)
from .websocket import SubscriptionEvent, UCWebsocketClient

Expand Down Expand Up @@ -81,6 +86,7 @@ async def validate_input(
self._remote = Remote(data["host"], data["pin"])

websocket_url = data.get(CONF_HA_WEBSOCKET_URL, get_ha_websocket_url(self.hass))
validate_websocket_address(websocket_url)

try:
await self._remote.validate_connection()
Expand Down Expand Up @@ -124,6 +130,8 @@ async def validate_input(
_LOGGER.debug("External system already registered %s", ex)
except TokenRegistrationError as ex:
_LOGGER.error("Error during external system registration %s", ex)
except InvalidWebsocketAddress as ex:
_LOGGER.error("Invalid websocket address supplied %s", ex)
except Exception as ex:
_LOGGER.error(
"Error during driver registration, continue config flow: %s", ex
Expand All @@ -144,7 +152,6 @@ async def validate_input(
"pin": data["pin"],
"mac_address": self._remote.mac_address,
"ip_address": self._remote.ip_address,
"websocket_url": websocket_url,
CONF_SERIAL: self._remote.serial_number,
CONF_MAC: mac_address,
"docks": docks,
Expand Down Expand Up @@ -245,6 +252,8 @@ async def async_step_zeroconf_confirm(
errors["base"] = "invalid_auth"
except CannotCreateHAToken:
errors["base"] = "cannot_create_ha_token"
except InvalidWebsocketAddress:
errors["base"] = "invalid_websocket_address"
else:
if info["docks"]:
return await self.async_step_dock(info=info, first_call=True)
Expand Down Expand Up @@ -291,6 +300,8 @@ async def async_step_user(
errors["base"] = "invalid_auth"
except CannotCreateHAToken:
errors["base"] = "cannot_create_ha_token"
except InvalidWebsocketAddress:
errors["base"] = "invalid_websocket_address"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
Expand Down Expand Up @@ -359,9 +370,7 @@ async def async_step_dock(
self.dock_count += 1
# Update other config entries where the same dock may be registered too
# (same dock associated to multiple remotes)
await synchronize_dock_password(
self.hass, dock_info, self.info["entry_id"]
)
await synchronize_dock_password(self.hass, dock_info, "")
else:
self.info["docks"][self.dock_count]["password"] = ""
raise InvalidDockPassword
Expand Down Expand Up @@ -442,9 +451,9 @@ async def async_step_reauth_confirm(
user_input, self.reauth_entry.data[CONF_HOST]
)
except CannotConnect:
errors["base"] = "Cannot Connect"
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "Invalid PIN"
errors["base"] = "invalid_auth"
except CannotCreateHAToken:
errors["base"] = "cannot_create_ha_token"
except Exception as ex: # pylint: disable=broad-except
Expand Down Expand Up @@ -478,7 +487,6 @@ async def async_step_select_entities(
self,
self.hass,
self._remote,
self.info.get("websocket_url", ""),
self.async_step_finish,
user_input,
)
Expand All @@ -505,7 +513,6 @@ async def async_step_error(
self,
self.hass,
self._remote,
self.info.get("websocket_url", ""),
self.async_step_finish,
user_input,
)
Expand Down Expand Up @@ -545,10 +552,39 @@ async def async_step_init(self, user_input=None): # pylint: disable=unused-argu
)
return await self.async_step_activities()

async def async_step_activities(self, user_input=None):
"""Handle options step two flow initialized by the user."""
if user_input is not None:
self.options.update(user_input)
return await self.async_step_media_player()

return self.async_show_form(
step_id="activities",
data_schema=vol.Schema(
{
vol.Optional(
CONF_ACTIVITIES_AS_SWITCHES,
default=self.config_entry.options.get(
CONF_ACTIVITIES_AS_SWITCHES, False
),
): bool,
vol.Optional(
CONF_SUPPRESS_ACTIVITIY_GROUPS,
default=self.config_entry.options.get(
CONF_SUPPRESS_ACTIVITIY_GROUPS, False
),
): bool,
}
),
last_step=False,
)

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 self.async_show_form(
Expand All @@ -575,34 +611,57 @@ async def async_step_media_player(self, user_input=None) -> FlowResult:
): bool,
}
),
last_step=True,
last_step=False,
)

async def async_step_activities(self, user_input=None):
"""Handle options step two flow initialized by the user."""
async def async_step_websocket(self, user_input=None):
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input is not None:
self.options.update(user_input)
return await self.async_step_media_player()
try:
if validate_websocket_address(user_input.get("websocket_url")):
try:
await register_system_and_driver(
self._remote,
self.hass,
user_input.get("websocket_url"),
)
except ExternalSystemNotRegistered as ex:
_LOGGER.debug(
"Error when registering the external system: %s", ex
)
errors["base"] = "ha_driver_failure"
except TokenRegistrationError as ex:
_LOGGER.error("Error during token creation: %s", ex)
errors["base"] = "ha_driver_failure"
except Exception as ex:
_LOGGER.error(
"Error during driver registration, continue config flow: %s",
ex,
)
else:
self.options.update(user_input)
return await self._update_options()
except InvalidWebsocketAddress as ex:
_LOGGER.error("Invalid Websocket Address: %s", ex)
errors["base"] = "invalid_websocket_address"

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

return self.async_show_form(
step_id="activities",
step_id="websocket",
data_schema=vol.Schema(
{
vol.Optional(
CONF_ACTIVITIES_AS_SWITCHES,
default=self.config_entry.options.get(
CONF_ACTIVITIES_AS_SWITCHES, False
),
): bool,
vol.Optional(
CONF_SUPPRESS_ACTIVITIY_GROUPS,
default=self.config_entry.options.get(
CONF_SUPPRESS_ACTIVITIY_GROUPS, False
),
): bool,
vol.Required(
"websocket_url",
default=url,
): str,
}
),
last_step=False,
last_step=True,
errors=errors,
)

async def _update_options(self):
Expand All @@ -618,7 +677,6 @@ async def async_step_select_entities(
self,
self.hass,
self._remote,
self.config_entry.data.get("websocket_url"),
self.async_step_finish,
user_input,
)
Expand All @@ -633,10 +691,12 @@ async def async_step_error(
self.hass,
get_ha_websocket_url(self.hass),
)
except ExternalSystemAlreadyRegistered as ex:
_LOGGER.debug("External system already registered %s", ex)
except ExternalSystemNotRegistered as ex:
_LOGGER.debug("Error when registering the external system: %s", ex)
except TokenRegistrationError as ex:
_LOGGER.error("Error during external system registration %s", ex)
except InvalidWebsocketAddress as ex:
_LOGGER.error("Invalid websocket address supplied %s", ex)
except Exception as ex:
_LOGGER.error(
"Error during driver registration, continue config flow: %s", ex
Expand All @@ -647,7 +707,6 @@ async def async_step_error(
self,
self.hass,
self._remote,
self.config_entry.data.get("websocket_url"),
self.async_step_finish,
user_input,
)
Expand All @@ -663,7 +722,6 @@ async def async_step_select_entities(
config_flow: ConfigFlow | config_entries.OptionsFlow,
hass: HomeAssistant,
remote: Remote,
websocket_url: str,
finish_callback: Callable[[dict[str, Any] | None], Awaitable[FlowResult]],
user_input: dict[str, Any] | None = None,
) -> FlowResult:
Expand All @@ -678,6 +736,10 @@ async def async_step_select_entities(
'Using remote ID "%s" to get and set subscribed entities', remote.hostname
)

websocket_url = await get_registered_websocket_url(remote)
if websocket_url is None:
websocket_url = get_ha_websocket_url(hass)

if user_input is None:
integration_id = ""
try:
Expand All @@ -694,6 +756,8 @@ async def async_step_select_entities(
)
except IntegrationNotFound:
_LOGGER.error("Integration with name: %s not found", integration_id)
except InvalidWebsocketAddress as ex:
_LOGGER.error("Invalid websocket address supplied %s", ex)
except Exception as ex:
_LOGGER.warning(
"Error while refreshing integration entities of integration: %s, %s",
Expand Down
Loading

0 comments on commit 8f0651f

Please sign in to comment.