Automatically create HomeKit accessory mode entries (#46473)

When we set up HomeKit, we asked users if they wanted
to create an entry in bridge or accessory mode.

This approach required the user to understand how HomeKit works and
choose which type to create.

When the user includes the media player or camera domains,
we exclude them from the bridge and create the additional entries
for each entity in accessory mode.
This commit is contained in:
J. Nick Koston 2021-02-23 18:22:23 -06:00 committed by GitHub
parent 9159f54900
commit 87cbbcb014
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 472 additions and 428 deletions

View file

@ -82,6 +82,22 @@ def entity_reg_fixture(hass):
return mock_registry(hass)
def _mock_homekit(hass, entry, homekit_mode, entity_filter=None):
return HomeKit(
hass=hass,
name=BRIDGE_NAME,
port=DEFAULT_PORT,
ip_address=None,
entity_filter=entity_filter or generate_filter([], [], [], []),
exclude_accessory_mode=False,
entity_config={},
homekit_mode=homekit_mode,
advertise_ip=None,
entry_id=entry.entry_id,
entry_title=entry.title,
)
async def test_setup_min(hass, mock_zeroconf):
"""Test async_setup with min config options."""
entry = MockConfigEntry(
@ -103,10 +119,12 @@ async def test_setup_min(hass, mock_zeroconf):
DEFAULT_PORT,
None,
ANY,
ANY,
{},
HOMEKIT_MODE_BRIDGE,
None,
entry.entry_id,
entry.title,
)
assert mock_homekit().setup.called is True
@ -139,10 +157,12 @@ async def test_setup_auto_start_disabled(hass, mock_zeroconf):
11111,
"172.0.0.0",
ANY,
ANY,
{},
HOMEKIT_MODE_BRIDGE,
None,
entry.entry_id,
entry.title,
)
assert mock_homekit().setup.called is True
@ -184,11 +204,13 @@ async def test_homekit_setup(hass, hk_driver, mock_zeroconf):
BRIDGE_NAME,
DEFAULT_PORT,
None,
True,
{},
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
entry_title=entry.title,
)
hass.states.async_set("light.demo", "on")
@ -205,6 +227,7 @@ async def test_homekit_setup(hass, hk_driver, mock_zeroconf):
hass,
entry.entry_id,
BRIDGE_NAME,
entry.title,
loop=hass.loop,
address=IP_ADDRESS,
port=DEFAULT_PORT,
@ -230,11 +253,13 @@ async def test_homekit_setup_ip_address(hass, hk_driver, mock_zeroconf):
BRIDGE_NAME,
DEFAULT_PORT,
"172.0.0.0",
True,
{},
{},
HOMEKIT_MODE_BRIDGE,
None,
entry_id=entry.entry_id,
entry_title=entry.title,
)
mock_zeroconf = MagicMock()
@ -245,6 +270,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver, mock_zeroconf):
hass,
entry.entry_id,
BRIDGE_NAME,
entry.title,
loop=hass.loop,
address="172.0.0.0",
port=DEFAULT_PORT,
@ -266,11 +292,13 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf):
BRIDGE_NAME,
DEFAULT_PORT,
"0.0.0.0",
True,
{},
{},
HOMEKIT_MODE_BRIDGE,
"192.168.1.100",
entry_id=entry.entry_id,
entry_title=entry.title,
)
zeroconf_instance = MagicMock()
@ -281,6 +309,7 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf):
hass,
entry.entry_id,
BRIDGE_NAME,
entry.title,
loop=hass.loop,
address="0.0.0.0",
port=DEFAULT_PORT,
@ -292,40 +321,40 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf):
async def test_homekit_add_accessory(hass, mock_zeroconf):
"""Add accessory if config exists and get_acc returns an accessory."""
entry = await async_init_integration(hass)
homekit = HomeKit(
hass,
None,
None,
None,
lambda entity_id: True,
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
entry = MockConfigEntry(
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
)
entry.add_to_hass(hass)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = "driver"
homekit.bridge = mock_bridge = Mock()
homekit.bridge.accessories = range(10)
homekit.async_start = AsyncMock()
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
mock_acc = Mock(category="any")
await async_init_integration(hass)
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
mock_get_acc.side_effect = [None, mock_acc, None]
homekit.add_bridge_accessory(State("light.demo", "on"))
mock_get_acc.assert_called_with(hass, "driver", ANY, 1403373688, {})
state = State("light.demo", "on")
homekit.add_bridge_accessory(state)
mock_get_acc.assert_called_with(hass, ANY, ANY, 1403373688, {})
assert not mock_bridge.add_accessory.called
homekit.add_bridge_accessory(State("demo.test", "on"))
mock_get_acc.assert_called_with(hass, "driver", ANY, 600325356, {})
state = State("demo.test", "on")
homekit.add_bridge_accessory(state)
mock_get_acc.assert_called_with(hass, ANY, ANY, 600325356, {})
assert mock_bridge.add_accessory.called
homekit.add_bridge_accessory(State("demo.test_2", "on"))
mock_get_acc.assert_called_with(hass, "driver", ANY, 1467253281, {})
mock_bridge.add_accessory.assert_called_with(mock_acc)
state = State("demo.test_2", "on")
homekit.add_bridge_accessory(state)
mock_get_acc.assert_called_with(hass, ANY, ANY, 1467253281, {})
assert mock_bridge.add_accessory.called
@pytest.mark.parametrize("acc_category", [CATEGORY_TELEVISION, CATEGORY_CAMERA])
@ -333,37 +362,30 @@ async def test_homekit_warn_add_accessory_bridge(
hass, acc_category, mock_zeroconf, caplog
):
"""Test we warn when adding cameras or tvs to a bridge."""
entry = await async_init_integration(hass)
homekit = HomeKit(
hass,
None,
None,
None,
lambda entity_id: True,
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
entry = MockConfigEntry(
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
)
entry.add_to_hass(hass)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = "driver"
homekit.bridge = mock_bridge = Mock()
homekit.bridge.accessories = range(10)
homekit.async_start = AsyncMock()
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
mock_camera_acc = Mock(category=acc_category)
await async_init_integration(hass)
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
mock_get_acc.side_effect = [None, mock_camera_acc, None]
homekit.add_bridge_accessory(State("light.demo", "on"))
mock_get_acc.assert_called_with(hass, "driver", ANY, 1403373688, {})
state = State("camera.test", "on")
homekit.add_bridge_accessory(state)
mock_get_acc.assert_called_with(hass, ANY, ANY, 1508819236, {})
assert not mock_bridge.add_accessory.called
homekit.add_bridge_accessory(State("camera.test", "on"))
mock_get_acc.assert_called_with(hass, "driver", ANY, 1508819236, {})
assert mock_bridge.add_accessory.called
assert "accessory mode" in caplog.text
@ -371,17 +393,8 @@ async def test_homekit_remove_accessory(hass, mock_zeroconf):
"""Remove accessory from bridge."""
entry = await async_init_integration(hass)
homekit = HomeKit(
hass,
None,
None,
None,
lambda entity_id: True,
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = "driver"
homekit.bridge = mock_bridge = Mock()
mock_bridge.accessories = {"light.demo": "acc"}
@ -396,17 +409,8 @@ async def test_homekit_entity_filter(hass, mock_zeroconf):
entry = await async_init_integration(hass)
entity_filter = generate_filter(["cover"], ["demo.test"], [], [])
homekit = HomeKit(
hass,
None,
None,
None,
entity_filter,
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE, entity_filter)
homekit.bridge = Mock()
homekit.bridge.accessories = {}
@ -432,17 +436,8 @@ async def test_homekit_entity_glob_filter(hass, mock_zeroconf):
entity_filter = generate_filter(
["cover"], ["demo.test"], [], [], ["*.included_*"], ["*.excluded_*"]
)
homekit = HomeKit(
hass,
None,
None,
None,
entity_filter,
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE, entity_filter)
homekit.bridge = Mock()
homekit.bridge.accessories = {}
@ -471,17 +466,8 @@ async def test_homekit_start(hass, hk_driver, device_reg):
entry = await async_init_integration(hass)
pin = b"123-45-678"
homekit = HomeKit(
hass,
None,
None,
None,
{},
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.bridge = Mock()
homekit.bridge.accessories = []
homekit.driver = hk_driver
@ -513,7 +499,9 @@ async def test_homekit_start(hass, hk_driver, device_reg):
await hass.async_block_till_done()
mock_add_acc.assert_any_call(state)
mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY)
mock_setup_msg.assert_called_with(
hass, entry.entry_id, "Mock Title (any)", pin, ANY
)
hk_driver_add_acc.assert_called_with(homekit.bridge)
assert hk_driver_start.called
assert homekit.status == STATUS_RUNNING
@ -563,17 +551,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc
entity_filter = generate_filter(["cover", "light"], ["demo.test"], [], [])
await async_init_entry(hass, entry)
homekit = HomeKit(
hass,
None,
None,
None,
entity_filter,
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE, entity_filter)
homekit.bridge = Mock()
homekit.bridge.accessories = []
@ -593,7 +571,9 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc
await homekit.async_start()
await hass.async_block_till_done()
mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY)
mock_setup_msg.assert_called_with(
hass, entry.entry_id, "Mock Title (any)", pin, ANY
)
hk_driver_add_acc.assert_called_with(homekit.bridge)
assert hk_driver_start.called
assert homekit.status == STATUS_RUNNING
@ -608,18 +588,8 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc
async def test_homekit_stop(hass):
"""Test HomeKit stop method."""
entry = await async_init_integration(hass)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit = HomeKit(
hass,
None,
None,
None,
{},
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit.driver = Mock()
homekit.driver.async_stop = AsyncMock()
homekit.bridge = Mock()
@ -649,17 +619,8 @@ async def test_homekit_reset_accessories(hass, mock_zeroconf):
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
)
entity_id = "light.demo"
homekit = HomeKit(
hass,
None,
None,
None,
{},
{entity_id: {}},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.bridge = Mock()
homekit.bridge.accessories = {}
@ -697,17 +658,7 @@ async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroco
entity_filter = generate_filter(["cover", "light"], ["demo.test"], [], [])
homekit = HomeKit(
hass,
None,
None,
None,
entity_filter,
{},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE, entity_filter)
def _mock_bridge(*_):
mock_bridge = HomeBridge(hass, hk_driver, "mock_bridge")
@ -738,17 +689,8 @@ async def test_homekit_finds_linked_batteries(
"""Test HomeKit start method."""
entry = await async_init_integration(hass)
homekit = HomeKit(
hass,
None,
None,
None,
{},
{"light.demo": {}},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
@ -792,9 +734,6 @@ async def test_homekit_finds_linked_batteries(
)
hass.states.async_set(light.entity_id, STATE_ON)
def _mock_get_accessory(*args, **kwargs):
return [None, "acc", None]
with patch.object(homekit.bridge, "add_accessory"), patch(
f"{PATH_HOMEKIT}.show_setup_message"
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
@ -823,18 +762,8 @@ async def test_homekit_async_get_integration_fails(
):
"""Test that we continue if async_get_integration fails."""
entry = await async_init_integration(hass)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit = HomeKit(
hass,
None,
None,
None,
{},
{"light.demo": {}},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
@ -877,9 +806,6 @@ async def test_homekit_async_get_integration_fails(
)
hass.states.async_set(light.entity_id, STATE_ON)
def _mock_get_accessory(*args, **kwargs):
return [None, "acc", None]
with patch.object(homekit.bridge, "add_accessory"), patch(
f"{PATH_HOMEKIT}.show_setup_message"
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
@ -927,10 +853,12 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf):
12345,
None,
ANY,
ANY,
{},
HOMEKIT_MODE_BRIDGE,
None,
entry.entry_id,
entry.title,
)
assert mock_homekit().setup.called is True
@ -989,18 +917,8 @@ async def test_homekit_ignored_missing_devices(
):
"""Test HomeKit handles a device in the entity registry but missing from the device registry."""
entry = await async_init_integration(hass)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit = HomeKit(
hass,
None,
None,
None,
{},
{"light.demo": {}},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
@ -1041,9 +959,6 @@ async def test_homekit_ignored_missing_devices(
hass.states.async_set(light.entity_id, STATE_ON)
hass.states.async_set("light.two", STATE_ON)
def _mock_get_accessory(*args, **kwargs):
return [None, "acc", None]
with patch.object(homekit.bridge, "add_accessory"), patch(
f"{PATH_HOMEKIT}.show_setup_message"
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
@ -1071,17 +986,8 @@ async def test_homekit_finds_linked_motion_sensors(
"""Test HomeKit start method."""
entry = await async_init_integration(hass)
homekit = HomeKit(
hass,
None,
None,
None,
{},
{"camera.camera_demo": {}},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
@ -1115,9 +1021,6 @@ async def test_homekit_finds_linked_motion_sensors(
)
hass.states.async_set(camera.entity_id, STATE_ON)
def _mock_get_accessory(*args, **kwargs):
return [None, "acc", None]
with patch.object(homekit.bridge, "add_accessory"), patch(
f"{PATH_HOMEKIT}.show_setup_message"
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
@ -1146,17 +1049,8 @@ async def test_homekit_finds_linked_humidity_sensors(
"""Test HomeKit start method."""
entry = await async_init_integration(hass)
homekit = HomeKit(
hass,
None,
None,
None,
{},
{"humidifier.humidifier": {}},
HOMEKIT_MODE_BRIDGE,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = hk_driver
homekit._filter = Mock(return_value=True)
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
@ -1192,9 +1086,6 @@ async def test_homekit_finds_linked_humidity_sensors(
)
hass.states.async_set(humidifier.entity_id, STATE_ON)
def _mock_get_accessory(*args, **kwargs):
return [None, "acc", None]
with patch.object(homekit.bridge, "add_accessory"), patch(
f"{PATH_HOMEKIT}.show_setup_message"
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
@ -1241,10 +1132,12 @@ async def test_reload(hass, mock_zeroconf):
12345,
None,
ANY,
False,
{},
HOMEKIT_MODE_BRIDGE,
None,
entry.entry_id,
entry.title,
)
assert mock_homekit().setup.called is True
yaml_path = os.path.join(
@ -1277,10 +1170,12 @@ async def test_reload(hass, mock_zeroconf):
45678,
None,
ANY,
False,
{},
HOMEKIT_MODE_BRIDGE,
None,
entry.entry_id,
entry.title,
)
assert mock_homekit2().setup.called is True
@ -1294,17 +1189,9 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg):
entry = await async_init_integration(hass)
pin = b"123-45-678"
homekit = HomeKit(
hass,
None,
None,
None,
{},
{},
HOMEKIT_MODE_ACCESSORY,
advertise_ip=None,
entry_id=entry.entry_id,
)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY)
homekit.bridge = Mock()
homekit.bridge.accessories = []
homekit.driver = hk_driver
@ -1323,6 +1210,8 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg):
await hass.async_block_till_done()
mock_add_acc.assert_not_called()
mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY)
mock_setup_msg.assert_called_with(
hass, entry.entry_id, "Mock Title (any)", pin, ANY
)
assert hk_driver_start.called
assert homekit.status == STATUS_RUNNING