mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
Ensure HomeKit connection is kept alive for devices that timeout too quickly (#123601)
This commit is contained in:
parent
f6e82ae0ba
commit
b20623447e
@ -845,21 +845,41 @@ class HKDevice:
|
|||||||
|
|
||||||
async def async_update(self, now: datetime | None = None) -> None:
|
async def async_update(self, now: datetime | None = None) -> None:
|
||||||
"""Poll state of all entities attached to this bridge/accessory."""
|
"""Poll state of all entities attached to this bridge/accessory."""
|
||||||
|
to_poll = self.pollable_characteristics
|
||||||
|
accessories = self.entity_map.accessories
|
||||||
|
|
||||||
if (
|
if (
|
||||||
len(self.entity_map.accessories) == 1
|
len(accessories) == 1
|
||||||
and self.available
|
and self.available
|
||||||
and not (self.pollable_characteristics - self.watchable_characteristics)
|
and not (to_poll - self.watchable_characteristics)
|
||||||
and self.pairing.is_available
|
and self.pairing.is_available
|
||||||
and await self.pairing.controller.async_reachable(
|
and await self.pairing.controller.async_reachable(
|
||||||
self.unique_id, timeout=5.0
|
self.unique_id, timeout=5.0
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
# If its a single accessory and all chars are watchable,
|
# If its a single accessory and all chars are watchable,
|
||||||
# we don't need to poll.
|
# only poll the firmware version to keep the connection alive
|
||||||
_LOGGER.debug("Accessory is reachable, skip polling: %s", self.unique_id)
|
# https://github.com/home-assistant/core/issues/123412
|
||||||
return
|
#
|
||||||
|
# Firmware revision is used here since iOS does this to keep camera
|
||||||
|
# connections alive, and the goal is to not regress
|
||||||
|
# https://github.com/home-assistant/core/issues/116143
|
||||||
|
# by polling characteristics that are not normally polled frequently
|
||||||
|
# and may not be tested by the device vendor.
|
||||||
|
#
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Accessory is reachable, limiting poll to firmware version: %s",
|
||||||
|
self.unique_id,
|
||||||
|
)
|
||||||
|
first_accessory = accessories[0]
|
||||||
|
accessory_info = first_accessory.services.first(
|
||||||
|
service_type=ServicesTypes.ACCESSORY_INFORMATION
|
||||||
|
)
|
||||||
|
assert accessory_info is not None
|
||||||
|
firmware_iid = accessory_info[CharacteristicsTypes.FIRMWARE_REVISION].iid
|
||||||
|
to_poll = {(first_accessory.aid, firmware_iid)}
|
||||||
|
|
||||||
if not self.pollable_characteristics:
|
if not to_poll:
|
||||||
self.async_update_available_state()
|
self.async_update_available_state()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"HomeKit connection not polling any characteristics: %s", self.unique_id
|
"HomeKit connection not polling any characteristics: %s", self.unique_id
|
||||||
@ -892,9 +912,7 @@ class HKDevice:
|
|||||||
_LOGGER.debug("Starting HomeKit device update: %s", self.unique_id)
|
_LOGGER.debug("Starting HomeKit device update: %s", self.unique_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_values_dict = await self.get_characteristics(
|
new_values_dict = await self.get_characteristics(to_poll)
|
||||||
self.pollable_characteristics
|
|
||||||
)
|
|
||||||
except AccessoryNotFoundError:
|
except AccessoryNotFoundError:
|
||||||
# Not only did the connection fail, but also the accessory is not
|
# Not only did the connection fail, but also the accessory is not
|
||||||
# visible on the network.
|
# visible on the network.
|
||||||
|
@ -344,10 +344,10 @@ async def test_thread_provision_migration_failed(hass: HomeAssistant) -> None:
|
|||||||
assert config_entry.data["Connection"] == "BLE"
|
assert config_entry.data["Connection"] == "BLE"
|
||||||
|
|
||||||
|
|
||||||
async def test_skip_polling_all_watchable_accessory_mode(
|
async def test_poll_firmware_version_only_all_watchable_accessory_mode(
|
||||||
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that we skip polling if available and all chars are watchable accessory mode."""
|
"""Test that we only poll firmware if available and all chars are watchable accessory mode."""
|
||||||
|
|
||||||
def _create_accessory(accessory):
|
def _create_accessory(accessory):
|
||||||
service = accessory.add_service(ServicesTypes.LIGHTBULB, name="TestDevice")
|
service = accessory.add_service(ServicesTypes.LIGHTBULB, name="TestDevice")
|
||||||
@ -370,7 +370,10 @@ async def test_skip_polling_all_watchable_accessory_mode(
|
|||||||
# Initial state is that the light is off
|
# Initial state is that the light is off
|
||||||
state = await helper.poll_and_get_state()
|
state = await helper.poll_and_get_state()
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
assert mock_get_characteristics.call_count == 0
|
assert mock_get_characteristics.call_count == 2
|
||||||
|
# Verify only firmware version is polled
|
||||||
|
assert mock_get_characteristics.call_args_list[0][0][0] == {(1, 7)}
|
||||||
|
assert mock_get_characteristics.call_args_list[1][0][0] == {(1, 7)}
|
||||||
|
|
||||||
# Test device goes offline
|
# Test device goes offline
|
||||||
helper.pairing.available = False
|
helper.pairing.available = False
|
||||||
@ -382,16 +385,16 @@ async def test_skip_polling_all_watchable_accessory_mode(
|
|||||||
state = await helper.poll_and_get_state()
|
state = await helper.poll_and_get_state()
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
# Tries twice before declaring unavailable
|
# Tries twice before declaring unavailable
|
||||||
assert mock_get_characteristics.call_count == 2
|
assert mock_get_characteristics.call_count == 4
|
||||||
|
|
||||||
# Test device comes back online
|
# Test device comes back online
|
||||||
helper.pairing.available = True
|
helper.pairing.available = True
|
||||||
state = await helper.poll_and_get_state()
|
state = await helper.poll_and_get_state()
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
assert mock_get_characteristics.call_count == 3
|
assert mock_get_characteristics.call_count == 6
|
||||||
|
|
||||||
# Next poll should not happen because its a single
|
# Next poll should not happen because its a single
|
||||||
# accessory, available, and all chars are watchable
|
# accessory, available, and all chars are watchable
|
||||||
state = await helper.poll_and_get_state()
|
state = await helper.poll_and_get_state()
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
assert mock_get_characteristics.call_count == 3
|
assert mock_get_characteristics.call_count == 8
|
||||||
|
Loading…
x
Reference in New Issue
Block a user