Skip to content

Commit

Permalink
Improve config flow around timeouts. Fix deprecations
Browse files Browse the repository at this point in the history
  • Loading branch information
Xyaren committed Aug 4, 2024
1 parent 7f1dd27 commit ac4381c
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 41 deletions.
15 changes: 8 additions & 7 deletions custom_components/magentatv/api/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Sample API Client."""

from __future__ import annotations

import asyncio
Expand Down Expand Up @@ -94,6 +95,7 @@ async def async_pair(self) -> str:
attempts = 0
while not self._pairing_event.is_set():
attempts += 1
LOGGER.debug("Attempt %s", attempts)
try:
await self._register_for_events()

Expand All @@ -108,15 +110,16 @@ async def async_pair(self) -> str:
except UpnpConnectionError as ex:
await self.async_close()
LOGGER.debug("Could not connect", exc_info=ex)
raise CommunicationException("No connection could be made to the receiver") from None
except (asyncio.CancelledError, asyncio.TimeoutError) as ex:
raise CommunicationException("No connection could be made to the receiver") from ex
except (asyncio.TimeoutError, PairingTimeoutException) as ex:
await self.async_close()
# pairing was not successfull, reset the client to start fresh
LOGGER.debug("Pairing Timed out", exc_info=ex)
if attempts > PAIRING_ATTEMPTS:
LOGGER.warning("Repeated failure")
raise PairingTimeoutException(
f"No pairingCode received from the receiver within {attempts} attempts waiting {PAIRING_EVENT_TIMEOUT} each"
) from None
) from ex

self.assert_paired()
return self._verification_code
Expand Down Expand Up @@ -172,9 +175,7 @@ async def _async_verify_pairing(self):
assert response.status_code == 200
assert "<pairingResult>0</pairingResult>" in response.body

async def _async_send_upnp_soap(
self, service: str, action: str, attributes: Mapping[str, str]
) -> HttpResponse:
async def _async_send_upnp_soap(self, service: str, action: str, attributes: Mapping[str, str]) -> HttpResponse:
try:
attributes = "".join([f" <{k}>{escape(v)}</{k}>\n" for k, v in attributes.items()])
full_body = (
Expand Down Expand Up @@ -249,7 +250,7 @@ async def async_send_key(self, key: KeyCode):
},
)
LOGGER.info("%s - %s: %s", "RemoteKey", key, response)
assert response.status_code== 200
assert response.status_code == 200

async def async_send_character_input(self, character_input: str):
self.assert_paired()
Expand Down
69 changes: 39 additions & 30 deletions custom_components/magentatv/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Adds config flow for Blueprint."""

from __future__ import annotations

import asyncio
Expand Down Expand Up @@ -27,6 +28,7 @@
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from custom_components.magentatv import async_get_notification_server
from custom_components.magentatv.api.exceptions import PairingTimeoutException

from .api import Client
from .const import CONF_USER_ID, DATA_USER_ID, DOMAIN, LOGGER
Expand Down Expand Up @@ -82,7 +84,7 @@ def __init__(self) -> None:
self.verification_code: str | None = None

self.user_id: str | None = None
self.task_pair = None
self.task_pair: asyncio.Task[None] | None = None

self.verification_code = None
self.last_error = None
Expand Down Expand Up @@ -290,21 +292,18 @@ async def _async_task_pair(self):

self.verification_code = await client.async_pair()
# A task that take some time to complete.
except (asyncio.exceptions.TimeoutError, asyncio.exceptions.CancelledError):
self.last_error = "timeout"
LOGGER.debug("pair finished")
except PairingTimeoutException as err:
LOGGER.debug("Timeout during pairing task", exc_info=err)
self.last_error = "pairing_timeout"
return
except Exception as err:
LOGGER.error("Error during pairing", exc_info=err)
LOGGER.debug("Error during pairing task", exc_info=err)
self.last_error = "unknown"
return
finally:
if client is not None:
await client.async_close()
# await _notify_server.async_stop()

# Continue the flow after show progress when the task is done.
# To avoid a potential deadlock we create a new task that continues the flow.
# The task must be completely done so the flow can await the task
# if needed and get the task result.
self.hass.async_create_task(self.hass.config_entries.flow.async_configure(flow_id=self.flow_id))

async def async_step_finish(self, user_input=None):
return self.async_create_entry(
Expand All @@ -322,43 +321,51 @@ async def async_step_finish(self, user_input=None):
},
)

async def async_step_pairing_failed(self, user_input=None) -> FlowResult:
return self.async_abort(reason="pairing_timeout")

async def async_step_pair(self, user_input=None) -> FlowResult:
if self.last_error is not None:
return self.async_show_progress_done(next_step_id="pairing_failed")

if not self.verification_code:
# start async pairing process
if not self.task_pair:
self.task_pair = self.hass.async_create_task(self._async_task_pair())

# TODO: Finish form (title,labels etc)
return self.async_show_progress(
step_id="pair",
progress_action="wait_for_pairing",
description_placeholders={"name": self.friendly_name},
)
else:
# verification code is present -> pairing done
LOGGER.debug("Task Pair - Create new task")

if self.verification_code:
return self.async_show_progress_done(next_step_id="finish")

# if there are errirs during paring, direct the user towards the user-id input dialog to fix possible bad inputs.
if self.last_error:
return self.async_show_progress_done(next_step_id="enter_user_id")

# start async pairing process
if not self.task_pair:
LOGGER.debug("Task Pair - Create new task")
self.task_pair = self.hass.async_create_task(self._async_task_pair())

# the current step will be called when the tasks finises / aborts
return self.async_show_progress(
progress_task=self.task_pair,
progress_action="wait_for_pairing",
step_id="pair",
description_placeholders={"name": self.friendly_name},
)

async def async_step_enter_user_id(self, user_input: dict[str, Any] | None = None) -> FlowResult:
"""Allow the user to enter his user id adding the device."""

self._abort_if_unique_id_configured()
self.hass.data.setdefault(DOMAIN, {})
self.context["title_placeholders"] = {"name": self.friendly_name}

_errors = {}

if self.last_error is None and user_input is not None:
LOGGER.debug("step_enter_user_id Received: %s", user_input)

self.user_id = user_input[CONF_USER_ID]

self.hass.data[DOMAIN][CONF_USER_ID] = self.user_id

self.task_pair = None
return await self.async_step_pair()

if self.last_error:
LOGGER.debug("step_enter_user_id Last Error: %s", user_input)

_errors["base"] = self.last_error
self.last_error = None

Expand All @@ -371,6 +378,8 @@ async def async_step_enter_user_id(self, user_input: dict[str, Any] | None = Non
if prefilled_user_id is not None:
prefilled_user_id = str(prefilled_user_id)

LOGGER.debug("step_enter_user_id Showing Form %s", user_input)

return self.async_show_form(
step_id="enter_user_id",
data_schema=vol.Schema(
Expand Down
5 changes: 3 additions & 2 deletions custom_components/magentatv/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
"progress": {
"wait_for_pairing": "Please wait while the integrations attempts pairing with the receiver: {name}"
},
"abort": {
"pairing_timeout": "Pairing timeout.\nPairing took too long."
"error": {
"pairing_timeout": "Pairing timeout. Please check your inputs and try again.",
"unknown": "An unknown error occured during pairing."
}
}
}
2 changes: 1 addition & 1 deletion hacs.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "MagentaTV",
"homeassistant": "2024.7.3",
"homeassistant": "2024.7.4",
"render_readme": true,
"country": "DE"
}
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
homeassistant==2024.7.3
homeassistant==2024.7.4
pydantic>=1.10.8
async-upnp-client>=0.35.0

0 comments on commit ac4381c

Please sign in to comment.