diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index fdf4b98faf8..2ef7db3a1b4 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -45,7 +45,11 @@ ATTR_TYPE = "type" ATTR_PROPERTIES = "properties" ZEROCONF_TYPE = "_home-assistant._tcp.local." -HOMEKIT_TYPE = "_hap._tcp.local." +HOMEKIT_TYPES = [ + "_hap._tcp.local.", + # Thread based devices + "_hap._udp.local.", +] CONF_DEFAULT_INTERFACE = "default_interface" CONF_IPV6 = "ipv6" @@ -229,8 +233,9 @@ async def _async_start_zeroconf_browser(hass, zeroconf): types = list(zeroconf_types) - if HOMEKIT_TYPE not in zeroconf_types: - types.append(HOMEKIT_TYPE) + for hk_type in HOMEKIT_TYPES: + if hk_type not in zeroconf_types: + types.append(hk_type) def service_update(zeroconf, service_type, name, state_change): """Service state changed.""" @@ -261,7 +266,7 @@ async def _async_start_zeroconf_browser(hass, zeroconf): _LOGGER.debug("Discovered new device %s %s", name, info) # If we can handle it as a HomeKit discovery, we do that here. - if service_type == HOMEKIT_TYPE: + if service_type in HOMEKIT_TYPES: discovery_was_forwarded = handle_homekit(hass, homekit_models, info) # Continue on here as homekit_controller # still needs to get updates on devices @@ -294,7 +299,9 @@ async def _async_start_zeroconf_browser(hass, zeroconf): else: uppercase_mac = None - for entry in zeroconf_types[service_type]: + # Not all homekit types are currently used for discovery + # so not all service type exist in zeroconf_types + for entry in zeroconf_types.get(service_type, []): if len(entry) > 1: if ( uppercase_mac is not None diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 51bdf269bf2..cc34a511573 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -31,9 +31,11 @@ HOMEKIT_STATUS_UNPAIRED = b"1" HOMEKIT_STATUS_PAIRED = b"0" -def service_update_mock(zeroconf, services, handlers): +def service_update_mock(zeroconf, services, handlers, *, limit_service=None): """Call service update handler.""" for service in services: + if limit_service is not None and service != limit_service: + continue handlers[0](zeroconf, service, f"name.{service}", ServiceStateChange.Added) @@ -307,12 +309,16 @@ async def test_homekit_match_partial_space(hass, mock_zeroconf): """Test configured options for a device are loaded via config entry.""" with patch.dict( zc_gen.ZEROCONF, - {zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]}, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, clear=True, ), patch.object( hass.config_entries.flow, "async_init" ) as mock_config_flow, patch.object( - zeroconf, "HaServiceBrowser", side_effect=service_update_mock + zeroconf, + "HaServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._tcp.local." + ), ) as mock_service_browser: mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock( "LIFX bulb", HOMEKIT_STATUS_UNPAIRED @@ -330,12 +336,16 @@ async def test_homekit_match_partial_dash(hass, mock_zeroconf): """Test configured options for a device are loaded via config entry.""" with patch.dict( zc_gen.ZEROCONF, - {zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]}, + {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, clear=True, ), patch.object( hass.config_entries.flow, "async_init" ) as mock_config_flow, patch.object( - zeroconf, "HaServiceBrowser", side_effect=service_update_mock + zeroconf, + "HaServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._udp.local." + ), ) as mock_service_browser: mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock( "Rachio-fa46ba", HOMEKIT_STATUS_UNPAIRED @@ -353,12 +363,16 @@ async def test_homekit_match_full(hass, mock_zeroconf): """Test configured options for a device are loaded via config entry.""" with patch.dict( zc_gen.ZEROCONF, - {zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]}, + {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, clear=True, ), patch.object( hass.config_entries.flow, "async_init" ) as mock_config_flow, patch.object( - zeroconf, "HaServiceBrowser", side_effect=service_update_mock + zeroconf, + "HaServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._udp.local." + ), ) as mock_service_browser: mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock( "BSB002", HOMEKIT_STATUS_UNPAIRED @@ -376,12 +390,16 @@ async def test_homekit_already_paired(hass, mock_zeroconf): """Test that an already paired device is sent to homekit_controller.""" with patch.dict( zc_gen.ZEROCONF, - {zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]}, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, clear=True, ), patch.object( hass.config_entries.flow, "async_init" ) as mock_config_flow, patch.object( - zeroconf, "HaServiceBrowser", side_effect=service_update_mock + zeroconf, + "HaServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._tcp.local." + ), ) as mock_service_browser: mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock( "tado", HOMEKIT_STATUS_PAIRED @@ -400,12 +418,16 @@ async def test_homekit_invalid_paring_status(hass, mock_zeroconf): """Test that missing paring data is not sent to homekit_controller.""" with patch.dict( zc_gen.ZEROCONF, - {zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]}, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, clear=True, ), patch.object( hass.config_entries.flow, "async_init" ) as mock_config_flow, patch.object( - zeroconf, "HaServiceBrowser", side_effect=service_update_mock + zeroconf, + "HaServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._tcp.local." + ), ) as mock_service_browser: mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock( "tado", b"invalid" @@ -423,7 +445,7 @@ async def test_homekit_not_paired(hass, mock_zeroconf): """Test that an not paired device is sent to homekit_controller.""" with patch.dict( zc_gen.ZEROCONF, - {zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]}, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, clear=True, ), patch.object( hass.config_entries.flow, "async_init"