From ef5e5c3f9675db4602a1291609c02ce2a2c4f45e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 22 Dec 2021 19:40:36 -1000 Subject: [PATCH] Dismiss existing discoveries when a HomeKit device is paired (#62632) --- .../homekit_controller/config_flow.py | 18 +++++-- .../homekit_controller/test_config_flow.py | 50 +++++++++++++++---- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 0728048ec84..26055b964f8 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.data_entry_flow import AbortFlow, FlowResult from homeassistant.helpers.device_registry import ( CONNECTION_NETWORK_MAC, async_get_registry as async_get_device_registry, @@ -223,6 +223,8 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # The hkid is a unique random number that looks like a pairing code. # It changes if a device is factory reset. hkid = properties[zeroconf.ATTR_PROPERTIES_ID] + normalized_hkid = normalize_hkid(hkid) + model = properties["md"] name = discovery_info.name.replace("._hap._tcp.local.", "") status_flags = int(properties["sf"]) @@ -240,7 +242,9 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): config_num = None # Set unique-id and error out if it's already configured - existing_entry = await self.async_set_unique_id(normalize_hkid(hkid)) + existing_entry = await self.async_set_unique_id( + normalized_hkid, raise_on_progress=False + ) updated_ip_port = { "AccessoryIP": discovery_info.host, "AccessoryPort": discovery_info.port, @@ -303,7 +307,15 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Set unique-id and error out if it's already configured self._abort_if_unique_id_configured(updates=updated_ip_port) - self.context["hkid"] = hkid + for progress in self._async_in_progress(include_uninitialized=True): + if progress["context"].get("unique_id") == normalized_hkid: + if paired: + # If the device gets paired, we want to dismiss + # an existing discovery since we can no longer + # pair with it + self.hass.config_entries.flow.async_abort(progress["flow_id"]) + else: + raise AbortFlow("already_in_progress") if paired: # Device is paired but not to us - ignore it diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index d077bb8eb4e..33b5b15698d 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -206,7 +206,6 @@ async def test_discovery_works(hass, controller, upper_case_props, missing_cshar assert result["type"] == "form" assert result["step_id"] == "pair" assert get_flow_context(hass, result) == { - "hkid": "00:00:00:00:00:00", "source": config_entries.SOURCE_ZEROCONF, "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", @@ -577,7 +576,6 @@ async def test_pair_form_errors_on_start(hass, controller, exception, expected): ) assert get_flow_context(hass, result) == { - "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", "source": config_entries.SOURCE_ZEROCONF, @@ -593,7 +591,6 @@ async def test_pair_form_errors_on_start(hass, controller, exception, expected): assert result["errors"]["pairing_code"] == expected assert get_flow_context(hass, result) == { - "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", "source": config_entries.SOURCE_ZEROCONF, @@ -627,7 +624,6 @@ async def test_pair_abort_errors_on_finish(hass, controller, exception, expected ) assert get_flow_context(hass, result) == { - "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", "source": config_entries.SOURCE_ZEROCONF, @@ -641,7 +637,6 @@ async def test_pair_abort_errors_on_finish(hass, controller, exception, expected assert result["type"] == "form" assert get_flow_context(hass, result) == { - "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", "source": config_entries.SOURCE_ZEROCONF, @@ -669,7 +664,6 @@ async def test_pair_form_errors_on_finish(hass, controller, exception, expected) ) assert get_flow_context(hass, result) == { - "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", "source": config_entries.SOURCE_ZEROCONF, @@ -683,7 +677,6 @@ async def test_pair_form_errors_on_finish(hass, controller, exception, expected) assert result["type"] == "form" assert get_flow_context(hass, result) == { - "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", "source": config_entries.SOURCE_ZEROCONF, @@ -697,7 +690,6 @@ async def test_pair_form_errors_on_finish(hass, controller, exception, expected) assert result["errors"]["pairing_code"] == expected assert get_flow_context(hass, result) == { - "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", "source": config_entries.SOURCE_ZEROCONF, @@ -820,7 +812,6 @@ async def test_unignore_works(hass, controller): assert result["type"] == "form" assert result["step_id"] == "pair" assert get_flow_context(hass, result) == { - "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, "unique_id": "00:00:00:00:00:00", "source": config_entries.SOURCE_UNIGNORE, @@ -852,3 +843,44 @@ async def test_unignore_ignores_missing_devices(hass, controller): assert result["type"] == "abort" assert result["reason"] == "no_devices" + + +async def test_discovery_dismiss_existing_flow_on_paired(hass, controller): + """Test that existing flows get dismissed once paired to something else.""" + device = setup_mock_accessory(controller) + discovery_info = get_device_discovery_info(device) + + # Set device as already not paired + discovery_info.properties["sf"] = 0x01 + discovery_info.properties["c#"] = 99999 + discovery_info.properties[zeroconf.ATTR_PROPERTIES_ID] = "AA:BB:CC:DD:EE:FF" + + # Device is discovered + result = await hass.config_entries.flow.async_init( + "homekit_controller", + context={"source": config_entries.SOURCE_ZEROCONF}, + data=discovery_info, + ) + assert result["type"] == "form" + assert result["step_id"] == "pair" + await hass.async_block_till_done() + assert ( + len(hass.config_entries.flow.async_progress_by_handler("homekit_controller")) + == 1 + ) + + # Set device as already paired + discovery_info.properties["sf"] = 0x00 + # Device is discovered again after pairing to someone else + result2 = await hass.config_entries.flow.async_init( + "homekit_controller", + context={"source": config_entries.SOURCE_ZEROCONF}, + data=discovery_info, + ) + assert result2["type"] == "abort" + assert result2["reason"] == "already_paired" + await hass.async_block_till_done() + assert ( + len(hass.config_entries.flow.async_progress_by_handler("homekit_controller")) + == 0 + )