From 5aca16ef015c87b26e833a0d1dbcdbe746cf872d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Apr 2020 09:59:50 -0500 Subject: [PATCH] Add homekit configuration option to bind to default interface (#33999) * Add homekit configuration option to bind to default interface Homekit can fail to be discoverable because the zeroconf default is to bind to all interfaces (InterfaceChoice.All). This does not work on some systems and (InterfaceChoice.Default) which binds to 0.0.0.0 is needed for homekit to zeroconf to function. A new option is available for homekit zeroconf_default_interface: true * Update tests * Update homeassistant/components/homekit/__init__.py Co-Authored-By: springstan <46536646+springstan@users.noreply.github.com> * Update homeassistant/components/homekit/__init__.py Co-Authored-By: springstan <46536646+springstan@users.noreply.github.com> * Review items * has a default * Revert "has a default" This reverts commit 24ecf0920f05f2793abe8a4242ca3f101306b93a. Breaks the tests Co-authored-by: springstan <46536646+springstan@users.noreply.github.com> --- homeassistant/components/homekit/__init__.py | 14 ++++++ homeassistant/components/homekit/const.py | 2 + tests/components/homekit/test_homekit.py | 46 +++++++++++++++++--- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 4fdad670f09..c2ba5a46525 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -4,6 +4,7 @@ import logging from zlib import adler32 import voluptuous as vol +from zeroconf import InterfaceChoice from homeassistant.components import cover from homeassistant.components.cover import DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE @@ -39,9 +40,11 @@ from .const import ( CONF_FEATURE_LIST, CONF_FILTER, CONF_SAFE_MODE, + CONF_ZEROCONF_DEFAULT_INTERFACE, DEFAULT_AUTO_START, DEFAULT_PORT, DEFAULT_SAFE_MODE, + DEFAULT_ZEROCONF_DEFAULT_INTERFACE, DEVICE_CLASS_CO, DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, @@ -98,6 +101,10 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional(CONF_SAFE_MODE, default=DEFAULT_SAFE_MODE): cv.boolean, vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA, vol.Optional(CONF_ENTITY_CONFIG, default={}): validate_entity_config, + vol.Optional( + CONF_ZEROCONF_DEFAULT_INTERFACE, + default=DEFAULT_ZEROCONF_DEFAULT_INTERFACE, + ): cv.boolean, } ) }, @@ -122,6 +129,9 @@ async def async_setup(hass, config): safe_mode = conf[CONF_SAFE_MODE] entity_filter = conf[CONF_FILTER] entity_config = conf[CONF_ENTITY_CONFIG] + interface_choice = ( + InterfaceChoice.Default if config.get(CONF_ZEROCONF_DEFAULT_INTERFACE) else None + ) homekit = HomeKit( hass, @@ -132,6 +142,7 @@ async def async_setup(hass, config): entity_config, safe_mode, advertise_ip, + interface_choice, ) await hass.async_add_executor_job(homekit.setup) @@ -287,6 +298,7 @@ class HomeKit: entity_config, safe_mode, advertise_ip=None, + interface_choice=None, ): """Initialize a HomeKit object.""" self.hass = hass @@ -297,6 +309,7 @@ class HomeKit: self._config = entity_config self._safe_mode = safe_mode self._advertise_ip = advertise_ip + self._interface_choice = interface_choice self.status = STATUS_READY self.bridge = None @@ -317,6 +330,7 @@ class HomeKit: port=self._port, persist_file=path, advertised_address=self._advertise_ip, + interface_choice=self._interface_choice, ) self.bridge = HomeBridge(self.hass, self.driver, self._name) if self._safe_mode: diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index c0f0abe8177..ccce082044b 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -21,12 +21,14 @@ CONF_FILTER = "filter" CONF_LINKED_BATTERY_SENSOR = "linked_battery_sensor" CONF_LOW_BATTERY_THRESHOLD = "low_battery_threshold" CONF_SAFE_MODE = "safe_mode" +CONF_ZEROCONF_DEFAULT_INTERFACE = "zeroconf_default_interface" # #### Config Defaults #### DEFAULT_AUTO_START = True DEFAULT_LOW_BATTERY_THRESHOLD = 20 DEFAULT_PORT = 51827 DEFAULT_SAFE_MODE = False +DEFAULT_ZEROCONF_DEFAULT_INTERFACE = False # #### Features #### FEATURE_ON_OFF = "on_off" diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index d984eef5fdc..124366ba241 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -2,6 +2,7 @@ from unittest.mock import ANY, Mock, patch import pytest +from zeroconf import InterfaceChoice from homeassistant import setup from homeassistant.components.homekit import ( @@ -67,7 +68,7 @@ async def test_setup_min(hass): assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: {}}) mock_homekit.assert_any_call( - hass, BRIDGE_NAME, DEFAULT_PORT, None, ANY, {}, DEFAULT_SAFE_MODE, None + hass, BRIDGE_NAME, DEFAULT_PORT, None, ANY, {}, DEFAULT_SAFE_MODE, None, None ) assert mock_homekit().setup.called is True @@ -96,7 +97,7 @@ async def test_setup_auto_start_disabled(hass): assert await setup.async_setup_component(hass, DOMAIN, config) mock_homekit.assert_any_call( - hass, "Test Name", 11111, "172.0.0.0", ANY, {}, DEFAULT_SAFE_MODE, None + hass, "Test Name", 11111, "172.0.0.0", ANY, {}, DEFAULT_SAFE_MODE, None, None ) assert mock_homekit().setup.called is True @@ -139,6 +140,7 @@ async def test_homekit_setup(hass, hk_driver): port=DEFAULT_PORT, persist_file=path, advertised_address=None, + interface_choice=None, ) assert homekit.driver.safe_mode is False @@ -160,6 +162,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver): port=DEFAULT_PORT, persist_file=ANY, advertised_address=None, + interface_choice=None, ) @@ -179,12 +182,41 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver): port=DEFAULT_PORT, persist_file=ANY, advertised_address="192.168.1.100", + interface_choice=None, + ) + + +async def test_homekit_setup_interface_choice(hass, hk_driver): + """Test setup with interface choice of Default.""" + homekit = HomeKit( + hass, + BRIDGE_NAME, + DEFAULT_PORT, + "0.0.0.0", + {}, + {}, + None, + None, + InterfaceChoice.Default, + ) + + with patch( + f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver + ) as mock_driver: + await hass.async_add_executor_job(homekit.setup) + mock_driver.assert_called_with( + hass, + address="0.0.0.0", + port=DEFAULT_PORT, + persist_file=ANY, + advertised_address=None, + interface_choice=InterfaceChoice.Default, ) async def test_homekit_setup_safe_mode(hass, hk_driver): """Test if safe_mode flag is set.""" - homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}, True) + homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}, True, None) with patch(f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver): await hass.async_add_executor_job(homekit.setup) @@ -193,7 +225,7 @@ async def test_homekit_setup_safe_mode(hass, hk_driver): async def test_homekit_add_accessory(): """Add accessory if config exists and get_acc returns an accessory.""" - homekit = HomeKit("hass", None, None, None, lambda entity_id: True, {}, None) + homekit = HomeKit("hass", None, None, None, lambda entity_id: True, {}, None, None) homekit.driver = "driver" homekit.bridge = mock_bridge = Mock() @@ -215,7 +247,7 @@ async def test_homekit_add_accessory(): async def test_homekit_remove_accessory(): """Remove accessory from bridge.""" - homekit = HomeKit("hass", None, None, None, lambda entity_id: True, {}, None) + homekit = HomeKit("hass", None, None, None, lambda entity_id: True, {}, None, None) homekit.driver = "driver" homekit.bridge = mock_bridge = Mock() mock_bridge.accessories = {"light.demo": "acc"} @@ -228,7 +260,7 @@ async def test_homekit_remove_accessory(): async def test_homekit_entity_filter(hass): """Test the entity filter.""" entity_filter = generate_filter(["cover"], ["demo.test"], [], []) - homekit = HomeKit(hass, None, None, None, entity_filter, {}, None) + homekit = HomeKit(hass, None, None, None, entity_filter, {}, None, None) with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: mock_get_acc.return_value = None @@ -248,7 +280,7 @@ async def test_homekit_entity_filter(hass): async def test_homekit_start(hass, hk_driver, debounce_patcher): """Test HomeKit start method.""" pin = b"123-45-678" - homekit = HomeKit(hass, None, None, None, {}, {"cover.demo": {}}, None) + homekit = HomeKit(hass, None, None, None, {}, {"cover.demo": {}}, None, None) homekit.bridge = Mock() homekit.bridge.accessories = [] homekit.driver = hk_driver