diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 29afd5fc236..1bfa44f3894 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -424,11 +424,16 @@ class ZeroconfDiscovery: # Since we prefer local control, if the integration that is being discovered # is cloud AND the homekit device is UNPAIRED we still want to discovery it. # + # Additionally if the integration is polling, HKC offers a local push + # experience for the user to control the device so we want to offer that + # as well. + # # As soon as the device becomes paired, the config flow will be dismissed # in the event the user does not want to pair with Home Assistant. # - if not integration.iot_class or not integration.iot_class.startswith( - "cloud" + if not integration.iot_class or ( + not integration.iot_class.startswith("cloud") + and "polling" not in integration.iot_class ): return diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 88dceb9d464..6bc37e10da2 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -530,7 +530,8 @@ async def test_homekit_match_partial_space(hass, mock_async_zeroconf): await hass.async_block_till_done() assert len(mock_service_browser.mock_calls) == 1 - assert len(mock_config_flow.mock_calls) == 1 + # One for HKC, and one for LIFX since lifx is local polling + assert len(mock_config_flow.mock_calls) == 2 assert mock_config_flow.mock_calls[0][1][0] == "lifx" @@ -741,6 +742,44 @@ async def test_homekit_controller_still_discovered_unpaired_for_cloud( assert mock_config_flow.mock_calls[1][1][0] == "homekit_controller" +async def test_homekit_controller_still_discovered_unpaired_for_polling( + hass, mock_async_zeroconf +): + """Test discovery is still passed to homekit controller when unpaired and discovered by polling integration. + + Since we prefer local push, if the integration that is being discovered + is polling AND the homekit device is unpaired we still want to discovery it + """ + with patch.dict( + zc_gen.ZEROCONF, + {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), patch.dict( + zc_gen.HOMEKIT, + {"iSmartGate": "gogogate2"}, + clear=True, + ), patch.object( + hass.config_entries.flow, "async_init" + ) as mock_config_flow, patch.object( + zeroconf, + "HaAsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._udp.local." + ), + ) as mock_service_browser, patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock("iSmartGate", HOMEKIT_STATUS_UNPAIRED), + ): + assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert len(mock_service_browser.mock_calls) == 1 + assert len(mock_config_flow.mock_calls) == 2 + assert mock_config_flow.mock_calls[0][1][0] == "gogogate2" + assert mock_config_flow.mock_calls[1][1][0] == "homekit_controller" + + async def test_info_from_service_non_utf8(hass): """Test info_from_service handles non UTF-8 property keys and values correctly.""" service_type = "_test._tcp.local."