Use system zeroconf singleton for homekit (#35502)

Zeroconf instances are expensive so we share a single instance
instead of running multiple.
This commit is contained in:
J. Nick Koston 2020-05-11 13:21:16 -05:00 committed by GitHub
parent 48899c7a1c
commit 751529feca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 1 deletions

View file

@ -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]

View file

@ -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
}

View file

@ -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

View file

@ -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)):