diff --git a/custom_components/magentatv/api/client.py b/custom_components/magentatv/api/client.py index 0bb4b2e..6c1454e 100644 --- a/custom_components/magentatv/api/client.py +++ b/custom_components/magentatv/api/client.py @@ -1,4 +1,5 @@ """Sample API Client.""" + from __future__ import annotations import asyncio @@ -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() @@ -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 @@ -172,9 +175,7 @@ async def _async_verify_pairing(self): assert response.status_code == 200 assert "0" 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)}\n" for k, v in attributes.items()]) full_body = ( @@ -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() diff --git a/custom_components/magentatv/config_flow.py b/custom_components/magentatv/config_flow.py index 45ab0f6..49215a8 100644 --- a/custom_components/magentatv/config_flow.py +++ b/custom_components/magentatv/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Blueprint.""" + from __future__ import annotations import asyncio @@ -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 @@ -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 @@ -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( @@ -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 @@ -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( diff --git a/custom_components/magentatv/translations/en.json b/custom_components/magentatv/translations/en.json index c2640b9..406c906 100644 --- a/custom_components/magentatv/translations/en.json +++ b/custom_components/magentatv/translations/en.json @@ -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." } } } \ No newline at end of file diff --git a/hacs.json b/hacs.json index cbf02e3..108e0c0 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,6 @@ { "name": "MagentaTV", - "homeassistant": "2024.7.3", + "homeassistant": "2024.7.4", "render_readme": true, "country": "DE" } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3d7b690..5023027 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -homeassistant==2024.7.3 +homeassistant==2024.7.4 pydantic>=1.10.8 async-upnp-client>=0.35.0 \ No newline at end of file