diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index da4ccfe9f9a..10d3ed0cfa8 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -420,6 +420,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Should never call this step without setting self.hkid assert self.hkid + description_placeholders = {} errors = {} @@ -465,10 +466,11 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="accessory_not_found_error") except InsecureSetupCode: errors["pairing_code"] = "insecure_setup_code" - except Exception: # pylint: disable=broad-except + except Exception as err: # pylint: disable=broad-except _LOGGER.exception("Pairing attempt failed with an unhandled exception") self.finish_pairing = None errors["pairing_code"] = "pairing_failed" + description_placeholders["error"] = str(err) if not self.finish_pairing: # Its possible that the first try may have been busy so @@ -496,11 +498,12 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # TLV error, usually not in pairing mode _LOGGER.exception("Pairing communication failed") return await self.async_step_protocol_error() - except Exception: # pylint: disable=broad-except + except Exception as err: # pylint: disable=broad-except _LOGGER.exception("Pairing attempt failed with an unhandled exception") errors["pairing_code"] = "pairing_failed" + description_placeholders["error"] = str(err) - return self._async_step_pair_show_form(errors) + return self._async_step_pair_show_form(errors, description_placeholders) async def async_step_busy_error( self, user_input: dict[str, Any] | None = None @@ -531,7 +534,9 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @callback def _async_step_pair_show_form( - self, errors: dict[str, str] | None = None + self, + errors: dict[str, str] | None = None, + description_placeholders: dict[str, str] | None = None, ) -> FlowResult: assert self.category @@ -547,7 +552,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id="pair", errors=errors or {}, - description_placeholders=placeholders, + description_placeholders=placeholders | (description_placeholders or {}), data_schema=vol.Schema(schema), ) diff --git a/homeassistant/components/homekit_controller/strings.json b/homeassistant/components/homekit_controller/strings.json index 2831dabc38d..201e0a9b3c2 100644 --- a/homeassistant/components/homekit_controller/strings.json +++ b/homeassistant/components/homekit_controller/strings.json @@ -37,7 +37,7 @@ "unknown_error": "Device reported an unknown error. Pairing failed.", "authentication_error": "Incorrect HomeKit code. Please check it and try again.", "max_peers_error": "Device refused to add pairing as it has no free pairing storage.", - "pairing_failed": "An unhandled error occurred while attempting to pair with this device. This may be a temporary failure or your device may not be supported currently." + "pairing_failed": "An unhandled error occurred while attempting to pair with this device. This may be a temporary failure or your device may not be supported currently: {error}" }, "abort": { "no_devices": "No unpaired devices could be found", diff --git a/homeassistant/components/homekit_controller/translations/en.json b/homeassistant/components/homekit_controller/translations/en.json index 2686e71d252..c5b5178a58b 100644 --- a/homeassistant/components/homekit_controller/translations/en.json +++ b/homeassistant/components/homekit_controller/translations/en.json @@ -14,7 +14,7 @@ "authentication_error": "Incorrect HomeKit code. Please check it and try again.", "insecure_setup_code": "The requested setup code is insecure because of its trivial nature. This accessory fails to meet basic security requirements.", "max_peers_error": "Device refused to add pairing as it has no free pairing storage.", - "pairing_failed": "An unhandled error occurred while attempting to pair with this device. This may be a temporary failure or your device may not be supported currently.", + "pairing_failed": "An unhandled error occurred while attempting to pair with this device. This may be a temporary failure or your device may not be supported currently: {error}", "unable_to_pair": "Unable to pair, please try again.", "unknown_error": "Device reported an unknown error. Pairing failed." }, diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 5e2c8249560..86af49a96c4 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -8,6 +8,7 @@ from aiohomekit.exceptions import AuthenticationError from aiohomekit.model import Accessories, Accessory from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes +from bleak.exc import BleakError import pytest from homeassistant import config_entries @@ -743,6 +744,57 @@ async def test_pair_form_errors_on_finish(hass, controller, exception, expected) } +async def test_pair_unknown_errors(hass, controller): + """Test describing unknown errors.""" + device = setup_mock_accessory(controller) + discovery_info = get_device_discovery_info(device) + + # Device is discovered + result = await hass.config_entries.flow.async_init( + "homekit_controller", + context={"source": config_entries.SOURCE_ZEROCONF}, + data=discovery_info, + ) + + assert get_flow_context(hass, result) == { + "title_placeholders": {"name": "TestDevice", "category": "Outlet"}, + "unique_id": "00:00:00:00:00:00", + "source": config_entries.SOURCE_ZEROCONF, + } + + # User initiates pairing - this triggers the device to show a pairing code + # and then HA to show a pairing form + finish_pairing = unittest.mock.AsyncMock( + side_effect=BleakError("The bluetooth connection failed") + ) + with patch.object(device, "async_start_pairing", return_value=finish_pairing): + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["type"] == "form" + assert get_flow_context(hass, result) == { + "title_placeholders": {"name": "TestDevice", "category": "Outlet"}, + "unique_id": "00:00:00:00:00:00", + "source": config_entries.SOURCE_ZEROCONF, + } + + # User enters pairing code + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"pairing_code": "111-22-333"} + ) + assert result["type"] == "form" + assert result["errors"]["pairing_code"] == "pairing_failed" + assert ( + result["description_placeholders"]["error"] == "The bluetooth connection failed" + ) + + assert get_flow_context(hass, result) == { + "title_placeholders": {"name": "TestDevice", "category": "Outlet"}, + "unique_id": "00:00:00:00:00:00", + "source": config_entries.SOURCE_ZEROCONF, + "pairing": True, + } + + async def test_user_works(hass, controller): """Test user initiated disovers devices.""" setup_mock_accessory(controller)