Skip poll of HKC accessory if reachable and chars are watchable (#116200)
parent
0927dd9090
commit
e4ef4b81ba
|
@ -845,6 +845,20 @@ 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."""
|
||||||
|
if (
|
||||||
|
len(self.entity_map.accessories) == 1
|
||||||
|
and self.available
|
||||||
|
and not (self.pollable_characteristics - self.watchable_characteristics)
|
||||||
|
and self.pairing.is_available
|
||||||
|
and await self.pairing.controller.async_reachable(
|
||||||
|
self.unique_id, timeout=5.0
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# If its a single accessory and all chars are watchable,
|
||||||
|
# we don't need to poll.
|
||||||
|
_LOGGER.debug("Accessory is reachable, skip polling: %s", self.unique_id)
|
||||||
|
return
|
||||||
|
|
||||||
if not self.pollable_characteristics:
|
if not self.pollable_characteristics:
|
||||||
self.async_update_available_state()
|
self.async_update_available_state()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
|
|
@ -5,7 +5,7 @@ from unittest import mock
|
||||||
|
|
||||||
from aiohomekit.exceptions import AccessoryDisconnectedError, EncryptionError
|
from aiohomekit.exceptions import AccessoryDisconnectedError, EncryptionError
|
||||||
from aiohomekit.model import CharacteristicsTypes, ServicesTypes
|
from aiohomekit.model import CharacteristicsTypes, ServicesTypes
|
||||||
from aiohomekit.testing import FakePairing
|
from aiohomekit.testing import FakeController, FakePairing
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.homekit_controller.connection import (
|
from homeassistant.components.homekit_controller.connection import (
|
||||||
|
@ -48,7 +48,14 @@ async def test_recover_from_failure(hass: HomeAssistant, failure_cls) -> None:
|
||||||
|
|
||||||
# Test that entity remains in the same state if there is a network error
|
# Test that entity remains in the same state if there is a network error
|
||||||
next_update = dt_util.utcnow() + timedelta(seconds=60)
|
next_update = dt_util.utcnow() + timedelta(seconds=60)
|
||||||
with mock.patch.object(FakePairing, "get_characteristics") as get_char:
|
with (
|
||||||
|
mock.patch.object(FakePairing, "get_characteristics") as get_char,
|
||||||
|
mock.patch.object(
|
||||||
|
FakeController,
|
||||||
|
"async_reachable",
|
||||||
|
return_value=False,
|
||||||
|
),
|
||||||
|
):
|
||||||
get_char.side_effect = failure_cls("Disconnected")
|
get_char.side_effect = failure_cls("Disconnected")
|
||||||
|
|
||||||
# Test that a poll triggers unavailable
|
# Test that a poll triggers unavailable
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
"""Tests for HKDevice."""
|
"""Tests for HKDevice."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
from aiohomekit.controller import TransportType
|
from aiohomekit.controller import TransportType
|
||||||
|
from aiohomekit.model.characteristics import CharacteristicsTypes
|
||||||
|
from aiohomekit.model.services import ServicesTypes
|
||||||
|
from aiohomekit.testing import FakeController
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.homekit_controller.const import (
|
from homeassistant.components.homekit_controller.const import (
|
||||||
|
@ -12,11 +17,17 @@ from homeassistant.components.homekit_controller.const import (
|
||||||
IDENTIFIER_LEGACY_SERIAL_NUMBER,
|
IDENTIFIER_LEGACY_SERIAL_NUMBER,
|
||||||
)
|
)
|
||||||
from homeassistant.components.thread import async_add_dataset, dataset_store
|
from homeassistant.components.thread import async_add_dataset, dataset_store
|
||||||
|
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from .common import setup_accessories_from_file, setup_platform, setup_test_accessories
|
from .common import (
|
||||||
|
setup_accessories_from_file,
|
||||||
|
setup_platform,
|
||||||
|
setup_test_accessories,
|
||||||
|
setup_test_component,
|
||||||
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
@ -331,3 +342,56 @@ 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(
|
||||||
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
|
) -> None:
|
||||||
|
"""Test that we skip polling if available and all chars are watchable accessory mode."""
|
||||||
|
|
||||||
|
def _create_accessory(accessory):
|
||||||
|
service = accessory.add_service(ServicesTypes.LIGHTBULB, name="TestDevice")
|
||||||
|
|
||||||
|
on_char = service.add_char(CharacteristicsTypes.ON)
|
||||||
|
on_char.value = 0
|
||||||
|
|
||||||
|
brightness = service.add_char(CharacteristicsTypes.BRIGHTNESS)
|
||||||
|
brightness.value = 0
|
||||||
|
|
||||||
|
return service
|
||||||
|
|
||||||
|
helper = await setup_test_component(hass, get_next_aid(), _create_accessory)
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
helper.pairing,
|
||||||
|
"get_characteristics",
|
||||||
|
wraps=helper.pairing.get_characteristics,
|
||||||
|
) as mock_get_characteristics:
|
||||||
|
# Initial state is that the light is off
|
||||||
|
state = await helper.poll_and_get_state()
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
assert mock_get_characteristics.call_count == 0
|
||||||
|
|
||||||
|
# Test device goes offline
|
||||||
|
helper.pairing.available = False
|
||||||
|
with mock.patch.object(
|
||||||
|
FakeController,
|
||||||
|
"async_reachable",
|
||||||
|
return_value=False,
|
||||||
|
):
|
||||||
|
state = await helper.poll_and_get_state()
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
# Tries twice before declaring unavailable
|
||||||
|
assert mock_get_characteristics.call_count == 2
|
||||||
|
|
||||||
|
# Test device comes back online
|
||||||
|
helper.pairing.available = True
|
||||||
|
state = await helper.poll_and_get_state()
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
assert mock_get_characteristics.call_count == 3
|
||||||
|
|
||||||
|
# Next poll should not happen because its a single
|
||||||
|
# accessory, available, and all chars are watchable
|
||||||
|
state = await helper.poll_and_get_state()
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
assert mock_get_characteristics.call_count == 3
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
"""Basic checks for HomeKitSwitch."""
|
"""Basic checks for HomeKitSwitch."""
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
from aiohomekit.model.characteristics import CharacteristicsTypes
|
from aiohomekit.model.characteristics import CharacteristicsTypes
|
||||||
from aiohomekit.model.services import ServicesTypes
|
from aiohomekit.model.services import ServicesTypes
|
||||||
|
from aiohomekit.testing import FakeController
|
||||||
|
|
||||||
from homeassistant.components.homekit_controller.const import KNOWN_DEVICES
|
from homeassistant.components.homekit_controller.const import KNOWN_DEVICES
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
|
@ -372,7 +374,12 @@ async def test_light_becomes_unavailable_but_recovers(
|
||||||
|
|
||||||
# Test device goes offline
|
# Test device goes offline
|
||||||
helper.pairing.available = False
|
helper.pairing.available = False
|
||||||
state = await helper.poll_and_get_state()
|
with mock.patch.object(
|
||||||
|
FakeController,
|
||||||
|
"async_reachable",
|
||||||
|
return_value=False,
|
||||||
|
):
|
||||||
|
state = await helper.poll_and_get_state()
|
||||||
assert state.state == "unavailable"
|
assert state.state == "unavailable"
|
||||||
|
|
||||||
# Simulate that someone switched on the device in the real world not via HA
|
# Simulate that someone switched on the device in the real world not via HA
|
||||||
|
|
Loading…
Reference in New Issue