Add option to specify mDNS advertised IP address for HomeKit Bridge (#26791)

* Add options to specify advertised IP and MAC for HomeKit Bridge

This makes use of HAP-python's new feature in version 2.6.0
that allows to specify the mDNS advertised IP and MAC address.

This is a requirement for the following use cases:
- Running Home Assistant behind a NAT, e.g. inside Docker.
- Running it on a system with multiple interfaces there
  the default IP address, DNS entry and hostname diverge.

The forwarding of the required mDNS packets can be done with
an avahi-daemon based gateway, e.g. by using enable-reflector=yes.

Specifying the MAC address makes it possible to identify an
accessory in case HA is run inside a ephemeral docker container.

Whitespace changes were performed due to black and flake8.

* Update tests for HomeKit Bridge due to IP and MAC advertising

Whitespace changes were performed due to black and flake8.

* Remove the possibility to set the MAC address of the HomeKit Bridge

Since the MAC address is a random device ID, there is no need
for the user to be able to set a custom MAC address value for it.

Whitespace changes were performed due to black and flake8.
This commit is contained in:
Marc Hörsken 2019-10-23 07:06:21 +02:00 committed by Paulus Schoutsen
parent da094e09fa
commit e3f0c904b0
3 changed files with 60 additions and 7 deletions

View File

@ -31,6 +31,7 @@ from homeassistant.util.decorator import Registry
from .const import (
BRIDGE_NAME,
CONF_ADVERTISE_IP,
CONF_AUTO_START,
CONF_ENTITY_CONFIG,
CONF_FEATURE_LIST,
@ -89,6 +90,9 @@ CONFIG_SCHEMA = vol.Schema(
),
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_IP_ADDRESS): vol.All(ipaddress.ip_address, cv.string),
vol.Optional(CONF_ADVERTISE_IP): vol.All(
ipaddress.ip_address, cv.string
),
vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): cv.boolean,
vol.Optional(CONF_SAFE_MODE, default=DEFAULT_SAFE_MODE): cv.boolean,
vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA,
@ -112,13 +116,21 @@ async def async_setup(hass, config):
name = conf[CONF_NAME]
port = conf[CONF_PORT]
ip_address = conf.get(CONF_IP_ADDRESS)
advertise_ip = conf.get(CONF_ADVERTISE_IP)
auto_start = conf[CONF_AUTO_START]
safe_mode = conf[CONF_SAFE_MODE]
entity_filter = conf[CONF_FILTER]
entity_config = conf[CONF_ENTITY_CONFIG]
homekit = HomeKit(
hass, name, port, ip_address, entity_filter, entity_config, safe_mode
hass,
name,
port,
ip_address,
entity_filter,
entity_config,
safe_mode,
advertise_ip,
)
await hass.async_add_executor_job(homekit.setup)
@ -265,7 +277,15 @@ class HomeKit:
"""Class to handle all actions between HomeKit and Home Assistant."""
def __init__(
self, hass, name, port, ip_address, entity_filter, entity_config, safe_mode
self,
hass,
name,
port,
ip_address,
entity_filter,
entity_config,
safe_mode,
advertise_ip=None,
):
"""Initialize a HomeKit object."""
self.hass = hass
@ -275,6 +295,7 @@ class HomeKit:
self._filter = entity_filter
self._config = entity_config
self._safe_mode = safe_mode
self._advertise_ip = advertise_ip
self.status = STATUS_READY
self.bridge = None
@ -289,7 +310,11 @@ class HomeKit:
ip_addr = self._ip_address or get_local_ip()
path = self.hass.config.path(HOMEKIT_FILE)
self.driver = HomeDriver(
self.hass, address=ip_addr, port=self._port, persist_file=path
self.hass,
address=ip_addr,
port=self._port,
persist_file=path,
advertised_address=self._advertise_ip,
)
self.bridge = HomeBridge(self.hass, self.driver, self._name)
if self._safe_mode:

View File

@ -10,6 +10,7 @@ ATTR_DISPLAY_NAME = "display_name"
ATTR_VALUE = "value"
# #### Config ####
CONF_ADVERTISE_IP = "advertise_ip"
CONF_AUTO_START = "auto_start"
CONF_ENTITY_CONFIG = "entity_config"
CONF_FEATURE = "feature"

View File

@ -69,7 +69,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
hass, BRIDGE_NAME, DEFAULT_PORT, None, ANY, {}, DEFAULT_SAFE_MODE, None
)
assert mock_homekit().setup.called is True
@ -98,7 +98,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
hass, "Test Name", 11111, "172.0.0.0", ANY, {}, DEFAULT_SAFE_MODE, None
)
assert mock_homekit().setup.called is True
@ -136,7 +136,11 @@ async def test_homekit_setup(hass, hk_driver):
path = hass.config.path(HOMEKIT_FILE)
assert isinstance(homekit.bridge, HomeBridge)
mock_driver.assert_called_with(
hass, address=IP_ADDRESS, port=DEFAULT_PORT, persist_file=path
hass,
address=IP_ADDRESS,
port=DEFAULT_PORT,
persist_file=path,
advertised_address=None,
)
assert homekit.driver.safe_mode is False
@ -153,7 +157,30 @@ async def test_homekit_setup_ip_address(hass, hk_driver):
) as mock_driver:
await hass.async_add_job(homekit.setup)
mock_driver.assert_called_with(
hass, address="172.0.0.0", port=DEFAULT_PORT, persist_file=ANY
hass,
address="172.0.0.0",
port=DEFAULT_PORT,
persist_file=ANY,
advertised_address=None,
)
async def test_homekit_setup_advertise_ip(hass, hk_driver):
"""Test setup with given IP address to advertise."""
homekit = HomeKit(
hass, BRIDGE_NAME, DEFAULT_PORT, "0.0.0.0", {}, {}, None, "192.168.1.100"
)
with patch(
PATH_HOMEKIT + ".accessories.HomeDriver", return_value=hk_driver
) as mock_driver:
await hass.async_add_job(homekit.setup)
mock_driver.assert_called_with(
hass,
address="0.0.0.0",
port=DEFAULT_PORT,
persist_file=ANY,
advertised_address="192.168.1.100",
)