diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 26ac841b802..0a208e012fb 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -7,6 +7,7 @@ from aiohttp import web import voluptuous as vol from zeroconf import InterfaceChoice +from homeassistant.components import zeroconf from homeassistant.components.binary_sensor import DEVICE_CLASS_BATTERY_CHARGING from homeassistant.components.http import HomeAssistantView from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -242,6 +243,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry.entry_id, ) await hass.async_add_executor_job(homekit.setup) + await homekit.async_setup_zeroconf() undo_listener = entry.add_update_listener(_async_update_listener) @@ -420,6 +422,7 @@ class HomeKit: self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) ip_addr = self._ip_address or get_local_ip() persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id) + self.driver = HomeDriver( self.hass, self._entry_id, @@ -430,11 +433,18 @@ class HomeKit: advertised_address=self._advertise_ip, interface_choice=self._interface_choice, ) + self.bridge = HomeBridge(self.hass, self.driver, self._name) if self._safe_mode: _LOGGER.debug("Safe_mode selected for %s", self._name) self.driver.safe_mode = True + async def async_setup_zeroconf(self): + """Share the system zeroconf instance.""" + # Replace the existing zeroconf instance. + await self.hass.async_add_executor_job(self.driver.advertiser.close) + self.driver.advertiser = await zeroconf.async_get_instance(self.hass) + def reset_accessories(self, entity_ids): """Reset the accessory to load the latest configuration.""" aid_storage = self.hass.data[DOMAIN][self._entry_id][AID_STORAGE] diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 785c7bb2b87..90e3b189e57 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": ["HAP-python==2.8.3","fnvhash==0.1.0","PyQRCode==1.2.1","base36==0.1.1","PyTurboJPEG==1.4.0"], "dependencies": ["http", "camera", "ffmpeg"], - "after_dependencies": ["logbook"], + "after_dependencies": ["logbook", "zeroconf"], "codeowners": ["@bdraco"], "config_flow": true } diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index d23bda67cee..2ee2e5849f7 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -29,3 +29,10 @@ def events(hass): EVENT_HOMEKIT_CHANGED, ha_callback(lambda e: events.append(e)) ) yield events + + +@pytest.fixture +def mock_zeroconf(): + """Mock zeroconf.""" + with patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc: + yield mock_zc.return_value diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index ac955418d33..8a4ac87f21b 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -5,6 +5,7 @@ from typing import Dict import pytest from zeroconf import InterfaceChoice +from homeassistant.components import zeroconf from homeassistant.components.binary_sensor import DEVICE_CLASS_BATTERY_CHARGING from homeassistant.components.homekit import ( MAX_DEVICES, @@ -94,6 +95,7 @@ async def test_setup_min(hass): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() + type(homekit).async_setup_zeroconf = AsyncMock() assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -135,6 +137,7 @@ async def test_setup_auto_start_disabled(hass): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() + type(homekit).async_setup_zeroconf = AsyncMock() assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -787,6 +790,7 @@ async def test_setup_imported(hass): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() + type(homekit).async_setup_zeroconf = AsyncMock() assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -841,6 +845,7 @@ async def test_yaml_updates_update_config_entry_for_name(hass): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() + type(homekit).async_setup_zeroconf = AsyncMock() assert await async_setup_component( hass, "homekit", {"homekit": {CONF_NAME: BRIDGE_NAME, CONF_PORT: 12345}} ) @@ -885,6 +890,24 @@ async def test_raise_config_entry_not_ready(hass): await hass.async_block_till_done() +async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): + """Test HomeKit uses system zeroconf.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT}, + options={}, + ) + system_zc = await zeroconf.async_get_instance(hass) + + with patch(f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver), patch( + f"{PATH_HOMEKIT}.HomeKit.async_start" + ): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert hk_driver.advertiser == system_zc + + def _write_data(path: str, data: Dict) -> None: """Write the data.""" if not os.path.isdir(os.path.dirname(path)):