Only poll HomeKit connection once for all entities on a single bridge/pairing (#25249)

* Stub for polling from a central location

* Allow connection to know the entity objects attached to it

* Move polling logic to connection

* Don't poll if no characteristics selected

* Loosen coupling between entity and HKDevice

* Disable track_time_interval when removing entry

* Revert self.entities changes

* Use @callback for async_state_changed

* Split out unload and remove and add a test

* Test that entity is gone and fix docstring
This commit is contained in:
Jc2k 2019-07-22 17:22:44 +01:00 committed by Paulus Schoutsen
parent 58f946e452
commit 8c69fd91ff
8 changed files with 266 additions and 62 deletions

View file

@ -14,7 +14,7 @@ async def test_aqara_gateway_setup(hass):
"""Test that a Aqara Gateway can be correctly setup in HA."""
accessories = await setup_accessories_from_file(
hass, 'aqara_gateway.json')
pairing = await setup_test_accessories(hass, accessories)
config_entry, pairing = await setup_test_accessories(hass, accessories)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
@ -24,7 +24,9 @@ async def test_aqara_gateway_setup(hass):
assert alarm.unique_id == 'homekit-0000000123456789-66304'
alarm_helper = Helper(
hass, 'alarm_control_panel.aqara_hub_1563', pairing, accessories[0])
hass, 'alarm_control_panel.aqara_hub_1563', pairing, accessories[0],
config_entry
)
alarm_state = await alarm_helper.poll_and_get_state()
assert alarm_state.attributes['friendly_name'] == 'Aqara Hub-1563'
@ -33,7 +35,7 @@ async def test_aqara_gateway_setup(hass):
assert light.unique_id == 'homekit-0000000123456789-65792'
light_helper = Helper(
hass, 'light.aqara_hub_1563', pairing, accessories[0])
hass, 'light.aqara_hub_1563', pairing, accessories[0], config_entry)
light_state = await light_helper.poll_and_get_state()
assert light_state.attributes['friendly_name'] == 'Aqara Hub-1563'
assert light_state.attributes['supported_features'] == (

View file

@ -7,30 +7,31 @@ https://github.com/home-assistant/home-assistant/issues/15336
from unittest import mock
from homekit import AccessoryDisconnectedError
import pytest
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY)
from tests.components.homekit_controller.common import (
FakePairing, device_config_changed, setup_accessories_from_file,
setup_test_accessories, Helper
setup_test_accessories, Helper, time_changed
)
async def test_ecobee3_setup(hass):
"""Test that a Ecbobee 3 can be correctly setup in HA."""
accessories = await setup_accessories_from_file(hass, 'ecobee3.json')
pairing = await setup_test_accessories(hass, accessories)
config_entry, pairing = await setup_test_accessories(hass, accessories)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
climate = entity_registry.async_get('climate.homew')
assert climate.unique_id == 'homekit-123456789012-16'
climate_helper = Helper(hass, 'climate.homew', pairing, accessories[0])
climate_helper = Helper(
hass, 'climate.homew', pairing, accessories[0], config_entry
)
climate_state = await climate_helper.poll_and_get_state()
assert climate_state.attributes['friendly_name'] == 'HomeW'
assert climate_state.attributes['supported_features'] == (
@ -53,7 +54,7 @@ async def test_ecobee3_setup(hass):
assert occ1.unique_id == 'homekit-AB1C-56'
occ1_helper = Helper(
hass, 'binary_sensor.kitchen', pairing, accessories[0])
hass, 'binary_sensor.kitchen', pairing, accessories[0], config_entry)
occ1_state = await occ1_helper.poll_and_get_state()
assert occ1_state.attributes['friendly_name'] == 'Kitchen'
@ -131,8 +132,8 @@ async def test_ecobee3_setup_connection_failure(hass):
# If there is no cached entity map and the accessory connection is
# failing then we have to fail the config entry setup.
with pytest.raises(ConfigEntryNotReady):
await setup_test_accessories(hass, accessories)
config_entry, pairing = await setup_test_accessories(hass, accessories)
assert config_entry.state == ENTRY_STATE_SETUP_RETRY
climate = entity_registry.async_get('climate.homew')
assert climate is None
@ -140,7 +141,16 @@ async def test_ecobee3_setup_connection_failure(hass):
# When accessory raises ConfigEntryNoteReady HA will retry - lets make
# sure there is no cruft causing conflicts left behind by now doing
# a successful setup.
await setup_test_accessories(hass, accessories)
# We just advance time by 5 minutes so that the retry happens, rather
# than manually invoking async_setup_entry - this means we need to
# make sure the IpPairing mock is in place or we'll try to connect to
# a real device. Normally this mocking is done by the helper in
# setup_test_accessories.
pairing_cls_loc = 'homekit.controller.ip_implementation.IpPairing'
with mock.patch(pairing_cls_loc) as pairing_cls:
pairing_cls.return_value = pairing
await time_changed(hass, 5 * 60)
climate = entity_registry.async_get('climate.homew')
assert climate.unique_id == 'homekit-123456789012-16'

View file

@ -19,7 +19,7 @@ LIGHT_ON = ('lightbulb', 'on')
async def test_koogeek_ls1_setup(hass):
"""Test that a Koogeek LS1 can be correctly setup in HA."""
accessories = await setup_accessories_from_file(hass, 'koogeek_ls1.json')
pairing = await setup_test_accessories(hass, accessories)
config_entry, pairing = await setup_test_accessories(hass, accessories)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
@ -27,7 +27,13 @@ async def test_koogeek_ls1_setup(hass):
entry = entity_registry.async_get('light.koogeek_ls1_20833f')
assert entry.unique_id == 'homekit-AAAA011111111111-7'
helper = Helper(hass, 'light.koogeek_ls1_20833f', pairing, accessories[0])
helper = Helper(
hass,
'light.koogeek_ls1_20833f',
pairing,
accessories[0],
config_entry
)
state = await helper.poll_and_get_state()
# Assert that the friendly name is detected correctly
@ -58,9 +64,11 @@ async def test_recover_from_failure(hass, utcnow, failure_cls):
See https://github.com/home-assistant/home-assistant/issues/18949
"""
accessories = await setup_accessories_from_file(hass, 'koogeek_ls1.json')
pairing = await setup_test_accessories(hass, accessories)
config_entry, pairing = await setup_test_accessories(hass, accessories)
helper = Helper(hass, 'light.koogeek_ls1_20833f', pairing, accessories[0])
helper = Helper(
hass, 'light.koogeek_ls1_20833f', pairing, accessories[0], config_entry
)
# Set light state on fake device to off
helper.characteristics[LIGHT_ON].set_value(False)
@ -80,7 +88,8 @@ async def test_recover_from_failure(hass, utcnow, failure_cls):
state = await helper.poll_and_get_state()
assert state.state == 'off'
get_char.assert_called_with([(1, 8), (1, 9), (1, 10), (1, 11)])
chars = get_char.call_args[0][0]
assert set(chars) == {(1, 8), (1, 9), (1, 10), (1, 11)}
# Test that entity changes state when network error goes away
next_update += timedelta(seconds=60)

View file

@ -14,14 +14,16 @@ from tests.components.homekit_controller.common import (
async def test_lennox_e30_setup(hass):
"""Test that a Lennox E30 can be correctly setup in HA."""
accessories = await setup_accessories_from_file(hass, 'lennox_e30.json')
pairing = await setup_test_accessories(hass, accessories)
config_entry, pairing = await setup_test_accessories(hass, accessories)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
climate = entity_registry.async_get('climate.lennox')
assert climate.unique_id == 'homekit-XXXXXXXX-100'
climate_helper = Helper(hass, 'climate.lennox', pairing, accessories[0])
climate_helper = Helper(
hass, 'climate.lennox', pairing, accessories[0], config_entry
)
climate_state = await climate_helper.poll_and_get_state()
assert climate_state.attributes['friendly_name'] == 'Lennox'
assert climate_state.attributes['supported_features'] == (